Skip to content

runsc: Allow map host user to non-root user in rootless mode #11972

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions runsc/cmd/gofer.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package cmd
import (
"context"
"encoding/json"
"encoding/binary"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -64,6 +65,8 @@ var goferCaps = &specs.LinuxCapabilities{
Bounding: caps,
Effective: caps,
Permitted: caps,
Inheritable: caps,
Ambient: caps,
}

var goferUdsOpenCaps = &specs.LinuxCapabilities{
Expand Down Expand Up @@ -818,6 +821,26 @@ func waitForFD(fd int, fdName string) error {
return nil
}

func waitForID(fd int, fdName string) (uint32, uint32, error) {
log.Debugf("Waiting on %s %d...", fdName, fd)
f := os.NewFile(uintptr(fd), fdName)
defer f.Close()

var uid uint32
var gid uint32
buf := make([]byte, 8)

if n, err := f.Read(buf); n != 8 || err != nil {
e := fmt.Errorf("failed to convert to int:%v :%v", uid, err)
return 0, 0, e
}
uid = binary.BigEndian.Uint32(buf[0:4])
gid = binary.BigEndian.Uint32(buf[4:8])


return uid, gid, nil
}

// spawnProcMounter executes the /proc unmounter process.
// It returns a function to wait on the proc unmounter process, which
// should be called (via defer) in case of errors in order to clean up the
Expand Down Expand Up @@ -872,17 +895,22 @@ func (g *goferSyncFDs) syncUsernsForRootless() {
//
// Postcondition: All callers must re-exec themselves after this returns.
func syncUsernsForRootless(fd int) {
if err := waitForFD(fd, "userns sync FD"); err != nil {
util.Fatalf("failed to sync on userns FD: %v", err)
var uid uint32
var gid uint32
var err error

if uid, gid, err = waitForID(fd, "userns sync FD"); err != nil {
util.Fatalf("failed to sync on userns FD:%v: %v %v", uid, gid, err)
}


// SETUID changes UID on the current system thread, so we have
// to re-execute current binary.
runtime.LockOSThread()
if _, _, errno := unix.RawSyscall(unix.SYS_SETUID, 0, 0, 0); errno != 0 {
if _, _, errno := unix.RawSyscall(unix.SYS_SETUID, uintptr(uid), 0, 0); errno != 0 {
util.Fatalf("failed to set UID: %v", errno)
}
if _, _, errno := unix.RawSyscall(unix.SYS_SETGID, 0, 0, 0); errno != 0 {
if _, _, errno := unix.RawSyscall(unix.SYS_SETGID, uintptr(gid), 0, 0); errno != 0 {
util.Fatalf("failed to set GID: %v", errno)
}
}
Expand Down
16 changes: 15 additions & 1 deletion runsc/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,13 @@ func (c *Container) createGoferProcess(conf *config.Config, mountHints *boot.Pod
{Type: specs.UTSNamespace},
}

var gSyncFile *os.File
defer func() {
if gSyncFile != nil {
gSyncFile.Close()
}
}()

rootlessEUID := unix.Geteuid() != 0
// Setup any uid/gid mappings, and create or join the configured user
// namespace so the gofer's view of the filesystem aligns with the
Expand All @@ -1413,7 +1420,7 @@ func (c *Container) createGoferProcess(conf *config.Config, mountHints *boot.Pod
if err != nil {
return nil, nil, nil, nil, err
}
defer syncFile.Close()
gSyncFile = syncFile
}

// Create synchronization FD for chroot.
Expand Down Expand Up @@ -1459,6 +1466,13 @@ func (c *Container) createGoferProcess(conf *config.Config, mountHints *boot.Pod
return nil, nil, nil, nil, fmt.Errorf("creating gofer filestore files: %w", err)
}

if rootlessEUID {
chrootSyncSandEnd.Close()
if err := sandbox.SendIDToSandbox(gSyncFile, c.Spec); err != nil {
return nil, nil, nil, nil, err
}
}

return sandEnds, goferFilestores, devSandEnd, mountsSand, nil
}

Expand Down
47 changes: 46 additions & 1 deletion runsc/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package sandbox

import (
"context"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -1030,6 +1031,13 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
// configured.
rootlessEUID := unix.Geteuid() != 0
setUserMappings := false
var gSyncFile *os.File
defer func() {
if gSyncFile != nil {
gSyncFile.Close()
}
}()

if conf.Network == config.NetworkHost || conf.DirectFS {
if userns, ok := specutils.GetNS(specs.UserNamespace, args.Spec); ok {
log.Infof("Sandbox will be started in container's user namespace: %+v", userns)
Expand All @@ -1039,7 +1047,7 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
if err != nil {
return err
}
defer syncFile.Close()
gSyncFile = syncFile
setUserMappings = true
} else {
specutils.SetUIDGIDMappings(cmd, args.Spec)
Expand Down Expand Up @@ -1284,6 +1292,9 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
if err := SetUserMappings(args.Spec, cmd.Process.Pid); err != nil {
return err
}
if err := SendIDToSandbox(gSyncFile, args.Spec); err != nil {
return err
}
}

s.child = true
Expand All @@ -1293,6 +1304,40 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
return nil
}

func rootMappedInContainer(IDMap []specs.LinuxIDMapping) bool {
for _, idMap := range IDMap {
if idMap.ContainerID == 0 {
return true
}
}
return false
}

// Send the UID & GID to the sandbox and gofer process
// This UID & GID is the ID for container init process
func SendIDToSandbox(syncFile *os.File, spec *specs.Spec) error {

uid := uint32(0)
gid := uint32(0)

if !rootMappedInContainer(spec.Linux.UIDMappings) {
uid = spec.Process.User.UID
}

if !rootMappedInContainer(spec.Linux.GIDMappings) {
gid = spec.Process.User.GID
}

buf := make([]byte, 8)
binary.BigEndian.PutUint32(buf[0:4], uid)
binary.BigEndian.PutUint32(buf[4:8], gid)
if _, err := syncFile.Write(buf); err != nil {
return fmt.Errorf("write uid&gid to sandbox error: %w", err)
}

return nil
}

// Wait waits for the containerized process to exit, and returns its WaitStatus.
func (s *Sandbox) Wait(cid string) (unix.WaitStatus, error) {
log.Debugf("Waiting for container %q in sandbox %q", cid, s.ID)
Expand Down