mirror of https://github.com/golang/go.git
[release-branch.go1.18] [release-branch.go1.18 crypto/rand: properly handle large Read on windows
Use the batched reader to chunk large Read calls on windows to a max of
1 << 31 - 1 bytes. This prevents an infinite loop when trying to read
more than 1 << 32 -1 bytes, due to how RtlGenRandom works.
This change moves the batched function from rand_unix.go to rand.go,
since it is now needed for both windows and unix implementations.
Updates #52561
Fixes #52933
Fixes CVE-2022-30634
Change-Id: Id98fc4b1427e5cb2132762a445b2aed646a37473
Reviewed-on: https://go-review.googlesource.com/c/go/+/402257
Run-TryBot: Roland Shoemaker <roland@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Filippo Valsorda <valsorda@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
(cherry picked from commit bb1f441618)
Reviewed-on: https://go-review.googlesource.com/c/go/+/406634
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
parent
04337a6d10
commit
32dedaa69e
|
|
@ -24,3 +24,21 @@ var Reader io.Reader
|
|||
func Read(b []byte) (n int, err error) {
|
||||
return io.ReadFull(Reader, b)
|
||||
}
|
||||
|
||||
// batched returns a function that calls f to populate a []byte by chunking it
|
||||
// into subslices of, at most, readMax bytes.
|
||||
func batched(f func([]byte) error, readMax int) func([]byte) error {
|
||||
return func(out []byte) error {
|
||||
for len(out) > 0 {
|
||||
read := len(out)
|
||||
if read > readMax {
|
||||
read = readMax
|
||||
}
|
||||
if err := f(out[:read]); err != nil {
|
||||
return err
|
||||
}
|
||||
out = out[read:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
package rand
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"internal/syscall/unix"
|
||||
)
|
||||
|
||||
|
|
@ -15,20 +16,6 @@ func init() {
|
|||
altGetRandom = batched(getRandomBatch, maxGetRandomRead)
|
||||
}
|
||||
|
||||
// batched returns a function that calls f to populate a []byte by chunking it
|
||||
// into subslices of, at most, readMax bytes.
|
||||
func batched(f func([]byte) bool, readMax int) func([]byte) bool {
|
||||
return func(buf []byte) bool {
|
||||
for len(buf) > readMax {
|
||||
if !f(buf[:readMax]) {
|
||||
return false
|
||||
}
|
||||
buf = buf[readMax:]
|
||||
}
|
||||
return len(buf) == 0 || f(buf)
|
||||
}
|
||||
}
|
||||
|
||||
// If the kernel is too old to support the getrandom syscall(),
|
||||
// unix.GetRandom will immediately return ENOSYS and we will then fall back to
|
||||
// reading from /dev/urandom in rand_unix.go. unix.GetRandom caches the ENOSYS
|
||||
|
|
@ -36,7 +23,10 @@ func batched(f func([]byte) bool, readMax int) func([]byte) bool {
|
|||
// If the kernel supports the getrandom() syscall, unix.GetRandom will block
|
||||
// until the kernel has sufficient randomness (as we don't use GRND_NONBLOCK).
|
||||
// In this case, unix.GetRandom will not return an error.
|
||||
func getRandomBatch(p []byte) (ok bool) {
|
||||
func getRandomBatch(p []byte) (err error) {
|
||||
n, err := unix.GetRandom(p, 0)
|
||||
return n == len(p) && err == nil
|
||||
if n != len(p) {
|
||||
return errors.New("short read")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,20 +8,21 @@ package rand
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBatched(t *testing.T) {
|
||||
fillBatched := batched(func(p []byte) bool {
|
||||
fillBatched := batched(func(p []byte) error {
|
||||
for i := range p {
|
||||
p[i] = byte(i)
|
||||
}
|
||||
return true
|
||||
return nil
|
||||
}, 5)
|
||||
|
||||
p := make([]byte, 13)
|
||||
if !fillBatched(p) {
|
||||
t.Fatal("batched function returned false")
|
||||
if err := fillBatched(p); err != nil {
|
||||
t.Fatalf("batched function returned error: %s", err)
|
||||
}
|
||||
expected := []byte{0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2}
|
||||
if !bytes.Equal(expected, p) {
|
||||
|
|
@ -30,15 +31,15 @@ func TestBatched(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestBatchedError(t *testing.T) {
|
||||
b := batched(func(p []byte) bool { return false }, 5)
|
||||
if b(make([]byte, 13)) {
|
||||
t.Fatal("batched function should have returned false")
|
||||
b := batched(func(p []byte) error { return errors.New("") }, 5)
|
||||
if b(make([]byte, 13)) == nil {
|
||||
t.Fatal("batched function should have returned error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchedEmpty(t *testing.T) {
|
||||
b := batched(func(p []byte) bool { return false }, 5)
|
||||
if !b(make([]byte, 0)) {
|
||||
t.Fatal("empty slice should always return true")
|
||||
b := batched(func(p []byte) error { return errors.New("") }, 5)
|
||||
if err := b(make([]byte, 0)); err != nil {
|
||||
t.Fatalf("empty slice should always return nil: %s", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ func init() {
|
|||
altGetRandom = getEntropy
|
||||
}
|
||||
|
||||
func getEntropy(p []byte) (ok bool) {
|
||||
func getEntropy(p []byte) error {
|
||||
// getentropy(2) returns a maximum of 256 bytes per call
|
||||
for i := 0; i < len(p); i += 256 {
|
||||
end := i + 256
|
||||
|
|
@ -23,8 +23,8 @@ func getEntropy(p []byte) (ok bool) {
|
|||
}
|
||||
err := unix.GetEntropy(p[i:end])
|
||||
if err != nil {
|
||||
return false
|
||||
return err
|
||||
}
|
||||
}
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ type devReader struct {
|
|||
|
||||
// altGetRandom if non-nil specifies an OS-specific function to get
|
||||
// urandom-style randomness.
|
||||
var altGetRandom func([]byte) (ok bool)
|
||||
var altGetRandom func([]byte) (err error)
|
||||
|
||||
func warnBlocked() {
|
||||
println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel")
|
||||
|
|
@ -58,7 +58,7 @@ func (r *devReader) Read(b []byte) (n int, err error) {
|
|||
t := time.AfterFunc(60*time.Second, warnBlocked)
|
||||
defer t.Stop()
|
||||
}
|
||||
if altGetRandom != nil && r.name == urandomDevice && altGetRandom(b) {
|
||||
if altGetRandom != nil && r.name == urandomDevice && altGetRandom(b) == nil {
|
||||
return len(b), nil
|
||||
}
|
||||
r.mu.Lock()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ package rand
|
|||
|
||||
import (
|
||||
"internal/syscall/windows"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() { Reader = &rngReader{} }
|
||||
|
|
@ -17,16 +16,11 @@ func init() { Reader = &rngReader{} }
|
|||
type rngReader struct{}
|
||||
|
||||
func (r *rngReader) Read(b []byte) (n int, err error) {
|
||||
// RtlGenRandom only accepts 2**32-1 bytes at a time, so truncate.
|
||||
inputLen := uint32(len(b))
|
||||
|
||||
if inputLen == 0 {
|
||||
return 0, nil
|
||||
// RtlGenRandom only returns 1<<32-1 bytes at a time. We only read at
|
||||
// most 1<<31-1 bytes at a time so that this works the same on 32-bit
|
||||
// and 64-bit systems.
|
||||
if err := batched(windows.RtlGenRandom, 1<<31-1)(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = windows.RtlGenRandom(b)
|
||||
if err != nil {
|
||||
return 0, os.NewSyscallError("RtlGenRandom", err)
|
||||
}
|
||||
return int(inputLen), nil
|
||||
return len(b), nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue