// 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") }