diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-02-08 13:03:45 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-02-08 13:03:45 +0900 |
| commit | e14923ae530dda0601c204fe17ac1956f54a45b0 (patch) | |
| tree | 1578bbc4c08d9afc94c4b009cd2bf64260574f05 /internal/proc | |
| parent | 7aff3ead3afaa7461e0054fbef23fb571b4add0a (diff) | |
helper/proc: move package out of internal
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/proc')
| -rw-r--r-- | internal/proc/fd.go | 45 | ||||
| -rw-r--r-- | internal/proc/files.go | 17 | ||||
| -rw-r--r-- | internal/proc/priv/init/early.go | 18 | ||||
| -rw-r--r-- | internal/proc/priv/init/main.go | 164 | ||||
| -rw-r--r-- | internal/proc/priv/init/payload.go | 13 | ||||
| -rw-r--r-- | internal/proc/priv/shim/main.go | 156 | ||||
| -rw-r--r-- | internal/proc/priv/shim/manager.go | 139 | ||||
| -rw-r--r-- | internal/proc/priv/shim/payload.go | 23 | ||||
| -rw-r--r-- | internal/proc/self.go | 26 |
9 files changed, 0 insertions, 601 deletions
diff --git a/internal/proc/fd.go b/internal/proc/fd.go deleted file mode 100644 index e99abb38..00000000 --- a/internal/proc/fd.go +++ /dev/null @@ -1,45 +0,0 @@ -package proc - -import ( - "encoding/gob" - "errors" - "os" - "strconv" -) - -var ( - ErrNotSet = errors.New("environment variable not set") - ErrInvalid = errors.New("bad file descriptor") -) - -// Setup appends the read end of a pipe for payload transmission and returns its fd. -func Setup(extraFiles *[]*os.File) (int, *gob.Encoder, error) { - if r, w, err := os.Pipe(); err != nil { - return -1, nil, err - } else { - fd := 3 + len(*extraFiles) - *extraFiles = append(*extraFiles, r) - return fd, gob.NewEncoder(w), nil - } -} - -// Receive retrieves payload pipe fd from the environment, -// receives its payload and returns the Close method of the pipe. -func Receive(key string, e any) (func() error, error) { - var setup *os.File - - if s, ok := os.LookupEnv(key); !ok { - return nil, ErrNotSet - } else { - if fd, err := strconv.Atoi(s); err != nil { - return nil, err - } else { - setup = os.NewFile(uintptr(fd), "setup") - if setup == nil { - return nil, ErrInvalid - } - } - } - - return func() error { return setup.Close() }, gob.NewDecoder(setup).Decode(e) -} diff --git a/internal/proc/files.go b/internal/proc/files.go deleted file mode 100644 index 712713a2..00000000 --- a/internal/proc/files.go +++ /dev/null @@ -1,17 +0,0 @@ -package proc - -import ( - "os" - "os/exec" -) - -func ExtraFile(cmd *exec.Cmd, f *os.File) (fd uintptr) { - return ExtraFileSlice(&cmd.ExtraFiles, f) -} - -func ExtraFileSlice(extraFiles *[]*os.File, f *os.File) (fd uintptr) { - // ExtraFiles: If non-nil, entry i becomes file descriptor 3+i. - fd = uintptr(3 + len(*extraFiles)) - *extraFiles = append(*extraFiles, f) - return -} diff --git a/internal/proc/priv/init/early.go b/internal/proc/priv/init/early.go deleted file mode 100644 index cc1c2f7e..00000000 --- a/internal/proc/priv/init/early.go +++ /dev/null @@ -1,18 +0,0 @@ -package init0 - -import ( - "os" - "path" - - "git.gensokyo.uk/security/fortify/internal/fmsg" -) - -// used by the parent process - -// TryArgv0 calls [Main] if argv0 indicates the process is started from a file named "init". -func TryArgv0() { - if len(os.Args) > 0 && path.Base(os.Args[0]) == "init" { - Main() - fmsg.Exit(0) - } -} diff --git a/internal/proc/priv/init/main.go b/internal/proc/priv/init/main.go deleted file mode 100644 index d9f57b3e..00000000 --- a/internal/proc/priv/init/main.go +++ /dev/null @@ -1,164 +0,0 @@ -package init0 - -import ( - "errors" - "os" - "os/exec" - "os/signal" - "syscall" - "time" - - "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/internal/proc" -) - -const ( - // time to wait for linger processes after death of initial process - residualProcessTimeout = 5 * time.Second -) - -// everything beyond this point runs within pid namespace -// proceed with caution! - -func Main() { - // sharing stdout with shim - // USE WITH CAUTION - fmsg.SetPrefix("init") - - // 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") - } - - if os.Getpid() != 1 { - fmsg.Fatal("this process must run as pid 1") - panic("unreachable") - } - - // 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_INIT not set") - } - - fmsg.Fatalf("cannot decode init setup payload: %v", err) - panic("unreachable") - } else { - fmsg.SetVerbose(payload.Verbose) - closeSetup = f - - // child does not need to see this - if err = os.Unsetenv(Env); err != nil { - fmsg.Printf("cannot unset %s: %v", Env, err) - // not fatal - } else { - fmsg.VPrintln("received configuration") - } - } - - // die with parent - if err := internal.PR_SET_PDEATHSIG__SIGKILL(); err != nil { - fmsg.Fatalf("prctl(PR_SET_PDEATHSIG, SIGKILL): %v", err) - } - - cmd := exec.Command(payload.Argv0) - cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr - cmd.Args = payload.Argv - cmd.Env = os.Environ() - - if err := cmd.Start(); err != nil { - fmsg.Fatalf("cannot start %q: %v", payload.Argv0, err) - } - fmsg.Suspend() - - // close setup pipe as setup is now complete - if err := closeSetup(); err != nil { - fmsg.Println("cannot close setup pipe:", err) - // not fatal - } - - sig := make(chan os.Signal, 2) - signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) - - type winfo struct { - wpid int - wstatus syscall.WaitStatus - } - info := make(chan winfo, 1) - done := make(chan struct{}) - - go func() { - var ( - err error - wpid = -2 - wstatus syscall.WaitStatus - ) - - // keep going until no child process is left - for wpid != -1 { - if err != nil { - break - } - - if wpid != -2 { - info <- winfo{wpid, wstatus} - } - - err = syscall.EINTR - for errors.Is(err, syscall.EINTR) { - wpid, err = syscall.Wait4(-1, &wstatus, 0, nil) - } - } - if !errors.Is(err, syscall.ECHILD) { - fmsg.Println("unexpected wait4 response:", err) - } - - close(done) - }() - - // closed after residualProcessTimeout has elapsed after initial process death - timeout := make(chan struct{}) - - r := 2 - for { - select { - case s := <-sig: - fmsg.VPrintln("received", s.String()) - fmsg.Resume() // output could still be withheld at this point, so resume is called - fmsg.Exit(0) - case w := <-info: - if w.wpid == cmd.Process.Pid { - // initial process exited, output is most likely available again - fmsg.Resume() - - switch { - case w.wstatus.Exited(): - r = w.wstatus.ExitStatus() - case w.wstatus.Signaled(): - r = 128 + int(w.wstatus.Signal()) - default: - r = 255 - } - - go func() { - time.Sleep(residualProcessTimeout) - close(timeout) - }() - } - case <-done: - fmsg.Exit(r) - case <-timeout: - fmsg.Println("timeout exceeded waiting for lingering processes") - fmsg.Exit(r) - } - } -} diff --git a/internal/proc/priv/init/payload.go b/internal/proc/priv/init/payload.go deleted file mode 100644 index d1dc9ec3..00000000 --- a/internal/proc/priv/init/payload.go +++ /dev/null @@ -1,13 +0,0 @@ -package init0 - -const Env = "FORTIFY_INIT" - -type Payload struct { - // target full exec path - Argv0 string - // child full argv - Argv []string - - // verbosity pass through - Verbose bool -} diff --git a/internal/proc/priv/shim/main.go b/internal/proc/priv/shim/main.go deleted file mode 100644 index 7ad0a44b..00000000 --- a/internal/proc/priv/shim/main.go +++ /dev/null @@ -1,156 +0,0 @@ -package shim - -import ( - "errors" - "os" - "path" - "strconv" - - "git.gensokyo.uk/security/fortify/fst" - "git.gensokyo.uk/security/fortify/helper" - "git.gensokyo.uk/security/fortify/helper/seccomp" - "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/internal/proc" - init0 "git.gensokyo.uk/security/fortify/internal/proc/priv/init" -) - -// 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") - } - - // 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 - var syncFd *os.File - if payload.Sync != nil { - syncFd = 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 fortify inside sandbox - var ( - innerSbin = path.Join(fst.Tmp, "sbin") - innerFortify = path.Join(innerSbin, "fortify") - innerInit = path.Join(innerSbin, "init") - ) - conf.Bind(proc.MustExecutable(), innerFortify) - conf.Symlink("fortify", innerInit) - - helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent - if fmsg.Verbose() { - seccomp.CPrintln = fmsg.Println - } - if b, err := helper.NewBwrap( - conf, innerInit, - nil, func(int, int) []string { return make([]string, 0) }, - extraFiles, - syncFd, - ); 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 - - // 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 deleted file mode 100644 index 064f5c87..00000000 --- a/internal/proc/priv/shim/manager.go +++ /dev/null @@ -1,139 +0,0 @@ -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 - // fallback exit notifier with error returned killing the process - killFallback chan error - // monitor to shim encoder - encoder *gob.Encoder - // bwrap --sync-fd value - sync *uintptr -} - -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( - // string representation of application id - aid string, - // string representation of supplementary group ids - supp []string, - // bwrap --sync-fd - syncFd *os.File, -) (*time.Time, error) { - // prepare user switcher invocation - var fsu string - if p, ok := internal.Path(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=" + aid, - } - } - - // format fsu supplementary groups - if len(supp) > 0 { - fmsg.VPrintf("attaching supplementary group ids %s", supp) - s.cmd.Env = append(s.cmd.Env, "FORTIFY_GROUPS="+strings.Join(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 syncFd != nil { - fd := proc.ExtraFile(s.cmd, syncFd) - s.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, payload *Payload) 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() }() - - payload.Sync = s.sync - encodeErr := make(chan error) - go func() { encodeErr <- s.encoder.Encode(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 deleted file mode 100644 index e659e3fd..00000000 --- a/internal/proc/priv/shim/payload.go +++ /dev/null @@ -1,23 +0,0 @@ -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/internal/proc/self.go b/internal/proc/self.go deleted file mode 100644 index 6dc92eb7..00000000 --- a/internal/proc/self.go +++ /dev/null @@ -1,26 +0,0 @@ -package proc - -import ( - "os" - "sync" - - "git.gensokyo.uk/security/fortify/internal/fmsg" -) - -var ( - executable string - executableOnce sync.Once -) - -func copyExecutable() { - if name, err := os.Executable(); err != nil { - fmsg.Fatalf("cannot read executable path: %v", err) - } else { - executable = name - } -} - -func MustExecutable() string { - executableOnce.Do(copyExecutable) - return executable -} |
