forked from rosa/hakurei
cmd/hsu: document hsurc format and internals
This was previously only documented via an unexported function. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
/* copied from hst and must never be changed */
|
/* keep in sync with hst */
|
||||||
|
|
||||||
const (
|
const (
|
||||||
userOffset = 100000
|
userOffset = 100000
|
||||||
|
|||||||
@@ -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
|
package main
|
||||||
|
|
||||||
// minimise imports to avoid inadvertently calling init or global variable functions
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -16,10 +67,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// envIdentity is the name of the environment variable holding a
|
// envShim is the name of the environment variable holding a single byte
|
||||||
// single byte representing the shim setup pipe file descriptor.
|
// representing the shim setup pipe file descriptor.
|
||||||
envShim = "HAKUREI_SHIM"
|
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.
|
// supplementary group gid. Membership requirements are enforced.
|
||||||
envGroups = "HAKUREI_GROUPS"
|
envGroups = "HAKUREI_GROUPS"
|
||||||
)
|
)
|
||||||
@@ -35,7 +89,6 @@ func main() {
|
|||||||
|
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("hsu: ")
|
log.SetPrefix("hsu: ")
|
||||||
log.SetOutput(os.Stderr)
|
|
||||||
|
|
||||||
if os.Geteuid() != 0 {
|
if os.Geteuid() != 0 {
|
||||||
log.Fatal("this program must be owned by uid 0 and have the setuid bit set")
|
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
|
// last possible uid outcome
|
||||||
uidEnd = 999919999
|
uidEnd = 999919999
|
||||||
)
|
)
|
||||||
|
|
||||||
// cast to int for use with library functions
|
|
||||||
uid := int(toUser(userid, identity))
|
uid := int(toUser(userid, identity))
|
||||||
|
|
||||||
// final bounds check to catch any bugs
|
// 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
|
// careful! users in the allowlist is effectively allowed to drop groups via hsu
|
||||||
|
|
||||||
if err := syscall.Setresgid(uid, uid, uid); err != nil {
|
if err := syscall.Setresgid(uid, uid, uid); err != nil {
|
||||||
log.Fatalf("cannot set gid: %v", err)
|
log.Fatalf("cannot set gid: %v", err)
|
||||||
}
|
}
|
||||||
@@ -146,10 +196,21 @@ func main() {
|
|||||||
if err := syscall.Setresuid(uid, uid, uid); err != nil {
|
if err := syscall.Setresuid(uid, uid, uid); err != nil {
|
||||||
log.Fatalf("cannot set uid: %v", err)
|
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())
|
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)
|
log.Fatalf("cannot start shim: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ const (
|
|||||||
useridEnd = useridStart + rangeSize - 1
|
useridEnd = useridStart + rangeSize - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseUint32Fast parses a string representation of an unsigned 32-bit integer value
|
// parseUint32Fast parses a string representation of an unsigned 32-bit integer
|
||||||
// using the fast path only. This limits the range of values it is defined in.
|
// 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) {
|
func parseUint32Fast(s string) (uint32, error) {
|
||||||
sLen := len(s)
|
sLen := len(s)
|
||||||
if sLen < 1 {
|
if sLen < 1 {
|
||||||
@@ -40,12 +41,14 @@ func parseUint32Fast(s string) (uint32, error) {
|
|||||||
return n, nil
|
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
|
// Each line of the file specifies a hakurei userid to kernel uid mapping. A
|
||||||
// of the string representation of the uid of the user wishing to start hakurei containers,
|
// line consists of the string representation of the uid of the user wishing to
|
||||||
// followed by a space, followed by the string representation of its userid. Duplicate uid
|
// start hakurei containers, followed by a space, followed by the string
|
||||||
// entries are ignored, with the first occurrence taking effect.
|
// representation of its userid. Duplicate uid entries are ignored, with the
|
||||||
|
// first occurrence taking effect.
|
||||||
//
|
//
|
||||||
// All string representations are parsed by calling parseUint32Fast.
|
// All string representations are parsed by calling parseUint32Fast.
|
||||||
func parseConfig(r io.Reader, puid uint32) (userid uint32, ok bool, err error) {
|
func parseConfig(r io.Reader, puid uint32) (userid uint32, ok bool, err error) {
|
||||||
@@ -112,10 +115,6 @@ func mustParseConfig(puid int) (userid uint32) {
|
|||||||
return
|
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,
|
// mustReadIdentity calls parseUint32Fast to interpret the value stored in envIdentity,
|
||||||
// terminating the program if the value is not set, malformed, or out of bounds.
|
// terminating the program if the value is not set, malformed, or out of bounds.
|
||||||
func mustReadIdentity() uint32 {
|
func mustReadIdentity() uint32 {
|
||||||
|
|||||||
Reference in New Issue
Block a user