From 6acd0d4e88af6a80836c5409023bbb6ebc57231f Mon Sep 17 00:00:00 2001 From: Ophestra Date: Wed, 1 Jan 2025 21:34:57 +0900 Subject: [PATCH] linux/std: handle fsu exit status 1 Printing "exit status 1" is confusing. This handles the ExitError and returns EACCES instead. Signed-off-by: Ophestra --- internal/linux/std.go | 10 +++++++++- main.go | 9 +++++---- parse.go | 8 ++++---- print.go | 4 ++-- test.nix | 21 ++++++++++++++++----- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/internal/linux/std.go b/internal/linux/std.go index 219997e..a82fff2 100644 --- a/internal/linux/std.go +++ b/internal/linux/std.go @@ -1,6 +1,7 @@ package linux import ( + "errors" "io" "io/fs" "os" @@ -8,6 +9,7 @@ import ( "os/user" "strconv" "sync" + "syscall" "git.gensokyo.uk/security/fortify/internal" "git.gensokyo.uk/security/fortify/internal/fmsg" @@ -79,9 +81,15 @@ func (s *Std) Uid(aid int) (int, error) { cmd.Stderr = os.Stderr // pass through fatal messages cmd.Env = []string{"FORTIFY_APP_ID=" + strconv.Itoa(aid)} cmd.Dir = "/" - var p []byte + var ( + p []byte + exitError *exec.ExitError + ) + if p, u.err = cmd.Output(); u.err == nil { u.uid, u.err = strconv.Atoi(string(p)) + } else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 { + u.err = syscall.EACCES } return u.uid, u.err } diff --git a/main.go b/main.go index 2449b68..e4f2860 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( _ "embed" "flag" "fmt" + "os" "os/user" "strconv" "strings" @@ -32,7 +33,7 @@ func init() { flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable") } -var os = new(linux.Std) +var sys linux.System = new(linux.Std) type gl []string @@ -64,7 +65,7 @@ func main() { fmt.Println("Usage:\tfortify [-v] [--json] COMMAND [OPTIONS]") fmt.Println() fmt.Println("Commands:") - w := tabwriter.NewWriter(os.Stdout(), 0, 1, 4, ' ', 0) + w := tabwriter.NewWriter(os.Stdout, 0, 1, 4, ' ', 0) commands := [][2]string{ {"app", "Launch app defined by the specified config file"}, {"run", "Configure and start a permissive default sandbox"}, @@ -206,7 +207,7 @@ func main() { passwdOnce sync.Once passwdFunc = func() { var us string - if uid, err := os.Uid(aid); err != nil { + if uid, err := sys.Uid(aid); err != nil { fmsg.Fatalf("cannot obtain uid from fsu: %v", err) } else { us = strconv.Itoa(uid) @@ -287,7 +288,7 @@ func main() { } func runApp(config *fst.Config) { - a, err := app.New(os) + a, err := app.New(sys) if err != nil { fmsg.Fatalf("cannot create app: %s\n", err) } else if err = a.Seal(config); err != nil { diff --git a/parse.go b/parse.go index 8be4a49..85dbfca 100644 --- a/parse.go +++ b/parse.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "io" - direct "os" + "os" "strconv" "strings" "syscall" @@ -38,7 +38,7 @@ func tryPath(name string) (config *fst.Config) { }() } } else { - r = direct.Stdin + r = os.Stdin } if err := json.NewDecoder(r).Decode(&config); err != nil { @@ -61,7 +61,7 @@ func tryFd(name string) io.ReadCloser { } fmsg.Fatalf("cannot get fd %d: %v", fd, errno) } - return direct.NewFile(fd, strconv.Itoa(v)) + return os.NewFile(fd, strconv.Itoa(v)) } } @@ -85,7 +85,7 @@ func tryShort(name string) (config *fst.Config, instance *state.State) { if likePrefix && len(name) >= 8 { fmsg.VPrintln("argument looks like prefix") - s := state.NewMulti(os.Paths().RunDirPath) + s := state.NewMulti(sys.Paths().RunDirPath) if entries, err := state.Join(s); err != nil { fmsg.Printf("cannot join store: %v", err) // drop to fetch from file diff --git a/print.go b/print.go index e78861f..ad23097 100644 --- a/print.go +++ b/print.go @@ -20,7 +20,7 @@ func printShowSystem(short bool) { info := new(fst.Info) // get fid by querying uid of aid 0 - if uid, err := os.Uid(0); err != nil { + if uid, err := sys.Uid(0); err != nil { fmsg.Fatalf("cannot obtain uid from fsu: %v", err) } else { info.User = (uid / 10000) - 100 @@ -194,7 +194,7 @@ func printPs(short bool) { now := time.Now().UTC() var entries state.Entries - s := state.NewMulti(os.Paths().RunDirPath) + s := state.NewMulti(sys.Paths().RunDirPath) if e, err := state.Join(s); err != nil { fmsg.Fatalf("cannot join store: %v", err) } else { diff --git a/test.nix b/test.nix index c7692ba..d25a187 100644 --- a/test.nix +++ b/test.nix @@ -19,11 +19,19 @@ nixosTest { nodes.machine = { lib, pkgs, ... }: { - users.users.alice = { - isNormalUser = true; - description = "Alice Foobar"; - password = "foobar"; - uid = 1000; + users.users = { + alice = { + isNormalUser = true; + description = "Alice Foobar"; + password = "foobar"; + uid = 1000; + }; + untrusted = { + isNormalUser = true; + description = "Untrusted user"; + password = "foobar"; + uid = 1001; + }; }; home-manager.users.alice.home.stateVersion = "24.11"; @@ -198,6 +206,9 @@ nixosTest { machine.wait_for_file("/run/user/1000/wayland-1") machine.wait_for_file("/tmp/sway-ipc.sock") + # Deny unmapped uid: + print(machine.fail("sudo -u untrusted -i ${self.packages.${system}.fortify}/bin/fortify -v run")) + # Create fortify uid 0 state directory: machine.succeed("install -dm 0755 -o u0_a0 -g users /var/lib/fortify/u0")