From 87e008d56de974947ebb99c2cc40b25d3c2cf43e Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 25 Jun 2025 03:59:52 +0900 Subject: treewide: rename to hakurei Fortify makes little sense for a container tool. Signed-off-by: Ophestra --- cmd/fpkg/app.go | 54 +++++++-------- cmd/fpkg/build.nix | 4 +- cmd/fpkg/main.go | 38 +++++------ cmd/fpkg/paths.go | 14 ++-- cmd/fpkg/proc.go | 12 ++-- cmd/fpkg/test/configuration.nix | 4 +- cmd/fpkg/test/default.nix | 2 +- cmd/fpkg/test/test.py | 22 +++--- cmd/fpkg/with.go | 30 ++++----- cmd/fsu/main.go | 146 ---------------------------------------- cmd/fsu/package.nix | 30 --------- cmd/fsu/parse.go | 67 ------------------ cmd/fsu/parse_test.go | 96 -------------------------- cmd/fsu/path.go | 21 ------ cmd/hsu/main.go | 146 ++++++++++++++++++++++++++++++++++++++++ cmd/hsu/package.nix | 30 +++++++++ cmd/hsu/parse.go | 67 ++++++++++++++++++ cmd/hsu/parse_test.go | 96 ++++++++++++++++++++++++++ cmd/hsu/path.go | 21 ++++++ 19 files changed, 450 insertions(+), 450 deletions(-) delete mode 100644 cmd/fsu/main.go delete mode 100644 cmd/fsu/package.nix delete mode 100644 cmd/fsu/parse.go delete mode 100644 cmd/fsu/parse_test.go delete mode 100644 cmd/fsu/path.go create mode 100644 cmd/hsu/main.go create mode 100644 cmd/hsu/package.nix create mode 100644 cmd/hsu/parse.go create mode 100644 cmd/hsu/parse_test.go create mode 100644 cmd/hsu/path.go (limited to 'cmd') diff --git a/cmd/fpkg/app.go b/cmd/fpkg/app.go index 72468700..257956d9 100644 --- a/cmd/fpkg/app.go +++ b/cmd/fpkg/app.go @@ -6,46 +6,46 @@ import ( "os" "path" - "git.gensokyo.uk/security/fortify/dbus" - "git.gensokyo.uk/security/fortify/fst" - "git.gensokyo.uk/security/fortify/sandbox/seccomp" - "git.gensokyo.uk/security/fortify/system" + "git.gensokyo.uk/security/hakurei/dbus" + "git.gensokyo.uk/security/hakurei/hst" + "git.gensokyo.uk/security/hakurei/sandbox/seccomp" + "git.gensokyo.uk/security/hakurei/system" ) type appInfo struct { Name string `json:"name"` Version string `json:"version"` - // passed through to [fst.Config] + // passed through to [hst.Config] ID string `json:"id"` - // passed through to [fst.Config] + // passed through to [hst.Config] Identity int `json:"identity"` - // passed through to [fst.Config] + // passed through to [hst.Config] Groups []string `json:"groups,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] Devel bool `json:"devel,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] Userns bool `json:"userns,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] Net bool `json:"net,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] Device bool `json:"dev,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] Tty bool `json:"tty,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] MapRealUID bool `json:"map_real_uid,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] DirectWayland bool `json:"direct_wayland,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] SystemBus *dbus.Config `json:"system_bus,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] SessionBus *dbus.Config `json:"session_bus,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] Enablements system.Enablement `json:"enablements"` - // passed through to [fst.Config] + // passed through to [hst.Config] Multiarch bool `json:"multiarch,omitempty"` - // passed through to [fst.Config] + // passed through to [hst.Config] Bluetooth bool `json:"bluetooth,omitempty"` // allow gpu access within sandbox @@ -62,8 +62,8 @@ type appInfo struct { ActivationPackage string `json:"activation_package"` } -func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool) *fst.Config { - config := &fst.Config{ +func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool) *hst.Config { + config := &hst.Config{ ID: app.ID, Path: argv[0], @@ -75,7 +75,7 @@ func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool SessionBus: app.SessionBus, DirectWayland: app.DirectWayland, - Username: "fortify", + Username: "hakurei", Shell: shellPath, Data: pathSet.homeDir, Dir: path.Join("/data/data", app.ID), @@ -83,7 +83,7 @@ func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool Identity: app.Identity, Groups: app.Groups, - Container: &fst.ContainerConfig{ + Container: &hst.ContainerConfig{ Hostname: formatHostname(app.Name), Devel: app.Devel, Userns: app.Userns, @@ -91,9 +91,9 @@ func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool Device: app.Device, Tty: app.Tty || flagDropShell, MapRealUID: app.MapRealUID, - Filesystem: []*fst.FilesystemConfig{ + Filesystem: []*hst.FilesystemConfig{ {Src: path.Join(pathSet.nixPath, "store"), Dst: "/nix/store", Must: true}, - {Src: pathSet.metaPath, Dst: path.Join(fst.Tmp, "app"), Must: true}, + {Src: pathSet.metaPath, Dst: path.Join(hst.Tmp, "app"), Must: true}, {Src: "/etc/resolv.conf"}, {Src: "/sys/block"}, {Src: "/sys/bus"}, @@ -109,7 +109,7 @@ func (app *appInfo) toFst(pathSet *appPathSet, argv []string, flagDropShell bool Etc: path.Join(pathSet.cacheDir, "etc"), AutoEtc: true, }, - ExtraPerms: []*fst.ExtraPermConfig{ + ExtraPerms: []*hst.ExtraPermConfig{ {Path: dataHome, Execute: true}, {Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true}, }, @@ -147,7 +147,7 @@ func loadAppInfo(name string, beforeFail func()) *appInfo { func formatHostname(name string) string { if h, err := os.Hostname(); err != nil { log.Printf("cannot get hostname: %v", err) - return "fortify-" + name + return "hakurei-" + name } else { return h + "-" + name } diff --git a/cmd/fpkg/build.nix b/cmd/fpkg/build.nix index 0a0a558f..b5c50801 100644 --- a/cmd/fpkg/build.nix +++ b/cmd/fpkg/build.nix @@ -57,7 +57,7 @@ let modules = modules ++ [ { home = { - username = "fortify"; + username = "hakurei"; homeDirectory = "/data/data/${id}"; stateVersion = "22.11"; }; @@ -65,7 +65,7 @@ let ]; }; - launcher = writeScript "fortify-${pname}" '' + launcher = writeScript "hakurei-${pname}" '' #!${runtimeShell} -el ${script} ''; diff --git a/cmd/fpkg/main.go b/cmd/fpkg/main.go index ac95bc1f..7d44d419 100644 --- a/cmd/fpkg/main.go +++ b/cmd/fpkg/main.go @@ -10,13 +10,13 @@ import ( "path" "syscall" - "git.gensokyo.uk/security/fortify/command" - "git.gensokyo.uk/security/fortify/fst" - "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/internal/app/instance" - "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/internal/sys" - "git.gensokyo.uk/security/fortify/sandbox" + "git.gensokyo.uk/security/hakurei/command" + "git.gensokyo.uk/security/hakurei/hst" + "git.gensokyo.uk/security/hakurei/internal" + "git.gensokyo.uk/security/hakurei/internal/app/instance" + "git.gensokyo.uk/security/hakurei/internal/hlog" + "git.gensokyo.uk/security/hakurei/internal/sys" + "git.gensokyo.uk/security/hakurei/sandbox" ) const shellPath = "/run/current-system/sw/bin/bash" @@ -28,7 +28,7 @@ var ( ) func init() { - fmsg.Prepare("fpkg") + hlog.Prepare("fpkg") if err := os.Setenv("SHELL", shellPath); err != nil { log.Fatalf("cannot set $SHELL: %v", err) } @@ -36,7 +36,7 @@ func init() { func main() { // early init path, skips root check and duplicate PR_SET_DUMPABLE - sandbox.TryArgv0(fmsg.Output{}, fmsg.Prepare, internal.InstallFmsg) + sandbox.TryArgv0(hlog.Output{}, hlog.Prepare, internal.InstallFmsg) if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { log.Printf("cannot set SUID_DUMP_DISABLE: %s", err) @@ -60,7 +60,7 @@ func main() { return nil }). Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). - Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next fortify action") + Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next hakurei action") c.Command("shim", command.UsageInternal, func([]string) error { instance.ShimMain(); return errSuccess }) @@ -166,10 +166,10 @@ func main() { } // sec: should compare version string - fmsg.Verbosef("installing application %q version %q over local %q", + hlog.Verbosef("installing application %q version %q over local %q", bundle.ID, bundle.Version, a.Version) } else { - fmsg.Verbosef("application %q clean installation", bundle.ID) + hlog.Verbosef("application %q clean installation", bundle.ID) // sec: should install credentials } @@ -179,7 +179,7 @@ func main() { withCacheDir(ctx, "install", []string{ // export inner bundle path in the environment - "export BUNDLE=" + fst.Tmp + "/bundle", + "export BUNDLE=" + hst.Tmp + "/bundle", // replace inner /etc "mkdir -p etc", "chmod -R +w etc", @@ -218,7 +218,7 @@ func main() { "rm -rf .local/state/{nix,home-manager}", // run activation script bundle.ActivationPackage + "/activate", - }, false, func(config *fst.Config) *fst.Config { return config }, + }, false, func(config *hst.Config) *hst.Config { return config }, bundle, pathSet, flagDropShellActivate, cleanup) /* @@ -291,8 +291,8 @@ func main() { "--out-link /nix/.nixGL/auto/vulkan " + "--override-input nixpkgs path:/etc/nixpkgs " + "path:" + a.NixGL + "#nixVulkanNvidia", - }, true, func(config *fst.Config) *fst.Config { - config.Container.Filesystem = append(config.Container.Filesystem, []*fst.FilesystemConfig{ + }, true, func(config *hst.Config) *hst.Config { + config.Container.Filesystem = append(config.Container.Filesystem, []*hst.FilesystemConfig{ {Src: "/etc/resolv.conf"}, {Src: "/sys/block"}, {Src: "/sys/bus"}, @@ -325,7 +325,7 @@ func main() { if a.GPU { config.Container.Filesystem = append(config.Container.Filesystem, - &fst.FilesystemConfig{Src: path.Join(pathSet.nixPath, ".nixGL"), Dst: path.Join(fst.Tmp, "nixGL")}) + &hst.FilesystemConfig{Src: path.Join(pathSet.nixPath, ".nixGL"), Dst: path.Join(hst.Tmp, "nixGL")}) appendGPUFilesystem(config) } @@ -341,9 +341,9 @@ func main() { } c.MustParse(os.Args[1:], func(err error) { - fmsg.Verbosef("command returned %v", err) + hlog.Verbosef("command returned %v", err) if errors.Is(err, errSuccess) { - fmsg.BeforeExit() + hlog.BeforeExit() os.Exit(0) } }) diff --git a/cmd/fpkg/paths.go b/cmd/fpkg/paths.go index 2eaeb425..aa581c9e 100644 --- a/cmd/fpkg/paths.go +++ b/cmd/fpkg/paths.go @@ -8,8 +8,8 @@ import ( "strconv" "sync/atomic" - "git.gensokyo.uk/security/fortify/fst" - "git.gensokyo.uk/security/fortify/internal/fmsg" + "git.gensokyo.uk/security/hakurei/hst" + "git.gensokyo.uk/security/hakurei/internal/hlog" ) var ( @@ -18,10 +18,10 @@ var ( func init() { // dataHome - if p, ok := os.LookupEnv("FORTIFY_DATA_HOME"); ok { + if p, ok := os.LookupEnv("HAKUREI_DATA_HOME"); ok { dataHome = p } else { - dataHome = "/var/lib/fortify/" + strconv.Itoa(os.Getuid()) + dataHome = "/var/lib/hakurei/" + strconv.Itoa(os.Getuid()) } } @@ -37,7 +37,7 @@ func lookPath(file string) string { var beforeRunFail = new(atomic.Pointer[func()]) func mustRun(name string, arg ...string) { - fmsg.Verbosef("spawning process: %q %q", name, arg) + hlog.Verbosef("spawning process: %q %q", name, arg) cmd := exec.Command(name, arg...) cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr if err := cmd.Run(); err != nil { @@ -71,8 +71,8 @@ func pathSetByApp(id string) *appPathSet { return pathSet } -func appendGPUFilesystem(config *fst.Config) { - config.Container.Filesystem = append(config.Container.Filesystem, []*fst.FilesystemConfig{ +func appendGPUFilesystem(config *hst.Config) { + config.Container.Filesystem = append(config.Container.Filesystem, []*hst.FilesystemConfig{ // flatpak commit 763a686d874dd668f0236f911de00b80766ffe79 {Src: "/dev/dri", Device: true}, // mali diff --git a/cmd/fpkg/proc.go b/cmd/fpkg/proc.go index 45fe3525..7862e476 100644 --- a/cmd/fpkg/proc.go +++ b/cmd/fpkg/proc.go @@ -4,19 +4,19 @@ import ( "context" "os" - "git.gensokyo.uk/security/fortify/fst" - "git.gensokyo.uk/security/fortify/internal/app" - "git.gensokyo.uk/security/fortify/internal/app/instance" - "git.gensokyo.uk/security/fortify/internal/fmsg" + "git.gensokyo.uk/security/hakurei/hst" + "git.gensokyo.uk/security/hakurei/internal/app" + "git.gensokyo.uk/security/hakurei/internal/app/instance" + "git.gensokyo.uk/security/hakurei/internal/hlog" ) -func mustRunApp(ctx context.Context, config *fst.Config, beforeFail func()) { +func mustRunApp(ctx context.Context, config *hst.Config, beforeFail func()) { rs := new(app.RunState) a := instance.MustNew(instance.ISetuid, ctx, std) var code int if sa, err := a.Seal(config); err != nil { - fmsg.PrintBaseError(err, "cannot seal app:") + hlog.PrintBaseError(err, "cannot seal app:") code = 1 } else { code = instance.PrintRunStateErr(instance.ISetuid, rs, sa.Run(rs)) diff --git a/cmd/fpkg/test/configuration.nix b/cmd/fpkg/test/configuration.nix index 2608fedd..ce74228a 100644 --- a/cmd/fpkg/test/configuration.nix +++ b/cmd/fpkg/test/configuration.nix @@ -50,9 +50,9 @@ ]; }; - environment.fortify = { + environment.hakurei = { enable = true; - stateDir = "/var/lib/fortify"; + stateDir = "/var/lib/hakurei"; users.alice = 0; extraHomeConfig = { diff --git a/cmd/fpkg/test/default.nix b/cmd/fpkg/test/default.nix index c0a22486..dcd2cc66 100644 --- a/cmd/fpkg/test/default.nix +++ b/cmd/fpkg/test/default.nix @@ -18,7 +18,7 @@ nixosTest { imports = [ ./configuration.nix - self.nixosModules.fortify + self.nixosModules.hakurei self.inputs.home-manager.nixosModules.home-manager ]; }; diff --git a/cmd/fpkg/test/test.py b/cmd/fpkg/test/test.py index 641062f2..9b9dfc16 100644 --- a/cmd/fpkg/test/test.py +++ b/cmd/fpkg/test/test.py @@ -47,22 +47,22 @@ def wait_for_window(pattern): def collect_state_ui(name): - swaymsg(f"exec fortify ps > '/tmp/{name}.ps'") + swaymsg(f"exec hakurei ps > '/tmp/{name}.ps'") machine.copy_from_vm(f"/tmp/{name}.ps", "") - swaymsg(f"exec fortify --json ps > '/tmp/{name}.json'") + swaymsg(f"exec hakurei --json ps > '/tmp/{name}.json'") machine.copy_from_vm(f"/tmp/{name}.json", "") machine.screenshot(name) def check_state(name, enablements): - instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 fortify --json ps")) + instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 hakurei --json ps")) if len(instances) != 1: raise Exception(f"unexpected state length {len(instances)}") instance = next(iter(instances.values())) config = instance['config'] - if len(config['args']) != 1 or not (config['args'][0].startswith("/nix/store/")) or f"fortify-{name}-" not in (config['args'][0]): + if len(config['args']) != 1 or not (config['args'][0].startswith("/nix/store/")) or f"hakurei-{name}-" not in (config['args'][0]): raise Exception(f"unexpected args {instance['config']['args']}") if config['enablements'] != enablements: @@ -72,15 +72,15 @@ def check_state(name, enablements): start_all() machine.wait_for_unit("multi-user.target") -# To check fortify's version: -print(machine.succeed("sudo -u alice -i fortify version")) +# To check hakurei's version: +print(machine.succeed("sudo -u alice -i hakurei version")) # Wait for Sway to complete startup: machine.wait_for_file("/run/user/1000/wayland-1") machine.wait_for_file("/tmp/sway-ipc.sock") # Prepare fpkg directory: -machine.succeed("install -dm 0700 -o alice -g users /var/lib/fortify/1000") +machine.succeed("install -dm 0700 -o alice -g users /var/lib/hakurei/1000") # Install fpkg app: swaymsg("exec fpkg -v install /etc/foot.pkg && touch /tmp/fpkg-install-done") @@ -88,9 +88,9 @@ machine.wait_for_file("/tmp/fpkg-install-done") # Start app (foot) with Wayland enablement: swaymsg("exec fpkg -v start org.codeberg.dnkl.foot") -wait_for_window("fortify@machine-foot") +wait_for_window("hakurei@machine-foot") machine.send_chars("clear; wayland-info && touch /tmp/success-client\n") -machine.wait_for_file("/tmp/fortify.1000/tmpdir/2/success-client") +machine.wait_for_file("/tmp/hakurei.1000/tmpdir/2/success-client") collect_state_ui("app_wayland") check_state("foot", 13) # Verify acl on XDG_RUNTIME_DIR: @@ -104,5 +104,5 @@ machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/ swaymsg("exit", succeed=False) machine.wait_for_file("/tmp/sway-exit-ok") -# Print fortify runDir contents: -print(machine.succeed("find /run/user/1000/fortify")) \ No newline at end of file +# Print hakurei runDir contents: +print(machine.succeed("find /run/user/1000/hakurei")) \ No newline at end of file diff --git a/cmd/fpkg/with.go b/cmd/fpkg/with.go index 9de1a397..ffacec71 100644 --- a/cmd/fpkg/with.go +++ b/cmd/fpkg/with.go @@ -5,17 +5,17 @@ import ( "path" "strings" - "git.gensokyo.uk/security/fortify/fst" - "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/sandbox/seccomp" + "git.gensokyo.uk/security/hakurei/hst" + "git.gensokyo.uk/security/hakurei/internal" + "git.gensokyo.uk/security/hakurei/sandbox/seccomp" ) func withNixDaemon( ctx context.Context, - action string, command []string, net bool, updateConfig func(config *fst.Config) *fst.Config, + action string, command []string, net bool, updateConfig func(config *hst.Config) *hst.Config, app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func(), ) { - mustRunAppDropShell(ctx, updateConfig(&fst.Config{ + mustRunAppDropShell(ctx, updateConfig(&hst.Config{ ID: app.ID, Path: shellPath, @@ -31,24 +31,24 @@ func withNixDaemon( " && pkill nix-daemon", }, - Username: "fortify", + Username: "hakurei", Shell: shellPath, Data: pathSet.homeDir, Dir: path.Join("/data/data", app.ID), - ExtraPerms: []*fst.ExtraPermConfig{ + ExtraPerms: []*hst.ExtraPermConfig{ {Path: dataHome, Execute: true}, {Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true}, }, Identity: app.Identity, - Container: &fst.ContainerConfig{ + Container: &hst.ContainerConfig{ Hostname: formatHostname(app.Name) + "-" + action, Userns: true, // nix sandbox requires userns Net: net, Seccomp: seccomp.FilterMultiarch, Tty: dropShell, - Filesystem: []*fst.FilesystemConfig{ + Filesystem: []*hst.FilesystemConfig{ {Src: pathSet.nixPath, Dst: "/nix", Write: true, Must: true}, }, Link: [][2]string{ @@ -66,7 +66,7 @@ func withCacheDir( ctx context.Context, action string, command []string, workDir string, app *appInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) { - mustRunAppDropShell(ctx, &fst.Config{ + mustRunAppDropShell(ctx, &hst.Config{ ID: app.ID, Path: shellPath, @@ -76,7 +76,7 @@ func withCacheDir( Shell: shellPath, Data: pathSet.cacheDir, // this also ensures cacheDir via shim Dir: path.Join("/data/data", app.ID, "cache"), - ExtraPerms: []*fst.ExtraPermConfig{ + ExtraPerms: []*hst.ExtraPermConfig{ {Path: dataHome, Execute: true}, {Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true}, {Path: workDir, Execute: true}, @@ -84,13 +84,13 @@ func withCacheDir( Identity: app.Identity, - Container: &fst.ContainerConfig{ + Container: &hst.ContainerConfig{ Hostname: formatHostname(app.Name) + "-" + action, Seccomp: seccomp.FilterMultiarch, Tty: dropShell, - Filesystem: []*fst.FilesystemConfig{ + Filesystem: []*hst.FilesystemConfig{ {Src: path.Join(workDir, "nix"), Dst: "/nix", Must: true}, - {Src: workDir, Dst: path.Join(fst.Tmp, "bundle"), Must: true}, + {Src: workDir, Dst: path.Join(hst.Tmp, "bundle"), Must: true}, }, Link: [][2]string{ {app.CurrentSystem, "/run/current-system"}, @@ -103,7 +103,7 @@ func withCacheDir( }, dropShell, beforeFail) } -func mustRunAppDropShell(ctx context.Context, config *fst.Config, dropShell bool, beforeFail func()) { +func mustRunAppDropShell(ctx context.Context, config *hst.Config, dropShell bool, beforeFail func()) { if dropShell { config.Args = []string{shellPath, "-l"} mustRunApp(ctx, config, beforeFail) diff --git a/cmd/fsu/main.go b/cmd/fsu/main.go deleted file mode 100644 index 33ecbf43..00000000 --- a/cmd/fsu/main.go +++ /dev/null @@ -1,146 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "log" - "os" - "path" - "slices" - "strconv" - "strings" - "syscall" -) - -const ( - fsuConfFile = "/etc/fsurc" - envShim = "FORTIFY_SHIM" - envAID = "FORTIFY_APP_ID" - envGroups = "FORTIFY_GROUPS" - - PR_SET_NO_NEW_PRIVS = 0x26 -) - -func main() { - log.SetFlags(0) - log.SetPrefix("fsu: ") - log.SetOutput(os.Stderr) - - if os.Geteuid() != 0 { - log.Fatal("this program must be owned by uid 0 and have the setuid bit set") - } - - puid := os.Getuid() - if puid == 0 { - log.Fatal("this program must not be started by root") - } - - var toolPath string - pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe") - if p, err := os.Readlink(pexe); err != nil { - log.Fatalf("cannot read parent executable path: %v", err) - } else if strings.HasSuffix(p, " (deleted)") { - log.Fatal("fortify executable has been deleted") - } else if p != mustCheckPath(fmain) && p != mustCheckPath(fpkg) { - log.Fatal("this program must be started by fortify") - } else { - toolPath = p - } - - // uid = 1000000 + - // fid * 10000 + - // aid - uid := 1000000 - - // refuse to run if fsurc is not protected correctly - if s, err := os.Stat(fsuConfFile); err != nil { - log.Fatal(err) - } else if s.Mode().Perm() != 0400 { - log.Fatal("bad fsurc perm") - } else if st := s.Sys().(*syscall.Stat_t); st.Uid != 0 || st.Gid != 0 { - log.Fatal("fsurc must be owned by uid 0") - } - - // authenticate before accepting user input - if f, err := os.Open(fsuConfFile); err != nil { - log.Fatal(err) - } else if fid, ok := mustParseConfig(f, puid); !ok { - log.Fatalf("uid %d is not in the fsurc file", puid) - } else { - uid += fid * 10000 - } - - // allowed aid range 0 to 9999 - if as, ok := os.LookupEnv(envAID); !ok { - log.Fatal("FORTIFY_APP_ID not set") - } else if aid, err := parseUint32Fast(as); err != nil || aid < 0 || aid > 9999 { - log.Fatal("invalid aid") - } else { - uid += aid - } - - // pass through setup fd to shim - var shimSetupFd string - if s, ok := os.LookupEnv(envShim); !ok { - // fortify requests target uid - // print resolved uid and exit - fmt.Print(uid) - os.Exit(0) - } else if len(s) != 1 || s[0] > '9' || s[0] < '3' { - log.Fatal("FORTIFY_SHIM holds an invalid value") - } else { - shimSetupFd = s - } - - // supplementary groups - var suppGroups, suppCurrent []int - - if gs, ok := os.LookupEnv(envGroups); ok { - if cur, err := os.Getgroups(); err != nil { - log.Fatalf("cannot get groups: %v", err) - } else { - suppCurrent = cur - } - - // parse space-separated list of group ids - gss := bytes.Split([]byte(gs), []byte{' '}) - suppGroups = make([]int, len(gss)+1) - for i, s := range gss { - if gid, err := strconv.Atoi(string(s)); err != nil { - log.Fatalf("cannot parse %q: %v", string(s), err) - } else if gid > 0 && gid != uid && gid != os.Getgid() && slices.Contains(suppCurrent, gid) { - suppGroups[i] = gid - } else { - log.Fatalf("invalid gid %d", gid) - } - } - suppGroups[len(suppGroups)-1] = uid - } else { - suppGroups = []int{uid} - } - - // final bounds check to catch any bugs - if uid < 1000000 || uid >= 2000000 { - panic("uid out of bounds") - } - - // careful! users in the allowlist is effectively allowed to drop groups via fsu - - if err := syscall.Setresgid(uid, uid, uid); err != nil { - log.Fatalf("cannot set gid: %v", err) - } - if err := syscall.Setgroups(suppGroups); err != nil { - log.Fatalf("cannot set supplementary groups: %v", err) - } - if err := syscall.Setresuid(uid, uid, uid); err != nil { - log.Fatalf("cannot set uid: %v", err) - } - if _, _, errno := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0); errno != 0 { - log.Fatalf("cannot set no_new_privs flag: %s", errno.Error()) - } - if err := syscall.Exec(toolPath, []string{"fortify", "shim"}, []string{envShim + "=" + shimSetupFd}); err != nil { - log.Fatalf("cannot start shim: %v", err) - } - - panic("unreachable") -} diff --git a/cmd/fsu/package.nix b/cmd/fsu/package.nix deleted file mode 100644 index ab5c4b4d..00000000 --- a/cmd/fsu/package.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ - lib, - buildGoModule, - fortify ? abort "fortify package required", -}: - -buildGoModule { - pname = "${fortify.pname}-fsu"; - inherit (fortify) version; - - src = ./.; - inherit (fortify) vendorHash; - env.CGO_ENABLED = 0; - - preBuild = '' - go mod init fsu >& /dev/null - ''; - - ldflags = - lib.attrsets.foldlAttrs - ( - ldflags: name: value: - ldflags ++ [ "-X main.${name}=${value}" ] - ) - [ "-s -w" ] - { - fmain = "${fortify}/libexec/fortify"; - fpkg = "${fortify}/libexec/fpkg"; - }; -} diff --git a/cmd/fsu/parse.go b/cmd/fsu/parse.go deleted file mode 100644 index 4b3e22a8..00000000 --- a/cmd/fsu/parse.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "bufio" - "errors" - "fmt" - "io" - "log" - "strings" -) - -func parseUint32Fast(s string) (int, error) { - sLen := len(s) - if sLen < 1 { - return -1, errors.New("zero length string") - } - if sLen > 10 { - return -1, errors.New("string too long") - } - - n := 0 - for i, ch := range []byte(s) { - ch -= '0' - if ch > 9 { - return -1, fmt.Errorf("invalid character '%s' at index %d", string(ch+'0'), i) - } - n = n*10 + int(ch) - } - return n, nil -} - -func parseConfig(r io.Reader, puid int) (fid int, ok bool, err error) { - s := bufio.NewScanner(r) - var line, puid0 int - for s.Scan() { - line++ - - // - lf := strings.SplitN(s.Text(), " ", 2) - if len(lf) != 2 { - return -1, false, fmt.Errorf("invalid entry on line %d", line) - } - - puid0, err = parseUint32Fast(lf[0]) - if err != nil || puid0 < 1 { - return -1, false, fmt.Errorf("invalid parent uid on line %d", line) - } - - ok = puid0 == puid - if ok { - // allowed fid range 0 to 99 - if fid, err = parseUint32Fast(lf[1]); err != nil || fid < 0 || fid > 99 { - return -1, false, fmt.Errorf("invalid fortify uid on line %d", line) - } - return - } - } - return -1, false, s.Err() -} - -func mustParseConfig(r io.Reader, puid int) (int, bool) { - fid, ok, err := parseConfig(r, puid) - if err != nil { - log.Fatal(err) - } - return fid, ok -} diff --git a/cmd/fsu/parse_test.go b/cmd/fsu/parse_test.go deleted file mode 100644 index baa239bc..00000000 --- a/cmd/fsu/parse_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "bytes" - "strconv" - "testing" -) - -func Test_parseUint32Fast(t *testing.T) { - t.Run("zero-length", func(t *testing.T) { - if _, err := parseUint32Fast(""); err == nil || err.Error() != "zero length string" { - t.Errorf(`parseUint32Fast(""): error = %v`, err) - return - } - }) - t.Run("overflow", func(t *testing.T) { - if _, err := parseUint32Fast("10000000000"); err == nil || err.Error() != "string too long" { - t.Errorf("parseUint32Fast: error = %v", err) - return - } - }) - t.Run("invalid byte", func(t *testing.T) { - if _, err := parseUint32Fast("meow"); err == nil || err.Error() != "invalid character 'm' at index 0" { - t.Errorf(`parseUint32Fast("meow"): error = %v`, err) - return - } - }) - t.Run("full range", func(t *testing.T) { - testRange := func(i, end int) { - for ; i < end; i++ { - s := strconv.Itoa(i) - w := i - t.Run("parse "+s, func(t *testing.T) { - t.Parallel() - v, err := parseUint32Fast(s) - if err != nil { - t.Errorf("parseUint32Fast(%q): error = %v", - s, err) - return - } - if v != w { - t.Errorf("parseUint32Fast(%q): got %v", - s, v) - return - } - }) - } - } - - testRange(0, 5000) - testRange(105000, 110000) - testRange(23005000, 23010000) - testRange(456005000, 456010000) - testRange(7890005000, 7890010000) - }) -} - -func Test_parseConfig(t *testing.T) { - testCases := []struct { - name string - puid, want int - wantErr string - rc string - }{ - {"empty", 0, -1, "", ``}, - {"invalid field", 0, -1, "invalid entry on line 1", `9`}, - {"invalid puid", 0, -1, "invalid parent uid on line 1", `f 9`}, - {"invalid fid", 1000, -1, "invalid fortify uid on line 1", `1000 f`}, - {"match", 1000, 0, "", `1000 0`}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - fid, ok, err := parseConfig(bytes.NewBufferString(tc.rc), tc.puid) - if err == nil && tc.wantErr != "" { - t.Errorf("parseConfig: error = %v; wantErr %q", - err, tc.wantErr) - return - } - if err != nil && err.Error() != tc.wantErr { - t.Errorf("parseConfig: error = %q; wantErr %q", - err, tc.wantErr) - return - } - if ok == (tc.want == -1) { - t.Errorf("parseConfig: ok = %v; want %v", - ok, tc.want) - return - } - if fid != tc.want { - t.Errorf("parseConfig: fid = %v; want %v", - fid, tc.want) - } - }) - } -} diff --git a/cmd/fsu/path.go b/cmd/fsu/path.go deleted file mode 100644 index 6ed5600e..00000000 --- a/cmd/fsu/path.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "log" - "path" -) - -const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID" - -var ( - fmain = compPoison - fpkg = compPoison -) - -func mustCheckPath(p string) string { - if p != compPoison && p != "" && path.IsAbs(p) { - return p - } - log.Fatal("this program is compiled incorrectly") - return compPoison -} diff --git a/cmd/hsu/main.go b/cmd/hsu/main.go new file mode 100644 index 00000000..cefcad48 --- /dev/null +++ b/cmd/hsu/main.go @@ -0,0 +1,146 @@ +package main + +import ( + "bytes" + "fmt" + "log" + "os" + "path" + "slices" + "strconv" + "strings" + "syscall" +) + +const ( + hsuConfFile = "/etc/hsurc" + envShim = "HAKUREI_SHIM" + envAID = "HAKUREI_APP_ID" + envGroups = "HAKUREI_GROUPS" + + PR_SET_NO_NEW_PRIVS = 0x26 +) + +func main() { + log.SetFlags(0) + log.SetPrefix("hsu: ") + log.SetOutput(os.Stderr) + + if os.Geteuid() != 0 { + log.Fatal("this program must be owned by uid 0 and have the setuid bit set") + } + + puid := os.Getuid() + if puid == 0 { + log.Fatal("this program must not be started by root") + } + + var toolPath string + pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe") + if p, err := os.Readlink(pexe); err != nil { + log.Fatalf("cannot read parent executable path: %v", err) + } else if strings.HasSuffix(p, " (deleted)") { + log.Fatal("hakurei executable has been deleted") + } else if p != mustCheckPath(hmain) && p != mustCheckPath(fpkg) { + log.Fatal("this program must be started by hakurei") + } else { + toolPath = p + } + + // uid = 1000000 + + // fid * 10000 + + // aid + uid := 1000000 + + // refuse to run if hsurc is not protected correctly + if s, err := os.Stat(hsuConfFile); err != nil { + log.Fatal(err) + } else if s.Mode().Perm() != 0400 { + log.Fatal("bad hsurc perm") + } else if st := s.Sys().(*syscall.Stat_t); st.Uid != 0 || st.Gid != 0 { + log.Fatal("hsurc must be owned by uid 0") + } + + // authenticate before accepting user input + if f, err := os.Open(hsuConfFile); err != nil { + log.Fatal(err) + } else if fid, ok := mustParseConfig(f, puid); !ok { + log.Fatalf("uid %d is not in the hsurc file", puid) + } else { + uid += fid * 10000 + } + + // allowed aid range 0 to 9999 + if as, ok := os.LookupEnv(envAID); !ok { + log.Fatal("HAKUREI_APP_ID not set") + } else if aid, err := parseUint32Fast(as); err != nil || aid < 0 || aid > 9999 { + log.Fatal("invalid aid") + } else { + uid += aid + } + + // pass through setup fd to shim + var shimSetupFd string + if s, ok := os.LookupEnv(envShim); !ok { + // hakurei requests target uid + // print resolved uid and exit + fmt.Print(uid) + os.Exit(0) + } else if len(s) != 1 || s[0] > '9' || s[0] < '3' { + log.Fatal("HAKUREI_SHIM holds an invalid value") + } else { + shimSetupFd = s + } + + // supplementary groups + var suppGroups, suppCurrent []int + + if gs, ok := os.LookupEnv(envGroups); ok { + if cur, err := os.Getgroups(); err != nil { + log.Fatalf("cannot get groups: %v", err) + } else { + suppCurrent = cur + } + + // parse space-separated list of group ids + gss := bytes.Split([]byte(gs), []byte{' '}) + suppGroups = make([]int, len(gss)+1) + for i, s := range gss { + if gid, err := strconv.Atoi(string(s)); err != nil { + log.Fatalf("cannot parse %q: %v", string(s), err) + } else if gid > 0 && gid != uid && gid != os.Getgid() && slices.Contains(suppCurrent, gid) { + suppGroups[i] = gid + } else { + log.Fatalf("invalid gid %d", gid) + } + } + suppGroups[len(suppGroups)-1] = uid + } else { + suppGroups = []int{uid} + } + + // final bounds check to catch any bugs + if uid < 1000000 || uid >= 2000000 { + panic("uid out of bounds") + } + + // careful! users in the allowlist is effectively allowed to drop groups via hsu + + if err := syscall.Setresgid(uid, uid, uid); err != nil { + log.Fatalf("cannot set gid: %v", err) + } + if err := syscall.Setgroups(suppGroups); err != nil { + log.Fatalf("cannot set supplementary groups: %v", err) + } + if err := syscall.Setresuid(uid, uid, uid); err != nil { + log.Fatalf("cannot set uid: %v", err) + } + if _, _, errno := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0); errno != 0 { + log.Fatalf("cannot set no_new_privs flag: %s", errno.Error()) + } + if err := syscall.Exec(toolPath, []string{"hakurei", "shim"}, []string{envShim + "=" + shimSetupFd}); err != nil { + log.Fatalf("cannot start shim: %v", err) + } + + panic("unreachable") +} diff --git a/cmd/hsu/package.nix b/cmd/hsu/package.nix new file mode 100644 index 00000000..96ec5630 --- /dev/null +++ b/cmd/hsu/package.nix @@ -0,0 +1,30 @@ +{ + lib, + buildGoModule, + hakurei ? abort "hakurei package required", +}: + +buildGoModule { + pname = "${hakurei.pname}-hsu"; + inherit (hakurei) version; + + src = ./.; + inherit (hakurei) vendorHash; + env.CGO_ENABLED = 0; + + preBuild = '' + go mod init hsu >& /dev/null + ''; + + ldflags = + lib.attrsets.foldlAttrs + ( + ldflags: name: value: + ldflags ++ [ "-X main.${name}=${value}" ] + ) + [ "-s -w" ] + { + hmain = "${hakurei}/libexec/hakurei"; + fpkg = "${hakurei}/libexec/fpkg"; + }; +} diff --git a/cmd/hsu/parse.go b/cmd/hsu/parse.go new file mode 100644 index 00000000..35704c61 --- /dev/null +++ b/cmd/hsu/parse.go @@ -0,0 +1,67 @@ +package main + +import ( + "bufio" + "errors" + "fmt" + "io" + "log" + "strings" +) + +func parseUint32Fast(s string) (int, error) { + sLen := len(s) + if sLen < 1 { + return -1, errors.New("zero length string") + } + if sLen > 10 { + return -1, errors.New("string too long") + } + + n := 0 + for i, ch := range []byte(s) { + ch -= '0' + if ch > 9 { + return -1, fmt.Errorf("invalid character '%s' at index %d", string(ch+'0'), i) + } + n = n*10 + int(ch) + } + return n, nil +} + +func parseConfig(r io.Reader, puid int) (fid int, ok bool, err error) { + s := bufio.NewScanner(r) + var line, puid0 int + for s.Scan() { + line++ + + // + lf := strings.SplitN(s.Text(), " ", 2) + if len(lf) != 2 { + return -1, false, fmt.Errorf("invalid entry on line %d", line) + } + + puid0, err = parseUint32Fast(lf[0]) + if err != nil || puid0 < 1 { + return -1, false, fmt.Errorf("invalid parent uid on line %d", line) + } + + ok = puid0 == puid + if ok { + // allowed fid range 0 to 99 + if fid, err = parseUint32Fast(lf[1]); err != nil || fid < 0 || fid > 99 { + return -1, false, fmt.Errorf("invalid identity on line %d", line) + } + return + } + } + return -1, false, s.Err() +} + +func mustParseConfig(r io.Reader, puid int) (int, bool) { + fid, ok, err := parseConfig(r, puid) + if err != nil { + log.Fatal(err) + } + return fid, ok +} diff --git a/cmd/hsu/parse_test.go b/cmd/hsu/parse_test.go new file mode 100644 index 00000000..d5d39467 --- /dev/null +++ b/cmd/hsu/parse_test.go @@ -0,0 +1,96 @@ +package main + +import ( + "bytes" + "strconv" + "testing" +) + +func Test_parseUint32Fast(t *testing.T) { + t.Run("zero-length", func(t *testing.T) { + if _, err := parseUint32Fast(""); err == nil || err.Error() != "zero length string" { + t.Errorf(`parseUint32Fast(""): error = %v`, err) + return + } + }) + t.Run("overflow", func(t *testing.T) { + if _, err := parseUint32Fast("10000000000"); err == nil || err.Error() != "string too long" { + t.Errorf("parseUint32Fast: error = %v", err) + return + } + }) + t.Run("invalid byte", func(t *testing.T) { + if _, err := parseUint32Fast("meow"); err == nil || err.Error() != "invalid character 'm' at index 0" { + t.Errorf(`parseUint32Fast("meow"): error = %v`, err) + return + } + }) + t.Run("full range", func(t *testing.T) { + testRange := func(i, end int) { + for ; i < end; i++ { + s := strconv.Itoa(i) + w := i + t.Run("parse "+s, func(t *testing.T) { + t.Parallel() + v, err := parseUint32Fast(s) + if err != nil { + t.Errorf("parseUint32Fast(%q): error = %v", + s, err) + return + } + if v != w { + t.Errorf("parseUint32Fast(%q): got %v", + s, v) + return + } + }) + } + } + + testRange(0, 5000) + testRange(105000, 110000) + testRange(23005000, 23010000) + testRange(456005000, 456010000) + testRange(7890005000, 7890010000) + }) +} + +func Test_parseConfig(t *testing.T) { + testCases := []struct { + name string + puid, want int + wantErr string + rc string + }{ + {"empty", 0, -1, "", ``}, + {"invalid field", 0, -1, "invalid entry on line 1", `9`}, + {"invalid puid", 0, -1, "invalid parent uid on line 1", `f 9`}, + {"invalid fid", 1000, -1, "invalid identity on line 1", `1000 f`}, + {"match", 1000, 0, "", `1000 0`}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fid, ok, err := parseConfig(bytes.NewBufferString(tc.rc), tc.puid) + if err == nil && tc.wantErr != "" { + t.Errorf("parseConfig: error = %v; wantErr %q", + err, tc.wantErr) + return + } + if err != nil && err.Error() != tc.wantErr { + t.Errorf("parseConfig: error = %q; wantErr %q", + err, tc.wantErr) + return + } + if ok == (tc.want == -1) { + t.Errorf("parseConfig: ok = %v; want %v", + ok, tc.want) + return + } + if fid != tc.want { + t.Errorf("parseConfig: fid = %v; want %v", + fid, tc.want) + } + }) + } +} diff --git a/cmd/hsu/path.go b/cmd/hsu/path.go new file mode 100644 index 00000000..b4a7756a --- /dev/null +++ b/cmd/hsu/path.go @@ -0,0 +1,21 @@ +package main + +import ( + "log" + "path" +) + +const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID" + +var ( + hmain = compPoison + fpkg = compPoison +) + +func mustCheckPath(p string) string { + if p != compPoison && p != "" && path.IsAbs(p) { + return p + } + log.Fatal("this program is compiled incorrectly") + return compPoison +} -- cgit v1.3.1