github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/pkg/rootless/rootless_linux.go (about) 1 // +build linux,cgo 2 3 package rootless 4 5 import ( 6 "bufio" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "os" 11 "os/exec" 12 gosignal "os/signal" 13 "os/user" 14 "runtime" 15 "strconv" 16 "sync" 17 "unsafe" 18 19 "github.com/containers/libpod/pkg/errorhandling" 20 "github.com/containers/storage/pkg/idtools" 21 "github.com/pkg/errors" 22 "github.com/sirupsen/logrus" 23 "golang.org/x/sys/unix" 24 ) 25 26 /* 27 #cgo remoteclient CFLAGS: -Wall -Werror -DDISABLE_JOIN_SHORTCUT 28 #include <stdlib.h> 29 #include <sys/types.h> 30 extern uid_t rootless_uid(); 31 extern uid_t rootless_gid(); 32 extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path, char *file_to_read, int fd); 33 extern int reexec_in_user_namespace_wait(int pid, int options); 34 extern int reexec_userns_join(int pid, char *pause_pid_file_path); 35 */ 36 import "C" 37 38 const ( 39 numSig = 65 // max number of signals 40 ) 41 42 func runInUser() error { 43 return os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done") 44 } 45 46 var ( 47 isRootlessOnce sync.Once 48 isRootless bool 49 ) 50 51 // IsRootless tells us if we are running in rootless mode 52 func IsRootless() bool { 53 isRootlessOnce.Do(func() { 54 rootlessUIDInit := int(C.rootless_uid()) 55 rootlessGIDInit := int(C.rootless_gid()) 56 if rootlessUIDInit != 0 { 57 // This happens if we joined the user+mount namespace as part of 58 if err := os.Setenv("_CONTAINERS_USERNS_CONFIGURED", "done"); err != nil { 59 logrus.Errorf("failed to set environment variable %s as %s", "_CONTAINERS_USERNS_CONFIGURED", "done") 60 } 61 if err := os.Setenv("_CONTAINERS_ROOTLESS_UID", fmt.Sprintf("%d", rootlessUIDInit)); err != nil { 62 logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_UID", rootlessUIDInit) 63 } 64 if err := os.Setenv("_CONTAINERS_ROOTLESS_GID", fmt.Sprintf("%d", rootlessGIDInit)); err != nil { 65 logrus.Errorf("failed to set environment variable %s as %d", "_CONTAINERS_ROOTLESS_GID", rootlessGIDInit) 66 } 67 } 68 isRootless = os.Geteuid() != 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" 69 }) 70 return isRootless 71 } 72 73 // GetRootlessUID returns the UID of the user in the parent userNS 74 func GetRootlessUID() int { 75 uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID") 76 if uidEnv != "" { 77 u, _ := strconv.Atoi(uidEnv) 78 return u 79 } 80 return os.Geteuid() 81 } 82 83 // GetRootlessGID returns the GID of the user in the parent userNS 84 func GetRootlessGID() int { 85 gidEnv := os.Getenv("_CONTAINERS_ROOTLESS_GID") 86 if gidEnv != "" { 87 u, _ := strconv.Atoi(gidEnv) 88 return u 89 } 90 91 /* If the _CONTAINERS_ROOTLESS_UID is set, assume the gid==uid. */ 92 uidEnv := os.Getenv("_CONTAINERS_ROOTLESS_UID") 93 if uidEnv != "" { 94 u, _ := strconv.Atoi(uidEnv) 95 return u 96 } 97 return os.Getegid() 98 } 99 100 func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error { 101 path, err := exec.LookPath(tool) 102 if err != nil { 103 return errors.Wrapf(err, "cannot find %s", tool) 104 } 105 106 appendTriplet := func(l []string, a, b, c int) []string { 107 return append(l, strconv.Itoa(a), strconv.Itoa(b), strconv.Itoa(c)) 108 } 109 110 args := []string{path, fmt.Sprintf("%d", pid)} 111 args = appendTriplet(args, 0, hostID, 1) 112 for _, i := range mappings { 113 args = appendTriplet(args, i.ContainerID+1, i.HostID, i.Size) 114 } 115 cmd := exec.Cmd{ 116 Path: path, 117 Args: args, 118 } 119 120 if output, err := cmd.CombinedOutput(); err != nil { 121 logrus.Debugf("error from %s: %s", tool, output) 122 return errors.Wrapf(err, "cannot setup namespace using %s", tool) 123 } 124 return nil 125 } 126 127 // joinUserAndMountNS re-exec podman in a new userNS and join the user and mount 128 // namespace of the specified PID without looking up its parent. Useful to join directly 129 // the conmon process. 130 func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) { 131 if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { 132 return false, -1, nil 133 } 134 135 cPausePid := C.CString(pausePid) 136 defer C.free(unsafe.Pointer(cPausePid)) 137 138 pidC := C.reexec_userns_join(C.int(pid), cPausePid) 139 if int(pidC) < 0 { 140 return false, -1, errors.Errorf("cannot re-exec process") 141 } 142 143 ret := C.reexec_in_user_namespace_wait(pidC, 0) 144 if ret < 0 { 145 return false, -1, errors.New("error waiting for the re-exec process") 146 } 147 148 return true, int(ret), nil 149 } 150 151 // GetConfiguredMappings returns the additional IDs configured for the current user. 152 func GetConfiguredMappings() ([]idtools.IDMap, []idtools.IDMap, error) { 153 var uids, gids []idtools.IDMap 154 username := os.Getenv("USER") 155 if username == "" { 156 var id string 157 if os.Geteuid() == 0 { 158 id = strconv.Itoa(GetRootlessUID()) 159 } else { 160 id = strconv.Itoa(os.Geteuid()) 161 } 162 userID, err := user.LookupId(id) 163 if err == nil { 164 username = userID.Username 165 } 166 } 167 mappings, err := idtools.NewIDMappings(username, username) 168 if err != nil { 169 logrus.Errorf("cannot find mappings for user %s: %v", username, err) 170 } else { 171 uids = mappings.UIDs() 172 gids = mappings.GIDs() 173 } 174 return uids, gids, nil 175 } 176 177 func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (bool, int, error) { 178 if os.Geteuid() == 0 || os.Getenv("_CONTAINERS_USERNS_CONFIGURED") != "" { 179 if os.Getenv("_CONTAINERS_USERNS_CONFIGURED") == "init" { 180 return false, 0, runInUser() 181 } 182 return false, 0, nil 183 } 184 185 cPausePid := C.CString(pausePid) 186 defer C.free(unsafe.Pointer(cPausePid)) 187 188 cFileToRead := C.CString(fileToRead) 189 defer C.free(unsafe.Pointer(cFileToRead)) 190 var fileOutputFD C.int 191 if fileOutput != nil { 192 fileOutputFD = C.int(fileOutput.Fd()) 193 } 194 195 runtime.LockOSThread() 196 defer runtime.UnlockOSThread() 197 198 fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) 199 if err != nil { 200 return false, -1, err 201 } 202 r, w := os.NewFile(uintptr(fds[0]), "sync host"), os.NewFile(uintptr(fds[1]), "sync child") 203 204 defer errorhandling.CloseQuiet(r) 205 defer errorhandling.CloseQuiet(w) 206 defer func() { 207 if _, err := w.Write([]byte("0")); err != nil { 208 logrus.Errorf("failed to write byte 0: %q", err) 209 } 210 }() 211 212 pidC := C.reexec_in_user_namespace(C.int(r.Fd()), cPausePid, cFileToRead, fileOutputFD) 213 pid := int(pidC) 214 if pid < 0 { 215 return false, -1, errors.Errorf("cannot re-exec process") 216 } 217 218 uids, gids, err := GetConfiguredMappings() 219 if err != nil { 220 return false, -1, err 221 } 222 223 uidsMapped := false 224 if uids != nil { 225 err := tryMappingTool("newuidmap", pid, os.Geteuid(), uids) 226 uidsMapped = err == nil 227 } 228 if !uidsMapped { 229 logrus.Warnf("using rootless single mapping into the namespace. This might break some images. Check /etc/subuid and /etc/subgid for adding subids") 230 setgroups := fmt.Sprintf("/proc/%d/setgroups", pid) 231 err = ioutil.WriteFile(setgroups, []byte("deny\n"), 0666) 232 if err != nil { 233 return false, -1, errors.Wrapf(err, "cannot write setgroups file") 234 } 235 logrus.Debugf("write setgroups file exited with 0") 236 237 uidMap := fmt.Sprintf("/proc/%d/uid_map", pid) 238 err = ioutil.WriteFile(uidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Geteuid())), 0666) 239 if err != nil { 240 return false, -1, errors.Wrapf(err, "cannot write uid_map") 241 } 242 logrus.Debugf("write uid_map exited with 0") 243 } 244 245 gidsMapped := false 246 if gids != nil { 247 err := tryMappingTool("newgidmap", pid, os.Getegid(), gids) 248 gidsMapped = err == nil 249 } 250 if !gidsMapped { 251 gidMap := fmt.Sprintf("/proc/%d/gid_map", pid) 252 err = ioutil.WriteFile(gidMap, []byte(fmt.Sprintf("%d %d 1\n", 0, os.Getegid())), 0666) 253 if err != nil { 254 return false, -1, errors.Wrapf(err, "cannot write gid_map") 255 } 256 } 257 258 _, err = w.Write([]byte("0")) 259 if err != nil { 260 return false, -1, errors.Wrapf(err, "write to sync pipe") 261 } 262 263 b := make([]byte, 1) 264 _, err = w.Read(b) 265 if err != nil { 266 return false, -1, errors.Wrapf(err, "read from sync pipe") 267 } 268 269 if fileOutput != nil { 270 return true, 0, nil 271 } 272 273 if b[0] == '2' { 274 // We have lost the race for writing the PID file, as probably another 275 // process created a namespace and wrote the PID. 276 // Try to join it. 277 data, err := ioutil.ReadFile(pausePid) 278 if err == nil { 279 pid, err := strconv.ParseUint(string(data), 10, 0) 280 if err == nil { 281 return joinUserAndMountNS(uint(pid), "") 282 } 283 } 284 return false, -1, errors.Wrapf(err, "error setting up the process") 285 } 286 287 if b[0] != '0' { 288 return false, -1, errors.Wrapf(err, "error setting up the process") 289 } 290 291 c := make(chan os.Signal, 1) 292 293 signals := []os.Signal{} 294 for sig := 0; sig < numSig; sig++ { 295 if sig == int(unix.SIGTSTP) { 296 continue 297 } 298 signals = append(signals, unix.Signal(sig)) 299 } 300 301 gosignal.Notify(c, signals...) 302 defer gosignal.Reset() 303 go func() { 304 for s := range c { 305 if s == unix.SIGCHLD || s == unix.SIGPIPE { 306 continue 307 } 308 309 if err := unix.Kill(int(pidC), s.(unix.Signal)); err != nil { 310 logrus.Errorf("failed to kill %d", int(pidC)) 311 } 312 } 313 }() 314 315 ret := C.reexec_in_user_namespace_wait(pidC, 0) 316 if ret < 0 { 317 return false, -1, errors.New("error waiting for the re-exec process") 318 } 319 320 return true, int(ret), nil 321 } 322 323 // BecomeRootInUserNS re-exec podman in a new userNS. It returns whether podman was re-executed 324 // into a new user namespace and the return code from the re-executed podman process. 325 // If podman was re-executed the caller needs to propagate the error code returned by the child 326 // process. 327 func BecomeRootInUserNS(pausePid string) (bool, int, error) { 328 return becomeRootInUserNS(pausePid, "", nil) 329 } 330 331 // TryJoinFromFilePaths attempts to join the namespaces of the pid files in paths. 332 // This is useful when there are already running containers and we 333 // don't have a pause process yet. We can use the paths to the conmon 334 // processes to attempt joining their namespaces. 335 // If needNewNamespace is set, the file is read from a temporary user 336 // namespace, this is useful for containers that are running with a 337 // different uidmap and the unprivileged user has no way to read the 338 // file owned by the root in the container. 339 func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []string) (bool, int, error) { 340 if len(paths) == 0 { 341 return BecomeRootInUserNS(pausePidPath) 342 } 343 344 var lastErr error 345 var pausePid int 346 foundProcess := false 347 348 for _, path := range paths { 349 if !needNewNamespace { 350 data, err := ioutil.ReadFile(path) 351 if err != nil { 352 lastErr = err 353 continue 354 } 355 356 pausePid, err = strconv.Atoi(string(data)) 357 if err != nil { 358 lastErr = errors.Wrapf(err, "cannot parse file %s", path) 359 continue 360 } 361 362 lastErr = nil 363 break 364 } else { 365 fds, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_DGRAM, 0) 366 if err != nil { 367 lastErr = err 368 continue 369 } 370 371 r, w := os.NewFile(uintptr(fds[0]), "read file"), os.NewFile(uintptr(fds[1]), "write file") 372 373 defer errorhandling.CloseQuiet(r) 374 375 if _, _, err := becomeRootInUserNS("", path, w); err != nil { 376 w.Close() 377 lastErr = err 378 continue 379 } 380 381 if err := w.Close(); err != nil { 382 return false, 0, err 383 } 384 defer func() { 385 C.reexec_in_user_namespace_wait(-1, 0) 386 }() 387 388 b := make([]byte, 32) 389 390 n, err := r.Read(b) 391 if err != nil { 392 lastErr = errors.Wrapf(err, "cannot read %s\n", path) 393 continue 394 } 395 396 pausePid, err = strconv.Atoi(string(b[:n])) 397 if err == nil && unix.Kill(pausePid, 0) == nil { 398 foundProcess = true 399 lastErr = nil 400 break 401 } 402 } 403 } 404 if !foundProcess && pausePidPath != "" { 405 return BecomeRootInUserNS(pausePidPath) 406 } 407 if lastErr != nil { 408 return false, 0, lastErr 409 } 410 411 return joinUserAndMountNS(uint(pausePid), pausePidPath) 412 } 413 414 // ReadMappingsProc parses and returns the ID mappings at the specified path. 415 func ReadMappingsProc(path string) ([]idtools.IDMap, error) { 416 file, err := os.Open(path) 417 if err != nil { 418 return nil, errors.Wrapf(err, "cannot open %s", path) 419 } 420 defer file.Close() 421 422 mappings := []idtools.IDMap{} 423 424 buf := bufio.NewReader(file) 425 for { 426 line, _, err := buf.ReadLine() 427 if err != nil { 428 if err == io.EOF { 429 return mappings, nil 430 } 431 return nil, errors.Wrapf(err, "cannot read line from %s", path) 432 } 433 if line == nil { 434 return mappings, nil 435 } 436 437 containerID, hostID, size := 0, 0, 0 438 if _, err := fmt.Sscanf(string(line), "%d %d %d", &containerID, &hostID, &size); err != nil { 439 return nil, errors.Wrapf(err, "cannot parse %s", string(line)) 440 } 441 mappings = append(mappings, idtools.IDMap{ContainerID: containerID, HostID: hostID, Size: size}) 442 } 443 } 444 445 func matches(id int, configuredIDs []idtools.IDMap, currentIDs []idtools.IDMap) bool { 446 // The first mapping is the host user, handle it separately. 447 if currentIDs[0].HostID != id || currentIDs[0].Size != 1 { 448 return false 449 } 450 451 currentIDs = currentIDs[1:] 452 if len(currentIDs) != len(configuredIDs) { 453 return false 454 } 455 456 // It is fine to iterate sequentially as both slices are sorted. 457 for i := range currentIDs { 458 if currentIDs[i].HostID != configuredIDs[i].HostID { 459 return false 460 } 461 if currentIDs[i].Size != configuredIDs[i].Size { 462 return false 463 } 464 } 465 466 return true 467 } 468 469 // ConfigurationMatches checks whether the additional uids/gids configured for the user 470 // match the current user namespace. 471 func ConfigurationMatches() (bool, error) { 472 if !IsRootless() || os.Geteuid() != 0 { 473 return true, nil 474 } 475 476 uids, gids, err := GetConfiguredMappings() 477 if err != nil { 478 return false, err 479 } 480 481 currentUIDs, err := ReadMappingsProc("/proc/self/uid_map") 482 if err != nil { 483 return false, err 484 } 485 486 if !matches(GetRootlessUID(), uids, currentUIDs) { 487 return false, err 488 } 489 490 currentGIDs, err := ReadMappingsProc("/proc/self/gid_map") 491 if err != nil { 492 return false, err 493 } 494 495 return matches(GetRootlessGID(), gids, currentGIDs), nil 496 }