diff --git a/container/container.go b/container/container.go index fb031c3..55e4c44 100644 --- a/container/container.go +++ b/container/container.go @@ -24,7 +24,7 @@ import ( const ( // CancelSignal is the signal expected by container init on context cancel. // A custom [Container.Cancel] function must eventually deliver this signal. - CancelSignal = SIGTERM + CancelSignal = SIGUSR2 ) type ( diff --git a/container/init.go b/container/init.go index 4e2345b..0f5edb1 100644 --- a/container/init.go +++ b/container/init.go @@ -390,7 +390,8 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) { // handle signals to dump withheld messages sig := make(chan os.Signal, 2) - k.notify(sig, os.Interrupt, CancelSignal) + k.notify(sig, CancelSignal, + os.Interrupt, SIGTERM, SIGQUIT) // closed after residualProcessTimeout has elapsed after initial process death timeout := make(chan struct{}) @@ -399,18 +400,28 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) { for { select { case s := <-sig: - if msg.Resume() { - msg.Verbosef("%s after process start", s.String()) - } else { - msg.Verbosef("got %s", s.String()) - } if s == CancelSignal && params.ForwardCancel && cmd.Process != nil { + msg.Resume() msg.Verbose("forwarding context cancellation") if err := k.signal(cmd, os.Interrupt); err != nil { k.printf(msg, "cannot forward cancellation: %v", err) } continue } + + if s == SIGTERM || s == SIGQUIT { + msg.Verbosef("got %s, forwarding to initial process", s.String()) + if err := k.signal(cmd, s); err != nil { + k.printf(msg, "cannot forward signal: %v", err) + } + continue + } + + if msg.Resume() { + msg.Verbosef("%s after process start", s.String()) + } else { + msg.Verbosef("got %s", s.String()) + } msg.BeforeExit() k.exit(0) diff --git a/container/init_test.go b/container/init_test.go index 27f41a4..3497fc6 100644 --- a/container/init_test.go +++ b/container/init_test.go @@ -2065,9 +2065,8 @@ func TestInitEntrypoint(t *testing.T) { call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(10)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), - call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), + call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- CancelSignal }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil), call("resume", stub.ExpectArgs{}, true, nil), - call("verbosef", stub.ExpectArgs{"%s after process start", []any{"terminated"}}, nil, nil), call("verbose", stub.ExpectArgs{[]any{"forwarding context cancellation"}}, nil, nil), // magicWait4Signal as ret causes wait4 stub to unblock call("signal", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent", os.Interrupt}, magicWait4Signal, stub.UniqueError(9)), @@ -2090,6 +2089,199 @@ func TestInitEntrypoint(t *testing.T) { }}}, }, nil}, + {"lowlastcap signaled cancel passthrough", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ + /* entrypoint */ + Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + call("getpid", stub.ExpectArgs{}, 1, nil), + call("setPtracer", stub.ExpectArgs{uintptr(0)}, 0, stub.UniqueError(8)), + call("verbosef", stub.ExpectArgs{"cannot enable ptrace protection via Yama LSM: %v", []any{stub.UniqueError(8)}}, nil, nil), + call("receive", stub.ExpectArgs{"HAKUREI_SETUP", new(initParams), new(uintptr), &initParams{Params{ + Dir: check.MustAbs("/.hakurei/nonexistent"), + Path: check.MustAbs("/run/current-system/sw/bin/bash"), + Args: []string{"bash", "-c", "false"}, + ForwardCancel: true, + AdoptWaitDelay: 5 * time.Nanosecond, + Uid: 1 << 24, + Gid: 1 << 47, + Hostname: "hakurei-check", + Ops: new(Ops).Bind(check.MustAbs("/"), check.MustAbs("/"), comp.BindDevice).Proc(check.MustAbs("/proc/")), + SeccompRules: make([]seccomp.NativeRule, 0), + SeccompDisable: true, + ParentPerm: 0750, + }, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(7), nil), + call("swapVerbose", stub.ExpectArgs{false}, false, nil), + call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), + call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), + call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil), + call("writeFile", stub.ExpectArgs{"/proc/self/setgroups", []byte("deny\n"), os.FileMode(0)}, nil, nil), + call("writeFile", stub.ExpectArgs{"/proc/self/gid_map", []byte("140737488355328 127 1\n"), os.FileMode(0)}, nil, nil), + call("setDumpable", stub.ExpectArgs{uintptr(0)}, nil, nil), + call("umask", stub.ExpectArgs{0}, 022, nil), + call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), + call("lastcap", stub.ExpectArgs{}, uintptr(4), nil), + call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), + /* begin early */ + call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), + /* end early */ + call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil), + call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil), + call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), + call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), + call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), + call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), + call("chdir", stub.ExpectArgs{"/"}, nil, nil), + /* begin apply */ + call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), + call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), + call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil), + call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), + call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: check.MustAbs("/proc/")}}}, nil, nil), + call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil), + call("mount", stub.ExpectArgs{"proc", "/sysroot/proc", "proc", uintptr(0xe), ""}, nil, nil), + /* end apply */ + call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil), + call("unmount", stub.ExpectArgs{"host", 2}, nil, nil), + call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR), + call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil), + call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil), + call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil), + call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil), + call("unmount", stub.ExpectArgs{".", 2}, nil, nil), + call("chdir", stub.ExpectArgs{"/"}, nil, nil), + call("close", stub.ExpectArgs{1 << 35}, nil, nil), + call("capAmbientClearAll", stub.ExpectArgs{}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x0)}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x1)}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x2)}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x3)}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x4)}, nil, nil), + call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, new([2]capData)}, nil, nil), + call("verbose", stub.ExpectArgs{[]any{"syscall filter not configured"}}, nil, nil), + call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil), + call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil), + call("umask", stub.ExpectArgs{022}, 0, nil), + call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil), + call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil), + call("suspend", stub.ExpectArgs{}, true, nil), + call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil), + call("New", stub.ExpectArgs{}, nil, nil), + call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- syscall.SIGQUIT }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil), + call("verbosef", stub.ExpectArgs{"got %s, forwarding to initial process", []any{"quit"}}, nil, nil), + // magicWait4Signal as ret causes wait4 stub to unblock + call("signal", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent", syscall.SIGQUIT}, magicWait4Signal, stub.UniqueError(0xfe)), + call("printf", stub.ExpectArgs{"cannot forward signal: %v", []any{stub.UniqueError(0xfe)}}, nil, nil), + call("resume", stub.ExpectArgs{}, true, nil), + call("verbosef", stub.ExpectArgs{"initial process exited with signal %s", []any{syscall.Signal(0x4e)}}, nil, nil), + call("printf", stub.ExpectArgs{"timeout exceeded waiting for lingering processes", []any(nil)}, nil, nil), + call("beforeExit", stub.ExpectArgs{}, nil, nil), + call("exit", stub.ExpectArgs{206}, nil, nil), + }, + + /* wait4 */ + Tracks: []stub.Expect{{Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + + // magicWait4Signal as args[4] causes this to block until simulated signal is delivered + call("wait4", stub.ExpectArgs{-1, syscall.WaitStatus(0xfade01ce), 0, nil, magicWait4Signal}, 0xbad, nil), + // this terminates the goroutine at the call, preventing it from leaking while preserving behaviour + call("wait4", stub.ExpectArgs{-1, nil, 0, nil, stub.PanicExit}, 0, syscall.ECHILD), + }}}, + }, nil}, + + {"lowlastcap signaled cancel resumed", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ + /* entrypoint */ + Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + call("getpid", stub.ExpectArgs{}, 1, nil), + call("setPtracer", stub.ExpectArgs{uintptr(0)}, 0, stub.UniqueError(8)), + call("verbosef", stub.ExpectArgs{"cannot enable ptrace protection via Yama LSM: %v", []any{stub.UniqueError(8)}}, nil, nil), + call("receive", stub.ExpectArgs{"HAKUREI_SETUP", new(initParams), new(uintptr), &initParams{Params{ + Dir: check.MustAbs("/.hakurei/nonexistent"), + Path: check.MustAbs("/run/current-system/sw/bin/bash"), + Args: []string{"bash", "-c", "false"}, + ForwardCancel: true, + AdoptWaitDelay: 5 * time.Nanosecond, + Uid: 1 << 24, + Gid: 1 << 47, + Hostname: "hakurei-check", + Ops: new(Ops).Bind(check.MustAbs("/"), check.MustAbs("/"), comp.BindDevice).Proc(check.MustAbs("/proc/")), + SeccompRules: make([]seccomp.NativeRule, 0), + SeccompDisable: true, + ParentPerm: 0750, + }, 1971, 127, 2, false}, uintptr(0x39)}, stub.UniqueError(7), nil), + call("swapVerbose", stub.ExpectArgs{false}, false, nil), + call("verbose", stub.ExpectArgs{[]any{"received setup parameters"}}, nil, nil), + call("setDumpable", stub.ExpectArgs{uintptr(1)}, nil, nil), + call("writeFile", stub.ExpectArgs{"/proc/self/uid_map", []byte("16777216 1971 1\n"), os.FileMode(0)}, nil, nil), + call("writeFile", stub.ExpectArgs{"/proc/self/setgroups", []byte("deny\n"), os.FileMode(0)}, nil, nil), + call("writeFile", stub.ExpectArgs{"/proc/self/gid_map", []byte("140737488355328 127 1\n"), os.FileMode(0)}, nil, nil), + call("setDumpable", stub.ExpectArgs{uintptr(0)}, nil, nil), + call("umask", stub.ExpectArgs{0}, 022, nil), + call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil), + call("lastcap", stub.ExpectArgs{}, uintptr(4), nil), + call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil), + /* begin early */ + call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil), + /* end early */ + call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil), + call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil), + call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil), + call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil), + call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil), + call("pivotRoot", stub.ExpectArgs{"/proc/self/fd", "host"}, nil, nil), + call("chdir", stub.ExpectArgs{"/"}, nil, nil), + /* begin apply */ + call("stat", stub.ExpectArgs{"/host"}, isDirFi(true), nil), + call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0700)}, nil, nil), + call("verbosef", stub.ExpectArgs{"mounting %q flags %#x", []any{"/sysroot", uintptr(0x4001)}}, nil, nil), + call("bindMount", stub.ExpectArgs{"/host", "/sysroot", uintptr(0x4001), false}, nil, nil), + call("verbosef", stub.ExpectArgs{"%s %s", []any{"mounting", &MountProcOp{Target: check.MustAbs("/proc/")}}}, nil, nil), + call("mkdirAll", stub.ExpectArgs{"/sysroot/proc", os.FileMode(0750)}, nil, nil), + call("mount", stub.ExpectArgs{"proc", "/sysroot/proc", "proc", uintptr(0xe), ""}, nil, nil), + /* end apply */ + call("mount", stub.ExpectArgs{"host", "host", "", uintptr(0x4c000), ""}, nil, nil), + call("unmount", stub.ExpectArgs{"host", 2}, nil, nil), + call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, syscall.EINTR), + call("open", stub.ExpectArgs{"/", syscall.O_DIRECTORY | syscall.O_RDONLY, uint32(0)}, 1<<35, nil), + call("chdir", stub.ExpectArgs{"/sysroot"}, nil, nil), + call("pivotRoot", stub.ExpectArgs{".", "."}, nil, nil), + call("fchdir", stub.ExpectArgs{1 << 35}, nil, nil), + call("unmount", stub.ExpectArgs{".", 2}, nil, nil), + call("chdir", stub.ExpectArgs{"/"}, nil, nil), + call("close", stub.ExpectArgs{1 << 35}, nil, nil), + call("capAmbientClearAll", stub.ExpectArgs{}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x0)}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x1)}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x2)}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x3)}, nil, nil), + call("capBoundingSetDrop", stub.ExpectArgs{uintptr(0x4)}, nil, nil), + call("capset", stub.ExpectArgs{&capHeader{_LINUX_CAPABILITY_VERSION_3, 0}, new([2]capData)}, nil, nil), + call("verbose", stub.ExpectArgs{[]any{"syscall filter not configured"}}, nil, nil), + call("newFile", stub.ExpectArgs{uintptr(0x3a), "extra file 0"}, (*os.File)(nil), nil), + call("newFile", stub.ExpectArgs{uintptr(0x3b), "extra file 1"}, (*os.File)(nil), nil), + call("umask", stub.ExpectArgs{022}, 0, nil), + call("verbosef", stub.ExpectArgs{"starting initial program %s", []any{check.MustAbs("/run/current-system/sw/bin/bash")}}, nil, nil), + call("start", stub.ExpectArgs{"/run/current-system/sw/bin/bash", []string{"bash", "-c", "false"}, ([]string)(nil), "/.hakurei/nonexistent"}, &os.Process{Pid: 0xbad}, nil), + call("suspend", stub.ExpectArgs{}, true, nil), + call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil), + call("New", stub.ExpectArgs{}, nil, nil), + call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil), + call("resume", stub.ExpectArgs{}, true, nil), + call("verbosef", stub.ExpectArgs{"%s after process start", []any{"interrupt"}}, nil, nil), + call("beforeExit", stub.ExpectArgs{}, nil, nil), + call("exit", stub.ExpectArgs{0}, nil, nil), + }, + + /* wait4 */ + Tracks: []stub.Expect{{Calls: []stub.Call{ + call("lockOSThread", stub.ExpectArgs{}, nil, nil), + + // this terminates the goroutine at the call, preventing it from leaking while preserving behaviour + call("wait4", stub.ExpectArgs{-1, nil, 0, nil, stub.PanicExit}, 0, syscall.ECHILD), + }}}, + }, nil}, + {"lowlastcap signaled cancel", func(k *kstub) error { initEntrypoint(k, k); return nil }, stub.Expect{ /* entrypoint */ Calls: []stub.Call{ @@ -2167,7 +2359,7 @@ func TestInitEntrypoint(t *testing.T) { call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(7)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), - call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), + call("notify", stub.ExpectArgs{func(c chan<- os.Signal) { c <- os.Interrupt }, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil), call("resume", stub.ExpectArgs{}, false, nil), call("verbosef", stub.ExpectArgs{"got %s", []any{"interrupt"}}, nil, nil), call("beforeExit", stub.ExpectArgs{}, nil, nil), @@ -2260,7 +2452,7 @@ func TestInitEntrypoint(t *testing.T) { call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(5)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), - call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), + call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil), call("resume", stub.ExpectArgs{}, true, nil), call("verbosef", stub.ExpectArgs{"initial process exited with signal %s", []any{syscall.Signal(0x4e)}}, nil, nil), call("printf", stub.ExpectArgs{"timeout exceeded waiting for lingering processes", ([]any)(nil)}, nil, nil), @@ -2355,7 +2547,7 @@ func TestInitEntrypoint(t *testing.T) { call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(3)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), - call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), + call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil), call("resume", stub.ExpectArgs{}, true, nil), call("verbosef", stub.ExpectArgs{"initial process exited with signal %s", []any{syscall.Signal(0x4e)}}, nil, nil), call("beforeExit", stub.ExpectArgs{}, nil, nil), @@ -2493,7 +2685,7 @@ func TestInitEntrypoint(t *testing.T) { call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(1)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), - call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), + call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil), call("resume", stub.ExpectArgs{}, true, nil), call("verbosef", stub.ExpectArgs{"initial process exited with code %d", []any{1}}, nil, nil), call("beforeExit", stub.ExpectArgs{}, nil, nil), @@ -2635,7 +2827,7 @@ func TestInitEntrypoint(t *testing.T) { call("suspend", stub.ExpectArgs{}, true, nil), call("printf", stub.ExpectArgs{"cannot close setup pipe: %v", []any{stub.UniqueError(0)}}, nil, nil), call("New", stub.ExpectArgs{}, nil, nil), - call("notify", stub.ExpectArgs{nil, []os.Signal{syscall.SIGINT, syscall.SIGTERM}}, nil, nil), + call("notify", stub.ExpectArgs{nil, []os.Signal{CancelSignal, os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}}, nil, nil), call("resume", stub.ExpectArgs{}, true, nil), call("verbosef", stub.ExpectArgs{"initial process exited with status %#x", []any{syscall.WaitStatus(0xfade007f)}}, nil, nil), call("beforeExit", stub.ExpectArgs{}, nil, nil),