Repository: containerd/containerd
File: pkg/sys/unshare_linux.go (Go)

Code before:
			HostID:      hID,
			Size:        size,
		},
	}, nil
}

// startProcessWithUserNamespace starts a ptraced dummy process to create
// a user namespace. It runs the thread as targetHostUID when creating the process
// and user namespace, ensuring that the kernel attributes user limits to targetHostUID
// and not containerd's user. On success it the pid, pidfd and no error.
// It is expected that the caller sets up the uid_map/gid_map for the
// pid (this cannot be done as part of os.StartProcess() since targetHostUID doesn't
// have CAP_SETUID)
func startProcessWithUserNamespace(targetHostUID int, unshareFlags uintptr) (int, int, error) {
	var pidfd int
	originalEUID := os.Geteuid()

	// Lock the thread, and use setresuid() directly to only apply the effective uid to this thread
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	if _, _, errno := syscall.RawSyscall(unix.SYS_SETRESUID, ^uintptr(0), uintptr(targetHostUID), ^uintptr(0)); errno != 0 {
		return -1, -1, fmt.Errorf("failed to set effective UID: %w", errno)
	}
	defer func() {
		syscall.RawSyscall(unix.SYS_SETRESUID, ^uintptr(0), uintptr(originalEUID), ^uintptr(0))
	}()

	proc, err := os.StartProcess("/proc/self/exe", []string{"UnshareAfterEnterUserns"}, &os.ProcAttr{
		Sys: &syscall.SysProcAttr{
			// clone new user namespace first and then unshare
			Cloneflags:   unix.CLONE_NEWUSER,
			Unshareflags: unshareFlags,
			// NOTE: It's reexec but it's not heavy because subprocess
			// be in PTRACE_TRACEME mode before performing execve.
			Ptrace:    true,
			Pdeathsig: syscall.SIGKILL,
			PidFD:     &pidfd,
		},
	})

	if err != nil {
		return -1, -1, fmt.Errorf("failed to start noop process for unshare: %w", err)
	}

	if pidfd == -1 {
		proc.Kill()
		proc.Wait()
		return -1, -1, fmt.Errorf("kernel doesn't support CLONE_PIDFD")
	}


Review comment (bug):
What if this fail? I guess containerd will be in a very weird state, as one OS thread will be running unprivileged and things will fail unexpectedly.

Is there a better alternative than all of the test in a goroutine with OSLockThread and, then, if it fails we just finish the goroutine without finishing the lock? In that case the go runtime will kill the OS thread:

See https://pkg.go.dev/runtime#LockOSThread:

> LockOSThread wires the calling goroutine to its current operating system thread. The calling goroutine will always execute in that thread, and no other goroutine will execute in it, until the calling goroutine has made as many calls to [UnlockOSThread](https://pkg.go.dev/runtime#UnlockOSThread) as to LockOSThread. If the calling goroutine exits without unlocking the thread, the thread will be terminated.

Code after:
		return -1, -1, fmt.Errorf("failed to restore capabilities: %w", err)
	}

	var pidfd int
	proc, err := os.StartProcess("/proc/self/exe", []string{"UnshareAfterEnterUserns"}, &os.ProcAttr{
		Sys: &syscall.SysProcAttr{
			// clone new user namespace first and then unshare
			Cloneflags:                 unix.CLONE_NEWUSER,
			Unshareflags:               unshareFlags,
			UidMappings:                uidMaps,
			GidMappings:                gidMaps,
			GidMappingsEnableSetgroups: true,
			// NOTE: It's reexec but it's not heavy because subprocess
			// be in PTRACE_TRACEME mode before performing execve.
			Ptrace:    true,
			Pdeathsig: syscall.SIGKILL,
			PidFD:     &pidfd,
		},
	})
	if err != nil {
		return -1, -1, fmt.Errorf("failed to start noop process for unshare: %w", err)
	}

	if pidfd == -1 {
		proc.Kill()
		proc.Wait()
		return -1, -1, fmt.Errorf("kernel doesn't support CLONE_PIDFD")
	}

	if _, _, errno := syscall.RawSyscall(unix.SYS_SETRESUID, ^uintptr(0), uintptr(originalEUID), ^uintptr(0)); errno != 0 {
		proc.Kill()
		proc.Wait()
		unix.Close(pidfd)
		return -1, -1, fmt.Errorf("failed to restore UID: %w", errno)
	}

	return proc.Pid, pidfd, nil
}

func pidfdWaitid(pidfd int) error {
	return IgnoringEINTR(func() error {
		return unix.Waitid(unix.P_PIDFD, pidfd, nil, unix.WEXITED, nil)
	})
}

type capSnapshot struct {
	hdr  unix.CapUserHeader
	data [2]unix.CapUserData
}

func getCurrentCaps() (*capSnapshot, error) {