mesh/repo-check head 64dfa90c3a6c merged into trunk 454be9b2693d — pass: its merge-check.sh passed
mesh/merge-gate head 64dfa90c3a6c merged into trunk 454be9b2693d — pass: group feat/r4a-sdk-first composed: every machine composes with the change as it di…
mesh/delivery delivered
mesh/delivery-group group feat/r4a-sdk-first delivered: every member is delivered
A build module's sandbox needs one, and a module can require only a capability the node-engine detects (ADR 0448 section 5). The probe drops to an unprivileged uid first when run as root, since root always can.
62 lines
2.2 KiB
Go
62 lines
2.2 KiB
Go
package profile
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// effectiveUID and readProcFile are replaceable so the unit tests stay off the machine.
|
|
var (
|
|
effectiveUID = os.Geteuid
|
|
readProcFile = func(path string) (string, error) {
|
|
b, err := os.ReadFile(path)
|
|
return string(b), err
|
|
}
|
|
)
|
|
|
|
// unprivilegedID is the uid and gid the probe drops to (nobody on every mesh node).
|
|
const unprivilegedID = "65534"
|
|
|
|
// userNamespaces builds the detector for CapUserNamespaces: it makes a user namespace as an
|
|
// unprivileged user. The node-engine runs as root, and root can always make one, so as root the
|
|
// probe drops first with setpriv (util-linux). Go's SysProcAttr Cloneflags with a Credential
|
|
// would clone before the uid drops and prove nothing, so it is not used.
|
|
func userNamespaces(runner Runner) Detector {
|
|
command, args := "unshare", []string{"--user", "true"}
|
|
if effectiveUID() == 0 {
|
|
command = "setpriv"
|
|
args = []string{"--reuid=" + unprivilegedID, "--regid=" + unprivilegedID, "--clear-groups", "unshare", "--user", "true"}
|
|
}
|
|
read := readProcFile // bound now, so a probe cut by the timeout never reads a later replacement
|
|
return commandCapability{
|
|
name: CapUserNamespaces, command: command, args: args,
|
|
why: "makes a user namespace as an unprivileged user — what bubblewrap needs, not a kernel setting read",
|
|
interpret: func(_ string, err error) (bool, string) {
|
|
if err == nil {
|
|
return true, "an unprivileged user can make a user namespace"
|
|
}
|
|
return false, err.Error() + userNamespaceSettings(read)
|
|
},
|
|
runner: runner,
|
|
}
|
|
}
|
|
|
|
// userNamespaceSettings names the two kernel settings that switch user namespaces off, when
|
|
// they can be read. It is added to the reason of an absent verdict only.
|
|
func userNamespaceSettings(read func(string) (string, error)) string {
|
|
var parts []string
|
|
for _, s := range []struct{ path, name string }{
|
|
{"/proc/sys/kernel/unprivileged_userns_clone", "unprivileged_userns_clone"},
|
|
{"/proc/sys/user/max_user_namespaces", "max_user_namespaces"},
|
|
} {
|
|
if v, err := read(s.path); err == nil {
|
|
parts = append(parts, fmt.Sprintf("%s=%s", s.name, strings.TrimSpace(v)))
|
|
}
|
|
}
|
|
if len(parts) == 0 {
|
|
return ""
|
|
}
|
|
return " (" + strings.Join(parts, ", ") + ")"
|
|
}
|