]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.17] crypto/rand: properly handle large Read on windows
authorRoland Shoemaker <roland@golang.org>
Tue, 26 Apr 2022 02:02:35 +0000 (19:02 -0700)
committerAlex Rakoczy <alex@golang.org>
Wed, 25 May 2022 19:26:16 +0000 (19:26 +0000)
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>
src/crypto/rand/rand.go
src/crypto/rand/rand_batched.go
src/crypto/rand/rand_batched_test.go
src/crypto/rand/rand_getentropy.go
src/crypto/rand/rand_unix.go
src/crypto/rand/rand_windows.go

index fddd1147e6e3c692b3f2d4da69f427a10e8eb6fb..f2c276008d787c021f33021ce429e1ab8a5ecdff 100644 (file)
@@ -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
+       }
+}
index d7c5bf3562d54747f0ab5db368f90a219dae964c..8df715fdd1474bfbe50f03a604d183fc388a1436 100644 (file)
@@ -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
 }
index 2d20922c8253098f59eac79fe913c28ff9631cf1..b56345e50f1592c2e64858b581a26567425aadb1 100644 (file)
@@ -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)
        }
 }
index dd725372ad918a85d533b3dc30e213d49616bcd7..b1c19f3d0da6f60cf856159e5fe45e6afa4d6407 100644 (file)
@@ -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
 }
index 81277eb6a5dea924896a1fa5f87aab0315f360a2..3d11159a3404ca305c4ff838d6dd85e4e5623534 100644 (file)
@@ -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()
index 7379f1489adbf13bb41598f56c745b9e03fccd6d..6c0655c72b692ae444a85025ecc3d27d65107404 100644 (file)
@@ -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
 }