blob: 1ee1ea6b8e0fa14149d04c3d45b1cc80e4aa24e5 [file] [log] [blame] [edit]
// Copyright 2025 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 main
import (
"bytes"
"fmt"
"os"
"runtime"
"runtime/debug"
"runtime/secret"
"sync"
"syscall"
"time"
_ "unsafe"
"weak"
)
// Same secret as in ../../crash_test.go
var secretStore = [8]byte{
0x00,
0x81,
0xa0,
0xc6,
0xb3,
0x01,
0x66,
0x53,
}
func main() {
enableCore()
useSecretProc()
// clear out secret. That way we don't have
// to figure out which secret is the allowed
// source
clear(secretStore[:])
panic("terminate")
}
// Copied from runtime/runtime-gdb_unix_test.go
func enableCore() {
debug.SetTraceback("crash")
var lim syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_CORE, &lim)
if err != nil {
panic(fmt.Sprintf("error getting rlimit: %v", err))
}
lim.Cur = lim.Max
fmt.Fprintf(os.Stderr, "Setting RLIMIT_CORE = %+#v\n", lim)
err = syscall.Setrlimit(syscall.RLIMIT_CORE, &lim)
if err != nil {
panic(fmt.Sprintf("error setting rlimit: %v", err))
}
}
// useSecretProc does 5 seconds of work, using the secret value
// inside secret.Do in a bunch of ways.
func useSecretProc() {
stop := make(chan bool)
var wg sync.WaitGroup
for i := 0; i < 4; i++ {
wg.Add(1)
go func() {
time.Sleep(1 * time.Second)
for {
select {
case <-stop:
wg.Done()
return
default:
secret.Do(func() {
// Copy key into a variable-sized heap allocation.
// This both puts secrets in heap objects,
// and more generally just causes allocation,
// which forces garbage collection, which
// requires interrupts and the like.
s := bytes.Repeat(secretStore[:], 1+i*2)
// Also spam the secret across all registers.
useSecret(s)
})
}
}
}()
}
// Send some allocations over a channel. This does 2 things:
// 1) forces some GCs to happen
// 2) causes more scheduling noise (Gs moving between Ms, etc.)
c := make(chan []byte)
wg.Add(2)
go func() {
for {
select {
case <-stop:
wg.Done()
return
case c <- make([]byte, 256):
}
}
}()
go func() {
for {
select {
case <-stop:
wg.Done()
return
case <-c:
}
}
}()
time.Sleep(5 * time.Second)
close(stop)
wg.Wait()
// use a weak reference for ensuring that the GC has cleared everything
// Use a large value to avoid the tiny allocator.
w := weak.Make(new([2048]byte))
// 20 seems like a decent amount?
for i := 0; i < 20; i++ {
runtime.GC() // GC should clear any secret heap objects and clear out scheduling buffers.
if w.Value() == nil {
fmt.Fprintf(os.Stderr, "number of GCs %v\n", i+1)
return
}
}
fmt.Fprintf(os.Stderr, "GC didn't clear out in time\n")
// This will cause the core dump to happen with the sentinel value still in memory
// so we will detect the fault.
panic("fault")
}