From 09d284498109b847da0da1e2c5f883268a7f2d46 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Thu, 21 Aug 2025 21:59:07 +0900 Subject: container/init: wrap syscall helper functions This allows tests to stub all kernel behaviour, enabling measurement of all function call arguments and error injection. Signed-off-by: Ophestra --- container/initoverlay_test.go | 231 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 230 insertions(+), 1 deletion(-) (limited to 'container/initoverlay_test.go') diff --git a/container/initoverlay_test.go b/container/initoverlay_test.go index 68377dd4..f5f0aec5 100644 --- a/container/initoverlay_test.go +++ b/container/initoverlay_test.go @@ -1,8 +1,237 @@ package container -import "testing" +import ( + "errors" + "io/fs" + "os" + "testing" +) func TestMountOverlayOp(t *testing.T) { + checkOpBehaviour(t, []opBehaviourTestCase{ + {"mkdirTemp invalid ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{ + Target: MustAbs("/"), + Lower: []*Absolute{ + MustAbs("/var/lib/planterette/base/debian:f92c9052"), + MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"), + }, + Upper: MustAbs("/proc/"), + }, nil, msg.WrapErr(fs.ErrInvalid, `upperdir has unexpected value "/proc/"`), nil, nil}, + + {"mkdirTemp upper ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{ + Target: MustAbs("/"), + Lower: []*Absolute{ + MustAbs("/var/lib/planterette/base/debian:f92c9052"), + MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"), + }, + Upper: MustAbs("/"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil}, + {"evalSymlinks", expectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot", os.FileMode(0705)}, nil, nil}, + {"mkdirTemp", expectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", errUnique}, + }, wrapErrSelf(errUnique)}, + + {"mkdirTemp work ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{ + Target: MustAbs("/"), + Lower: []*Absolute{ + MustAbs("/var/lib/planterette/base/debian:f92c9052"), + MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"), + }, + Upper: MustAbs("/"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil}, + {"evalSymlinks", expectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot", os.FileMode(0705)}, nil, nil}, + {"mkdirTemp", expectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil}, + {"mkdirTemp", expectArgs{"/", "overlay.work.*"}, "overlay.work.32768", errUnique}, + }, wrapErrSelf(errUnique)}, + + {"success ephemeral", &Params{ParentPerm: 0705}, &MountOverlayOp{ + Target: MustAbs("/"), + Lower: []*Absolute{ + MustAbs("/var/lib/planterette/base/debian:f92c9052"), + MustAbs("/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"), + }, + Upper: MustAbs("/"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/var/lib/planterette/base/debian:f92c9052"}, "/var/lib/planterette/base/debian:f92c9052", nil}, + {"evalSymlinks", expectArgs{"/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052"}, "/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot", os.FileMode(0705)}, nil, nil}, + {"mkdirTemp", expectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil}, + {"mkdirTemp", expectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil}, + {"mount", expectArgs{"overlay", "/sysroot", "overlay", uintptr(0), "" + + "upperdir=overlay.upper.32768," + + "workdir=overlay.work.32768," + + "lowerdir=" + + `/host/var/lib/planterette/base/debian\:f92c9052:` + + `/host/var/lib/planterette/app/org.chromium.Chromium@debian\:f92c9052,` + + "userxattr"}, nil, nil}, + }, nil}, + + {"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Lower: []*Absolute{ + MustAbs("/mnt-root/nix/.ro-store"), + }, + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/.ro-store", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil}, + }, msg.WrapErr(fs.ErrInvalid, "readonly overlay requires at least two lowerdir")}, + + {"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Lower: []*Absolute{ + MustAbs("/mnt-root/nix/.ro-store"), + MustAbs("/mnt-root/nix/.ro-store0"), + }, + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/.ro-store", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil}, + {"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" + + "lowerdir=" + + "/host/mnt-root/nix/.ro-store:" + + "/host/mnt-root/nix/.ro-store0," + + "userxattr"}, nil, nil}, + }, nil}, + + {"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Upper: MustAbs("/mnt-root/nix/.rw-store/upper"), + Work: MustAbs("/mnt-root/nix/.rw-store/work"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil}, + }, msg.WrapErr(fs.ErrInvalid, "overlay requires at least one lowerdir")}, + + {"evalSymlinks upper", &Params{ParentPerm: 0700}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")}, + Upper: MustAbs("/mnt-root/nix/.rw-store/upper"), + Work: MustAbs("/mnt-root/nix/.rw-store/work"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", errUnique}, + }, wrapErrSelf(errUnique), nil, nil}, + + {"evalSymlinks work", &Params{ParentPerm: 0700}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")}, + Upper: MustAbs("/mnt-root/nix/.rw-store/upper"), + Work: MustAbs("/mnt-root/nix/.rw-store/work"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", errUnique}, + }, wrapErrSelf(errUnique), nil, nil}, + + {"evalSymlinks lower", &Params{ParentPerm: 0700}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")}, + Upper: MustAbs("/mnt-root/nix/.rw-store/upper"), + Work: MustAbs("/mnt-root/nix/.rw-store/work"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", errUnique}, + }, wrapErrSelf(errUnique), nil, nil}, + + {"mkdirAll", &Params{ParentPerm: 0700}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")}, + Upper: MustAbs("/mnt-root/nix/.rw-store/upper"), + Work: MustAbs("/mnt-root/nix/.rw-store/work"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, errUnique}, + }, wrapErrSelf(errUnique)}, + + {"mount", &Params{ParentPerm: 0700}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")}, + Upper: MustAbs("/mnt-root/nix/.rw-store/upper"), + Work: MustAbs("/mnt-root/nix/.rw-store/work"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil}, + {"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "upperdir=/host/mnt-root/nix/.rw-store/.upper,workdir=/host/mnt-root/nix/.rw-store/.work,lowerdir=/host/mnt-root/nix/ro-store,userxattr"}, nil, errUnique}, + }, wrapErrSuffix(errUnique, `cannot mount overlay on "/nix/store":`)}, + + {"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Lower: []*Absolute{MustAbs("/mnt-root/nix/.ro-store")}, + Upper: MustAbs("/mnt-root/nix/.rw-store/upper"), + Work: MustAbs("/mnt-root/nix/.rw-store/work"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil}, + {"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" + + "upperdir=/host/mnt-root/nix/.rw-store/.upper," + + "workdir=/host/mnt-root/nix/.rw-store/.work," + + "lowerdir=/host/mnt-root/nix/ro-store," + + "userxattr"}, nil, nil}, + }, nil}, + + {"success", &Params{ParentPerm: 0700}, &MountOverlayOp{ + Target: MustAbs("/nix/store"), + Lower: []*Absolute{ + MustAbs("/mnt-root/nix/.ro-store"), + MustAbs("/mnt-root/nix/.ro-store0"), + MustAbs("/mnt-root/nix/.ro-store1"), + MustAbs("/mnt-root/nix/.ro-store2"), + MustAbs("/mnt-root/nix/.ro-store3"), + }, + Upper: MustAbs("/mnt-root/nix/.rw-store/upper"), + Work: MustAbs("/mnt-root/nix/.rw-store/work"), + }, []kexpect{ + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/upper"}, "/mnt-root/nix/.rw-store/.upper", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.rw-store/work"}, "/mnt-root/nix/.rw-store/.work", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/ro-store0", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store1"}, "/mnt-root/nix/ro-store1", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store2"}, "/mnt-root/nix/ro-store2", nil}, + {"evalSymlinks", expectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil}, + }, nil, []kexpect{ + {"mkdirAll", expectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil}, + {"mount", expectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" + + "upperdir=/host/mnt-root/nix/.rw-store/.upper," + + "workdir=/host/mnt-root/nix/.rw-store/.work," + + "lowerdir=" + + "/host/mnt-root/nix/ro-store:" + + "/host/mnt-root/nix/ro-store0:" + + "/host/mnt-root/nix/ro-store1:" + + "/host/mnt-root/nix/ro-store2:" + + "/host/mnt-root/nix/ro-store3," + + "userxattr"}, nil, nil}, + }, nil}, + }) + + t.Run("unreachable", func(t *testing.T) { + t.Run("nil Upper non-nil Work not ephemeral", func(t *testing.T) { + wantErr := msg.WrapErr(fs.ErrClosed, "impossible overlay state reached") + if err := (&MountOverlayOp{ + Work: MustAbs("/"), + }).early(nil, nil); !errors.Is(err, wantErr) { + t.Errorf("apply: error = %v, want %v", err, wantErr) + } + }) + }) + checkOpsValid(t, []opValidTestCase{ {"nil", (*MountOverlayOp)(nil), false}, {"zero", new(MountOverlayOp), false}, -- cgit v1.3.1