shim: new shim implementation
This implementation of shim accepts configuration as a gob stream over a unix socket, with support for mediating access to wayland via WAYLAND_SOCKET fd. All configuration is now included in the payload, and child is started inside bwrap configured with supplied bwrap.Config. Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
This commit is contained in:
parent
6eb712aec7
commit
b86fa6b4c9
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue