]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/rand: move OS interaction to crypto/internal/sysrand
authorFilippo Valsorda <filippo@golang.org>
Mon, 4 Nov 2024 11:56:04 +0000 (12:56 +0100)
committerGopher Robot <gobot@golang.org>
Tue, 19 Nov 2024 00:29:28 +0000 (00:29 +0000)
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>
17 files changed:
src/crypto/internal/sysrand/internal/seccomp/seccomp_linux.go [moved from src/crypto/rand/internal/seccomp/seccomp_linux.go with 100% similarity]
src/crypto/internal/sysrand/internal/seccomp/seccomp_unsupported.go [moved from src/crypto/rand/internal/seccomp/seccomp_unsupported.go with 100% similarity]
src/crypto/internal/sysrand/rand.go [new file with mode: 0644]
src/crypto/internal/sysrand/rand_aix.go [moved from src/crypto/rand/rand_aix.go with 93% similarity]
src/crypto/internal/sysrand/rand_arc4random.go [moved from src/crypto/rand/rand_arc4random.go with 97% similarity]
src/crypto/internal/sysrand/rand_getrandom.go [moved from src/crypto/rand/rand_getrandom.go with 99% similarity]
src/crypto/internal/sysrand/rand_js.go [moved from src/crypto/rand/rand_js.go with 97% similarity]
src/crypto/internal/sysrand/rand_linux_test.go [moved from src/crypto/rand/rand_linux_test.go with 95% similarity]
src/crypto/internal/sysrand/rand_netbsd.go [moved from src/crypto/rand/rand_netbsd.go with 97% similarity]
src/crypto/internal/sysrand/rand_plan9.go [moved from src/crypto/rand/rand_plan9.go with 98% similarity]
src/crypto/internal/sysrand/rand_test.go [new file with mode: 0644]
src/crypto/internal/sysrand/rand_wasip1.go [moved from src/crypto/rand/rand_wasip1.go with 97% similarity]
src/crypto/internal/sysrand/rand_windows.go [moved from src/crypto/rand/rand_windows.go with 94% similarity]
src/crypto/rand/rand.go
src/crypto/rand/rand_test.go
src/go/build/deps_test.go
src/runtime/panic.go

diff --git a/src/crypto/internal/sysrand/rand.go b/src/crypto/internal/sysrand/rand.go
new file mode 100644 (file)
index 0000000..034bf61
--- /dev/null
@@ -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
+}
similarity index 93%
rename from src/crypto/rand/rand_aix.go
rename to src/crypto/internal/sysrand/rand_aix.go
index 4cc080d8fc7430cff30bc50315fb92a1448938ec..52928b6d74ad60595b4b5b27bc476dd8986d8294 100644 (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)
similarity index 97%
rename from src/crypto/rand/rand_arc4random.go
rename to src/crypto/internal/sysrand/rand_arc4random.go
index f2e27004f8384850415f84f6ea4983b15f327e35..aee97c4a84237b430eb97a19a8d665d683b00343 100644 (file)
@@ -4,7 +4,7 @@
 
 //go:build darwin || openbsd
 
-package rand
+package sysrand
 
 import "internal/syscall/unix"
 
similarity index 99%
rename from src/crypto/rand/rand_getrandom.go
rename to src/crypto/internal/sysrand/rand_getrandom.go
index 26ba716100f9f345914ccae4b0f3d41aadc40ea1..11e9683a4fa941c8747a2a9bc4c23375858be375 100644 (file)
@@ -4,7 +4,7 @@
 
 //go:build dragonfly || freebsd || linux || solaris
 
-package rand
+package sysrand
 
 import (
        "errors"
similarity index 97%
rename from src/crypto/rand/rand_js.go
rename to src/crypto/internal/sysrand/rand_js.go
index d45031a039e3c67806fee1d6b154313265926e9a..b9eb8e78ea2d7f2c9b41c1c94f6caadf0b7b0f47 100644 (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
similarity index 95%
rename from src/crypto/rand/rand_linux_test.go
rename to src/crypto/internal/sysrand/rand_linux_test.go
index 5238b458e53bb4cb56dc3462b3affd08e0350ef5..417523c29ddc9f890eead7fe216781537d2e6c3f 100644 (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() {
similarity index 97%
rename from src/crypto/rand/rand_netbsd.go
rename to src/crypto/internal/sysrand/rand_netbsd.go
index b25d261e1528d837040b7196e98e11fd5b5b6386..d203f1b4a474cdd2b760f92dbb9512b640b4ddf7 100644 (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"
 
similarity index 98%
rename from src/crypto/rand/rand_plan9.go
rename to src/crypto/internal/sysrand/rand_plan9.go
index a9b0b5857f5b04e4be0432275c153e5f6fba238d..3dfd2e966d58e2aece236299b422b89a7f338886 100644 (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"
diff --git a/src/crypto/internal/sysrand/rand_test.go b/src/crypto/internal/sysrand/rand_test.go
new file mode 100644 (file)
index 0000000..41eee46
--- /dev/null
@@ -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)
+       }
+}
similarity index 97%
rename from src/crypto/rand/rand_wasip1.go
rename to src/crypto/internal/sysrand/rand_wasip1.go
index 3ffc18d203bbb138049af101b5a822506380d763..524df6d259526096ce8a52dc0681e786dc63f5ba 100644 (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"
 
similarity index 94%
rename from src/crypto/rand/rand_windows.go
rename to src/crypto/internal/sysrand/rand_windows.go
index ef513ffca00e75c7b801f56d0bf4cef9c4307d87..91f1490c15cda35e859421893fd71431522df993 100644 (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"
 
index b3d0a7368f7fa0550f108055d35b2514a3df4077..08851d23d947ae65f5e4737c96bc4fad9d4570ca 100644 (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
-}
index 5cf0ad8ec1fd5df47c23601b050443fb6ec4271a..5ddb9437b61c74abaf8d8d455a8837a8a2141aaf 100644 (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
        }
 
index bdb6b5a0d7358939664ea9197a391c80f4401a87..abff1be2ada4e6df8e18ad16413d9ab0f59e34d4 100644 (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
index 7447854989918a3570c9996960d6ceb2b13a78fa..8e8ee8559afc97df538dfe52bb3f6f8d232890d1 100644 (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)