app: integrate fsu
All checks were successful
test / test (push) Successful in 21s

This removes the dependency on external user switchers like sudo/machinectl and decouples fortify user ids from the passwd database.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
2024-11-16 21:19:45 +09:00
parent 1a09b55bd4
commit df33123bd7
25 changed files with 450 additions and 378 deletions

View File

@@ -5,6 +5,7 @@ import (
"net"
"os"
"os/exec"
"strings"
"sync"
"sync/atomic"
"syscall"
@@ -12,6 +13,7 @@ import (
"git.ophivana.moe/security/fortify/acl"
shim0 "git.ophivana.moe/security/fortify/cmd/fshim/ipc"
"git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/fmsg"
)
@@ -24,24 +26,26 @@ type Shim struct {
cmd *exec.Cmd
// uid of shim target user
uid uint32
// whether to check shim pid
checkPid bool
// user switcher executable path
executable string
// string representation of application id
aid string
// string representation of supplementary group ids
supp []string
// path to setup socket
socket string
// shim setup abort reason and completion
abort chan error
abortErr atomic.Pointer[error]
abortOnce sync.Once
// fallback exit notifier with error returned killing the process
killFallback chan error
// wayland mediation, nil if disabled
wl *shim0.Wayland
// shim setup payload
payload *shim0.Payload
}
func New(executable string, uid uint32, socket string, wl *shim0.Wayland, payload *shim0.Payload, checkPid bool) *Shim {
return &Shim{uid: uid, executable: executable, socket: socket, wl: wl, payload: payload, checkPid: checkPid}
func New(uid uint32, aid string, supp []string, socket string, wl *shim0.Wayland, payload *shim0.Payload) *Shim {
return &Shim{uid: uid, aid: aid, supp: supp, socket: socket, wl: wl, payload: payload}
}
func (s *Shim) String() string {
@@ -68,9 +72,11 @@ func (s *Shim) AbortWait(err error) {
<-s.abort
}
type CommandBuilder func(shimEnv string) (args []string)
func (s *Shim) WaitFallback() chan error {
return s.killFallback
}
func (s *Shim) Start(f CommandBuilder) (*time.Time, error) {
func (s *Shim) Start() (*time.Time, error) {
var (
cf chan *net.UnixConn
accept func()
@@ -87,22 +93,37 @@ func (s *Shim) Start(f CommandBuilder) (*time.Time, error) {
}
// start user switcher process and save time
s.cmd = exec.Command(s.executable, f(shim0.Env+"="+s.socket)...)
s.cmd.Env = []string{}
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)
s.cmd.Env = []string{
shim0.Env + "=" + s.socket,
"FORTIFY_APP_ID=" + s.aid,
}
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 = "/"
fmsg.VPrintln("starting shim via user switcher:", s.cmd)
fmsg.Withhold() // withhold messages to stderr
fmsg.VPrintln("starting shim via fsu:", s.cmd)
fmsg.Suspend() // withhold messages to stderr
if err := s.cmd.Start(); err != nil {
return nil, fmsg.WrapErrorSuffix(err,
"cannot start user switcher:")
"cannot start fsu:")
}
startTime := time.Now().UTC()
// 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 {
fmsg.Println("cannot terminate shim on faulted setup:", err)
s.killFallback <- err
}
}
defer func() { killShim() }()
@@ -132,7 +153,7 @@ func (s *Shim) Start(f CommandBuilder) (*time.Time, error) {
err = errors.New("compromised fortify build")
s.Abort(err)
return &startTime, err
} else if s.checkPid && cred.Pid != int32(s.cmd.Process.Pid) {
} else if cred.Pid != int32(s.cmd.Process.Pid) {
fmsg.Printf("process %d tried to connect to shim setup socket, expecting shim %d",
cred.Pid, s.cmd.Process.Pid)
err = errors.New("compromised target user")

View File

@@ -58,7 +58,7 @@ func main() {
// dial setup socket
var conn *net.UnixConn
if c, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: socketPath, Net: "unix"}); err != nil {
fmsg.Fatal("cannot dial setup socket:", err)
fmsg.Fatal(err.Error())
panic("unreachable")
} else {
conn = c
@@ -67,7 +67,7 @@ func main() {
// decode payload gob stream
var payload shim.Payload
if err := gob.NewDecoder(conn).Decode(&payload); err != nil {
fmsg.Fatal("cannot decode shim payload:", err)
fmsg.Fatalf("cannot decode shim payload: %v", err)
} else {
fmsg.SetVerbose(payload.Verbose)
}
@@ -80,7 +80,7 @@ func main() {
wfd := -1
if payload.WL {
if fd, err := receiveWLfd(conn); err != nil {
fmsg.Fatal("cannot receive wayland fd:", err)
fmsg.Fatalf("cannot receive wayland fd: %v", err)
} else {
wfd = fd
}
@@ -102,7 +102,10 @@ func main() {
} else {
// no argv, look up shell instead
var ok bool
if ic.Argv0, ok = os.LookupEnv("SHELL"); !ok {
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")
}
@@ -125,7 +128,7 @@ func main() {
// share config pipe
if r, w, err := os.Pipe(); err != nil {
fmsg.Fatal("cannot pipe:", err)
fmsg.Fatalf("cannot pipe: %v", err)
} else {
conf.SetEnv[init0.Env] = strconv.Itoa(3 + len(extraFiles))
extraFiles = append(extraFiles, r)
@@ -134,7 +137,7 @@ func main() {
go func() {
// stream config to pipe
if err = gob.NewEncoder(w).Encode(&ic); err != nil {
fmsg.Fatal("cannot transmit init config:", err)
fmsg.Fatalf("cannot transmit init config: %v", err)
}
}()
}
@@ -142,7 +145,7 @@ func main() {
helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
if b, err := helper.NewBwrap(conf, nil, finitPath,
func(int, int) []string { return make([]string, 0) }); err != nil {
fmsg.Fatal("malformed sandbox config:", err)
fmsg.Fatalf("malformed sandbox config: %v", err)
} else {
cmd := b.Unwrap()
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
@@ -154,7 +157,7 @@ func main() {
// run and pass through exit code
if err = b.Start(); err != nil {
fmsg.Fatal("cannot start target process:", err)
fmsg.Fatalf("cannot start target process: %v", err)
} else if err = b.Wait(); err != nil {
fmsg.VPrintln("wait:", err)
}