diff --git a/cmd/hakurei/print_test.go b/cmd/hakurei/print_test.go index b9d4bb2..efe6ffb 100644 --- a/cmd/hakurei/print_test.go +++ b/cmd/hakurei/print_test.go @@ -256,7 +256,7 @@ App ], "container": { "hostname": "localhost", - "immediate_termination": true, + "wait_delay": -1, "seccomp_flags": 1, "seccomp_presets": 1, "seccomp_compat": true, @@ -384,7 +384,7 @@ App ], "container": { "hostname": "localhost", - "immediate_termination": true, + "wait_delay": -1, "seccomp_flags": 1, "seccomp_presets": 1, "seccomp_compat": true, @@ -566,7 +566,7 @@ func Test_printPs(t *testing.T) { ], "container": { "hostname": "localhost", - "immediate_termination": true, + "wait_delay": -1, "seccomp_flags": 1, "seccomp_presets": 1, "seccomp_compat": true, diff --git a/hst/container.go b/hst/container.go index 1ee9e83..c385344 100644 --- a/hst/container.go +++ b/hst/container.go @@ -1,6 +1,8 @@ package hst import ( + "time" + "hakurei.app/container/seccomp" ) @@ -10,8 +12,10 @@ type ( // container hostname Hostname string `json:"hostname,omitempty"` - // do not interrupt and wait for initial process during termination - ImmediateTermination bool `json:"immediate_termination,omitempty"` + // duration to wait for after interrupting a container's initial process in nanoseconds; + // a negative value causes the container to be terminated immediately on cancellation + WaitDelay time.Duration `json:"wait_delay,omitempty"` + // extra seccomp flags SeccompFlags seccomp.ExportFlag `json:"seccomp_flags"` // extra seccomp presets diff --git a/hst/template.go b/hst/template.go index 60af2eb..3ab9c27 100644 --- a/hst/template.go +++ b/hst/template.go @@ -57,18 +57,18 @@ func Template() *Config { Groups: []string{"video", "dialout", "plugdev"}, Container: &ContainerConfig{ - Hostname: "localhost", - Devel: true, - Userns: true, - Net: true, - Device: true, - ImmediateTermination: true, - SeccompFlags: seccomp.AllowMultiarch, - SeccompPresets: seccomp.PresetExt, - SeccompCompat: true, - Tty: true, - Multiarch: true, - MapRealUID: true, + Hostname: "localhost", + Devel: true, + Userns: true, + Net: true, + Device: true, + WaitDelay: -1, + SeccompFlags: seccomp.AllowMultiarch, + SeccompPresets: seccomp.PresetExt, + SeccompCompat: true, + Tty: true, + Multiarch: true, + MapRealUID: true, // example API credentials pulled from Google Chrome // DO NOT USE THESE IN A REAL BROWSER Env: map[string]string{ diff --git a/hst/template_test.go b/hst/template_test.go index a9b9226..8761b7d 100644 --- a/hst/template_test.go +++ b/hst/template_test.go @@ -80,7 +80,7 @@ func TestTemplate(t *testing.T) { ], "container": { "hostname": "localhost", - "immediate_termination": true, + "wait_delay": -1, "seccomp_flags": 1, "seccomp_presets": 1, "seccomp_compat": true, diff --git a/internal/app/container_linux.go b/internal/app/container_linux.go index baea57d..5efd3de 100644 --- a/internal/app/container_linux.go +++ b/internal/app/container_linux.go @@ -35,7 +35,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain // 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, + ForwardCancel: s.WaitDelay >= 0, } { diff --git a/internal/app/errors_linux.go b/internal/app/errors.go similarity index 100% rename from internal/app/errors_linux.go rename to internal/app/errors.go diff --git a/internal/app/process_linux.go b/internal/app/process_linux.go index 818c7d7..516beb4 100644 --- a/internal/app/process_linux.go +++ b/internal/app/process_linux.go @@ -123,7 +123,15 @@ func (seal *outcome) Run(rs *RunState) error { // this prevents blocking forever on an early failure waitErr, setupErr := make(chan error, 1), make(chan error, 1) go func() { waitErr <- cmd.Wait(); cancel() }() - go func() { setupErr <- e.Encode(&shimParams{os.Getpid(), seal.container, seal.user.data, hlog.Load()}) }() + go func() { + setupErr <- e.Encode(&shimParams{ + os.Getpid(), + seal.waitDelay, + seal.container, + seal.user.data, + hlog.Load(), + }) + }() select { case err := <-setupErr: diff --git a/internal/app/seal_linux.go b/internal/app/seal_linux.go index 40c68c1..1325721 100644 --- a/internal/app/seal_linux.go +++ b/internal/app/seal_linux.go @@ -15,6 +15,7 @@ import ( "strings" "sync/atomic" "syscall" + "time" "hakurei.app/container" "hakurei.app/hst" @@ -79,6 +80,7 @@ type outcome struct { sys *system.I ctx context.Context + waitDelay time.Duration container *container.Params env map[string]string sync *os.File @@ -281,6 +283,7 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co var uid, gid int var err error seal.container, seal.env, err = newContainer(config.Container, sys, &uid, &gid) + seal.waitDelay = config.Container.WaitDelay if err != nil { return hlog.WrapErrSuffix(err, "cannot initialise container configuration:") diff --git a/internal/app/shim_linux.go b/internal/app/shim_linux.go index c88c867..e423b32 100644 --- a/internal/app/shim_linux.go +++ b/internal/app/shim_linux.go @@ -28,6 +28,10 @@ type shimParams struct { // monitor pid, checked against ppid in signal handler Monitor int + // duration to wait for after interrupting a container's initial process before the container is killed; + // zero value defaults to [DefaultShimWaitDelay], values exceeding [MaxShimWaitDelay] becomes [MaxShimWaitDelay] + WaitDelay time.Duration + // finalised container params Container *container.Params // path to outer home directory @@ -43,9 +47,8 @@ const ( // ShimExitOrphan is returned when the shim is orphaned before monitor delivers a signal. 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 + DefaultShimWaitDelay = 5 * time.Second + MaxShimWaitDelay = 30 * time.Second ) // ShimMain is the main function of the shim process and runs as the unconstrained target user. @@ -163,7 +166,14 @@ func ShimMain() { z := container.New(ctx, name) z.Params = *params.Container z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr - z.WaitDelay = ShimWaitDelay + + z.WaitDelay = params.WaitDelay + if z.WaitDelay == 0 { + z.WaitDelay = DefaultShimWaitDelay + } + if z.WaitDelay > MaxShimWaitDelay { + z.WaitDelay = MaxShimWaitDelay + } if err := z.Start(); err != nil { hlog.PrintBaseError(err, "cannot start container:") diff --git a/internal/app/strings_linux.go b/internal/app/strings.go similarity index 100% rename from internal/app/strings_linux.go rename to internal/app/strings.go diff --git a/nixos.nix b/nixos.nix index 919b2c9..bda2988 100644 --- a/nixos.nix +++ b/nixos.nix @@ -128,7 +128,7 @@ in container = { inherit (app) - immediate_termination + wait_delay devel userns net diff --git a/options.nix b/options.nix index 17c5537..90ad356 100644 --- a/options.nix +++ b/options.nix @@ -76,6 +76,7 @@ in type = let inherit (types) + int ints str bool @@ -195,7 +196,16 @@ in ''; }; - immediate_termination = mkEnableOption "immediate termination of the container on interrupt"; + wait_delay = mkOption { + type = nullOr int; + default = null; + description = '' + Duration to wait for after interrupting a container's initial process in nanoseconds. + A negative value causes the container to be terminated immediately on cancellation. + Setting this to null defaults to five seconds. + ''; + }; + devel = mkEnableOption "debugging-related kernel interfaces"; userns = mkEnableOption "user namespace creation"; tty = mkEnableOption "access to the controlling terminal"; diff --git a/test/configuration.nix b/test/configuration.nix index 58bc19a..e1fa892 100644 --- a/test/configuration.nix +++ b/test/configuration.nix @@ -132,7 +132,7 @@ identity = 1; shareUid = true; verbose = true; - immediate_termination = true; + wait_delay = -1; share = pkgs.foot; packages = [ ]; command = "foot";