]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/rand: batch large calls to linux getrandom
authorMichael McLoughlin <mmcloughlin@gmail.com>
Sun, 16 Jul 2017 00:21:26 +0000 (18:21 -0600)
committerAdam Langley <agl@golang.org>
Wed, 9 Aug 2017 19:29:14 +0000 (19:29 +0000)
The linux getrandom system call returns at most 33554431 = 2^25-1 bytes per
call. The existing behavior for larger reads is to report a failure, because
there appears to have been an unexpected short read. In this case the system
falls back to reading from "/dev/urandom".

This change performs reads of 2^25 bytes or more with multiple calls to
getrandom.

Fixes #20877

Change-Id: I618855bdedafd86cd11219fe453af1d6fa2c88a7
Reviewed-on: https://go-review.googlesource.com/49170
Reviewed-by: Adam Langley <agl@golang.org>
Run-TryBot: Adam Langley <agl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

src/crypto/rand/rand_linux.go
src/crypto/rand/rand_linux_test.go [new file with mode: 0644]

index 8a4c757236387934ad520a12a3bbce366734faee..dbd038cc582413e3c80618eaaf229e1631e30734 100644 (file)
@@ -9,7 +9,30 @@ import (
 )
 
 func init() {
-       altGetRandom = getRandomLinux
+       altGetRandom = batched(getRandomLinux, maxGetRandomRead)
+}
+
+// maxGetRandomRead is the maximum number of bytes to ask for in one call to the
+// getrandom() syscall. In linux at most 2^25-1 bytes will be returned per call.
+// From the manpage
+//
+//     *  When reading from the urandom source, a maximum of 33554431 bytes
+//        is returned by a single call to getrandom() on systems where int
+//        has a size of 32 bits.
+const maxGetRandomRead = (1 << 25) - 1
+
+// 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 (before 3.17) to support the getrandom syscall(),
diff --git a/src/crypto/rand/rand_linux_test.go b/src/crypto/rand/rand_linux_test.go
new file mode 100644 (file)
index 0000000..77b7b6e
--- /dev/null
@@ -0,0 +1,42 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package rand
+
+import (
+       "bytes"
+       "testing"
+)
+
+func TestBatched(t *testing.T) {
+       fillBatched := batched(func(p []byte) bool {
+               for i := range p {
+                       p[i] = byte(i)
+               }
+               return true
+       }, 5)
+
+       p := make([]byte, 13)
+       if !fillBatched(p) {
+               t.Fatal("batched function returned false")
+       }
+       expected := []byte{0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2}
+       if !bytes.Equal(expected, p) {
+               t.Errorf("incorrect batch result: got %x, want %x", p, expected)
+       }
+}
+
+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")
+       }
+}
+
+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")
+       }
+}