diff --git a/src/math/rand/auto_test.go b/src/math/rand/auto_test.go new file mode 100644 index 00000000000..b057370eea9 --- /dev/null +++ b/src/math/rand/auto_test.go @@ -0,0 +1,40 @@ +// Copyright 2022 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_test + +import ( + . "math/rand" + "testing" +) + +// This test is first, in its own file with an alphabetically early name, +// to try to make sure that it runs early. It has the best chance of +// detecting deterministic seeding if it's the first test that runs. + +func TestAuto(t *testing.T) { + // Pull out 10 int64s from the global source + // and then check that they don't appear in that + // order in the deterministic Seed(1) result. + var out []int64 + for i := 0; i < 10; i++ { + out = append(out, Int63()) + } + + // Look for out in Seed(1)'s output. + // Strictly speaking, we should look for them in order, + // but this is good enough and not significantly more + // likely to have a false positive. + Seed(1) + found := 0 + for i := 0; i < 1000; i++ { + x := Int63() + if x == out[found] { + found++ + if found == len(out) { + t.Fatalf("found unseeded output in Seed(1) output") + } + } + } +} diff --git a/src/math/rand/rand.go b/src/math/rand/rand.go index 4627d4515fb..2d45a28280b 100644 --- a/src/math/rand/rand.go +++ b/src/math/rand/rand.go @@ -5,22 +5,28 @@ // Package rand implements pseudo-random number generators unsuitable for // security-sensitive work. // -// Random numbers are generated by a Source. Top-level functions, such as -// Float64 and Int, use a default shared Source that produces a deterministic -// sequence of values each time a program is run. Use the Seed function to -// initialize the default Source if different behavior is required for each run. -// The default Source is safe for concurrent use by multiple goroutines, but -// Sources created by NewSource are not. +// Random numbers are generated by a [Source], usually wrapped in a [Rand]. +// Both types should be used by a single goroutine at a time: sharing among +// multiple goroutines requires some kind of synchronization. +// +// Top-level functions, such as [Float64] and [Int], +// are safe for concurrent use by multiple goroutines. // // This package's outputs might be easily predictable regardless of how it's // seeded. For random numbers suitable for security-sensitive work, see the // crypto/rand package. package rand -import "sync" +import ( + "internal/godebug" + "sync" + _ "unsafe" // for go:linkname +) // A Source represents a source of uniformly-distributed // pseudo-random int64 values in the range [0, 1<<63). +// +// A Source is not safe for concurrent use by multiple goroutines. type Source interface { Int63() int64 Seed(seed int64) @@ -298,10 +304,23 @@ func read(p []byte, src Source, readVal *int64, readPos *int8) (n int, err error var globalRand = New(new(lockedSource)) // Seed uses the provided seed value to initialize the default Source to a -// deterministic state. If Seed is not called, the generator behaves as -// if seeded by Seed(1). Seed values that have the same remainder when +// deterministic state. Seed values that have the same remainder when // divided by 2³¹-1 generate the same pseudo-random sequence. // Seed, unlike the Rand.Seed method, is safe for concurrent use. +// +// If Seed is not called, the generator is seeded randomly at program startup. +// +// Prior to Go 1.20, the generator was seeded like Seed(1) at program startup. +// To force the old behavior, call Seed(1) at program startup. +// Alternately, set GODEBUG=randautoseed=0 in the environment +// before making any calls to functions in this package. +// +// Note: Programs that call Seed and then expect a specific sequence +// of results from the global random source (using functions such as Int) +// can be broken when a dependency changes how much it consumes +// from the global random source. To avoid such breakages, programs +// that need a specific result sequence should use NewRand(NewSource(seed)) +// to obtain a random generator that other packages cannot access. func Seed(seed int64) { globalRand.Seed(seed) } // Int63 returns a non-negative pseudo-random 63-bit integer as an int64 @@ -384,11 +403,20 @@ type lockedSource struct { s *rngSource // nil if not yet allocated } +//go:linkname fastrand64 +func fastrand64() uint64 + // source returns r.s, allocating and seeding it if needed. // The caller must have locked r. func (r *lockedSource) source() *rngSource { if r.s == nil { - r.s = newSource(1) + var seed int64 + if godebug.Get("randautoseed") == "0" { + seed = 1 + } else { + seed = int64(fastrand64()) + } + r.s = newSource(seed) } return r.s } diff --git a/src/runtime/stubs.go b/src/runtime/stubs.go index 929f8fadca8..20487fdf883 100644 --- a/src/runtime/stubs.go +++ b/src/runtime/stubs.go @@ -196,6 +196,9 @@ func fastrandu() uint { return uint(fastrand64()) } +//go:linkname rand_fastrand64 math/rand.fastrand64 +func rand_fastrand64() uint64 { return fastrand64() } + //go:linkname sync_fastrandn sync.fastrandn func sync_fastrandn(n uint32) uint32 { return fastrandn(n) }