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 52691ce..9d7d428 100644 --- a/container/container.go +++ b/container/container.go @@ -92,6 +92,8 @@ type ( RetainSession bool // Do not [syscall.CLONE_NEWNET]. HostNet bool + // Do not [LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET]. + HostAbstract bool // Retain CAP_SYS_ADMIN. Privileged bool } @@ -185,6 +187,53 @@ func (p *Container) Start() error { "prctl(PR_SET_NO_NEW_PRIVS):") } + // landlock: depends on per-thread state but acts on a process group + { + scoped := LANDLOCK_SCOPE_SIGNAL + if !p.HostAbstract { + scoped |= LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET + } + rulesetAttr := NewRulesetAttr(scoped) + + if abi, err := LandlockGetABI(); err != nil { + if p.HostAbstract { + // landlock can be skipped here as it restricts access to resources + // already covered by namespaces (pid) + goto landlockOut + } + return wrapErrSuffix(err, + "landlock does not appear to be enabled:") + } else if abi < 6 { + if p.HostAbstract { + // see above comment + goto landlockOut + } + return msg.WrapErr(ENOSYS, + "kernel version too old for LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET") + } else { + msg.Verbosef("landlock abi version %d", abi) + } + + msg.Verbosef("creating landlock ruleset scoped %#x", int(scoped)) + if rulesetFd, err := LandlockCreateRuleset(&rulesetAttr, 0); err != nil { + return wrapErrSuffix(err, + "cannot create landlock ruleset:") + } else { + msg.Verbose("enforcing landlock ruleset") + if err = LandlockRestrictSelf(rulesetFd, 0); err != nil { + _ = Close(rulesetFd) + return wrapErrSuffix(err, + "cannot enforce landlock ruleset:") + } + if err = Close(rulesetFd); err != nil { + msg.Verbosef("cannot close landlock ruleset: %v", err) + // not fatal + } + } + + landlockOut: + } + msg.Verbose("starting container init") if err := p.cmd.Start(); err != nil { return msg.WrapErr(err, err.Error()) diff --git a/container/landlock.go b/container/landlock.go new file mode 100644 index 0000000..5ca9d51 --- /dev/null +++ b/container/landlock.go @@ -0,0 +1,59 @@ +package container + +/* +#include +#include +*/ +import "C" +import ( + "syscall" + "unsafe" + + "hakurei.app/container/seccomp" +) + +const ( + LANDLOCK_CREATE_RULESET_VERSION = C.LANDLOCK_CREATE_RULESET_VERSION + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = C.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET + LANDLOCK_SCOPE_SIGNAL = C.LANDLOCK_SCOPE_SIGNAL +) + +type RulesetAttr = C.struct_landlock_ruleset_attr + +func NewRulesetAttr(scoped int) RulesetAttr { return RulesetAttr{scoped: C.__u64(scoped)} } + +/* TODO: remove everything above this */ + +func LandlockCreateRuleset(rulesetAttr *RulesetAttr, flags uintptr) (fd int, err error) { + var pointer, size uintptr + // NULL needed for abi version + if rulesetAttr != nil { + pointer = uintptr(unsafe.Pointer(rulesetAttr)) + size = unsafe.Sizeof(*rulesetAttr) + } + + rulesetFd, _, errno := syscall.Syscall(seccomp.SYS_LANDLOCK_CREATE_RULESET, pointer, size, flags) + fd = int(rulesetFd) + err = errno + + if fd < 0 { + return + } + + if rulesetAttr != nil { // not a fd otherwise + syscall.CloseOnExec(fd) + } + return fd, nil +} + +func LandlockGetABI() (int, error) { + return LandlockCreateRuleset(nil, LANDLOCK_CREATE_RULESET_VERSION) +} + +func LandlockRestrictSelf(rulesetFd int, flags uintptr) error { + r, _, errno := syscall.Syscall(seccomp.SYS_LANDLOCK_RESTRICT_SELF, uintptr(rulesetFd), flags, 0) + if r != 0 { + return errno + } + return nil +} diff --git a/hst/config.go b/hst/config.go index 34100e4..d6f07be 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"` + // share abstract unix socket scope + Abstract bool `json:"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..7f08819 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, + HostAbstract: s.Abstract, // 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..6607ef1 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 fails with scoped abstract unix sockets despite pathname socket being available + z.HostAbstract = true + 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 e9cf724..c1a2f5f 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 ff7bf03..d95cbb8 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; }; }