|
7 | 7 | package rand
|
8 | 8 |
|
9 | 9 | import (
|
| 10 | + "errors" |
10 | 11 | "internal/syscall/unix"
|
| 12 | + "math" |
11 | 13 | "runtime"
|
12 | 14 | "syscall"
|
13 | 15 | )
|
14 | 16 |
|
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 |
31 | 29 |
|
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 |
43 | 39 | }
|
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:] |
46 | 57 | }
|
47 | 58 | return nil
|
48 | 59 | }
|
0 commit comments