container: optionally isolate host abstract UNIX domain sockets via landlock
Some checks failed
Test / Create distribution (push) Failing after 52s
Test / Sandbox (push) Successful in 2m22s
Test / Sandbox (race detector) (push) Successful in 4m4s
Test / Planterette (push) Successful in 4m3s
Test / Create distribution (pull_request) Failing after 29s
Test / Sandbox (pull_request) Successful in 40s
Test / Sandbox (race detector) (pull_request) Successful in 41s
Test / Planterette (pull_request) Successful in 41s
Test / Hakurei (push) Failing after 20m46s
Test / Hakurei (race detector) (push) Failing after 22m7s
Test / Flake checks (push) Has been skipped
Test / Hakurei (pull_request) Failing after 34m54s
Test / Hakurei (race detector) (pull_request) Failing after 36m37s
Test / Flake checks (pull_request) Has been skipped

This commit is contained in:
Clayton Gilmer 2025-08-17 13:27:39 +09:00 committed by Ophestra
parent 6ba19a7ba5
commit 7add29b79c
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
13 changed files with 127 additions and 2 deletions

View File

@ -29,6 +29,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

@ -83,6 +83,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
}

View File

@ -13,6 +13,7 @@ import (
. "syscall"
"time"
"hakurei.app/container/landlock"
"hakurei.app/container/seccomp"
)
@ -256,6 +257,12 @@ func Init(prepare func(prefix string), setVerbose func(verbose bool)) {
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)
for i := range extraFiles {
// 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

@ -0,0 +1,57 @@
package landlock
/*
#cgo linux pkg-config: --static libpsx
#include <linux/landlock.h>
#include <sys/syscall.h>
#include "landlock-helper.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
)
type LandlockRulesetAttr = C.struct_landlock_ruleset_attr
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))
var errno C.int
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", errno)
}
return nil
}

View File

@ -22,6 +22,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

@ -32,6 +32,7 @@ func newContainer(s *hst.ContainerConfig, os sys.State, uid, gid *int) (*contain
SeccompPresets: s.SeccompPresets,
RetainSession: s.Tty,
HostNet: s.Net,
ScopeAbstract: s.ScopeAbstract,
}
{

View File

@ -135,6 +135,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

@ -203,6 +203,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

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

View File

@ -68,6 +68,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"
z.CommandContext = p.CommandContext
if p.output != nil {