Compare commits

..

1 Commits

Author SHA1 Message Date
55d8c3bacb
container: optionally isolate host abstract UNIX domain sockets via landlock
Some checks failed
Test / Create distribution (pull_request) Failing after 32s
Test / Hpkg (push) Has been cancelled
Test / Flake checks (push) Has been cancelled
Test / Create distribution (push) Has been cancelled
Test / Sandbox (push) Has started running
Test / Hakurei (push) Has been cancelled
Test / Hakurei (race detector) (push) Has been cancelled
Test / Sandbox (race detector) (push) Has been cancelled
Test / Hakurei (pull_request) Failing after 3m7s
Test / Sandbox (pull_request) Failing after 3m27s
Test / Sandbox (race detector) (pull_request) Failing after 4m3s
Test / Hpkg (pull_request) Successful in 4m16s
Test / Hakurei (race detector) (pull_request) Failing after 5m7s
Test / Flake checks (pull_request) Has been skipped
2025-08-18 11:48:03 +09:00
14 changed files with 53 additions and 54 deletions

View File

@ -92,6 +92,8 @@ type (
RetainSession bool RetainSession bool
// Do not [syscall.CLONE_NEWNET]. // Do not [syscall.CLONE_NEWNET].
HostNet bool HostNet bool
// Scope abstract UNIX domain sockets using LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET.
ScopeAbstract bool
// Retain CAP_SYS_ADMIN. // Retain CAP_SYS_ADMIN.
Privileged bool Privileged bool
} }

View File

@ -13,6 +13,7 @@ import (
. "syscall" . "syscall"
"time" "time"
"hakurei.app/container/landlock"
"hakurei.app/container/seccomp" "hakurei.app/container/seccomp"
) )
@ -260,6 +261,12 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
msg.Verbose("syscall filter not configured") msg.Verbose("syscall filter not configured")
} }
if params.ScopeAbstract {
if err := landlock.ScopeAbstract(); err != nil {
log.Fatalf("could not scope abstract unix sockets: %v", err)
}
}
extraFiles := make([]*os.File, params.Count) extraFiles := make([]*os.File, params.Count)
for i := range extraFiles { for i := range extraFiles {
// setup fd is placed before all extra files // setup fd is placed before all extra files

View File

@ -0,0 +1,14 @@
#include <errno.h>
#include <linux/landlock.h>
#include <sys/psx_syscall.h>
#include <sys/syscall.h>
#include "landlock-helper.h"
int hakurei_scope_abstract_unix_sockets(int* p_errno, int fd) {
int res = psx_syscall3(SYS_landlock_restrict_self, fd, 0, 0);
*p_errno = errno;
return res;
}

View File

@ -0,0 +1,3 @@
#pragma once
int hakurei_scope_abstract_unix_sockets(int* p_errno, int fd);

View File

@ -1,8 +1,12 @@
package landlock package landlock
/* /*
#cgo linux pkg-config: --static libpsx
#include <linux/landlock.h> #include <linux/landlock.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include "landlock-helper.h"
*/ */
import "C" import "C"
@ -17,13 +21,10 @@ const (
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = C.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET = C.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
SYS_LANDLOCK_CREATE_RULESET = C.SYS_landlock_create_ruleset 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 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 { func ScopeAbstract() error {
abi, _, err := syscall.Syscall(SYS_LANDLOCK_CREATE_RULESET, 0, 0, LANDLOCK_CREATE_RULESET_VERSION) abi, _, err := syscall.Syscall(SYS_LANDLOCK_CREATE_RULESET, 0, 0, LANDLOCK_CREATE_RULESET_VERSION)
@ -47,9 +48,10 @@ func ScopeAbstract() error {
defer syscall.Close(int(fd)) defer syscall.Close(int(fd))
r, _, err := syscall.Syscall(SYS_LANDLOCK_RESTRICT_SELF, fd, 0, 0) var errno C.int
if r != 0 { if rv := C.hakurei_scope_abstract_unix_sockets(&errno, C.int(fd)); rv != 0 {
return fmt.Errorf("could not restrict self via landlock: errno %v", err) return fmt.Errorf("could not restrict self via landlock: errno %v", errno)
} }
return nil return nil
} }

View File

@ -416,22 +416,6 @@ func (seal *outcome) finalise(ctx context.Context, sys sys.State, config *hst.Co
seal.sys.ChangeHosts("#" + seal.user.uid.String()) seal.sys.ChangeHosts("#" + seal.user.uid.String())
seal.env[display] = d seal.env[display] = d
seal.container.Bind(socketDir, socketDir, 0) seal.container.Bind(socketDir, socketDir, 0)
// the socket file at `/tmp/.X11-unix/X%d` is typically owned by the priv user
// and not accessible by the target user
var socketPath *container.Absolute
if len(d) > 1 && d[0] == ':' { // `:%d`
if n, err := strconv.Atoi(d[1:]); err == nil && n >= 0 {
socketPath = socketDir.Append("X" + strconv.Itoa(n))
}
} else if len(d) > 5 && strings.HasPrefix(d, "unix:") { // `unix:%s`
if a, err := container.NewAbs(d[5:]); err == nil {
socketPath = a
}
}
if socketPath != nil {
seal.sys.UpdatePermTypeOptional(system.EX11, socketPath.String(), acl.Read, acl.Write, acl.Execute)
}
} }
} }

View File

@ -5,6 +5,7 @@
makeBinaryWrapper, makeBinaryWrapper,
xdg-dbus-proxy, xdg-dbus-proxy,
pkg-config, pkg-config,
libcap,
libffi, libffi,
libseccomp, libseccomp,
acl, acl,
@ -80,10 +81,16 @@ buildGoModule rec {
hsu = "/run/wrappers/bin/hsu"; hsu = "/run/wrappers/bin/hsu";
}; };
env = {
# required by libpsx
CGO_LDFLAGS_ALLOW = "-Wl,(--no-whole-archive|--whole-archive)";
# nix build environment does not allow acls # nix build environment does not allow acls
env.GO_TEST_SKIP_ACL = 1; GO_TEST_SKIP_ACL = 1;
};
buildInputs = [ buildInputs = [
libcap
libffi libffi
libseccomp libseccomp
acl acl

View File

@ -21,17 +21,7 @@ func (sys *I) UpdatePermType(et Enablement, path string, perms ...acl.Perm) *I {
sys.lock.Lock() sys.lock.Lock()
defer sys.lock.Unlock() defer sys.lock.Unlock()
sys.ops = append(sys.ops, &ACL{et, path, perms, false}) sys.ops = append(sys.ops, &ACL{et, path, perms})
return sys
}
// UpdatePermTypeOptional appends an acl update Op that silently continues if the target does not exist.
func (sys *I) UpdatePermTypeOptional(et Enablement, path string, perms ...acl.Perm) *I {
sys.lock.Lock()
defer sys.lock.Unlock()
sys.ops = append(sys.ops, &ACL{et, path, perms, true})
return sys return sys
} }
@ -40,24 +30,14 @@ type ACL struct {
et Enablement et Enablement
path string path string
perms acl.Perms perms acl.Perms
// since revert operations are cross-process, the success of apply must not affect the outcome of revert
skipNotExist bool
} }
func (a *ACL) Type() Enablement { return a.et } func (a *ACL) Type() Enablement { return a.et }
func (a *ACL) apply(sys *I) error { func (a *ACL) apply(sys *I) error {
msg.Verbose("applying ACL", a) msg.Verbose("applying ACL", a)
if err := acl.Update(a.path, sys.uid, a.perms...); err != nil { return wrapErrSuffix(acl.Update(a.path, sys.uid, a.perms...),
if !a.skipNotExist || !os.IsNotExist(err) {
return wrapErrSuffix(err,
fmt.Sprintf("cannot apply ACL entry to %q:", a.path)) fmt.Sprintf("cannot apply ACL entry to %q:", a.path))
}
msg.Verbosef("path %q does not exist", a.path)
return nil
}
return nil
} }
func (a *ACL) revert(sys *I, ec *Criteria) error { func (a *ACL) revert(sys *I, ec *Criteria) error {

View File

@ -20,7 +20,7 @@ func TestUpdatePerm(t *testing.T) {
t.Run(tc.path+permSubTestSuffix(tc.perms), func(t *testing.T) { t.Run(tc.path+permSubTestSuffix(tc.perms), func(t *testing.T) {
sys := New(150) sys := New(150)
sys.UpdatePerm(tc.path, tc.perms...) sys.UpdatePerm(tc.path, tc.perms...)
(&tcOp{Process, tc.path}).test(t, sys.ops, []Op{&ACL{Process, tc.path, tc.perms, false}}, "UpdatePerm") (&tcOp{Process, tc.path}).test(t, sys.ops, []Op{&ACL{Process, tc.path, tc.perms}}, "UpdatePerm")
}) })
} }
} }
@ -42,7 +42,7 @@ func TestUpdatePermType(t *testing.T) {
t.Run(tc.path+"_"+TypeString(tc.et)+permSubTestSuffix(tc.perms), func(t *testing.T) { t.Run(tc.path+"_"+TypeString(tc.et)+permSubTestSuffix(tc.perms), func(t *testing.T) {
sys := New(150) sys := New(150)
sys.UpdatePermType(tc.et, tc.path, tc.perms...) sys.UpdatePermType(tc.et, tc.path, tc.perms...)
tc.test(t, sys.ops, []Op{&ACL{tc.et, tc.path, tc.perms, false}}, "UpdatePermType") tc.test(t, sys.ops, []Op{&ACL{tc.et, tc.path, tc.perms}}, "UpdatePermType")
}) })
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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