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, ", ") + ")" }