diff options
Diffstat (limited to 'container')
| -rw-r--r-- | container/container.go | 4 | ||||
| -rw-r--r-- | container/container_test.go | 9 | ||||
| -rw-r--r-- | container/dispatcher.go | 16 | ||||
| -rw-r--r-- | container/dispatcher_test.go | 3 | ||||
| -rw-r--r-- | container/init.go | 4 | ||||
| -rw-r--r-- | container/mount.go | 2 | ||||
| -rw-r--r-- | container/seccomp/libseccomp-helper.c | 218 | ||||
| -rw-r--r-- | container/seccomp/libseccomp-helper.h | 23 | ||||
| -rw-r--r-- | container/seccomp/presets.go | 159 | ||||
| -rw-r--r-- | container/syscall.go | 2 | ||||
| -rw-r--r-- | container/syscall_386.go | 7 | ||||
| -rw-r--r-- | container/syscall_amd64.go | 7 | ||||
| -rw-r--r-- | container/sysctl.go | 69 | ||||
| -rw-r--r-- | container/sysctl_deprecated.go | 19 |
14 files changed, 288 insertions, 254 deletions
diff --git a/container/container.go b/container/container.go index 3571d04a..6929c652 100644 --- a/container/container.go +++ b/container/container.go @@ -231,10 +231,10 @@ func (p *Container) Start() error { // map to overflow id to work around ownership checks if p.Uid < 1 { - p.Uid = OverflowUid(p.msg) + p.Uid = OverflowUID() } if p.Gid < 1 { - p.Gid = OverflowGid(p.msg) + p.Gid = OverflowGID() } if !p.RetainSession { diff --git a/container/container_test.go b/container/container_test.go index ca00fe7a..e70e23d3 100644 --- a/container/container_test.go +++ b/container/container_test.go @@ -342,8 +342,7 @@ var containerTestCases = []struct { ",upperdir="+ toHost(ctx.Value(testVal("upper")).(*check.Absolute).String())+ ",workdir="+ - toHost(ctx.Value(testVal("work")).(*check.Absolute).String())+ - ",redirect_dir=nofollow,uuid=on,userxattr"), + toHost(ctx.Value(testVal("work")).(*check.Absolute).String())), } }, 1 << 3, 1 << 14, nil, 0, std.PresetStrict}, @@ -396,8 +395,7 @@ var containerTestCases = []struct { ",lowerdir+="+ toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+ ",lowerdir+="+ - toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+ - ",redirect_dir=nofollow,userxattr"), + toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())), } }, 1 << 3, 1 << 14, nil, 0, std.PresetStrict}, @@ -828,6 +826,9 @@ func init() { for _, s := range []string{ "seclabel", "inode64", + "redirect_dir=nofollow", + "uuid=on", + "userxattr", } { cur.FsOptstr = strings.Replace(cur.FsOptstr, ","+s, "", 1) mnt[i].FsOptstr = strings.Replace(mnt[i].FsOptstr, ","+s, "", 1) diff --git a/container/dispatcher.go b/container/dispatcher.go index 4636a469..ba5e9c10 100644 --- a/container/dispatcher.go +++ b/container/dispatcher.go @@ -37,15 +37,15 @@ type syscallDispatcher interface { // lockOSThread provides [runtime.LockOSThread]. lockOSThread() - // setPtracer provides [SetPtracer]. + // setPtracer provides [ext.SetPtracer]. setPtracer(pid uintptr) error - // setDumpable provides [SetDumpable]. + // setDumpable provides [ext.SetDumpable]. setDumpable(dumpable uintptr) error - // setNoNewPrivs provides [SetNoNewPrivs]. + // setNoNewPrivs provides setNoNewPrivs. setNoNewPrivs() error - // lastcap provides [LastCap]. - lastcap(msg message.Msg) uintptr + // lastcap provides lastcap. + lastcap() uintptr // capset provides capset. capset(hdrp *capHeader, datap *[2]capData) error // capBoundingSetDrop provides capBoundingSetDrop. @@ -54,9 +54,9 @@ type syscallDispatcher interface { capAmbientClearAll() error // capAmbientRaise provides capAmbientRaise. capAmbientRaise(cap uintptr) error - // isatty provides [Isatty]. + // isatty provides [ext.Isatty]. isatty(fd int) bool - // receive provides [Receive]. + // receive provides [params.Receive]. receive(key string, e any, fdp *int) (closeFunc func() error, err error) // bindMount provides procPaths.bindMount. @@ -152,7 +152,7 @@ func (direct) setPtracer(pid uintptr) error { return ext.SetPtracer(pid) } func (direct) setDumpable(dumpable uintptr) error { return ext.SetDumpable(dumpable) } func (direct) setNoNewPrivs() error { return setNoNewPrivs() } -func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) } +func (direct) lastcap() uintptr { return lastcap() } func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) } func (direct) capBoundingSetDrop(cap uintptr) error { return capBoundingSetDrop(cap) } func (direct) capAmbientClearAll() error { return capAmbientClearAll() } diff --git a/container/dispatcher_test.go b/container/dispatcher_test.go index 242af1bf..9276a2c5 100644 --- a/container/dispatcher_test.go +++ b/container/dispatcher_test.go @@ -352,9 +352,8 @@ func (k *kstub) setDumpable(dumpable uintptr) error { } func (k *kstub) setNoNewPrivs() error { k.Helper(); return k.Expects("setNoNewPrivs").Err } -func (k *kstub) lastcap(msg message.Msg) uintptr { +func (k *kstub) lastcap() uintptr { k.Helper() - k.checkMsg(msg) return k.Expects("lastcap").Ret.(uintptr) } diff --git a/container/init.go b/container/init.go index 07fff357..03501646 100644 --- a/container/init.go +++ b/container/init.go @@ -226,7 +226,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) { } // cache sysctl before pivot_root - lastcap := k.lastcap(msg) + lc := k.lastcap() if err := k.mount(zeroString, fhs.Root, zeroString, MS_SILENT|MS_SLAVE|MS_REC, zeroString); err != nil { k.fatalf(msg, "cannot make / rslave: %v", optionalErrorUnwrap(err)) @@ -399,7 +399,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) { if err := k.capAmbientClearAll(); err != nil { k.fatalf(msg, "cannot clear the ambient capability set: %v", err) } - for i := range lastcap + 1 { + for i := range lc + 1 { if slices.Contains(keepCaps, i) { continue } diff --git a/container/mount.go b/container/mount.go index 84415fa8..978b90a9 100644 --- a/container/mount.go +++ b/container/mount.go @@ -123,7 +123,7 @@ func (p procPaths) remount(msg message.Msg, target string, flags uintptr) error { var destFd int if err := ext.IgnoringEINTR(func() (err error) { - destFd, err = p.k.open(targetFinal, O_PATH|O_CLOEXEC, 0) + destFd, err = p.k.open(targetFinal, ext.O_PATH|O_CLOEXEC, 0) return }); err != nil { return &os.PathError{Op: "open", Path: targetFinal, Err: err} diff --git a/container/seccomp/libseccomp-helper.c b/container/seccomp/libseccomp-helper.c index de8abc8d..7c640bac 100644 --- a/container/seccomp/libseccomp-helper.c +++ b/container/seccomp/libseccomp-helper.c @@ -9,130 +9,142 @@ #define LEN(arr) (sizeof(arr) / sizeof((arr)[0])) -int32_t hakurei_scmp_make_filter( - int *ret_p, uintptr_t allocate_p, - uint32_t arch, uint32_t multiarch, - struct hakurei_syscall_rule *rules, - size_t rules_sz, hakurei_export_flag flags) { - int i; - int last_allowed_family; - int disallowed; - struct hakurei_syscall_rule *rule; - void *buf; - size_t len = 0; +int32_t hakurei_scmp_make_filter(int *ret_p, uintptr_t allocate_p, + uint32_t arch, uint32_t multiarch, + struct hakurei_syscall_rule *rules, + size_t rules_sz, hakurei_export_flag flags) +{ + int i; + int last_allowed_family; + int disallowed; + struct hakurei_syscall_rule *rule; + void *buf; + size_t len = 0; - int32_t res = 0; /* refer to resPrefix for message */ + int32_t res = 0; /* refer to resPrefix for message */ - /* Blocklist all but unix, inet, inet6 and netlink */ - struct { - int family; - hakurei_export_flag flags_mask; - } socket_family_allowlist[] = { - /* NOTE: Keep in numerical order */ - {AF_UNSPEC, 0}, - {AF_LOCAL, 0}, - {AF_INET, 0}, - {AF_INET6, 0}, - {AF_NETLINK, 0}, - {AF_CAN, HAKUREI_EXPORT_CAN}, - {AF_BLUETOOTH, HAKUREI_EXPORT_BLUETOOTH}, - }; + /* Blocklist all but unix, inet, inet6 and netlink */ + struct { + int family; + hakurei_export_flag flags_mask; + } socket_family_allowlist[] = { + /* NOTE: Keep in numerical order */ + { AF_UNSPEC, 0 }, + { AF_LOCAL, 0 }, + { AF_INET, 0 }, + { AF_INET6, 0 }, + { AF_NETLINK, 0 }, + { AF_CAN, HAKUREI_EXPORT_CAN }, + { AF_BLUETOOTH, HAKUREI_EXPORT_BLUETOOTH }, + }; - scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); - if (ctx == NULL) { - res = 1; - goto out; - } else - errno = 0; + scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW); + if (ctx == NULL) { + res = 1; + goto out; + } else + errno = 0; - /* We only really need to handle arches on multiarch systems. + /* We only really need to handle arches on multiarch systems. * If only one arch is supported the default is fine */ - if (arch != 0) { - /* This *adds* the target arch, instead of replacing the + if (arch != 0) { + /* This *adds* the target arch, instead of replacing the * native one. This is not ideal, because we'd like to only * allow the target arch, but we can't really disallow the * native arch at this point, because then bubblewrap * couldn't continue running. */ - *ret_p = seccomp_arch_add(ctx, arch); - if (*ret_p < 0 && *ret_p != -EEXIST) { - res = 2; - goto out; - } + *ret_p = seccomp_arch_add(ctx, arch); + if (*ret_p < 0 && *ret_p != -EEXIST) { + res = 2; + goto out; + } - if (flags & HAKUREI_EXPORT_MULTIARCH && multiarch != 0) { - *ret_p = seccomp_arch_add(ctx, multiarch); - if (*ret_p < 0 && *ret_p != -EEXIST) { - res = 3; - goto out; - } - } - } + if (flags & HAKUREI_EXPORT_MULTIARCH && multiarch != 0) { + *ret_p = seccomp_arch_add(ctx, multiarch); + if (*ret_p < 0 && *ret_p != -EEXIST) { + res = 3; + goto out; + } + } + } - for (i = 0; i < rules_sz; i++) { - rule = &rules[i]; - assert(rule->m_errno == EPERM || rule->m_errno == ENOSYS); + for (i = 0; i < rules_sz; i++) { + rule = &rules[i]; + assert(rule->m_errno == EPERM || rule->m_errno == ENOSYS); - if (rule->arg) - *ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno), rule->syscall, 1, *rule->arg); - else - *ret_p = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(rule->m_errno), rule->syscall, 0); + if (rule->arg) + *ret_p = seccomp_rule_add(ctx, + SCMP_ACT_ERRNO(rule->m_errno), + rule->syscall, 1, *rule->arg); + else + *ret_p = seccomp_rule_add(ctx, + SCMP_ACT_ERRNO(rule->m_errno), + rule->syscall, 0); - if (*ret_p == -EFAULT) { - res = 4; - goto out; - } else if (*ret_p < 0) { - res = 5; - goto out; - } - } + if (*ret_p == -EFAULT) { + res = 4; + goto out; + } else if (*ret_p < 0) { + res = 5; + goto out; + } + } - /* Socket filtering doesn't work on e.g. i386, so ignore failures here + /* Socket filtering doesn't work on e.g. i386, so ignore failures here * However, we need to user seccomp_rule_add_exact to avoid libseccomp doing * something else: https://github.com/seccomp/libseccomp/issues/8 */ - last_allowed_family = -1; - for (i = 0; i < LEN(socket_family_allowlist); i++) { - if (socket_family_allowlist[i].flags_mask != 0 && - (socket_family_allowlist[i].flags_mask & flags) != socket_family_allowlist[i].flags_mask) - continue; + last_allowed_family = -1; + for (i = 0; i < LEN(socket_family_allowlist); i++) { + if (socket_family_allowlist[i].flags_mask != 0 && + (socket_family_allowlist[i].flags_mask & flags) != + socket_family_allowlist[i].flags_mask) + continue; - for (disallowed = last_allowed_family + 1; disallowed < socket_family_allowlist[i].family; disallowed++) { - /* Blocklist the in-between valid families */ - seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_EQ, disallowed)); - } - last_allowed_family = socket_family_allowlist[i].family; - } - /* Blocklist the rest */ - seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), SCMP_SYS(socket), 1, SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1)); + for (disallowed = last_allowed_family + 1; + disallowed < socket_family_allowlist[i].family; + disallowed++) { + /* Blocklist the in-between valid families */ + seccomp_rule_add_exact( + ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), + SCMP_SYS(socket), 1, + SCMP_A0(SCMP_CMP_EQ, disallowed)); + } + last_allowed_family = socket_family_allowlist[i].family; + } + /* Blocklist the rest */ + seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EAFNOSUPPORT), + SCMP_SYS(socket), 1, + SCMP_A0(SCMP_CMP_GE, last_allowed_family + 1)); - if (allocate_p == 0) { - *ret_p = seccomp_load(ctx); - if (*ret_p != 0) { - res = 7; - goto out; - } - } else { - *ret_p = seccomp_export_bpf_mem(ctx, NULL, &len); - if (*ret_p != 0) { - res = 6; - goto out; - } + if (allocate_p == 0) { + *ret_p = seccomp_load(ctx); + if (*ret_p != 0) { + res = 7; + goto out; + } + } else { + *ret_p = seccomp_export_bpf_mem(ctx, NULL, &len); + if (*ret_p != 0) { + res = 6; + goto out; + } - buf = hakurei_scmp_allocate(allocate_p, len); - if (buf == NULL) { - res = 4; - goto out; - } + buf = hakurei_scmp_allocate(allocate_p, len); + if (buf == NULL) { + res = 4; + goto out; + } - *ret_p = seccomp_export_bpf_mem(ctx, buf, &len); - if (*ret_p != 0) { - res = 6; - goto out; - } - } + *ret_p = seccomp_export_bpf_mem(ctx, buf, &len); + if (*ret_p != 0) { + res = 6; + goto out; + } + } out: - if (ctx) - seccomp_release(ctx); + if (ctx) + seccomp_release(ctx); - return res; + return res; } diff --git a/container/seccomp/libseccomp-helper.h b/container/seccomp/libseccomp-helper.h index 655110e4..29d5d5de 100644 --- a/container/seccomp/libseccomp-helper.h +++ b/container/seccomp/libseccomp-helper.h @@ -2,25 +2,24 @@ #include <stdint.h> #if (SCMP_VER_MAJOR < 2) || (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR < 5) || \ - (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 5 && SCMP_VER_MICRO < 1) + (SCMP_VER_MAJOR == 2 && SCMP_VER_MINOR == 5 && SCMP_VER_MICRO < 1) #error This package requires libseccomp >= v2.5.1 #endif typedef enum { - HAKUREI_EXPORT_MULTIARCH = 1 << 0, - HAKUREI_EXPORT_CAN = 1 << 1, - HAKUREI_EXPORT_BLUETOOTH = 1 << 2, + HAKUREI_EXPORT_MULTIARCH = 1 << 0, + HAKUREI_EXPORT_CAN = 1 << 1, + HAKUREI_EXPORT_BLUETOOTH = 1 << 2, } hakurei_export_flag; struct hakurei_syscall_rule { - int syscall; - int m_errno; - struct scmp_arg_cmp *arg; + int syscall; + int m_errno; + struct scmp_arg_cmp *arg; }; extern void *hakurei_scmp_allocate(uintptr_t f, size_t len); -int32_t hakurei_scmp_make_filter( - int *ret_p, uintptr_t allocate_p, - uint32_t arch, uint32_t multiarch, - struct hakurei_syscall_rule *rules, - size_t rules_sz, hakurei_export_flag flags); +int32_t hakurei_scmp_make_filter(int *ret_p, uintptr_t allocate_p, + uint32_t arch, uint32_t multiarch, + struct hakurei_syscall_rule *rules, + size_t rules_sz, hakurei_export_flag flags); diff --git a/container/seccomp/presets.go b/container/seccomp/presets.go index 4b76af61..252bf9f1 100644 --- a/container/seccomp/presets.go +++ b/container/seccomp/presets.go @@ -3,7 +3,7 @@ package seccomp /* flatpak commit 4c3bf179e2e4a2a298cd1db1d045adaf3f564532 */ import ( - . "syscall" + "syscall" . "hakurei.app/container/std" . "hakurei.app/ext" @@ -69,121 +69,124 @@ func Preset(presets FilterPreset, flags ExportFlag) (rules []NativeRule) { var ( presetCommon = []NativeRule{ /* Block dmesg */ - {Syscall: SNR_SYSLOG, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_SYSLOG, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* Useless old syscall */ - {Syscall: SNR_USELIB, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_USELIB, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* Don't allow disabling accounting */ - {Syscall: SNR_ACCT, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_ACCT, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* Don't allow reading current quota use */ - {Syscall: SNR_QUOTACTL, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_QUOTACTL, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* Don't allow access to the kernel keyring */ - {Syscall: SNR_ADD_KEY, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_KEYCTL, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_REQUEST_KEY, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_ADD_KEY, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_KEYCTL, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_REQUEST_KEY, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* Scary VM/NUMA ops */ - {Syscall: SNR_MOVE_PAGES, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_MBIND, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_GET_MEMPOLICY, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SET_MEMPOLICY, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_MIGRATE_PAGES, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_MOVE_PAGES, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_MBIND, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_GET_MEMPOLICY, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SET_MEMPOLICY, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_MIGRATE_PAGES, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, } /* hakurei: project-specific extensions */ presetCommonExt = []NativeRule{ /* system calls for changing the system clock */ - {Syscall: SNR_ADJTIMEX, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_CLOCK_ADJTIME, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_CLOCK_ADJTIME64, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_CLOCK_SETTIME, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_CLOCK_SETTIME64, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETTIMEOFDAY, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_ADJTIMEX, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_CLOCK_ADJTIME, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_CLOCK_ADJTIME64, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_CLOCK_SETTIME, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_CLOCK_SETTIME64, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETTIMEOFDAY, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* loading and unloading of kernel modules */ - {Syscall: SNR_DELETE_MODULE, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_FINIT_MODULE, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_INIT_MODULE, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_DELETE_MODULE, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_FINIT_MODULE, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_INIT_MODULE, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* system calls for rebooting and reboot preparation */ - {Syscall: SNR_KEXEC_FILE_LOAD, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_KEXEC_LOAD, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_REBOOT, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_KEXEC_FILE_LOAD, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_KEXEC_LOAD, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_REBOOT, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* system calls for enabling/disabling swap devices */ - {Syscall: SNR_SWAPOFF, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SWAPON, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_SWAPOFF, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SWAPON, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, } presetNamespace = []NativeRule{ /* Don't allow subnamespace setups: */ - {Syscall: SNR_UNSHARE, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETNS, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_MOUNT, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_UMOUNT, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_UMOUNT2, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_PIVOT_ROOT, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_CHROOT, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_CLONE, Errno: ScmpErrno(EPERM), - Arg: &ScmpArgCmp{Arg: cloneArg, Op: SCMP_CMP_MASKED_EQ, DatumA: CLONE_NEWUSER, DatumB: CLONE_NEWUSER}}, + {Syscall: SNR_UNSHARE, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETNS, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_MOUNT, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_UMOUNT, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_UMOUNT2, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_PIVOT_ROOT, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_CHROOT, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_CLONE, Errno: ScmpErrno(syscall.EPERM), + Arg: &ScmpArgCmp{Arg: cloneArg, Op: SCMP_CMP_MASKED_EQ, + DatumA: syscall.CLONE_NEWUSER, DatumB: syscall.CLONE_NEWUSER}}, /* seccomp can't look into clone3()'s struct clone_args to check whether * the flags are OK, so we have no choice but to block clone3(). - * Return ENOSYS so user-space will fall back to clone(). + * Return syscall.ENOSYS so user-space will fall back to clone(). * (CVE-2021-41133; see also https://github.com/moby/moby/commit/9f6b562d) */ - {Syscall: SNR_CLONE3, Errno: ScmpErrno(ENOSYS), Arg: nil}, + {Syscall: SNR_CLONE3, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, /* New mount manipulation APIs can also change our VFS. There's no * legitimate reason to do these in the sandbox, so block all of them * rather than thinking about which ones might be dangerous. * (CVE-2021-41133) */ - {Syscall: SNR_OPEN_TREE, Errno: ScmpErrno(ENOSYS), Arg: nil}, - {Syscall: SNR_MOVE_MOUNT, Errno: ScmpErrno(ENOSYS), Arg: nil}, - {Syscall: SNR_FSOPEN, Errno: ScmpErrno(ENOSYS), Arg: nil}, - {Syscall: SNR_FSCONFIG, Errno: ScmpErrno(ENOSYS), Arg: nil}, - {Syscall: SNR_FSMOUNT, Errno: ScmpErrno(ENOSYS), Arg: nil}, - {Syscall: SNR_FSPICK, Errno: ScmpErrno(ENOSYS), Arg: nil}, - {Syscall: SNR_MOUNT_SETATTR, Errno: ScmpErrno(ENOSYS), Arg: nil}, + {Syscall: SNR_OPEN_TREE, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, + {Syscall: SNR_MOVE_MOUNT, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, + {Syscall: SNR_FSOPEN, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, + {Syscall: SNR_FSCONFIG, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, + {Syscall: SNR_FSMOUNT, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, + {Syscall: SNR_FSPICK, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, + {Syscall: SNR_MOUNT_SETATTR, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, } /* hakurei: project-specific extensions */ presetNamespaceExt = []NativeRule{ /* changing file ownership */ - {Syscall: SNR_CHOWN, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_CHOWN32, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_FCHOWN, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_FCHOWN32, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_FCHOWNAT, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_LCHOWN, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_LCHOWN32, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_CHOWN, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_CHOWN32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_FCHOWN, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_FCHOWN32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_FCHOWNAT, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_LCHOWN, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_LCHOWN32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* system calls for changing user ID and group ID credentials */ - {Syscall: SNR_SETGID, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETGID32, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETGROUPS, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETGROUPS32, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETREGID, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETREGID32, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETRESGID, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETRESGID32, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETRESUID, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETRESUID32, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETREUID, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETREUID32, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETUID, Errno: ScmpErrno(EPERM), Arg: nil}, - {Syscall: SNR_SETUID32, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_SETGID, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETGID32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETGROUPS, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETGROUPS32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETREGID, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETREGID32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETRESGID, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETRESGID32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETRESUID, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETRESUID32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETREUID, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETREUID32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETUID, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, + {Syscall: SNR_SETUID32, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, } presetTTY = []NativeRule{ /* Don't allow faking input to the controlling tty (CVE-2017-5226) */ - {Syscall: SNR_IOCTL, Errno: ScmpErrno(EPERM), - Arg: &ScmpArgCmp{Arg: 1, Op: SCMP_CMP_MASKED_EQ, DatumA: 0xFFFFFFFF, DatumB: TIOCSTI}}, + {Syscall: SNR_IOCTL, Errno: ScmpErrno(syscall.EPERM), + Arg: &ScmpArgCmp{Arg: 1, Op: SCMP_CMP_MASKED_EQ, + DatumA: 0xFFFFFFFF, DatumB: syscall.TIOCSTI}}, /* In the unlikely event that the controlling tty is a Linux virtual * console (/dev/tty2 or similar), copy/paste operations have an effect * similar to TIOCSTI (CVE-2023-28100) */ - {Syscall: SNR_IOCTL, Errno: ScmpErrno(EPERM), - Arg: &ScmpArgCmp{Arg: 1, Op: SCMP_CMP_MASKED_EQ, DatumA: 0xFFFFFFFF, DatumB: TIOCLINUX}}, + {Syscall: SNR_IOCTL, Errno: ScmpErrno(syscall.EPERM), + Arg: &ScmpArgCmp{Arg: 1, Op: SCMP_CMP_MASKED_EQ, + DatumA: 0xFFFFFFFF, DatumB: syscall.TIOCLINUX}}, } presetEmu = []NativeRule{ @@ -191,15 +194,15 @@ var ( * so it's disabled as a hardening measure. * However, it is required to run old 16-bit applications * as well as some Wine patches, so it's allowed in multiarch. */ - {Syscall: SNR_MODIFY_LDT, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_MODIFY_LDT, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, } /* hakurei: project-specific extensions */ presetEmuExt = []NativeRule{ - {Syscall: SNR_SUBPAGE_PROT, Errno: ScmpErrno(ENOSYS), Arg: nil}, - {Syscall: SNR_SWITCH_ENDIAN, Errno: ScmpErrno(ENOSYS), Arg: nil}, - {Syscall: SNR_VM86, Errno: ScmpErrno(ENOSYS), Arg: nil}, - {Syscall: SNR_VM86OLD, Errno: ScmpErrno(ENOSYS), Arg: nil}, + {Syscall: SNR_SUBPAGE_PROT, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, + {Syscall: SNR_SWITCH_ENDIAN, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, + {Syscall: SNR_VM86, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, + {Syscall: SNR_VM86OLD, Errno: ScmpErrno(syscall.ENOSYS), Arg: nil}, } ) @@ -207,11 +210,11 @@ func presetDevel(allowedPersonality ScmpDatum) []NativeRule { return []NativeRule{ /* Profiling operations; we expect these to be done by tools from outside * the sandbox. In particular perf has been the source of many CVEs. */ - {Syscall: SNR_PERF_EVENT_OPEN, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_PERF_EVENT_OPEN, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, /* Don't allow you to switch to bsd emulation or whatnot */ - {Syscall: SNR_PERSONALITY, Errno: ScmpErrno(EPERM), + {Syscall: SNR_PERSONALITY, Errno: ScmpErrno(syscall.EPERM), Arg: &ScmpArgCmp{Arg: 0, Op: SCMP_CMP_NE, DatumA: allowedPersonality}}, - {Syscall: SNR_PTRACE, Errno: ScmpErrno(EPERM), Arg: nil}, + {Syscall: SNR_PTRACE, Errno: ScmpErrno(syscall.EPERM), Arg: nil}, } } diff --git a/container/syscall.go b/container/syscall.go index e008ecea..43e49bea 100644 --- a/container/syscall.go +++ b/container/syscall.go @@ -9,7 +9,7 @@ import ( // setNoNewPrivs sets the calling thread's no_new_privs attribute. func setNoNewPrivs() error { - return ext.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0) + return ext.Prctl(ext.PR_SET_NO_NEW_PRIVS, 1, 0) } // schedParam is equivalent to struct sched_param from include/linux/sched.h. diff --git a/container/syscall_386.go b/container/syscall_386.go deleted file mode 100644 index 4b8e2df3..00000000 --- a/container/syscall_386.go +++ /dev/null @@ -1,7 +0,0 @@ -package container - -const ( - O_PATH = 0x200000 - - PR_SET_NO_NEW_PRIVS = 0x26 -) diff --git a/container/syscall_amd64.go b/container/syscall_amd64.go deleted file mode 100644 index 4b8e2df3..00000000 --- a/container/syscall_amd64.go +++ /dev/null @@ -1,7 +0,0 @@ -package container - -const ( - O_PATH = 0x200000 - - PR_SET_NO_NEW_PRIVS = 0x26 -) diff --git a/container/sysctl.go b/container/sysctl.go index e4b7af23..2cf47258 100644 --- a/container/sysctl.go +++ b/container/sysctl.go @@ -2,14 +2,32 @@ package container import ( "bytes" + "fmt" "os" "strconv" "sync" "hakurei.app/fhs" - "hakurei.app/message" ) +// mustSysctlInt reads a sysctl path via proc, interprets it as a decimal +// representation of an integer, and terminates the program if the read was +// unsuccessful or the response is invalid. +func mustSysctlInt(path string) (v int) { + p, err := os.ReadFile(fhs.ProcSys + path) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + if v, err = strconv.Atoi(string(bytes.TrimSpace(p))); err != nil { + fmt.Printf("invalid sysctl response %q\n", string(p)) + os.Exit(1) + } + return +} + +// cached sysctl responses for populateSysctl var ( kernelOverflowuid int kernelOverflowgid int @@ -18,34 +36,31 @@ var ( sysctlOnce sync.Once ) -const ( - kernelOverflowuidPath = fhs.ProcSys + "kernel/overflowuid" - kernelOverflowgidPath = fhs.ProcSys + "kernel/overflowgid" - kernelCapLastCapPath = fhs.ProcSys + "kernel/cap_last_cap" -) - -func mustReadSysctl(msg message.Msg) { +// populateSysctl populates cached sysctl responses guarded by sysctlOnce. +func populateSysctl() { sysctlOnce.Do(func() { - if v, err := os.ReadFile(kernelOverflowuidPath); err != nil { - msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowuidPath, err) - } else if kernelOverflowuid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { - msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowuidPath, err) - } + kernelOverflowuid = mustSysctlInt("kernel/overflowuid") + kernelOverflowgid = mustSysctlInt("kernel/overflowgid") + kernelCapLastCap = mustSysctlInt("kernel/cap_last_cap") + }) +} - if v, err := os.ReadFile(kernelOverflowgidPath); err != nil { - msg.GetLogger().Fatalf("cannot read %q: %v", kernelOverflowgidPath, err) - } else if kernelOverflowgid, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { - msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelOverflowgidPath, err) - } +// OverflowUID returns the fixed uid returned by legacy 16-bit UID system calls. +// This value is also returned for unmapped UIDs in a user namespace. +func OverflowUID() int { + populateSysctl() + return kernelOverflowuid +} - if v, err := os.ReadFile(kernelCapLastCapPath); err != nil { - msg.GetLogger().Fatalf("cannot read %q: %v", kernelCapLastCapPath, err) - } else if kernelCapLastCap, err = strconv.Atoi(string(bytes.TrimSpace(v))); err != nil { - msg.GetLogger().Fatalf("cannot interpret %q: %v", kernelCapLastCapPath, err) - } - }) +// OverflowGID is like [OverflowUID], but for GID instead. +func OverflowGID() int { + populateSysctl() + return kernelOverflowgid } -func OverflowUid(msg message.Msg) int { mustReadSysctl(msg); return kernelOverflowuid } -func OverflowGid(msg message.Msg) int { mustReadSysctl(msg); return kernelOverflowgid } -func LastCap(msg message.Msg) uintptr { mustReadSysctl(msg); return uintptr(kernelCapLastCap) } +// lastcap returns the highest valid capability of the running kernel. Exports +// CAP_LAST_CAP from the kernel. +func lastcap() uintptr { + populateSysctl() + return uintptr(kernelCapLastCap) +} diff --git a/container/sysctl_deprecated.go b/container/sysctl_deprecated.go new file mode 100644 index 00000000..3c76be2c --- /dev/null +++ b/container/sysctl_deprecated.go @@ -0,0 +1,19 @@ +package container + +import "hakurei.app/message" + +// OverflowUid is the old function signature of [OverflowUID]. +// +// Deprecated: This function will be removed in 0.5. +func OverflowUid(_ message.Msg) int { return OverflowUID() } + +// OverflowGid is the old function signature of [OverflowGID]. +// +// Deprecated: This function will be removed in 0.5. +func OverflowGid(_ message.Msg) int { return OverflowGID() } + +// LastCap returns the highest valid capability of the running kernel. Exports +// CAP_LAST_CAP from the kernel. +// +// Deprecated: This function will be unexported in 0.5. +func LastCap(_ message.Msg) uintptr { return lastcap() } |
