diff --git a/cmd/hpkg/app.go b/cmd/hpkg/app.go index 67d1249..d302169 100644 --- a/cmd/hpkg/app.go +++ b/cmd/hpkg/app.go @@ -28,6 +28,8 @@ type appInfo struct { // passed through to [hst.Config] Net bool `json:"net,omitempty"` // passed through to [hst.Config] + ScopeAbstract bool `json:"scope_abstract,omitempty"` + // passed through to [hst.Config] Device bool `json:"dev,omitempty"` // passed through to [hst.Config] Tty bool `json:"tty,omitempty"` diff --git a/container/container.go b/container/container.go index 8f90235..d3328ee 100644 --- a/container/container.go +++ b/container/container.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "log" "os" "os/exec" "runtime" @@ -14,6 +15,7 @@ import ( . "syscall" "time" + "hakurei.app/container/landlock" "hakurei.app/container/seccomp" ) @@ -92,6 +94,8 @@ type ( RetainSession bool // Do not [syscall.CLONE_NEWNET]. HostNet bool + // Scope abstract UNIX domain sockets using LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET. + ScopeAbstract bool // Retain CAP_SYS_ADMIN. Privileged bool } @@ -179,6 +183,12 @@ func (p *Container) Start() error { p.wait = make(chan struct{}) done <- func() error { // setup depending on per-thread state must happen here + if p.ScopeAbstract { + if err := landlock.ScopeAbstract(); err != nil { + log.Fatalf("could not scope abstract unix sockets: %v", err) + } + } + msg.Verbose("starting container init") if err := p.cmd.Start(); err != nil { return msg.WrapErr(err, err.Error()) diff --git a/container/landlock/landlock.go b/container/landlock/landlock.go new file mode 100644 index 0000000..0e77e0a --- /dev/null +++ b/container/landlock/landlock.go @@ -0,0 +1,55 @@ +package landlock + +/* +#include +#include +*/ +import "C" + +import ( + "fmt" + "syscall" + "unsafe" +) + +const ( + LANDLOCK_CREATE_RULESET_VERSION = C.LANDLOCK_CREATE_RULESET_VERSION + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = C.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET + + SYS_LANDLOCK_CREATE_RULESET = C.SYS_landlock_create_ruleset + SYS_LANDLOCK_RESTRICT_SELF = C.SYS_landlock_restrict_self +) + +type LandlockRulesetAttr = C.struct_landlock_ruleset_attr + +// ScopeAbstract calls landlock_restrict_self and must be called from a goroutine wired to an m +// with the process starting from the same goroutine. +func ScopeAbstract() error { + abi, _, err := syscall.Syscall(SYS_LANDLOCK_CREATE_RULESET, 0, 0, LANDLOCK_CREATE_RULESET_VERSION) + + if err != 0 { + return fmt.Errorf("could not fetch landlock ABI: errno %v", err) + } + + if abi < 6 { + return fmt.Errorf("landlock ABI must be >= 6, got %d", abi) + } + + attrs := LandlockRulesetAttr{ + scoped: LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET, + } + + fd, _, err := syscall.Syscall(SYS_LANDLOCK_CREATE_RULESET, uintptr(unsafe.Pointer(&attrs)), unsafe.Sizeof(attrs), 0) + + if err != 0 { + return fmt.Errorf("could not create landlock ruleset: errno %v", err) + } + + defer syscall.Close(int(fd)) + + r, _, err := syscall.Syscall(SYS_LANDLOCK_RESTRICT_SELF, fd, 0, 0) + if r != 0 { + return fmt.Errorf("could not restrict self via landlock: errno %v", err) + } + return nil +} diff --git a/hst/config.go b/hst/config.go index 34100e4..0c0d974 100644 --- a/hst/config.go +++ b/hst/config.go @@ -79,6 +79,8 @@ type ( Userns bool `json:"userns,omitempty"` // share host net namespace Net bool `json:"net,omitempty"` + // disallow accessing abstract UNIX domain sockets created outside the container + ScopeAbstract bool `json:"scope_abstract,omitempty"` // allow dangerous terminal I/O Tty bool `json:"tty,omitempty"` // allow multiarch diff --git a/internal/app/container_linux.go b/internal/app/container_linux.go index 3f1259f..4f0e6b5 100644 --- a/internal/app/container_linux.go +++ b/internal/app/container_linux.go @@ -33,6 +33,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid SeccompPresets: s.SeccompPresets, RetainSession: s.Tty, HostNet: s.Net, + ScopeAbstract: s.ScopeAbstract, // the container is canceled when shim is requested to exit or receives an interrupt or termination signal; // this behaviour is implemented in the shim diff --git a/nixos.nix b/nixos.nix index 4f083bc..2544710 100644 --- a/nixos.nix +++ b/nixos.nix @@ -137,6 +137,7 @@ in multiarch env ; + scope_abstract = app.scopeAbstract; map_real_uid = app.mapRealUid; filesystem = diff --git a/options.md b/options.md index 7d6fc26..bdac871 100644 --- a/options.md +++ b/options.md @@ -572,6 +572,28 @@ boolean +*Example:* +` true ` + + +## environment\.hakurei\.apps\.\\.scopeAbstract + + + +Whether to restrict abstract UNIX domain socket access\. + + + +*Type:* +boolean + + + +*Default:* +` true ` + + + *Example:* ` true ` diff --git a/options.nix b/options.nix index a51609c..578cc88 100644 --- a/options.nix +++ b/options.nix @@ -182,6 +182,9 @@ in net = mkEnableOption "network access" // { default = true; }; + scopeAbstract = mkEnableOption "abstract unix domain socket access" // { + default = true; + }; nix = mkEnableOption "nix daemon access"; mapRealUid = mkEnableOption "mapping to priv-user uid"; diff --git a/system/dbus/proc.go b/system/dbus/proc.go index 0e5f1ce..323f4b2 100644 --- a/system/dbus/proc.go +++ b/system/dbus/proc.go @@ -64,6 +64,10 @@ func (p *Proxy) Start() error { argF, func(z *container.Container) { z.SeccompFlags |= seccomp.AllowMultiarch z.SeccompPresets |= seccomp.PresetStrict + + // xdg-dbus-proxy requires host abstract UNIX domain socket access + z.ScopeAbstract = false + z.Hostname = "hakurei-dbus" if p.output != nil { z.Stdout, z.Stderr = p.output, p.output diff --git a/test/sandbox/case/device.nix b/test/sandbox/case/device.nix index 10011d7..6a5bc42 100644 --- a/test/sandbox/case/device.nix +++ b/test/sandbox/case/device.nix @@ -243,7 +243,7 @@ in seccomp = true; try_socket = "/tmp/.X11-unix/X0"; - socket_abstract = true; + socket_abstract = false; socket_pathname = true; }; } diff --git a/test/sandbox/case/mapuid.nix b/test/sandbox/case/mapuid.nix index 18d82e7..3ef5f24 100644 --- a/test/sandbox/case/mapuid.nix +++ b/test/sandbox/case/mapuid.nix @@ -269,7 +269,7 @@ in seccomp = true; try_socket = "/tmp/.X11-unix/X0"; - socket_abstract = true; + socket_abstract = false; socket_pathname = false; }; } diff --git a/test/sandbox/case/pdlike.nix b/test/sandbox/case/pdlike.nix index da6753f..ea34147 100644 --- a/test/sandbox/case/pdlike.nix +++ b/test/sandbox/case/pdlike.nix @@ -264,7 +264,7 @@ in seccomp = true; try_socket = "/tmp/.X11-unix/X0"; - socket_abstract = true; + socket_abstract = false; socket_pathname = false; }; } diff --git a/test/sandbox/case/preset.nix b/test/sandbox/case/preset.nix index be376a1..41f612f 100644 --- a/test/sandbox/case/preset.nix +++ b/test/sandbox/case/preset.nix @@ -262,7 +262,7 @@ in seccomp = true; try_socket = "/tmp/.X11-unix/X0"; - socket_abstract = true; + socket_abstract = false; socket_pathname = false; }; } diff --git a/test/sandbox/case/tty.nix b/test/sandbox/case/tty.nix index b94350e..d75d6f1 100644 --- a/test/sandbox/case/tty.nix +++ b/test/sandbox/case/tty.nix @@ -275,7 +275,7 @@ in seccomp = true; try_socket = "/tmp/.X11-unix/X0"; - socket_abstract = true; + socket_abstract = false; socket_pathname = true; }; }