Files
runc/libcontainer/rootfs_linux_test.go
Aleksa Sarai a0292ca6ff [1.1] rootfs: fix 'can we mount on top of /proc' check
(This is a cherry-pick of cdff09ab87 but
modified so that changes like 8e8b136c49 and a60933bb24 don't also
need to be backported. Ideally we would backport the entire "remove all
mount logic from nsexec" series, but that would be a bit too much.)

Our previous test for whether we can mount on top of /proc incorrectly
assumed that it would only be called with bind-mount sources. This meant
that having a non bind-mount entry for a pseudo-filesystem (like
overlayfs) with a dummy source set to /proc on the host would let you
bypass the check, which could easily lead to security issues.

In addition, the check should be applied more uniformly to all mount
types, so fix that as well. And add some tests for some of the tricky
cases to make sure we protect against them properly.

Fixes: 331692baa7 ("Only allow proc mount if it is procfs")
Signed-off-by: Aleksa Sarai <cyphar@cyphar.com>
2024-07-04 14:18:41 +10:00

193 lines
4.5 KiB
Go

package libcontainer
import (
"testing"
"golang.org/x/sys/unix"
"github.com/opencontainers/runc/libcontainer/configs"
)
func TestCheckMountDestInProc(t *testing.T) {
m := &configs.Mount{
Destination: "/proc/sys",
Source: "/proc/sys",
Device: "bind",
Flags: unix.MS_BIND,
}
dest := "/rootfs/proc/sys"
err := checkProcMount("/rootfs", dest, m, m.Source)
if err == nil {
t.Fatal("destination inside proc should return an error")
}
}
func TestCheckProcMountOnProc(t *testing.T) {
m := &configs.Mount{
Destination: "/proc",
Source: "foo",
Device: "proc",
}
dest := "/rootfs/proc/"
err := checkProcMount("/rootfs", dest, m, m.Source)
if err != nil {
t.Fatalf("procfs type mount on /proc should not return an error: %v", err)
}
}
func TestCheckBindMountOnProc(t *testing.T) {
m := &configs.Mount{
Destination: "/proc",
Source: "/proc/self",
Device: "bind",
Flags: unix.MS_BIND,
}
dest := "/rootfs/proc/"
err := checkProcMount("/rootfs", dest, m, m.Source)
if err != nil {
t.Fatalf("bind-mount of procfs on top of /proc should not return an error (for now): %v", err)
}
}
func TestCheckTrickyMountOnProc(t *testing.T) {
// Make a non-bind mount that looks like a bit like a bind-mount.
m := &configs.Mount{
Destination: "/proc",
Source: "/proc",
Device: "overlay",
Data: "lowerdir=/tmp/fakeproc,upperdir=/tmp/fakeproc2,workdir=/tmp/work",
}
dest := "/rootfs/proc/"
err := checkProcMount("/rootfs", dest, m, m.Source)
if err == nil {
t.Fatalf("dodgy overlayfs mount on top of /proc should return an error")
}
}
func TestCheckTrickyBindMountOnProc(t *testing.T) {
// Make a bind mount that looks like it might be a procfs mount.
m := &configs.Mount{
Destination: "/proc",
Source: "/sys",
Device: "proc",
Flags: unix.MS_BIND,
}
dest := "/rootfs/proc/"
err := checkProcMount("/rootfs", dest, m, m.Source)
if err == nil {
t.Fatalf("dodgy bind-mount on top of /proc should return an error")
}
}
func TestCheckMountDestInSys(t *testing.T) {
m := &configs.Mount{
Destination: "/sys/fs/cgroup",
Source: "tmpfs",
Device: "tmpfs",
}
dest := "/rootfs//sys/fs/cgroup"
err := checkProcMount("/rootfs", dest, m, m.Source)
if err != nil {
t.Fatalf("destination inside /sys should not return an error: %v", err)
}
}
func TestCheckMountDestFalsePositive(t *testing.T) {
m := &configs.Mount{
Destination: "/sysfiles/fs/cgroup",
Source: "tmpfs",
Device: "tmpfs",
}
dest := "/rootfs/sysfiles/fs/cgroup"
err := checkProcMount("/rootfs", dest, m, m.Source)
if err != nil {
t.Fatal(err)
}
}
func TestCheckMountDestNsLastPid(t *testing.T) {
m := &configs.Mount{
Destination: "/proc/sys/kernel/ns_last_pid",
Source: "lxcfs",
Device: "fuse.lxcfs",
}
dest := "/rootfs/proc/sys/kernel/ns_last_pid"
err := checkProcMount("/rootfs", dest, m, m.Source)
if err != nil {
t.Fatalf("/proc/sys/kernel/ns_last_pid should not return an error: %v", err)
}
}
func TestCheckCryptoFipsEnabled(t *testing.T) {
m := &configs.Mount{
Destination: "/proc/sys/crypto/fips_enabled",
Source: "tmpfs",
Device: "tmpfs",
}
dest := "/rootfs/proc/sys/crypto/fips_enabled"
err := checkProcMount("/rootfs", dest, m, m.Source)
if err != nil {
t.Fatalf("/proc/sys/crypto/fips_enabled should not return an error: %v", err)
}
}
func TestNeedsSetupDev(t *testing.T) {
config := &configs.Config{
Mounts: []*configs.Mount{
{
Device: "bind",
Source: "/dev",
Destination: "/dev",
},
},
}
if needsSetupDev(config) {
t.Fatal("expected needsSetupDev to be false, got true")
}
}
func TestNeedsSetupDevStrangeSource(t *testing.T) {
config := &configs.Config{
Mounts: []*configs.Mount{
{
Device: "bind",
Source: "/devx",
Destination: "/dev",
},
},
}
if needsSetupDev(config) {
t.Fatal("expected needsSetupDev to be false, got true")
}
}
func TestNeedsSetupDevStrangeDest(t *testing.T) {
config := &configs.Config{
Mounts: []*configs.Mount{
{
Device: "bind",
Source: "/dev",
Destination: "/devx",
},
},
}
if !needsSetupDev(config) {
t.Fatal("expected needsSetupDev to be true, got false")
}
}
func TestNeedsSetupDevStrangeSourceDest(t *testing.T) {
config := &configs.Config{
Mounts: []*configs.Mount{
{
Device: "bind",
Source: "/devx",
Destination: "/devx",
},
},
}
if !needsSetupDev(config) {
t.Fatal("expected needsSetupDev to be true, got false")
}
}