aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-03-28 18:06:12 +0900
committerOphestra <cat@gensokyo.uk>2026-03-28 18:17:31 +0900
commit62002efd08158512f6015d47461d264a14961553 (patch)
tree723cb027a3aac2eb8e61062826884505479370bf /cmd
parente33294db9c334530582215b2c5dd23c0730bc162 (diff)
cmd/hsu: document hsurc format and internals
This was previously only documented via an unexported function. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/hsu/hst.go2
-rw-r--r--cmd/hsu/main.go83
-rw-r--r--cmd/hsu/parse.go21
3 files changed, 83 insertions, 23 deletions
diff --git a/cmd/hsu/hst.go b/cmd/hsu/hst.go
index d7ffb89a..ed0e5575 100644
--- a/cmd/hsu/hst.go
+++ b/cmd/hsu/hst.go
@@ -1,6 +1,6 @@
package main
-/* copied from hst and must never be changed */
+/* keep in sync with hst */
const (
userOffset = 100000
diff --git a/cmd/hsu/main.go b/cmd/hsu/main.go
index 5c626a86..497279b8 100644
--- a/cmd/hsu/main.go
+++ b/cmd/hsu/main.go
@@ -1,7 +1,58 @@
+// 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
-// minimise imports to avoid inadvertently calling init or global variable functions
-
import (
"bytes"
"fmt"
@@ -16,10 +67,13 @@ import (
)
const (
- // envIdentity is the name of the environment variable holding a
- // single byte representing the shim setup pipe file descriptor.
+ // envShim is the name of the environment variable holding a single byte
+ // representing the shim setup pipe file descriptor.
envShim = "HAKUREI_SHIM"
- // envGroups holds a ' ' separated list of string representations of
+ // 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"
)
@@ -35,7 +89,6 @@ 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")
@@ -99,8 +152,6 @@ func main() {
// last possible uid outcome
uidEnd = 999919999
)
-
- // cast to int for use with library functions
uid := int(toUser(userid, identity))
// final bounds check to catch any bugs
@@ -136,7 +187,6 @@ func main() {
}
// 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)
}
@@ -146,10 +196,21 @@ func main() {
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 {
+
+ 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 {
+
+ if err := syscall.Exec(toolPath, []string{
+ "hakurei",
+ "shim",
+ }, []string{
+ envShim + "=" + shimSetupFd,
+ }); err != nil {
log.Fatalf("cannot start shim: %v", err)
}
diff --git a/cmd/hsu/parse.go b/cmd/hsu/parse.go
index 6003f6cf..d4f7df43 100644
--- a/cmd/hsu/parse.go
+++ b/cmd/hsu/parse.go
@@ -18,8 +18,9 @@ const (
useridEnd = useridStart + rangeSize - 1
)
-// parseUint32Fast parses a string representation of an unsigned 32-bit integer value
-// using the fast path only. This limits the range of values it is defined in.
+// parseUint32Fast parses a string representation of an unsigned 32-bit integer
+// value using the fast path only. This limits the range of values it is defined
+// in but is perfectly adequate for this use case.
func parseUint32Fast(s string) (uint32, error) {
sLen := len(s)
if sLen < 1 {
@@ -40,12 +41,14 @@ func parseUint32Fast(s string) (uint32, error) {
return n, nil
}
-// parseConfig reads a list of allowed users from r until it encounters puid or [io.EOF].
+// parseConfig reads a list of allowed users from r until it encounters puid or
+// [io.EOF].
//
-// Each line of the file specifies a hakurei userid to kernel uid mapping. A line consists
-// of the string representation of the uid of the user wishing to start hakurei containers,
-// followed by a space, followed by the string representation of its userid. Duplicate uid
-// entries are ignored, with the first occurrence taking effect.
+// Each line of the file specifies a hakurei userid to kernel uid mapping. A
+// line consists of the string representation of the uid of the user wishing to
+// start hakurei containers, followed by a space, followed by the string
+// representation of its userid. Duplicate uid entries are ignored, with the
+// first occurrence taking effect.
//
// All string representations are parsed by calling parseUint32Fast.
func parseConfig(r io.Reader, puid uint32) (userid uint32, ok bool, err error) {
@@ -112,10 +115,6 @@ func mustParseConfig(puid int) (userid uint32) {
return
}
-// envIdentity is the name of the environment variable holding a
-// string representation of the current application identity.
-var envIdentity = "HAKUREI_IDENTITY"
-
// mustReadIdentity calls parseUint32Fast to interpret the value stored in envIdentity,
// terminating the program if the value is not set, malformed, or out of bounds.
func mustReadIdentity() uint32 {