From d401c427b29f48d5cbc5092e62c20aa8524ce356 Mon Sep 17 00:00:00 2001 From: Michael McLoughlin Date: Sat, 15 Jul 2017 18:21:26 -0600 Subject: [PATCH] crypto/rand: batch large calls to linux getrandom 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 Run-TryBot: Adam Langley TryBot-Result: Gobot Gobot --- src/crypto/rand/rand_linux.go | 25 +++++++++++++++++- src/crypto/rand/rand_linux_test.go | 42 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 src/crypto/rand/rand_linux_test.go diff --git a/src/crypto/rand/rand_linux.go b/src/crypto/rand/rand_linux.go index 8a4c7572363..dbd038cc582 100644 --- a/src/crypto/rand/rand_linux.go +++ b/src/crypto/rand/rand_linux.go @@ -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 index 00000000000..77b7b6ebd7a --- /dev/null +++ b/src/crypto/rand/rand_linux_test.go @@ -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") + } +}