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

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 @@ -784,6 +787,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 @@ -838,17 +861,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 @@ -1393,6 +1393,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 @@ -1414,7 +1421,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 @@ -1460,6 +1467,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
43 changes: 42 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 @@ -1029,6 +1030,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 @@ -1038,7 +1046,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 @@ -1283,6 +1291,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 @@ -1292,6 +1303,36 @@ func (s *Sandbox) createSandboxProcess(conf *config.Config, args *Args, startSyn
return nil
}

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.

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.

egid := uint32(os.Getegid())

var cuid uint32
var cgid uint32

for _, idMap := range spec.Linux.UIDMappings {
if euid >= idMap.HostID && euid < idMap.Size + idMap.HostID {
cuid = euid - idMap.HostID + idMap.ContainerID
break
}
}

for _, idMap := range spec.Linux.GIDMappings {
if egid >= idMap.HostID && euid < idMap.Size + idMap.HostID {
cgid = egid - idMap.HostID + idMap.ContainerID
break
}
}
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