aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoland Shoemaker <roland@golang.org>2022-04-25 19:02:35 -0700
committerAlex Rakoczy <alex@golang.org>2022-05-25 19:26:16 +0000
commit2be03d789de905a4b050ff5f3a51b724e1b09494 (patch)
treec7b85c81b9cf6aa0fa9c3de63ae5bb36b5919137
parent65701ad2b430466dd4bd6e1df107f81c0f8ee9cb (diff)
downloadgo-2be03d789de905a4b050ff5f3a51b724e1b09494.tar.gz
go-2be03d789de905a4b050ff5f3a51b724e1b09494.zip
[release-branch.go1.17] 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 #52932 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 bb1f4416180511231de6d17a1f2f55c82aafc863) Reviewed-on: https://go-review.googlesource.com/c/go/+/406635 Reviewed-by: Damien Neil <dneil@google.com>
-rw-r--r--src/crypto/rand/rand.go18
-rw-r--r--src/crypto/rand/rand_batched.go22
-rw-r--r--src/crypto/rand/rand_batched_test.go21
-rw-r--r--src/crypto/rand/rand_getentropy.go6
-rw-r--r--src/crypto/rand/rand_unix.go4
-rw-r--r--src/crypto/rand/rand_windows.go18
6 files changed, 46 insertions, 43 deletions
diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go
index fddd1147e6..f2c276008d 100644
--- a/src/crypto/rand/rand.go
+++ b/src/crypto/rand/rand.go
@@ -23,3 +23,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
+ }
+}
diff --git a/src/crypto/rand/rand_batched.go b/src/crypto/rand/rand_batched.go
index d7c5bf3562..8df715fdd1 100644
--- a/src/crypto/rand/rand_batched.go
+++ b/src/crypto/rand/rand_batched.go
@@ -8,6 +8,7 @@
package rand
import (
+ "errors"
"internal/syscall/unix"
)
@@ -16,20 +17,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
@@ -37,7 +24,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) error {
n, err := unix.GetRandom(p, 0)
- return n == len(p) && err == nil
+ if n != len(p) {
+ return errors.New("short read")
+ }
+ return err
}
diff --git a/src/crypto/rand/rand_batched_test.go b/src/crypto/rand/rand_batched_test.go
index 2d20922c82..b56345e50f 100644
--- a/src/crypto/rand/rand_batched_test.go
+++ b/src/crypto/rand/rand_batched_test.go
@@ -9,20 +9,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) {
@@ -31,15 +32,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 an 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)
}
}
diff --git a/src/crypto/rand/rand_getentropy.go b/src/crypto/rand/rand_getentropy.go
index dd725372ad..b1c19f3d0d 100644
--- a/src/crypto/rand/rand_getentropy.go
+++ b/src/crypto/rand/rand_getentropy.go
@@ -15,7 +15,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
@@ -24,8 +24,8 @@ func getEntropy(p []byte) (ok bool) {
}
err := unix.GetEntropy(p[i:end])
if err != nil {
- return false
+ return err
}
}
- return true
+ return nil
}
diff --git a/src/crypto/rand/rand_unix.go b/src/crypto/rand/rand_unix.go
index 81277eb6a5..3d11159a34 100644
--- a/src/crypto/rand/rand_unix.go
+++ b/src/crypto/rand/rand_unix.go
@@ -46,7 +46,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")
@@ -59,7 +59,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()
diff --git a/src/crypto/rand/rand_windows.go b/src/crypto/rand/rand_windows.go
index 7379f1489a..6c0655c72b 100644
--- a/src/crypto/rand/rand_windows.go
+++ b/src/crypto/rand/rand_windows.go
@@ -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
- }
-
- err = windows.RtlGenRandom(b)
- if err != nil {
- return 0, os.NewSyscallError("RtlGenRandom", err)
+ // 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
}
- return int(inputLen), nil
+ return len(b), nil
}