From ea8f228af36b2c9ae9918f7760c3af4f1e21cfa2 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Fri, 17 Jan 2025 23:43:32 +0900 Subject: proc/priv/shim: merge shim into main program Signed-off-by: Ophestra --- cmd/fpkg/with.go | 2 +- cmd/fshim/ipc/payload.go | 23 ------ cmd/fshim/ipc/shim/shim.go | 142 ------------------------------- cmd/fshim/main.go | 165 ------------------------------------- cmd/fsu/main.go | 10 +-- dist/install.sh | 1 - dist/release.sh | 3 +- internal/app/app.go | 4 +- internal/app/start.go | 5 +- internal/linux/std.go | 2 +- internal/proc/priv/shim/main.go | 164 ++++++++++++++++++++++++++++++++++++ internal/proc/priv/shim/manager.go | 141 +++++++++++++++++++++++++++++++ internal/proc/priv/shim/payload.go | 21 +++++ main.go | 7 ++ package.nix | 1 - 15 files changed, 342 insertions(+), 349 deletions(-) delete mode 100644 cmd/fshim/ipc/payload.go delete mode 100644 cmd/fshim/ipc/shim/shim.go delete mode 100644 cmd/fshim/main.go create mode 100644 internal/proc/priv/shim/main.go create mode 100644 internal/proc/priv/shim/manager.go create mode 100644 internal/proc/priv/shim/payload.go diff --git a/cmd/fpkg/with.go b/cmd/fpkg/with.go index e4bb7299..53313671 100644 --- a/cmd/fpkg/with.go +++ b/cmd/fpkg/with.go @@ -62,7 +62,7 @@ func withCacheDir(action string, command []string, workDir string, app *bundleIn AppID: app.AppID, Username: "nixos", Inner: path.Join("/data/data", app.ID, "cache"), - Outer: pathSet.cacheDir, // this also ensures cacheDir via fshim + Outer: pathSet.cacheDir, // this also ensures cacheDir via shim Sandbox: &fst.SandboxConfig{ Hostname: formatHostname(app.Name) + "-" + action, NoNewSession: dropShell, diff --git a/cmd/fshim/ipc/payload.go b/cmd/fshim/ipc/payload.go deleted file mode 100644 index 92e51e8c..00000000 --- a/cmd/fshim/ipc/payload.go +++ /dev/null @@ -1,23 +0,0 @@ -package shim0 - -import ( - "git.gensokyo.uk/security/fortify/helper/bwrap" -) - -const Env = "FORTIFY_SHIM" - -type Payload struct { - // child full argv - Argv []string - // bwrap, target full exec path - Exec [2]string - // bwrap config - Bwrap *bwrap.Config - // path to outer home directory - Home string - // sync fd - Sync *uintptr - - // verbosity pass through - Verbose bool -} diff --git a/cmd/fshim/ipc/shim/shim.go b/cmd/fshim/ipc/shim/shim.go deleted file mode 100644 index 4f60531b..00000000 --- a/cmd/fshim/ipc/shim/shim.go +++ /dev/null @@ -1,142 +0,0 @@ -package shim - -import ( - "context" - "encoding/gob" - "errors" - "os" - "os/exec" - "strconv" - "strings" - "time" - - shim0 "git.gensokyo.uk/security/fortify/cmd/fshim/ipc" - "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/internal/proc" -) - -// used by the parent process - -type Shim struct { - // user switcher process - cmd *exec.Cmd - // uid of shim target user - uid uint32 - // string representation of application id - aid string - // string representation of supplementary group ids - supp []string - // fallback exit notifier with error returned killing the process - killFallback chan error - // shim setup payload - payload *shim0.Payload - // monitor to shim encoder - encoder *gob.Encoder -} - -func New(uid uint32, aid string, supp []string, payload *shim0.Payload) *Shim { - return &Shim{uid: uid, aid: aid, supp: supp, payload: payload} -} - -func (s *Shim) String() string { - if s.cmd == nil { - return "(unused shim manager)" - } - return s.cmd.String() -} - -func (s *Shim) Unwrap() *exec.Cmd { - return s.cmd -} - -func (s *Shim) WaitFallback() chan error { - return s.killFallback -} - -func (s *Shim) Start() (*time.Time, error) { - // prepare user switcher invocation - var fsu string - if p, ok := internal.Check(internal.Fsu); !ok { - fmsg.Fatal("invalid fsu path, this copy of fshim is not compiled correctly") - panic("unreachable") - } else { - fsu = p - } - s.cmd = exec.Command(fsu) - - // pass shim setup pipe - if fd, e, err := proc.Setup(&s.cmd.ExtraFiles); err != nil { - return nil, fmsg.WrapErrorSuffix(err, - "cannot create shim setup pipe:") - } else { - s.encoder = e - s.cmd.Env = []string{ - shim0.Env + "=" + strconv.Itoa(fd), - "FORTIFY_APP_ID=" + s.aid, - } - } - - // format fsu supplementary groups - if len(s.supp) > 0 { - fmsg.VPrintf("attaching supplementary group ids %s", s.supp) - s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(s.supp, " ")) - } - s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr - s.cmd.Dir = "/" - - // pass sync fd if set - if s.payload.Bwrap.Sync() != nil { - fd := proc.ExtraFile(s.cmd, s.payload.Bwrap.Sync()) - s.payload.Sync = &fd - } - - fmsg.VPrintln("starting shim via fsu:", s.cmd) - // withhold messages to stderr - fmsg.Suspend() - if err := s.cmd.Start(); err != nil { - return nil, fmsg.WrapErrorSuffix(err, - "cannot start fsu:") - } - startTime := time.Now().UTC() - return &startTime, nil -} - -func (s *Shim) Serve(ctx context.Context) error { - // kill shim if something goes wrong and an error is returned - s.killFallback = make(chan error, 1) - killShim := func() { - if err := s.cmd.Process.Signal(os.Interrupt); err != nil { - s.killFallback <- err - } - } - defer func() { killShim() }() - - encodeErr := make(chan error) - go func() { encodeErr <- s.encoder.Encode(s.payload) }() - - select { - // encode return indicates setup completion - case err := <-encodeErr: - if err != nil { - return fmsg.WrapErrorSuffix(err, - "cannot transmit shim config:") - } - killShim = func() {} - return nil - - // setup canceled before payload was accepted - case <-ctx.Done(): - err := ctx.Err() - if errors.Is(err, context.Canceled) { - return fmsg.WrapError(errors.New("shim setup canceled"), - "shim setup canceled") - } - if errors.Is(err, context.DeadlineExceeded) { - return fmsg.WrapError(errors.New("deadline exceeded waiting for shim"), - "deadline exceeded waiting for shim") - } - // unreachable - return err - } -} diff --git a/cmd/fshim/main.go b/cmd/fshim/main.go deleted file mode 100644 index 5aece38f..00000000 --- a/cmd/fshim/main.go +++ /dev/null @@ -1,165 +0,0 @@ -package main - -import ( - "errors" - "os" - "path" - "strconv" - "syscall" - - init0 "git.gensokyo.uk/security/fortify/cmd/finit/ipc" - shim "git.gensokyo.uk/security/fortify/cmd/fshim/ipc" - "git.gensokyo.uk/security/fortify/fst" - "git.gensokyo.uk/security/fortify/helper" - "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/internal/proc" -) - -// everything beyond this point runs as unconstrained target user -// proceed with caution! - -func main() { - // sharing stdout with fortify - // USE WITH CAUTION - fmsg.SetPrefix("shim") - - // setting this prevents ptrace - if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { - fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) - panic("unreachable") - } - - // re-exec - if len(os.Args) > 0 && (os.Args[0] != "fshim" || len(os.Args) != 1) && path.IsAbs(os.Args[0]) { - if err := syscall.Exec(os.Args[0], []string{"fshim"}, os.Environ()); err != nil { - fmsg.Println("cannot re-exec self:", err) - // continue anyway - } - } - - // check path to finit - var finitPath string - if p, ok := internal.Path(internal.Finit); !ok { - fmsg.Fatal("invalid finit path, this copy of fshim is not compiled correctly") - } else { - finitPath = p - } - - // receive setup payload - var ( - payload shim.Payload - closeSetup func() error - ) - if f, err := proc.Receive(shim.Env, &payload); err != nil { - if errors.Is(err, proc.ErrInvalid) { - fmsg.Fatal("invalid config descriptor") - } - if errors.Is(err, proc.ErrNotSet) { - fmsg.Fatal("FORTIFY_SHIM not set") - } - - fmsg.Fatalf("cannot decode shim setup payload: %v", err) - panic("unreachable") - } else { - fmsg.SetVerbose(payload.Verbose) - closeSetup = f - } - - if payload.Bwrap == nil { - fmsg.Fatal("bwrap config not supplied") - } - - // restore bwrap sync fd - if payload.Sync != nil { - payload.Bwrap.SetSync(os.NewFile(*payload.Sync, "sync")) - } - - // close setup socket - if err := closeSetup(); err != nil { - fmsg.Println("cannot close setup pipe:", err) - // not fatal - } - - // ensure home directory as target user - if s, err := os.Stat(payload.Home); err != nil { - if os.IsNotExist(err) { - if err = os.Mkdir(payload.Home, 0700); err != nil { - fmsg.Fatalf("cannot create home directory: %v", err) - } - } else { - fmsg.Fatalf("cannot access home directory: %v", err) - } - - // home directory is created, proceed - } else if !s.IsDir() { - fmsg.Fatalf("data path %q is not a directory", payload.Home) - } - - var ic init0.Payload - - // resolve argv0 - ic.Argv = payload.Argv - if len(ic.Argv) > 0 { - // looked up from $PATH by parent - ic.Argv0 = payload.Exec[1] - } else { - // no argv, look up shell instead - var ok bool - if payload.Bwrap.SetEnv == nil { - fmsg.Fatal("no command was specified and environment is unset") - } - if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok { - fmsg.Fatal("no command was specified and $SHELL was unset") - } - - ic.Argv = []string{ic.Argv0} - } - - conf := payload.Bwrap - - var extraFiles []*os.File - - // serve setup payload - if fd, encoder, err := proc.Setup(&extraFiles); err != nil { - fmsg.Fatalf("cannot pipe: %v", err) - } else { - conf.SetEnv[init0.Env] = strconv.Itoa(fd) - go func() { - fmsg.VPrintln("transmitting config to init") - if err = encoder.Encode(&ic); err != nil { - fmsg.Fatalf("cannot transmit init config: %v", err) - } - }() - } - - // bind finit inside sandbox - finitInnerPath := path.Join(fst.Tmp, "sbin", "init") - conf.Bind(finitPath, finitInnerPath) - - helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent - if b, err := helper.NewBwrap(conf, nil, finitInnerPath, - func(int, int) []string { return make([]string, 0) }); err != nil { - fmsg.Fatalf("malformed sandbox config: %v", err) - } else { - cmd := b.Unwrap() - cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr - cmd.ExtraFiles = extraFiles - - if fmsg.Verbose() { - fmsg.VPrintln("bwrap args:", conf.Args()) - } - - // run and pass through exit code - if err = b.Start(); err != nil { - fmsg.Fatalf("cannot start target process: %v", err) - } else if err = b.Wait(); err != nil { - fmsg.VPrintln("wait:", err) - } - if b.Unwrap().ProcessState != nil { - fmsg.Exit(b.Unwrap().ProcessState.ExitCode()) - } else { - fmsg.Exit(127) - } - } -} diff --git a/cmd/fsu/main.go b/cmd/fsu/main.go index 2412a0e6..1f90a390 100644 --- a/cmd/fsu/main.go +++ b/cmd/fsu/main.go @@ -24,7 +24,6 @@ const ( var ( Fmain = compPoison - Fshim = compPoison ) func main() { @@ -41,17 +40,12 @@ func main() { log.Fatal("this program must not be started by root") } - var fmain, fshim string + var fmain string if p, ok := checkPath(Fmain); !ok { log.Fatal("invalid fortify path, this copy of fsu is not compiled correctly") } else { fmain = p } - if p, ok := checkPath(Fshim); !ok { - log.Fatal("invalid fshim path, this copy of fsu is not compiled correctly") - } else { - fshim = p - } pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe") if p, err := os.Readlink(pexe); err != nil { @@ -142,7 +136,7 @@ func main() { 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(fshim, []string{"fshim"}, []string{envShim + "=" + shimSetupFd}); err != nil { + if err := syscall.Exec(fmain, []string{"fortify", "shim"}, []string{envShim + "=" + shimSetupFd}); err != nil { log.Fatalf("cannot start shim: %v", err) } diff --git a/dist/install.sh b/dist/install.sh index cb0349c8..c881507f 100755 --- a/dist/install.sh +++ b/dist/install.sh @@ -4,7 +4,6 @@ cd "$(dirname -- "$0")" || exit 1 install -vDm0755 "bin/fortify" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fortify" install -vDm0755 "bin/fpkg" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fpkg" -install -vDm0755 "bin/fshim" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fshim" install -vDm0755 "bin/finit" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/finit" install -vDm0755 "bin/fuserdb" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fuserdb" diff --git a/dist/release.sh b/dist/release.sh index 15d7d9f2..1ebf6e35 100755 --- a/dist/release.sh +++ b/dist/release.sh @@ -14,8 +14,7 @@ go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-s -X git.gensokyo.uk/security/fortify/internal.Fortify=/usr/bin/fortify -X git.gensokyo.uk/security/fortify/internal.Fsu=/usr/bin/fsu -X git.gensokyo.uk/security/fortify/internal.Finit=/usr/libexec/fortify/finit - -X main.Fmain=/usr/bin/fortify - -X main.Fshim=/usr/libexec/fortify/fshim" ./... + -X main.Fmain=/usr/bin/fortify" ./... rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}" rm -rf "./${out}" diff --git a/internal/app/app.go b/internal/app/app.go index 175c315a..0da8148b 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -5,9 +5,9 @@ import ( "sync" "sync/atomic" - "git.gensokyo.uk/security/fortify/cmd/fshim/ipc/shim" "git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/internal/linux" + "git.gensokyo.uk/security/fortify/internal/proc/priv/shim" ) type App interface { @@ -23,7 +23,7 @@ type App interface { type RunState struct { // Start is true if fsu is successfully started. Start bool - // ExitCode is the value returned by fshim. + // ExitCode is the value returned by shim. ExitCode int // WaitErr is error returned by the underlying wait syscall. WaitErr error diff --git a/internal/app/start.go b/internal/app/start.go index 6fffda55..630f5194 100644 --- a/internal/app/start.go +++ b/internal/app/start.go @@ -9,10 +9,9 @@ import ( "strings" "time" - shim0 "git.gensokyo.uk/security/fortify/cmd/fshim/ipc" - "git.gensokyo.uk/security/fortify/cmd/fshim/ipc/shim" "git.gensokyo.uk/security/fortify/helper" "git.gensokyo.uk/security/fortify/internal/fmsg" + "git.gensokyo.uk/security/fortify/internal/proc/priv/shim" "git.gensokyo.uk/security/fortify/internal/state" "git.gensokyo.uk/security/fortify/internal/system" ) @@ -51,7 +50,7 @@ func (a *app) Run(ctx context.Context, rs *RunState) error { uint32(a.seal.sys.UID()), a.seal.sys.user.as, a.seal.sys.user.supp, - &shim0.Payload{ + &shim.Payload{ Argv: a.seal.command, Exec: shimExec, Bwrap: a.seal.sys.bwrap, diff --git a/internal/linux/std.go b/internal/linux/std.go index 7c6b9279..08f031ed 100644 --- a/internal/linux/std.go +++ b/internal/linux/std.go @@ -73,7 +73,7 @@ func (s *Std) Uid(aid int) (int, error) { u.uid = -1 if fsu, ok := internal.Check(internal.Fsu); !ok { - fmsg.Fatal("invalid fsu path, this copy of fshim is not compiled correctly") + fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly") panic("unreachable") } else { cmd := exec.Command(fsu) diff --git a/internal/proc/priv/shim/main.go b/internal/proc/priv/shim/main.go new file mode 100644 index 00000000..e3a819d6 --- /dev/null +++ b/internal/proc/priv/shim/main.go @@ -0,0 +1,164 @@ +package shim + +import ( + "errors" + "os" + "path" + "strconv" + "syscall" + + init0 "git.gensokyo.uk/security/fortify/cmd/finit/ipc" + "git.gensokyo.uk/security/fortify/fst" + "git.gensokyo.uk/security/fortify/helper" + "git.gensokyo.uk/security/fortify/internal" + "git.gensokyo.uk/security/fortify/internal/fmsg" + "git.gensokyo.uk/security/fortify/internal/proc" +) + +// everything beyond this point runs as unconstrained target user +// proceed with caution! + +func Main() { + // sharing stdout with fortify + // USE WITH CAUTION + fmsg.SetPrefix("shim") + + // setting this prevents ptrace + if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil { + fmsg.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) + panic("unreachable") + } + + // re-exec + 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 { + fmsg.Println("cannot re-exec self:", err) + // continue anyway + } + } + + // check path to finit + var finitPath string + if p, ok := internal.Path(internal.Finit); !ok { + fmsg.Fatal("invalid finit path, this copy of fortify is not compiled correctly") + } else { + finitPath = p + } + + // receive setup payload + var ( + payload Payload + closeSetup func() error + ) + if f, err := proc.Receive(Env, &payload); err != nil { + if errors.Is(err, proc.ErrInvalid) { + fmsg.Fatal("invalid config descriptor") + } + if errors.Is(err, proc.ErrNotSet) { + fmsg.Fatal("FORTIFY_SHIM not set") + } + + fmsg.Fatalf("cannot decode shim setup payload: %v", err) + panic("unreachable") + } else { + fmsg.SetVerbose(payload.Verbose) + closeSetup = f + } + + if payload.Bwrap == nil { + fmsg.Fatal("bwrap config not supplied") + } + + // restore bwrap sync fd + if payload.Sync != nil { + payload.Bwrap.SetSync(os.NewFile(*payload.Sync, "sync")) + } + + // close setup socket + if err := closeSetup(); err != nil { + fmsg.Println("cannot close setup pipe:", err) + // not fatal + } + + // ensure home directory as target user + if s, err := os.Stat(payload.Home); err != nil { + if os.IsNotExist(err) { + if err = os.Mkdir(payload.Home, 0700); err != nil { + fmsg.Fatalf("cannot create home directory: %v", err) + } + } else { + fmsg.Fatalf("cannot access home directory: %v", err) + } + + // home directory is created, proceed + } else if !s.IsDir() { + fmsg.Fatalf("data path %q is not a directory", payload.Home) + } + + var ic init0.Payload + + // resolve argv0 + ic.Argv = payload.Argv + if len(ic.Argv) > 0 { + // looked up from $PATH by parent + ic.Argv0 = payload.Exec[1] + } else { + // no argv, look up shell instead + var ok bool + if payload.Bwrap.SetEnv == nil { + fmsg.Fatal("no command was specified and environment is unset") + } + if ic.Argv0, ok = payload.Bwrap.SetEnv["SHELL"]; !ok { + fmsg.Fatal("no command was specified and $SHELL was unset") + } + + ic.Argv = []string{ic.Argv0} + } + + conf := payload.Bwrap + + var extraFiles []*os.File + + // serve setup payload + if fd, encoder, err := proc.Setup(&extraFiles); err != nil { + fmsg.Fatalf("cannot pipe: %v", err) + } else { + conf.SetEnv[init0.Env] = strconv.Itoa(fd) + go func() { + fmsg.VPrintln("transmitting config to init") + if err = encoder.Encode(&ic); err != nil { + fmsg.Fatalf("cannot transmit init config: %v", err) + } + }() + } + + // bind finit inside sandbox + finitInnerPath := path.Join(fst.Tmp, "sbin", "init") + conf.Bind(finitPath, finitInnerPath) + + helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent + if b, err := helper.NewBwrap(conf, nil, finitInnerPath, + func(int, int) []string { return make([]string, 0) }); err != nil { + fmsg.Fatalf("malformed sandbox config: %v", err) + } else { + cmd := b.Unwrap() + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + cmd.ExtraFiles = extraFiles + + if fmsg.Verbose() { + fmsg.VPrintln("bwrap args:", conf.Args()) + } + + // run and pass through exit code + if err = b.Start(); err != nil { + fmsg.Fatalf("cannot start target process: %v", err) + } else if err = b.Wait(); err != nil { + fmsg.VPrintln("wait:", err) + } + if b.Unwrap().ProcessState != nil { + fmsg.Exit(b.Unwrap().ProcessState.ExitCode()) + } else { + fmsg.Exit(127) + } + } +} diff --git a/internal/proc/priv/shim/manager.go b/internal/proc/priv/shim/manager.go new file mode 100644 index 00000000..26b54925 --- /dev/null +++ b/internal/proc/priv/shim/manager.go @@ -0,0 +1,141 @@ +package shim + +import ( + "context" + "encoding/gob" + "errors" + "os" + "os/exec" + "strconv" + "strings" + "time" + + "git.gensokyo.uk/security/fortify/internal" + "git.gensokyo.uk/security/fortify/internal/fmsg" + "git.gensokyo.uk/security/fortify/internal/proc" +) + +// used by the parent process + +type Shim struct { + // user switcher process + cmd *exec.Cmd + // uid of shim target user + uid uint32 + // string representation of application id + aid string + // string representation of supplementary group ids + supp []string + // fallback exit notifier with error returned killing the process + killFallback chan error + // shim setup payload + payload *Payload + // monitor to shim encoder + encoder *gob.Encoder +} + +func New(uid uint32, aid string, supp []string, payload *Payload) *Shim { + return &Shim{uid: uid, aid: aid, supp: supp, payload: payload} +} + +func (s *Shim) String() string { + if s.cmd == nil { + return "(unused shim manager)" + } + return s.cmd.String() +} + +func (s *Shim) Unwrap() *exec.Cmd { + return s.cmd +} + +func (s *Shim) WaitFallback() chan error { + return s.killFallback +} + +func (s *Shim) Start() (*time.Time, error) { + // prepare user switcher invocation + var fsu string + if p, ok := internal.Check(internal.Fsu); !ok { + fmsg.Fatal("invalid fsu path, this copy of fortify is not compiled correctly") + panic("unreachable") + } else { + fsu = p + } + s.cmd = exec.Command(fsu) + + // pass shim setup pipe + if fd, e, err := proc.Setup(&s.cmd.ExtraFiles); err != nil { + return nil, fmsg.WrapErrorSuffix(err, + "cannot create shim setup pipe:") + } else { + s.encoder = e + s.cmd.Env = []string{ + Env + "=" + strconv.Itoa(fd), + "FORTIFY_APP_ID=" + s.aid, + } + } + + // format fsu supplementary groups + if len(s.supp) > 0 { + fmsg.VPrintf("attaching supplementary group ids %s", s.supp) + s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(s.supp, " ")) + } + s.cmd.Stdin, s.cmd.Stdout, s.cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + s.cmd.Dir = "/" + + // pass sync fd if set + if s.payload.Bwrap.Sync() != nil { + fd := proc.ExtraFile(s.cmd, s.payload.Bwrap.Sync()) + s.payload.Sync = &fd + } + + fmsg.VPrintln("starting shim via fsu:", s.cmd) + // withhold messages to stderr + fmsg.Suspend() + if err := s.cmd.Start(); err != nil { + return nil, fmsg.WrapErrorSuffix(err, + "cannot start fsu:") + } + startTime := time.Now().UTC() + return &startTime, nil +} + +func (s *Shim) Serve(ctx context.Context) error { + // kill shim if something goes wrong and an error is returned + s.killFallback = make(chan error, 1) + killShim := func() { + if err := s.cmd.Process.Signal(os.Interrupt); err != nil { + s.killFallback <- err + } + } + defer func() { killShim() }() + + encodeErr := make(chan error) + go func() { encodeErr <- s.encoder.Encode(s.payload) }() + + select { + // encode return indicates setup completion + case err := <-encodeErr: + if err != nil { + return fmsg.WrapErrorSuffix(err, + "cannot transmit shim config:") + } + killShim = func() {} + return nil + + // setup canceled before payload was accepted + case <-ctx.Done(): + err := ctx.Err() + if errors.Is(err, context.Canceled) { + return fmsg.WrapError(errors.New("shim setup canceled"), + "shim setup canceled") + } + if errors.Is(err, context.DeadlineExceeded) { + return fmsg.WrapError(errors.New("deadline exceeded waiting for shim"), + "deadline exceeded waiting for shim") + } + // unreachable + return err + } +} diff --git a/internal/proc/priv/shim/payload.go b/internal/proc/priv/shim/payload.go new file mode 100644 index 00000000..e9425031 --- /dev/null +++ b/internal/proc/priv/shim/payload.go @@ -0,0 +1,21 @@ +package shim + +import "git.gensokyo.uk/security/fortify/helper/bwrap" + +const Env = "FORTIFY_SHIM" + +type Payload struct { + // child full argv + Argv []string + // bwrap, target full exec path + Exec [2]string + // bwrap config + Bwrap *bwrap.Config + // path to outer home directory + Home string + // sync fd + Sync *uintptr + + // verbosity pass through + Verbose bool +} diff --git a/main.go b/main.go index 197dd1e7..21043ea1 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "git.gensokyo.uk/security/fortify/internal/app" "git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/linux" + "git.gensokyo.uk/security/fortify/internal/proc/priv/shim" "git.gensokyo.uk/security/fortify/internal/system" ) @@ -283,6 +284,12 @@ func main() { // invoke app runApp(config) + + // internal commands + case "shim": + shim.Main() + fmsg.Exit(0) + default: fmsg.Fatalf("%q is not a valid command", args[0]) } diff --git a/package.nix b/package.nix index 0141dda6..0da2e6e1 100644 --- a/package.nix +++ b/package.nix @@ -33,7 +33,6 @@ buildGoModule rec { [ "-s -w" "-X main.Fmain=${placeholder "out"}/libexec/fortify" - "-X main.Fshim=${placeholder "out"}/libexec/fshim" ] { Version = "v${version}"; -- cgit v1.3.1