Skip to content

Commit

Permalink
Open bind mount sources from the host userns
Browse files Browse the repository at this point in the history
The source of the bind mount might not be accessible in a different user
namespace because a component of the source path might not be traversed
under the users and groups mapped inside the user namespace. This caused
errors such as the following:

  # time="2020-06-22T13:48:26Z" level=error msg="container_linux.go:367:
  starting container process caused: process_linux.go:459:
  container init caused: rootfs_linux.go:58:
  mounting \"/tmp/busyboxtest/source-inaccessible/dir\"
  to rootfs at \"/tmp/inaccessible\" caused:
  stat /tmp/busyboxtest/source-inaccessible/dir: permission denied"

To solve this problem, this patch performs the following:

1. in nsexec.c, it opens the source path in the host userns (so we have
   the right permissions to open it) but in the container mntns (so the
   kernel cross mntns mount check let us mount it later:
   https://github.com/torvalds/linux/blob/v5.8/fs/namespace.c#L2312).

2. in nsexec.c, it passes the file descriptors of the source to the
   child process with SCM_RIGHTS.

3. In runc-init in Golang, it finishes the mounts while inside the
   userns even without access to the some components of the source
   paths.

Passing the fds with SCM_RIGHTS is necessary because once the child
process is in the container mntns, it is already in the container userns
so it cannot temporarily join the host mntns.

This patch uses the existing mechanism with _LIBCONTAINER_* environment
variables to pass the file descriptors from runc to runc init.

This patch uses the existing mechanism with the Netlink-style bootstrap
to pass information about the list of source mounts to nsexec.c.

Signed-off-by: Alban Crequy <[email protected]>
  • Loading branch information
alban committed Sep 10, 2020
1 parent 165ecd2 commit 4dbfb35
Show file tree
Hide file tree
Showing 7 changed files with 359 additions and 35 deletions.
41 changes: 40 additions & 1 deletion libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,24 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, messageSockPa
if err != nil {
return nil, err
}

for i, m := range c.config.Mounts {
if m.Device != "bind" {
continue
}
// The fd passed here will not be used: nsexec.c will overwrite it with dup2(). We just need
// to allocate a fd so that we know the number to pass in the environment variable. The fd
// must not be closed before cmd.Start(), so we reuse messageSockPair.child because the
// lifecycle of that fd is already taken care of.
cmd.ExtraFiles = append(cmd.ExtraFiles, messageSockPair.child)
cmd.Env = append(cmd.Env,
fmt.Sprintf("_LIBCONTAINER_MOUNT_FILE_%d=%d", i, stdioFdCount+len(cmd.ExtraFiles)-1),
)
}
cmd.Env = append(cmd.Env,
fmt.Sprintf("_LIBCONTAINER_MOUNT_FILE_COUNT=%d", len(c.config.Mounts)),
)

init := &initProcess{
cmd: cmd,
messageSockPair: messageSockPair,
Expand Down Expand Up @@ -1170,7 +1188,9 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
case "bind":
// The prepareBindMount() function checks if source
// exists. So it cannot be used for other filesystem types.
if err := prepareBindMount(m, c.config.Rootfs); err != nil {
// TODO: pass something else than nil? Not sure if criu is
// impacted by issue #2484
if err := prepareBindMount(m, c.config.Rootfs, nil); err != nil {
return err
}
default:
Expand Down Expand Up @@ -2053,6 +2073,25 @@ func (c *linuxContainer) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Na
Value: c.config.RootlessEUID,
})

// bind mount source to open
if len(c.config.Mounts) > 0 &&
c.config.Namespaces.Contains(configs.NEWUSER) &&
c.config.Namespaces.Contains(configs.NEWNS) {

var mounts []byte
for _, m := range c.config.Mounts {
if m.Device == "bind" {
mounts = append(mounts, []byte(m.Source)...)
}
mounts = append(mounts, byte(0))
}

r.AddData(&Bytemsg{
Type: MountSourcesAttr,
Value: mounts,
})
}

return bytes.NewReader(r.Serialize()), nil
}

Expand Down
36 changes: 30 additions & 6 deletions libcontainer/factory_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,11 +336,13 @@ func (l *LinuxFactory) Type() string {
// This is a low level implementation detail of the reexec and should not be consumed externally
func (l *LinuxFactory) StartInitialization() (err error) {
var (
pipefd, fifofd int
consoleSocket *os.File
envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE")
envFifoFd = os.Getenv("_LIBCONTAINER_FIFOFD")
envConsole = os.Getenv("_LIBCONTAINER_CONSOLE")
pipefd, fifofd int
mountFileCount int
consoleSocket *os.File
envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE")
envFifoFd = os.Getenv("_LIBCONTAINER_FIFOFD")
envConsole = os.Getenv("_LIBCONTAINER_CONSOLE")
envMountFileCount = os.Getenv("_LIBCONTAINER_MOUNT_FILE_COUNT")
)

// Get the INITPIPE.
Expand Down Expand Up @@ -372,6 +374,28 @@ func (l *LinuxFactory) StartInitialization() (err error) {
defer consoleSocket.Close()
}

// Get the mount files (O_PATH)
mountFileCount, err = strconv.Atoi(envMountFileCount)
if err != nil {
return fmt.Errorf("unable to convert _LIBCONTAINER_MOUNT_FILE_COUNT=%s to int: %s",
envMountFileCount, err)
}
mountFiles := make([]*os.File, mountFileCount)
for i := 0; i < mountFileCount; i++ {
envMountFile := os.Getenv(fmt.Sprintf("_LIBCONTAINER_MOUNT_FILE_%d", i))
if envMountFile == "" {
continue
}
mountFileFd, err := strconv.Atoi(envMountFile)
if err != nil {
return fmt.Errorf("unable to convert _LIBCONTAINER_MOUNT_FILE_%d=%s to int: %s",
i, envMountFile, err)
}
mountFile := os.NewFile(uintptr(mountFileFd), "mount-file")
defer mountFile.Close()
mountFiles[i] = mountFile
}

// clear the current process's environment to clean any libcontainer
// specific env vars.
os.Clearenv()
Expand All @@ -394,7 +418,7 @@ func (l *LinuxFactory) StartInitialization() (err error) {
}
}()

i, err := newContainerInit(it, pipe, consoleSocket, fifofd)
i, err := newContainerInit(it, pipe, consoleSocket, fifofd, mountFiles)
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion libcontainer/init_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ type initer interface {
Init() error
}

func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd int) (initer, error) {
func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd int, mountFiles []*os.File) (initer, error) {
var config *initConfig
if err := json.NewDecoder(pipe).Decode(&config); err != nil {
return nil, err
Expand All @@ -97,6 +97,7 @@ func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd
parentPid: unix.Getppid(),
config: config,
fifoFd: fifoFd,
mountFiles: mountFiles,
}, nil
}
return nil, fmt.Errorf("unknown init type %q", t)
Expand Down
1 change: 1 addition & 0 deletions libcontainer/message_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
RootlessEUIDAttr uint16 = 27287
UidmapPathAttr uint16 = 27288
GidmapPathAttr uint16 = 27289
MountSourcesAttr uint16 = 27290
)

type Int32msg struct {
Expand Down
Loading

0 comments on commit 4dbfb35

Please sign in to comment.