container: optionally isolate host abstract UNIX domain sockets via landlock
Some checks failed
Test / Create distribution (push) Successful in 36s
Test / Create distribution (pull_request) Successful in 31s
Test / Sandbox (pull_request) Successful in 2m13s
Test / Sandbox (push) Successful in 2m20s
Test / Hpkg (push) Successful in 4m6s
Test / Hpkg (pull_request) Successful in 3m59s
Test / Sandbox (race detector) (pull_request) Successful in 4m15s
Test / Sandbox (race detector) (push) Successful in 4m27s
Test / Hakurei (race detector) (push) Failing after 22m30s
Test / Flake checks (pull_request) Has been cancelled
Test / Hakurei (pull_request) Has been cancelled
Test / Hakurei (race detector) (pull_request) Has been cancelled
Test / Hakurei (push) Failing after 39m52s
Test / Flake checks (push) Has been skipped

This commit is contained in:
Clayton Gilmer 2025-08-17 13:27:39 +09:00 committed by Ophestra
parent 551ec8c27d
commit c1ff73b1b1
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
14 changed files with 105 additions and 5 deletions

View File

@ -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"`

View File

@ -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())

View File

@ -0,0 +1,55 @@
package landlock
/*
#include <linux/landlock.h>
#include <sys/syscall.h>
*/
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
}

View File

@ -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

View File

@ -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

View File

@ -137,6 +137,7 @@ in
multiarch
env
;
scope_abstract = app.scopeAbstract;
map_real_uid = app.mapRealUid;
filesystem =

View File

@ -572,6 +572,28 @@ boolean
*Example:*
` true `
## environment\.hakurei\.apps\.\<name>\.scopeAbstract
Whether to restrict abstract UNIX domain socket access\.
*Type:*
boolean
*Default:*
` true `
*Example:*
` true `

View File

@ -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";

View File

@ -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

View File

@ -243,7 +243,7 @@ in
seccomp = true;
try_socket = "/tmp/.X11-unix/X0";
socket_abstract = true;
socket_abstract = false;
socket_pathname = true;
};
}

View File

@ -269,7 +269,7 @@ in
seccomp = true;
try_socket = "/tmp/.X11-unix/X0";
socket_abstract = true;
socket_abstract = false;
socket_pathname = false;
};
}

View File

@ -264,7 +264,7 @@ in
seccomp = true;
try_socket = "/tmp/.X11-unix/X0";
socket_abstract = true;
socket_abstract = false;
socket_pathname = false;
};
}

View File

@ -262,7 +262,7 @@ in
seccomp = true;
try_socket = "/tmp/.X11-unix/X0";
socket_abstract = true;
socket_abstract = false;
socket_pathname = false;
};
}

View File

@ -275,7 +275,7 @@ in
seccomp = true;
try_socket = "/tmp/.X11-unix/X0";
socket_abstract = true;
socket_abstract = false;
socket_pathname = true;
};
}