diff --git a/libcontainer/container_linux.go b/libcontainer/container_linux.go index 092792040f5..36d599b94cf 100644 --- a/libcontainer/container_linux.go +++ b/libcontainer/container_linux.go @@ -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" @@ -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 diff --git a/libcontainer/mount/mount.go b/libcontainer/mount/mount.go index e8965e081bb..e28bd057967 100644 --- a/libcontainer/mount/mount.go +++ b/libcontainer/mount/mount.go @@ -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() @@ -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 +} diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go index 1513c1d94b6..ce81ed5f518 100644 --- a/libcontainer/rootfs_linux.go +++ b/libcontainer/rootfs_linux.go @@ -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" @@ -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. @@ -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. @@ -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, @@ -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 diff --git a/libcontainer/rootfs_linux_test.go b/libcontainer/rootfs_linux_test.go index d755984bc0f..1bfe7c66322 100644 --- a/libcontainer/rootfs_linux_test.go +++ b/libcontainer/rootfs_linux_test.go @@ -10,7 +10,7 @@ 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") } @@ -18,7 +18,7 @@ func TestCheckMountDestOnProc(t *testing.T) { 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") } @@ -26,7 +26,7 @@ func TestCheckMountDestOnProcChroot(t *testing.T) { 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") } @@ -34,7 +34,7 @@ func TestCheckMountDestInSys(t *testing.T) { func TestCheckMountDestFalsePositive(t *testing.T) { dest := "/rootfs/sysfiles/fs/cgroup" - err := checkMountDestination("/rootfs", dest) + err := checkProcMount("/rootfs", dest, "") if err != nil { t.Fatal(err) }