From 4de404971341a5cebab88cad6233965362fdeb4c Mon Sep 17 00:00:00 2001 From: Ophestra Date: Fri, 28 Feb 2025 14:56:08 +0900 Subject: [PATCH] test/sandbox: wrap libc getmntent For checking mounts outcome. Signed-off-by: Ophestra --- test/sandbox/mount.go | 134 +++++++++++++++++++++++++++++++++++++ test/sandbox/mount_test.go | 112 +++++++++++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 test/sandbox/mount.go create mode 100644 test/sandbox/mount_test.go diff --git a/test/sandbox/mount.go b/test/sandbox/mount.go new file mode 100644 index 0000000..9c279f8 --- /dev/null +++ b/test/sandbox/mount.go @@ -0,0 +1,134 @@ +package sandbox + +/* +#include +#include +#include + +const char *F_PROC_MOUNTS = ""; +const char *F_SET_TYPE = "r"; +*/ +import "C" + +import ( + "fmt" + "runtime" + "sync" + "unsafe" +) + +type Mntent struct { + /* name of mounted filesystem */ + FSName string `json:"fsname"` + /* filesystem path prefix */ + Dir string `json:"dir"` + /* mount type (see mntent.h) */ + Type string `json:"type"` + /* mount options (see mntent.h) */ + Opts string `json:"opts"` + /* dump frequency in days */ + Freq int `json:"freq"` + /* pass number on parallel fsck */ + Passno int `json:"passno"` +} + +func (e *Mntent) String() string { + return fmt.Sprintf("%s %s %s %s %d %d", + e.FSName, e.Dir, e.Type, e.Opts, e.Freq, e.Passno) +} + +func IterMounts(name string, f func(e *Mntent)) error { + m := new(mounts) + m.p = name + if err := m.open(); err != nil { + return err + } + + for m.scan() { + e := new(Mntent) + m.copy(e) + f(e) + } + + m.close() + return m.Err() +} + +type mounts struct { + p string + f *C.FILE + mu sync.RWMutex + + ent *C.struct_mntent + err error +} + +func (m *mounts) open() error { + m.mu.Lock() + defer m.mu.Unlock() + + if m.f != nil { + panic("open called twice") + } + + if m.p == "" { + m.p = "/proc/mounts" + } + + name := C.CString(m.p) + f, err := C.setmntent(name, C.F_SET_TYPE) + C.free(unsafe.Pointer(name)) + + if f == nil { + return err + } + m.f = f + runtime.SetFinalizer(m, (*mounts).close) + return err +} + +func (m *mounts) close() { + m.mu.Lock() + defer m.mu.Unlock() + + if m.f == nil { + panic("close called before open") + } + + C.endmntent(m.f) + runtime.SetFinalizer(m, nil) +} + +func (m *mounts) scan() bool { + m.mu.Lock() + defer m.mu.Unlock() + + if m.f == nil { + panic("invalid file") + } + + m.ent, m.err = C.getmntent(m.f) + return m.ent != nil +} + +func (m *mounts) Err() error { + m.mu.RLock() + defer m.mu.RUnlock() + + return m.err +} + +func (m *mounts) copy(v *Mntent) { + m.mu.RLock() + defer m.mu.RUnlock() + + if m.ent == nil { + panic("invalid entry") + } + v.FSName = C.GoString(m.ent.mnt_fsname) + v.Dir = C.GoString(m.ent.mnt_dir) + v.Type = C.GoString(m.ent.mnt_type) + v.Opts = C.GoString(m.ent.mnt_opts) + v.Freq = int(m.ent.mnt_freq) + v.Passno = int(m.ent.mnt_passno) +} diff --git a/test/sandbox/mount_test.go b/test/sandbox/mount_test.go new file mode 100644 index 0000000..977bc6c --- /dev/null +++ b/test/sandbox/mount_test.go @@ -0,0 +1,112 @@ +package sandbox_test + +import ( + "os" + "path" + "testing" + + "git.gensokyo.uk/security/fortify/test/sandbox" +) + +func TestMounts(t *testing.T) { + testCases := []struct { + name string + + sample string + want []sandbox.Mntent + }{ + {"fpkg", `tmpfs / tmpfs rw,nosuid,nodev,relatime,uid=1000002,gid=1000002 0 0 +proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 +tmpfs /.fortify tmpfs rw,nosuid,nodev,relatime,size=4k,mode=755,uid=1000002,gid=1000002 0 0 +tmpfs /dev tmpfs rw,nosuid,nodev,relatime,mode=755,uid=1000002,gid=1000002 0 0 +devtmpfs /dev/null devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0 +devtmpfs /dev/zero devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0 +devtmpfs /dev/full devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0 +devtmpfs /dev/random devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0 +devtmpfs /dev/urandom devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0 +devtmpfs /dev/tty devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0 +devpts /dev/pts devpts rw,nosuid,noexec,relatime,mode=620,ptmxmode=666 0 0 +mqueue /dev/mqueue mqueue rw,relatime 0 0 +/dev/disk/by-label/nixos /nix/store ext4 ro,nosuid,nodev,relatime 0 0 +/dev/disk/by-label/nixos /.fortify/app ext4 ro,nosuid,nodev,relatime 0 0 +/dev/disk/by-label/nixos /etc/resolv.conf ext4 ro,nosuid,nodev,relatime 0 0 +sysfs /sys/block sysfs ro,nosuid,nodev,noexec,relatime 0 0 +sysfs /sys/bus sysfs ro,nosuid,nodev,noexec,relatime 0 0 +sysfs /sys/class sysfs ro,nosuid,nodev,noexec,relatime 0 0 +sysfs /sys/dev sysfs ro,nosuid,nodev,noexec,relatime 0 0 +sysfs /sys/devices sysfs ro,nosuid,nodev,noexec,relatime 0 0 +/dev/disk/by-label/nixos /.fortify/nixGL ext4 ro,nosuid,nodev,relatime 0 0 +devtmpfs /dev/dri devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0 +/dev/disk/by-label/nixos /.fortify/etc ext4 ro,nosuid,nodev,relatime 0 0 +tmpfs /run/user tmpfs rw,nosuid,nodev,relatime,size=1024k,mode=755,uid=1000002,gid=1000002 0 0 +tmpfs /run/user/65534 tmpfs rw,nosuid,nodev,relatime,size=8192k,mode=755,uid=1000002,gid=1000002 0 0 +/dev/disk/by-label/nixos /tmp ext4 rw,nosuid,nodev,relatime 0 0 +/dev/disk/by-label/nixos /data/data/org.codeberg.dnkl.foot ext4 rw,nosuid,nodev,relatime 0 0 +tmpfs /etc/passwd tmpfs ro,nosuid,nodev,relatime,uid=1000002,gid=1000002 0 0 +tmpfs /etc/group tmpfs ro,nosuid,nodev,relatime,uid=1000002,gid=1000002 0 0 +/dev/disk/by-label/nixos /run/user/65534/wayland-0 ext4 ro,nosuid,nodev,relatime 0 0 +tmpfs /run/user/65534/pulse/native tmpfs ro,nosuid,nodev,relatime,size=98784k,nr_inodes=24696,mode=700,uid=1000,gid=100 0 0 +/dev/disk/by-label/nixos /run/user/65534/bus ext4 ro,nosuid,nodev,relatime 0 0 +overlay /.fortify/sbin/fortify overlay ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on 0 0 +`, []sandbox.Mntent{ + {"tmpfs", "/", "tmpfs", "rw,nosuid,nodev,relatime,uid=1000002,gid=1000002", 0, 0}, + {"proc", "/proc", "proc", "rw,nosuid,nodev,noexec,relatime", 0, 0}, + {"tmpfs", "/.fortify", "tmpfs", "rw,nosuid,nodev,relatime,size=4k,mode=755,uid=1000002,gid=1000002", 0, 0}, + {"tmpfs", "/dev", "tmpfs", "rw,nosuid,nodev,relatime,mode=755,uid=1000002,gid=1000002", 0, 0}, + {"devtmpfs", "/dev/null", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0}, + {"devtmpfs", "/dev/zero", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0}, + {"devtmpfs", "/dev/full", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0}, + {"devtmpfs", "/dev/random", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0}, + {"devtmpfs", "/dev/urandom", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0}, + {"devtmpfs", "/dev/tty", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0}, + {"devpts", "/dev/pts", "devpts", "rw,nosuid,noexec,relatime,mode=620,ptmxmode=666", 0, 0}, + {"mqueue", "/dev/mqueue", "mqueue", "rw,relatime", 0, 0}, + {"/dev/disk/by-label/nixos", "/nix/store", "ext4", "ro,nosuid,nodev,relatime", 0, 0}, + {"/dev/disk/by-label/nixos", "/.fortify/app", "ext4", "ro,nosuid,nodev,relatime", 0, 0}, + {"/dev/disk/by-label/nixos", "/etc/resolv.conf", "ext4", "ro,nosuid,nodev,relatime", 0, 0}, + {"sysfs", "/sys/block", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0}, + {"sysfs", "/sys/bus", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0}, + {"sysfs", "/sys/class", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0}, + {"sysfs", "/sys/dev", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0}, + {"sysfs", "/sys/devices", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0}, + {"/dev/disk/by-label/nixos", "/.fortify/nixGL", "ext4", "ro,nosuid,nodev,relatime", 0, 0}, + {"devtmpfs", "/dev/dri", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0}, + {"/dev/disk/by-label/nixos", "/.fortify/etc", "ext4", "ro,nosuid,nodev,relatime", 0, 0}, + {"tmpfs", "/run/user", "tmpfs", "rw,nosuid,nodev,relatime,size=1024k,mode=755,uid=1000002,gid=1000002", 0, 0}, + {"tmpfs", "/run/user/65534", "tmpfs", "rw,nosuid,nodev,relatime,size=8192k,mode=755,uid=1000002,gid=1000002", 0, 0}, + {"/dev/disk/by-label/nixos", "/tmp", "ext4", "rw,nosuid,nodev,relatime", 0, 0}, + {"/dev/disk/by-label/nixos", "/data/data/org.codeberg.dnkl.foot", "ext4", "rw,nosuid,nodev,relatime", 0, 0}, + {"tmpfs", "/etc/passwd", "tmpfs", "ro,nosuid,nodev,relatime,uid=1000002,gid=1000002", 0, 0}, + {"tmpfs", "/etc/group", "tmpfs", "ro,nosuid,nodev,relatime,uid=1000002,gid=1000002", 0, 0}, + {"/dev/disk/by-label/nixos", "/run/user/65534/wayland-0", "ext4", "ro,nosuid,nodev,relatime", 0, 0}, + {"tmpfs", "/run/user/65534/pulse/native", "tmpfs", "ro,nosuid,nodev,relatime,size=98784k,nr_inodes=24696,mode=700,uid=1000,gid=100", 0, 0}, + {"/dev/disk/by-label/nixos", "/run/user/65534/bus", "ext4", "ro,nosuid,nodev,relatime", 0, 0}, + {"overlay", "/.fortify/sbin/fortify", "overlay", "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on", 0, 0}, + }}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + name := path.Join(t.TempDir(), "sample") + if err := os.WriteFile(name, []byte(tc.sample), 0400); err != nil { + t.Fatalf("cannot write sample: %v", err) + } + + i := 0 + if err := sandbox.IterMounts(name, func(e *sandbox.Mntent) { + if i == len(tc.want) { + t.Errorf("IterMounts: got more than %d entries", i) + t.FailNow() + } + if *e != tc.want[i] { + t.Errorf("IterMounts: entry %d\n got: %s\nwant: %s", i, + e, &tc.want[i]) + t.FailNow() + } + i++ + }); err != nil { + t.Fatalf("IterMounts: error = %v", err) + } + }) + } +}