test/sandbox: parse full test case
This makes declaring multiple tests much cleaner. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
770b37ae16
commit
28c2685170
@ -117,11 +117,14 @@ func TestContainer(t *testing.T) {
|
|||||||
mnt = append(mnt, &check.Mntent{FSName: "\x00", Dir: name, Type: "\x00", Opts: "\x00", Freq: -1, Passno: -1})
|
mnt = append(mnt, &check.Mntent{FSName: "\x00", Dir: name, Type: "\x00", Opts: "\x00", Freq: -1, Passno: -1})
|
||||||
}
|
}
|
||||||
mnt = append(mnt, &check.Mntent{FSName: "proc", Dir: "/proc", Type: "proc", Opts: "rw,nosuid,nodev,noexec,relatime"})
|
mnt = append(mnt, &check.Mntent{FSName: "proc", Dir: "/proc", Type: "proc", Opts: "rw,nosuid,nodev,noexec,relatime"})
|
||||||
mntentWant := new(bytes.Buffer)
|
want := new(bytes.Buffer)
|
||||||
if err := json.NewEncoder(mntentWant).Encode(mnt); err != nil {
|
if err := json.NewEncoder(want).Encode(&check.TestCase{
|
||||||
t.Fatalf("cannot serialise mntent: %v", err)
|
Mount: mnt,
|
||||||
|
Seccomp: true,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("cannot serialise want: %v", err)
|
||||||
}
|
}
|
||||||
container.Stdin = mntentWant
|
container.Stdin = want
|
||||||
|
|
||||||
// needs /proc to check mntent
|
// needs /proc to check mntent
|
||||||
container.Proc("/proc")
|
container.Proc("/proc")
|
||||||
@ -185,8 +188,7 @@ func TestHelperCheckContainer(t *testing.T) {
|
|||||||
t.Errorf("/etc/hostname: %q, want %q", string(p), os.Args[5])
|
t.Errorf("/etc/hostname: %q, want %q", string(p), os.Args[5])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("seccomp", func(t *testing.T) { check.MustAssertSeccomp() })
|
t.Run("sandbox", func(t *testing.T) { (&check.T{PMountsPath: "/proc/mounts"}).MustCheckFile("/proc/self/fd/0") })
|
||||||
t.Run("mntent", func(t *testing.T) { check.MustAssertMounts("", "/proc/mounts", "/proc/self/fd/0") })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandContext(ctx context.Context) *exec.Cmd {
|
func commandContext(ctx context.Context) *exec.Cmd {
|
||||||
|
@ -4,6 +4,12 @@
|
|||||||
config,
|
config,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
testCases = import ./sandbox/case {
|
||||||
|
inherit (pkgs) lib callPackage foot;
|
||||||
|
inherit (config.environment.fortify.package) version;
|
||||||
|
};
|
||||||
|
in
|
||||||
{
|
{
|
||||||
users.users = {
|
users.users = {
|
||||||
alice = {
|
alice = {
|
||||||
@ -102,21 +108,8 @@
|
|||||||
home-manager = _: _: { home.stateVersion = "23.05"; };
|
home-manager = _: _: { home.stateVersion = "23.05"; };
|
||||||
|
|
||||||
apps = [
|
apps = [
|
||||||
{
|
testCases.moduleDefault
|
||||||
name = "check-sandbox";
|
|
||||||
verbose = true;
|
|
||||||
share = pkgs.foot;
|
|
||||||
packages = [ ];
|
|
||||||
command = "${pkgs.callPackage ./sandbox {
|
|
||||||
inherit (config.environment.fortify.package) version;
|
|
||||||
}}";
|
|
||||||
extraPaths = [
|
|
||||||
{
|
|
||||||
src = "/proc/mounts";
|
|
||||||
dst = "/.fortify/mounts";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
name = "ne-foot";
|
name = "ne-foot";
|
||||||
verbose = true;
|
verbose = true;
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -16,76 +17,103 @@ var (
|
|||||||
func printf(format string, v ...any) { printfFunc(format, v...) }
|
func printf(format string, v ...any) { printfFunc(format, v...) }
|
||||||
func fatalf(format string, v ...any) { fatalfFunc(format, v...) }
|
func fatalf(format string, v ...any) { fatalfFunc(format, v...) }
|
||||||
|
|
||||||
func mustDecode(wantFile string, v any) {
|
type TestCase struct {
|
||||||
if f, err := os.Open(wantFile); err != nil {
|
FS *FS `json:"fs"`
|
||||||
fatalf("cannot open %q: %v", wantFile, err)
|
Mount []*Mntent `json:"mount"`
|
||||||
} else if err = json.NewDecoder(f).Decode(v); err != nil {
|
Seccomp bool `json:"seccomp"`
|
||||||
fatalf("cannot decode %q: %v", wantFile, err)
|
|
||||||
} else if err = f.Close(); err != nil {
|
|
||||||
fatalf("cannot close %q: %v", wantFile, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustAssertMounts(name, hostMountsFile, wantFile string) {
|
type T struct {
|
||||||
hostMounts := make([]*Mntent, 0, 128)
|
FS fs.FS
|
||||||
if err := IterMounts(hostMountsFile, func(e *Mntent) {
|
|
||||||
hostMounts = append(hostMounts, e)
|
MountsPath, PMountsPath string
|
||||||
}); err != nil {
|
}
|
||||||
fatalf("cannot parse host mounts: %v", err)
|
|
||||||
|
func (t *T) MustCheckFile(wantFilePath string) {
|
||||||
|
var want *TestCase
|
||||||
|
mustDecode(wantFilePath, &want)
|
||||||
|
t.MustCheck(want)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *T) MustCheck(want *TestCase) {
|
||||||
|
if want.FS != nil && t.FS != nil {
|
||||||
|
if err := want.FS.Compare(".", t.FS); err != nil {
|
||||||
|
fatalf("%v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("skipping fs check")
|
||||||
}
|
}
|
||||||
|
|
||||||
var want []Mntent
|
if want.Mount != nil && t.PMountsPath != "" {
|
||||||
mustDecode(wantFile, &want)
|
pm := mustOpenMounts(t.PMountsPath)
|
||||||
|
passthruMounts := slices.AppendSeq(make([]*Mntent, 0, 128), pm.Entries())
|
||||||
|
if err := pm.Err(); err != nil {
|
||||||
|
fatalf("cannot parse host mounts: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
for i := range want {
|
for _, e := range want.Mount {
|
||||||
if want[i].Opts == "host_passthrough" {
|
if e.Opts == "host_passthrough" {
|
||||||
for _, ent := range hostMounts {
|
for _, ent := range passthruMounts {
|
||||||
if want[i].FSName == ent.FSName && want[i].Type == ent.Type {
|
if e.FSName == ent.FSName && e.Type == ent.Type {
|
||||||
// special case for tmpfs bind mounts
|
// special case for tmpfs bind mounts
|
||||||
if want[i].FSName == "tmpfs" && want[i].Dir != ent.Dir {
|
if e.FSName == "tmpfs" && e.Dir != ent.Dir {
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Opts = ent.Opts
|
||||||
|
goto out
|
||||||
}
|
}
|
||||||
|
|
||||||
want[i].Opts = ent.Opts
|
|
||||||
goto out
|
|
||||||
}
|
}
|
||||||
|
fatalf("host passthrough missing %q", e.FSName)
|
||||||
|
out:
|
||||||
}
|
}
|
||||||
fatalf("host passthrough missing %q", want[i].FSName)
|
|
||||||
out:
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f := mustOpenMounts(t.MountsPath)
|
||||||
|
i := 0
|
||||||
|
for e := range f.Entries() {
|
||||||
|
if i == len(want.Mount) {
|
||||||
|
fatalf("got more than %d entries", i)
|
||||||
|
}
|
||||||
|
if !e.Is(want.Mount[i]) {
|
||||||
|
fatalf("entry %d\n got: %s\nwant: %s", i,
|
||||||
|
e, want.Mount[i])
|
||||||
|
}
|
||||||
|
printf("%s", e)
|
||||||
|
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if err := f.Err(); err != nil {
|
||||||
|
fatalf("cannot parse mounts: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("skipping mounts check")
|
||||||
}
|
}
|
||||||
|
|
||||||
i := 0
|
if want.Seccomp {
|
||||||
if err := IterMounts(name, func(e *Mntent) {
|
if TrySyscalls() != nil {
|
||||||
if i == len(want) {
|
os.Exit(1)
|
||||||
fatalf("got more than %d entries", i)
|
|
||||||
}
|
}
|
||||||
if !e.Is(&want[i]) {
|
} else {
|
||||||
fatalf("entry %d\n got: %s\nwant: %s", i,
|
printf("skipping seccomp check")
|
||||||
e, &want[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("%s", e)
|
|
||||||
i++
|
|
||||||
}); err != nil {
|
|
||||||
fatalf("cannot iterate mounts: %v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustAssertFS(e fs.FS, wantFile string) {
|
func mustDecode(wantFilePath string, v any) {
|
||||||
var want *FS
|
if f, err := os.Open(wantFilePath); err != nil {
|
||||||
mustDecode(wantFile, &want)
|
fatalf("cannot open %q: %v", wantFilePath, err)
|
||||||
if want == nil {
|
} else if err = json.NewDecoder(f).Decode(v); err != nil {
|
||||||
fatalf("invalid payload")
|
fatalf("cannot decode %q: %v", wantFilePath, err)
|
||||||
}
|
} else if err = f.Close(); err != nil {
|
||||||
|
fatalf("cannot close %q: %v", wantFilePath, err)
|
||||||
if err := want.Compare(".", e); err != nil {
|
|
||||||
fatalf("%v", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustAssertSeccomp() {
|
func mustOpenMounts(name string) *MountsFile {
|
||||||
if TrySyscalls() != nil {
|
if f, err := OpenMounts(name); err != nil {
|
||||||
os.Exit(1)
|
fatalf("cannot open mounts %q: %v", name, err)
|
||||||
|
panic("unreachable")
|
||||||
|
} else {
|
||||||
|
return f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,22 @@
|
|||||||
buildGoModule,
|
buildGoModule,
|
||||||
|
|
||||||
version,
|
version,
|
||||||
|
name,
|
||||||
|
want,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
wantFile = writeText "fortify-${name}-want.json" (builtins.toJSON want);
|
||||||
mainFile = writeText "main.go" ''
|
mainFile = writeText "main.go" ''
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "os"
|
||||||
import "git.gensokyo.uk/security/fortify/test/sandbox"
|
import "git.gensokyo.uk/security/fortify/test/sandbox"
|
||||||
|
|
||||||
func main() { sandbox.MustAssertSeccomp() }
|
func main() { (&sandbox.T{FS: os.DirFS("/"), PMountsPath: "/.fortify/mounts"}).MustCheckFile("${wantFile}") }
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
buildGoModule {
|
buildGoModule {
|
||||||
pname = "check-seccomp";
|
pname = "fortify-${name}-check-sandbox";
|
||||||
inherit version;
|
inherit version;
|
||||||
|
|
||||||
src = ../.;
|
src = ../.;
|
57
test/sandbox/case/default.nix
Normal file
57
test/sandbox/case/default.nix
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
callPackage,
|
||||||
|
foot,
|
||||||
|
|
||||||
|
version,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
fs = mode: dir: data: {
|
||||||
|
mode = lib.fromHexString mode;
|
||||||
|
inherit
|
||||||
|
dir
|
||||||
|
data
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
ent = fsname: dir: type: opts: freq: passno: {
|
||||||
|
inherit
|
||||||
|
fsname
|
||||||
|
dir
|
||||||
|
type
|
||||||
|
opts
|
||||||
|
freq
|
||||||
|
passno
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
callTestCase =
|
||||||
|
path:
|
||||||
|
let
|
||||||
|
tc = import path {
|
||||||
|
inherit
|
||||||
|
fs
|
||||||
|
ent
|
||||||
|
;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
name = "check-sandbox-${tc.name}";
|
||||||
|
verbose = true;
|
||||||
|
share = foot;
|
||||||
|
packages = [ ];
|
||||||
|
command = "${callPackage ../. {
|
||||||
|
inherit (tc) name want;
|
||||||
|
inherit version;
|
||||||
|
}}";
|
||||||
|
extraPaths = [
|
||||||
|
{
|
||||||
|
src = "/proc/mounts";
|
||||||
|
dst = "/.fortify/mounts";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
moduleDefault = callTestCase ./module-default.nix;
|
||||||
|
}
|
@ -1,22 +1,9 @@
|
|||||||
|
{ fs, ent }:
|
||||||
{
|
{
|
||||||
lib,
|
name = "module-default";
|
||||||
writeText,
|
|
||||||
buildGoModule,
|
|
||||||
|
|
||||||
version,
|
want = {
|
||||||
}:
|
fs = fs "dead" {
|
||||||
let
|
|
||||||
wantFS =
|
|
||||||
let
|
|
||||||
fs = mode: dir: data: {
|
|
||||||
mode = lib.fromHexString mode;
|
|
||||||
inherit
|
|
||||||
dir
|
|
||||||
data
|
|
||||||
;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
fs "dead" {
|
|
||||||
".fortify" = fs "800001ed" {
|
".fortify" = fs "800001ed" {
|
||||||
etc = fs "800001ed" null null;
|
etc = fs "800001ed" null null;
|
||||||
sbin = fs "800001c0" {
|
sbin = fs "800001c0" {
|
||||||
@ -191,24 +178,45 @@ let
|
|||||||
} null;
|
} null;
|
||||||
} null;
|
} null;
|
||||||
|
|
||||||
mainFile = writeText "main.go" ''
|
mount = [
|
||||||
package main
|
(ent "tmpfs" "/" "tmpfs" "rw,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0)
|
||||||
|
(ent "proc" "/proc" "proc" "rw,nosuid,nodev,noexec,relatime" 0 0)
|
||||||
|
(ent "tmpfs" "/.fortify" "tmpfs" "rw,nosuid,nodev,relatime,size=4k,mode=755,uid=1000001,gid=1000001" 0 0)
|
||||||
|
(ent "tmpfs" "/dev" "tmpfs" "rw,nosuid,nodev,relatime,mode=755,uid=1000001,gid=1000001" 0 0)
|
||||||
|
(ent "devtmpfs" "/dev/null" "devtmpfs" "host_passthrough" 0 0)
|
||||||
|
(ent "devtmpfs" "/dev/zero" "devtmpfs" "host_passthrough" 0 0)
|
||||||
|
(ent "devtmpfs" "/dev/full" "devtmpfs" "host_passthrough" 0 0)
|
||||||
|
(ent "devtmpfs" "/dev/random" "devtmpfs" "host_passthrough" 0 0)
|
||||||
|
(ent "devtmpfs" "/dev/urandom" "devtmpfs" "host_passthrough" 0 0)
|
||||||
|
(ent "devtmpfs" "/dev/tty" "devtmpfs" "host_passthrough" 0 0)
|
||||||
|
(ent "devpts" "/dev/pts" "devpts" "rw,nosuid,noexec,relatime,mode=620,ptmxmode=666" 0 0)
|
||||||
|
(ent "mqueue" "/dev/mqueue" "mqueue" "rw,relatime" 0 0)
|
||||||
|
(ent "/dev/disk/by-label/nixos" "/bin" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
||||||
|
(ent "/dev/disk/by-label/nixos" "/usr/bin" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
||||||
|
(ent "overlay" "/nix/store" "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)
|
||||||
|
(ent "overlay" "/run/current-system" "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)
|
||||||
|
(ent "sysfs" "/sys/block" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
||||||
|
(ent "sysfs" "/sys/bus" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
||||||
|
(ent "sysfs" "/sys/class" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
||||||
|
(ent "sysfs" "/sys/dev" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
||||||
|
(ent "sysfs" "/sys/devices" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
||||||
|
(ent "overlay" "/run/opengl-driver" "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)
|
||||||
|
(ent "devtmpfs" "/dev/dri" "devtmpfs" "host_passthrough" 0 0)
|
||||||
|
(ent "proc" "/.fortify/mounts" "proc" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
||||||
|
(ent "/dev/disk/by-label/nixos" "/.fortify/etc" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
||||||
|
(ent "tmpfs" "/run/user" "tmpfs" "rw,nosuid,nodev,relatime,size=1024k,mode=755,uid=1000001,gid=1000001" 0 0)
|
||||||
|
(ent "tmpfs" "/run/user/65534" "tmpfs" "rw,nosuid,nodev,relatime,size=8192k,mode=755,uid=1000001,gid=1000001" 0 0)
|
||||||
|
(ent "/dev/disk/by-label/nixos" "/tmp" "ext4" "rw,nosuid,nodev,relatime" 0 0)
|
||||||
|
(ent "/dev/disk/by-label/nixos" "/var/lib/fortify/u0/a1" "ext4" "rw,nosuid,nodev,relatime" 0 0)
|
||||||
|
(ent "tmpfs" "/etc/passwd" "tmpfs" "ro,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0)
|
||||||
|
(ent "tmpfs" "/etc/group" "tmpfs" "ro,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0)
|
||||||
|
(ent "/dev/disk/by-label/nixos" "/run/user/65534/wayland-0" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
||||||
|
(ent "tmpfs" "/run/user/65534/pulse/native" "tmpfs" "host_passthrough" 0 0)
|
||||||
|
(ent "/dev/disk/by-label/nixos" "/run/user/65534/bus" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
||||||
|
(ent "tmpfs" "/var/run/nscd" "tmpfs" "rw,nosuid,nodev,relatime,size=8k,mode=755,uid=1000001,gid=1000001" 0 0)
|
||||||
|
(ent "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)
|
||||||
|
];
|
||||||
|
|
||||||
import "os"
|
seccomp = true;
|
||||||
import "git.gensokyo.uk/security/fortify/test/sandbox"
|
};
|
||||||
|
|
||||||
func main() { sandbox.MustAssertFS(os.DirFS("/"), "${writeText "want-fs.json" (builtins.toJSON wantFS)}") }
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
buildGoModule {
|
|
||||||
pname = "check-fs";
|
|
||||||
inherit version;
|
|
||||||
|
|
||||||
src = ../.;
|
|
||||||
vendorHash = null;
|
|
||||||
|
|
||||||
preBuild = ''
|
|
||||||
go mod init git.gensokyo.uk/security/fortify/test >& /dev/null
|
|
||||||
cp ${mainFile} main.go
|
|
||||||
'';
|
|
||||||
}
|
}
|
@ -2,13 +2,12 @@
|
|||||||
writeShellScript,
|
writeShellScript,
|
||||||
callPackage,
|
callPackage,
|
||||||
|
|
||||||
|
name,
|
||||||
version,
|
version,
|
||||||
|
want,
|
||||||
}:
|
}:
|
||||||
writeShellScript "check-sandbox" ''
|
writeShellScript "fortify-${name}-check-sandbox-script" ''
|
||||||
set -e
|
set -e
|
||||||
${callPackage ./mount.nix { inherit version; }}/bin/test
|
${callPackage ./assert.nix { inherit name version want; }}/bin/test
|
||||||
${callPackage ./fs.nix { inherit version; }}/bin/test
|
|
||||||
${callPackage ./seccomp.nix { inherit version; }}/bin/test
|
|
||||||
|
|
||||||
touch /tmp/sandbox-ok
|
touch /tmp/sandbox-ok
|
||||||
''
|
''
|
||||||
|
@ -75,10 +75,4 @@ func TestCompare(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("assert", func(t *testing.T) {
|
|
||||||
oldFatal := sandbox.SwapFatal(t.Fatalf)
|
|
||||||
t.Cleanup(func() { sandbox.SwapFatal(oldFatal) })
|
|
||||||
sandbox.MustAssertFS(make(fstest.MapFS), sandbox.MustWantFile(t, &sandbox.FS{Mode: 0xDEADBEEF}))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import "C"
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"iter"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@ -49,21 +50,38 @@ func (e *Mntent) Is(want *Mntent) bool {
|
|||||||
(e.Passno == want.Passno || want.Passno == -1)
|
(e.Passno == want.Passno || want.Passno == -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IterMounts(name string, f func(e *Mntent)) error {
|
type MountsFile struct {
|
||||||
m := new(mounts)
|
m *mounts
|
||||||
m.p = name
|
mu sync.Mutex
|
||||||
if err := m.open(); err != nil {
|
done bool
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for m.scan() {
|
func OpenMounts(name string) (*MountsFile, error) {
|
||||||
e := new(Mntent)
|
f := new(MountsFile)
|
||||||
m.copy(e)
|
f.m = new(mounts)
|
||||||
f(e)
|
f.m.p = name
|
||||||
}
|
return f, f.m.open()
|
||||||
|
}
|
||||||
|
|
||||||
m.close()
|
func (f *MountsFile) Err() error { return f.m.Err() }
|
||||||
return m.Err()
|
func (f *MountsFile) Entries() iter.Seq[*Mntent] {
|
||||||
|
return func(yield func(*Mntent) bool) {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
if f.done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for f.m.scan() {
|
||||||
|
e := new(Mntent)
|
||||||
|
f.m.copy(e)
|
||||||
|
if !yield(e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.done = true
|
||||||
|
f.m.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type mounts struct {
|
type mounts struct {
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
{
|
|
||||||
writeText,
|
|
||||||
buildGoModule,
|
|
||||||
|
|
||||||
version,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
wantMounts =
|
|
||||||
let
|
|
||||||
ent = fsname: dir: type: opts: freq: passno: {
|
|
||||||
inherit
|
|
||||||
fsname
|
|
||||||
dir
|
|
||||||
type
|
|
||||||
opts
|
|
||||||
freq
|
|
||||||
passno
|
|
||||||
;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
[
|
|
||||||
(ent "tmpfs" "/" "tmpfs" "rw,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "proc" "/proc" "proc" "rw,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/.fortify" "tmpfs" "rw,nosuid,nodev,relatime,size=4k,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "tmpfs" "/dev" "tmpfs" "rw,nosuid,nodev,relatime,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/null" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/zero" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/full" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/random" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/urandom" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/tty" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devpts" "/dev/pts" "devpts" "rw,nosuid,noexec,relatime,mode=620,ptmxmode=666" 0 0)
|
|
||||||
(ent "mqueue" "/dev/mqueue" "mqueue" "rw,relatime" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/bin" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/usr/bin" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "overlay" "/nix/store" "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)
|
|
||||||
(ent "overlay" "/run/current-system" "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)
|
|
||||||
(ent "sysfs" "/sys/block" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "sysfs" "/sys/bus" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "sysfs" "/sys/class" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "sysfs" "/sys/dev" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "sysfs" "/sys/devices" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "overlay" "/run/opengl-driver" "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)
|
|
||||||
(ent "devtmpfs" "/dev/dri" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "proc" "/.fortify/mounts" "proc" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/.fortify/etc" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/run/user" "tmpfs" "rw,nosuid,nodev,relatime,size=1024k,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "tmpfs" "/run/user/65534" "tmpfs" "rw,nosuid,nodev,relatime,size=8192k,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/tmp" "ext4" "rw,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/var/lib/fortify/u0/a1" "ext4" "rw,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/etc/passwd" "tmpfs" "ro,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "tmpfs" "/etc/group" "tmpfs" "ro,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/run/user/65534/wayland-0" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/run/user/65534/pulse/native" "tmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/run/user/65534/bus" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/var/run/nscd" "tmpfs" "rw,nosuid,nodev,relatime,size=8k,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "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)
|
|
||||||
];
|
|
||||||
|
|
||||||
mainFile = writeText "main.go" ''
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "git.gensokyo.uk/security/fortify/test/sandbox"
|
|
||||||
|
|
||||||
func main() { sandbox.MustAssertMounts("", "/.fortify/mounts", "${writeText "want-mounts.json" (builtins.toJSON wantMounts)}") }
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
buildGoModule {
|
|
||||||
pname = "check-mounts";
|
|
||||||
inherit version;
|
|
||||||
|
|
||||||
src = ../.;
|
|
||||||
vendorHash = null;
|
|
||||||
|
|
||||||
preBuild = ''
|
|
||||||
go mod init git.gensokyo.uk/security/fortify/test >& /dev/null
|
|
||||||
cp ${mainFile} main.go
|
|
||||||
'';
|
|
||||||
}
|
|
@ -92,27 +92,29 @@ overlay /.fortify/sbin/fortify overlay ro,nosuid,nodev,relatime,lowerdir=/mnt-ro
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
f, err := sandbox.OpenMounts(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("OpenMounts: error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
if err := sandbox.IterMounts(name, func(e *sandbox.Mntent) {
|
for e := range f.Entries() {
|
||||||
if i == len(tc.want) {
|
if i == len(tc.want) {
|
||||||
t.Errorf("IterMounts: got more than %d entries", i)
|
t.Errorf("Entries: got more than %d entries", i)
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
if *e != tc.want[i] {
|
if *e != tc.want[i] {
|
||||||
t.Errorf("IterMounts: entry %d\n got: %s\nwant: %s", i,
|
t.Errorf("Entries: entry %d\n got: %s\nwant: %s", i,
|
||||||
e, &tc.want[i])
|
e, &tc.want[i])
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
i++
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("IterMounts: error = %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run(tc.name+" assert", func(t *testing.T) {
|
i++
|
||||||
oldFatal := sandbox.SwapFatal(t.Fatalf)
|
}
|
||||||
t.Cleanup(func() { sandbox.SwapFatal(oldFatal) })
|
|
||||||
sandbox.MustAssertMounts(name, name, sandbox.MustWantFile(t, tc.want))
|
if err = f.Err(); err != nil {
|
||||||
|
t.Fatalf("MountsFile: error = %v", err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := os.Remove(name); err != nil {
|
if err := os.Remove(name); err != nil {
|
||||||
|
@ -102,8 +102,8 @@ if denyOutput != "fsu: uid 1001 is not in the fsurc file\n":
|
|||||||
if denyOutputVerbose != "fsu: uid 1001 is not in the fsurc file\nfortify: *cannot obtain uid from fsu: permission denied\n":
|
if denyOutputVerbose != "fsu: uid 1001 is not in the fsurc file\nfortify: *cannot obtain uid from fsu: permission denied\n":
|
||||||
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
|
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
|
||||||
|
|
||||||
# Check sandbox state:
|
# Check sandbox outcome:
|
||||||
swaymsg("exec check-sandbox")
|
swaymsg("exec check-sandbox-module-default")
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/sandbox-ok", timeout=15)
|
machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/sandbox-ok", timeout=15)
|
||||||
|
|
||||||
# Start fortify permissive defaults outside Wayland session:
|
# Start fortify permissive defaults outside Wayland session:
|
||||||
|
Loading…
Reference in New Issue
Block a user