diff --git a/cmd/flaunch/activate.go b/cmd/flaunch/activate.go new file mode 100644 index 0000000..8515fa7 --- /dev/null +++ b/cmd/flaunch/activate.go @@ -0,0 +1,49 @@ +package main + +import ( + "errors" + "os" + "os/exec" + "path" + + "git.gensokyo.uk/security/fortify/internal/fmsg" +) + +func actionActivate(args []string) { + home := os.Getenv("HOME") + if !path.IsAbs(home) { + fmsg.Fatalf("path %q is not aboslute", home) + } + marker := path.Join(home, ".hm-activation") + + if len(args) != 1 { + fmsg.Fatalf("invalid argument") + } + activate := path.Join(args[0], "activate") + + var cmd *exec.Cmd + if l, err := os.Readlink(marker); err != nil && !errors.Is(err, os.ErrNotExist) { + fmsg.Fatalf("cannot read activation marker %q: %v", marker, err) + } else if err != nil || l != activate { + cmd = exec.Command(activate) + } + + // marker present and equals to current activation package + if cmd == nil { + fmsg.Exit(0) + panic("unreachable") + } + + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + cmd.Env = os.Environ() + if err := cmd.Run(); err != nil { + fmsg.Fatalf("cannot activate: %v", err) + } + + if err := os.Remove(marker); err != nil && !errors.Is(err, os.ErrNotExist) { + fmsg.Fatalf("cannot remove existing marker: %v", err) + } + if err := os.Symlink(activate, marker); err != nil { + fmsg.Fatalf("cannot create activation marker: %v", err) + } +} diff --git a/cmd/flaunch/main.go b/cmd/flaunch/main.go new file mode 100644 index 0000000..50eecba --- /dev/null +++ b/cmd/flaunch/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "flag" + + "git.gensokyo.uk/security/fortify/internal/fmsg" +) + +var ( + flagVerbose bool +) + +func init() { + flag.BoolVar(&flagVerbose, "v", false, "Verbose output") +} + +func main() { + fmsg.SetPrefix("launch") + + flag.Parse() + fmsg.SetVerbose(flagVerbose) + + args := flag.Args() + if len(args) < 1 { + fmsg.Fatal("invalid arguments") + } + + switch args[0] { + case "activate": + actionActivate(args[1:]) + case "start": + actionStart(args[1:]) + default: + fmsg.Fatal("invalid arguments") + } + + fmsg.Exit(0) +} diff --git a/cmd/flaunch/start.go b/cmd/flaunch/start.go new file mode 100644 index 0000000..92178a8 --- /dev/null +++ b/cmd/flaunch/start.go @@ -0,0 +1,164 @@ +package main + +import ( + "encoding/json" + "errors" + "io" + "os" + "os/exec" + "path" + + "git.gensokyo.uk/security/fortify/dbus" + "git.gensokyo.uk/security/fortify/fst" + "git.gensokyo.uk/security/fortify/internal" + "git.gensokyo.uk/security/fortify/internal/fmsg" + "git.gensokyo.uk/security/fortify/internal/system" +) + +type bundleInfo struct { + Name string `json:"name"` + Version string `json:"version"` + + // passed through to [fst.Config] + ID string `json:"id"` + // passed through to [fst.Config] + AppID int `json:"app_id"` + // passed through to [fst.Config] + Groups []string `json:"groups,omitempty"` + // passed through to [fst.Config] + UserNS bool `json:"userns,omitempty"` + // passed through to [fst.Config] + Net bool `json:"net,omitempty"` + // passed through to [fst.Config] + Dev bool `json:"dev,omitempty"` + // passed through to [fst.Config] + NoNewSession bool `json:"no_new_session,omitempty"` + // passed through to [fst.Config] + MapRealUID bool `json:"map_real_uid,omitempty"` + // passed through to [fst.Config] + DirectWayland bool `json:"direct_wayland,omitempty"` + // passed through to [fst.Config] + SystemBus *dbus.Config `json:"system_bus,omitempty"` + // passed through to [fst.Config] + SessionBus *dbus.Config `json:"session_bus,omitempty"` + // passed through to [fst.Config] + Enablements system.Enablements `json:"enablements"` + + // allow gpu access within sandbox + GPU bool `json:"gpu"` + // inner nix store path to activate-and-exec script + Launcher string `json:"launcher"` +} + +func actionStart(args []string) { + if len(args) < 1 { + fmsg.Fatal("invalid arguments") + } + name := args[0] + + bundle := new(bundleInfo) + if f, err := os.Open(path.Join(name, "bundle.json")); err != nil { + fmsg.Fatalf("cannot open bundle: %v", err) + } else if err = json.NewDecoder(f).Decode(&bundle); err != nil { + fmsg.Fatalf("cannot parse bundle metadata: %v", err) + } else if err = f.Close(); err != nil { + fmsg.Printf("cannot close bundle metadata: %v", err) + } + + config := &fst.Config{ + ID: bundle.ID, + Command: append([]string{bundle.Launcher}, args[1:]...), + Confinement: fst.ConfinementConfig{ + AppID: bundle.AppID, + Groups: bundle.Groups, + Username: "fortify", + Inner: path.Join("/data/data", bundle.ID), + Outer: formatDataPath(bundle.ID), + Sandbox: &fst.SandboxConfig{ + Hostname: formatHostname(bundle.Name), + UserNS: bundle.UserNS, + Net: bundle.Net, + Dev: bundle.Dev, + NoNewSession: bundle.NoNewSession, + MapRealUID: bundle.MapRealUID, + DirectWayland: bundle.DirectWayland, + Filesystem: []*fst.FilesystemConfig{ + {Src: path.Join(name, "nix", "store"), Dst: "/nix/store", Must: true}, + {Src: "/sys/block"}, + {Src: "/sys/bus"}, + {Src: "/sys/class"}, + {Src: "/sys/dev"}, + {Src: "/sys/devices"}, + }, + AutoEtc: true, + }, + SystemBus: bundle.SystemBus, + SessionBus: bundle.SessionBus, + Enablements: bundle.Enablements, + }, + } + + if bundle.GPU { + config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem, + &fst.FilesystemConfig{Src: "/dev/dri", Device: true}) + } + + var ( + cmd *exec.Cmd + st io.WriteCloser + ) + if p, ok := internal.Check(internal.Fortify); !ok { + fmsg.Fatal("invalid fortify path, this copy of flaunch is not compiled correctly") + panic("unreachable") + } else if r, w, err := os.Pipe(); err != nil { + fmsg.Fatalf("cannot pipe: %v", err) + panic("unreachable") + } else { + if fmsg.Verbose() { + cmd = exec.Command(p, "-v", "app", "3") + } else { + cmd = exec.Command(p, "app", "3") + } + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + cmd.ExtraFiles = []*os.File{r} + st = w + } + + go func() { + if err := json.NewEncoder(st).Encode(config); err != nil { + fmsg.Fatalf("cannot send configuration: %v", err) + } + }() + + if err := cmd.Start(); err != nil { + fmsg.Fatalf("cannot start fortify: %v", err) + } + if err := cmd.Wait(); err != nil { + var exitError *exec.ExitError + if errors.As(err, &exitError) { + fmsg.Exit(exitError.ExitCode()) + } else { + fmsg.Fatalf("cannot wait: %v", err) + } + } + fmsg.Exit(0) +} + +func formatHostname(name string) string { + if h, err := os.Hostname(); err != nil { + fmsg.Printf("cannot get hostname: %v", err) + return "fortify-" + name + } else { + return h + "-" + name + } +} + +func formatDataPath(id string) string { + if p, ok := os.LookupEnv("FORTIFY_DATA_HOME"); ok { + return path.Join(p, id) + } else if p, ok = os.LookupEnv("HOME"); ok { + return path.Join(p, ".app", id) + } else { + return path.Join("/var/lib/fortify/app", id) + } +} diff --git a/dist/install.sh b/dist/install.sh index 9694969..97971f0 100755 --- a/dist/install.sh +++ b/dist/install.sh @@ -4,6 +4,7 @@ cd "$(dirname -- "$0")" || exit 1 install -vDm0755 "bin/fortify" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fortify" install -vDm0755 "bin/fshim" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fshim" install -vDm0755 "bin/finit" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/finit" +install -vDm0755 "bin/flaunch" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/flaunch" install -vDm0755 "bin/fuserdb" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fuserdb" install -vDm6511 "bin/fsu" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fsu"