runtime: use bootstrapRand to initialize hashkey

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>
diff --git a/src/runtime/alg.go b/src/runtime/alg.go
index f5125d4..aadd5f1 100644
--- a/src/runtime/alg.go
+++ b/src/runtime/alg.go
@@ -391,7 +391,7 @@
 		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
 	}
 }
 
diff --git a/src/runtime/map_test.go b/src/runtime/map_test.go
index 2c51236..c29fb93 100644
--- a/src/runtime/map_test.go
+++ b/src/runtime/map_test.go
@@ -8,7 +8,9 @@
 	"fmt"
 	"internal/abi"
 	"internal/goarch"
+	"internal/testenv"
 	"math"
+	"os"
 	"reflect"
 	"runtime"
 	"sort"
@@ -1464,3 +1466,81 @@
 		}
 	}
 }
+
+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)
+		}
+	})
+}