Skip to content

Commit

Permalink
crypto/rand: remove all buffering
Browse files Browse the repository at this point in the history
The kernel's RNG is fast enough, and buffering means taking locks, which
we don't want to do. So just remove all buffering. This also means the
randomness we get is "fresher". That also means we don't need any
locking, making this potentially faster if multiple cores are hitting
GetRandom() at the same time on newer Linuxes.

Also, change the build tag of the tests to be 'unix' instead of
enumerating them.

Change-Id: Ia773fab768270d2aa20c0649f4171c5326b71d02
Reviewed-on: https://go-review.googlesource.com/c/go/+/390038
Reviewed-by: Filippo Valsorda <valsorda@google.com>
Run-TryBot: Jason Donenfeld <Jason@zx2c4.com>
Auto-Submit: Jason Donenfeld <Jason@zx2c4.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
  • Loading branch information
zx2c4 authored and gopherbot committed Apr 19, 2022
1 parent d68a8d0 commit 3ae414c
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 45 deletions.
2 changes: 1 addition & 1 deletion src/crypto/rand/rand_batched_test.go
Expand Up @@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
//go:build unix

package rand

Expand Down
62 changes: 18 additions & 44 deletions src/crypto/rand/rand_unix.go
Expand Up @@ -10,11 +10,11 @@
package rand

import (
"bufio"
"errors"
"io"
"os"
"sync"
"sync/atomic"
"syscall"
"time"
)
Expand All @@ -29,32 +29,19 @@ func init() {
type reader struct {
f io.Reader
mu sync.Mutex
used bool // whether this reader has been used
used uint32 // Atomic: 0 - never used, 1 - used, but f == nil, 2 - used, and f != nil
}

// altGetRandom if non-nil specifies an OS-specific function to get
// urandom-style randomness.
var altGetRandom func([]byte) (ok bool)

// batched returns a function that calls f to populate a []byte by chunking it
// into subslices of, at most, readMax bytes, buffering min(readMax, 4096)
// bytes at a time.
// into subslices of, at most, readMax bytes.
func batched(f func([]byte) error, readMax int) func([]byte) bool {
bufferSize := 4096
if bufferSize > readMax {
bufferSize = readMax
}
fullBuffer := make([]byte, bufferSize)
var buf []byte
return func(out []byte) bool {
// First we copy any amount remaining in the buffer.
n := copy(out, buf)
out, buf = out[n:], buf[n:]

// Then, if we're requesting more than the buffer size,
// generate directly into the output, chunked by readMax.
for len(out) >= len(fullBuffer) {
read := len(out) - (len(out) % len(fullBuffer))
for len(out) > 0 {
read := len(out)
if read > readMax {
read = readMax
}
Expand All @@ -63,22 +50,6 @@ func batched(f func([]byte) error, readMax int) func([]byte) bool {
}
out = out[read:]
}

// If there's a partial block left over, fill the buffer,
// and copy in the remainder.
if len(out) > 0 {
if f(fullBuffer[:]) != nil {
return false
}
buf = fullBuffer[:]
n = copy(out, buf)
out, buf = out[n:], buf[n:]
}

if len(out) > 0 {
panic("crypto/rand batching failed to fill buffer")
}

return true
}
}
Expand All @@ -88,10 +59,7 @@ func warnBlocked() {
}

func (r *reader) Read(b []byte) (n int, err error) {
r.mu.Lock()
defer r.mu.Unlock()
if !r.used {
r.used = true
if atomic.CompareAndSwapUint32(&r.used, 0, 1) {
// First use of randomness. Start timer to warn about
// being blocked on entropy not being available.
t := time.AfterFunc(time.Minute, warnBlocked)
Expand All @@ -100,14 +68,20 @@ func (r *reader) Read(b []byte) (n int, err error) {
if altGetRandom != nil && altGetRandom(b) {
return len(b), nil
}
if r.f == nil {
f, err := os.Open(urandomDevice)
if err != nil {
return 0, err
if atomic.LoadUint32(&r.used) != 2 {
r.mu.Lock()
if r.used != 2 {
f, err := os.Open(urandomDevice)
if err != nil {
r.mu.Unlock()
return 0, err
}
r.f = hideAgainReader{f}
atomic.StoreUint32(&r.used, 2)
}
r.f = bufio.NewReader(hideAgainReader{f})
r.mu.Unlock()
}
return r.f.Read(b)
return io.ReadFull(r.f, b)
}

// hideAgainReader masks EAGAIN reads from /dev/urandom.
Expand Down

1 comment on commit 3ae414c

@rdp
Copy link

@rdp rdp commented on 3ae414c Oct 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this affect speed in OS X?

Please sign in to comment.