Compare commits

..

No commits in common. "f545e154f0c6e838cee130c3d1fae47687e5fdc4" and "f955b15b84354c2f83ab542512f198404c1ec52f" have entirely different histories.

11 changed files with 117 additions and 79 deletions

View File

@ -8,7 +8,9 @@ on:
jobs: jobs:
release: release:
name: Create release name: Create release
runs-on: nix runs-on: ubuntu-latest
permissions:
actions: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -18,6 +20,27 @@ jobs:
with: with:
go-version: '>=1.23.0' go-version: '>=1.23.0'
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
# explicitly enable sandbox
install_options: --daemon
extra_nix_config: |
sandbox = true
system-features = nixos-test benchmark big-parallel kvm
enable_kvm: true
- name: Ensure environment
run: >-
apt-get update && apt-get install -y sqlite3
if: ${{ runner.os == 'Linux' }}
- name: Restore Nix store
uses: nix-community/cache-nix-action@v5
with:
primary-key: build-dist-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-dist-${{ runner.os }}-
- name: Build for release - name: Build for release
run: nix build --print-out-paths --print-build-logs .#dist run: nix build --print-out-paths --print-build-logs .#dist

View File

@ -7,11 +7,39 @@ on:
jobs: jobs:
test: test:
name: Run NixOS test name: Run NixOS test
runs-on: nix runs-on: ubuntu-latest
permissions:
actions: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
# explicitly enable sandbox
install_options: --daemon
extra_nix_config: |
sandbox = true
system-features = nixos-test benchmark big-parallel kvm
enable_kvm: true
- name: Ensure environment
run: >-
apt-get update && apt-get install -y sqlite3
if: ${{ runner.os == 'Linux' }}
- name: Restore Nix store
uses: nix-community/cache-nix-action@v5
with:
primary-key: flake-check-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: flake-check-${{ runner.os }}-
gc-max-store-size-linux: 1073741824
purge: true
purge-prefixes: flake-check-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Run tests - name: Run tests
run: | run: |
nix --print-build-logs --experimental-features 'nix-command flakes' flake check nix --print-build-logs --experimental-features 'nix-command flakes' flake check
@ -26,11 +54,39 @@ jobs:
dist: dist:
name: Create distribution name: Create distribution
runs-on: nix runs-on: ubuntu-latest
permissions:
actions: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v30
with:
# explicitly enable sandbox
install_options: --daemon
extra_nix_config: |
sandbox = true
system-features = nixos-test benchmark big-parallel kvm
enable_kvm: true
- name: Ensure environment
run: >-
apt-get update && apt-get install -y sqlite3
if: ${{ runner.os == 'Linux' }}
- name: Restore Nix store
uses: nix-community/cache-nix-action@v5
with:
primary-key: build-dist-${{ runner.os }}-${{ hashFiles('**/*.nix') }}
restore-prefixes-first-match: build-dist-${{ runner.os }}-
gc-max-store-size-linux: 1073741824
purge: true
purge-prefixes: build-dist-${{ runner.os }}-
purge-created: 60
purge-primary-key: never
- name: Build for test - name: Build for test
id: build-test id: build-test
run: >- run: >-

View File

@ -62,6 +62,7 @@ var testCasesNixos = []sealTestCase{
Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute). Ensure("/run/user/1971/fortify", 0700).UpdatePermType(system.User, "/run/user/1971/fortify", acl.Execute).
Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset Ensure("/run/user/1971", 0700).UpdatePermType(system.User, "/run/user/1971", acl.Execute). // this is ordered as is because the previous Ensure only calls mkdir if XDG_RUNTIME_DIR is unset
Ephemeral(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute). Ephemeral(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", 0700).UpdatePermType(system.Process, "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1", acl.Execute).
Link("/run/user/1971/wayland-0", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/wayland").
UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute). UpdatePermType(system.EWayland, "/run/user/1971/wayland-0", acl.Read, acl.Write, acl.Execute).
Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse"). Link("/run/user/1971/pulse/native", "/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse").
CopyFile("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie"). CopyFile("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", "/home/ophestra/xdg/config/pulse/cookie").
@ -102,7 +103,7 @@ var testCasesNixos = []sealTestCase{
"SHELL": "/run/current-system/sw/bin/zsh", "SHELL": "/run/current-system/sw/bin/zsh",
"TERM": "xterm-256color", "TERM": "xterm-256color",
"USER": "u0_a1", "USER": "u0_a1",
"WAYLAND_DISPLAY": "wayland-0", "WAYLAND_DISPLAY": "/run/user/1971/wayland-0",
"XDG_RUNTIME_DIR": "/run/user/1971", "XDG_RUNTIME_DIR": "/run/user/1971",
"XDG_SESSION_CLASS": "user", "XDG_SESSION_CLASS": "user",
"XDG_SESSION_TYPE": "tty", "XDG_SESSION_TYPE": "tty",
@ -211,7 +212,7 @@ var testCasesNixos = []sealTestCase{
Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", false, true). Bind("/var/lib/persist/module/fortify/0/1", "/var/lib/persist/module/fortify/0/1", false, true).
CopyBind("/etc/passwd", []byte("u0_a1:x:1971:1971:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")). CopyBind("/etc/passwd", []byte("u0_a1:x:1971:1971:Fortify:/var/lib/persist/module/fortify/0/1:/run/current-system/sw/bin/zsh\n")).
CopyBind("/etc/group", []byte("fortify:x:1971:\n")). CopyBind("/etc/group", []byte("fortify:x:1971:\n")).
Bind("/run/user/1971/wayland-0", "/run/user/1971/wayland-0"). Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/wayland", "/run/user/1971/wayland-0").
Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native"). Bind("/run/user/1971/fortify/8e2c76b066dabe574cf073bdb46eb5c1/pulse", "/run/user/1971/pulse/native").
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", fst.Tmp+"/pulse-cookie"). Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/pulse-cookie", fst.Tmp+"/pulse-cookie").
Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus"). Bind("/tmp/fortify.1971/8e2c76b066dabe574cf073bdb46eb5c1/bus", "/run/user/1971/bus").

View File

@ -267,7 +267,7 @@ var testCasesPd = []sealTestCase{
"SHELL": "/run/current-system/sw/bin/zsh", "SHELL": "/run/current-system/sw/bin/zsh",
"TERM": "xterm-256color", "TERM": "xterm-256color",
"USER": "chronos", "USER": "chronos",
"WAYLAND_DISPLAY": "wayland-0", "WAYLAND_DISPLAY": "/run/user/65534/wayland-0",
"XDG_RUNTIME_DIR": "/run/user/65534", "XDG_RUNTIME_DIR": "/run/user/65534",
"XDG_SESSION_CLASS": "user", "XDG_SESSION_CLASS": "user",
"XDG_SESSION_TYPE": "tty", "XDG_SESSION_TYPE": "tty",

View File

@ -13,7 +13,6 @@ import (
"git.gensokyo.uk/security/fortify/internal/fmsg" "git.gensokyo.uk/security/fortify/internal/fmsg"
"git.gensokyo.uk/security/fortify/internal/linux" "git.gensokyo.uk/security/fortify/internal/linux"
"git.gensokyo.uk/security/fortify/internal/system" "git.gensokyo.uk/security/fortify/internal/system"
"git.gensokyo.uk/security/fortify/wl"
) )
const ( const (
@ -28,6 +27,9 @@ const (
term = "TERM" term = "TERM"
display = "DISPLAY" display = "DISPLAY"
// https://manpages.debian.org/experimental/libwayland-doc/wl_display_connect.3.en.html
waylandDisplay = "WAYLAND_DISPLAY"
pulseServer = "PULSE_SERVER" pulseServer = "PULSE_SERVER"
pulseCookie = "PULSE_COOKIE" pulseCookie = "PULSE_COOKIE"
@ -36,6 +38,7 @@ const (
) )
var ( var (
ErrWayland = errors.New(waylandDisplay + " unset")
ErrXDisplay = errors.New(display + " unset") ErrXDisplay = errors.New(display + " unset")
ErrPulseCookie = errors.New("pulse cookie not present") ErrPulseCookie = errors.New("pulse cookie not present")
@ -141,36 +144,36 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
// set up wayland // set up wayland
if seal.et.Has(system.EWayland) { if seal.et.Has(system.EWayland) {
var socketPath string var wp string
if name, ok := os.LookupEnv(wl.WaylandDisplay); !ok { if wd, ok := os.LookupEnv(waylandDisplay); !ok {
fmsg.VPrintln(wl.WaylandDisplay + " is not set, assuming " + wl.FallbackName) return fmsg.WrapError(ErrWayland,
socketPath = path.Join(seal.RuntimePath, wl.FallbackName) "WAYLAND_DISPLAY is not set")
} else if !path.IsAbs(name) {
socketPath = path.Join(seal.RuntimePath, name)
} else { } else {
socketPath = name wp = path.Join(seal.RuntimePath, wd)
} }
innerPath := path.Join(seal.sys.runtime, wl.FallbackName) w := path.Join(seal.sys.runtime, "wayland-0")
seal.sys.bwrap.SetEnv[wl.WaylandDisplay] = wl.FallbackName seal.sys.bwrap.SetEnv[waylandDisplay] = w
if !seal.directWayland { // set up security-context-v1 if !seal.directWayland { // set up security-context-v1
socketDir := path.Join(seal.SharePath, "wayland") wc := path.Join(seal.SharePath, "wayland")
outerPath := path.Join(socketDir, seal.id) wt := path.Join(wc, seal.id)
seal.sys.Ensure(socketDir, 0711) seal.sys.Ensure(wc, 0711)
appID := seal.fid appID := seal.fid
if appID == "" { if appID == "" {
// use instance ID in case app id is not set // use instance ID in case app id is not set
appID = "uk.gensokyo.fortify." + seal.id appID = "uk.gensokyo.fortify." + seal.id
} }
seal.sys.Wayland(outerPath, socketPath, appID, seal.id) seal.sys.Wayland(wt, wp, appID, seal.id)
seal.sys.bwrap.Bind(outerPath, innerPath) seal.sys.bwrap.Bind(wt, w)
} else { // bind mount wayland socket (insecure) } else { // bind mount wayland socket (insecure)
fmsg.VPrintln("direct wayland access, PROCEED WITH CAUTION") // hardlink wayland socket
seal.sys.bwrap.Bind(socketPath, innerPath) wpi := path.Join(seal.shareLocal, "wayland")
seal.sys.Link(wp, wpi)
seal.sys.bwrap.Bind(wpi, w)
// ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`) // ensure Wayland socket ACL (e.g. `/run/user/%d/wayland-%d`)
seal.sys.UpdatePermType(system.EWayland, socketPath, acl.Read, acl.Write, acl.Execute) seal.sys.UpdatePermType(system.EWayland, wp, acl.Read, acl.Write, acl.Execute)
} }
} }

View File

@ -38,10 +38,6 @@ func (w Wayland) apply(sys *I) error {
} }
if err := w.conn.Attach(w.pair[1]); err != nil { if err := w.conn.Attach(w.pair[1]); err != nil {
// make console output less nasty
if errors.Is(err, os.ErrNotExist) {
err = os.ErrNotExist
}
return fmsg.WrapErrorSuffix(err, return fmsg.WrapErrorSuffix(err,
fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1])) fmt.Sprintf("cannot attach to wayland on %q:", w.pair[1]))
} else { } else {

View File

@ -328,14 +328,14 @@ func runApp(config *fst.Config) {
} else { } else {
logWaitError(err) logWaitError(err)
} }
if rs.ExitCode == 0 {
rs.ExitCode = 126
}
} }
if rs.WaitErr != nil { if rs.WaitErr != nil {
fmsg.Println("inner wait failed:", rs.WaitErr) fmsg.Println("inner wait failed:", rs.WaitErr)
} }
if rs.ExitCode < 0 {
fmsg.VPrintf("got negative exit %v", rs.ExitCode)
fmsg.Exit(1)
}
fmsg.Exit(rs.ExitCode) fmsg.Exit(rs.ExitCode)
panic("unreachable") panic("unreachable")
} }

View File

@ -9,6 +9,7 @@ let
inherit (lib) inherit (lib)
mkMerge mkMerge
mkIf mkIf
mkDefault
mapAttrs mapAttrs
mergeAttrsList mergeAttrsList
imap1 imap1
@ -45,6 +46,10 @@ in
) "" cfg.users; ) "" cfg.users;
}; };
systemd.services.nix-daemon.unitConfig.RequiresMountsFor = [ "/etc/userdb" ];
services.userdbd.enable = mkDefault true;
home-manager = home-manager =
let let
privPackages = mapAttrs (username: fid: { privPackages = mapAttrs (username: fid: {
@ -118,7 +123,6 @@ in
}; };
map_real_uid = app.mapRealUid; map_real_uid = app.mapRealUid;
no_new_session = app.tty; no_new_session = app.tty;
direct_wayland = app.insecureWayland;
filesystem = filesystem =
let let
bind = src: { inherit src; }; bind = src: { inherit src; };

View File

@ -146,7 +146,6 @@ in
mapRealUid = mkEnableOption "mapping to priv-user uid"; mapRealUid = mkEnableOption "mapping to priv-user uid";
dev = mkEnableOption "access to all devices"; dev = mkEnableOption "access to all devices";
tty = mkEnableOption "access to the controlling terminal"; tty = mkEnableOption "access to the controlling terminal";
insecureWayland = mkEnableOption "direct access to the Wayland socket";
net = mkEnableOption "network access" // { net = mkEnableOption "network access" // {
default = true; default = true;

View File

@ -81,7 +81,7 @@ nixosTest {
mkdir -p ~/.config/sway mkdir -p ~/.config/sway
(sed s/Mod4/Mod1/ /etc/sway/config && (sed s/Mod4/Mod1/ /etc/sway/config &&
echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' && echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' &&
echo 'output Virtual-1 res 1680x1050') > ~/.config/sway/config echo 'output Virtual-1 res 1280x768') > ~/.config/sway/config
sway --validate sway --validate
systemd-cat --identifier=sway sway && touch /tmp/sway-exit-ok systemd-cat --identifier=sway sway && touch /tmp/sway-exit-ok
@ -148,18 +148,6 @@ nixosTest {
pulse = false; pulse = false;
}; };
} }
{
name = "da-foot";
verbose = true;
insecureWayland = true;
share = pkgs.foot;
packages = [ pkgs.foot ];
command = "foot";
capability = {
dbus = false;
pulse = false;
};
}
{ {
name = "strace-failure"; name = "strace-failure";
verbose = true; verbose = true;
@ -282,9 +270,6 @@ nixosTest {
if output != "": if output != "":
raise Exception(f"unexpected output\n{output}") raise Exception(f"unexpected output\n{output}")
# Verify graceful failure on bad Wayland display name:
print(machine.fail("sudo -u alice -i fortify -v run --wayland true"))
# Start fortify permissive defaults within Wayland session: # Start fortify permissive defaults within Wayland session:
fortify('-v run --wayland --dbus notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-done') fortify('-v run --wayland --dbus notify-send -a "NixOS Tests" "Test notification" "Notification from within sandbox." && touch /tmp/dbus-done')
machine.wait_for_file("/tmp/dbus-done") machine.wait_for_file("/tmp/dbus-done")
@ -338,20 +323,6 @@ nixosTest {
machine.send_chars("exit\n") machine.send_chars("exit\n")
machine.wait_until_fails("pgrep alacritty") machine.wait_until_fails("pgrep alacritty")
# Start app (foot) with direct Wayland access:
swaymsg("exec da-foot")
wait_for_window("u0_a4@machine")
machine.send_chars("clear; wayland-info && touch /tmp/success-direct\n")
machine.wait_for_file("/tmp/fortify.1000/tmpdir/4/success-direct")
collect_state_ui("foot_direct")
check_state("da-foot", 1)
# Verify acl on XDG_RUNTIME_DIR:
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000004"))
machine.send_chars("exit\n")
machine.wait_until_fails("pgrep foot")
# Verify acl cleanup on XDG_RUNTIME_DIR:
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000004")
# Test syscall filter: # Test syscall filter:
print(machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 strace-failure")) print(machine.fail("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 strace-failure"))

View File

@ -1,15 +0,0 @@
package wl
const (
// WaylandDisplay contains the name of the server socket
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1147)
// which is concatenated with XDG_RUNTIME_DIR
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1171)
// or used as-is if absolute
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1176).
WaylandDisplay = "WAYLAND_DISPLAY"
// FallbackName is used as the wayland socket name if WAYLAND_DISPLAY is unset
// (https://gitlab.freedesktop.org/wayland/wayland/-/blob/1.23.1/src/wayland-client.c#L1149).
FallbackName = "wayland-0"
)