Skip to content

Commit

Permalink
Only allow proc mount if it is procfs
Browse files Browse the repository at this point in the history
Fixes #2128

This allows proc to be bind mounted for host and rootless namespace usecases but
it removes the ability to mount over the top of proc with a directory.

```bash
> sudo docker run --rm  apparmor
docker: Error response from daemon: OCI runtime create failed:
container_linux.go:346: starting container process caused "process_linux.go:449:
container init caused \"rootfs_linux.go:58: mounting
\\\"/var/lib/docker/volumes/aae28ea068c33d60e64d1a75916cf3ec2dc3634f97571854c9ed30c8401460c1/_data\\\"
to rootfs
\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged\\\"
at \\\"/proc\\\" caused
\\\"\\\\\\\"/var/lib/docker/overlay2/a6be5ae911bf19f8eecb23a295dec85be9a8ee8da66e9fb55b47c841d1e381b7/merged/proc\\\\\\\"
cannot be mounted because it is not of type proc\\\"\"": unknown.

> sudo docker run --rm -v /proc:/proc apparmor

docker-default (enforce)        root     18989  0.9  0.0   1288     4 ?
Ss   16:47   0:00 sleep 20
```

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
  • Loading branch information
crosbymichael committed Sep 23, 2019
1 parent 7507c64 commit f979aca
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 18 deletions.
4 changes: 2 additions & 2 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"syscall" // only for SysProcAttr and Signal
"time"

"github.com/cyphar/filepath-securejoin"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/intelrdt"
Expand Down Expand Up @@ -1176,7 +1176,7 @@ func (c *linuxContainer) makeCriuRestoreMountpoints(m *configs.Mount) error {
if err != nil {
return err
}
if err := checkMountDestination(c.config.Rootfs, dest); err != nil {
if err := checkProcMount(c.config.Rootfs, dest, m.Source); err != nil {
return err
}
m.Destination = dest
Expand Down
22 changes: 22 additions & 0 deletions libcontainer/mount/mount.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package mount

import "errors"

// ErrNotMounted is returned when a path is not mounted.
var ErrNotMounted = errors.New("path not mounted")

// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
return parseMountTable()
Expand All @@ -21,3 +26,20 @@ func Mounted(mountpoint string) (bool, error) {
}
return false, nil
}

// FSType returns the file-system type of a mountpoint.
//
// ErrNotMounted is returned if the mountpoint refers to a path
// that is not mounted.
func FSType(mountpoint string) (string, error) {
entries, err := parseMountTable()
if err != nil {
return "", err
}
for _, e := range entries {
if e.Mountpoint == mountpoint {
return e.Fstype, nil
}
}
return "", ErrNotMounted
}
31 changes: 19 additions & 12 deletions libcontainer/rootfs_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"strings"
"time"

"github.com/cyphar/filepath-securejoin"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/mrunalp/fileutils"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
Expand Down Expand Up @@ -197,7 +197,7 @@ func prepareBindMount(m *configs.Mount, rootfs string) error {
if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
return err
}
if err := checkMountDestination(rootfs, dest); err != nil {
if err := checkProcMount(rootfs, dest, m.Source); err != nil {
return err
}
// update the mount with the correct dest after symlinks are resolved.
Expand Down Expand Up @@ -414,7 +414,7 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string, enableCgroupns b
if dest, err = securejoin.SecureJoin(rootfs, m.Destination); err != nil {
return err
}
if err := checkMountDestination(rootfs, dest); err != nil {
if err := checkProcMount(rootfs, dest, m.Source); err != nil {
return err
}
// update the mount with the correct dest after symlinks are resolved.
Expand Down Expand Up @@ -461,12 +461,9 @@ func getCgroupMounts(m *configs.Mount) ([]*configs.Mount, error) {
return binds, nil
}

// checkMountDestination checks to ensure that the mount destination is not over the top of /proc.
// checkProcMount checks to ensure that the mount destination is not over the top of /proc.
// dest is required to be an abs path and have any symlinks resolved before calling this function.
func checkMountDestination(rootfs, dest string) error {
invalidDestinations := []string{
"/proc",
}
func checkProcMount(rootfs, dest, source string) error {
// White list, it should be sub directories of invalid destinations
validDestinations := []string{
// These entries can be bind mounted by files emulated by fuse,
Expand All @@ -489,13 +486,23 @@ func checkMountDestination(rootfs, dest string) error {
return nil
}
}
for _, invalid := range invalidDestinations {
path, err := filepath.Rel(filepath.Join(rootfs, invalid), dest)
const procPath = "/proc"
path, err := filepath.Rel(filepath.Join(rootfs, procPath), dest)
if err != nil {
return err
}
// check if the path is outside the rootfs
if path == "." || !strings.HasPrefix(path, "..") {
// only allow a mount on-top of proc if it's source is "procfs"
fstype, err := mount.FSType(source)
if err != nil {
if err == mount.ErrNotMounted {
return fmt.Errorf("%q cannot be mounted because it is not of type proc", dest)
}
return err
}
if path != "." && !strings.HasPrefix(path, "..") {
return fmt.Errorf("%q cannot be mounted because it is located inside %q", dest, invalid)
if fstype != "proc" {
return fmt.Errorf("%q cannot be mounted because it is not of type proc", dest)
}
}
return nil
Expand Down
8 changes: 4 additions & 4 deletions libcontainer/rootfs_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,31 @@ import (

func TestCheckMountDestOnProc(t *testing.T) {
dest := "/rootfs/proc/sys"
err := checkMountDestination("/rootfs", dest)
err := checkProcMount("/rootfs", dest, "")
if err == nil {
t.Fatal("destination inside proc should return an error")
}
}

func TestCheckMountDestOnProcChroot(t *testing.T) {
dest := "/rootfs/proc/"
err := checkMountDestination("/rootfs", dest)
err := checkProcMount("/rootfs", dest, "/proc")
if err != nil {
t.Fatal("destination inside proc when using chroot should not return an error")
}
}

func TestCheckMountDestInSys(t *testing.T) {
dest := "/rootfs//sys/fs/cgroup"
err := checkMountDestination("/rootfs", dest)
err := checkProcMount("/rootfs", dest, "")
if err != nil {
t.Fatal("destination inside /sys should not return an error")
}
}

func TestCheckMountDestFalsePositive(t *testing.T) {
dest := "/rootfs/sysfiles/fs/cgroup"
err := checkMountDestination("/rootfs", dest)
err := checkProcMount("/rootfs", dest, "")
if err != nil {
t.Fatal(err)
}
Expand Down

0 comments on commit f979aca

Please sign in to comment.