aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/shim/main.go188
-rw-r--r--internal/shim/parent.go90
-rw-r--r--internal/shim/payload.go23
3 files changed, 301 insertions, 0 deletions
diff --git a/internal/shim/main.go b/internal/shim/main.go
new file mode 100644
index 00000000..618f466d
--- /dev/null
+++ b/internal/shim/main.go
@@ -0,0 +1,188 @@
+package shim
+
+import (
+ "encoding/gob"
+ "errors"
+ "fmt"
+ "net"
+ "os"
+ "strconv"
+ "strings"
+ "syscall"
+
+ "git.ophivana.moe/cat/fortify/helper"
+ "git.ophivana.moe/cat/fortify/helper/bwrap"
+ "git.ophivana.moe/cat/fortify/internal/verbose"
+)
+
+// everything beyond this point runs as target user
+// proceed with caution!
+
+func shim(socket string) {
+ verbose.Prefix = "fortify-shim:"
+
+ // dial setup socket
+ var conn *net.UnixConn
+ if c, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: socket, Net: "unix"}); err != nil {
+ fmt.Println("fortify-shim: cannot dial setup socket:", err)
+ os.Exit(1)
+ } else {
+ conn = c
+ }
+
+ // decode payload gob stream
+ var payload Payload
+ if err := gob.NewDecoder(conn).Decode(&payload); err != nil {
+ fmt.Println("fortify-shim: cannot decode shim payload:", err)
+ os.Exit(1)
+ } else {
+ // sharing stdout with parent
+ // USE WITH CAUTION
+ verbose.Set(payload.Verbose)
+ }
+
+ // receive wayland fd over socket
+ wfd := -1
+ if payload.WL {
+ if fd, err := receiveWLfd(conn); err != nil {
+ fmt.Println("fortify-shim: cannot receive wayland fd:", err)
+ os.Exit(1)
+ } else {
+ wfd = fd
+ }
+ }
+
+ // close setup socket
+ if err := conn.Close(); err != nil {
+ fmt.Println("fortify-shim: cannot close setup socket:", err)
+ // not fatal
+ }
+
+ // resolve argv0
+ var (
+ argv0 string
+ argv = payload.Argv
+ )
+ if len(argv) > 0 {
+ // looked up from $PATH by parent
+ argv0 = payload.Exec[1]
+ } else {
+ // no argv, look up shell instead
+ var ok bool
+ if argv0, ok = os.LookupEnv("SHELL"); !ok {
+ fmt.Println("fortify-shim: no command was specified and $SHELL was unset")
+ os.Exit(1)
+ }
+
+ argv = []string{argv0}
+ }
+
+ _ = conn.Close()
+
+ conf := payload.Bwrap
+ if conf == nil {
+ verbose.Println("sandbox configuration not supplied, PROCEED WITH CAUTION")
+ conf = &bwrap.Config{
+ Net: true,
+ UserNS: true,
+ Clearenv: true,
+ Procfs: []string{"/proc"},
+ DevTmpfs: []string{"/dev"},
+ Mqueue: []string{"/dev/mqueue"},
+ DieWithParent: true,
+ }
+
+ if d, err := os.ReadDir("/"); err != nil {
+ fmt.Println("fortify-shim: cannot readdir '/':", err)
+ } else {
+ conf.Bind = make([][2]string, 0, len(d))
+ for _, ent := range d {
+ name := ent.Name()
+ switch name {
+ case "proc":
+ case "dev":
+ default:
+ p := "/" + name
+ conf.Bind = append(conf.Bind, [2]string{p, p})
+ }
+ }
+ }
+ }
+ if conf.SetEnv == nil {
+ conf.SetEnv = make(map[string]string, len(payload.Env))
+ }
+
+ var extraFiles []*os.File
+
+ // set environment passed by parent
+ for _, s := range payload.Env {
+ kv := strings.SplitN(s, "=", 2)
+ if len(kv) != 2 {
+ fmt.Println("fortify-shim: invalid environment string:", s)
+ } else {
+ conf.SetEnv[kv[0]] = kv[1]
+ }
+ }
+
+ // pass wayland fd
+ if wfd != -1 {
+ if f := os.NewFile(uintptr(wfd), "wayland"); f != nil {
+ conf.SetEnv["WAYLAND_SOCKET"] = strconv.Itoa(3 + len(extraFiles))
+ extraFiles = append(extraFiles, f)
+ }
+ }
+
+ helper.BubblewrapName = payload.Exec[0] // resolved bwrap path by parent
+ if b, err := helper.NewBwrap(conf, nil, argv0, func(_, _ int) []string { return argv[1:] }); err != nil {
+ fmt.Println("fortify-shim: malformed sandbox config:", err)
+ os.Exit(1)
+ } else {
+ cmd := b.Unwrap()
+ cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
+ cmd.ExtraFiles = extraFiles
+
+ if verbose.Get() {
+ verbose.Println("bwrap args:", conf.Args())
+ }
+
+ // run and pass through exit code
+ if err = b.Start(); err != nil {
+ fmt.Println("fortify-shim: cannot start target process:", err)
+ os.Exit(1)
+ } else if err = b.Wait(); err != nil {
+ verbose.Println("wait:", err)
+ }
+ if b.Unwrap().ProcessState != nil {
+ os.Exit(b.Unwrap().ProcessState.ExitCode())
+ } else {
+ os.Exit(127)
+ }
+ }
+}
+
+func receiveWLfd(conn *net.UnixConn) (int, error) {
+ oob := make([]byte, syscall.CmsgSpace(4)) // single fd
+
+ if _, oobn, _, _, err := conn.ReadMsgUnix(nil, oob); err != nil {
+ return -1, err
+ } else if len(oob) != oobn {
+ return -1, errors.New("invalid message length")
+ }
+
+ var msg syscall.SocketControlMessage
+ if messages, err := syscall.ParseSocketControlMessage(oob); err != nil {
+ return -1, err
+ } else if len(messages) != 1 {
+ return -1, errors.New("unexpected message count")
+ } else {
+ msg = messages[0]
+ }
+
+ if fds, err := syscall.ParseUnixRights(&msg); err != nil {
+ return -1, err
+ } else if len(fds) != 1 {
+ return -1, errors.New("unexpected fd count")
+ } else {
+ return fds[0], nil
+ }
+}
diff --git a/internal/shim/parent.go b/internal/shim/parent.go
new file mode 100644
index 00000000..7ffc1c41
--- /dev/null
+++ b/internal/shim/parent.go
@@ -0,0 +1,90 @@
+package shim
+
+import (
+ "encoding/gob"
+ "errors"
+ "fmt"
+ "net"
+ "os"
+ "syscall"
+
+ "git.ophivana.moe/cat/fortify/internal/verbose"
+)
+
+// called in the parent process
+
+func ServeConfig(socket string, payload *Payload, wl string, done chan struct{}) (*net.UnixConn, error) {
+ var ws *net.UnixConn
+ if payload.WL {
+ if f, err := net.DialUnix("unix", nil, &net.UnixAddr{Name: wl, Net: "unix"}); err != nil {
+ return nil, err
+ } else {
+ verbose.Println("connected to wayland at", wl)
+ ws = f
+ }
+ }
+
+ if c, err := net.ListenUnix("unix", &net.UnixAddr{Name: socket, Net: "unix"}); err != nil {
+ return nil, err
+ } else {
+ verbose.Println("configuring shim on socket", socket)
+ if err = os.Chmod(socket, 0777); err != nil {
+ fmt.Println("fortify: cannot change permissions of shim setup socket:", err)
+ }
+
+ go func() {
+ var conn *net.UnixConn
+ if conn, err = c.AcceptUnix(); err != nil {
+ fmt.Println("fortify: cannot accept connection from shim:", err)
+ } else {
+ if err = gob.NewEncoder(conn).Encode(*payload); err != nil {
+ fmt.Println("fortify: cannot stream shim payload:", err)
+ return
+ }
+
+ if payload.WL {
+ // get raw connection
+ var rc syscall.RawConn
+ if rc, err = ws.SyscallConn(); err != nil {
+ fmt.Println("fortify: cannot obtain raw wayland connection:", err)
+ return
+ } else {
+ go func() {
+ // pass wayland socket fd
+ if err = rc.Control(func(fd uintptr) {
+ if _, _, err = conn.WriteMsgUnix(nil, syscall.UnixRights(int(fd)), nil); err != nil {
+ fmt.Println("fortify: cannot pass wayland connection to shim:", err)
+ return
+ }
+ _ = conn.Close()
+
+ // block until shim exits
+ <-done
+ verbose.Println("releasing wayland connection")
+ }); err != nil {
+ fmt.Println("fortify: cannot obtain wayland connection fd:", err)
+ }
+ }()
+ }
+ } else {
+ _ = conn.Close()
+ }
+ }
+ if err = c.Close(); err != nil {
+ fmt.Println("fortify: cannot close shim socket:", err)
+ }
+ if err = os.Remove(socket); err != nil && !errors.Is(err, os.ErrNotExist) {
+ fmt.Println("fortify: cannot remove dangling shim socket:", err)
+ }
+ }()
+ return ws, nil
+ }
+}
+
+// Try runs shim and stops execution if FORTIFY_SHIM is set.
+func Try() {
+ if s, ok := os.LookupEnv(EnvShim); ok {
+ shim(s)
+ }
+ panic("unreachable")
+}
diff --git a/internal/shim/payload.go b/internal/shim/payload.go
new file mode 100644
index 00000000..6202bfe7
--- /dev/null
+++ b/internal/shim/payload.go
@@ -0,0 +1,23 @@
+package shim
+
+import (
+ "git.ophivana.moe/cat/fortify/helper/bwrap"
+)
+
+const EnvShim = "FORTIFY_SHIM"
+
+type Payload struct {
+ // child full argv
+ Argv []string
+ // env variables passed through to bwrap
+ Env []string
+ // bwrap, target full exec path
+ Exec [2]string
+ // bwrap config, nil for permissive
+ Bwrap *bwrap.Config
+ // whether to pas wayland fd
+ WL bool
+
+ // verbosity pass through
+ Verbose bool
+}