cmd/hsu: return hsurc id
All checks were successful
Test / Create distribution (push) Successful in 24s
Test / Sandbox (push) Successful in 2m19s
Test / Hpkg (push) Successful in 3m28s
Test / Sandbox (race detector) (push) Successful in 3m53s
Test / Hakurei (race detector) (push) Successful in 5m18s
Test / Hakurei (push) Successful in 43s
Test / Flake checks (push) Successful in 1m34s

The uid format is stable, this value is what caller has to obtain through hsu.

Closes #14.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-09-24 21:10:13 +09:00
parent 773253fdf5
commit afa7a0800d
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
3 changed files with 56 additions and 72 deletions

View File

@ -15,7 +15,7 @@ import (
const ( const (
hsuConfFile = "/etc/hsurc" hsuConfFile = "/etc/hsurc"
envShim = "HAKUREI_SHIM" envShim = "HAKUREI_SHIM"
envAID = "HAKUREI_APP_ID" envIdentity = "HAKUREI_IDENTITY"
envGroups = "HAKUREI_GROUPS" envGroups = "HAKUREI_GROUPS"
PR_SET_NO_NEW_PRIVS = 0x26 PR_SET_NO_NEW_PRIVS = 0x26
@ -48,8 +48,8 @@ func main() {
} }
// uid = 1000000 + // uid = 1000000 +
// fid * 10000 + // id * 10000 +
// aid // identity
uid := 1000000 uid := 1000000
// refuse to run if hsurc is not protected correctly // refuse to run if hsurc is not protected correctly
@ -62,29 +62,25 @@ func main() {
} }
// authenticate before accepting user input // authenticate before accepting user input
var id int
if f, err := os.Open(hsuConfFile); err != nil { if f, err := os.Open(hsuConfFile); err != nil {
log.Fatal(err) log.Fatal(err)
} else if fid, ok := mustParseConfig(f, puid); !ok { } else if v, ok := mustParseConfig(f, puid); !ok {
log.Fatalf("uid %d is not in the hsurc file", puid) log.Fatalf("uid %d is not in the hsurc file", puid)
} else { } else {
uid += fid * 10000 id = v
} if err = f.Close(); err != nil {
log.Fatal(err)
}
// allowed aid range 0 to 9999 uid += id * 10000
if as, ok := os.LookupEnv(envAID); !ok {
log.Fatal("HAKUREI_APP_ID not set")
} else if aid, err := parseUint32Fast(as); err != nil || aid < 0 || aid > 9999 {
log.Fatal("invalid aid")
} else {
uid += aid
} }
// pass through setup fd to shim // pass through setup fd to shim
var shimSetupFd string var shimSetupFd string
if s, ok := os.LookupEnv(envShim); !ok { if s, ok := os.LookupEnv(envShim); !ok {
// hakurei requests target uid // hakurei requests hsurc user id
// print resolved uid and exit fmt.Print(id)
fmt.Print(uid)
os.Exit(0) os.Exit(0)
} else if len(s) != 1 || s[0] > '9' || s[0] < '3' { } else if len(s) != 1 || s[0] > '9' || s[0] < '3' {
log.Fatal("HAKUREI_SHIM holds an invalid value") log.Fatal("HAKUREI_SHIM holds an invalid value")
@ -92,6 +88,15 @@ func main() {
shimSetupFd = s 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 < 0 || identity > 9999 {
log.Fatal("invalid identity")
} else {
uid += identity
}
// supplementary groups // supplementary groups
var suppGroups, suppCurrent []int var suppGroups, suppCurrent []int

View File

@ -255,7 +255,7 @@ func (seal *outcome) main() {
// passed through to shim by hsu // passed through to shim by hsu
shimEnv + "=" + strconv.Itoa(fd), shimEnv + "=" + strconv.Itoa(fd),
// interpreted by hsu // interpreted by hsu
"HAKUREI_APP_ID=" + seal.user.identity.String(), "HAKUREI_IDENTITY=" + seal.user.identity.String(),
} }
} }

View File

@ -17,69 +17,48 @@ import (
// Hsu caches responses from cmd/hsu. // Hsu caches responses from cmd/hsu.
type Hsu struct { type Hsu struct {
uidOnce sync.Once idOnce sync.Once
uidCopy map[int]struct { idErr error
uid int id int
err error
}
uidMu sync.RWMutex
} }
var ErrHsuAccess = errors.New("current user is not in the hsurc file") var ErrHsuAccess = errors.New("current user is not in the hsurc file")
func (h *Hsu) Uid(identity int) (int, error) { func (h *Hsu) Uid(identity int) (int, error) {
h.uidOnce.Do(func() { h.idOnce.Do(func() {
h.uidCopy = make(map[int]struct { h.id = -1
uid int hsuPath := internal.MustHsuPath()
err error
}) cmd := exec.Command(hsuPath)
cmd.Path = hsuPath
cmd.Stderr = os.Stderr // pass through fatal messages
cmd.Env = make([]string, 0)
cmd.Dir = container.FHSRoot
var (
p []byte
exitError *exec.ExitError
)
const step = "obtain uid from hsu"
if p, h.idErr = cmd.Output(); h.idErr == nil {
h.id, h.idErr = strconv.Atoi(string(p))
if h.idErr != nil {
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
}
} else if errors.As(h.idErr, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
// hsu prints an error message in this case
h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
} else if os.IsNotExist(h.idErr) {
h.idErr = &hst.AppError{Step: step, Err: os.ErrNotExist,
Msg: fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
}
}) })
{ uid := -1
h.uidMu.RLock() if h.id >= 0 {
u, ok := h.uidCopy[identity] uid = 1000000 + h.id*10000 + identity
h.uidMu.RUnlock()
if ok {
return u.uid, u.err
}
} }
return uid, h.idErr
h.uidMu.Lock()
defer h.uidMu.Unlock()
u := struct {
uid int
err error
}{}
defer func() { h.uidCopy[identity] = u }()
u.uid = -1
hsuPath := internal.MustHsuPath()
cmd := exec.Command(hsuPath)
cmd.Path = hsuPath
cmd.Stderr = os.Stderr // pass through fatal messages
cmd.Env = []string{"HAKUREI_APP_ID=" + strconv.Itoa(identity)}
cmd.Dir = container.FHSRoot
var (
p []byte
exitError *exec.ExitError
)
const step = "obtain uid from hsu"
if p, u.err = cmd.Output(); u.err == nil {
u.uid, u.err = strconv.Atoi(string(p))
if u.err != nil {
u.err = &hst.AppError{Step: step, Err: u.err, Msg: "invalid uid string from hsu"}
}
} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
// hsu prints an error message in this case
u.err = &hst.AppError{Step: step, Err: ErrHsuAccess}
} else if os.IsNotExist(u.err) {
u.err = &hst.AppError{Step: step, Err: os.ErrNotExist,
Msg: fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
}
return u.uid, u.err
} }
// MustUid calls [State.Uid] and terminates on error. // MustUid calls [State.Uid] and terminates on error.