diff options
| author | Clayton Gilmer <netadr_t@outlook.com> | 2025-08-18 12:00:52 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-08-18 16:28:14 +0900 |
| commit | 5db07140726e6e1bf615ed075e9a8b653508c2a7 (patch) | |
| tree | 15cdb5ed164ec5cdc32527f87ee969ad704cdd11 /container/landlock.go | |
| parent | 69a4ab81053ef31d745ac0d92531cefb7d3a3e44 (diff) | |
container: optionally isolate host abstract UNIX domain sockets via landlock
Diffstat (limited to 'container/landlock.go')
| -rw-r--r-- | container/landlock.go | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/container/landlock.go b/container/landlock.go new file mode 100644 index 00000000..7f7461e2 --- /dev/null +++ b/container/landlock.go @@ -0,0 +1,239 @@ +package container + +import ( + "strings" + "syscall" + "unsafe" + + "hakurei.app/container/seccomp" +) + +// include/uapi/linux/landlock.h + +const ( + LANDLOCK_CREATE_RULESET_VERSION = 1 << iota +) + +type LandlockAccessFS uintptr + +const ( + LANDLOCK_ACCESS_FS_EXECUTE LandlockAccessFS = 1 << iota + LANDLOCK_ACCESS_FS_WRITE_FILE + LANDLOCK_ACCESS_FS_READ_FILE + LANDLOCK_ACCESS_FS_READ_DIR + LANDLOCK_ACCESS_FS_REMOVE_DIR + LANDLOCK_ACCESS_FS_REMOVE_FILE + LANDLOCK_ACCESS_FS_MAKE_CHAR + LANDLOCK_ACCESS_FS_MAKE_DIR + LANDLOCK_ACCESS_FS_MAKE_REG + LANDLOCK_ACCESS_FS_MAKE_SOCK + LANDLOCK_ACCESS_FS_MAKE_FIFO + LANDLOCK_ACCESS_FS_MAKE_BLOCK + LANDLOCK_ACCESS_FS_MAKE_SYM + LANDLOCK_ACCESS_FS_REFER + LANDLOCK_ACCESS_FS_TRUNCATE + LANDLOCK_ACCESS_FS_IOCTL_DEV + + _LANDLOCK_ACCESS_FS_DELIM +) + +func (f LandlockAccessFS) String() string { + switch f { + case LANDLOCK_ACCESS_FS_EXECUTE: + return "execute" + + case LANDLOCK_ACCESS_FS_WRITE_FILE: + return "write_file" + + case LANDLOCK_ACCESS_FS_READ_FILE: + return "read_file" + + case LANDLOCK_ACCESS_FS_READ_DIR: + return "read_dir" + + case LANDLOCK_ACCESS_FS_REMOVE_DIR: + return "remove_dir" + + case LANDLOCK_ACCESS_FS_REMOVE_FILE: + return "remove_file" + + case LANDLOCK_ACCESS_FS_MAKE_CHAR: + return "make_char" + + case LANDLOCK_ACCESS_FS_MAKE_DIR: + return "make_dir" + + case LANDLOCK_ACCESS_FS_MAKE_REG: + return "make_reg" + + case LANDLOCK_ACCESS_FS_MAKE_SOCK: + return "make_sock" + + case LANDLOCK_ACCESS_FS_MAKE_FIFO: + return "make_fifo" + + case LANDLOCK_ACCESS_FS_MAKE_BLOCK: + return "make_block" + + case LANDLOCK_ACCESS_FS_MAKE_SYM: + return "make_sym" + + case LANDLOCK_ACCESS_FS_REFER: + return "fs_refer" + + case LANDLOCK_ACCESS_FS_TRUNCATE: + return "fs_truncate" + + case LANDLOCK_ACCESS_FS_IOCTL_DEV: + return "fs_ioctl_dev" + + default: + var c []LandlockAccessFS + for i := LandlockAccessFS(1); i < _LANDLOCK_ACCESS_FS_DELIM; i <<= 1 { + if f&i != 0 { + c = append(c, i) + } + } + if len(c) == 0 { + return "NULL" + } + s := make([]string, len(c)) + for i, v := range c { + s[i] = v.String() + } + return strings.Join(s, " ") + } +} + +type LandlockAccessNet uintptr + +const ( + LANDLOCK_ACCESS_NET_BIND_TCP LandlockAccessNet = 1 << iota + LANDLOCK_ACCESS_NET_CONNECT_TCP + + _LANDLOCK_ACCESS_NET_DELIM +) + +func (f LandlockAccessNet) String() string { + switch f { + case LANDLOCK_ACCESS_NET_BIND_TCP: + return "bind_tcp" + + case LANDLOCK_ACCESS_NET_CONNECT_TCP: + return "connect_tcp" + + default: + var c []LandlockAccessNet + for i := LandlockAccessNet(1); i < _LANDLOCK_ACCESS_NET_DELIM; i <<= 1 { + if f&i != 0 { + c = append(c, i) + } + } + if len(c) == 0 { + return "NULL" + } + s := make([]string, len(c)) + for i, v := range c { + s[i] = v.String() + } + return strings.Join(s, " ") + } +} + +type LandlockScope uintptr + +const ( + LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET LandlockScope = 1 << iota + LANDLOCK_SCOPE_SIGNAL + + _LANDLOCK_SCOPE_DELIM +) + +func (f LandlockScope) String() string { + switch f { + case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: + return "abstract_unix_socket" + + case LANDLOCK_SCOPE_SIGNAL: + return "signal" + + default: + var c []LandlockScope + for i := LandlockScope(1); i < _LANDLOCK_SCOPE_DELIM; i <<= 1 { + if f&i != 0 { + c = append(c, i) + } + } + if len(c) == 0 { + return "NULL" + } + s := make([]string, len(c)) + for i, v := range c { + s[i] = v.String() + } + return strings.Join(s, " ") + } +} + +type RulesetAttr struct { + // Bitmask of handled filesystem actions. + HandledAccessFS LandlockAccessFS + // Bitmask of handled network actions. + HandledAccessNet LandlockAccessNet + // Bitmask of scopes restricting a Landlock domain from accessing outside resources (e.g. IPCs). + Scoped LandlockScope +} + +func (rulesetAttr *RulesetAttr) String() string { + if rulesetAttr == nil { + return "NULL" + } + elems := make([]string, 0, 3) + if rulesetAttr.HandledAccessFS > 0 { + elems = append(elems, "fs: "+rulesetAttr.HandledAccessFS.String()) + } + if rulesetAttr.HandledAccessNet > 0 { + elems = append(elems, "net: "+rulesetAttr.HandledAccessNet.String()) + } + if rulesetAttr.Scoped > 0 { + elems = append(elems, "scoped: "+rulesetAttr.Scoped.String()) + } + if len(elems) == 0 { + return "0" + } + return strings.Join(elems, ", ") +} + +func (rulesetAttr *RulesetAttr) Create(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 (*RulesetAttr)(nil).Create(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 +} |
