Skip to content

Commit

Permalink
runtime: use bootstrapRand to initialize hashkey
Browse files Browse the repository at this point in the history
The seed for rand is not initialized until after alginit. Before
initialization, rand returns a deterministic sequence, making hashkey
deterministic across processes.

Switch to bootstrapRand, like other early rand calls, such as
initialization of aeskeysched.

Fixes #66885.

Change-Id: I5023a9161232b49fda2ebd1d5f9338bbdd17b1fe
Reviewed-on: https://go-review.googlesource.com/c/go/+/580136
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
  • Loading branch information
prattmic committed Apr 19, 2024
1 parent 1a0b863 commit 1a3682b
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/runtime/alg.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ func alginit() {
return
}
for i := range hashkey {
hashkey[i] = uintptr(rand()) | 1 // make sure these numbers are odd
hashkey[i] = uintptr(bootstrapRand()) | 1 // make sure these numbers are odd
}
}

Expand Down
80 changes: 80 additions & 0 deletions src/runtime/map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"fmt"
"internal/abi"
"internal/goarch"
"internal/testenv"
"math"
"os"
"reflect"
"runtime"
"sort"
Expand Down Expand Up @@ -1464,3 +1466,81 @@ func TestMapValues(t *testing.T) {
}
}
}

func computeHash() uintptr {
var v struct{}
return runtime.MemHash(unsafe.Pointer(&v), 0, unsafe.Sizeof(v))
}

func subprocessHash(t *testing.T, env string) uintptr {
t.Helper()

cmd := testenv.CleanCmdEnv(testenv.Command(t, os.Args[0], "-test.run=^TestMemHashGlobalSeed$"))
cmd.Env = append(cmd.Env, "GO_TEST_SUBPROCESS_HASH=1")
if env != "" {
cmd.Env = append(cmd.Env, env)
}

out, err := cmd.Output()
if err != nil {
t.Fatalf("cmd.Output got err %v want nil", err)
}

s := strings.TrimSpace(string(out))
h, err := strconv.ParseUint(s, 10, 64)
if err != nil {
t.Fatalf("Parse output %q got err %v want nil", s, err)
}
return uintptr(h)
}

// memhash has unique per-process seeds, so hashes should differ across
// processes.
//
// Regression test for https://go.dev/issue/66885.
func TestMemHashGlobalSeed(t *testing.T) {
if os.Getenv("GO_TEST_SUBPROCESS_HASH") != "" {
fmt.Println(computeHash())
os.Exit(0)
return
}

testenv.MustHaveExec(t)

// aeshash and memhashFallback use separate per-process seeds, so test
// both.
t.Run("aes", func(t *testing.T) {
if !*runtime.UseAeshash {
t.Skip("No AES")
}

h1 := subprocessHash(t, "")
t.Logf("%d", h1)
h2 := subprocessHash(t, "")
t.Logf("%d", h2)
h3 := subprocessHash(t, "")
t.Logf("%d", h3)

if h1 == h2 && h2 == h3 {
t.Errorf("got duplicate hash %d want unique", h1)
}
})

t.Run("noaes", func(t *testing.T) {
env := ""
if *runtime.UseAeshash {
env = "GODEBUG=cpu.aes=off"
}

h1 := subprocessHash(t, env)
t.Logf("%d", h1)
h2 := subprocessHash(t, env)
t.Logf("%d", h2)
h3 := subprocessHash(t, env)
t.Logf("%d", h3)

if h1 == h2 && h2 == h3 {
t.Errorf("got duplicate hash %d want unique", h1)
}
})
}

0 comments on commit 1a3682b

Please sign in to comment.