hakurei/cmd/hsu/main.go
Ophestra 5bf28901a4
All checks were successful
Test / Create distribution (push) Successful in 35s
Test / Sandbox (push) Successful in 2m10s
Test / Hpkg (push) Successful in 4m5s
Test / Sandbox (race detector) (push) Successful in 4m33s
Test / Hakurei (race detector) (push) Successful in 5m20s
Test / Hakurei (push) Successful in 2m18s
Test / Flake checks (push) Successful in 1m31s
cmd/hsu: check against setgid bit
The getgroups behaviour is already checked for, but it never hurts to be more careful in a setuid program.

Signed-off-by: Ophestra <cat@gensokyo.uk>
2025-10-08 18:22:24 +09:00

160 lines
4.0 KiB
Go

package main
// minimise imports to avoid inadvertently calling init or global variable functions
import (
"bytes"
"fmt"
"log"
"os"
"path"
"slices"
"strconv"
"strings"
"syscall"
)
const (
hsuConfFile = "/etc/hsurc"
envShim = "HAKUREI_SHIM"
envIdentity = "HAKUREI_IDENTITY"
envGroups = "HAKUREI_GROUPS"
PR_SET_NO_NEW_PRIVS = 0x26
identityMin = 0
identityMax = 9999
)
func main() {
log.SetFlags(0)
log.SetPrefix("hsu: ")
log.SetOutput(os.Stderr)
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")
}
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 != mustCheckPath(hmain) {
log.Fatal("this program must be started by hakurei")
} else {
toolPath = p
}
// uid = 1000000 +
// id * 10000 +
// identity
uid := 1000000
// refuse to run if hsurc is not protected correctly
if s, err := os.Stat(hsuConfFile); 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
var id int
if f, err := os.Open(hsuConfFile); err != nil {
log.Fatal(err)
} else if v, ok := mustParseConfig(f, puid); !ok {
log.Fatalf("uid %d is not in the hsurc file", puid)
} else {
id = v
if err = f.Close(); err != nil {
log.Fatal(err)
}
uid += id * 10000
}
// pass through setup fd to shim
var shimSetupFd string
if s, ok := os.LookupEnv(envShim); !ok {
// hakurei requests hsurc user id
fmt.Print(id)
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
}
// allowed identity range 0 to 9999
if as, ok := os.LookupEnv(envIdentity); !ok {
log.Fatal("HAKUREI_IDENTITY not set")
} else if identity, err := parseUint32Fast(as); err != nil || identity < identityMin || identity > identityMax {
log.Fatal("invalid identity")
} else {
uid += identity
}
// 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}
}
// final bounds check to catch any bugs
if uid < 1000000 || uid >= 2000000 {
panic("uid out of bounds")
}
// 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")
}