Skip to content

runsc: Make identity user mapping work for filesystem #11875

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

Closed
wants to merge 1 commit into from
Closed
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
67 changes: 66 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,60 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
return nil
}

// 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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls, write a comment to explain what this function is doing.


specUID := uint32(spec.Process.User.UID)
specGID := uint32(spec.Process.User.GID)

euid := uint32(os.Geteuid())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this right, the sandbox will be running under the current user. Strictly speaking, the current user might be unmapped in the container user namespace. I think sandbox/gofer processes should ideally run under the root user if that user is mapped; otherwise, they need to run under the user associated with the init process.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I make a new PR:
b799444

egid := uint32(os.Getegid())

var cuid uint32
var cgid uint32
var uidFound, gidFound bool

// Find which container UID & GID the host user map to
for _, idMap := range spec.Linux.UIDMappings {
if euid >= idMap.HostID && euid < idMap.Size + idMap.HostID {
cuid = euid - idMap.HostID + idMap.ContainerID
uidFound = true
break
}
}

for _, idMap := range spec.Linux.GIDMappings {
if egid >= idMap.HostID && euid < idMap.Size + idMap.HostID {
cgid = egid - idMap.HostID + idMap.ContainerID
gidFound = true
break
}
}

if !uidFound || !gidFound {
return fmt.Errorf("current host uid(%d) or gid(%d) has no mapping in container", euid, egid)
}

// Make sure the specified UID & GID is the same as the host user map to
if cuid != specUID {
return fmt.Errorf("host uid %d can't map to container uid %d, expected %d", euid, specUID, cuid)
}

if cgid != specGID {
return fmt.Errorf("host gid %d can't map to container gid %d, expected %d", egid, specGID, cgid)
}

buf := make([]byte, 8)
binary.BigEndian.PutUint32(buf[0:4], cuid)
binary.BigEndian.PutUint32(buf[4:8], cgid)
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