app: integrate interrupt forwarding
Some checks failed
Test / Create distribution (push) Successful in 33s
Test / Hakurei (push) Failing after 56s
Test / Hakurei (race detector) (push) Failing after 1m2s
Test / Planterette (push) Failing after 1m3s
Test / Sandbox (race detector) (push) Failing after 1m19s
Test / Sandbox (push) Failing after 1m25s
Test / Flake checks (push) Has been skipped
Some checks failed
Test / Create distribution (push) Successful in 33s
Test / Hakurei (push) Failing after 56s
Test / Hakurei (race detector) (push) Failing after 1m2s
Test / Planterette (push) Failing after 1m3s
Test / Sandbox (race detector) (push) Failing after 1m19s
Test / Sandbox (push) Failing after 1m25s
Test / Flake checks (push) Has been skipped
This significantly increases usability of command line tools running through hakurei. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
ddf48a6c22
commit
e72f96ad0a
@ -10,6 +10,8 @@ type (
|
|||||||
// container hostname
|
// container hostname
|
||||||
Hostname string `json:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty"`
|
||||||
|
|
||||||
|
// do not interrupt and wait for initial process during termination
|
||||||
|
ImmediateTermination bool `json:"immediate_termination,omitempty"`
|
||||||
// extra seccomp flags
|
// extra seccomp flags
|
||||||
SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"`
|
SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"`
|
||||||
// extra seccomp presets
|
// extra seccomp presets
|
||||||
|
@ -57,16 +57,18 @@ func Template() *Config {
|
|||||||
Groups: []string{"video", "dialout", "plugdev"},
|
Groups: []string{"video", "dialout", "plugdev"},
|
||||||
|
|
||||||
Container: &ContainerConfig{
|
Container: &ContainerConfig{
|
||||||
Hostname: "localhost",
|
Hostname: "localhost",
|
||||||
Devel: true,
|
Devel: true,
|
||||||
Userns: true,
|
Userns: true,
|
||||||
Net: true,
|
Net: true,
|
||||||
Device: true,
|
Device: true,
|
||||||
SeccompFlags: seccomp.AllowMultiarch,
|
ImmediateTermination: true,
|
||||||
SeccompPresets: seccomp.PresetExt,
|
SeccompFlags: seccomp.AllowMultiarch,
|
||||||
Tty: true,
|
SeccompPresets: seccomp.PresetExt,
|
||||||
Multiarch: true,
|
SeccompCompat: true,
|
||||||
MapRealUID: true,
|
Tty: true,
|
||||||
|
Multiarch: true,
|
||||||
|
MapRealUID: true,
|
||||||
// example API credentials pulled from Google Chrome
|
// example API credentials pulled from Google Chrome
|
||||||
// DO NOT USE THESE IN A REAL BROWSER
|
// DO NOT USE THESE IN A REAL BROWSER
|
||||||
Env: map[string]string{
|
Env: map[string]string{
|
||||||
|
@ -80,8 +80,10 @@ func TestTemplate(t *testing.T) {
|
|||||||
],
|
],
|
||||||
"container": {
|
"container": {
|
||||||
"hostname": "localhost",
|
"hostname": "localhost",
|
||||||
|
"immediate_termination": true,
|
||||||
"seccomp_flags": 1,
|
"seccomp_flags": 1,
|
||||||
"seccomp_presets": 1,
|
"seccomp_presets": 1,
|
||||||
|
"seccomp_compat": true,
|
||||||
"devel": true,
|
"devel": true,
|
||||||
"userns": true,
|
"userns": true,
|
||||||
"net": true,
|
"net": true,
|
||||||
|
@ -144,6 +144,7 @@ var testCasesNixos = []sealTestCase{
|
|||||||
Tmpfs("/var/run/nscd", 8192, 0755),
|
Tmpfs("/var/run/nscd", 8192, 0755),
|
||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
|
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyTTY | seccomp.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
|
ForwardCancel: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
|
ForwardCancel: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -220,6 +221,7 @@ var testCasesPd = []sealTestCase{
|
|||||||
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
SeccompPresets: seccomp.PresetExt | seccomp.PresetDenyDevel,
|
||||||
HostNet: true,
|
HostNet: true,
|
||||||
RetainSession: true,
|
RetainSession: true,
|
||||||
|
ForwardCancel: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,10 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
|
|||||||
SeccompPresets: s.SeccompPresets,
|
SeccompPresets: s.SeccompPresets,
|
||||||
RetainSession: s.Tty,
|
RetainSession: s.Tty,
|
||||||
HostNet: s.Net,
|
HostNet: s.Net,
|
||||||
|
|
||||||
|
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
|
||||||
|
// this behaviour is implemented in the shim
|
||||||
|
ForwardCancel: !s.ImmediateTermination,
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -41,6 +42,10 @@ const (
|
|||||||
ShimExitRequest = 254
|
ShimExitRequest = 254
|
||||||
// ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal.
|
// ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal.
|
||||||
ShimExitOrphan = 3
|
ShimExitOrphan = 3
|
||||||
|
|
||||||
|
// ShimWaitDelay is the duration to wait after interrupting a container's initial process
|
||||||
|
// before the container is fully killed off.
|
||||||
|
ShimWaitDelay = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
// ShimMain is the main function of the shim process and runs as the unconstrained target user.
|
||||||
@ -86,6 +91,7 @@ func ShimMain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// signal handler outcome
|
// signal handler outcome
|
||||||
|
var cancelContainer atomic.Pointer[context.CancelFunc]
|
||||||
go func() {
|
go func() {
|
||||||
buf := make([]byte, 1)
|
buf := make([]byte, 1)
|
||||||
for {
|
for {
|
||||||
@ -94,23 +100,30 @@ func ShimMain() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch buf[0] {
|
switch buf[0] {
|
||||||
case 0:
|
case 0: // got SIGCONT from monitor: shim exit requested
|
||||||
|
if fp := cancelContainer.Load(); params.Container.ForwardCancel && fp != nil && *fp != nil {
|
||||||
|
(*fp)()
|
||||||
|
// shim now bound by ShimWaitDelay, implemented below
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup has not completed, terminate immediately
|
||||||
hlog.Resume()
|
hlog.Resume()
|
||||||
os.Exit(ShimExitRequest)
|
os.Exit(ShimExitRequest)
|
||||||
return
|
return
|
||||||
|
|
||||||
case 1:
|
case 1: // got SIGCONT after adoption: monitor died before delivering signal
|
||||||
hlog.BeforeExit()
|
hlog.BeforeExit()
|
||||||
os.Exit(ShimExitOrphan)
|
os.Exit(ShimExitOrphan)
|
||||||
return
|
return
|
||||||
|
|
||||||
case 2:
|
case 2: // unreachable
|
||||||
log.Println("sa_sigaction got invalid siginfo")
|
log.Println("sa_sigaction got invalid siginfo")
|
||||||
|
|
||||||
case 3:
|
case 3: // got SIGCONT from unexpected process: hopefully the terminal driver
|
||||||
log.Println("got SIGCONT from unexpected process")
|
log.Println("got SIGCONT from unexpected process")
|
||||||
|
|
||||||
default:
|
default: // unreachable
|
||||||
log.Fatalf("got invalid message %d from signal handler", buf[0])
|
log.Fatalf("got invalid message %d from signal handler", buf[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,12 +159,11 @@ func ShimMain() {
|
|||||||
name = params.Container.Args[0]
|
name = params.Container.Args[0]
|
||||||
}
|
}
|
||||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
defer stop() // unreachable
|
cancelContainer.Store(&stop)
|
||||||
z := container.New(ctx, name)
|
z := container.New(ctx, name)
|
||||||
z.Params = *params.Container
|
z.Params = *params.Container
|
||||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
z.Cancel = func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) }
|
z.WaitDelay = ShimWaitDelay
|
||||||
z.WaitDelay = 2 * time.Second
|
|
||||||
|
|
||||||
if err := z.Start(); err != nil {
|
if err := z.Start(); err != nil {
|
||||||
hlog.PrintBaseError(err, "cannot start container:")
|
hlog.PrintBaseError(err, "cannot start container:")
|
||||||
|
@ -128,6 +128,7 @@ in
|
|||||||
|
|
||||||
container = {
|
container = {
|
||||||
inherit (app)
|
inherit (app)
|
||||||
|
immediate_termination
|
||||||
devel
|
devel
|
||||||
userns
|
userns
|
||||||
net
|
net
|
||||||
|
@ -195,6 +195,7 @@ in
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
immediate_termination = mkEnableOption "immediate termination of the container on interrupt";
|
||||||
devel = mkEnableOption "debugging-related kernel interfaces";
|
devel = mkEnableOption "debugging-related kernel interfaces";
|
||||||
userns = mkEnableOption "user namespace creation";
|
userns = mkEnableOption "user namespace creation";
|
||||||
tty = mkEnableOption "access to the controlling terminal";
|
tty = mkEnableOption "access to the controlling terminal";
|
||||||
|
@ -127,6 +127,21 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
"cat.gensokyo.extern.foot.noEnablements.immediate" = {
|
||||||
|
name = "ne-foot-immediate";
|
||||||
|
identity = 1;
|
||||||
|
shareUid = true;
|
||||||
|
verbose = true;
|
||||||
|
immediate_termination = true;
|
||||||
|
share = pkgs.foot;
|
||||||
|
packages = [ ];
|
||||||
|
command = "foot";
|
||||||
|
capability = {
|
||||||
|
dbus = false;
|
||||||
|
pulse = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
"cat.gensokyo.extern.foot.pulseaudio" = {
|
"cat.gensokyo.extern.foot.pulseaudio" = {
|
||||||
name = "pa-foot";
|
name = "pa-foot";
|
||||||
identity = 2;
|
identity = 2;
|
||||||
|
10
test/test.py
10
test/test.py
@ -178,6 +178,16 @@ machine.succeed("pkill -INT -f 'hakurei -v app '")
|
|||||||
machine.wait_until_fails("pgrep foot", timeout=5)
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
machine.wait_for_file("/tmp/monitor-exit-code")
|
machine.wait_for_file("/tmp/monitor-exit-code")
|
||||||
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
||||||
|
if interrupt_exit_code != 230:
|
||||||
|
raise Exception(f"unexpected exit code {interrupt_exit_code}")
|
||||||
|
|
||||||
|
# Check interrupt shim behaviour immediate termination:
|
||||||
|
swaymsg("exec sh -c 'ne-foot-immediate; echo -n $? > /tmp/monitor-exit-code'")
|
||||||
|
wait_for_window(f"u0_a{aid(0)}@machine")
|
||||||
|
machine.succeed("pkill -INT -f 'hakurei -v app '")
|
||||||
|
machine.wait_until_fails("pgrep foot", timeout=5)
|
||||||
|
machine.wait_for_file("/tmp/monitor-exit-code")
|
||||||
|
interrupt_exit_code = int(machine.succeed("cat /tmp/monitor-exit-code"))
|
||||||
if interrupt_exit_code != 254:
|
if interrupt_exit_code != 254:
|
||||||
raise Exception(f"unexpected exit code {interrupt_exit_code}")
|
raise Exception(f"unexpected exit code {interrupt_exit_code}")
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user