//go:build darwin // +build darwin package main import ( "bytes" "fmt" "io" "io/fs" "os" "path/filepath" "strings" "syscall" "text/template" "github.com/pkg/errors" "github.com/spf13/cobra" ) const ( rwx_rx_rx = 0755 rw_r_r = 0644 ) const launchConfig = ` Label com.github.containers.podman.helper-{{.User}} ProgramArguments {{.Program}} service {{.Target}} inetdCompatibility Wait UserName root Sockets Listeners SockFamily Unix SockPathName /private/var/run/podman-helper-{{.User}}.socket SockPathOwner {{.UID}} SockPathMode 384 SockType stream ` type launchParams struct { Program string User string UID string Target string } var installCmd = &cobra.Command{ Use: "install", Short: "installs the podman helper agent", Long: "installs the podman helper agent, which manages the /var/run/docker.sock link", PreRun: silentUsage, RunE: install, } func init() { addPrefixFlag(installCmd) rootCmd.AddCommand(installCmd) } func install(cmd *cobra.Command, args []string) error { userName, uid, homeDir, err := getUser() if err != nil { return err } labelName := fmt.Sprintf("com.github.containers.podman.helper-%s.plist", userName) fileName := filepath.Join("/Library", "LaunchDaemons", labelName) if _, err := os.Stat(fileName); err == nil || !os.IsNotExist(err) { return errors.New("helper is already installed, uninstall first") } prog, err := installExecutable(userName) if err != nil { return err } target := filepath.Join(homeDir, ".local", "share", "containers", "podman", "machine", "podman.sock") var buf bytes.Buffer t := template.Must(template.New("launchdConfig").Parse(launchConfig)) err = t.Execute(&buf, launchParams{prog, userName, uid, target}) if err != nil { return err } file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, rw_r_r) if err != nil { return errors.Wrap(err, "error creating helper plist file") } defer file.Close() _, err = buf.WriteTo(file) if err != nil { return err } if err = runDetectErr("launchctl", "load", fileName); err != nil { return errors.Wrap(err, "launchctl failed loading service") } return nil } func restrictRecursive(targetDir string, until string) error { for targetDir != until && len(targetDir) > 1 { info, err := os.Lstat(targetDir) if err != nil { return err } if info.Mode()&fs.ModeSymlink != 0 { return errors.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir) } if err = os.Chown(targetDir, 0, 0); err != nil { return errors.Wrap(err, "could not update ownership of helper path") } if err = os.Chmod(targetDir, rwx_rx_rx|fs.ModeSticky); err != nil { return errors.Wrap(err, "could not update permissions of helper path") } targetDir = filepath.Dir(targetDir) } return nil } func verifyRootDeep(path string) error { path = filepath.Clean(path) current := "/" segs := strings.Split(path, "/") depth := 0 for i := 1; i < len(segs); i++ { seg := segs[i] current = filepath.Join(current, seg) info, err := os.Lstat(current) if err != nil { return err } stat := info.Sys().(*syscall.Stat_t) if stat.Uid != 0 { return errors.Errorf("installation target path must be solely owned by root: %s is not", current) } if info.Mode()&fs.ModeSymlink != 0 { target, err := os.Readlink(current) if err != nil { return err } targetParts := strings.Split(target, "/") segs = append(targetParts, segs[i+1:]...) if depth++; depth > 1000 { return errors.New("reached max recursion depth, link structure is cyclical or too complex") } if !filepath.IsAbs(target) { current = filepath.Dir(current) i = -1 // Start at 0 } else { current = "/" i = 0 // Skip empty first segment } } } return nil } func installExecutable(user string) (string, error) { // Since the installed executable runs as root, as a precaution verify root ownership of // the entire installation path, and utilize sticky + read only perms for the helper path // suffix. The goal is to help users harden against privilege escalation from loose // filesystem permissions. // // Since userpsace package management tools, such as brew, delegate management of system // paths to standard unix users, the daemon executable is copied into a separate more // restricted area of the filesystem. if err := verifyRootDeep(installPrefix); err != nil { return "", err } targetDir := filepath.Join(installPrefix, "podman", "helper", user) if err := os.MkdirAll(targetDir, rwx_rx_rx); err != nil { return "", errors.Wrap(err, "could not create helper directory structure") } // Correct any incorrect perms on previously existing directories and verify no symlinks if err := restrictRecursive(targetDir, installPrefix); err != nil { return "", err } exec, err := os.Executable() if err != nil { return "", err } install := filepath.Join(targetDir, filepath.Base(exec)) return install, copyFile(install, exec, rwx_rx_rx) } func copyFile(dest string, source string, perms fs.FileMode) error { in, err := os.Open(source) if err != nil { return err } defer in.Close() out, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms) if err != nil { return err } defer out.Close() if _, err := io.Copy(out, in); err != nil { return err } return nil }