| // 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") |
| } |