proc/priv/shim: resolve and load seccomp rules
All checks were successful
Build / Create distribution (push) Successful in 1m33s
Test / Run NixOS test (push) Successful in 3m36s

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
Ophestra 2025-01-20 23:52:56 +09:00
parent 3df344828f
commit 20a3d4c458
Signed by: cat
SSH Key Fingerprint: SHA256:gQ67O0enBZ7UdZypgtspB2FDM1g3GVw8nX0XSdcFw8Q
2 changed files with 107 additions and 8 deletions

View File

@ -2,13 +2,15 @@ package shim
import ( import (
"errors" "errors"
"flag"
"io"
"os" "os"
"path" "path"
"strconv" "strconv"
"syscall"
"git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/fst"
"git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/helper"
"git.gensokyo.uk/security/fortify/helper/bwrap"
"git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal"
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/proc" "git.gensokyo.uk/security/fortify/internal/proc"
@ -18,7 +20,7 @@ import (
// everything beyond this point runs as unconstrained target user // everything beyond this point runs as unconstrained target user
// proceed with caution! // proceed with caution!
func Main() { func Main(args []string) {
// sharing stdout with fortify // sharing stdout with fortify
// USE WITH CAUTION // USE WITH CAUTION
fmsg.SetPrefix("shim") fmsg.SetPrefix("shim")
@ -29,12 +31,44 @@ func Main() {
panic("unreachable") panic("unreachable")
} }
// re-exec set := flag.NewFlagSet("shim", flag.ExitOnError)
if len(os.Args) > 0 && (os.Args[0] != "fortify" || os.Args[1] != "shim" || len(os.Args) != 2) && path.IsAbs(os.Args[0]) {
if err := syscall.Exec(os.Args[0], []string{"fortify", "shim"}, os.Environ()); err != nil { // debug: export seccomp filter
fmsg.Println("cannot re-exec self:", err) debugExportSeccomp := set.String("export-seccomp", "", "export the seccomp filter to file")
// continue anyway debugExportSeccompFlags := [...]struct {
o syscallOpts
v *bool
}{
{flagDenyNS, set.Bool("deny-ns", false, "deny namespace-related syscalls")},
{flagDenyTTY, set.Bool("deny-tty", false, "deny faking input ioctls")},
{flagDenyDevel, set.Bool("deny-devel", false, "deny development syscalls")},
{flagMultiarch, set.Bool("multiarch", false, "allow multiarch")},
{flagLinux32, set.Bool("linux32", false, "allow PER_LINUX32")},
{flagCan, set.Bool("can", false, "allow AF_CAN")},
{flagBluetooth, set.Bool("bluetooth", false, "AF_BLUETOOTH")},
} }
// Ignore errors; set is set for ExitOnError.
_ = set.Parse(args[1:])
// debug: export seccomp filter
if *debugExportSeccomp != "" {
var opts syscallOpts
for _, opt := range debugExportSeccompFlags {
if *opt.v {
opts |= opt.o
}
}
if f, err := os.Create(*debugExportSeccomp); err != nil {
fmsg.Fatalf("cannot create %q: %v", *debugExportSeccomp, err)
} else {
mustExportFilter(f, opts)
if err = f.Close(); err != nil {
fmsg.Fatalf("cannot close %q: %v", *debugExportSeccomp, err)
}
}
fmsg.Exit(0)
} }
// receive setup payload // receive setup payload
@ -142,6 +176,9 @@ func Main() {
// keep this fd open while sandbox is running // keep this fd open while sandbox is running
// (--sync-fd FD) // (--sync-fd FD)
{"--sync-fd", syncFd}, {"--sync-fd", syncFd},
// load and use seccomp rules from FD (not repeatable)
// (--seccomp FD)
{"--seccomp", mustResolveSeccomp(payload.Bwrap, payload.Syscall)},
}, },
); err != nil { ); err != nil {
fmsg.Fatalf("malformed sandbox config: %v", err) fmsg.Fatalf("malformed sandbox config: %v", err)
@ -163,3 +200,65 @@ func Main() {
} }
} }
} }
func mustResolveSeccomp(bwrap *bwrap.Config, syscall *fst.SyscallConfig) (seccompFd *os.File) {
if syscall == nil {
fmsg.VPrintln("syscall filter not configured, PROCEED WITH CAUTION")
return
}
// resolve seccomp filter opts
var (
opts syscallOpts
optd []string
optCond = [...]struct {
v bool
o syscallOpts
d string
}{
{!bwrap.UserNS, flagDenyNS, "denyns"},
{bwrap.NewSession, flagDenyTTY, "denytty"},
{syscall.DenyDevel, flagDenyDevel, "denydevel"},
{syscall.Multiarch, flagMultiarch, "multiarch"},
{syscall.Linux32, flagLinux32, "linux32"},
{syscall.Can, flagCan, "can"},
{syscall.Bluetooth, flagBluetooth, "bluetooth"},
}
)
if fmsg.Verbose() {
optd = make([]string, 1, len(optCond)+1)
optd[0] = "fortify"
}
for _, opt := range optCond {
if opt.v {
opts |= opt.o
if fmsg.Verbose() {
optd = append(optd, opt.d)
}
}
}
if fmsg.Verbose() {
fmsg.VPrintf("seccomp flags: %s", optd)
}
// export seccomp filter to tmpfile
if f, err := tmpfile(); err != nil {
fmsg.Fatalf("cannot create tmpfile: %v", err)
panic("unreachable")
} else {
mustExportFilter(f, opts)
seccompFd = f
return
}
}
func mustExportFilter(f *os.File, opts syscallOpts) {
if err := exportFilter(f.Fd(), opts); err != nil {
fmsg.Fatalf("cannot export seccomp filter: %v", err)
panic("unreachable")
}
if _, err := f.Seek(0, io.SeekStart); err != nil {
fmsg.Fatalf("cannot lseek seccomp file: %v", err)
panic("unreachable")
}
}

View File

@ -291,7 +291,7 @@ func main() {
// internal commands // internal commands
case "shim": case "shim":
shim.Main() shim.Main(args)
fmsg.Exit(0) fmsg.Exit(0)
case "init": case "init":
init0.Main() init0.Main()