Skip to content

Commit 3ae414c

Browse files
zx2c4gopherbot
authored andcommitted
crypto/rand: remove all buffering
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 <[email protected]> Run-TryBot: Jason Donenfeld <[email protected]> Auto-Submit: Jason Donenfeld <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent d68a8d0 commit 3ae414c

File tree

2 files changed

+19
-45
lines changed

2 files changed

+19
-45
lines changed

src/crypto/rand/rand_batched_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
5+
//go:build unix
66

77
package rand
88

src/crypto/rand/rand_unix.go

Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
package rand
1111

1212
import (
13-
"bufio"
1413
"errors"
1514
"io"
1615
"os"
1716
"sync"
17+
"sync/atomic"
1818
"syscall"
1919
"time"
2020
)
@@ -29,32 +29,19 @@ func init() {
2929
type reader struct {
3030
f io.Reader
3131
mu sync.Mutex
32-
used bool // whether this reader has been used
32+
used uint32 // Atomic: 0 - never used, 1 - used, but f == nil, 2 - used, and f != nil
3333
}
3434

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

3939
// batched returns a function that calls f to populate a []byte by chunking it
40-
// into subslices of, at most, readMax bytes, buffering min(readMax, 4096)
41-
// bytes at a time.
40+
// into subslices of, at most, readMax bytes.
4241
func batched(f func([]byte) error, readMax int) func([]byte) bool {
43-
bufferSize := 4096
44-
if bufferSize > readMax {
45-
bufferSize = readMax
46-
}
47-
fullBuffer := make([]byte, bufferSize)
48-
var buf []byte
4942
return func(out []byte) bool {
50-
// First we copy any amount remaining in the buffer.
51-
n := copy(out, buf)
52-
out, buf = out[n:], buf[n:]
53-
54-
// Then, if we're requesting more than the buffer size,
55-
// generate directly into the output, chunked by readMax.
56-
for len(out) >= len(fullBuffer) {
57-
read := len(out) - (len(out) % len(fullBuffer))
43+
for len(out) > 0 {
44+
read := len(out)
5845
if read > readMax {
5946
read = readMax
6047
}
@@ -63,22 +50,6 @@ func batched(f func([]byte) error, readMax int) func([]byte) bool {
6350
}
6451
out = out[read:]
6552
}
66-
67-
// If there's a partial block left over, fill the buffer,
68-
// and copy in the remainder.
69-
if len(out) > 0 {
70-
if f(fullBuffer[:]) != nil {
71-
return false
72-
}
73-
buf = fullBuffer[:]
74-
n = copy(out, buf)
75-
out, buf = out[n:], buf[n:]
76-
}
77-
78-
if len(out) > 0 {
79-
panic("crypto/rand batching failed to fill buffer")
80-
}
81-
8253
return true
8354
}
8455
}
@@ -88,10 +59,7 @@ func warnBlocked() {
8859
}
8960

9061
func (r *reader) Read(b []byte) (n int, err error) {
91-
r.mu.Lock()
92-
defer r.mu.Unlock()
93-
if !r.used {
94-
r.used = true
62+
if atomic.CompareAndSwapUint32(&r.used, 0, 1) {
9563
// First use of randomness. Start timer to warn about
9664
// being blocked on entropy not being available.
9765
t := time.AfterFunc(time.Minute, warnBlocked)
@@ -100,14 +68,20 @@ func (r *reader) Read(b []byte) (n int, err error) {
10068
if altGetRandom != nil && altGetRandom(b) {
10169
return len(b), nil
10270
}
103-
if r.f == nil {
104-
f, err := os.Open(urandomDevice)
105-
if err != nil {
106-
return 0, err
71+
if atomic.LoadUint32(&r.used) != 2 {
72+
r.mu.Lock()
73+
if r.used != 2 {
74+
f, err := os.Open(urandomDevice)
75+
if err != nil {
76+
r.mu.Unlock()
77+
return 0, err
78+
}
79+
r.f = hideAgainReader{f}
80+
atomic.StoreUint32(&r.used, 2)
10781
}
108-
r.f = bufio.NewReader(hideAgainReader{f})
82+
r.mu.Unlock()
10983
}
110-
return r.f.Read(b)
84+
return io.ReadFull(r.f, b)
11185
}
11286

11387
// hideAgainReader masks EAGAIN reads from /dev/urandom.

0 commit comments

Comments
 (0)