1
0
mirror of https://github.com/golang/go synced 2024-11-25 22:37:59 -07:00

crypto/rand: move OS interaction to crypto/internal/sysrand

We're going to use that package as the passive entropy source for the
FIPS module, and we need to import it from a package that will be
imported by crypto/rand.

Since there is no overridable Reader now, introduced a mechanism to test
the otherwise impossible failure of the OS entropy source.

For #69536

Change-Id: I558687ed1ec896dba05b99b937970bb809de3fe7
Reviewed-on: https://go-review.googlesource.com/c/go/+/624976
Reviewed-by: Daniel McCarney <daniel@binaryparadox.net>
Reviewed-by: Roland Shoemaker <roland@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
This commit is contained in:
Filippo Valsorda 2024-11-04 12:56:04 +01:00 committed by Gopher Robot
parent f705cf8f96
commit 644628536f
17 changed files with 245 additions and 84 deletions

View File

@ -0,0 +1,77 @@
// Copyright 2010 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 provides cryptographically secure random bytes from the
// operating system.
package sysrand
import (
"os"
"sync"
"sync/atomic"
"time"
_ "unsafe"
)
var firstUse atomic.Bool
func warnBlocked() {
println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel")
}
// fatal is [runtime.fatal], pushed via linkname.
//
//go:linkname fatal
func fatal(string)
var testingOnlyFailRead bool
// Read fills b with cryptographically secure random bytes from the operating
// system. It always fills b entirely and crashes the program irrecoverably if
// an error is encountered. The operating system APIs are documented to never
// return an error on all but legacy Linux systems.
func Read(b []byte) {
if firstUse.CompareAndSwap(false, true) {
// First use of randomness. Start timer to warn about
// being blocked on entropy not being available.
t := time.AfterFunc(time.Minute, warnBlocked)
defer t.Stop()
}
if err := read(b); err != nil || testingOnlyFailRead {
var errStr string
if !testingOnlyFailRead {
errStr = err.Error()
} else {
errStr = "testing simulated failure"
}
fatal("crypto/rand: failed to read random data (see https://go.dev/issue/66821): " + errStr)
panic("unreachable") // To be sure.
}
}
// The urandom fallback is only used on Linux kernels before 3.17 and on AIX.
var urandomOnce sync.Once
var urandomFile *os.File
var urandomErr error
func urandomRead(b []byte) error {
urandomOnce.Do(func() {
urandomFile, urandomErr = os.Open("/dev/urandom")
})
if urandomErr != nil {
return urandomErr
}
for len(b) > 0 {
n, err := urandomFile.Read(b)
// Note that we don't ignore EAGAIN because it should not be possible to
// hit for a blocking read from urandom, although there were
// unreproducible reports of it at https://go.dev/issue/9205.
if err != nil {
return err
}
b = b[n:]
}
return nil
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rand
package sysrand
func read(b []byte) error {
return urandomRead(b)

View File

@ -4,7 +4,7 @@
//go:build darwin || openbsd
package rand
package sysrand
import "internal/syscall/unix"

View File

@ -4,7 +4,7 @@
//go:build dragonfly || freebsd || linux || solaris
package rand
package sysrand
import (
"errors"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rand
package sysrand
// The maximum buffer size for crypto.getRandomValues is 65536 bytes.
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#exceptions

View File

@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rand_test
package sysrand_test
import (
"bytes"
"crypto/rand/internal/seccomp"
"crypto/internal/sysrand/internal/seccomp"
"internal/syscall/unix"
"internal/testenv"
"os"
@ -33,7 +33,6 @@ func TestNoGetrandom(t *testing.T) {
t.Skip("skipping test in short mode")
}
testenv.MustHaveExec(t)
testenv.MustHaveCGO(t)
done := make(chan struct{})
go func() {

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rand
package sysrand
import "internal/syscall/unix"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rand
package sysrand
import (
"internal/byteorder"

View File

@ -0,0 +1,142 @@
// Copyright 2010 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 sysrand
import (
"bytes"
"compress/flate"
"internal/asan"
"internal/msan"
"internal/race"
"internal/testenv"
"os"
"runtime"
"sync"
"testing"
)
func TestRead(t *testing.T) {
// 40MiB, more than the documented maximum of 32Mi-1 on Linux 32-bit.
b := make([]byte, 40<<20)
Read(b)
if testing.Short() {
b = b[len(b)-100_000:]
}
var z bytes.Buffer
f, _ := flate.NewWriter(&z, 5)
f.Write(b)
f.Close()
if z.Len() < len(b)*99/100 {
t.Fatalf("Compressed %d -> %d", len(b), z.Len())
}
}
func TestReadByteValues(t *testing.T) {
b := make([]byte, 1)
v := make(map[byte]bool)
for {
Read(b)
v[b[0]] = true
if len(v) == 256 {
break
}
}
}
func TestReadEmpty(t *testing.T) {
Read(make([]byte, 0))
Read(nil)
}
func TestConcurrentRead(t *testing.T) {
if testing.Short() {
t.Skip("skipping in short mode")
}
const N = 100
const M = 1000
var wg sync.WaitGroup
wg.Add(N)
for i := 0; i < N; i++ {
go func() {
defer wg.Done()
for i := 0; i < M; i++ {
b := make([]byte, 32)
Read(b)
}
}()
}
wg.Wait()
}
var sink byte
func TestAllocations(t *testing.T) {
if race.Enabled || msan.Enabled || asan.Enabled {
t.Skip("urandomRead allocates under -race, -asan, and -msan")
}
if runtime.GOOS == "plan9" {
t.Skip("plan9 allocates")
}
testenv.SkipIfOptimizationOff(t)
n := int(testing.AllocsPerRun(10, func() {
buf := make([]byte, 32)
Read(buf)
sink ^= buf[0]
}))
if n > 0 {
t.Errorf("allocs = %d, want 0", n)
}
}
// TestNoUrandomFallback ensures the urandom fallback is not reached in
// normal operations.
func TestNoUrandomFallback(t *testing.T) {
expectFallback := false
if runtime.GOOS == "aix" {
// AIX always uses the urandom fallback.
expectFallback = true
}
if os.Getenv("GO_GETRANDOM_DISABLED") == "1" {
// We are testing the urandom fallback intentionally.
expectFallback = true
}
Read(make([]byte, 1))
if urandomFile != nil && !expectFallback {
t.Error("/dev/urandom fallback used unexpectedly")
t.Log("note: if this test fails, it may be because the system does not have getrandom(2)")
}
if urandomFile == nil && expectFallback {
t.Error("/dev/urandom fallback not used as expected")
}
}
func TestReadError(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
}
testenv.MustHaveExec(t)
// We run this test in a subprocess because it's expected to crash.
if os.Getenv("GO_TEST_READ_ERROR") == "1" {
testingOnlyFailRead = true
Read(make([]byte, 32))
t.Error("Read did not crash")
return
}
cmd := testenv.Command(t, os.Args[0], "-test.run=TestReadError")
cmd.Env = append(os.Environ(), "GO_TEST_READ_ERROR=1")
out, err := cmd.CombinedOutput()
if err == nil {
t.Error("subprocess succeeded unexpectedly")
}
exp := "fatal error: crypto/rand: failed to read random data"
if !bytes.Contains(out, []byte(exp)) {
t.Errorf("subprocess output does not contain %q: %s", exp, out)
}
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rand
package sysrand
import "syscall"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rand
package sysrand
import "internal/syscall/windows"

View File

@ -8,11 +8,8 @@ package rand
import (
"crypto/internal/boring"
"crypto/internal/sysrand"
"io"
"os"
"sync"
"sync/atomic"
"time"
_ "unsafe"
)
@ -36,26 +33,11 @@ func init() {
Reader = &reader{}
}
var firstUse atomic.Bool
func warnBlocked() {
println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel")
}
type reader struct{}
// Read always returns len(b) or an error.
func (r *reader) Read(b []byte) (n int, err error) {
boring.Unreachable()
if firstUse.CompareAndSwap(false, true) {
// First use of randomness. Start timer to warn about
// being blocked on entropy not being available.
t := time.AfterFunc(time.Minute, warnBlocked)
defer t.Stop()
}
if err := read(b); err != nil {
return 0, err
}
sysrand.Read(b)
return len(b), nil
}
@ -88,29 +70,3 @@ func Read(b []byte) (n int, err error) {
}
return len(b), nil
}
// The urandom fallback is only used on Linux kernels before 3.17 and on AIX.
var urandomOnce sync.Once
var urandomFile *os.File
var urandomErr error
func urandomRead(b []byte) error {
urandomOnce.Do(func() {
urandomFile, urandomErr = os.Open("/dev/urandom")
})
if urandomErr != nil {
return urandomErr
}
for len(b) > 0 {
n, err := urandomFile.Read(b)
// Note that we don't ignore EAGAIN because it should not be possible to
// hit for a blocking read from urandom, although there were
// unreproducible reports of it at https://go.dev/issue/9205.
if err != nil {
return err
}
b = b[n:]
}
return nil
}

View File

@ -20,6 +20,10 @@ import (
"testing"
)
// These tests are mostly duplicates of the tests in crypto/internal/sysrand,
// and testing both the Reader and Read is pretty redundant when one calls the
// other, but better safe than sorry.
func testReadAndReader(t *testing.T, f func(*testing.T, func([]byte) (int, error))) {
t.Run("Read", func(t *testing.T) {
f(t, Read)
@ -175,28 +179,6 @@ func TestAllocations(t *testing.T) {
}
}
// TestNoUrandomFallback ensures the urandom fallback is not reached in
// normal operations.
func TestNoUrandomFallback(t *testing.T) {
expectFallback := false
if runtime.GOOS == "aix" {
// AIX always uses the urandom fallback.
expectFallback = true
}
if os.Getenv("GO_GETRANDOM_DISABLED") == "1" {
// We are testing the urandom fallback intentionally.
expectFallback = true
}
Read(make([]byte, 1))
if urandomFile != nil && !expectFallback {
t.Error("/dev/urandom fallback used unexpectedly")
t.Log("note: if this test fails, it may be because the system does not have getrandom(2)")
}
if urandomFile == nil && expectFallback {
t.Error("/dev/urandom fallback not used as expected")
}
}
func TestReadError(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode")
@ -209,9 +191,8 @@ func TestReadError(t *testing.T) {
Reader = readerFunc(func([]byte) (int, error) {
return 0, errors.New("error")
})
if _, err := Read(make([]byte, 32)); err == nil {
t.Error("Read did not return error")
}
Read(make([]byte, 32))
t.Error("Read did not crash")
return
}

View File

@ -441,12 +441,13 @@ var depsRules = `
< net/mail;
STR < crypto/internal/impl;
OS < crypto/internal/sysrand;
# FIPS is the FIPS 140 module.
# It must not depend on external crypto packages.
# Internal packages imported by FIPS might need to retain
# backwards compatibility with older versions of the module.
STR, crypto/internal/impl
STR, crypto/internal/impl, crypto/internal/sysrand
< crypto/internal/fips
< crypto/internal/fips/alias
< crypto/internal/fips/subtle
@ -666,7 +667,7 @@ var depsRules = `
< crypto/internal/cryptotest;
CGO, FMT
< crypto/rand/internal/seccomp;
< crypto/internal/sysrand/internal/seccomp;
# v2 execution trace parser.
FMT

View File

@ -1038,6 +1038,11 @@ func rand_fatal(s string) {
fatal(s)
}
//go:linkname sysrand_fatal crypto/internal/sysrand.fatal
func sysrand_fatal(s string) {
fatal(s)
}
//go:linkname fips_fatal crypto/internal/fips.fatal
func fips_fatal(s string) {
fatal(s)