Compare commits

..

1 Commits

Author SHA1 Message Date
a6b2b9df22
container: optionally isolate host abstract UNIX domain sockets via landlock
Some checks failed
Test / Create distribution (push) Successful in 38s
Test / Create distribution (pull_request) Successful in 33s
Test / Sandbox (pull_request) Successful in 2m16s
Test / Sandbox (push) Successful in 2m26s
Test / Hakurei (pull_request) Successful in 3m9s
Test / Hakurei (push) Successful in 3m19s
Test / Hpkg (push) Successful in 4m25s
Test / Hpkg (pull_request) Successful in 4m20s
Test / Sandbox (race detector) (push) Successful in 4m41s
Test / Sandbox (race detector) (pull_request) Successful in 4m36s
Test / Hakurei (race detector) (pull_request) Successful in 5m10s
Test / Hakurei (race detector) (push) Successful in 5m16s
Test / Flake checks (push) Has been cancelled
Test / Flake checks (pull_request) Successful in 1m43s
2025-08-18 12:00:52 +09:00
5 changed files with 52 additions and 60 deletions

View File

@ -7,13 +7,13 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
"strconv" "strconv"
. "syscall" . "syscall"
"time" "time"
"unsafe"
"hakurei.app/container/landlock" "hakurei.app/container/landlock"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
@ -94,8 +94,8 @@ type (
RetainSession bool RetainSession bool
// Do not [syscall.CLONE_NEWNET]. // Do not [syscall.CLONE_NEWNET].
HostNet bool HostNet bool
// Do not [landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET]. // Scope abstract UNIX domain sockets using LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET.
HostAbstract bool ScopeAbstract bool
// Retain CAP_SYS_ADMIN. // Retain CAP_SYS_ADMIN.
Privileged bool Privileged bool
} }
@ -189,56 +189,10 @@ func (p *Container) Start() error {
"prctl(PR_SET_NO_NEW_PRIVS):") "prctl(PR_SET_NO_NEW_PRIVS):")
} }
// landlock: depends on per-thread state but acts on a process group if p.ScopeAbstract {
{ if err := landlock.ScopeAbstract(); err != nil {
scoped := landlock.LANDLOCK_SCOPE_SIGNAL log.Fatalf("could not scope abstract unix sockets: %v", err)
if !p.HostAbstract {
scoped |= landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
} }
rulesetAttr := landlock.NewRulesetAttr(scoped)
if abi, _, errno := Syscall(seccomp.SYS_LANDLOCK_CREATE_RULESET, 0, 0, landlock.LANDLOCK_CREATE_RULESET_VERSION); abi < 0 {
if p.HostAbstract {
// landlock can be skipped here as it restricts access to resources
// already covered by namespaces (pid)
goto landlockOut
}
return wrapErrSuffix(errno,
"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)
}
if rulesetFd, _, errno := Syscall(seccomp.SYS_LANDLOCK_CREATE_RULESET,
uintptr(unsafe.Pointer(&rulesetAttr)),
unsafe.Sizeof(rulesetAttr), 0,
); rulesetFd < 0 {
return wrapErrSuffix(errno,
"cannot create landlock ruleset:")
} else {
CloseOnExec(int(rulesetFd))
var r uintptr
if r, _, errno = Syscall(seccomp.SYS_LANDLOCK_RESTRICT_SELF, rulesetFd, 0, 0); r != 0 {
_ = Close(int(rulesetFd))
return wrapErrSuffix(errno,
"cannot enforce landlock ruleset:")
}
msg.Verbosef("enforced landlock ruleset scoped %#x", int(scoped))
if err := Close(int(rulesetFd)); err != nil {
msg.Verbosef("cannot close landlock ruleset: %v", err)
// not fatal
}
}
landlockOut:
} }
msg.Verbose("starting container init") msg.Verbose("starting container init")

View File

@ -6,12 +6,50 @@ package landlock
*/ */
import "C" import "C"
import (
"fmt"
"syscall"
"unsafe"
)
const ( const (
LANDLOCK_CREATE_RULESET_VERSION = C.LANDLOCK_CREATE_RULESET_VERSION LANDLOCK_CREATE_RULESET_VERSION = C.LANDLOCK_CREATE_RULESET_VERSION
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = C.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = C.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
LANDLOCK_SCOPE_SIGNAL = C.LANDLOCK_SCOPE_SIGNAL
SYS_LANDLOCK_CREATE_RULESET = C.SYS_landlock_create_ruleset
SYS_LANDLOCK_RESTRICT_SELF = C.SYS_landlock_restrict_self
) )
type RulesetAttr = C.struct_landlock_ruleset_attr type LandlockRulesetAttr = C.struct_landlock_ruleset_attr
func NewRulesetAttr(scoped int) RulesetAttr { return RulesetAttr{scoped: C.__u64(scoped)} } // 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,8 +79,8 @@ type (
Userns bool `json:"userns,omitempty"` Userns bool `json:"userns,omitempty"`
// share host net namespace // share host net namespace
Net bool `json:"net,omitempty"` Net bool `json:"net,omitempty"`
// share abstract unix socket scope // disallow accessing abstract UNIX domain sockets created outside the container
Abstract bool `json:"abstract,omitempty"` ScopeAbstract bool `json:"scope_abstract,omitempty"`
// allow dangerous terminal I/O // allow dangerous terminal I/O
Tty bool `json:"tty,omitempty"` Tty bool `json:"tty,omitempty"`
// allow multiarch // allow multiarch

View File

@ -33,7 +33,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, prefix string, uid, gid
SeccompPresets: s.SeccompPresets, SeccompPresets: s.SeccompPresets,
RetainSession: s.Tty, RetainSession: s.Tty,
HostNet: s.Net, HostNet: s.Net,
HostAbstract: s.Abstract, ScopeAbstract: s.ScopeAbstract,
// the container is canceled when shim is requested to exit or receives an interrupt or termination signal; // the container is canceled when shim is requested to exit or receives an interrupt or termination signal;
// this behaviour is implemented in the shim // this behaviour is implemented in the shim

View File

@ -65,8 +65,8 @@ func (p *Proxy) Start() error {
z.SeccompFlags |= seccomp.AllowMultiarch z.SeccompFlags |= seccomp.AllowMultiarch
z.SeccompPresets |= seccomp.PresetStrict z.SeccompPresets |= seccomp.PresetStrict
// xdg-dbus-proxy fails with scoped abstract unix sockets despite pathname socket being available // xdg-dbus-proxy requires host abstract UNIX domain socket access
z.HostAbstract = true z.ScopeAbstract = false
z.Hostname = "hakurei-dbus" z.Hostname = "hakurei-dbus"
if p.output != nil { if p.output != nil {