All checks were successful
Test / Create distribution (push) Successful in 1m11s
Test / Sandbox (push) Successful in 2m3s
Test / Sandbox (race detector) (push) Successful in 3m7s
Test / ShareFS (push) Successful in 3m18s
Test / Hakurei (race detector) (push) Successful in 4m14s
Test / Hakurei (push) Successful in 3m9s
Test / Flake checks (push) Successful in 1m36s
This was previously only documented via an unexported function. Signed-off-by: Ophestra <cat@gensokyo.uk>
219 lines
7.0 KiB
Go
219 lines
7.0 KiB
Go
// hsu starts the hakurei shim as the target subordinate user.
|
|
//
|
|
// The hsu program must be installed with the setuid and setgid bit set, and
|
|
// owned by root. A configuration file must be installed at /etc/hsurc with
|
|
// permission bits 0400, and owned by root. Each line of the file specifies a
|
|
// hakurei userid to kernel uid mapping. A line consists of the decimal string
|
|
// representation of the uid of the user wishing to start hakurei containers,
|
|
// followed by a space, followed by the decimal string representation of its
|
|
// userid. Duplicate uid entries are ignored, with the first occurrence taking
|
|
// effect.
|
|
//
|
|
// For example, to map the kernel uid 1000 to the hakurei user id 0:
|
|
//
|
|
// 1000 0
|
|
//
|
|
// # Internals
|
|
//
|
|
// Hakurei and hsu holds pathnames pointing to each other set at link time. For
|
|
// this reason, a distribution of hakurei has fixed installation prefix. Since
|
|
// this program is never invoked by the user, behaviour described in the
|
|
// following paragraphs are considered an internal detail and not covered by the
|
|
// compatibility promise.
|
|
//
|
|
// After checking credentials, hsu checks via /proc/ the absolute pathname of
|
|
// its parent process, and fails if it does not match the hakurei pathname set
|
|
// at link time. This is not a security feature: the priv-side is considered
|
|
// trusted, and this feature makes no attempt to address the racy nature of
|
|
// querying /proc/, or debuggers attached to the parent process. Instead, this
|
|
// aims to discourage misuse and reduce confusion if the user accidentally
|
|
// stumbles upon this program. It also prevents accidental use of the incorrect
|
|
// installation of hsu in some environments.
|
|
//
|
|
// Since target container environment variables are set up in shim via the
|
|
// [container] infrastructure, the environment is used for parameters from the
|
|
// parent process.
|
|
//
|
|
// HAKUREI_SHIM specifies a single byte between '3' and '9' representing the
|
|
// setup pipe file descriptor. It is passed as is to the shim process and is the
|
|
// only value in the environment of the shim process. Since hsurc is not
|
|
// accessible to the parent process, leaving this unset causes hsu to print the
|
|
// corresponding hakurei user id of the parent and terminate.
|
|
//
|
|
// HAKUREI_IDENTITY specifies the identity of the instance being started and is
|
|
// used to produce the kernel uid alongside hakurei user id looked up from hsurc.
|
|
//
|
|
// HAKUREI_GROUPS specifies supplementary groups to inherit from the credentials
|
|
// of the parent process in a ' ' separated list of decimal string
|
|
// representations of gid. This has the unfortunate consequence of allowing
|
|
// users mapped via hsurc to effectively drop group membership, so special care
|
|
// must be taken to ensure this does not lead to an increase in access. This is
|
|
// not applicable to Rosa OS since unsigned code execution is not permitted
|
|
// outside hakurei containers, and is generally nonapplicable to the security
|
|
// model of hakurei, where all untrusted code runs within containers.
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
const (
|
|
// envShim is the name of the environment variable holding a single byte
|
|
// representing the shim setup pipe file descriptor.
|
|
envShim = "HAKUREI_SHIM"
|
|
// envIdentity is the name of the environment variable holding a decimal
|
|
// string representation of the current application identity.
|
|
envIdentity = "HAKUREI_IDENTITY"
|
|
// envGroups holds a ' ' separated list of decimal string representations of
|
|
// supplementary group gid. Membership requirements are enforced.
|
|
envGroups = "HAKUREI_GROUPS"
|
|
)
|
|
|
|
// hakureiPath is the absolute path to Hakurei.
|
|
//
|
|
// This is set by the linker.
|
|
var hakureiPath string
|
|
|
|
func main() {
|
|
const PR_SET_NO_NEW_PRIVS = 0x26
|
|
runtime.LockOSThread()
|
|
|
|
log.SetFlags(0)
|
|
log.SetPrefix("hsu: ")
|
|
|
|
if os.Geteuid() != 0 {
|
|
log.Fatal("this program must be owned by uid 0 and have the setuid bit set")
|
|
}
|
|
if os.Getegid() != os.Getgid() {
|
|
log.Fatal("this program must not have the setgid bit set")
|
|
}
|
|
|
|
puid := os.Getuid()
|
|
if puid == 0 {
|
|
log.Fatal("this program must not be started by root")
|
|
}
|
|
|
|
if !path.IsAbs(hakureiPath) {
|
|
log.Fatal("this program is compiled incorrectly")
|
|
return
|
|
}
|
|
|
|
var toolPath string
|
|
pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
|
|
if p, err := os.Readlink(pexe); err != nil {
|
|
log.Fatalf("cannot read parent executable path: %v", err)
|
|
} else if strings.HasSuffix(p, " (deleted)") {
|
|
log.Fatal("hakurei executable has been deleted")
|
|
} else if p != hakureiPath {
|
|
log.Fatal("this program must be started by hakurei")
|
|
} else {
|
|
toolPath = p
|
|
}
|
|
|
|
// refuse to run if hsurc is not protected correctly
|
|
if s, err := os.Stat(hsuConfPath); err != nil {
|
|
log.Fatal(err)
|
|
} else if s.Mode().Perm() != 0400 {
|
|
log.Fatal("bad hsurc perm")
|
|
} else if st := s.Sys().(*syscall.Stat_t); st.Uid != 0 || st.Gid != 0 {
|
|
log.Fatal("hsurc must be owned by uid 0")
|
|
}
|
|
|
|
// authenticate before accepting user input
|
|
userid := mustParseConfig(puid)
|
|
|
|
// pass through setup fd to shim
|
|
var shimSetupFd string
|
|
if s, ok := os.LookupEnv(envShim); !ok {
|
|
// hakurei requests hsurc user id
|
|
fmt.Print(userid)
|
|
os.Exit(0)
|
|
} else if len(s) != 1 || s[0] > '9' || s[0] < '3' {
|
|
log.Fatal("HAKUREI_SHIM holds an invalid value")
|
|
} else {
|
|
shimSetupFd = s
|
|
}
|
|
|
|
// start is going ahead at this point
|
|
identity := mustReadIdentity()
|
|
|
|
const (
|
|
// first possible uid outcome
|
|
uidStart = 10000
|
|
// last possible uid outcome
|
|
uidEnd = 999919999
|
|
)
|
|
uid := int(toUser(userid, identity))
|
|
|
|
// final bounds check to catch any bugs
|
|
if uid < uidStart || uid >= uidEnd {
|
|
panic("uid out of bounds")
|
|
}
|
|
|
|
// supplementary groups
|
|
var suppGroups, suppCurrent []int
|
|
|
|
if gs, ok := os.LookupEnv(envGroups); ok {
|
|
if cur, err := os.Getgroups(); err != nil {
|
|
log.Fatalf("cannot get groups: %v", err)
|
|
} else {
|
|
suppCurrent = cur
|
|
}
|
|
|
|
// parse space-separated list of group ids
|
|
gss := bytes.Split([]byte(gs), []byte{' '})
|
|
suppGroups = make([]int, len(gss)+1)
|
|
for i, s := range gss {
|
|
if gid, err := strconv.Atoi(string(s)); err != nil {
|
|
log.Fatalf("cannot parse %q: %v", string(s), err)
|
|
} else if gid > 0 && gid != uid && gid != os.Getgid() && slices.Contains(suppCurrent, gid) {
|
|
suppGroups[i] = gid
|
|
} else {
|
|
log.Fatalf("invalid gid %d", gid)
|
|
}
|
|
}
|
|
suppGroups[len(suppGroups)-1] = uid
|
|
} else {
|
|
suppGroups = []int{uid}
|
|
}
|
|
|
|
// careful! users in the allowlist is effectively allowed to drop groups via hsu
|
|
if err := syscall.Setresgid(uid, uid, uid); err != nil {
|
|
log.Fatalf("cannot set gid: %v", err)
|
|
}
|
|
if err := syscall.Setgroups(suppGroups); err != nil {
|
|
log.Fatalf("cannot set supplementary groups: %v", err)
|
|
}
|
|
if err := syscall.Setresuid(uid, uid, uid); err != nil {
|
|
log.Fatalf("cannot set uid: %v", err)
|
|
}
|
|
|
|
if _, _, errno := syscall.AllThreadsSyscall(
|
|
syscall.SYS_PRCTL,
|
|
PR_SET_NO_NEW_PRIVS, 1,
|
|
0,
|
|
); errno != 0 {
|
|
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
|
}
|
|
|
|
if err := syscall.Exec(toolPath, []string{
|
|
"hakurei",
|
|
"shim",
|
|
}, []string{
|
|
envShim + "=" + shimSetupFd,
|
|
}); err != nil {
|
|
log.Fatalf("cannot start shim: %v", err)
|
|
}
|
|
|
|
panic("unreachable")
|
|
}
|