diff options
| author | Ophestra <cat@gensokyo.uk> | 2025-03-25 05:21:47 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2025-03-25 05:21:47 +0900 |
| commit | 532feb4bfaa84a57dc8de97f36b13a89610a0584 (patch) | |
| tree | 8883b79734a219a8abff067b120797c0bb68132a /internal | |
| parent | ec5e91b8c97f02707a70a789ba4af84d51394ea5 (diff) | |
app: merge shim into app package
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/app/process.go | 5 | ||||
| -rw-r--r-- | internal/app/shim.go | 212 | ||||
| -rw-r--r-- | internal/app/shim/main.go | 115 | ||||
| -rw-r--r-- | internal/app/shim/proc.go | 117 |
4 files changed, 214 insertions, 235 deletions
diff --git a/internal/app/process.go b/internal/app/process.go index 00f66432..69a66e51 100644 --- a/internal/app/process.go +++ b/internal/app/process.go @@ -9,7 +9,6 @@ import ( "git.gensokyo.uk/security/fortify/fst" "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/internal/app/shim" "git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/state" "git.gensokyo.uk/security/fortify/system" @@ -95,7 +94,7 @@ func (seal *outcome) Run(rs *fst.RunState) error { */ waitErr := make(chan error, 1) - cmd := new(shim.Shim) + cmd := new(shimProcess) if startTime, err := cmd.Start( seal.user.aid.String(), seal.user.supp, @@ -115,7 +114,7 @@ func (seal *outcome) Run(rs *fst.RunState) error { cancel() }() - if err := cmd.Serve(ctx, &shim.Params{ + if err := cmd.Serve(ctx, &shimParams{ Container: seal.container, Home: seal.user.data, diff --git a/internal/app/shim.go b/internal/app/shim.go new file mode 100644 index 00000000..0c12606e --- /dev/null +++ b/internal/app/shim.go @@ -0,0 +1,212 @@ +package app + +import ( + "context" + "encoding/gob" + "errors" + "log" + "os" + "os/exec" + "os/signal" + "strconv" + "strings" + "syscall" + "time" + + "git.gensokyo.uk/security/fortify/internal" + "git.gensokyo.uk/security/fortify/internal/fmsg" + "git.gensokyo.uk/security/fortify/sandbox" +) + +const shimEnv = "FORTIFY_SHIM" + +type shimParams struct { + // finalised container params + Container *sandbox.Params + // path to outer home directory + Home string + + // verbosity pass through + Verbose bool +} + +// ShimMain is the main function of the shim process and runs as the unconstrained target user. +func ShimMain() { + fmsg.Prepare("shim") + + if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { + log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) + } + + var ( + params shimParams + closeSetup func() error + ) + if f, err := sandbox.Receive(shimEnv, ¶ms, nil); err != nil { + if errors.Is(err, sandbox.ErrInvalid) { + log.Fatal("invalid config descriptor") + } + if errors.Is(err, sandbox.ErrNotSet) { + log.Fatal("FORTIFY_SHIM not set") + } + + log.Fatalf("cannot receive shim setup params: %v", err) + } else { + internal.InstallFmsg(params.Verbose) + closeSetup = f + } + + if params.Container == nil || params.Container.Ops == nil { + log.Fatal("invalid container params") + } + + // close setup socket + if err := closeSetup(); err != nil { + log.Printf("cannot close setup pipe: %v", err) + // not fatal + } + + // ensure home directory as target user + if s, err := os.Stat(params.Home); err != nil { + if os.IsNotExist(err) { + if err = os.Mkdir(params.Home, 0700); err != nil { + log.Fatalf("cannot create home directory: %v", err) + } + } else { + log.Fatalf("cannot access home directory: %v", err) + } + + // home directory is created, proceed + } else if !s.IsDir() { + log.Fatalf("path %q is not a directory", params.Home) + } + + var name string + if len(params.Container.Args) > 0 { + name = params.Container.Args[0] + } + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() // unreachable + container := sandbox.New(ctx, name) + container.Params = *params.Container + container.Stdin, container.Stdout, container.Stderr = os.Stdin, os.Stdout, os.Stderr + container.Cancel = func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) } + container.WaitDelay = 2 * time.Second + + if err := container.Start(); err != nil { + fmsg.PrintBaseError(err, "cannot start container:") + os.Exit(1) + } + if err := container.Serve(); err != nil { + fmsg.PrintBaseError(err, "cannot configure container:") + } + if err := container.Wait(); err != nil { + var exitError *exec.ExitError + if !errors.As(err, &exitError) { + if errors.Is(err, context.Canceled) { + os.Exit(2) + } + log.Printf("wait: %v", err) + os.Exit(127) + } + os.Exit(exitError.ExitCode()) + } +} + +type shimProcess 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 +} + +func (s *shimProcess) Unwrap() *exec.Cmd { return s.cmd } +func (s *shimProcess) Fallback() chan error { return s.killFallback } + +func (s *shimProcess) String() string { + if s.cmd == nil { + return "(unused shim manager)" + } + return s.cmd.String() +} + +func (s *shimProcess) Start( + aid string, + supp []string, +) (*time.Time, error) { + // prepare user switcher invocation + fsuPath := internal.MustFsuPath() + s.cmd = exec.Command(fsuPath) + + // pass shim setup pipe + if fd, e, err := sandbox.Setup(&s.cmd.ExtraFiles); err != nil { + return nil, fmsg.WrapErrorSuffix(err, + "cannot create shim setup pipe:") + } else { + s.encoder = e + s.cmd.Env = []string{ + shimEnv + "=" + strconv.Itoa(fd), + "FORTIFY_APP_ID=" + aid, + } + } + + // format fsu supplementary groups + if len(supp) > 0 { + fmsg.Verbosef("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 = "/" + + fmsg.Verbose("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 *shimProcess) Serve(ctx context.Context, params *shimParams) 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(params) }() + + 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(syscall.ECANCELED, + "shim setup canceled") + } + if errors.Is(err, context.DeadlineExceeded) { + return fmsg.WrapError(syscall.ETIMEDOUT, + "deadline exceeded waiting for shim") + } + // unreachable + return err + } +} diff --git a/internal/app/shim/main.go b/internal/app/shim/main.go deleted file mode 100644 index 17f346a2..00000000 --- a/internal/app/shim/main.go +++ /dev/null @@ -1,115 +0,0 @@ -package shim - -import ( - "context" - "errors" - "log" - "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/sandbox" -) - -const Env = "FORTIFY_SHIM" - -type Params struct { - // finalised container params - Container *sandbox.Params - // path to outer home directory - Home string - - // verbosity pass through - Verbose bool -} - -// everything beyond this point runs as unconstrained target user -// proceed with caution! - -func Main() { - // sharing stdout with fortify - // USE WITH CAUTION - fmsg.Prepare("shim") - - if err := sandbox.SetDumpable(sandbox.SUID_DUMP_DISABLE); err != nil { - log.Fatalf("cannot set SUID_DUMP_DISABLE: %s", err) - } - - var ( - params Params - closeSetup func() error - ) - if f, err := sandbox.Receive(Env, ¶ms, nil); err != nil { - if errors.Is(err, sandbox.ErrInvalid) { - log.Fatal("invalid config descriptor") - } - if errors.Is(err, sandbox.ErrNotSet) { - log.Fatal("FORTIFY_SHIM not set") - } - - log.Fatalf("cannot receive shim setup params: %v", err) - } else { - internal.InstallFmsg(params.Verbose) - closeSetup = f - } - - if params.Container == nil || params.Container.Ops == nil { - log.Fatal("invalid container params") - } - - // close setup socket - if err := closeSetup(); err != nil { - log.Printf("cannot close setup pipe: %v", err) - // not fatal - } - - // ensure home directory as target user - if s, err := os.Stat(params.Home); err != nil { - if os.IsNotExist(err) { - if err = os.Mkdir(params.Home, 0700); err != nil { - log.Fatalf("cannot create home directory: %v", err) - } - } else { - log.Fatalf("cannot access home directory: %v", err) - } - - // home directory is created, proceed - } else if !s.IsDir() { - log.Fatalf("path %q is not a directory", params.Home) - } - - var name string - if len(params.Container.Args) > 0 { - name = params.Container.Args[0] - } - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - defer stop() // unreachable - container := sandbox.New(ctx, name) - container.Params = *params.Container - container.Stdin, container.Stdout, container.Stderr = os.Stdin, os.Stdout, os.Stderr - container.Cancel = func(cmd *exec.Cmd) error { return cmd.Process.Signal(os.Interrupt) } - container.WaitDelay = 2 * time.Second - - if err := container.Start(); err != nil { - fmsg.PrintBaseError(err, "cannot start container:") - os.Exit(1) - } - if err := container.Serve(); err != nil { - fmsg.PrintBaseError(err, "cannot configure container:") - } - if err := container.Wait(); err != nil { - var exitError *exec.ExitError - if !errors.As(err, &exitError) { - if errors.Is(err, context.Canceled) { - os.Exit(2) - } - log.Printf("wait: %v", err) - os.Exit(127) - } - os.Exit(exitError.ExitCode()) - } -} diff --git a/internal/app/shim/proc.go b/internal/app/shim/proc.go deleted file mode 100644 index 8c73e697..00000000 --- a/internal/app/shim/proc.go +++ /dev/null @@ -1,117 +0,0 @@ -package shim - -import ( - "context" - "encoding/gob" - "errors" - "os" - "os/exec" - "strconv" - "strings" - "syscall" - "time" - - "git.gensokyo.uk/security/fortify/internal" - "git.gensokyo.uk/security/fortify/internal/fmsg" - "git.gensokyo.uk/security/fortify/sandbox" -) - -// 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 -} - -func (s *Shim) Unwrap() *exec.Cmd { return s.cmd } -func (s *Shim) Fallback() chan error { return s.killFallback } - -func (s *Shim) String() string { - if s.cmd == nil { - return "(unused shim manager)" - } - return s.cmd.String() -} - -func (s *Shim) Start( - aid string, - supp []string, -) (*time.Time, error) { - // prepare user switcher invocation - fsuPath := internal.MustFsuPath() - s.cmd = exec.Command(fsuPath) - - // pass shim setup pipe - if fd, e, err := sandbox.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.Verbosef("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 = "/" - - fmsg.Verbose("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, params *Params) 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(params) }() - - 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(syscall.ECANCELED, - "shim setup canceled") - } - if errors.Is(err, context.DeadlineExceeded) { - return fmsg.WrapError(syscall.ETIMEDOUT, - "deadline exceeded waiting for shim") - } - // unreachable - return err - } -} |
