diff --git a/src/crypto/rand/rand_batched_test.go b/src/crypto/rand/rand_batched_test.go index 7a981e7892..dfb9517d5e 100644 --- a/src/crypto/rand/rand_batched_test.go +++ b/src/crypto/rand/rand_batched_test.go @@ -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 diff --git a/src/crypto/rand/rand_unix.go b/src/crypto/rand/rand_unix.go index 0b137e131f..87ba9e3af7 100644 --- a/src/crypto/rand/rand_unix.go +++ b/src/crypto/rand/rand_unix.go @@ -10,11 +10,11 @@ package rand import ( - "bufio" "errors" "io" "os" "sync" + "sync/atomic" "syscall" "time" ) @@ -35,7 +35,7 @@ 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 @@ -43,24 +43,11 @@ type reader struct { 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 } @@ -69,22 +56,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 } } @@ -95,10 +66,7 @@ func warnBlocked() { func (r *reader) Read(b []byte) (n int, err error) { boring.Unreachable() - 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) @@ -107,14 +75,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.