Skip to content

Commit a62566f

Browse files
committed
crypto/rand: remove /dev/urandom fallback and improve getrandom batching
The fallback was reachable on - Linux, where starting in Go 1.24 we require a kernel with getrandom(2), see #67001. - FreeBSD, which added getrandom(2) in FreeBSD 12.0, which we require since Go 1.19. - OpenBSD, which added getentropy(2) in OpenBSD 5.6, and we only support the latest version. - DragonFly BSD, which has getrandom(2) and where we support only the latest version. - NetBSD, where we switched to kern.arandom in CL 511036, available since NetBSD 4.0. - illumos, which has getrandom(2). (Supported versions unclear.) - Solaris, which had getrandom(2) at least since Oracle Solaris 11.4. - AIX, which... ugh, fine, but that code is now in rand_aix.go. At the end of the day the platform-specific code is just a global func(b []byte) error, so simplified the package around that assumption. This also includes the following change, which used to be a separate CL. crypto/rand: improve getrandom batching and retry logic The previous logic assumed getrandom never returned short, and then applied stricter-than-necessary batch size limits, presumably to avoid short returns. This was still not sufficient because above 256 bytes getrandom(2) can be interrupted by a signal and return short *or* it can simply return EINTR if the pool is not initialized (regardless of buffer size). https://man.archlinux.org/man/getrandom.2#Interruption_by_a_signal_handler Whether this ever failed in practice is unknown: it would have been masked by the /dev/urandom fallback before. Instead, we apply buffer size limits only where necessary (really, only Solaris in practice and FreeBSD in theory) and then handle gracefully short returns and EINTR. Change-Id: I8677b457aab68a8fb6137a3b43538efc62eb7c93 It turns out that we now know that large getrandom calls *did* fail in practice, falling back on /dev/urandom, because when we removed the fallback TestBidiStreamReverseProxy with its 4KiB read started failing. https://cr-buildbucket.appspot.com/build/8740779846954406033 For #66821 Change-Id: Iaca62997604f326501a51401cdc2659c2790ff22 Reviewed-on: https://go-review.googlesource.com/c/go/+/602495 Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: David Chase <[email protected]> Reviewed-by: Daniel McCarney <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 2f50798 commit a62566f

12 files changed

+259
-214
lines changed

src/crypto/rand/rand.go

+41-7
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,55 @@
66
// random number generator.
77
package rand
88

9-
import "io"
9+
import (
10+
"crypto/internal/boring"
11+
"io"
12+
"sync/atomic"
13+
"time"
14+
)
1015

1116
// Reader is a global, shared instance of a cryptographically
12-
// secure random number generator.
17+
// secure random number generator. It is safe for concurrent use.
1318
//
14-
// - On Linux, FreeBSD, Dragonfly, and Solaris, Reader uses getrandom(2)
15-
// if available, and /dev/urandom otherwise.
19+
// - On Linux, FreeBSD, Dragonfly, and Solaris, Reader uses getrandom(2).
1620
// - On macOS and iOS, Reader uses arc4random_buf(3).
17-
// - On OpenBSD and NetBSD, Reader uses getentropy(2).
18-
// - On other Unix-like systems, Reader reads from /dev/urandom.
21+
// - On OpenBSD, Reader uses getentropy(2).
22+
// - On NetBSD, Reader uses the kern.arandom sysctl.
1923
// - On Windows, Reader uses the ProcessPrng API.
2024
// - On js/wasm, Reader uses the Web Crypto API.
21-
// - On wasip1/wasm, Reader uses random_get from wasi_snapshot_preview1.
25+
// - On wasip1/wasm, Reader uses random_get.
2226
var Reader io.Reader
2327

28+
func init() {
29+
if boring.Enabled {
30+
Reader = boring.RandReader
31+
return
32+
}
33+
Reader = &reader{}
34+
}
35+
36+
var firstUse atomic.Bool
37+
38+
func warnBlocked() {
39+
println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel")
40+
}
41+
42+
type reader struct{}
43+
44+
func (r *reader) Read(b []byte) (n int, err error) {
45+
boring.Unreachable()
46+
if firstUse.CompareAndSwap(false, true) {
47+
// First use of randomness. Start timer to warn about
48+
// being blocked on entropy not being available.
49+
t := time.AfterFunc(time.Minute, warnBlocked)
50+
defer t.Stop()
51+
}
52+
if err := read(b); err != nil {
53+
return 0, err
54+
}
55+
return len(b), nil
56+
}
57+
2458
// Read is a helper function that calls Reader.Read using io.ReadFull.
2559
// On return, n == len(b) if and only if err == nil.
2660
func Read(b []byte) (n int, err error) {

src/crypto/rand/rand_aix.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2010 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package rand
6+
7+
import (
8+
"errors"
9+
"io"
10+
"os"
11+
"sync"
12+
"sync/atomic"
13+
"syscall"
14+
)
15+
16+
const urandomDevice = "/dev/urandom"
17+
18+
var (
19+
f io.Reader
20+
mu sync.Mutex
21+
used atomic.Bool
22+
)
23+
24+
func read(b []byte) error {
25+
if !used.Load() {
26+
mu.Lock()
27+
if !used.Load() {
28+
dev, err := os.Open(urandomDevice)
29+
if err != nil {
30+
mu.Unlock()
31+
return err
32+
}
33+
f = hideAgainReader{dev}
34+
used.Store(true)
35+
}
36+
mu.Unlock()
37+
}
38+
if _, err := io.ReadFull(f, b); err != nil {
39+
return err
40+
}
41+
return nil
42+
}
43+
44+
// hideAgainReader masks EAGAIN reads from /dev/urandom.
45+
// See golang.org/issue/9205.
46+
type hideAgainReader struct {
47+
r io.Reader
48+
}
49+
50+
func (hr hideAgainReader) Read(p []byte) (n int, err error) {
51+
n, err = hr.r.Read(p)
52+
if errors.Is(err, syscall.EAGAIN) {
53+
err = nil
54+
}
55+
return
56+
}

src/crypto/rand/rand_darwin.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ package rand
66

77
import "internal/syscall/unix"
88

9-
func init() {
10-
// arc4random_buf is the recommended application CSPRNG, accepts buffers of
11-
// any size, and never returns an error.
12-
//
13-
// "The subsystem is re-seeded from the kernel random number subsystem on a
14-
// regular basis, and also upon fork(2)." - arc4random(3)
15-
//
16-
// Note that despite its legacy name, it uses a secure CSPRNG (not RC4) in
17-
// all supported macOS versions.
18-
altGetRandom = func(b []byte) error { unix.ARC4Random(b); return nil }
9+
// arc4random_buf is the recommended application CSPRNG, accepts buffers of
10+
// any size, and never returns an error.
11+
//
12+
// "The subsystem is re-seeded from the kernel random number subsystem on a
13+
// regular basis, and also upon fork(2)." - arc4random(3)
14+
//
15+
// Note that despite its legacy name, it uses a secure CSPRNG (not RC4) in
16+
// all supported macOS versions.
17+
func read(b []byte) error {
18+
unix.ARC4Random(b)
19+
return nil
1920
}

src/crypto/rand/rand_getentropy.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,5 @@ package rand
88

99
import "internal/syscall/unix"
1010

11-
func init() {
12-
// getentropy(2) returns a maximum of 256 bytes per call.
13-
altGetRandom = batched(unix.GetEntropy, 256)
14-
}
11+
// getentropy(2) returns a maximum of 256 bytes per call.
12+
var read = batched(unix.GetEntropy, 256)

src/crypto/rand/rand_getrandom.go

+40-29
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,53 @@
77
package rand
88

99
import (
10+
"errors"
1011
"internal/syscall/unix"
12+
"math"
1113
"runtime"
1214
"syscall"
1315
)
1416

15-
func init() {
16-
var maxGetRandomRead int
17-
switch runtime.GOOS {
18-
case "linux", "android":
19-
// Per the manpage:
20-
// When reading from the urandom source, a maximum of 33554431 bytes
21-
// is returned by a single call to getrandom() on systems where int
22-
// has a size of 32 bits.
23-
maxGetRandomRead = (1 << 25) - 1
24-
case "dragonfly", "freebsd", "illumos", "solaris":
25-
maxGetRandomRead = 1 << 8
26-
default:
27-
panic("no maximum specified for GetRandom")
28-
}
29-
altGetRandom = batched(getRandom, maxGetRandomRead)
30-
}
17+
func read(b []byte) error {
18+
// Linux, DragonFly, and illumos don't have a limit on the buffer size.
19+
// FreeBSD has a limit of IOSIZE_MAX, which seems to be either INT_MAX or
20+
// SSIZE_MAX. 2^31-1 is a safe and high enough value to use for all of them.
21+
//
22+
// Note that Linux returns "a maximum of 32Mi-1 bytes", but that will only
23+
// result in a short read, not an error. Short reads can also happen above
24+
// 256 bytes due to signals. Reads up to 256 bytes are guaranteed not to
25+
// return short (and not to return an error IF THE POOL IS INITIALIZED) on
26+
// at least Linux, FreeBSD, DragonFly, and Oracle Solaris, but we don't make
27+
// use of that.
28+
maxSize := math.MaxInt32
3129

32-
// If the kernel is too old to support the getrandom syscall(),
33-
// unix.GetRandom will immediately return ENOSYS and we will then fall back to
34-
// reading from /dev/urandom in rand_unix.go. unix.GetRandom caches the ENOSYS
35-
// result so we only suffer the syscall overhead once in this case.
36-
// If the kernel supports the getrandom() syscall, unix.GetRandom will block
37-
// until the kernel has sufficient randomness (as we don't use GRND_NONBLOCK).
38-
// In this case, unix.GetRandom will not return an error.
39-
func getRandom(p []byte) error {
40-
n, err := unix.GetRandom(p, 0)
41-
if err != nil {
42-
return err
30+
// Oracle Solaris has a limit of 133120 bytes. Very specific.
31+
//
32+
// The getrandom() and getentropy() functions fail if: [...]
33+
//
34+
// - bufsz is <= 0 or > 133120, when GRND_RANDOM is not set
35+
//
36+
// https://docs.oracle.com/cd/E88353_01/html/E37841/getrandom-2.html
37+
if runtime.GOOS == "solaris" {
38+
maxSize = 133120
4339
}
44-
if n != len(p) {
45-
return syscall.EIO
40+
41+
for len(b) > 0 {
42+
size := len(b)
43+
if size > maxSize {
44+
size = maxSize
45+
}
46+
n, err := unix.GetRandom(b[:size], 0)
47+
if errors.Is(err, syscall.EINTR) {
48+
// If getrandom(2) is blocking, either because it is waiting for the
49+
// entropy pool to become initialized or because we requested more
50+
// than 256 bytes, it might get interrupted by a signal.
51+
continue
52+
}
53+
if err != nil {
54+
return err
55+
}
56+
b = b[n:]
4657
}
4758
return nil
4859
}

src/crypto/rand/rand_js.go

+4-20
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
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 js && wasm
6-
75
package rand
86

97
import "syscall/js"
@@ -12,27 +10,13 @@ import "syscall/js"
1210
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#exceptions
1311
const maxGetRandomRead = 64 << 10
1412

15-
var batchedGetRandom func([]byte) error
16-
17-
func init() {
18-
Reader = &reader{}
19-
batchedGetRandom = batched(getRandom, maxGetRandomRead)
20-
}
21-
22-
var jsCrypto = js.Global().Get("crypto")
23-
var uint8Array = js.Global().Get("Uint8Array")
24-
25-
// reader implements a pseudorandom generator
13+
// read implements a pseudorandom generator
2614
// using JavaScript crypto.getRandomValues method.
2715
// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues.
28-
type reader struct{}
16+
var read = batched(getRandom, maxGetRandomRead)
2917

30-
func (r *reader) Read(b []byte) (int, error) {
31-
if err := batchedGetRandom(b); err != nil {
32-
return 0, err
33-
}
34-
return len(b), nil
35-
}
18+
var jsCrypto = js.Global().Get("crypto")
19+
var uint8Array = js.Global().Get("Uint8Array")
3620

3721
func getRandom(b []byte) error {
3822
a := uint8Array.New(len(b))

src/crypto/rand/rand_plan9.go

+19-27
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// Plan9 cryptographically secure pseudorandom number
6-
// generator.
7-
85
package rand
96

107
import (
@@ -18,44 +15,40 @@ import (
1815

1916
const randomDevice = "/dev/random"
2017

21-
func init() {
22-
Reader = &reader{}
23-
}
18+
// This is a pseudorandom generator that seeds itself by reading from
19+
// /dev/random. The read function always returns the full amount asked for, or
20+
// else it returns an error. The generator is a fast key erasure RNG.
2421

25-
// reader is a new pseudorandom generator that seeds itself by
26-
// reading from /dev/random. The Read method on the returned
27-
// reader always returns the full amount asked for, or else it
28-
// returns an error. The generator is a fast key erasure RNG.
29-
type reader struct {
22+
var (
3023
mu sync.Mutex
3124
seeded sync.Once
3225
seedErr error
3326
key [32]byte
34-
}
27+
)
3528

36-
func (r *reader) Read(b []byte) (n int, err error) {
37-
r.seeded.Do(func() {
29+
func read(b []byte) error {
30+
seeded.Do(func() {
3831
t := time.AfterFunc(time.Minute, func() {
3932
println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel")
4033
})
4134
defer t.Stop()
4235
entropy, err := os.Open(randomDevice)
4336
if err != nil {
44-
r.seedErr = err
37+
seedErr = err
4538
return
4639
}
4740
defer entropy.Close()
48-
_, r.seedErr = io.ReadFull(entropy, r.key[:])
41+
_, seedErr = io.ReadFull(entropy, key[:])
4942
})
50-
if r.seedErr != nil {
51-
return 0, r.seedErr
43+
if seedErr != nil {
44+
return seedErr
5245
}
5346

54-
r.mu.Lock()
55-
blockCipher, err := aes.NewCipher(r.key[:])
47+
mu.Lock()
48+
blockCipher, err := aes.NewCipher(key[:])
5649
if err != nil {
57-
r.mu.Unlock()
58-
return 0, err
50+
mu.Unlock()
51+
return err
5952
}
6053
var (
6154
counter uint64
@@ -68,13 +61,12 @@ func (r *reader) Read(b []byte) (n int, err error) {
6861
}
6962
byteorder.LePutUint64(block[:], counter)
7063
}
71-
blockCipher.Encrypt(r.key[:aes.BlockSize], block[:])
64+
blockCipher.Encrypt(key[:aes.BlockSize], block[:])
7265
inc()
73-
blockCipher.Encrypt(r.key[aes.BlockSize:], block[:])
66+
blockCipher.Encrypt(key[aes.BlockSize:], block[:])
7467
inc()
75-
r.mu.Unlock()
68+
mu.Unlock()
7669

77-
n = len(b)
7870
for len(b) >= aes.BlockSize {
7971
blockCipher.Encrypt(b[:aes.BlockSize], block[:])
8072
inc()
@@ -84,5 +76,5 @@ func (r *reader) Read(b []byte) (n int, err error) {
8476
blockCipher.Encrypt(block[:], block[:])
8577
copy(b, block[:])
8678
}
87-
return n, nil
79+
return nil
8880
}

0 commit comments

Comments
 (0)