Compare commits

...

10 Commits

Author SHA1 Message Date
Ophestra Umiker f831948bca
release: 0.1.0
release / release (push) Successful in 28s Details
test / test (push) Successful in 21s Details
This release significantly changes the command line interface, and updates the NixOS module to finally produce meaningful sandbox configuration.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-06 04:37:43 +09:00
Ophestra Umiker 2e31b3d3a1
update README document
test / test (push) Successful in 32s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-06 04:35:52 +09:00
Ophestra Umiker 4d90e73366
nix: generate strict sandbox configuration
test / test (push) Successful in 22s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-06 04:25:15 +09:00
Ophestra Umiker 3dfc1fcd56
app: support full /dev access
test / test (push) Successful in 22s Details
Also moved /dev/fortify to /fortify since it is impossible to create new directories in /dev from the init namespace and bind mounting its contents has undesirable side effects.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-06 03:49:39 +09:00
Ophestra Umiker 89bafd0c22
fortify: root check before command handling
test / test (push) Successful in 22s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-05 12:57:03 +09:00
Ophestra Umiker 861bb1274f
fortify: override default usage function
test / test (push) Successful in 23s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-05 00:12:31 +09:00
Ophestra Umiker 714818c8aa
fortify: implement cleaner argument structure
test / test (push) Successful in 24s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-05 00:07:36 +09:00
Ophestra Umiker 69cc64ef56
linux: provide access to stdout
test / test (push) Successful in 22s Details
Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-04 22:55:46 +09:00
Ophestra Umiker fc25ac2523
app: separate auto etc from permissive defaults
test / test (push) Successful in 23s Details
Populating /etc with symlinks is quite useful even outside the permissive defaults usage pattern.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-04 22:18:05 +09:00
Ophestra Umiker d909b1190a
app/config: UseRealUID as true in template
test / test (push) Successful in 24s Details
The template is based on a Chromium setup, which this workaround was created for.

Signed-off-by: Ophestra Umiker <cat@ophivana.moe>
2024-11-04 19:45:31 +09:00
13 changed files with 670 additions and 524 deletions

View File

@ -2,6 +2,7 @@ Fortify
======= =======
[![Go Reference](https://pkg.go.dev/badge/git.ophivana.moe/security/fortify.svg)](https://pkg.go.dev/git.ophivana.moe/security/fortify) [![Go Reference](https://pkg.go.dev/badge/git.ophivana.moe/security/fortify.svg)](https://pkg.go.dev/git.ophivana.moe/security/fortify)
[![Go Report Card](https://goreportcard.com/badge/git.ophivana.moe/security/fortify)](https://goreportcard.com/report/git.ophivana.moe/security/fortify)
Lets you run graphical applications as another user in a confined environment with a nice NixOS Lets you run graphical applications as another user in a confined environment with a nice NixOS
module to configure target users and provide launchers and desktop files for your privileged user. module to configure target users and provide launchers and desktop files for your privileged user.
@ -77,40 +78,54 @@ This adds the `environment.fortify` option:
claws-mail.capability.pulse = false; claws-mail.capability.pulse = false;
discord = { discord = {
id = "dev.vencord.Vesktop";
command = "vesktop --ozone-platform-hint=wayland"; command = "vesktop --ozone-platform-hint=wayland";
userns = true;
useRealUid = true;
dbus = {
session =
f:
f {
talk = [ "org.kde.StatusNotifierWatcher" ];
own = [ ];
call = { };
broadcast = { };
};
system.filter = true;
};
share = pkgs.vesktop; share = pkgs.vesktop;
}; };
chromium.dbus = { chromium = {
configSystem = { id = "org.chromium.Chromium";
filter = true; userns = true;
talk = [ useRealUid = true;
"org.bluez" dbus = {
"org.freedesktop.Avahi" system = {
"org.freedesktop.UPower" filter = true;
]; talk = [
}; "org.bluez"
config = { "org.freedesktop.Avahi"
filter = true; "org.freedesktop.UPower"
talk = [ ];
"org.freedesktop.DBus"
"org.freedesktop.FileManager1"
"org.freedesktop.Notifications"
"org.freedesktop.ScreenSaver"
"org.freedesktop.secrets"
"org.kde.kwalletd5"
"org.kde.kwalletd6"
];
own = [
"org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.chromium.*"
];
call = {
"org.freedesktop.portal.*" = "*";
}; };
broadcast = { session = f: f {
"org.freedesktop.portal.*" = "@/org/freedesktop/portal/*"; talk = [
"org.freedesktop.DBus"
"org.freedesktop.FileManager1"
"org.freedesktop.Notifications"
"org.freedesktop.ScreenSaver"
"org.freedesktop.secrets"
"org.kde.kwalletd5"
"org.kde.kwalletd6"
];
own = [
"org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.org.chromium.Chromium.*"
"org.mpris.MediaPlayer2.chromium.*"
];
call = { };
broadcast = { };
}; };
}; };
}; };
@ -156,15 +171,29 @@ This adds the `environment.fortify` option:
The available options are: The available options are:
* `id`, the freedesktop application ID, primarily used by dbus, null to disable.
* `command`, the command to run as the target user. Defaults to launcher name. * `command`, the command to run as the target user. Defaults to launcher name.
* `dbus.config`, D-Bus proxy custom configuration. * `dbus.session`, D-Bus session proxy custom configuration.
* `dbus.configSystem`, D-Bus system bus custom configuration, null to disable. * `dbus.configSystem`, D-Bus system proxy custom configuration, null to disable.
* `dbus.id`, D-Bus application id, has no effect if `dbus.config` is set. * `env`, attrset of environment variables to set for the initial process in the sandbox.
* `dbus.mpris`, whether to enable MPRIS defaults, has no effect if `dbus.config` is set. * `nix`, whether to allow nix daemon connections from within the sandbox.
* `userns`, whether to allow userns within the sandbox.
* `useRealUid`, whether to map to the real UID within the sandbox.
* `net`, whether to allow network access within the sandbox.
* `gpu`, target process GPU and driver access, null to follow Wayland or X capability.
* `dev`, whether to allow full device access within the sandbox.
* `extraPaths`, a list of extra paths to make available inside the sandbox.
* `capability.wayland`, whether to share the Wayland socket. * `capability.wayland`, whether to share the Wayland socket.
@ -176,4 +205,4 @@ This adds the `environment.fortify` option:
* `share`, package containing desktop/icon files. Defaults to launcher name. * `share`, package containing desktop/icon files. Defaults to launcher name.
* `method`, the launch method for the sandboxed program, can be `"fortify"`, `"fortify-sudo"`, `"sudo"`. * `method`, the launch method for the sandboxed program, can be `"sudo"`, `"systemd"`, `"simple"`.

135
config.go
View File

@ -1,135 +0,0 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/internal/app"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/system"
)
var (
printTemplate bool
confPath string
dbusConfigSession string
dbusConfigSystem string
dbusID string
mpris bool
dbusVerbose bool
userName string
enablements [system.ELen]bool
launchMethodText string
)
func init() {
flag.BoolVar(&printTemplate, "template", false, "Print a full config template and exit")
// config file, disables every other flag here
flag.StringVar(&confPath, "c", "nil", "Path to full app configuration, or \"nil\" to configure from flags")
flag.StringVar(&dbusConfigSession, "dbus-config", "builtin", "Path to D-Bus proxy config file, or \"builtin\" for defaults")
flag.StringVar(&dbusConfigSystem, "dbus-system", "nil", "Path to system D-Bus proxy config file, or \"nil\" to disable")
flag.StringVar(&dbusID, "dbus-id", "", "D-Bus ID of application, leave empty to disable own paths, has no effect if custom config is available")
flag.BoolVar(&mpris, "mpris", false, "Allow owning MPRIS D-Bus path, has no effect if custom config is available")
flag.BoolVar(&dbusVerbose, "dbus-log", false, "Force logging in the D-Bus proxy")
flag.StringVar(&userName, "u", "chronos", "Passwd name of user to run as")
flag.BoolVar(&enablements[system.EWayland], "wayland", false, "Share Wayland socket")
flag.BoolVar(&enablements[system.EX11], "X", false, "Share X11 socket and allow connection")
flag.BoolVar(&enablements[system.EDBus], "dbus", false, "Proxy D-Bus connection")
flag.BoolVar(&enablements[system.EPulse], "pulse", false, "Share PulseAudio socket and cookie")
}
func init() {
methodHelpString := "Method of launching the child process, can be one of \"sudo\""
if os.SdBooted() {
methodHelpString += ", \"systemd\""
}
flag.StringVar(&launchMethodText, "method", "sudo", methodHelpString)
}
func tryTemplate() {
if printTemplate {
if s, err := json.MarshalIndent(app.Template(), "", " "); err != nil {
fmsg.Fatalf("cannot generate template: %v", err)
panic("unreachable")
} else {
fmt.Println(string(s))
}
fmsg.Exit(0)
}
}
func loadConfig() *app.Config {
if confPath == "nil" {
// config from flags
return configFromFlags()
} else {
// config from file
c := new(app.Config)
if f, err := os.Open(confPath); err != nil {
fmsg.Fatalf("cannot access config file %q: %s", confPath, err)
panic("unreachable")
} else if err = json.NewDecoder(f).Decode(&c); err != nil {
fmsg.Fatalf("cannot parse config file %q: %s", confPath, err)
panic("unreachable")
} else {
return c
}
}
}
func configFromFlags() (config *app.Config) {
// initialise config from flags
config = &app.Config{
ID: dbusID,
User: userName,
Command: flag.Args(),
Method: launchMethodText,
}
// enablements from flags
for i := system.Enablement(0); i < system.Enablement(system.ELen); i++ {
if enablements[i] {
config.Confinement.Enablements.Set(i)
}
}
// parse D-Bus config file from flags if applicable
if enablements[system.EDBus] {
if dbusConfigSession == "builtin" {
config.Confinement.SessionBus = dbus.NewConfig(dbusID, true, mpris)
} else {
if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
} else {
config.Confinement.SessionBus = c
}
}
// system bus proxy is optional
if dbusConfigSystem != "nil" {
if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
} else {
config.Confinement.SystemBus = c
}
}
// override log from configuration
if dbusVerbose {
config.Confinement.SessionBus.Log = true
config.Confinement.SystemBus.Log = true
}
}
return
}

View File

@ -2,6 +2,7 @@ package app_test
import ( import (
"fmt" "fmt"
"io"
"io/fs" "io/fs"
"os/user" "os/user"
"strconv" "strconv"
@ -54,11 +55,11 @@ var testCasesNixos = []sealTestCase{
DieWithParent: true, DieWithParent: true,
AsInit: true, AsInit: true,
}).SetUID(65534).SetGID(65534). }).SetUID(65534).SetGID(65534).
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue"). Procfs("/proc").
Tmpfs("/dev/fortify", 4096). Tmpfs("/fortify", 4096).
DevTmpfs("/dev").Mqueue("/dev/mqueue").
Bind("/bin", "/bin", false, true). Bind("/bin", "/bin", false, true).
Bind("/boot", "/boot", false, true). Bind("/boot", "/boot", false, true).
Bind("/etc", "/dev/fortify/etc").
Bind("/home", "/home", false, true). Bind("/home", "/home", false, true).
Bind("/lib", "/lib", false, true). Bind("/lib", "/lib", false, true).
Bind("/lib64", "/lib64", false, true). Bind("/lib64", "/lib64", false, true).
@ -102,84 +103,85 @@ var testCasesNixos = []sealTestCase{
Bind("/run/wrappers", "/run/wrappers", false, true). Bind("/run/wrappers", "/run/wrappers", false, true).
Bind("/run/zed.pid", "/run/zed.pid", false, true). Bind("/run/zed.pid", "/run/zed.pid", false, true).
Bind("/run/zed.state", "/run/zed.state", false, true). Bind("/run/zed.state", "/run/zed.state", false, true).
Symlink("/dev/fortify/etc/alsa", "/etc/alsa"). Bind("/etc", "/fortify/etc").
Symlink("/dev/fortify/etc/bashrc", "/etc/bashrc"). Symlink("/fortify/etc/alsa", "/etc/alsa").
Symlink("/dev/fortify/etc/binfmt.d", "/etc/binfmt.d"). Symlink("/fortify/etc/bashrc", "/etc/bashrc").
Symlink("/dev/fortify/etc/dbus-1", "/etc/dbus-1"). Symlink("/fortify/etc/binfmt.d", "/etc/binfmt.d").
Symlink("/dev/fortify/etc/default", "/etc/default"). Symlink("/fortify/etc/dbus-1", "/etc/dbus-1").
Symlink("/dev/fortify/etc/ethertypes", "/etc/ethertypes"). Symlink("/fortify/etc/default", "/etc/default").
Symlink("/dev/fortify/etc/fonts", "/etc/fonts"). Symlink("/fortify/etc/ethertypes", "/etc/ethertypes").
Symlink("/dev/fortify/etc/fstab", "/etc/fstab"). Symlink("/fortify/etc/fonts", "/etc/fonts").
Symlink("/dev/fortify/etc/fuse.conf", "/etc/fuse.conf"). Symlink("/fortify/etc/fstab", "/etc/fstab").
Symlink("/dev/fortify/etc/host.conf", "/etc/host.conf"). Symlink("/fortify/etc/fuse.conf", "/etc/fuse.conf").
Symlink("/dev/fortify/etc/hostid", "/etc/hostid"). Symlink("/fortify/etc/host.conf", "/etc/host.conf").
Symlink("/dev/fortify/etc/hostname", "/etc/hostname"). Symlink("/fortify/etc/hostid", "/etc/hostid").
Symlink("/dev/fortify/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). Symlink("/fortify/etc/hostname", "/etc/hostname").
Symlink("/dev/fortify/etc/hosts", "/etc/hosts"). Symlink("/fortify/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
Symlink("/dev/fortify/etc/inputrc", "/etc/inputrc"). Symlink("/fortify/etc/hosts", "/etc/hosts").
Symlink("/dev/fortify/etc/ipsec.d", "/etc/ipsec.d"). Symlink("/fortify/etc/inputrc", "/etc/inputrc").
Symlink("/dev/fortify/etc/issue", "/etc/issue"). Symlink("/fortify/etc/ipsec.d", "/etc/ipsec.d").
Symlink("/dev/fortify/etc/kbd", "/etc/kbd"). Symlink("/fortify/etc/issue", "/etc/issue").
Symlink("/dev/fortify/etc/libblockdev", "/etc/libblockdev"). Symlink("/fortify/etc/kbd", "/etc/kbd").
Symlink("/dev/fortify/etc/locale.conf", "/etc/locale.conf"). Symlink("/fortify/etc/libblockdev", "/etc/libblockdev").
Symlink("/dev/fortify/etc/localtime", "/etc/localtime"). Symlink("/fortify/etc/locale.conf", "/etc/locale.conf").
Symlink("/dev/fortify/etc/login.defs", "/etc/login.defs"). Symlink("/fortify/etc/localtime", "/etc/localtime").
Symlink("/dev/fortify/etc/lsb-release", "/etc/lsb-release"). Symlink("/fortify/etc/login.defs", "/etc/login.defs").
Symlink("/dev/fortify/etc/lvm", "/etc/lvm"). Symlink("/fortify/etc/lsb-release", "/etc/lsb-release").
Symlink("/dev/fortify/etc/machine-id", "/etc/machine-id"). Symlink("/fortify/etc/lvm", "/etc/lvm").
Symlink("/dev/fortify/etc/man_db.conf", "/etc/man_db.conf"). Symlink("/fortify/etc/machine-id", "/etc/machine-id").
Symlink("/dev/fortify/etc/modprobe.d", "/etc/modprobe.d"). Symlink("/fortify/etc/man_db.conf", "/etc/man_db.conf").
Symlink("/dev/fortify/etc/modules-load.d", "/etc/modules-load.d"). Symlink("/fortify/etc/modprobe.d", "/etc/modprobe.d").
Symlink("/fortify/etc/modules-load.d", "/etc/modules-load.d").
Symlink("/proc/mounts", "/etc/mtab"). Symlink("/proc/mounts", "/etc/mtab").
Symlink("/dev/fortify/etc/nanorc", "/etc/nanorc"). Symlink("/fortify/etc/nanorc", "/etc/nanorc").
Symlink("/dev/fortify/etc/netgroup", "/etc/netgroup"). Symlink("/fortify/etc/netgroup", "/etc/netgroup").
Symlink("/dev/fortify/etc/NetworkManager", "/etc/NetworkManager"). Symlink("/fortify/etc/NetworkManager", "/etc/NetworkManager").
Symlink("/dev/fortify/etc/nix", "/etc/nix"). Symlink("/fortify/etc/nix", "/etc/nix").
Symlink("/dev/fortify/etc/nixos", "/etc/nixos"). Symlink("/fortify/etc/nixos", "/etc/nixos").
Symlink("/dev/fortify/etc/NIXOS", "/etc/NIXOS"). Symlink("/fortify/etc/NIXOS", "/etc/NIXOS").
Symlink("/dev/fortify/etc/nscd.conf", "/etc/nscd.conf"). Symlink("/fortify/etc/nscd.conf", "/etc/nscd.conf").
Symlink("/dev/fortify/etc/nsswitch.conf", "/etc/nsswitch.conf"). Symlink("/fortify/etc/nsswitch.conf", "/etc/nsswitch.conf").
Symlink("/dev/fortify/etc/opensnitchd", "/etc/opensnitchd"). Symlink("/fortify/etc/opensnitchd", "/etc/opensnitchd").
Symlink("/dev/fortify/etc/os-release", "/etc/os-release"). Symlink("/fortify/etc/os-release", "/etc/os-release").
Symlink("/dev/fortify/etc/pam", "/etc/pam"). Symlink("/fortify/etc/pam", "/etc/pam").
Symlink("/dev/fortify/etc/pam.d", "/etc/pam.d"). Symlink("/fortify/etc/pam.d", "/etc/pam.d").
Symlink("/dev/fortify/etc/pipewire", "/etc/pipewire"). Symlink("/fortify/etc/pipewire", "/etc/pipewire").
Symlink("/dev/fortify/etc/pki", "/etc/pki"). Symlink("/fortify/etc/pki", "/etc/pki").
Symlink("/dev/fortify/etc/polkit-1", "/etc/polkit-1"). Symlink("/fortify/etc/polkit-1", "/etc/polkit-1").
Symlink("/dev/fortify/etc/profile", "/etc/profile"). Symlink("/fortify/etc/profile", "/etc/profile").
Symlink("/dev/fortify/etc/protocols", "/etc/protocols"). Symlink("/fortify/etc/protocols", "/etc/protocols").
Symlink("/dev/fortify/etc/qemu", "/etc/qemu"). Symlink("/fortify/etc/qemu", "/etc/qemu").
Symlink("/dev/fortify/etc/resolv.conf", "/etc/resolv.conf"). Symlink("/fortify/etc/resolv.conf", "/etc/resolv.conf").
Symlink("/dev/fortify/etc/resolvconf.conf", "/etc/resolvconf.conf"). Symlink("/fortify/etc/resolvconf.conf", "/etc/resolvconf.conf").
Symlink("/dev/fortify/etc/rpc", "/etc/rpc"). Symlink("/fortify/etc/rpc", "/etc/rpc").
Symlink("/dev/fortify/etc/samba", "/etc/samba"). Symlink("/fortify/etc/samba", "/etc/samba").
Symlink("/dev/fortify/etc/sddm.conf", "/etc/sddm.conf"). Symlink("/fortify/etc/sddm.conf", "/etc/sddm.conf").
Symlink("/dev/fortify/etc/secureboot", "/etc/secureboot"). Symlink("/fortify/etc/secureboot", "/etc/secureboot").
Symlink("/dev/fortify/etc/services", "/etc/services"). Symlink("/fortify/etc/services", "/etc/services").
Symlink("/dev/fortify/etc/set-environment", "/etc/set-environment"). Symlink("/fortify/etc/set-environment", "/etc/set-environment").
Symlink("/dev/fortify/etc/shadow", "/etc/shadow"). Symlink("/fortify/etc/shadow", "/etc/shadow").
Symlink("/dev/fortify/etc/shells", "/etc/shells"). Symlink("/fortify/etc/shells", "/etc/shells").
Symlink("/dev/fortify/etc/ssh", "/etc/ssh"). Symlink("/fortify/etc/ssh", "/etc/ssh").
Symlink("/dev/fortify/etc/ssl", "/etc/ssl"). Symlink("/fortify/etc/ssl", "/etc/ssl").
Symlink("/dev/fortify/etc/static", "/etc/static"). Symlink("/fortify/etc/static", "/etc/static").
Symlink("/dev/fortify/etc/subgid", "/etc/subgid"). Symlink("/fortify/etc/subgid", "/etc/subgid").
Symlink("/dev/fortify/etc/subuid", "/etc/subuid"). Symlink("/fortify/etc/subuid", "/etc/subuid").
Symlink("/dev/fortify/etc/sudoers", "/etc/sudoers"). Symlink("/fortify/etc/sudoers", "/etc/sudoers").
Symlink("/dev/fortify/etc/sysctl.d", "/etc/sysctl.d"). Symlink("/fortify/etc/sysctl.d", "/etc/sysctl.d").
Symlink("/dev/fortify/etc/systemd", "/etc/systemd"). Symlink("/fortify/etc/systemd", "/etc/systemd").
Symlink("/dev/fortify/etc/terminfo", "/etc/terminfo"). Symlink("/fortify/etc/terminfo", "/etc/terminfo").
Symlink("/dev/fortify/etc/tmpfiles.d", "/etc/tmpfiles.d"). Symlink("/fortify/etc/tmpfiles.d", "/etc/tmpfiles.d").
Symlink("/dev/fortify/etc/udev", "/etc/udev"). Symlink("/fortify/etc/udev", "/etc/udev").
Symlink("/dev/fortify/etc/udisks2", "/etc/udisks2"). Symlink("/fortify/etc/udisks2", "/etc/udisks2").
Symlink("/dev/fortify/etc/UPower", "/etc/UPower"). Symlink("/fortify/etc/UPower", "/etc/UPower").
Symlink("/dev/fortify/etc/vconsole.conf", "/etc/vconsole.conf"). Symlink("/fortify/etc/vconsole.conf", "/etc/vconsole.conf").
Symlink("/dev/fortify/etc/X11", "/etc/X11"). Symlink("/fortify/etc/X11", "/etc/X11").
Symlink("/dev/fortify/etc/zfs", "/etc/zfs"). Symlink("/fortify/etc/zfs", "/etc/zfs").
Symlink("/dev/fortify/etc/zinputrc", "/etc/zinputrc"). Symlink("/fortify/etc/zinputrc", "/etc/zinputrc").
Symlink("/dev/fortify/etc/zoneinfo", "/etc/zoneinfo"). Symlink("/fortify/etc/zoneinfo", "/etc/zoneinfo").
Symlink("/dev/fortify/etc/zprofile", "/etc/zprofile"). Symlink("/fortify/etc/zprofile", "/etc/zprofile").
Symlink("/dev/fortify/etc/zshenv", "/etc/zshenv"). Symlink("/fortify/etc/zshenv", "/etc/zshenv").
Symlink("/dev/fortify/etc/zshrc", "/etc/zshrc"). Symlink("/fortify/etc/zshrc", "/etc/zshrc").
Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true). Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
Tmpfs("/tmp/fortify.1971", 1048576). Tmpfs("/tmp/fortify.1971", 1048576).
Tmpfs("/run/user", 1048576). Tmpfs("/run/user", 1048576).
@ -304,11 +306,11 @@ var testCasesNixos = []sealTestCase{
DieWithParent: true, DieWithParent: true,
AsInit: true, AsInit: true,
}).SetUID(65534).SetGID(65534). }).SetUID(65534).SetGID(65534).
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue"). Procfs("/proc").
Tmpfs("/dev/fortify", 4096). Tmpfs("/fortify", 4096).
DevTmpfs("/dev").Mqueue("/dev/mqueue").
Bind("/bin", "/bin", false, true). Bind("/bin", "/bin", false, true).
Bind("/boot", "/boot", false, true). Bind("/boot", "/boot", false, true).
Bind("/etc", "/dev/fortify/etc").
Bind("/home", "/home", false, true). Bind("/home", "/home", false, true).
Bind("/lib", "/lib", false, true). Bind("/lib", "/lib", false, true).
Bind("/lib64", "/lib64", false, true). Bind("/lib64", "/lib64", false, true).
@ -353,84 +355,85 @@ var testCasesNixos = []sealTestCase{
Bind("/run/zed.pid", "/run/zed.pid", false, true). Bind("/run/zed.pid", "/run/zed.pid", false, true).
Bind("/run/zed.state", "/run/zed.state", false, true). Bind("/run/zed.state", "/run/zed.state", false, true).
Bind("/dev/dri", "/dev/dri", true, true, true). Bind("/dev/dri", "/dev/dri", true, true, true).
Symlink("/dev/fortify/etc/alsa", "/etc/alsa"). Bind("/etc", "/fortify/etc").
Symlink("/dev/fortify/etc/bashrc", "/etc/bashrc"). Symlink("/fortify/etc/alsa", "/etc/alsa").
Symlink("/dev/fortify/etc/binfmt.d", "/etc/binfmt.d"). Symlink("/fortify/etc/bashrc", "/etc/bashrc").
Symlink("/dev/fortify/etc/dbus-1", "/etc/dbus-1"). Symlink("/fortify/etc/binfmt.d", "/etc/binfmt.d").
Symlink("/dev/fortify/etc/default", "/etc/default"). Symlink("/fortify/etc/dbus-1", "/etc/dbus-1").
Symlink("/dev/fortify/etc/ethertypes", "/etc/ethertypes"). Symlink("/fortify/etc/default", "/etc/default").
Symlink("/dev/fortify/etc/fonts", "/etc/fonts"). Symlink("/fortify/etc/ethertypes", "/etc/ethertypes").
Symlink("/dev/fortify/etc/fstab", "/etc/fstab"). Symlink("/fortify/etc/fonts", "/etc/fonts").
Symlink("/dev/fortify/etc/fuse.conf", "/etc/fuse.conf"). Symlink("/fortify/etc/fstab", "/etc/fstab").
Symlink("/dev/fortify/etc/host.conf", "/etc/host.conf"). Symlink("/fortify/etc/fuse.conf", "/etc/fuse.conf").
Symlink("/dev/fortify/etc/hostid", "/etc/hostid"). Symlink("/fortify/etc/host.conf", "/etc/host.conf").
Symlink("/dev/fortify/etc/hostname", "/etc/hostname"). Symlink("/fortify/etc/hostid", "/etc/hostid").
Symlink("/dev/fortify/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM"). Symlink("/fortify/etc/hostname", "/etc/hostname").
Symlink("/dev/fortify/etc/hosts", "/etc/hosts"). Symlink("/fortify/etc/hostname.CHECKSUM", "/etc/hostname.CHECKSUM").
Symlink("/dev/fortify/etc/inputrc", "/etc/inputrc"). Symlink("/fortify/etc/hosts", "/etc/hosts").
Symlink("/dev/fortify/etc/ipsec.d", "/etc/ipsec.d"). Symlink("/fortify/etc/inputrc", "/etc/inputrc").
Symlink("/dev/fortify/etc/issue", "/etc/issue"). Symlink("/fortify/etc/ipsec.d", "/etc/ipsec.d").
Symlink("/dev/fortify/etc/kbd", "/etc/kbd"). Symlink("/fortify/etc/issue", "/etc/issue").
Symlink("/dev/fortify/etc/libblockdev", "/etc/libblockdev"). Symlink("/fortify/etc/kbd", "/etc/kbd").
Symlink("/dev/fortify/etc/locale.conf", "/etc/locale.conf"). Symlink("/fortify/etc/libblockdev", "/etc/libblockdev").
Symlink("/dev/fortify/etc/localtime", "/etc/localtime"). Symlink("/fortify/etc/locale.conf", "/etc/locale.conf").
Symlink("/dev/fortify/etc/login.defs", "/etc/login.defs"). Symlink("/fortify/etc/localtime", "/etc/localtime").
Symlink("/dev/fortify/etc/lsb-release", "/etc/lsb-release"). Symlink("/fortify/etc/login.defs", "/etc/login.defs").
Symlink("/dev/fortify/etc/lvm", "/etc/lvm"). Symlink("/fortify/etc/lsb-release", "/etc/lsb-release").
Symlink("/dev/fortify/etc/machine-id", "/etc/machine-id"). Symlink("/fortify/etc/lvm", "/etc/lvm").
Symlink("/dev/fortify/etc/man_db.conf", "/etc/man_db.conf"). Symlink("/fortify/etc/machine-id", "/etc/machine-id").
Symlink("/dev/fortify/etc/modprobe.d", "/etc/modprobe.d"). Symlink("/fortify/etc/man_db.conf", "/etc/man_db.conf").
Symlink("/dev/fortify/etc/modules-load.d", "/etc/modules-load.d"). Symlink("/fortify/etc/modprobe.d", "/etc/modprobe.d").
Symlink("/fortify/etc/modules-load.d", "/etc/modules-load.d").
Symlink("/proc/mounts", "/etc/mtab"). Symlink("/proc/mounts", "/etc/mtab").
Symlink("/dev/fortify/etc/nanorc", "/etc/nanorc"). Symlink("/fortify/etc/nanorc", "/etc/nanorc").
Symlink("/dev/fortify/etc/netgroup", "/etc/netgroup"). Symlink("/fortify/etc/netgroup", "/etc/netgroup").
Symlink("/dev/fortify/etc/NetworkManager", "/etc/NetworkManager"). Symlink("/fortify/etc/NetworkManager", "/etc/NetworkManager").
Symlink("/dev/fortify/etc/nix", "/etc/nix"). Symlink("/fortify/etc/nix", "/etc/nix").
Symlink("/dev/fortify/etc/nixos", "/etc/nixos"). Symlink("/fortify/etc/nixos", "/etc/nixos").
Symlink("/dev/fortify/etc/NIXOS", "/etc/NIXOS"). Symlink("/fortify/etc/NIXOS", "/etc/NIXOS").
Symlink("/dev/fortify/etc/nscd.conf", "/etc/nscd.conf"). Symlink("/fortify/etc/nscd.conf", "/etc/nscd.conf").
Symlink("/dev/fortify/etc/nsswitch.conf", "/etc/nsswitch.conf"). Symlink("/fortify/etc/nsswitch.conf", "/etc/nsswitch.conf").
Symlink("/dev/fortify/etc/opensnitchd", "/etc/opensnitchd"). Symlink("/fortify/etc/opensnitchd", "/etc/opensnitchd").
Symlink("/dev/fortify/etc/os-release", "/etc/os-release"). Symlink("/fortify/etc/os-release", "/etc/os-release").
Symlink("/dev/fortify/etc/pam", "/etc/pam"). Symlink("/fortify/etc/pam", "/etc/pam").
Symlink("/dev/fortify/etc/pam.d", "/etc/pam.d"). Symlink("/fortify/etc/pam.d", "/etc/pam.d").
Symlink("/dev/fortify/etc/pipewire", "/etc/pipewire"). Symlink("/fortify/etc/pipewire", "/etc/pipewire").
Symlink("/dev/fortify/etc/pki", "/etc/pki"). Symlink("/fortify/etc/pki", "/etc/pki").
Symlink("/dev/fortify/etc/polkit-1", "/etc/polkit-1"). Symlink("/fortify/etc/polkit-1", "/etc/polkit-1").
Symlink("/dev/fortify/etc/profile", "/etc/profile"). Symlink("/fortify/etc/profile", "/etc/profile").
Symlink("/dev/fortify/etc/protocols", "/etc/protocols"). Symlink("/fortify/etc/protocols", "/etc/protocols").
Symlink("/dev/fortify/etc/qemu", "/etc/qemu"). Symlink("/fortify/etc/qemu", "/etc/qemu").
Symlink("/dev/fortify/etc/resolv.conf", "/etc/resolv.conf"). Symlink("/fortify/etc/resolv.conf", "/etc/resolv.conf").
Symlink("/dev/fortify/etc/resolvconf.conf", "/etc/resolvconf.conf"). Symlink("/fortify/etc/resolvconf.conf", "/etc/resolvconf.conf").
Symlink("/dev/fortify/etc/rpc", "/etc/rpc"). Symlink("/fortify/etc/rpc", "/etc/rpc").
Symlink("/dev/fortify/etc/samba", "/etc/samba"). Symlink("/fortify/etc/samba", "/etc/samba").
Symlink("/dev/fortify/etc/sddm.conf", "/etc/sddm.conf"). Symlink("/fortify/etc/sddm.conf", "/etc/sddm.conf").
Symlink("/dev/fortify/etc/secureboot", "/etc/secureboot"). Symlink("/fortify/etc/secureboot", "/etc/secureboot").
Symlink("/dev/fortify/etc/services", "/etc/services"). Symlink("/fortify/etc/services", "/etc/services").
Symlink("/dev/fortify/etc/set-environment", "/etc/set-environment"). Symlink("/fortify/etc/set-environment", "/etc/set-environment").
Symlink("/dev/fortify/etc/shadow", "/etc/shadow"). Symlink("/fortify/etc/shadow", "/etc/shadow").
Symlink("/dev/fortify/etc/shells", "/etc/shells"). Symlink("/fortify/etc/shells", "/etc/shells").
Symlink("/dev/fortify/etc/ssh", "/etc/ssh"). Symlink("/fortify/etc/ssh", "/etc/ssh").
Symlink("/dev/fortify/etc/ssl", "/etc/ssl"). Symlink("/fortify/etc/ssl", "/etc/ssl").
Symlink("/dev/fortify/etc/static", "/etc/static"). Symlink("/fortify/etc/static", "/etc/static").
Symlink("/dev/fortify/etc/subgid", "/etc/subgid"). Symlink("/fortify/etc/subgid", "/etc/subgid").
Symlink("/dev/fortify/etc/subuid", "/etc/subuid"). Symlink("/fortify/etc/subuid", "/etc/subuid").
Symlink("/dev/fortify/etc/sudoers", "/etc/sudoers"). Symlink("/fortify/etc/sudoers", "/etc/sudoers").
Symlink("/dev/fortify/etc/sysctl.d", "/etc/sysctl.d"). Symlink("/fortify/etc/sysctl.d", "/etc/sysctl.d").
Symlink("/dev/fortify/etc/systemd", "/etc/systemd"). Symlink("/fortify/etc/systemd", "/etc/systemd").
Symlink("/dev/fortify/etc/terminfo", "/etc/terminfo"). Symlink("/fortify/etc/terminfo", "/etc/terminfo").
Symlink("/dev/fortify/etc/tmpfiles.d", "/etc/tmpfiles.d"). Symlink("/fortify/etc/tmpfiles.d", "/etc/tmpfiles.d").
Symlink("/dev/fortify/etc/udev", "/etc/udev"). Symlink("/fortify/etc/udev", "/etc/udev").
Symlink("/dev/fortify/etc/udisks2", "/etc/udisks2"). Symlink("/fortify/etc/udisks2", "/etc/udisks2").
Symlink("/dev/fortify/etc/UPower", "/etc/UPower"). Symlink("/fortify/etc/UPower", "/etc/UPower").
Symlink("/dev/fortify/etc/vconsole.conf", "/etc/vconsole.conf"). Symlink("/fortify/etc/vconsole.conf", "/etc/vconsole.conf").
Symlink("/dev/fortify/etc/X11", "/etc/X11"). Symlink("/fortify/etc/X11", "/etc/X11").
Symlink("/dev/fortify/etc/zfs", "/etc/zfs"). Symlink("/fortify/etc/zfs", "/etc/zfs").
Symlink("/dev/fortify/etc/zinputrc", "/etc/zinputrc"). Symlink("/fortify/etc/zinputrc", "/etc/zinputrc").
Symlink("/dev/fortify/etc/zoneinfo", "/etc/zoneinfo"). Symlink("/fortify/etc/zoneinfo", "/etc/zoneinfo").
Symlink("/dev/fortify/etc/zprofile", "/etc/zprofile"). Symlink("/fortify/etc/zprofile", "/etc/zprofile").
Symlink("/dev/fortify/etc/zshenv", "/etc/zshenv"). Symlink("/fortify/etc/zshenv", "/etc/zshenv").
Symlink("/dev/fortify/etc/zshrc", "/etc/zshrc"). Symlink("/fortify/etc/zshrc", "/etc/zshrc").
Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true). Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
Tmpfs("/tmp/fortify.1971", 1048576). Tmpfs("/tmp/fortify.1971", 1048576).
Tmpfs("/run/user", 1048576). Tmpfs("/run/user", 1048576).
@ -579,6 +582,10 @@ func (s *stubNixOS) Exit(code int) {
panic("called exit on stub with code " + strconv.Itoa(code)) panic("called exit on stub with code " + strconv.Itoa(code))
} }
func (s *stubNixOS) Stdout() io.Writer {
panic("requested stdout")
}
func (s *stubNixOS) FshimPath() string { func (s *stubNixOS) FshimPath() string {
return "/nix/store/00000000000000000000000000000000-fortify-0.0.10/bin/.fshim" return "/nix/store/00000000000000000000000000000000-fortify-0.0.10/bin/.fshim"
} }

View File

@ -1,13 +1,16 @@
package app package app
import ( import (
"os" "errors"
"git.ophivana.moe/security/fortify/dbus" "git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/helper/bwrap" "git.ophivana.moe/security/fortify/helper/bwrap"
"git.ophivana.moe/security/fortify/internal/linux"
"git.ophivana.moe/security/fortify/internal/system" "git.ophivana.moe/security/fortify/internal/system"
) )
const fTmp = "/fortify"
// Config is used to seal an *App // Config is used to seal an *App
type Config struct { type Config struct {
// D-Bus application ID // D-Bus application ID
@ -47,6 +50,8 @@ type SandboxConfig struct {
UserNS bool `json:"userns,omitempty"` UserNS bool `json:"userns,omitempty"`
// share net namespace // share net namespace
Net bool `json:"net,omitempty"` Net bool `json:"net,omitempty"`
// share all devices
Dev bool `json:"dev,omitempty"`
// do not run in new session // do not run in new session
NoNewSession bool `json:"no_new_session,omitempty"` NoNewSession bool `json:"no_new_session,omitempty"`
// map target user uid to privileged user uid in the user namespace // map target user uid to privileged user uid in the user namespace
@ -60,6 +65,8 @@ type SandboxConfig struct {
Filesystem []*FilesystemConfig `json:"filesystem"` Filesystem []*FilesystemConfig `json:"filesystem"`
// symlinks created inside the sandbox // symlinks created inside the sandbox
Link [][2]string `json:"symlink"` Link [][2]string `json:"symlink"`
// automatically set up /etc symlinks
AutoEtc bool `json:"auto_etc"`
// paths to override by mounting tmpfs over them // paths to override by mounting tmpfs over them
Override []string `json:"override"` Override []string `json:"override"`
} }
@ -79,13 +86,16 @@ type FilesystemConfig struct {
// Bwrap returns the address of the corresponding bwrap.Config to s. // Bwrap returns the address of the corresponding bwrap.Config to s.
// Note that remaining tmpfs entries must be queued by the caller prior to launch. // Note that remaining tmpfs entries must be queued by the caller prior to launch.
func (s *SandboxConfig) Bwrap(uid int) *bwrap.Config { func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
if s == nil { if s == nil {
return nil return nil, errors.New("nil sandbox config")
} }
var uid int
if !s.UseRealUID { if !s.UseRealUID {
uid = 65534 uid = 65534
} else {
uid = os.Geteuid()
} }
conf := (&bwrap.Config{ conf := (&bwrap.Config{
@ -99,11 +109,21 @@ func (s *SandboxConfig) Bwrap(uid int) *bwrap.Config {
AsInit: true, AsInit: true,
// initialise map // initialise map
Chmod: make(map[string]os.FileMode), Chmod: make(bwrap.ChmodConfig),
}). }).
SetUID(uid).SetGID(uid). SetUID(uid).SetGID(uid).
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue"). Procfs("/proc").
Tmpfs("/dev/fortify", 4*1024) Tmpfs(fTmp, 4*1024)
if !s.Dev {
conf.DevTmpfs("/dev").Mqueue("/dev/mqueue")
} else {
conf.Bind("/dev", "/dev", false, true, true)
}
if !s.AutoEtc {
conf.Dir("/etc")
}
for _, c := range s.Filesystem { for _, c := range s.Filesystem {
if c == nil { if c == nil {
@ -121,7 +141,29 @@ func (s *SandboxConfig) Bwrap(uid int) *bwrap.Config {
conf.Symlink(l[0], l[1]) conf.Symlink(l[0], l[1])
} }
return conf if s.AutoEtc {
conf.Bind("/etc", fTmp+"/etc")
// link host /etc contents to prevent passwd/group from being overwritten
if d, err := os.ReadDir("/etc"); err != nil {
return nil, err
} else {
for _, ent := range d {
name := ent.Name()
switch name {
case "passwd":
case "group":
case "mtab":
conf.Symlink("/proc/mounts", "/etc/"+name)
default:
conf.Symlink(fTmp+"/etc/"+name, "/etc/"+name)
}
}
}
}
return conf, nil
} }
// Template returns a fully populated instance of Config. // Template returns a fully populated instance of Config.
@ -143,6 +185,8 @@ func Template() *Config {
UserNS: true, UserNS: true,
Net: true, Net: true,
NoNewSession: true, NoNewSession: true,
UseRealUID: true,
Dev: true,
Wayland: false, Wayland: false,
// example API credentials pulled from Google Chrome // example API credentials pulled from Google Chrome
// DO NOT USE THESE IN A REAL BROWSER // DO NOT USE THESE IN A REAL BROWSER
@ -152,12 +196,15 @@ func Template() *Config {
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT", "GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT",
}, },
Filesystem: []*FilesystemConfig{ Filesystem: []*FilesystemConfig{
{Src: "/nix"}, {Src: "/nix/store"},
{Src: "/storage/emulated/0", Write: true, Must: true}, {Src: "/run/current-system"},
{Src: "/data/user/0", Dst: "/data/data", Write: true, Must: true}, {Src: "/run/opengl-driver"},
{Src: "/var/tmp", Write: true}, {Src: "/var/db/nix-channels"},
{Src: "/home/chronos", Write: true, Must: true},
{Src: "/dev/dri", Device: true},
}, },
Link: [][2]string{{"/dev/fortify/etc", "/etc"}}, Link: [][2]string{{"/run/user/65534", "/run/user/150"}},
AutoEtc: true,
Override: []string{"/var/run/nscd"}, Override: []string{"/var/run/nscd"},
}, },
SystemBus: &dbus.Config{ SystemBus: &dbus.Config{

View File

@ -159,6 +159,7 @@ func (a *app) Seal(config *Config) error {
UserNS: true, UserNS: true,
Net: true, Net: true,
NoNewSession: true, NoNewSession: true,
AutoEtc: true,
} }
// bind entries in / // bind entries in /
if d, err := a.os.ReadDir("/"); err != nil { if d, err := a.os.ReadDir("/"); err != nil {
@ -173,9 +174,8 @@ func (a *app) Seal(config *Config) error {
case "/run": case "/run":
case "/tmp": case "/tmp":
case "/mnt": case "/mnt":
case "/etc": case "/etc":
b = append(b, &FilesystemConfig{Src: p, Dst: "/dev/fortify/etc", Write: false, Must: true})
default: default:
b = append(b, &FilesystemConfig{Src: p, Write: true, Must: true}) b = append(b, &FilesystemConfig{Src: p, Write: true, Must: true})
} }
@ -208,35 +208,14 @@ func (a *app) Seal(config *Config) error {
if config.Confinement.Enablements.Has(system.EX11) || config.Confinement.Enablements.Has(system.EWayland) { if config.Confinement.Enablements.Has(system.EX11) || config.Confinement.Enablements.Has(system.EWayland) {
conf.Filesystem = append(conf.Filesystem, &FilesystemConfig{Src: "/dev/dri", Device: true}) conf.Filesystem = append(conf.Filesystem, &FilesystemConfig{Src: "/dev/dri", Device: true})
} }
// link host /etc to prevent passwd/group from being overwritten
if d, err := a.os.ReadDir("/etc"); err != nil {
return err
} else {
b := make([][2]string, 0, len(d))
for _, ent := range d {
name := ent.Name()
switch name {
case "passwd":
case "group":
case "mtab":
b = append(b, [2]string{
"/proc/mounts",
"/etc/" + name,
})
default:
b = append(b, [2]string{
"/dev/fortify/etc/" + name,
"/etc/" + name,
})
}
}
conf.Link = append(conf.Link, b...)
}
config.Confinement.Sandbox = conf config.Confinement.Sandbox = conf
} }
seal.sys.bwrap = config.Confinement.Sandbox.Bwrap(a.os.Geteuid()) if b, err := config.Confinement.Sandbox.Bwrap(a.os); err != nil {
return err
} else {
seal.sys.bwrap = b
}
seal.sys.override = config.Confinement.Sandbox.Override seal.sys.override = config.Confinement.Sandbox.Override
if seal.sys.bwrap.SetEnv == nil { if seal.sys.bwrap.SetEnv == nil {
seal.sys.bwrap.SetEnv = make(map[string]string) seal.sys.bwrap.SetEnv = make(map[string]string)

View File

@ -1,6 +1,7 @@
package linux package linux
import ( import (
"io"
"io/fs" "io/fs"
"os/user" "os/user"
"path" "path"
@ -31,6 +32,8 @@ type System interface {
Open(name string) (fs.File, error) Open(name string) (fs.File, error)
// Exit provides [os.Exit]. // Exit provides [os.Exit].
Exit(code int) Exit(code int)
// Stdout provides [os.Stdout].
Stdout() io.Writer
// FshimPath returns an absolute path to the fshim binary. // FshimPath returns an absolute path to the fshim binary.
FshimPath() string FshimPath() string

View File

@ -2,6 +2,7 @@ package linux
import ( import (
"errors" "errors"
"io"
"io/fs" "io/fs"
"os" "os"
"os/exec" "os/exec"
@ -34,6 +35,7 @@ func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(na
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) } func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) } func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
func (s *Std) Exit(code int) { fmsg.Exit(code) } func (s *Std) Exit(code int) { fmsg.Exit(code) }
func (s *Std) Stdout() io.Writer { return os.Stdout }
const xdgRuntimeDir = "XDG_RUNTIME_DIR" const xdgRuntimeDir = "XDG_RUNTIME_DIR"

View File

@ -1,25 +0,0 @@
package main
import (
_ "embed"
"flag"
"fmt"
)
var (
//go:embed LICENSE
license string
printLicense bool
)
func init() {
flag.BoolVar(&printLicense, "license", false, "Print license")
}
func tryLicense() {
if printLicense {
fmt.Println(license)
os.Exit(0)
}
}

205
main.go
View File

@ -1,16 +1,26 @@
package main package main
import ( import (
_ "embed"
"encoding/json"
"flag" "flag"
"fmt"
"text/tabwriter"
"git.ophivana.moe/security/fortify/dbus"
"git.ophivana.moe/security/fortify/internal" "git.ophivana.moe/security/fortify/internal"
"git.ophivana.moe/security/fortify/internal/app" "git.ophivana.moe/security/fortify/internal/app"
"git.ophivana.moe/security/fortify/internal/fmsg" "git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/linux" "git.ophivana.moe/security/fortify/internal/linux"
"git.ophivana.moe/security/fortify/internal/state"
"git.ophivana.moe/security/fortify/internal/system"
) )
var ( var (
flagVerbose bool flagVerbose bool
//go:embed LICENSE
license string
) )
func init() { func init() {
@ -25,32 +35,194 @@ func main() {
// not fatal: this program runs as the privileged user // not fatal: this program runs as the privileged user
} }
flag.Parse()
fmsg.SetVerbose(flagVerbose)
if os.SdBooted() {
fmsg.VPrintln("system booted with systemd as init system")
}
// root check
if os.Geteuid() == 0 { if os.Geteuid() == 0 {
fmsg.Fatal("this program must not run as root") fmsg.Fatal("this program must not run as root")
panic("unreachable") panic("unreachable")
} }
// version/license/template command early exit flag.CommandLine.Usage = func() {
tryVersion() fmt.Println()
tryLicense() fmt.Println("Usage:\tfortify [-v] COMMAND [OPTIONS]")
tryTemplate() fmt.Println()
fmt.Println("Commands:")
w := tabwriter.NewWriter(os.Stdout(), 0, 1, 4, ' ', 0)
commands := [][2]string{
{"app", "Launch app defined by the specified config file"},
{"run", "Configure and start a permissive default sandbox"},
{"ps", "List active apps and their state"},
{"version", "Show fortify version"},
{"license", "Show full license text"},
{"template", "Produce a config template"},
{"help", "Show this help message"},
}
for _, c := range commands {
_, _ = fmt.Fprintf(w, "\t%s\t%s\n", c[0], c[1])
}
if err := w.Flush(); err != nil {
fmt.Printf("fortify: cannot write command list: %v\n", err)
}
fmt.Println()
}
flag.Parse()
fmsg.SetVerbose(flagVerbose)
// state query command early exit args := flag.Args()
tryState() if len(args) == 0 {
flag.CommandLine.Usage()
fmsg.Exit(0)
}
switch args[0] {
case "version": // print version string
if v, ok := internal.Check(internal.Version); ok {
fmt.Println(v)
} else {
fmt.Println("impure")
}
fmsg.Exit(0)
case "license": // print embedded license
fmt.Println(license)
fmsg.Exit(0)
case "template": // print full template configuration
if s, err := json.MarshalIndent(app.Template(), "", " "); err != nil {
fmsg.Fatalf("cannot generate template: %v", err)
panic("unreachable")
} else {
fmt.Println(string(s))
}
fmsg.Exit(0)
case "help": // print help message
flag.CommandLine.Usage()
fmsg.Exit(0)
case "ps": // print all state info
var w *tabwriter.Writer
state.MustPrintLauncherStateSimpleGlobal(&w, os.Paths().RunDirPath)
if w != nil {
if err := w.Flush(); err != nil {
fmsg.Println("cannot format output:", err)
}
} else {
fmt.Println("No information available")
}
fmsg.Exit(0)
case "app": // launch app from configuration file
if len(args) < 2 {
fmsg.Fatal("app requires at least 1 argument")
}
config := new(app.Config)
if f, err := os.Open(args[1]); err != nil {
fmsg.Fatalf("cannot access config file %q: %s", args[1], err)
panic("unreachable")
} else if err = json.NewDecoder(f).Decode(&config); err != nil {
fmsg.Fatalf("cannot parse config file %q: %s", args[1], err)
panic("unreachable")
}
// append extra args
config.Command = append(config.Command, args[2:]...)
// invoke app
runApp(config)
case "run": // run app in permissive defaults usage pattern
set := flag.NewFlagSet("run", flag.ExitOnError)
var (
dbusConfigSession string
dbusConfigSystem string
dbusID string
mpris bool
dbusVerbose bool
userName string
enablements [system.ELen]bool
launchMethodText string
)
set.StringVar(&dbusConfigSession, "dbus-config", "builtin", "Path to D-Bus proxy config file, or \"builtin\" for defaults")
set.StringVar(&dbusConfigSystem, "dbus-system", "nil", "Path to system D-Bus proxy config file, or \"nil\" to disable")
set.StringVar(&dbusID, "dbus-id", "", "D-Bus ID of application, leave empty to disable own paths, has no effect if custom config is available")
set.BoolVar(&mpris, "mpris", false, "Allow owning MPRIS D-Bus path, has no effect if custom config is available")
set.BoolVar(&dbusVerbose, "dbus-log", false, "Force logging in the D-Bus proxy")
set.StringVar(&userName, "u", "chronos", "Passwd name of user to run as")
set.BoolVar(&enablements[system.EWayland], "wayland", false, "Share Wayland socket")
set.BoolVar(&enablements[system.EX11], "X", false, "Share X11 socket and allow connection")
set.BoolVar(&enablements[system.EDBus], "dbus", false, "Proxy D-Bus connection")
set.BoolVar(&enablements[system.EPulse], "pulse", false, "Share PulseAudio socket and cookie")
methodHelpString := "Method of launching the child process, can be one of \"sudo\""
if os.SdBooted() {
methodHelpString += ", \"systemd\""
}
set.StringVar(&launchMethodText, "method", "sudo", methodHelpString)
// Ignore errors; set is set for ExitOnError.
_ = set.Parse(args[1:])
// initialise config from flags
config := &app.Config{
ID: dbusID,
User: userName,
Command: set.Args(),
Method: launchMethodText,
}
// enablements from flags
for i := system.Enablement(0); i < system.Enablement(system.ELen); i++ {
if enablements[i] {
config.Confinement.Enablements.Set(i)
}
}
// parse D-Bus config file from flags if applicable
if enablements[system.EDBus] {
if dbusConfigSession == "builtin" {
config.Confinement.SessionBus = dbus.NewConfig(dbusID, true, mpris)
} else {
if c, err := dbus.NewConfigFromFile(dbusConfigSession); err != nil {
fmsg.Fatalf("cannot load session bus proxy config from %q: %s", dbusConfigSession, err)
} else {
config.Confinement.SessionBus = c
}
}
// system bus proxy is optional
if dbusConfigSystem != "nil" {
if c, err := dbus.NewConfigFromFile(dbusConfigSystem); err != nil {
fmsg.Fatalf("cannot load system bus proxy config from %q: %s", dbusConfigSystem, err)
} else {
config.Confinement.SystemBus = c
}
}
// override log from configuration
if dbusVerbose {
config.Confinement.SessionBus.Log = true
config.Confinement.SystemBus.Log = true
}
}
// invoke app
runApp(config)
default:
fmsg.Fatalf("%q is not a valid command", args[0])
}
panic("unreachable")
}
func runApp(config *app.Config) {
if os.SdBooted() {
fmsg.VPrintln("system booted with systemd as init system")
}
// invoke app
a, err := app.New(os) a, err := app.New(os)
if err != nil { if err != nil {
fmsg.Fatalf("cannot create app: %s\n", err) fmsg.Fatalf("cannot create app: %s\n", err)
} else if err = a.Seal(loadConfig()); err != nil { } else if err = a.Seal(config); err != nil {
logBaseError(err, "cannot seal app:") logBaseError(err, "cannot seal app:")
fmsg.Exit(1) fmsg.Exit(1)
} else if err = a.Start(); err != nil { } else if err = a.Start(); err != nil {
@ -69,4 +241,5 @@ func main() {
fmsg.Println("inner wait failed:", err) fmsg.Println("inner wait failed:", err)
} }
fmsg.Exit(r) fmsg.Exit(r)
panic("unreachable")
} }

230
nixos.nix
View File

@ -15,6 +15,7 @@ let
mapAttrsToList mapAttrsToList
foldlAttrs foldlAttrs
optional optional
optionals
; ;
cfg = config.environment.fortify; cfg = config.environment.fortify;
@ -39,6 +40,7 @@ in
listOf listOf
attrsOf attrsOf
nullOr nullOr
functionTo
; ;
in in
attrsOf (submodule { attrsOf (submodule {
@ -54,6 +56,14 @@ in
launchers = mkOption { launchers = mkOption {
type = attrsOf (submodule { type = attrsOf (submodule {
options = { options = {
id = mkOption {
type = nullOr str;
default = null;
description = ''
Freedesktop application ID.
'';
};
command = mkOption { command = mkOption {
type = nullOr str; type = nullOr str;
default = null; default = null;
@ -63,17 +73,29 @@ in
''; '';
}; };
method = mkOption {
type = enum [
"simple"
"sudo"
"systemd"
];
default = "systemd";
description = ''
Launch method for the sandboxed program.
'';
};
dbus = { dbus = {
config = mkOption { session = mkOption {
type = nullOr anything; type = nullOr (functionTo anything);
default = null; default = null;
description = '' description = ''
D-Bus custom configuration. D-Bus session bus custom configuration.
Setting this to null will enable built-in defaults. Setting this to null will enable built-in defaults.
''; '';
}; };
configSystem = mkOption { system = mkOption {
type = nullOr anything; type = nullOr anything;
default = null; default = null;
description = '' description = ''
@ -81,24 +103,55 @@ in
Setting this to null will disable the system bus proxy. Setting this to null will disable the system bus proxy.
''; '';
}; };
};
id = mkOption { env = mkOption {
type = nullOr str; type = nullOr (attrsOf str);
default = null; default = null;
description = '' description = ''
D-Bus application id. Environment variables to set for the initial process in the sandbox.
Setting this to null will disable own path in defaults. '';
Has no effect if custom configuration is set. };
'';
nix = mkEnableOption ''
Whether to allow nix daemon connections from within sandbox.
'';
userns = mkEnableOption ''
Whether to allow userns within sandbox.
'';
useRealUid = mkEnableOption ''
Whether to map to fortify's real UID within the sandbox.
'';
net =
mkEnableOption ''
Whether to allow network access within sandbox.
''
// {
default = true;
}; };
mpris = mkOption { gpu = mkOption {
type = bool; type = nullOr bool;
default = false; default = null;
description = '' description = ''
Whether to enable MPRIS in D-Bus defaults. Target process GPU and driver access.
''; Setting this to null will enable GPU whenever X or Wayland is enabled.
}; '';
};
dev = mkEnableOption ''
Whether to allow access to all devices within sandbox.
'';
extraPaths = mkOption {
type = listOf anything;
default = [ ];
description = ''
Extra paths to make available inside the sandbox.
'';
}; };
capability = { capability = {
@ -143,18 +196,6 @@ in
Setting this to null will default package name to wrapper name. Setting this to null will default package name to wrapper name.
''; '';
}; };
method = mkOption {
type = enum [
"simple"
"sudo"
"systemd"
];
default = "systemd";
description = ''
Launch method for the sandboxed program.
'';
};
}; };
}); });
default = { }; default = { };
@ -222,26 +263,113 @@ in
name: launcher: name: launcher:
with launcher.capability; with launcher.capability;
let let
command = if launcher.command == null then name else launcher.command; extendDBusDefault = id: ext: {
filter = true;
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
own =
(optionals (launcher.id != null) [
"${id}.*"
"org.mpris.MediaPlayer2.${id}.*"
])
++ ext.own;
call = {
"org.freedesktop.portal.*" = "*";
} // ext.call;
broadcast = {
"org.freedesktop.portal.*" = "@/org/freedesktop/portal/*";
} // ext.broadcast;
};
dbusConfig = dbusConfig =
if launcher.dbus.config != null then let
pkgs.writeText "${name}-dbus.json" (builtins.toJSON launcher.dbus.config) default = {
else talk = [ ];
null; own = [ ];
dbusSystem = call = { };
if launcher.dbus.configSystem != null then broadcast = { };
pkgs.writeText "${name}-dbus-system.json" (builtins.toJSON launcher.dbus.configSystem) };
else in
null; {
capArgs = session_bus =
(if wayland then " --wayland" else "") if launcher.dbus.session != null then
+ (if x11 then " -X" else "") (launcher.dbus.session (extendDBusDefault launcher.id))
+ (if dbus then " --dbus" else "") else
+ (if pulse then " --pulse" else "") (extendDBusDefault launcher.id default);
+ (if launcher.dbus.mpris then " --mpris" else "") system_bus = launcher.dbus.system;
+ (if launcher.dbus.id != null then " --dbus-id ${launcher.dbus.id}" else "") };
+ (if dbusConfig != null then " --dbus-config ${dbusConfig}" else "") command = if launcher.command == null then name else launcher.command;
+ (if dbusSystem != null then " --dbus-system ${dbusSystem}" else ""); enablements =
(if wayland then 1 else 0)
+ (if x11 then 2 else 0)
+ (if dbus then 4 else 0)
+ (if pulse then 8 else 0);
conf = {
inherit (launcher) id method;
inherit user;
command = [
"/run/current-system/sw/bin/zsh"
(pkgs.writeShellScript "${name}-start" ("exec " + command + " $@"))
];
confinement = {
sandbox = {
inherit (launcher)
userns
net
dev
env
;
use_real_uid = launcher.useRealUid;
filesystem =
[
{ src = "/bin"; }
{ src = "/usr/bin"; }
{ src = "/nix/store"; }
{ src = "/run/current-system"; }
{
src = "/sys/block";
require = false;
}
{
src = "/sys/bus";
require = false;
}
{
src = "/sys/class";
require = false;
}
{
src = "/sys/dev";
require = false;
}
{
src = "/sys/devices";
require = false;
}
{
src = "/home/${user}";
write = true;
require = true;
}
]
++ optionals launcher.nix [
{ src = "/nix/var"; }
{ src = "/var/db/nix-channels"; }
]
++ optionals (if launcher.gpu != null then launcher.gpu else wayland || x11) [
{ src = "/run/opengl-driver"; }
{
src = "/dev/dri";
dev = true;
}
]
++ launcher.extraPaths;
auto_etc = true;
override = [ "/var/run/nscd" ];
};
inherit enablements;
inherit (dbusConfig) session_bus system_bus;
};
};
in in
pkgs.writeShellScriptBin name ( pkgs.writeShellScriptBin name (
if launcher.method == "simple" then if launcher.method == "simple" then
@ -250,7 +378,7 @@ in
'' ''
else else
'' ''
exec fortify${capArgs} --method ${launcher.method} -u ${user} $SHELL -c "exec ${command} $@" exec fortify app ${pkgs.writeText "fortify-${name}.json" (builtins.toJSON conf)} $@
'' ''
) )
) launchers; ) launchers;

View File

@ -10,7 +10,7 @@
buildGoModule rec { buildGoModule rec {
pname = "fortify"; pname = "fortify";
version = "0.0.11"; version = "0.1.0";
src = ./.; src = ./.;
vendorHash = null; vendorHash = null;

View File

@ -1,35 +0,0 @@
package main
import (
"flag"
"fmt"
"text/tabwriter"
"git.ophivana.moe/security/fortify/internal/fmsg"
"git.ophivana.moe/security/fortify/internal/state"
)
var (
stateActionEarly bool
)
func init() {
flag.BoolVar(&stateActionEarly, "state", false, "print state information of active launchers")
}
// tryState is called after app initialisation
func tryState() {
if stateActionEarly {
var w *tabwriter.Writer
state.MustPrintLauncherStateSimpleGlobal(&w, os.Paths().RunDirPath)
if w != nil {
if err := w.Flush(); err != nil {
fmsg.Println("cannot format output:", err)
}
} else {
fmt.Println("No information available")
}
fmsg.Exit(0)
}
}

View File

@ -1,27 +0,0 @@
package main
import (
"flag"
"fmt"
"git.ophivana.moe/security/fortify/internal"
)
var (
printVersion bool
)
func init() {
flag.BoolVar(&printVersion, "V", false, "Print version")
}
func tryVersion() {
if printVersion {
if v, ok := internal.Check(internal.Version); ok {
fmt.Println(v)
} else {
fmt.Println("impure")
}
os.Exit(0)
}
}