github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/cmd/podman-mac-helper/install.go (about) 1 //go:build darwin 2 // +build darwin 3 4 package main 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "io/fs" 11 "os" 12 "path/filepath" 13 "strings" 14 "syscall" 15 "text/template" 16 17 "github.com/pkg/errors" 18 "github.com/spf13/cobra" 19 ) 20 21 const ( 22 rwx_rx_rx = 0755 23 rw_r_r = 0644 24 ) 25 26 const launchConfig = `<?xml version="1.0" encoding="UTF-8"?> 27 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 28 <plist version="1.0"> 29 <dict> 30 <key>Label</key> 31 <string>com.github.containers.podman.helper-{{.User}}</string> 32 <key>ProgramArguments</key> 33 <array> 34 <string>{{.Program}}</string> 35 <string>service</string> 36 <string>{{.Target}}</string> 37 </array> 38 <key>inetdCompatibility</key> 39 <dict> 40 <key>Wait</key> 41 <false/> 42 </dict> 43 <key>UserName</key> 44 <string>root</string> 45 <key>Sockets</key> 46 <dict> 47 <key>Listeners</key> 48 <dict> 49 <key>SockFamily</key> 50 <string>Unix</string> 51 <key>SockPathName</key> 52 <string>/private/var/run/podman-helper-{{.User}}.socket</string> 53 <key>SockPathOwner</key> 54 <integer>{{.UID}}</integer> 55 <key>SockPathMode</key> 56 <!-- SockPathMode takes base 10 (384 = 0600) --> 57 <integer>384</integer> 58 <key>SockType</key> 59 <string>stream</string> 60 </dict> 61 </dict> 62 </dict> 63 </plist> 64 ` 65 66 type launchParams struct { 67 Program string 68 User string 69 UID string 70 Target string 71 } 72 73 var installCmd = &cobra.Command{ 74 Use: "install", 75 Short: "installs the podman helper agent", 76 Long: "installs the podman helper agent, which manages the /var/run/docker.sock link", 77 PreRun: silentUsage, 78 RunE: install, 79 } 80 81 func init() { 82 addPrefixFlag(installCmd) 83 rootCmd.AddCommand(installCmd) 84 } 85 86 func install(cmd *cobra.Command, args []string) error { 87 userName, uid, homeDir, err := getUser() 88 if err != nil { 89 return err 90 } 91 92 labelName := fmt.Sprintf("com.github.containers.podman.helper-%s.plist", userName) 93 fileName := filepath.Join("/Library", "LaunchDaemons", labelName) 94 95 if _, err := os.Stat(fileName); err == nil || !os.IsNotExist(err) { 96 return errors.New("helper is already installed, uninstall first") 97 } 98 99 prog, err := installExecutable(userName) 100 if err != nil { 101 return err 102 } 103 104 target := filepath.Join(homeDir, ".local", "share", "containers", "podman", "machine", "podman.sock") 105 var buf bytes.Buffer 106 t := template.Must(template.New("launchdConfig").Parse(launchConfig)) 107 err = t.Execute(&buf, launchParams{prog, userName, uid, target}) 108 if err != nil { 109 return err 110 } 111 112 file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_EXCL, rw_r_r) 113 if err != nil { 114 return errors.Wrap(err, "error creating helper plist file") 115 } 116 defer file.Close() 117 _, err = buf.WriteTo(file) 118 if err != nil { 119 return err 120 } 121 122 if err = runDetectErr("launchctl", "load", fileName); err != nil { 123 return errors.Wrap(err, "launchctl failed loading service") 124 } 125 126 return nil 127 } 128 129 func restrictRecursive(targetDir string, until string) error { 130 for targetDir != until && len(targetDir) > 1 { 131 info, err := os.Lstat(targetDir) 132 if err != nil { 133 return err 134 } 135 if info.Mode()&fs.ModeSymlink != 0 { 136 return errors.Errorf("symlinks not allowed in helper paths (remove them and rerun): %s", targetDir) 137 } 138 if err = os.Chown(targetDir, 0, 0); err != nil { 139 return errors.Wrap(err, "could not update ownership of helper path") 140 } 141 if err = os.Chmod(targetDir, rwx_rx_rx|fs.ModeSticky); err != nil { 142 return errors.Wrap(err, "could not update permissions of helper path") 143 } 144 targetDir = filepath.Dir(targetDir) 145 } 146 147 return nil 148 } 149 150 func verifyRootDeep(path string) error { 151 path = filepath.Clean(path) 152 current := "/" 153 segs := strings.Split(path, "/") 154 depth := 0 155 for i := 1; i < len(segs); i++ { 156 seg := segs[i] 157 current = filepath.Join(current, seg) 158 info, err := os.Lstat(current) 159 if err != nil { 160 return err 161 } 162 163 stat := info.Sys().(*syscall.Stat_t) 164 if stat.Uid != 0 { 165 return errors.Errorf("installation target path must be solely owned by root: %s is not", current) 166 } 167 168 if info.Mode()&fs.ModeSymlink != 0 { 169 target, err := os.Readlink(current) 170 if err != nil { 171 return err 172 } 173 174 targetParts := strings.Split(target, "/") 175 segs = append(targetParts, segs[i+1:]...) 176 177 if depth++; depth > 1000 { 178 return errors.New("reached max recursion depth, link structure is cyclical or too complex") 179 } 180 181 if !filepath.IsAbs(target) { 182 current = filepath.Dir(current) 183 i = -1 // Start at 0 184 } else { 185 current = "/" 186 i = 0 // Skip empty first segment 187 } 188 } 189 } 190 191 return nil 192 } 193 194 func installExecutable(user string) (string, error) { 195 // Since the installed executable runs as root, as a precaution verify root ownership of 196 // the entire installation path, and utilize sticky + read only perms for the helper path 197 // suffix. The goal is to help users harden against privilege escalation from loose 198 // filesystem permissions. 199 // 200 // Since userspace package management tools, such as brew, delegate management of system 201 // paths to standard unix users, the daemon executable is copied into a separate more 202 // restricted area of the filesystem. 203 if err := verifyRootDeep(installPrefix); err != nil { 204 return "", err 205 } 206 207 targetDir := filepath.Join(installPrefix, "podman", "helper", user) 208 if err := os.MkdirAll(targetDir, rwx_rx_rx); err != nil { 209 return "", errors.Wrap(err, "could not create helper directory structure") 210 } 211 212 // Correct any incorrect perms on previously existing directories and verify no symlinks 213 if err := restrictRecursive(targetDir, installPrefix); err != nil { 214 return "", err 215 } 216 217 exec, err := os.Executable() 218 if err != nil { 219 return "", err 220 } 221 install := filepath.Join(targetDir, filepath.Base(exec)) 222 223 return install, copyFile(install, exec, rwx_rx_rx) 224 } 225 226 func copyFile(dest string, source string, perms fs.FileMode) error { 227 in, err := os.Open(source) 228 if err != nil { 229 return err 230 } 231 232 defer in.Close() 233 out, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perms) 234 if err != nil { 235 return err 236 } 237 238 defer out.Close() 239 if _, err := io.Copy(out, in); err != nil { 240 return err 241 } 242 243 return nil 244 }