Compare commits
No commits in common. "d8e9d71f87db912d73345fec2f9fcb61b25231cf" and "71135f339a4384ec67fa87ed6624a09168a7e52b" have entirely different histories.
d8e9d71f87
...
71135f339a
@ -22,23 +22,6 @@ jobs:
|
|||||||
path: result/*
|
path: result/*
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
|
|
||||||
fpkg:
|
|
||||||
name: Fpkg
|
|
||||||
runs-on: nix
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Run NixOS test
|
|
||||||
run: nix build --out-link "result" --print-out-paths --print-build-logs .#checks.x86_64-linux.fpkg
|
|
||||||
|
|
||||||
- name: Upload test output
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: "fpkg-vm-output"
|
|
||||||
path: result/*
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
race:
|
race:
|
||||||
name: Data race detector
|
name: Data race detector
|
||||||
runs-on: nix
|
runs-on: nix
|
||||||
@ -60,7 +43,6 @@ jobs:
|
|||||||
name: Flake checks
|
name: Flake checks
|
||||||
needs:
|
needs:
|
||||||
- fortify
|
- fortify
|
||||||
- fpkg
|
|
||||||
- race
|
- race
|
||||||
runs-on: nix
|
runs-on: nix
|
||||||
steps:
|
steps:
|
||||||
|
@ -7,9 +7,8 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
stdenv,
|
|
||||||
closureInfo,
|
|
||||||
writeScript,
|
writeScript,
|
||||||
|
writeScriptBin,
|
||||||
runtimeShell,
|
runtimeShell,
|
||||||
writeText,
|
writeText,
|
||||||
symlinkJoin,
|
symlinkJoin,
|
||||||
@ -17,15 +16,12 @@
|
|||||||
runCommand,
|
runCommand,
|
||||||
fetchFromGitHub,
|
fetchFromGitHub,
|
||||||
|
|
||||||
zstd,
|
|
||||||
nix,
|
nix,
|
||||||
sqlite,
|
|
||||||
|
|
||||||
name ? throw "name is required",
|
name ? throw "name is required",
|
||||||
version ? throw "version is required",
|
version ? throw "version is required",
|
||||||
pname ? "${name}-${version}",
|
pname ? "${name}-${version}",
|
||||||
modules ? [ ],
|
modules ? [ ],
|
||||||
nixosModules ? [ ],
|
|
||||||
script ? ''
|
script ? ''
|
||||||
exec "$SHELL" "$@"
|
exec "$SHELL" "$@"
|
||||||
'',
|
'',
|
||||||
@ -77,8 +73,6 @@ let
|
|||||||
etc.nixpkgs.source = nixpkgs.outPath;
|
etc.nixpkgs.source = nixpkgs.outPath;
|
||||||
systemPackages = [ pkgs.nix ];
|
systemPackages = [ pkgs.nix ];
|
||||||
};
|
};
|
||||||
|
|
||||||
imports = nixosModules;
|
|
||||||
};
|
};
|
||||||
nixos = nixpkgs.lib.nixosSystem {
|
nixos = nixpkgs.lib.nixosSystem {
|
||||||
inherit system;
|
inherit system;
|
||||||
@ -171,7 +165,11 @@ let
|
|||||||
broadcast = { };
|
broadcast = { };
|
||||||
});
|
});
|
||||||
|
|
||||||
enablements = (if allow_wayland then 1 else 0) + (if allow_x11 then 2 else 0) + (if allow_dbus then 4 else 0) + (if allow_pulse then 8 else 0);
|
enablements =
|
||||||
|
(if allow_wayland then 1 else 0)
|
||||||
|
+ (if allow_x11 then 2 else 0)
|
||||||
|
+ (if allow_dbus then 4 else 0)
|
||||||
|
+ (if allow_pulse then 8 else 0);
|
||||||
|
|
||||||
mesa = if gpu then mesaWrappers else null;
|
mesa = if gpu then mesaWrappers else null;
|
||||||
nix_gl = if gpu then nixGL else null;
|
nix_gl = if gpu then nixGL else null;
|
||||||
@ -180,73 +178,26 @@ let
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
||||||
stdenv.mkDerivation {
|
writeScriptBin "build-fpkg-${pname}" ''
|
||||||
name = "${pname}.pkg";
|
#!${runtimeShell} -el
|
||||||
inherit version;
|
OUT="$(mktemp -d)"
|
||||||
__structuredAttrs = true;
|
TAR="$(mktemp -u)"
|
||||||
|
set -x
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nix copy --no-check-sigs --to "$OUT" "${nix}" "${nixos.config.system.build.toplevel}"
|
||||||
zstd
|
nix store --store "$OUT" optimise
|
||||||
nix
|
chmod -R +r "$OUT/nix/var"
|
||||||
sqlite
|
nix copy --no-check-sigs --to "file://$OUT/res?compression=zstd&compression-level=19¶llel-compression=true" \
|
||||||
];
|
"${homeManagerConfiguration.activationPackage}" \
|
||||||
|
"${launcher}" ${if gpu then "${mesaWrappers} ${nixGL}" else ""}
|
||||||
|
mkdir -p "$OUT/etc"
|
||||||
|
tar -C "$OUT/etc" -xf "${etc}/etc.tar"
|
||||||
|
cp "${writeText "bundle.json" info}" "$OUT/bundle.json"
|
||||||
|
|
||||||
buildCommand = ''
|
# creating an intermediate file improves zstd performance
|
||||||
NIX_ROOT="$(mktemp -d)"
|
tar -C "$OUT" -cf "$TAR" .
|
||||||
export USER="nobody"
|
chmod +w -R "$OUT" && rm -rf "$OUT"
|
||||||
|
|
||||||
# create bootstrap store
|
zstd -T0 -19 -fo "${pname}.pkg" "$TAR"
|
||||||
bootstrapClosureInfo="${
|
rm "$TAR"
|
||||||
closureInfo {
|
''
|
||||||
rootPaths = [
|
|
||||||
nix
|
|
||||||
nixos.config.system.build.toplevel
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
echo "copying bootstrap store paths..."
|
|
||||||
mkdir -p "$NIX_ROOT/nix/store"
|
|
||||||
xargs -n 1 -a "$bootstrapClosureInfo/store-paths" cp -at "$NIX_ROOT/nix/store/"
|
|
||||||
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --load-db < "$bootstrapClosureInfo/registration"
|
|
||||||
NIX_REMOTE="local?root=$NIX_ROOT" nix-store --optimise
|
|
||||||
sqlite3 "$NIX_ROOT/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
|
|
||||||
chmod -R +r "$NIX_ROOT/nix/var"
|
|
||||||
|
|
||||||
# create binary cache
|
|
||||||
closureInfo="${
|
|
||||||
closureInfo {
|
|
||||||
rootPaths =
|
|
||||||
[
|
|
||||||
homeManagerConfiguration.activationPackage
|
|
||||||
launcher
|
|
||||||
]
|
|
||||||
++ optionals gpu [
|
|
||||||
mesaWrappers
|
|
||||||
nixGL
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
echo "copying application paths..."
|
|
||||||
TMP_STORE="$(mktemp -d)"
|
|
||||||
mkdir -p "$TMP_STORE/nix/store"
|
|
||||||
xargs -n 1 -a "$closureInfo/store-paths" cp -at "$TMP_STORE/nix/store/"
|
|
||||||
NIX_REMOTE="local?root=$TMP_STORE" nix-store --load-db < "$closureInfo/registration"
|
|
||||||
sqlite3 "$TMP_STORE/nix/var/nix/db/db.sqlite" "UPDATE ValidPaths SET registrationTime = ''${SOURCE_DATE_EPOCH}"
|
|
||||||
NIX_REMOTE="local?root=$TMP_STORE" nix --offline --extra-experimental-features nix-command \
|
|
||||||
--verbose --log-format raw-with-logs \
|
|
||||||
copy --all --no-check-sigs --to \
|
|
||||||
"file://$NIX_ROOT/res?compression=zstd&compression-level=19¶llel-compression=true"
|
|
||||||
|
|
||||||
# package /etc
|
|
||||||
mkdir -p "$NIX_ROOT/etc"
|
|
||||||
tar -C "$NIX_ROOT/etc" -xf "${etc}/etc.tar"
|
|
||||||
|
|
||||||
# write metadata
|
|
||||||
cp "${writeText "bundle.json" info}" "$NIX_ROOT/bundle.json"
|
|
||||||
|
|
||||||
# create an intermediate file to improve zstd performance
|
|
||||||
INTER="$(mktemp)"
|
|
||||||
tar -C "$NIX_ROOT" -cf "$INTER" .
|
|
||||||
zstd -T0 -19 -fo "$out" "$INTER"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
|
191
cmd/fpkg/install.go
Normal file
191
cmd/fpkg/install.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func actionInstall(args []string) {
|
||||||
|
set := flag.NewFlagSet("install", flag.ExitOnError)
|
||||||
|
var (
|
||||||
|
dropShellInstall bool
|
||||||
|
dropShellActivate bool
|
||||||
|
)
|
||||||
|
set.BoolVar(&dropShellInstall, "si", false, "Drop to a shell on installation")
|
||||||
|
set.BoolVar(&dropShellActivate, "sa", false, "Drop to a shell on activation")
|
||||||
|
|
||||||
|
// Ignore errors; set is set for ExitOnError.
|
||||||
|
_ = set.Parse(args)
|
||||||
|
|
||||||
|
args = set.Args()
|
||||||
|
|
||||||
|
if len(args) != 1 {
|
||||||
|
log.Fatal("invalid argument")
|
||||||
|
}
|
||||||
|
pkgPath := args[0]
|
||||||
|
if !path.IsAbs(pkgPath) {
|
||||||
|
if dir, err := os.Getwd(); err != nil {
|
||||||
|
log.Fatalf("cannot get current directory: %v", err)
|
||||||
|
} else {
|
||||||
|
pkgPath = path.Join(dir, pkgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Look up paths to programs started by fpkg.
|
||||||
|
This is done here to ease error handling as cleanup is not yet required.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ = lookPath("zstd")
|
||||||
|
tar = lookPath("tar")
|
||||||
|
chmod = lookPath("chmod")
|
||||||
|
rm = lookPath("rm")
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Extract package and set up for cleanup.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var workDir string
|
||||||
|
if p, err := os.MkdirTemp("", "fpkg.*"); err != nil {
|
||||||
|
log.Fatalf("cannot create temporary directory: %v", err)
|
||||||
|
} else {
|
||||||
|
workDir = p
|
||||||
|
}
|
||||||
|
cleanup := func() {
|
||||||
|
// should be faster than a native implementation
|
||||||
|
mustRun(chmod, "-R", "+w", workDir)
|
||||||
|
mustRun(rm, "-rf", workDir)
|
||||||
|
}
|
||||||
|
beforeRunFail.Store(&cleanup)
|
||||||
|
|
||||||
|
mustRun(tar, "-C", workDir, "-xf", pkgPath)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parse bundle and app metadata, do pre-install checks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
bundle := loadBundleInfo(path.Join(workDir, "bundle.json"), cleanup)
|
||||||
|
pathSet := pathSetByApp(bundle.ID)
|
||||||
|
|
||||||
|
app := bundle
|
||||||
|
if s, err := os.Stat(pathSet.metaPath); err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
cleanup()
|
||||||
|
log.Fatalf("cannot access %q: %v", pathSet.metaPath, err)
|
||||||
|
}
|
||||||
|
// did not modify app, clean installation condition met later
|
||||||
|
} else if s.IsDir() {
|
||||||
|
cleanup()
|
||||||
|
log.Fatalf("metadata path %q is not a file", pathSet.metaPath)
|
||||||
|
} else {
|
||||||
|
app = loadBundleInfo(pathSet.metaPath, cleanup)
|
||||||
|
if app.ID != bundle.ID {
|
||||||
|
cleanup()
|
||||||
|
log.Fatalf("app %q claims to have identifier %q", bundle.ID, app.ID)
|
||||||
|
}
|
||||||
|
// sec: should verify credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
if app != bundle {
|
||||||
|
// do not try to re-install
|
||||||
|
if app.NixGL == bundle.NixGL &&
|
||||||
|
app.CurrentSystem == bundle.CurrentSystem &&
|
||||||
|
app.Launcher == bundle.Launcher &&
|
||||||
|
app.ActivationPackage == bundle.ActivationPackage {
|
||||||
|
cleanup()
|
||||||
|
log.Printf("package %q is identical to local application %q", pkgPath, app.ID)
|
||||||
|
internal.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppID determines uid
|
||||||
|
if app.AppID != bundle.AppID {
|
||||||
|
cleanup()
|
||||||
|
log.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sec: should compare version string
|
||||||
|
fmsg.Verbosef("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version)
|
||||||
|
} else {
|
||||||
|
fmsg.Verbosef("application %q clean installation", bundle.ID)
|
||||||
|
// sec: should install credentials
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Setup steps for files owned by the target user.
|
||||||
|
*/
|
||||||
|
|
||||||
|
withCacheDir("install", []string{
|
||||||
|
// export inner bundle path in the environment
|
||||||
|
"export BUNDLE=" + fst.Tmp + "/bundle",
|
||||||
|
// replace inner /etc
|
||||||
|
"mkdir -p etc",
|
||||||
|
"chmod -R +w etc",
|
||||||
|
"rm -rf etc",
|
||||||
|
"cp -dRf $BUNDLE/etc etc",
|
||||||
|
// replace inner /nix
|
||||||
|
"mkdir -p nix",
|
||||||
|
"chmod -R +w nix",
|
||||||
|
"rm -rf nix",
|
||||||
|
"cp -dRf /nix nix",
|
||||||
|
// copy from binary cache
|
||||||
|
"nix copy --offline --no-check-sigs --all --from file://$BUNDLE/res --to $PWD",
|
||||||
|
// deduplicate nix store
|
||||||
|
"nix store --offline --store $PWD optimise",
|
||||||
|
// make cache directory world-readable for autoetc
|
||||||
|
"chmod 0755 .",
|
||||||
|
}, workDir, bundle, pathSet, dropShellInstall, cleanup)
|
||||||
|
|
||||||
|
if bundle.GPU {
|
||||||
|
withCacheDir("mesa-wrappers", []string{
|
||||||
|
// link nixGL mesa wrappers
|
||||||
|
"mkdir -p nix/.nixGL",
|
||||||
|
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
|
||||||
|
"ln -s " + bundle.Mesa + "/bin/nixVulkanIntel nix/.nixGL/nixVulkan",
|
||||||
|
}, workDir, bundle, pathSet, false, cleanup)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Activate home-manager generation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
withNixDaemon("activate", []string{
|
||||||
|
// clean up broken links
|
||||||
|
"mkdir -p .local/state/{nix,home-manager}",
|
||||||
|
"chmod -R +w .local/state/{nix,home-manager}",
|
||||||
|
"rm -rf .local/state/{nix,home-manager}",
|
||||||
|
// run activation script
|
||||||
|
bundle.ActivationPackage + "/activate",
|
||||||
|
}, false, func(config *fst.Config) *fst.Config { return config }, bundle, pathSet, dropShellActivate, cleanup)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Installation complete. Write metadata to block re-installs or downgrades.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// serialise metadata to ensure consistency
|
||||||
|
if f, err := os.OpenFile(pathSet.metaPath+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
|
||||||
|
cleanup()
|
||||||
|
log.Fatalf("cannot create metadata file: %v", err)
|
||||||
|
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
|
||||||
|
cleanup()
|
||||||
|
log.Fatalf("cannot write metadata: %v", err)
|
||||||
|
} else if err = f.Close(); err != nil {
|
||||||
|
log.Printf("cannot close metadata file: %v", err)
|
||||||
|
// not fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil {
|
||||||
|
cleanup()
|
||||||
|
log.Fatalf("cannot rename metadata file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
}
|
412
cmd/fpkg/main.go
412
cmd/fpkg/main.go
@ -1,402 +1,50 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"flag"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"path"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/command"
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/seccomp"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
init0 "git.gensokyo.uk/security/fortify/internal/app/init"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/app/shim"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/sys"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const shellPath = "/run/current-system/sw/bin/bash"
|
const shellPath = "/run/current-system/sw/bin/bash"
|
||||||
|
|
||||||
var (
|
|
||||||
errSuccess = errors.New("success")
|
|
||||||
|
|
||||||
std sys.State = new(sys.Std)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
fmsg.Prepare("fpkg")
|
|
||||||
if err := os.Setenv("SHELL", shellPath); err != nil {
|
if err := os.Setenv("SHELL", shellPath); err != nil {
|
||||||
log.Fatalf("cannot set $SHELL: %v", err)
|
log.Fatalf("cannot set $SHELL: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
var (
|
||||||
// early init argv0 check, skips root check and duplicate PR_SET_DUMPABLE
|
flagVerbose bool
|
||||||
init0.TryArgv0()
|
)
|
||||||
|
|
||||||
if err := internal.PR_SET_DUMPABLE__SUID_DUMP_DISABLE(); err != nil {
|
func init() {
|
||||||
log.Printf("cannot set SUID_DUMP_DISABLE: %s", err)
|
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
|
||||||
// not fatal: this program runs as the privileged user
|
}
|
||||||
}
|
|
||||||
|
func main() {
|
||||||
if os.Geteuid() == 0 {
|
fmsg.Prepare("fpkg")
|
||||||
log.Fatal("this program must not run as root")
|
|
||||||
}
|
flag.Parse()
|
||||||
|
fmsg.Store(flagVerbose)
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
|
||||||
syscall.SIGINT, syscall.SIGTERM)
|
args := flag.Args()
|
||||||
defer stop() // unreachable
|
if len(args) < 1 {
|
||||||
|
log.Fatal("invalid argument")
|
||||||
var (
|
}
|
||||||
flagVerbose bool
|
|
||||||
flagDropShell bool
|
switch args[0] {
|
||||||
)
|
case "install":
|
||||||
c := command.New(os.Stderr, log.Printf, "fpkg", func([]string) error {
|
actionInstall(args[1:])
|
||||||
fmsg.Store(flagVerbose)
|
case "start":
|
||||||
if flagVerbose {
|
actionStart(args[1:])
|
||||||
seccomp.CPrintln = log.Println
|
|
||||||
}
|
default:
|
||||||
return nil
|
log.Fatal("invalid argument")
|
||||||
}).
|
}
|
||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
|
||||||
Flag(&flagDropShell, "s", command.BoolFlag(false), "Drop to a shell in place of next fortify action")
|
internal.Exit(0)
|
||||||
|
|
||||||
// internal commands
|
|
||||||
c.Command("shim", command.UsageInternal, func([]string) error { shim.Main(); return errSuccess })
|
|
||||||
c.Command("init", command.UsageInternal, func([]string) error { init0.Main(); return errSuccess })
|
|
||||||
|
|
||||||
{
|
|
||||||
var (
|
|
||||||
flagDropShellActivate bool
|
|
||||||
)
|
|
||||||
c.NewCommand("install", "Install an application from its package", func(args []string) error {
|
|
||||||
if len(args) != 1 {
|
|
||||||
log.Println("invalid argument")
|
|
||||||
return syscall.EINVAL
|
|
||||||
}
|
|
||||||
pkgPath := args[0]
|
|
||||||
if !path.IsAbs(pkgPath) {
|
|
||||||
if dir, err := os.Getwd(); err != nil {
|
|
||||||
log.Printf("cannot get current directory: %v", err)
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
pkgPath = path.Join(dir, pkgPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Look up paths to programs started by fpkg.
|
|
||||||
This is done here to ease error handling as cleanup is not yet required.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ = lookPath("zstd")
|
|
||||||
tar = lookPath("tar")
|
|
||||||
chmod = lookPath("chmod")
|
|
||||||
rm = lookPath("rm")
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Extract package and set up for cleanup.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var workDir string
|
|
||||||
if p, err := os.MkdirTemp("", "fpkg.*"); err != nil {
|
|
||||||
log.Printf("cannot create temporary directory: %v", err)
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
workDir = p
|
|
||||||
}
|
|
||||||
cleanup := func() {
|
|
||||||
// should be faster than a native implementation
|
|
||||||
mustRun(chmod, "-R", "+w", workDir)
|
|
||||||
mustRun(rm, "-rf", workDir)
|
|
||||||
}
|
|
||||||
beforeRunFail.Store(&cleanup)
|
|
||||||
|
|
||||||
mustRun(tar, "-C", workDir, "-xf", pkgPath)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Parse bundle and app metadata, do pre-install checks.
|
|
||||||
*/
|
|
||||||
|
|
||||||
bundle := loadBundleInfo(path.Join(workDir, "bundle.json"), cleanup)
|
|
||||||
pathSet := pathSetByApp(bundle.ID)
|
|
||||||
|
|
||||||
app := bundle
|
|
||||||
if s, err := os.Stat(pathSet.metaPath); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("cannot access %q: %v", pathSet.metaPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// did not modify app, clean installation condition met later
|
|
||||||
} else if s.IsDir() {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("metadata path %q is not a file", pathSet.metaPath)
|
|
||||||
return syscall.EBADMSG
|
|
||||||
} else {
|
|
||||||
app = loadBundleInfo(pathSet.metaPath, cleanup)
|
|
||||||
if app.ID != bundle.ID {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("app %q claims to have identifier %q",
|
|
||||||
bundle.ID, app.ID)
|
|
||||||
return syscall.EBADE
|
|
||||||
}
|
|
||||||
// sec: should verify credentials
|
|
||||||
}
|
|
||||||
|
|
||||||
if app != bundle {
|
|
||||||
// do not try to re-install
|
|
||||||
if app.NixGL == bundle.NixGL &&
|
|
||||||
app.CurrentSystem == bundle.CurrentSystem &&
|
|
||||||
app.Launcher == bundle.Launcher &&
|
|
||||||
app.ActivationPackage == bundle.ActivationPackage {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("package %q is identical to local application %q",
|
|
||||||
pkgPath, app.ID)
|
|
||||||
return errSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppID determines uid
|
|
||||||
if app.AppID != bundle.AppID {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("package %q app id %d differs from installed %d",
|
|
||||||
pkgPath, bundle.AppID, app.AppID)
|
|
||||||
return syscall.EBADE
|
|
||||||
}
|
|
||||||
|
|
||||||
// sec: should compare version string
|
|
||||||
fmsg.Verbosef("installing application %q version %q over local %q",
|
|
||||||
bundle.ID, bundle.Version, app.Version)
|
|
||||||
} else {
|
|
||||||
fmsg.Verbosef("application %q clean installation", bundle.ID)
|
|
||||||
// sec: should install credentials
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Setup steps for files owned by the target user.
|
|
||||||
*/
|
|
||||||
|
|
||||||
withCacheDir(ctx, "install", []string{
|
|
||||||
// export inner bundle path in the environment
|
|
||||||
"export BUNDLE=" + fst.Tmp + "/bundle",
|
|
||||||
// replace inner /etc
|
|
||||||
"mkdir -p etc",
|
|
||||||
"chmod -R +w etc",
|
|
||||||
"rm -rf etc",
|
|
||||||
"cp -dRf $BUNDLE/etc etc",
|
|
||||||
// replace inner /nix
|
|
||||||
"mkdir -p nix",
|
|
||||||
"chmod -R +w nix",
|
|
||||||
"rm -rf nix",
|
|
||||||
"cp -dRf /nix nix",
|
|
||||||
// copy from binary cache
|
|
||||||
"nix copy --offline --no-check-sigs --all --from file://$BUNDLE/res --to $PWD",
|
|
||||||
// deduplicate nix store
|
|
||||||
"nix store --offline --store $PWD optimise",
|
|
||||||
// make cache directory world-readable for autoetc
|
|
||||||
"chmod 0755 .",
|
|
||||||
}, workDir, bundle, pathSet, flagDropShell, cleanup)
|
|
||||||
|
|
||||||
if bundle.GPU {
|
|
||||||
withCacheDir(ctx, "mesa-wrappers", []string{
|
|
||||||
// link nixGL mesa wrappers
|
|
||||||
"mkdir -p nix/.nixGL",
|
|
||||||
"ln -s " + bundle.Mesa + "/bin/nixGLIntel nix/.nixGL/nixGL",
|
|
||||||
"ln -s " + bundle.Mesa + "/bin/nixVulkanIntel nix/.nixGL/nixVulkan",
|
|
||||||
}, workDir, bundle, pathSet, false, cleanup)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Activate home-manager generation.
|
|
||||||
*/
|
|
||||||
|
|
||||||
withNixDaemon(ctx, "activate", []string{
|
|
||||||
// clean up broken links
|
|
||||||
"mkdir -p .local/state/{nix,home-manager}",
|
|
||||||
"chmod -R +w .local/state/{nix,home-manager}",
|
|
||||||
"rm -rf .local/state/{nix,home-manager}",
|
|
||||||
// run activation script
|
|
||||||
bundle.ActivationPackage + "/activate",
|
|
||||||
}, false, func(config *fst.Config) *fst.Config { return config },
|
|
||||||
bundle, pathSet, flagDropShellActivate, cleanup)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Installation complete. Write metadata to block re-installs or downgrades.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// serialise metadata to ensure consistency
|
|
||||||
if f, err := os.OpenFile(pathSet.metaPath+"~", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("cannot create metadata file: %v", err)
|
|
||||||
return err
|
|
||||||
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("cannot write metadata: %v", err)
|
|
||||||
return err
|
|
||||||
} else if err = f.Close(); err != nil {
|
|
||||||
log.Printf("cannot close metadata file: %v", err)
|
|
||||||
// not fatal
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil {
|
|
||||||
cleanup()
|
|
||||||
log.Printf("cannot rename metadata file: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup()
|
|
||||||
return errSuccess
|
|
||||||
}).
|
|
||||||
Flag(&flagDropShellActivate, "s", command.BoolFlag(false), "Drop to a shell on activation")
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var (
|
|
||||||
flagDropShellNixGL bool
|
|
||||||
flagAutoDrivers bool
|
|
||||||
)
|
|
||||||
c.NewCommand("start", "Start an application", func(args []string) error {
|
|
||||||
if len(args) < 1 {
|
|
||||||
log.Println("invalid argument")
|
|
||||||
return syscall.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Parse app metadata.
|
|
||||||
*/
|
|
||||||
|
|
||||||
id := args[0]
|
|
||||||
pathSet := pathSetByApp(id)
|
|
||||||
app := loadBundleInfo(pathSet.metaPath, func() {})
|
|
||||||
if app.ID != id {
|
|
||||||
log.Printf("app %q claims to have identifier %q", id, app.ID)
|
|
||||||
return syscall.EBADE
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prepare nixGL.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if app.GPU && flagAutoDrivers {
|
|
||||||
withNixDaemon(ctx, "nix-gl", []string{
|
|
||||||
"mkdir -p /nix/.nixGL/auto",
|
|
||||||
"rm -rf /nix/.nixGL/auto",
|
|
||||||
"export NIXPKGS_ALLOW_UNFREE=1",
|
|
||||||
"nix build --impure " +
|
|
||||||
"--out-link /nix/.nixGL/auto/opengl " +
|
|
||||||
"--override-input nixpkgs path:/etc/nixpkgs " +
|
|
||||||
"path:" + app.NixGL,
|
|
||||||
"nix build --impure " +
|
|
||||||
"--out-link /nix/.nixGL/auto/vulkan " +
|
|
||||||
"--override-input nixpkgs path:/etc/nixpkgs " +
|
|
||||||
"path:" + app.NixGL + "#nixVulkanNvidia",
|
|
||||||
}, true, func(config *fst.Config) *fst.Config {
|
|
||||||
config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem, []*fst.FilesystemConfig{
|
|
||||||
{Src: "/etc/resolv.conf"},
|
|
||||||
{Src: "/sys/block"},
|
|
||||||
{Src: "/sys/bus"},
|
|
||||||
{Src: "/sys/class"},
|
|
||||||
{Src: "/sys/dev"},
|
|
||||||
{Src: "/sys/devices"},
|
|
||||||
}...)
|
|
||||||
appendGPUFilesystem(config)
|
|
||||||
return config
|
|
||||||
}, app, pathSet, flagDropShellNixGL, func() {})
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Create app configuration.
|
|
||||||
*/
|
|
||||||
|
|
||||||
argv := make([]string, 1, len(args))
|
|
||||||
if !flagDropShell {
|
|
||||||
argv[0] = app.Launcher
|
|
||||||
} else {
|
|
||||||
argv[0] = shellPath
|
|
||||||
}
|
|
||||||
argv = append(argv, args[1:]...)
|
|
||||||
|
|
||||||
config := &fst.Config{
|
|
||||||
ID: app.ID,
|
|
||||||
Command: argv,
|
|
||||||
Confinement: fst.ConfinementConfig{
|
|
||||||
AppID: app.AppID,
|
|
||||||
Groups: app.Groups,
|
|
||||||
Username: "fortify",
|
|
||||||
Inner: path.Join("/data/data", app.ID),
|
|
||||||
Outer: pathSet.homeDir,
|
|
||||||
Sandbox: &fst.SandboxConfig{
|
|
||||||
Hostname: formatHostname(app.Name),
|
|
||||||
UserNS: app.UserNS,
|
|
||||||
Net: app.Net,
|
|
||||||
Dev: app.Dev,
|
|
||||||
Syscall: &bwrap.SyscallPolicy{DenyDevel: !app.Devel, Multiarch: app.Multiarch, Bluetooth: app.Bluetooth},
|
|
||||||
NoNewSession: app.NoNewSession || flagDropShell,
|
|
||||||
MapRealUID: app.MapRealUID,
|
|
||||||
DirectWayland: app.DirectWayland,
|
|
||||||
Filesystem: []*fst.FilesystemConfig{
|
|
||||||
{Src: path.Join(pathSet.nixPath, "store"), Dst: "/nix/store", Must: true},
|
|
||||||
{Src: pathSet.metaPath, Dst: path.Join(fst.Tmp, "app"), Must: true},
|
|
||||||
{Src: "/etc/resolv.conf"},
|
|
||||||
{Src: "/sys/block"},
|
|
||||||
{Src: "/sys/bus"},
|
|
||||||
{Src: "/sys/class"},
|
|
||||||
{Src: "/sys/dev"},
|
|
||||||
{Src: "/sys/devices"},
|
|
||||||
},
|
|
||||||
Link: [][2]string{
|
|
||||||
{app.CurrentSystem, "/run/current-system"},
|
|
||||||
{"/run/current-system/sw/bin", "/bin"},
|
|
||||||
{"/run/current-system/sw/bin", "/usr/bin"},
|
|
||||||
},
|
|
||||||
Etc: path.Join(pathSet.cacheDir, "etc"),
|
|
||||||
AutoEtc: true,
|
|
||||||
},
|
|
||||||
ExtraPerms: []*fst.ExtraPermConfig{
|
|
||||||
{Path: dataHome, Execute: true},
|
|
||||||
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
|
||||||
},
|
|
||||||
SystemBus: app.SystemBus,
|
|
||||||
SessionBus: app.SessionBus,
|
|
||||||
Enablements: app.Enablements,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Expose GPU devices.
|
|
||||||
*/
|
|
||||||
|
|
||||||
if app.GPU {
|
|
||||||
config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem,
|
|
||||||
&fst.FilesystemConfig{Src: path.Join(pathSet.nixPath, ".nixGL"), Dst: path.Join(fst.Tmp, "nixGL")})
|
|
||||||
appendGPUFilesystem(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Spawn app.
|
|
||||||
*/
|
|
||||||
|
|
||||||
mustRunApp(ctx, config, func() {})
|
|
||||||
return errSuccess
|
|
||||||
}).
|
|
||||||
Flag(&flagDropShellNixGL, "s", command.BoolFlag(false), "Drop to a shell on nixGL build").
|
|
||||||
Flag(&flagAutoDrivers, "auto-drivers", command.BoolFlag(false), "Attempt automatic opengl driver detection")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
|
||||||
fmsg.Verbosef("command returned %v", err)
|
|
||||||
if errors.Is(err, errSuccess) {
|
|
||||||
fmsg.BeforeExit()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
log.Fatal("unreachable")
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -70,32 +69,3 @@ func pathSetByApp(id string) *appPathSet {
|
|||||||
pathSet.nixPath = path.Join(pathSet.cacheDir, "nix")
|
pathSet.nixPath = path.Join(pathSet.cacheDir, "nix")
|
||||||
return pathSet
|
return pathSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendGPUFilesystem(config *fst.Config) {
|
|
||||||
config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem, []*fst.FilesystemConfig{
|
|
||||||
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
|
|
||||||
{Src: "/dev/dri", Device: true},
|
|
||||||
// mali
|
|
||||||
{Src: "/dev/mali", Device: true},
|
|
||||||
{Src: "/dev/mali0", Device: true},
|
|
||||||
{Src: "/dev/umplock", Device: true},
|
|
||||||
// nvidia
|
|
||||||
{Src: "/dev/nvidiactl", Device: true},
|
|
||||||
{Src: "/dev/nvidia-modeset", Device: true},
|
|
||||||
// nvidia OpenCL/CUDA
|
|
||||||
{Src: "/dev/nvidia-uvm", Device: true},
|
|
||||||
{Src: "/dev/nvidia-uvm-tools", Device: true},
|
|
||||||
|
|
||||||
// flatpak commit d2dff2875bb3b7e2cd92d8204088d743fd07f3ff
|
|
||||||
{Src: "/dev/nvidia0", Device: true}, {Src: "/dev/nvidia1", Device: true},
|
|
||||||
{Src: "/dev/nvidia2", Device: true}, {Src: "/dev/nvidia3", Device: true},
|
|
||||||
{Src: "/dev/nvidia4", Device: true}, {Src: "/dev/nvidia5", Device: true},
|
|
||||||
{Src: "/dev/nvidia6", Device: true}, {Src: "/dev/nvidia7", Device: true},
|
|
||||||
{Src: "/dev/nvidia8", Device: true}, {Src: "/dev/nvidia9", Device: true},
|
|
||||||
{Src: "/dev/nvidia10", Device: true}, {Src: "/dev/nvidia11", Device: true},
|
|
||||||
{Src: "/dev/nvidia12", Device: true}, {Src: "/dev/nvidia13", Device: true},
|
|
||||||
{Src: "/dev/nvidia14", Device: true}, {Src: "/dev/nvidia15", Device: true},
|
|
||||||
{Src: "/dev/nvidia16", Device: true}, {Src: "/dev/nvidia17", Device: true},
|
|
||||||
{Src: "/dev/nvidia18", Device: true}, {Src: "/dev/nvidia19", Device: true},
|
|
||||||
}...)
|
|
||||||
}
|
|
||||||
|
@ -1,28 +1,65 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/internal/app"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustRunApp(ctx context.Context, config *fst.Config, beforeFail func()) {
|
const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
|
||||||
rs := new(fst.RunState)
|
|
||||||
a := app.MustNew(std)
|
|
||||||
|
|
||||||
if sa, err := a.Seal(config); err != nil {
|
var (
|
||||||
fmsg.PrintBaseError(err, "cannot seal app:")
|
Fmain = compPoison
|
||||||
rs.ExitCode = 1
|
)
|
||||||
|
|
||||||
|
func fortifyApp(config *fst.Config, beforeFail func()) {
|
||||||
|
var (
|
||||||
|
cmd *exec.Cmd
|
||||||
|
st io.WriteCloser
|
||||||
|
)
|
||||||
|
if p, ok := internal.Path(Fmain); !ok {
|
||||||
|
beforeFail()
|
||||||
|
log.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly")
|
||||||
|
} else if r, w, err := os.Pipe(); err != nil {
|
||||||
|
beforeFail()
|
||||||
|
log.Fatalf("cannot pipe: %v", err)
|
||||||
} else {
|
} else {
|
||||||
// this updates ExitCode
|
if fmsg.Load() {
|
||||||
app.PrintRunStateErr(rs, sa.Run(ctx, rs))
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if rs.ExitCode != 0 {
|
go func() {
|
||||||
|
if err := json.NewEncoder(st).Encode(config); err != nil {
|
||||||
|
beforeFail()
|
||||||
|
log.Fatalf("cannot send configuration: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
beforeFail()
|
beforeFail()
|
||||||
os.Exit(rs.ExitCode)
|
log.Fatalf("cannot start fortify: %v", err)
|
||||||
|
}
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
var exitError *exec.ExitError
|
||||||
|
if errors.As(err, &exitError) {
|
||||||
|
beforeFail()
|
||||||
|
internal.Exit(exitError.ExitCode())
|
||||||
|
} else {
|
||||||
|
beforeFail()
|
||||||
|
log.Fatalf("cannot wait: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
178
cmd/fpkg/start.go
Normal file
178
cmd/fpkg/start.go
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func actionStart(args []string) {
|
||||||
|
set := flag.NewFlagSet("start", flag.ExitOnError)
|
||||||
|
var (
|
||||||
|
dropShell bool
|
||||||
|
dropShellNixGL bool
|
||||||
|
autoDrivers bool
|
||||||
|
)
|
||||||
|
set.BoolVar(&dropShell, "s", false, "Drop to a shell")
|
||||||
|
set.BoolVar(&dropShellNixGL, "sg", false, "Drop to a shell on nixGL build")
|
||||||
|
set.BoolVar(&autoDrivers, "autodrivers", false, "Attempt automatic opengl driver detection")
|
||||||
|
|
||||||
|
// Ignore errors; set is set for ExitOnError.
|
||||||
|
_ = set.Parse(args)
|
||||||
|
|
||||||
|
args = set.Args()
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
log.Fatal("invalid argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parse app metadata.
|
||||||
|
*/
|
||||||
|
|
||||||
|
id := args[0]
|
||||||
|
pathSet := pathSetByApp(id)
|
||||||
|
app := loadBundleInfo(pathSet.metaPath, func() {})
|
||||||
|
if app.ID != id {
|
||||||
|
log.Fatalf("app %q claims to have identifier %q", id, app.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prepare nixGL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if app.GPU && autoDrivers {
|
||||||
|
withNixDaemon("nix-gl", []string{
|
||||||
|
"mkdir -p /nix/.nixGL/auto",
|
||||||
|
"rm -rf /nix/.nixGL/auto",
|
||||||
|
"export NIXPKGS_ALLOW_UNFREE=1",
|
||||||
|
"nix build --impure " +
|
||||||
|
"--out-link /nix/.nixGL/auto/opengl " +
|
||||||
|
"--override-input nixpkgs path:/etc/nixpkgs " +
|
||||||
|
"path:" + app.NixGL,
|
||||||
|
"nix build --impure " +
|
||||||
|
"--out-link /nix/.nixGL/auto/vulkan " +
|
||||||
|
"--override-input nixpkgs path:/etc/nixpkgs " +
|
||||||
|
"path:" + app.NixGL + "#nixVulkanNvidia",
|
||||||
|
}, true, func(config *fst.Config) *fst.Config {
|
||||||
|
config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem, []*fst.FilesystemConfig{
|
||||||
|
{Src: "/etc/resolv.conf"},
|
||||||
|
{Src: "/sys/block"},
|
||||||
|
{Src: "/sys/bus"},
|
||||||
|
{Src: "/sys/class"},
|
||||||
|
{Src: "/sys/dev"},
|
||||||
|
{Src: "/sys/devices"},
|
||||||
|
}...)
|
||||||
|
appendGPUFilesystem(config)
|
||||||
|
return config
|
||||||
|
}, app, pathSet, dropShellNixGL, func() {})
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create app configuration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
command := make([]string, 1, len(args))
|
||||||
|
if !dropShell {
|
||||||
|
command[0] = app.Launcher
|
||||||
|
} else {
|
||||||
|
command[0] = shellPath
|
||||||
|
}
|
||||||
|
command = append(command, args[1:]...)
|
||||||
|
|
||||||
|
config := &fst.Config{
|
||||||
|
ID: app.ID,
|
||||||
|
Command: command,
|
||||||
|
Confinement: fst.ConfinementConfig{
|
||||||
|
AppID: app.AppID,
|
||||||
|
Groups: app.Groups,
|
||||||
|
Username: "fortify",
|
||||||
|
Inner: path.Join("/data/data", app.ID),
|
||||||
|
Outer: pathSet.homeDir,
|
||||||
|
Sandbox: &fst.SandboxConfig{
|
||||||
|
Hostname: formatHostname(app.Name),
|
||||||
|
UserNS: app.UserNS,
|
||||||
|
Net: app.Net,
|
||||||
|
Dev: app.Dev,
|
||||||
|
Syscall: &bwrap.SyscallPolicy{DenyDevel: !app.Devel, Multiarch: app.Multiarch, Bluetooth: app.Bluetooth},
|
||||||
|
NoNewSession: app.NoNewSession || dropShell,
|
||||||
|
MapRealUID: app.MapRealUID,
|
||||||
|
DirectWayland: app.DirectWayland,
|
||||||
|
Filesystem: []*fst.FilesystemConfig{
|
||||||
|
{Src: path.Join(pathSet.nixPath, "store"), Dst: "/nix/store", Must: true},
|
||||||
|
{Src: pathSet.metaPath, Dst: path.Join(fst.Tmp, "app"), Must: true},
|
||||||
|
{Src: "/etc/resolv.conf"},
|
||||||
|
{Src: "/sys/block"},
|
||||||
|
{Src: "/sys/bus"},
|
||||||
|
{Src: "/sys/class"},
|
||||||
|
{Src: "/sys/dev"},
|
||||||
|
{Src: "/sys/devices"},
|
||||||
|
},
|
||||||
|
Link: [][2]string{
|
||||||
|
{app.CurrentSystem, "/run/current-system"},
|
||||||
|
{"/run/current-system/sw/bin", "/bin"},
|
||||||
|
{"/run/current-system/sw/bin", "/usr/bin"},
|
||||||
|
},
|
||||||
|
Etc: path.Join(pathSet.cacheDir, "etc"),
|
||||||
|
AutoEtc: true,
|
||||||
|
},
|
||||||
|
ExtraPerms: []*fst.ExtraPermConfig{
|
||||||
|
{Path: dataHome, Execute: true},
|
||||||
|
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||||
|
},
|
||||||
|
SystemBus: app.SystemBus,
|
||||||
|
SessionBus: app.SessionBus,
|
||||||
|
Enablements: app.Enablements,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Expose GPU devices.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if app.GPU {
|
||||||
|
config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem,
|
||||||
|
&fst.FilesystemConfig{Src: path.Join(pathSet.nixPath, ".nixGL"), Dst: path.Join(fst.Tmp, "nixGL")})
|
||||||
|
appendGPUFilesystem(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Spawn app.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fortifyApp(config, func() {})
|
||||||
|
internal.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendGPUFilesystem(config *fst.Config) {
|
||||||
|
config.Confinement.Sandbox.Filesystem = append(config.Confinement.Sandbox.Filesystem, []*fst.FilesystemConfig{
|
||||||
|
// flatpak commit 763a686d874dd668f0236f911de00b80766ffe79
|
||||||
|
{Src: "/dev/dri", Device: true},
|
||||||
|
// mali
|
||||||
|
{Src: "/dev/mali", Device: true},
|
||||||
|
{Src: "/dev/mali0", Device: true},
|
||||||
|
{Src: "/dev/umplock", Device: true},
|
||||||
|
// nvidia
|
||||||
|
{Src: "/dev/nvidiactl", Device: true},
|
||||||
|
{Src: "/dev/nvidia-modeset", Device: true},
|
||||||
|
// nvidia OpenCL/CUDA
|
||||||
|
{Src: "/dev/nvidia-uvm", Device: true},
|
||||||
|
{Src: "/dev/nvidia-uvm-tools", Device: true},
|
||||||
|
|
||||||
|
// flatpak commit d2dff2875bb3b7e2cd92d8204088d743fd07f3ff
|
||||||
|
{Src: "/dev/nvidia0", Device: true}, {Src: "/dev/nvidia1", Device: true},
|
||||||
|
{Src: "/dev/nvidia2", Device: true}, {Src: "/dev/nvidia3", Device: true},
|
||||||
|
{Src: "/dev/nvidia4", Device: true}, {Src: "/dev/nvidia5", Device: true},
|
||||||
|
{Src: "/dev/nvidia6", Device: true}, {Src: "/dev/nvidia7", Device: true},
|
||||||
|
{Src: "/dev/nvidia8", Device: true}, {Src: "/dev/nvidia9", Device: true},
|
||||||
|
{Src: "/dev/nvidia10", Device: true}, {Src: "/dev/nvidia11", Device: true},
|
||||||
|
{Src: "/dev/nvidia12", Device: true}, {Src: "/dev/nvidia13", Device: true},
|
||||||
|
{Src: "/dev/nvidia14", Device: true}, {Src: "/dev/nvidia15", Device: true},
|
||||||
|
{Src: "/dev/nvidia16", Device: true}, {Src: "/dev/nvidia17", Device: true},
|
||||||
|
{Src: "/dev/nvidia18", Device: true}, {Src: "/dev/nvidia19", Device: true},
|
||||||
|
}...)
|
||||||
|
}
|
@ -1,60 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
{
|
|
||||||
users.users = {
|
|
||||||
alice = {
|
|
||||||
isNormalUser = true;
|
|
||||||
description = "Alice Foobar";
|
|
||||||
password = "foobar";
|
|
||||||
uid = 1000;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager.users.alice.home.stateVersion = "24.11";
|
|
||||||
|
|
||||||
# Automatically login on tty1 as a normal user:
|
|
||||||
services.getty.autologinUser = "alice";
|
|
||||||
|
|
||||||
environment = {
|
|
||||||
variables = {
|
|
||||||
SWAYSOCK = "/tmp/sway-ipc.sock";
|
|
||||||
WLR_RENDERER = "pixman";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Automatically configure and start Sway when logging in on tty1:
|
|
||||||
programs.bash.loginShellInit = ''
|
|
||||||
if [ "$(tty)" = "/dev/tty1" ]; then
|
|
||||||
set -e
|
|
||||||
|
|
||||||
mkdir -p ~/.config/sway
|
|
||||||
(sed s/Mod4/Mod1/ /etc/sway/config &&
|
|
||||||
echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill' &&
|
|
||||||
echo 'output Virtual-1 res 1680x1050') > ~/.config/sway/config
|
|
||||||
|
|
||||||
sway --validate
|
|
||||||
systemd-cat --identifier=sway sway && touch /tmp/sway-exit-ok
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
programs.sway.enable = true;
|
|
||||||
|
|
||||||
virtualisation = {
|
|
||||||
diskSize = 6 * 1024;
|
|
||||||
|
|
||||||
qemu.options = [
|
|
||||||
# Need to switch to a different GPU driver than the default one (-vga std) so that Sway can launch:
|
|
||||||
"-vga none -device virtio-gpu-pci"
|
|
||||||
|
|
||||||
# Increase zstd performance:
|
|
||||||
"-smp 8"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.fortify = {
|
|
||||||
enable = true;
|
|
||||||
stateDir = "/var/lib/fortify";
|
|
||||||
users.alice = 0;
|
|
||||||
|
|
||||||
home-manager = _: _: { home.stateVersion = "23.05"; };
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
nixosTest,
|
|
||||||
callPackage,
|
|
||||||
|
|
||||||
system,
|
|
||||||
self,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
buildPackage = self.buildPackage.${system};
|
|
||||||
in
|
|
||||||
nixosTest {
|
|
||||||
name = "fpkg";
|
|
||||||
nodes.machine = {
|
|
||||||
environment.etc = {
|
|
||||||
"foot.pkg".source = callPackage ./foot.nix { inherit buildPackage; };
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [
|
|
||||||
./configuration.nix
|
|
||||||
|
|
||||||
self.nixosModules.fortify
|
|
||||||
self.inputs.home-manager.nixosModules.home-manager
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# adapted from nixos sway integration tests
|
|
||||||
|
|
||||||
# testScriptWithTypes:49: error: Cannot call function of unknown type
|
|
||||||
# (machine.succeed if succeed else machine.execute)(
|
|
||||||
# ^
|
|
||||||
# Found 1 error in 1 file (checked 1 source file)
|
|
||||||
skipTypeCheck = true;
|
|
||||||
testScript = builtins.readFile ./test.py;
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
buildPackage,
|
|
||||||
foot,
|
|
||||||
wayland-utils,
|
|
||||||
inconsolata,
|
|
||||||
}:
|
|
||||||
|
|
||||||
buildPackage {
|
|
||||||
name = "foot";
|
|
||||||
inherit (foot) version;
|
|
||||||
|
|
||||||
app_id = 2;
|
|
||||||
id = "org.codeberg.dnkl.foot";
|
|
||||||
|
|
||||||
modules = [
|
|
||||||
{
|
|
||||||
home.packages = [
|
|
||||||
foot
|
|
||||||
|
|
||||||
# For wayland-info:
|
|
||||||
wayland-utils
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
nixosModules = [
|
|
||||||
{
|
|
||||||
# To help with OCR:
|
|
||||||
environment.etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
|
|
||||||
main = {
|
|
||||||
font = "inconsolata:size=14";
|
|
||||||
};
|
|
||||||
colors = rec {
|
|
||||||
foreground = "000000";
|
|
||||||
background = "ffffff";
|
|
||||||
regular2 = foreground;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
fonts.packages = [ inconsolata ];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
exec foot "$@"
|
|
||||||
'';
|
|
||||||
}
|
|
@ -1,108 +0,0 @@
|
|||||||
import json
|
|
||||||
import shlex
|
|
||||||
|
|
||||||
q = shlex.quote
|
|
||||||
NODE_GROUPS = ["nodes", "floating_nodes"]
|
|
||||||
|
|
||||||
|
|
||||||
def swaymsg(command: str = "", succeed=True, type="command"):
|
|
||||||
assert command != "" or type != "command", "Must specify command or type"
|
|
||||||
shell = q(f"swaymsg -t {q(type)} -- {q(command)}")
|
|
||||||
with machine.nested(
|
|
||||||
f"sending swaymsg {shell!r}" + " (allowed to fail)" * (not succeed)
|
|
||||||
):
|
|
||||||
ret = (machine.succeed if succeed else machine.execute)(
|
|
||||||
f"su - alice -c {shell}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# execute also returns a status code, but disregard.
|
|
||||||
if not succeed:
|
|
||||||
_, ret = ret
|
|
||||||
|
|
||||||
if not succeed and not ret:
|
|
||||||
return None
|
|
||||||
|
|
||||||
parsed = json.loads(ret)
|
|
||||||
return parsed
|
|
||||||
|
|
||||||
|
|
||||||
def walk(tree):
|
|
||||||
yield tree
|
|
||||||
for group in NODE_GROUPS:
|
|
||||||
for node in tree.get(group, []):
|
|
||||||
yield from walk(node)
|
|
||||||
|
|
||||||
|
|
||||||
def wait_for_window(pattern):
|
|
||||||
def func(last_chance):
|
|
||||||
nodes = (node["name"] for node in walk(swaymsg(type="get_tree")))
|
|
||||||
|
|
||||||
if last_chance:
|
|
||||||
nodes = list(nodes)
|
|
||||||
machine.log(f"Last call! Current list of windows: {nodes}")
|
|
||||||
|
|
||||||
return any(pattern in name for name in nodes)
|
|
||||||
|
|
||||||
retry(func)
|
|
||||||
|
|
||||||
|
|
||||||
def collect_state_ui(name):
|
|
||||||
swaymsg(f"exec fortify ps > '/tmp/{name}.ps'")
|
|
||||||
machine.copy_from_vm(f"/tmp/{name}.ps", "")
|
|
||||||
swaymsg(f"exec fortify --json ps > '/tmp/{name}.json'")
|
|
||||||
machine.copy_from_vm(f"/tmp/{name}.json", "")
|
|
||||||
machine.screenshot(name)
|
|
||||||
|
|
||||||
|
|
||||||
def check_state(name, enablements):
|
|
||||||
instances = json.loads(machine.succeed("sudo -u alice -i XDG_RUNTIME_DIR=/run/user/1000 fortify --json ps"))
|
|
||||||
if len(instances) != 1:
|
|
||||||
raise Exception(f"unexpected state length {len(instances)}")
|
|
||||||
instance = next(iter(instances.values()))
|
|
||||||
|
|
||||||
config = instance['config']
|
|
||||||
|
|
||||||
if len(config['command']) != 1 or not (config['command'][0].startswith("/nix/store/")) or f"fortify-{name}-" not in (config['command'][0]):
|
|
||||||
raise Exception(f"unexpected command {instance['config']['command']}")
|
|
||||||
|
|
||||||
if config['confinement']['enablements'] != enablements:
|
|
||||||
raise Exception(f"unexpected enablements {instance['config']['confinement']['enablements']}")
|
|
||||||
|
|
||||||
|
|
||||||
start_all()
|
|
||||||
machine.wait_for_unit("multi-user.target")
|
|
||||||
|
|
||||||
# To check fortify's version:
|
|
||||||
print(machine.succeed("sudo -u alice -i fortify version"))
|
|
||||||
|
|
||||||
# Wait for Sway to complete startup:
|
|
||||||
machine.wait_for_file("/run/user/1000/wayland-1")
|
|
||||||
machine.wait_for_file("/tmp/sway-ipc.sock")
|
|
||||||
|
|
||||||
# Prepare fpkg directory:
|
|
||||||
machine.succeed("install -dm 0700 -o alice -g users /var/lib/fortify/1000")
|
|
||||||
|
|
||||||
# Install fpkg app:
|
|
||||||
swaymsg("exec fpkg -v install /etc/foot.pkg && touch /tmp/fpkg-install-done")
|
|
||||||
machine.wait_for_file("/tmp/fpkg-install-done")
|
|
||||||
|
|
||||||
# Start app (foot) with Wayland enablement:
|
|
||||||
swaymsg("exec fpkg -v start org.codeberg.dnkl.foot")
|
|
||||||
wait_for_window("fortify@machine-foot")
|
|
||||||
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
|
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/2/success-client")
|
|
||||||
collect_state_ui("app_wayland")
|
|
||||||
check_state("foot", 13)
|
|
||||||
# Verify acl on XDG_RUNTIME_DIR:
|
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000002"))
|
|
||||||
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 1000002")
|
|
||||||
|
|
||||||
# Exit Sway and verify process exit status 0:
|
|
||||||
swaymsg("exit", succeed=False)
|
|
||||||
machine.wait_for_file("/tmp/sway-exit-ok")
|
|
||||||
|
|
||||||
# Print fortify runDir contents:
|
|
||||||
print(machine.succeed("find /run/user/1000/fortify"))
|
|
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -11,11 +10,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func withNixDaemon(
|
func withNixDaemon(
|
||||||
ctx context.Context,
|
|
||||||
action string, command []string, net bool, updateConfig func(config *fst.Config) *fst.Config,
|
action string, command []string, net bool, updateConfig func(config *fst.Config) *fst.Config,
|
||||||
app *bundleInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
app *bundleInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
||||||
) {
|
) {
|
||||||
mustRunAppDropShell(ctx, updateConfig(&fst.Config{
|
fortifyAppDropShell(updateConfig(&fst.Config{
|
||||||
ID: app.ID,
|
ID: app.ID,
|
||||||
Command: []string{shellPath, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
|
Command: []string{shellPath, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
|
||||||
// start nix-daemon
|
// start nix-daemon
|
||||||
@ -58,11 +56,8 @@ func withNixDaemon(
|
|||||||
}), dropShell, beforeFail)
|
}), dropShell, beforeFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withCacheDir(
|
func withCacheDir(action string, command []string, workDir string, app *bundleInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
|
||||||
ctx context.Context,
|
fortifyAppDropShell(&fst.Config{
|
||||||
action string, command []string, workDir string,
|
|
||||||
app *bundleInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
|
|
||||||
mustRunAppDropShell(ctx, &fst.Config{
|
|
||||||
ID: app.ID,
|
ID: app.ID,
|
||||||
Command: []string{shellPath, "-lc", strings.Join(command, " && ")},
|
Command: []string{shellPath, "-lc", strings.Join(command, " && ")},
|
||||||
Confinement: fst.ConfinementConfig{
|
Confinement: fst.ConfinementConfig{
|
||||||
@ -95,12 +90,12 @@ func withCacheDir(
|
|||||||
}, dropShell, beforeFail)
|
}, dropShell, beforeFail)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustRunAppDropShell(ctx context.Context, config *fst.Config, dropShell bool, beforeFail func()) {
|
func fortifyAppDropShell(config *fst.Config, dropShell bool, beforeFail func()) {
|
||||||
if dropShell {
|
if dropShell {
|
||||||
config.Command = []string{shellPath, "-l"}
|
config.Command = []string{shellPath, "-l"}
|
||||||
mustRunApp(ctx, config, beforeFail)
|
fortifyApp(config, beforeFail)
|
||||||
beforeFail()
|
beforeFail()
|
||||||
internal.Exit(0)
|
internal.Exit(0)
|
||||||
}
|
}
|
||||||
mustRunApp(ctx, config, beforeFail)
|
fortifyApp(config, beforeFail)
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
|
||||||
fsuConfFile = "/etc/fsurc"
|
fsuConfFile = "/etc/fsurc"
|
||||||
envShim = "FORTIFY_SHIM"
|
envShim = "FORTIFY_SHIM"
|
||||||
envAID = "FORTIFY_APP_ID"
|
envAID = "FORTIFY_APP_ID"
|
||||||
@ -21,6 +22,10 @@ const (
|
|||||||
PR_SET_NO_NEW_PRIVS = 0x26
|
PR_SET_NO_NEW_PRIVS = 0x26
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Fmain = compPoison
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("fsu: ")
|
log.SetPrefix("fsu: ")
|
||||||
@ -35,16 +40,20 @@ func main() {
|
|||||||
log.Fatal("this program must not be started by root")
|
log.Fatal("this program must not be started by root")
|
||||||
}
|
}
|
||||||
|
|
||||||
var toolPath string
|
var fmain string
|
||||||
|
if p, ok := checkPath(Fmain); !ok {
|
||||||
|
log.Fatal("invalid fortify path, this copy of fsu is not compiled correctly")
|
||||||
|
} else {
|
||||||
|
fmain = p
|
||||||
|
}
|
||||||
|
|
||||||
pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
|
pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
|
||||||
if p, err := os.Readlink(pexe); err != nil {
|
if p, err := os.Readlink(pexe); err != nil {
|
||||||
log.Fatalf("cannot read parent executable path: %v", err)
|
log.Fatalf("cannot read parent executable path: %v", err)
|
||||||
} else if strings.HasSuffix(p, " (deleted)") {
|
} else if strings.HasSuffix(p, " (deleted)") {
|
||||||
log.Fatal("fortify executable has been deleted")
|
log.Fatal("fortify executable has been deleted")
|
||||||
} else if p != mustCheckPath(fmain) && p != mustCheckPath(fpkg) {
|
} else if p != fmain {
|
||||||
log.Fatal("this program must be started by fortify")
|
log.Fatal("this program must be started by fortify")
|
||||||
} else {
|
|
||||||
toolPath = p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// uid = 1000000 +
|
// uid = 1000000 +
|
||||||
@ -138,9 +147,13 @@ func main() {
|
|||||||
if _, _, errno := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0); errno != 0 {
|
if _, _, errno := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0); errno != 0 {
|
||||||
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
||||||
}
|
}
|
||||||
if err := syscall.Exec(toolPath, []string{"fortify", "shim"}, []string{envShim + "=" + shimSetupFd}); err != nil {
|
if err := syscall.Exec(fmain, []string{"fortify", "shim"}, []string{envShim + "=" + shimSetupFd}); err != nil {
|
||||||
log.Fatalf("cannot start shim: %v", err)
|
log.Fatalf("cannot start shim: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkPath(p string) (string, bool) {
|
||||||
|
return p, p != compPoison && p != "" && path.IsAbs(p)
|
||||||
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
lib,
|
|
||||||
buildGoModule,
|
buildGoModule,
|
||||||
fortify ? abort "fortify package required",
|
fortify ? abort "fortify package required",
|
||||||
}:
|
}:
|
||||||
@ -16,15 +15,5 @@ buildGoModule {
|
|||||||
go mod init fsu >& /dev/null
|
go mod init fsu >& /dev/null
|
||||||
'';
|
'';
|
||||||
|
|
||||||
ldflags =
|
ldflags = [ "-X main.Fmain=${fortify}/libexec/fortify" ];
|
||||||
lib.attrsets.foldlAttrs
|
|
||||||
(
|
|
||||||
ldflags: name: value:
|
|
||||||
ldflags ++ [ "-X main.${name}=${value}" ]
|
|
||||||
)
|
|
||||||
[ "-s -w" ]
|
|
||||||
{
|
|
||||||
fmain = "${fortify}/libexec/fortify";
|
|
||||||
fpkg = "${fortify}/libexec/fpkg";
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
|
|
||||||
|
|
||||||
var (
|
|
||||||
fmain = compPoison
|
|
||||||
fpkg = compPoison
|
|
||||||
)
|
|
||||||
|
|
||||||
func mustCheckPath(p string) string {
|
|
||||||
if p != compPoison && p != "" && path.IsAbs(p) {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
log.Fatal("this program is compiled incorrectly")
|
|
||||||
return compPoison
|
|
||||||
}
|
|
@ -110,7 +110,7 @@ func (p *Proxy) Start(ctx context.Context, output io.Writer, sandbox bool) error
|
|||||||
bc.Bind(k, k)
|
bc.Bind(k, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
h = helper.MustNewBwrap(bc, toolPath, true, p.seal, argF, nil, nil)
|
h = helper.MustNewBwrap(bc, toolPath, p.seal, argF, nil, nil)
|
||||||
p.bwrap = bc
|
p.bwrap = bc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
dist/release.sh
vendored
7
dist/release.sh
vendored
@ -10,10 +10,9 @@ cp -rv "comp" "${out}"
|
|||||||
|
|
||||||
go generate ./...
|
go generate ./...
|
||||||
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static'
|
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w -buildid= -extldflags '-static'
|
||||||
-X git.gensokyo.uk/security/fortify/internal.version=${VERSION}
|
-X git.gensokyo.uk/security/fortify/internal.Version=${VERSION}
|
||||||
-X git.gensokyo.uk/security/fortify/internal.fsu=/usr/bin/fsu
|
-X git.gensokyo.uk/security/fortify/internal.Fsu=/usr/bin/fsu
|
||||||
-X main.fmain=/usr/bin/fortify
|
-X main.Fmain=/usr/bin/fortify" ./...
|
||||||
-X main.fpkg=/usr/bin/fpkg" ./...
|
|
||||||
|
|
||||||
rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}"
|
rm -f "./${out}.tar.gz" && tar -C dist -czf "${out}.tar.gz" "${pname}"
|
||||||
rm -rf "./${out}"
|
rm -rf "./${out}"
|
||||||
|
46
error.go
Normal file
46
error.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func logWaitError(err error) {
|
||||||
|
var e *fmsg.BaseError
|
||||||
|
if !fmsg.AsBaseError(err, &e) {
|
||||||
|
log.Println("wait failed:", err)
|
||||||
|
} else {
|
||||||
|
// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError
|
||||||
|
var se *app.StateStoreError
|
||||||
|
if !errors.As(err, &se) {
|
||||||
|
// does not need special handling
|
||||||
|
log.Print(e.Message())
|
||||||
|
} else {
|
||||||
|
// inner error are either unwrapped store errors
|
||||||
|
// or joined errors returned by *appSealTx revert
|
||||||
|
// wrapped in *app.BaseError
|
||||||
|
var ej app.RevertCompoundError
|
||||||
|
if !errors.As(se.InnerErr, &ej) {
|
||||||
|
// does not require special handling
|
||||||
|
log.Print(e.Message())
|
||||||
|
} else {
|
||||||
|
errs := ej.Unwrap()
|
||||||
|
|
||||||
|
// every error here is wrapped in *app.BaseError
|
||||||
|
for _, ei := range errs {
|
||||||
|
var eb *fmsg.BaseError
|
||||||
|
if !errors.As(ei, &eb) {
|
||||||
|
// unreachable
|
||||||
|
log.Println("invalid error type returned by revert:", ei)
|
||||||
|
} else {
|
||||||
|
// print inner *app.BaseError message
|
||||||
|
log.Print(eb.Message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
flake.nix
37
flake.nix
@ -58,7 +58,6 @@
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
fortify = callPackage ./test { inherit system self; };
|
fortify = callPackage ./test { inherit system self; };
|
||||||
fpkg = callPackage ./cmd/fpkg/test { inherit system self; };
|
|
||||||
race = callPackage ./test {
|
race = callPackage ./test {
|
||||||
inherit system self;
|
inherit system self;
|
||||||
withRace = true;
|
withRace = true;
|
||||||
@ -68,7 +67,7 @@
|
|||||||
cd ${./.}
|
cd ${./.}
|
||||||
|
|
||||||
echo "running nixfmt..."
|
echo "running nixfmt..."
|
||||||
nixfmt --width=256 --check .
|
nixfmt --check .
|
||||||
|
|
||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
@ -98,36 +97,28 @@
|
|||||||
packages = forAllSystems (
|
packages = forAllSystems (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
inherit (self.packages.${system}) fortify fsu;
|
inherit (self.packages.${system}) fortify;
|
||||||
pkgs = nixpkgsFor.${system};
|
pkgs = nixpkgsFor.${system};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
default = fortify;
|
default = self.packages.${system}.fortify;
|
||||||
fortify = pkgs.pkgsStatic.callPackage ./package.nix {
|
fortify = pkgs.pkgsStatic.callPackage ./package.nix {
|
||||||
inherit (pkgs)
|
inherit (pkgs) bubblewrap xdg-dbus-proxy glibc;
|
||||||
bubblewrap
|
|
||||||
xdg-dbus-proxy
|
|
||||||
glibc
|
|
||||||
zstd
|
|
||||||
gnutar
|
|
||||||
coreutils
|
|
||||||
;
|
|
||||||
};
|
};
|
||||||
fsu = pkgs.callPackage ./cmd/fsu/package.nix { inherit (self.packages.${system}) fortify; };
|
fsu = pkgs.callPackage ./cmd/fsu/package.nix { inherit (self.packages.${system}) fortify; };
|
||||||
|
|
||||||
dist = pkgs.runCommand "${fortify.name}-dist" { inherit (self.devShells.${system}.default) buildInputs; } ''
|
dist =
|
||||||
# go requires XDG_CACHE_HOME for the build cache
|
pkgs.runCommand "${fortify.name}-dist" { inherit (self.devShells.${system}.default) buildInputs; }
|
||||||
export XDG_CACHE_HOME="$(mktemp -d)"
|
''
|
||||||
|
# go requires XDG_CACHE_HOME for the build cache
|
||||||
|
export XDG_CACHE_HOME="$(mktemp -d)"
|
||||||
|
|
||||||
# get a different workdir as go does not like /build
|
# get a different workdir as go does not like /build
|
||||||
cd $(mktemp -d) \
|
cd $(mktemp -d) && cp -r ${fortify.src}/. . && chmod -R +w .
|
||||||
&& cp -r ${fortify.src}/. . \
|
|
||||||
&& chmod +w cmd && cp -r ${fsu.src}/. cmd/fsu/ \
|
|
||||||
&& chmod -R +w .
|
|
||||||
|
|
||||||
export FORTIFY_VERSION="v${fortify.version}"
|
export FORTIFY_VERSION="v${fortify.version}"
|
||||||
./dist/release.sh && mkdir $out && cp -v "dist/fortify-$FORTIFY_VERSION.tar.gz"* $out
|
./dist/release.sh && mkdir $out && cp -v "dist/fortify-$FORTIFY_VERSION.tar.gz"* $out
|
||||||
'';
|
'';
|
||||||
|
|
||||||
fhs = pkgs.buildFHSEnv {
|
fhs = pkgs.buildFHSEnv {
|
||||||
pname = "fortify-fhs";
|
pname = "fortify-fhs";
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
"git.gensokyo.uk/security/fortify/helper/proc"
|
"git.gensokyo.uk/security/fortify/helper/proc"
|
||||||
@ -24,9 +23,6 @@ type bubblewrap struct {
|
|||||||
// name of the command to run in bwrap
|
// name of the command to run in bwrap
|
||||||
name string
|
name string
|
||||||
|
|
||||||
// whether to set process group id
|
|
||||||
setpgid bool
|
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
*helperCmd
|
*helperCmd
|
||||||
}
|
}
|
||||||
@ -42,10 +38,6 @@ func (b *bubblewrap) Start(ctx context.Context, stat bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
args := b.finalise(ctx, stat)
|
args := b.finalise(ctx, stat)
|
||||||
if b.setpgid {
|
|
||||||
b.Cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.Cmd.Args = slices.Grow(b.Cmd.Args, 4+len(args))
|
b.Cmd.Args = slices.Grow(b.Cmd.Args, 4+len(args))
|
||||||
b.Cmd.Args = append(b.Cmd.Args, "--args", strconv.Itoa(int(b.argsFd)), "--", b.name)
|
b.Cmd.Args = append(b.Cmd.Args, "--args", strconv.Itoa(int(b.argsFd)), "--", b.name)
|
||||||
b.Cmd.Args = append(b.Cmd.Args, args...)
|
b.Cmd.Args = append(b.Cmd.Args, args...)
|
||||||
@ -56,12 +48,12 @@ func (b *bubblewrap) Start(ctx context.Context, stat bool) error {
|
|||||||
// If wt is nil, the child process spawned by bwrap will not get an argument pipe.
|
// If wt is nil, the child process spawned by bwrap will not get an argument pipe.
|
||||||
// Function argF returns an array of arguments passed directly to the child process.
|
// Function argF returns an array of arguments passed directly to the child process.
|
||||||
func MustNewBwrap(
|
func MustNewBwrap(
|
||||||
conf *bwrap.Config, name string, setpgid bool,
|
conf *bwrap.Config, name string,
|
||||||
wt io.WriterTo, argF func(argsFD, statFD int) []string,
|
wt io.WriterTo, argF func(argsFD, statFD int) []string,
|
||||||
extraFiles []*os.File,
|
extraFiles []*os.File,
|
||||||
syncFd *os.File,
|
syncFd *os.File,
|
||||||
) Helper {
|
) Helper {
|
||||||
b, err := NewBwrap(conf, name, setpgid, wt, argF, extraFiles, syncFd)
|
b, err := NewBwrap(conf, name, wt, argF, extraFiles, syncFd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
} else {
|
} else {
|
||||||
@ -73,7 +65,7 @@ func MustNewBwrap(
|
|||||||
// If wt is nil, the child process spawned by bwrap will not get an argument pipe.
|
// If wt is nil, the child process spawned by bwrap will not get an argument pipe.
|
||||||
// Function argF returns an array of arguments passed directly to the child process.
|
// Function argF returns an array of arguments passed directly to the child process.
|
||||||
func NewBwrap(
|
func NewBwrap(
|
||||||
conf *bwrap.Config, name string, setpgid bool,
|
conf *bwrap.Config, name string,
|
||||||
wt io.WriterTo, argF func(argsFd, statFd int) []string,
|
wt io.WriterTo, argF func(argsFd, statFd int) []string,
|
||||||
extraFiles []*os.File,
|
extraFiles []*os.File,
|
||||||
syncFd *os.File,
|
syncFd *os.File,
|
||||||
@ -81,7 +73,6 @@ func NewBwrap(
|
|||||||
b := new(bubblewrap)
|
b := new(bubblewrap)
|
||||||
|
|
||||||
b.name = name
|
b.name = name
|
||||||
b.setpgid = setpgid
|
|
||||||
b.helperCmd = newHelperCmd(b, BubblewrapName, wt, argF, extraFiles)
|
b.helperCmd = newHelperCmd(b, BubblewrapName, wt, argF, extraFiles)
|
||||||
|
|
||||||
if v, err := NewCheckedArgs(conf.Args(syncFd, b.extraFiles, &b.files)); err != nil {
|
if v, err := NewCheckedArgs(conf.Args(syncFd, b.extraFiles, &b.files)); err != nil {
|
||||||
|
@ -31,7 +31,7 @@ func TestBwrap(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
h := helper.MustNewBwrap(
|
h := helper.MustNewBwrap(
|
||||||
sc, "fortify", false,
|
sc, "fortify",
|
||||||
argsWt, argF,
|
argsWt, argF,
|
||||||
nil, nil,
|
nil, nil,
|
||||||
)
|
)
|
||||||
@ -44,7 +44,7 @@ func TestBwrap(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("valid new helper nil check", func(t *testing.T) {
|
t.Run("valid new helper nil check", func(t *testing.T) {
|
||||||
if got := helper.MustNewBwrap(
|
if got := helper.MustNewBwrap(
|
||||||
sc, "fortify", false,
|
sc, "fortify",
|
||||||
argsWt, argF,
|
argsWt, argF,
|
||||||
nil, nil,
|
nil, nil,
|
||||||
); got == nil {
|
); got == nil {
|
||||||
@ -64,7 +64,7 @@ func TestBwrap(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
helper.MustNewBwrap(
|
helper.MustNewBwrap(
|
||||||
&bwrap.Config{Hostname: "\x00"}, "fortify", false,
|
&bwrap.Config{Hostname: "\x00"}, "fortify",
|
||||||
nil, argF,
|
nil, argF,
|
||||||
nil, nil,
|
nil, nil,
|
||||||
)
|
)
|
||||||
@ -74,7 +74,7 @@ func TestBwrap(t *testing.T) {
|
|||||||
helper.InternalReplaceExecCommand(t)
|
helper.InternalReplaceExecCommand(t)
|
||||||
|
|
||||||
h := helper.MustNewBwrap(
|
h := helper.MustNewBwrap(
|
||||||
sc, "crash-test-dummy", false,
|
sc, "crash-test-dummy",
|
||||||
nil, argFChecked,
|
nil, argFChecked,
|
||||||
nil, nil,
|
nil, nil,
|
||||||
)
|
)
|
||||||
@ -98,11 +98,6 @@ func TestBwrap(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("implementation compliance", func(t *testing.T) {
|
t.Run("implementation compliance", func(t *testing.T) {
|
||||||
testHelper(t, func() helper.Helper {
|
testHelper(t, func() helper.Helper { return helper.MustNewBwrap(sc, "crash-test-dummy", argsWt, argF, nil, nil) })
|
||||||
return helper.MustNewBwrap(
|
|
||||||
sc, "crash-test-dummy", false,
|
|
||||||
argsWt, argF, nil, nil,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,179 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
|
||||||
|
|
||||||
func PrintRunStateErr(rs *fst.RunState, runErr error) {
|
|
||||||
if runErr != nil {
|
|
||||||
if rs.Time == nil {
|
|
||||||
fmsg.PrintBaseError(runErr, "cannot start app:")
|
|
||||||
} else {
|
|
||||||
var e *fmsg.BaseError
|
|
||||||
if !fmsg.AsBaseError(runErr, &e) {
|
|
||||||
log.Println("wait failed:", runErr)
|
|
||||||
} else {
|
|
||||||
// Wait only returns either *app.ProcessError or *app.StateStoreError wrapped in a *app.BaseError
|
|
||||||
var se *StateStoreError
|
|
||||||
if !errors.As(runErr, &se) {
|
|
||||||
// does not need special handling
|
|
||||||
log.Print(e.Message())
|
|
||||||
} else {
|
|
||||||
// inner error are either unwrapped store errors
|
|
||||||
// or joined errors returned by *appSealTx revert
|
|
||||||
// wrapped in *app.BaseError
|
|
||||||
var ej RevertCompoundError
|
|
||||||
if !errors.As(se.InnerErr, &ej) {
|
|
||||||
// does not require special handling
|
|
||||||
log.Print(e.Message())
|
|
||||||
} else {
|
|
||||||
errs := ej.Unwrap()
|
|
||||||
|
|
||||||
// every error here is wrapped in *app.BaseError
|
|
||||||
for _, ei := range errs {
|
|
||||||
var eb *fmsg.BaseError
|
|
||||||
if !errors.As(ei, &eb) {
|
|
||||||
// unreachable
|
|
||||||
log.Println("invalid error type returned by revert:", ei)
|
|
||||||
} else {
|
|
||||||
// print inner *app.BaseError message
|
|
||||||
log.Print(eb.Message())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs.ExitCode == 0 {
|
|
||||||
rs.ExitCode = 126
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs.RevertErr != nil {
|
|
||||||
var stateStoreError *StateStoreError
|
|
||||||
if !errors.As(rs.RevertErr, &stateStoreError) || stateStoreError == nil {
|
|
||||||
fmsg.PrintBaseError(rs.RevertErr, "generic fault during cleanup:")
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.Err != nil {
|
|
||||||
if len(stateStoreError.Err) == 2 {
|
|
||||||
if stateStoreError.Err[0] != nil {
|
|
||||||
if joinedErrs, ok := stateStoreError.Err[0].(interface{ Unwrap() []error }); !ok {
|
|
||||||
fmsg.PrintBaseError(stateStoreError.Err[0], "generic fault during revert:")
|
|
||||||
} else {
|
|
||||||
for _, err := range joinedErrs.Unwrap() {
|
|
||||||
if err != nil {
|
|
||||||
fmsg.PrintBaseError(err, "fault during revert:")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if stateStoreError.Err[1] != nil {
|
|
||||||
log.Printf("cannot close store: %v", stateStoreError.Err[1])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("fault during cleanup: %v",
|
|
||||||
errors.Join(stateStoreError.Err...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.OpErr != nil {
|
|
||||||
log.Printf("blind revert due to store fault: %v",
|
|
||||||
stateStoreError.OpErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.DoErr != nil {
|
|
||||||
fmsg.PrintBaseError(stateStoreError.DoErr, "state store operation unsuccessful:")
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateStoreError.Inner && stateStoreError.InnerErr != nil {
|
|
||||||
fmsg.PrintBaseError(stateStoreError.InnerErr, "cannot destroy state entry:")
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
if rs.ExitCode == 0 {
|
|
||||||
rs.ExitCode = 128
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if rs.WaitErr != nil {
|
|
||||||
log.Println("inner wait failed:", rs.WaitErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateStoreError is returned for a failed state save
|
|
||||||
type StateStoreError struct {
|
|
||||||
// whether inner function was called
|
|
||||||
Inner bool
|
|
||||||
// returned by the Save/Destroy method of [state.Cursor]
|
|
||||||
InnerErr error
|
|
||||||
// returned by the Do method of [state.Store]
|
|
||||||
DoErr error
|
|
||||||
// stores an arbitrary store operation error
|
|
||||||
OpErr error
|
|
||||||
// stores arbitrary errors
|
|
||||||
Err []error
|
|
||||||
}
|
|
||||||
|
|
||||||
// save saves arbitrary errors in [StateStoreError] once.
|
|
||||||
func (e *StateStoreError) save(errs []error) {
|
|
||||||
if len(errs) == 0 || e.Err != nil {
|
|
||||||
panic("invalid call to save")
|
|
||||||
}
|
|
||||||
e.Err = errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StateStoreError) equiv(a ...any) error {
|
|
||||||
if e.Inner && e.InnerErr == nil && e.DoErr == nil && e.OpErr == nil && errors.Join(e.Err...) == nil {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return fmsg.WrapErrorSuffix(e, a...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StateStoreError) Error() string {
|
|
||||||
if e.Inner && e.InnerErr != nil {
|
|
||||||
return e.InnerErr.Error()
|
|
||||||
}
|
|
||||||
if e.DoErr != nil {
|
|
||||||
return e.DoErr.Error()
|
|
||||||
}
|
|
||||||
if e.OpErr != nil {
|
|
||||||
return e.OpErr.Error()
|
|
||||||
}
|
|
||||||
if err := errors.Join(e.Err...); err != nil {
|
|
||||||
return err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// equiv nullifies e for values where this is reached
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *StateStoreError) Unwrap() (errs []error) {
|
|
||||||
errs = make([]error, 0, 3)
|
|
||||||
if e.InnerErr != nil {
|
|
||||||
errs = append(errs, e.InnerErr)
|
|
||||||
}
|
|
||||||
if e.DoErr != nil {
|
|
||||||
errs = append(errs, e.DoErr)
|
|
||||||
}
|
|
||||||
if e.OpErr != nil {
|
|
||||||
errs = append(errs, e.OpErr)
|
|
||||||
}
|
|
||||||
if err := errors.Join(e.Err...); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// A RevertCompoundError encapsulates errors returned by
|
|
||||||
// the Revert method of [system.I].
|
|
||||||
type RevertCompoundError interface {
|
|
||||||
Error() string
|
|
||||||
Unwrap() []error
|
|
||||||
}
|
|
@ -12,7 +12,6 @@ import (
|
|||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/fst"
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
"git.gensokyo.uk/security/fortify/helper"
|
"git.gensokyo.uk/security/fortify/helper"
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/app/shim"
|
"git.gensokyo.uk/security/fortify/internal/app/shim"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
@ -33,10 +32,6 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
|
|||||||
panic("invalid state")
|
panic("invalid state")
|
||||||
}
|
}
|
||||||
|
|
||||||
// read comp values early to allow for early failure
|
|
||||||
fmsg.Verbosef("version %s", internal.Version())
|
|
||||||
fmsg.Verbosef("setuid helper at %s", internal.MustFsuPath())
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
resolve exec paths
|
resolve exec paths
|
||||||
*/
|
*/
|
||||||
@ -84,8 +79,7 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
|
|||||||
ec.Set(system.Process)
|
ec.Set(system.Process)
|
||||||
if states, err := c.Load(); err != nil {
|
if states, err := c.Load(); err != nil {
|
||||||
// revert per-process state here to limit damage
|
// revert per-process state here to limit damage
|
||||||
storeErr.OpErr = err
|
return errors.Join(err, seal.sys.Revert(ec))
|
||||||
return seal.sys.Revert(ec)
|
|
||||||
} else {
|
} else {
|
||||||
if l := len(states); l == 0 {
|
if l := len(states); l == 0 {
|
||||||
fmsg.Verbose("no other launchers active, will clean up globals")
|
fmsg.Verbose("no other launchers active, will clean up globals")
|
||||||
@ -121,10 +115,14 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return seal.sys.Revert(ec)
|
err := seal.sys.Revert(ec)
|
||||||
|
if err != nil {
|
||||||
|
err = err.(RevertCompoundError)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}()
|
}()
|
||||||
})
|
})
|
||||||
storeErr.save([]error{revertErr, store.Close()})
|
storeErr.Err = errors.Join(revertErr, store.Close())
|
||||||
rs.RevertErr = storeErr.equiv("error returned during cleanup:")
|
rs.RevertErr = storeErr.equiv("error returned during cleanup:")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -172,9 +170,7 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
|
|||||||
Time: *rs.Time,
|
Time: *rs.Time,
|
||||||
}
|
}
|
||||||
var earlyStoreErr = new(StateStoreError) // returned after blocking on waitErr
|
var earlyStoreErr = new(StateStoreError) // returned after blocking on waitErr
|
||||||
earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) {
|
earlyStoreErr.Inner, earlyStoreErr.DoErr = store.Do(seal.user.aid.unwrap(), func(c state.Cursor) { earlyStoreErr.InnerErr = c.Save(&sd, seal.ct) })
|
||||||
earlyStoreErr.InnerErr = c.Save(&sd, seal.ct)
|
|
||||||
})
|
|
||||||
// destroy defunct state entry
|
// destroy defunct state entry
|
||||||
deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(seal.id.unwrap()) }
|
deferredStoreFunc = func(c state.Cursor) error { return c.Destroy(seal.id.unwrap()) }
|
||||||
|
|
||||||
@ -216,3 +212,69 @@ func (seal *outcome) Run(ctx context.Context, rs *fst.RunState) error {
|
|||||||
|
|
||||||
return earlyStoreErr.equiv("cannot save process state:")
|
return earlyStoreErr.equiv("cannot save process state:")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StateStoreError is returned for a failed state save
|
||||||
|
type StateStoreError struct {
|
||||||
|
// whether inner function was called
|
||||||
|
Inner bool
|
||||||
|
// returned by the Do method of [state.Store]
|
||||||
|
DoErr error
|
||||||
|
// returned by the Save/Destroy method of [state.Cursor]
|
||||||
|
InnerErr error
|
||||||
|
// stores an arbitrary error
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// save saves exactly one arbitrary error in [StateStoreError].
|
||||||
|
func (e *StateStoreError) save(err error) {
|
||||||
|
if err == nil || e.Err != nil {
|
||||||
|
panic("invalid call to save")
|
||||||
|
}
|
||||||
|
e.Err = err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StateStoreError) equiv(a ...any) error {
|
||||||
|
if e.Inner && e.DoErr == nil && e.InnerErr == nil && e.Err == nil {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmsg.WrapErrorSuffix(e, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StateStoreError) Error() string {
|
||||||
|
if e.Inner && e.InnerErr != nil {
|
||||||
|
return e.InnerErr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.DoErr != nil {
|
||||||
|
return e.DoErr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Err != nil {
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// equiv nullifies e for values where this is reached
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *StateStoreError) Unwrap() (errs []error) {
|
||||||
|
errs = make([]error, 0, 3)
|
||||||
|
if e.DoErr != nil {
|
||||||
|
errs = append(errs, e.DoErr)
|
||||||
|
}
|
||||||
|
if e.InnerErr != nil {
|
||||||
|
errs = append(errs, e.InnerErr)
|
||||||
|
}
|
||||||
|
if e.Err != nil {
|
||||||
|
errs = append(errs, e.Err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A RevertCompoundError encapsulates errors returned by
|
||||||
|
// the Revert method of [system.I].
|
||||||
|
type RevertCompoundError interface {
|
||||||
|
Error() string
|
||||||
|
Unwrap() []error
|
||||||
|
}
|
||||||
|
@ -125,7 +125,7 @@ func Main() {
|
|||||||
seccomp.CPrintln = log.Println
|
seccomp.CPrintln = log.Println
|
||||||
}
|
}
|
||||||
if b, err := helper.NewBwrap(
|
if b, err := helper.NewBwrap(
|
||||||
conf, path.Join(fst.Tmp, "sbin/init"), false,
|
conf, path.Join(fst.Tmp, "sbin/init"),
|
||||||
nil, func(int, int) []string { return make([]string, 0) },
|
nil, func(int, int) []string { return make([]string, 0) },
|
||||||
extraFiles,
|
extraFiles,
|
||||||
syncFd,
|
syncFd,
|
||||||
|
@ -52,8 +52,14 @@ func (s *Shim) Start(
|
|||||||
syncFd *os.File,
|
syncFd *os.File,
|
||||||
) (*time.Time, error) {
|
) (*time.Time, error) {
|
||||||
// prepare user switcher invocation
|
// prepare user switcher invocation
|
||||||
fsuPath := internal.MustFsuPath()
|
var fsu string
|
||||||
s.cmd = exec.Command(fsuPath)
|
if p, ok := internal.Path(internal.Fsu); !ok {
|
||||||
|
return nil, fmsg.WrapError(errors.New("bad fsu path"),
|
||||||
|
"invalid fsu path, this copy of fortify is not compiled correctly")
|
||||||
|
} else {
|
||||||
|
fsu = p
|
||||||
|
}
|
||||||
|
s.cmd = exec.Command(fsu)
|
||||||
|
|
||||||
// pass shim setup pipe
|
// pass shim setup pipe
|
||||||
if fd, e, err := proc.Setup(&s.cmd.ExtraFiles); err != nil {
|
if fd, e, err := proc.Setup(&s.cmd.ExtraFiles); err != nil {
|
||||||
|
@ -3,15 +3,10 @@ package internal
|
|||||||
const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
|
const compPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = compPoison
|
Version = compPoison
|
||||||
)
|
)
|
||||||
|
|
||||||
// check validates string value set at compile time.
|
// Check validates string value set at compile time.
|
||||||
func check(s string) (string, bool) { return s, s != compPoison && s != "" }
|
func Check(s string) (string, bool) {
|
||||||
|
return s, s != compPoison && s != ""
|
||||||
func Version() string {
|
|
||||||
if v, ok := check(version); ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return "impure"
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,11 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import "path"
|
||||||
"log"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
fsu = compPoison
|
Fsu = compPoison
|
||||||
)
|
)
|
||||||
|
|
||||||
func MustFsuPath() string {
|
func Path(p string) (string, bool) {
|
||||||
if name, ok := checkPath(fsu); ok {
|
return p, p != compPoison && p != "" && path.IsAbs(p)
|
||||||
return name
|
|
||||||
}
|
|
||||||
fmsg.BeforeExit()
|
|
||||||
log.Fatal("invalid fsu path, this program is compiled incorrectly")
|
|
||||||
return compPoison
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPath(p string) (string, bool) { return p, p != compPoison && p != "" && path.IsAbs(p) }
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -78,27 +79,32 @@ func (s *Std) Uid(aid int) (int, error) {
|
|||||||
defer func() { s.uidCopy[aid] = u }()
|
defer func() { s.uidCopy[aid] = u }()
|
||||||
|
|
||||||
u.uid = -1
|
u.uid = -1
|
||||||
fsuPath := internal.MustFsuPath()
|
if fsu, ok := internal.Check(internal.Fsu); !ok {
|
||||||
|
fmsg.BeforeExit()
|
||||||
|
log.Fatal("invalid fsu path, this copy of fortify is not compiled correctly")
|
||||||
|
// unreachable
|
||||||
|
return 0, syscall.EBADE
|
||||||
|
} else {
|
||||||
|
cmd := exec.Command(fsu)
|
||||||
|
cmd.Path = fsu
|
||||||
|
cmd.Stderr = os.Stderr // pass through fatal messages
|
||||||
|
cmd.Env = []string{"FORTIFY_APP_ID=" + strconv.Itoa(aid)}
|
||||||
|
cmd.Dir = "/"
|
||||||
|
var (
|
||||||
|
p []byte
|
||||||
|
exitError *exec.ExitError
|
||||||
|
)
|
||||||
|
|
||||||
cmd := exec.Command(fsuPath)
|
if p, u.err = cmd.Output(); u.err == nil {
|
||||||
cmd.Path = fsuPath
|
u.uid, u.err = strconv.Atoi(string(p))
|
||||||
cmd.Stderr = os.Stderr // pass through fatal messages
|
if u.err != nil {
|
||||||
cmd.Env = []string{"FORTIFY_APP_ID=" + strconv.Itoa(aid)}
|
u.err = fmsg.WrapErrorSuffix(u.err, "cannot parse uid from fsu:")
|
||||||
cmd.Dir = "/"
|
}
|
||||||
var (
|
} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
||||||
p []byte
|
u.err = fmsg.WrapError(syscall.EACCES, "") // fsu prints to stderr in this case
|
||||||
exitError *exec.ExitError
|
} else if os.IsNotExist(u.err) {
|
||||||
)
|
u.err = fmsg.WrapError(os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", fsu))
|
||||||
|
|
||||||
if p, u.err = cmd.Output(); u.err == nil {
|
|
||||||
u.uid, u.err = strconv.Atoi(string(p))
|
|
||||||
if u.err != nil {
|
|
||||||
u.err = fmsg.WrapErrorSuffix(u.err, "cannot parse uid from fsu:")
|
|
||||||
}
|
}
|
||||||
} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
return u.uid, u.err
|
||||||
u.err = fmsg.WrapError(syscall.EACCES, "") // fsu prints to stderr in this case
|
|
||||||
} else if os.IsNotExist(u.err) {
|
|
||||||
u.err = fmsg.WrapError(os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", fsuPath))
|
|
||||||
}
|
}
|
||||||
return u.uid, u.err
|
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ func Exec(ctx context.Context, p string) ([]*Entry, error) {
|
|||||||
Syscall: &bwrap.SyscallPolicy{DenyDevel: true, Multiarch: true},
|
Syscall: &bwrap.SyscallPolicy{DenyDevel: true, Multiarch: true},
|
||||||
NewSession: true,
|
NewSession: true,
|
||||||
DieWithParent: true,
|
DieWithParent: true,
|
||||||
}).Bind("/", "/").DevTmpfs("/dev"), toolPath, false,
|
}).Bind("/", "/").DevTmpfs("/dev"), toolPath,
|
||||||
nil, func(_, _ int) []string { return []string{p} },
|
nil, func(_, _ int) []string { return []string{p} },
|
||||||
nil, nil,
|
nil, nil,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
52
main.go
52
main.go
@ -68,20 +68,10 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
flagVerbose bool
|
flagVerbose bool
|
||||||
flagJSON bool
|
flagJSON bool
|
||||||
)
|
)
|
||||||
c := command.New(out, log.Printf, "fortify", func([]string) error {
|
c := command.New(out, log.Printf, "fortify", func([]string) error { fmsg.Store(flagVerbose); return nil }).
|
||||||
fmsg.Store(flagVerbose)
|
|
||||||
if flagVerbose {
|
|
||||||
seccomp.CPrintln = log.Println
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}).
|
|
||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console").
|
||||||
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output as JSON when applicable")
|
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output as JSON when applicable")
|
||||||
|
|
||||||
// internal commands
|
|
||||||
c.Command("shim", command.UsageInternal, func([]string) error { shim.Main(); return errSuccess })
|
|
||||||
c.Command("init", command.UsageInternal, func([]string) error { init0.Main(); return errSuccess })
|
|
||||||
|
|
||||||
c.Command("app", "Launch app defined by the specified config file", func(args []string) error {
|
c.Command("app", "Launch app defined by the specified config file", func(args []string) error {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
log.Fatal("app requires at least 1 argument")
|
log.Fatal("app requires at least 1 argument")
|
||||||
@ -259,7 +249,11 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
}).Flag(&psFlagShort, "short", command.BoolFlag(false), "Print instance id")
|
}).Flag(&psFlagShort, "short", command.BoolFlag(false), "Print instance id")
|
||||||
|
|
||||||
c.Command("version", "Show fortify version", func(args []string) error {
|
c.Command("version", "Show fortify version", func(args []string) error {
|
||||||
fmt.Println(internal.Version())
|
if v, ok := internal.Check(internal.Version); ok {
|
||||||
|
fmt.Println(v)
|
||||||
|
} else {
|
||||||
|
fmt.Println("impure")
|
||||||
|
}
|
||||||
return errSuccess
|
return errSuccess
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -278,21 +272,45 @@ func buildCommand(out io.Writer) command.Command {
|
|||||||
return errSuccess
|
return errSuccess
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// internal commands
|
||||||
|
c.Command("shim", command.UsageInternal, func([]string) error { shim.Main(); return errSuccess })
|
||||||
|
c.Command("init", command.UsageInternal, func([]string) error { init0.Main(); return errSuccess })
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func runApp(a fst.App, config *fst.Config) {
|
func runApp(a fst.App, config *fst.Config) {
|
||||||
|
rs := new(fst.RunState)
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
ctx, stop := signal.NotifyContext(context.Background(),
|
||||||
syscall.SIGINT, syscall.SIGTERM)
|
syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop() // unreachable
|
defer stop() // unreachable
|
||||||
|
|
||||||
rs := new(fst.RunState)
|
if fmsg.Load() {
|
||||||
|
seccomp.CPrintln = log.Println
|
||||||
|
}
|
||||||
|
|
||||||
if sa, err := a.Seal(config); err != nil {
|
if sa, err := a.Seal(config); err != nil {
|
||||||
fmsg.PrintBaseError(err, "cannot seal app:")
|
fmsg.PrintBaseError(err, "cannot seal app:")
|
||||||
rs.ExitCode = 1
|
internal.Exit(1)
|
||||||
} else {
|
} else if err = sa.Run(ctx, rs); err != nil {
|
||||||
// this updates ExitCode
|
if rs.Time == nil {
|
||||||
app.PrintRunStateErr(rs, sa.Run(ctx, rs))
|
fmsg.PrintBaseError(err, "cannot start app:")
|
||||||
|
} else {
|
||||||
|
logWaitError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.ExitCode == 0 {
|
||||||
|
rs.ExitCode = 126
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rs.RevertErr != nil {
|
||||||
|
fmsg.PrintBaseError(rs.RevertErr, "generic error returned during cleanup:")
|
||||||
|
if rs.ExitCode == 0 {
|
||||||
|
rs.ExitCode = 128
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if rs.WaitErr != nil {
|
||||||
|
log.Println("inner wait failed:", rs.WaitErr)
|
||||||
}
|
}
|
||||||
internal.Exit(rs.ExitCode)
|
internal.Exit(rs.ExitCode)
|
||||||
}
|
}
|
||||||
|
17
nixos.nix
17
nixos.nix
@ -77,12 +77,21 @@ in
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
session_bus = if app.dbus.session != null then (app.dbus.session (extendDBusDefault app.id)) else (extendDBusDefault app.id default);
|
session_bus =
|
||||||
|
if app.dbus.session != null then
|
||||||
|
(app.dbus.session (extendDBusDefault app.id))
|
||||||
|
else
|
||||||
|
(extendDBusDefault app.id default);
|
||||||
system_bus = app.dbus.system;
|
system_bus = app.dbus.system;
|
||||||
};
|
};
|
||||||
command = if app.command == null then app.name else app.command;
|
command = if app.command == null then app.name else app.command;
|
||||||
script = if app.script == null then ("exec " + command + " $@") else app.script;
|
script = if app.script == null then ("exec " + command + " $@") else app.script;
|
||||||
enablements = with app.capability; (if wayland then 1 else 0) + (if x11 then 2 else 0) + (if dbus then 4 else 0) + (if pulse then 8 else 0);
|
enablements =
|
||||||
|
with app.capability;
|
||||||
|
(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 = {
|
conf = {
|
||||||
inherit (app) id;
|
inherit (app) id;
|
||||||
command = [
|
command = [
|
||||||
@ -156,7 +165,9 @@ in
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
pkgs.writeShellScriptBin app.name ''
|
pkgs.writeShellScriptBin app.name ''
|
||||||
exec fortify${if app.verbose then " -v" else ""} app ${pkgs.writeText "fortify-${app.name}.json" (builtins.toJSON conf)} $@
|
exec fortify${
|
||||||
|
if app.verbose then " -v" else ""
|
||||||
|
} app ${pkgs.writeText "fortify-${app.name}.json" (builtins.toJSON conf)} $@
|
||||||
''
|
''
|
||||||
) cfg.apps;
|
) cfg.apps;
|
||||||
in
|
in
|
||||||
|
@ -3,14 +3,7 @@
|
|||||||
let
|
let
|
||||||
inherit (lib) types mkOption mkEnableOption;
|
inherit (lib) types mkOption mkEnableOption;
|
||||||
fortify = pkgs.pkgsStatic.callPackage ./package.nix {
|
fortify = pkgs.pkgsStatic.callPackage ./package.nix {
|
||||||
inherit (pkgs)
|
inherit (pkgs) bubblewrap xdg-dbus-proxy glibc;
|
||||||
bubblewrap
|
|
||||||
xdg-dbus-proxy
|
|
||||||
glibc
|
|
||||||
zstd
|
|
||||||
gnutar
|
|
||||||
coreutils
|
|
||||||
;
|
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|
||||||
|
58
package.nix
58
package.nix
@ -14,11 +14,6 @@
|
|||||||
wayland-scanner,
|
wayland-scanner,
|
||||||
xorg,
|
xorg,
|
||||||
|
|
||||||
# for fpkg
|
|
||||||
zstd,
|
|
||||||
gnutar,
|
|
||||||
coreutils,
|
|
||||||
|
|
||||||
glibc, # for ldd
|
glibc, # for ldd
|
||||||
withStatic ? stdenv.hostPlatform.isStatic,
|
withStatic ? stdenv.hostPlatform.isStatic,
|
||||||
}:
|
}:
|
||||||
@ -30,7 +25,10 @@ buildGoModule rec {
|
|||||||
src = builtins.path {
|
src = builtins.path {
|
||||||
name = "${pname}-src";
|
name = "${pname}-src";
|
||||||
path = lib.cleanSource ./.;
|
path = lib.cleanSource ./.;
|
||||||
filter = path: type: !(type == "regular" && (lib.hasSuffix ".nix" path || lib.hasSuffix ".py" path)) && !(type == "directory" && lib.hasSuffix "/cmd/fsu" path);
|
filter =
|
||||||
|
path: type:
|
||||||
|
!(type == "regular" && lib.hasSuffix ".nix" path)
|
||||||
|
&& !(type == "directory" && lib.hasSuffix "/cmd/fsu" path);
|
||||||
};
|
};
|
||||||
vendorHash = null;
|
vendorHash = null;
|
||||||
|
|
||||||
@ -41,15 +39,17 @@ buildGoModule rec {
|
|||||||
ldflags ++ [ "-X git.gensokyo.uk/security/fortify/internal.${name}=${value}" ]
|
ldflags ++ [ "-X git.gensokyo.uk/security/fortify/internal.${name}=${value}" ]
|
||||||
)
|
)
|
||||||
(
|
(
|
||||||
[ "-s -w" ]
|
[
|
||||||
|
"-s -w"
|
||||||
|
]
|
||||||
++ lib.optionals withStatic [
|
++ lib.optionals withStatic [
|
||||||
"-linkmode external"
|
"-linkmode external"
|
||||||
"-extldflags \"-static\""
|
"-extldflags \"-static\""
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
version = "v${version}";
|
Version = "v${version}";
|
||||||
fsu = "/run/wrappers/bin/fsu";
|
Fsu = "/run/wrappers/bin/fsu";
|
||||||
};
|
};
|
||||||
|
|
||||||
# nix build environment does not allow acls
|
# nix build environment does not allow acls
|
||||||
@ -79,33 +79,19 @@ buildGoModule rec {
|
|||||||
HOME="$(mktemp -d)" PATH="${pkg-config}/bin:$PATH" go generate ./...
|
HOME="$(mktemp -d)" PATH="${pkg-config}/bin:$PATH" go generate ./...
|
||||||
'';
|
'';
|
||||||
|
|
||||||
postInstall =
|
postInstall = ''
|
||||||
let
|
install -D --target-directory=$out/share/zsh/site-functions comp/*
|
||||||
appPackages = [
|
|
||||||
glibc
|
|
||||||
bubblewrap
|
|
||||||
xdg-dbus-proxy
|
|
||||||
];
|
|
||||||
in
|
|
||||||
''
|
|
||||||
install -D --target-directory=$out/share/zsh/site-functions comp/*
|
|
||||||
|
|
||||||
mkdir "$out/libexec"
|
mkdir "$out/libexec"
|
||||||
mv "$out"/bin/* "$out/libexec/"
|
mv "$out"/bin/* "$out/libexec/"
|
||||||
|
|
||||||
makeBinaryWrapper "$out/libexec/fortify" "$out/bin/fortify" \
|
makeBinaryWrapper "$out/libexec/fortify" "$out/bin/fortify" \
|
||||||
--inherit-argv0 --prefix PATH : ${lib.makeBinPath appPackages}
|
--inherit-argv0 --prefix PATH : ${
|
||||||
|
lib.makeBinPath [
|
||||||
makeBinaryWrapper "$out/libexec/fpkg" "$out/bin/fpkg" \
|
glibc
|
||||||
--inherit-argv0 --prefix PATH : ${
|
bubblewrap
|
||||||
lib.makeBinPath (
|
xdg-dbus-proxy
|
||||||
appPackages
|
]
|
||||||
++ [
|
}
|
||||||
zstd
|
'';
|
||||||
gnutar
|
|
||||||
coreutils
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/acl"
|
"git.gensokyo.uk/security/fortify/acl"
|
||||||
@ -43,13 +41,7 @@ func (a *ACL) apply(sys *I) error {
|
|||||||
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
func (a *ACL) revert(sys *I, ec *Criteria) error {
|
||||||
if ec.hasType(a) {
|
if ec.hasType(a) {
|
||||||
sys.println("stripping ACL", a)
|
sys.println("stripping ACL", a)
|
||||||
err := acl.Update(a.path, sys.uid)
|
return sys.wrapErrSuffix(acl.Update(a.path, sys.uid),
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
// the ACL is effectively stripped if the file no longer exists
|
|
||||||
sys.printf("target of ACL %s no longer exists", a)
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return sys.wrapErrSuffix(err,
|
|
||||||
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
fmt.Sprintf("cannot strip ACL entry from %q:", a.path))
|
||||||
} else {
|
} else {
|
||||||
sys.println("skipping ACL", a)
|
sys.println("skipping ACL", a)
|
||||||
|
@ -2,7 +2,6 @@ package system
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
@ -97,12 +96,7 @@ func (d *DBus) revert(sys *I, _ *Criteria) error {
|
|||||||
sys.println("terminating message bus proxy")
|
sys.println("terminating message bus proxy")
|
||||||
d.proxy.Close()
|
d.proxy.Close()
|
||||||
defer sys.println("message bus proxy exit")
|
defer sys.println("message bus proxy exit")
|
||||||
err := d.proxy.Wait()
|
return sys.wrapErrSuffix(d.proxy.Wait(), "message bus proxy error:")
|
||||||
if errors.Is(err, context.Canceled) {
|
|
||||||
sys.println("message bus proxy canceled upstream")
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return sys.wrapErrSuffix(err, "message bus proxy error:")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DBus) Is(o Op) bool {
|
func (d *DBus) Is(o Op) bool {
|
||||||
|
@ -102,21 +102,6 @@
|
|||||||
home-manager = _: _: { home.stateVersion = "23.05"; };
|
home-manager = _: _: { home.stateVersion = "23.05"; };
|
||||||
|
|
||||||
apps = [
|
apps = [
|
||||||
{
|
|
||||||
name = "check-sandbox";
|
|
||||||
verbose = true;
|
|
||||||
share = pkgs.foot;
|
|
||||||
packages = [ ];
|
|
||||||
command = "${pkgs.callPackage ./sandbox {
|
|
||||||
inherit (config.environment.fortify.package) version;
|
|
||||||
}}";
|
|
||||||
extraPaths = [
|
|
||||||
{
|
|
||||||
src = "/proc/mounts";
|
|
||||||
dst = "/.fortify/host-mounts";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
name = "ne-foot";
|
name = "ne-foot";
|
||||||
verbose = true;
|
verbose = true;
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
package sandbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
assert = log.New(os.Stderr, "sandbox: ", 0)
|
|
||||||
fatalfFunc = assert.Fatalf
|
|
||||||
)
|
|
||||||
|
|
||||||
func fatalf(format string, v ...any) { fatalfFunc(format, v...) }
|
|
||||||
|
|
||||||
func MustAssertMounts(name, hostMountsFile, wantFile string) {
|
|
||||||
hostMounts := make([]*Mntent, 0, 128)
|
|
||||||
if err := IterMounts(hostMountsFile, func(e *Mntent) {
|
|
||||||
hostMounts = append(hostMounts, e)
|
|
||||||
}); err != nil {
|
|
||||||
fatalf("cannot parse host mounts: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var want []Mntent
|
|
||||||
if f, err := os.Open(wantFile); err != nil {
|
|
||||||
fatalf("cannot open %q: %v", wantFile, err)
|
|
||||||
} else if err = json.NewDecoder(f).Decode(&want); err != nil {
|
|
||||||
fatalf("cannot decode %q: %v", wantFile, err)
|
|
||||||
} else if err = f.Close(); err != nil {
|
|
||||||
fatalf("cannot close %q: %v", wantFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range want {
|
|
||||||
if want[i].Opts == "host_passthrough" {
|
|
||||||
for _, ent := range hostMounts {
|
|
||||||
if want[i].FSName == ent.FSName {
|
|
||||||
want[i].Opts = ent.Opts
|
|
||||||
goto out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fatalf("host passthrough missing %q", want[i].FSName)
|
|
||||||
out:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
if err := IterMounts(name, func(e *Mntent) {
|
|
||||||
if i == len(want) {
|
|
||||||
fatalf("got more than %d entries", i)
|
|
||||||
}
|
|
||||||
if *e != want[i] {
|
|
||||||
fatalf("entry %d\n got: %s\nwant: %s", i,
|
|
||||||
e, &want[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Printf("%s", e)
|
|
||||||
i++
|
|
||||||
}); err != nil {
|
|
||||||
fatalf("cannot iterate mounts: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package sandbox
|
|
||||||
|
|
||||||
func ReplaceFatal(f func(format string, v ...any)) { fatalfFunc = f }
|
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
writeShellScript,
|
|
||||||
callPackage,
|
|
||||||
|
|
||||||
version,
|
|
||||||
}:
|
|
||||||
writeShellScript "check-sandbox" ''
|
|
||||||
set -e
|
|
||||||
${callPackage ./mount.nix { inherit version; }}/bin/test
|
|
||||||
|
|
||||||
touch /tmp/sandbox-ok
|
|
||||||
''
|
|
@ -1,134 +0,0 @@
|
|||||||
package sandbox
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <mntent.h>
|
|
||||||
|
|
||||||
const char *F_PROC_MOUNTS = "";
|
|
||||||
const char *F_SET_TYPE = "r";
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Mntent struct {
|
|
||||||
/* name of mounted filesystem */
|
|
||||||
FSName string `json:"fsname"`
|
|
||||||
/* filesystem path prefix */
|
|
||||||
Dir string `json:"dir"`
|
|
||||||
/* mount type (see mntent.h) */
|
|
||||||
Type string `json:"type"`
|
|
||||||
/* mount options (see mntent.h) */
|
|
||||||
Opts string `json:"opts"`
|
|
||||||
/* dump frequency in days */
|
|
||||||
Freq int `json:"freq"`
|
|
||||||
/* pass number on parallel fsck */
|
|
||||||
Passno int `json:"passno"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *Mntent) String() string {
|
|
||||||
return fmt.Sprintf("%s %s %s %s %d %d",
|
|
||||||
e.FSName, e.Dir, e.Type, e.Opts, e.Freq, e.Passno)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IterMounts(name string, f func(e *Mntent)) error {
|
|
||||||
m := new(mounts)
|
|
||||||
m.p = name
|
|
||||||
if err := m.open(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for m.scan() {
|
|
||||||
e := new(Mntent)
|
|
||||||
m.copy(e)
|
|
||||||
f(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.close()
|
|
||||||
return m.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
type mounts struct {
|
|
||||||
p string
|
|
||||||
f *C.FILE
|
|
||||||
mu sync.RWMutex
|
|
||||||
|
|
||||||
ent *C.struct_mntent
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mounts) open() error {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.f != nil {
|
|
||||||
panic("open called twice")
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.p == "" {
|
|
||||||
m.p = "/proc/mounts"
|
|
||||||
}
|
|
||||||
|
|
||||||
name := C.CString(m.p)
|
|
||||||
f, err := C.setmntent(name, C.F_SET_TYPE)
|
|
||||||
C.free(unsafe.Pointer(name))
|
|
||||||
|
|
||||||
if f == nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.f = f
|
|
||||||
runtime.SetFinalizer(m, (*mounts).close)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mounts) close() {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.f == nil {
|
|
||||||
panic("close called before open")
|
|
||||||
}
|
|
||||||
|
|
||||||
C.endmntent(m.f)
|
|
||||||
runtime.SetFinalizer(m, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mounts) scan() bool {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.f == nil {
|
|
||||||
panic("invalid file")
|
|
||||||
}
|
|
||||||
|
|
||||||
m.ent, m.err = C.getmntent(m.f)
|
|
||||||
return m.ent != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mounts) Err() error {
|
|
||||||
m.mu.RLock()
|
|
||||||
defer m.mu.RUnlock()
|
|
||||||
|
|
||||||
return m.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mounts) copy(v *Mntent) {
|
|
||||||
m.mu.RLock()
|
|
||||||
defer m.mu.RUnlock()
|
|
||||||
|
|
||||||
if m.ent == nil {
|
|
||||||
panic("invalid entry")
|
|
||||||
}
|
|
||||||
v.FSName = C.GoString(m.ent.mnt_fsname)
|
|
||||||
v.Dir = C.GoString(m.ent.mnt_dir)
|
|
||||||
v.Type = C.GoString(m.ent.mnt_type)
|
|
||||||
v.Opts = C.GoString(m.ent.mnt_opts)
|
|
||||||
v.Freq = int(m.ent.mnt_freq)
|
|
||||||
v.Passno = int(m.ent.mnt_passno)
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
{
|
|
||||||
writeText,
|
|
||||||
buildGoModule,
|
|
||||||
|
|
||||||
version,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
wantMounts =
|
|
||||||
let
|
|
||||||
ent = fsname: dir: type: opts: freq: passno: {
|
|
||||||
inherit
|
|
||||||
fsname
|
|
||||||
dir
|
|
||||||
type
|
|
||||||
opts
|
|
||||||
freq
|
|
||||||
passno
|
|
||||||
;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
[
|
|
||||||
(ent "tmpfs" "/" "tmpfs" "rw,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "proc" "/proc" "proc" "rw,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/.fortify" "tmpfs" "rw,nosuid,nodev,relatime,size=4k,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "tmpfs" "/dev" "tmpfs" "rw,nosuid,nodev,relatime,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/null" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/zero" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/full" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/random" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/urandom" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/tty" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "devpts" "/dev/pts" "devpts" "rw,nosuid,noexec,relatime,mode=620,ptmxmode=666" 0 0)
|
|
||||||
(ent "mqueue" "/dev/mqueue" "mqueue" "rw,relatime" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/bin" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/usr/bin" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "overlay" "/nix/store" "overlay" "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on" 0 0)
|
|
||||||
(ent "overlay" "/run/current-system" "overlay" "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on" 0 0)
|
|
||||||
(ent "sysfs" "/sys/block" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "sysfs" "/sys/bus" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "sysfs" "/sys/class" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "sysfs" "/sys/dev" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "sysfs" "/sys/devices" "sysfs" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "overlay" "/run/opengl-driver" "overlay" "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on" 0 0)
|
|
||||||
(ent "devtmpfs" "/dev/dri" "devtmpfs" "host_passthrough" 0 0)
|
|
||||||
(ent "proc" "/.fortify/host-mounts" "proc" "ro,nosuid,nodev,noexec,relatime" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/.fortify/etc" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/run/user" "tmpfs" "rw,nosuid,nodev,relatime,size=1024k,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "tmpfs" "/run/user/65534" "tmpfs" "rw,nosuid,nodev,relatime,size=8192k,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/tmp" "ext4" "rw,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/var/lib/fortify/u0/a1" "ext4" "rw,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/etc/passwd" "tmpfs" "ro,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "tmpfs" "/etc/group" "tmpfs" "ro,nosuid,nodev,relatime,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/run/user/65534/wayland-0" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/run/user/65534/pulse/native" "tmpfs" "ro,nosuid,nodev,relatime,size=98784k,nr_inodes=24696,mode=700,uid=1000,gid=100" 0 0)
|
|
||||||
(ent "/dev/disk/by-label/nixos" "/run/user/65534/bus" "ext4" "ro,nosuid,nodev,relatime" 0 0)
|
|
||||||
(ent "tmpfs" "/var/run/nscd" "tmpfs" "rw,nosuid,nodev,relatime,size=8k,mode=755,uid=1000001,gid=1000001" 0 0)
|
|
||||||
(ent "overlay" "/.fortify/sbin/fortify" "overlay" "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on" 0 0)
|
|
||||||
];
|
|
||||||
|
|
||||||
mainFile = writeText "main.go" ''
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "git.gensokyo.uk/security/fortify/test/sandbox"
|
|
||||||
|
|
||||||
func main() { sandbox.MustAssertMounts("", "/.fortify/host-mounts", "${writeText "want-mounts.json" (builtins.toJSON wantMounts)}") }
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
buildGoModule {
|
|
||||||
pname = "check-mounts";
|
|
||||||
inherit version;
|
|
||||||
|
|
||||||
src = ../.;
|
|
||||||
vendorHash = null;
|
|
||||||
|
|
||||||
preBuild = ''
|
|
||||||
go mod init git.gensokyo.uk/security/fortify/test >& /dev/null
|
|
||||||
cp ${mainFile} main.go
|
|
||||||
'';
|
|
||||||
}
|
|
@ -1,136 +0,0 @@
|
|||||||
package sandbox_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/test/sandbox"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMounts(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
|
|
||||||
sample string
|
|
||||||
want []sandbox.Mntent
|
|
||||||
}{
|
|
||||||
{"fpkg", `tmpfs / tmpfs rw,nosuid,nodev,relatime,uid=1000002,gid=1000002 0 0
|
|
||||||
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
|
|
||||||
tmpfs /.fortify tmpfs rw,nosuid,nodev,relatime,size=4k,mode=755,uid=1000002,gid=1000002 0 0
|
|
||||||
tmpfs /dev tmpfs rw,nosuid,nodev,relatime,mode=755,uid=1000002,gid=1000002 0 0
|
|
||||||
devtmpfs /dev/null devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0
|
|
||||||
devtmpfs /dev/zero devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0
|
|
||||||
devtmpfs /dev/full devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0
|
|
||||||
devtmpfs /dev/random devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0
|
|
||||||
devtmpfs /dev/urandom devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0
|
|
||||||
devtmpfs /dev/tty devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0
|
|
||||||
devpts /dev/pts devpts rw,nosuid,noexec,relatime,mode=620,ptmxmode=666 0 0
|
|
||||||
mqueue /dev/mqueue mqueue rw,relatime 0 0
|
|
||||||
/dev/disk/by-label/nixos /nix/store ext4 ro,nosuid,nodev,relatime 0 0
|
|
||||||
/dev/disk/by-label/nixos /.fortify/app ext4 ro,nosuid,nodev,relatime 0 0
|
|
||||||
/dev/disk/by-label/nixos /etc/resolv.conf ext4 ro,nosuid,nodev,relatime 0 0
|
|
||||||
sysfs /sys/block sysfs ro,nosuid,nodev,noexec,relatime 0 0
|
|
||||||
sysfs /sys/bus sysfs ro,nosuid,nodev,noexec,relatime 0 0
|
|
||||||
sysfs /sys/class sysfs ro,nosuid,nodev,noexec,relatime 0 0
|
|
||||||
sysfs /sys/dev sysfs ro,nosuid,nodev,noexec,relatime 0 0
|
|
||||||
sysfs /sys/devices sysfs ro,nosuid,nodev,noexec,relatime 0 0
|
|
||||||
/dev/disk/by-label/nixos /.fortify/nixGL ext4 ro,nosuid,nodev,relatime 0 0
|
|
||||||
devtmpfs /dev/dri devtmpfs rw,nosuid,size=49396k,nr_inodes=121247,mode=755 0 0
|
|
||||||
/dev/disk/by-label/nixos /.fortify/etc ext4 ro,nosuid,nodev,relatime 0 0
|
|
||||||
tmpfs /run/user tmpfs rw,nosuid,nodev,relatime,size=1024k,mode=755,uid=1000002,gid=1000002 0 0
|
|
||||||
tmpfs /run/user/65534 tmpfs rw,nosuid,nodev,relatime,size=8192k,mode=755,uid=1000002,gid=1000002 0 0
|
|
||||||
/dev/disk/by-label/nixos /tmp ext4 rw,nosuid,nodev,relatime 0 0
|
|
||||||
/dev/disk/by-label/nixos /data/data/org.codeberg.dnkl.foot ext4 rw,nosuid,nodev,relatime 0 0
|
|
||||||
tmpfs /etc/passwd tmpfs ro,nosuid,nodev,relatime,uid=1000002,gid=1000002 0 0
|
|
||||||
tmpfs /etc/group tmpfs ro,nosuid,nodev,relatime,uid=1000002,gid=1000002 0 0
|
|
||||||
/dev/disk/by-label/nixos /run/user/65534/wayland-0 ext4 ro,nosuid,nodev,relatime 0 0
|
|
||||||
tmpfs /run/user/65534/pulse/native tmpfs ro,nosuid,nodev,relatime,size=98784k,nr_inodes=24696,mode=700,uid=1000,gid=100 0 0
|
|
||||||
/dev/disk/by-label/nixos /run/user/65534/bus ext4 ro,nosuid,nodev,relatime 0 0
|
|
||||||
overlay /.fortify/sbin/fortify overlay ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on 0 0
|
|
||||||
`, []sandbox.Mntent{
|
|
||||||
{"tmpfs", "/", "tmpfs", "rw,nosuid,nodev,relatime,uid=1000002,gid=1000002", 0, 0},
|
|
||||||
{"proc", "/proc", "proc", "rw,nosuid,nodev,noexec,relatime", 0, 0},
|
|
||||||
{"tmpfs", "/.fortify", "tmpfs", "rw,nosuid,nodev,relatime,size=4k,mode=755,uid=1000002,gid=1000002", 0, 0},
|
|
||||||
{"tmpfs", "/dev", "tmpfs", "rw,nosuid,nodev,relatime,mode=755,uid=1000002,gid=1000002", 0, 0},
|
|
||||||
{"devtmpfs", "/dev/null", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0},
|
|
||||||
{"devtmpfs", "/dev/zero", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0},
|
|
||||||
{"devtmpfs", "/dev/full", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0},
|
|
||||||
{"devtmpfs", "/dev/random", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0},
|
|
||||||
{"devtmpfs", "/dev/urandom", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0},
|
|
||||||
{"devtmpfs", "/dev/tty", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0},
|
|
||||||
{"devpts", "/dev/pts", "devpts", "rw,nosuid,noexec,relatime,mode=620,ptmxmode=666", 0, 0},
|
|
||||||
{"mqueue", "/dev/mqueue", "mqueue", "rw,relatime", 0, 0},
|
|
||||||
{"/dev/disk/by-label/nixos", "/nix/store", "ext4", "ro,nosuid,nodev,relatime", 0, 0},
|
|
||||||
{"/dev/disk/by-label/nixos", "/.fortify/app", "ext4", "ro,nosuid,nodev,relatime", 0, 0},
|
|
||||||
{"/dev/disk/by-label/nixos", "/etc/resolv.conf", "ext4", "ro,nosuid,nodev,relatime", 0, 0},
|
|
||||||
{"sysfs", "/sys/block", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0},
|
|
||||||
{"sysfs", "/sys/bus", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0},
|
|
||||||
{"sysfs", "/sys/class", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0},
|
|
||||||
{"sysfs", "/sys/dev", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0},
|
|
||||||
{"sysfs", "/sys/devices", "sysfs", "ro,nosuid,nodev,noexec,relatime", 0, 0},
|
|
||||||
{"/dev/disk/by-label/nixos", "/.fortify/nixGL", "ext4", "ro,nosuid,nodev,relatime", 0, 0},
|
|
||||||
{"devtmpfs", "/dev/dri", "devtmpfs", "rw,nosuid,size=49396k,nr_inodes=121247,mode=755", 0, 0},
|
|
||||||
{"/dev/disk/by-label/nixos", "/.fortify/etc", "ext4", "ro,nosuid,nodev,relatime", 0, 0},
|
|
||||||
{"tmpfs", "/run/user", "tmpfs", "rw,nosuid,nodev,relatime,size=1024k,mode=755,uid=1000002,gid=1000002", 0, 0},
|
|
||||||
{"tmpfs", "/run/user/65534", "tmpfs", "rw,nosuid,nodev,relatime,size=8192k,mode=755,uid=1000002,gid=1000002", 0, 0},
|
|
||||||
{"/dev/disk/by-label/nixos", "/tmp", "ext4", "rw,nosuid,nodev,relatime", 0, 0},
|
|
||||||
{"/dev/disk/by-label/nixos", "/data/data/org.codeberg.dnkl.foot", "ext4", "rw,nosuid,nodev,relatime", 0, 0},
|
|
||||||
{"tmpfs", "/etc/passwd", "tmpfs", "ro,nosuid,nodev,relatime,uid=1000002,gid=1000002", 0, 0},
|
|
||||||
{"tmpfs", "/etc/group", "tmpfs", "ro,nosuid,nodev,relatime,uid=1000002,gid=1000002", 0, 0},
|
|
||||||
{"/dev/disk/by-label/nixos", "/run/user/65534/wayland-0", "ext4", "ro,nosuid,nodev,relatime", 0, 0},
|
|
||||||
{"tmpfs", "/run/user/65534/pulse/native", "tmpfs", "ro,nosuid,nodev,relatime,size=98784k,nr_inodes=24696,mode=700,uid=1000,gid=100", 0, 0},
|
|
||||||
{"/dev/disk/by-label/nixos", "/run/user/65534/bus", "ext4", "ro,nosuid,nodev,relatime", 0, 0},
|
|
||||||
{"overlay", "/.fortify/sbin/fortify", "overlay", "ro,nosuid,nodev,relatime,lowerdir=/mnt-root/nix/.ro-store,upperdir=/mnt-root/nix/.rw-store/upper,workdir=/mnt-root/nix/.rw-store/work,uuid=on", 0, 0},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
name := path.Join(t.TempDir(), "sample")
|
|
||||||
if err := os.WriteFile(name, []byte(tc.sample), 0400); err != nil {
|
|
||||||
t.Fatalf("cannot write sample: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
i := 0
|
|
||||||
if err := sandbox.IterMounts(name, func(e *sandbox.Mntent) {
|
|
||||||
if i == len(tc.want) {
|
|
||||||
t.Errorf("IterMounts: got more than %d entries", i)
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
if *e != tc.want[i] {
|
|
||||||
t.Errorf("IterMounts: entry %d\n got: %s\nwant: %s", i,
|
|
||||||
e, &tc.want[i])
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("IterMounts: error = %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run(tc.name+" assert", func(t *testing.T) {
|
|
||||||
sandbox.ReplaceFatal(t.Fatalf)
|
|
||||||
|
|
||||||
wantFile := path.Join(t.TempDir(), "want.json")
|
|
||||||
if f, err := os.OpenFile(wantFile, os.O_CREATE|os.O_WRONLY, 0400); err != nil {
|
|
||||||
t.Fatalf("cannot create %q: %v", wantFile, err)
|
|
||||||
} else if err = json.NewEncoder(f).Encode(tc.want); err != nil {
|
|
||||||
t.Fatalf("cannot encode to %q: %v", wantFile, err)
|
|
||||||
} else if err = f.Close(); err != nil {
|
|
||||||
t.Fatalf("cannot close %q: %v", wantFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sandbox.MustAssertMounts(name, name, wantFile)
|
|
||||||
|
|
||||||
if err := os.Remove(wantFile); err != nil {
|
|
||||||
t.Fatalf("cannot remove %q: %v", wantFile, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := os.Remove(name); err != nil {
|
|
||||||
t.Fatalf("cannot remove %q: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
59
test/test.py
59
test/test.py
@ -95,19 +95,12 @@ print(denyOutput)
|
|||||||
denyOutputVerbose = machine.fail("sudo -u untrusted -i fortify -v run &>/dev/stdout")
|
denyOutputVerbose = machine.fail("sudo -u untrusted -i fortify -v run &>/dev/stdout")
|
||||||
print(denyOutputVerbose)
|
print(denyOutputVerbose)
|
||||||
|
|
||||||
# Fail direct fsu call:
|
|
||||||
print(machine.fail("sudo -u alice -i fsu"))
|
|
||||||
|
|
||||||
# Verify PrintBaseError behaviour:
|
# Verify PrintBaseError behaviour:
|
||||||
if denyOutput != "fsu: uid 1001 is not in the fsurc file\n":
|
if denyOutput != "fsu: uid 1001 is not in the fsurc file\n":
|
||||||
raise Exception(f"unexpected deny output:\n{denyOutput}")
|
raise Exception(f"unexpected deny output:\n{denyOutput}")
|
||||||
if denyOutputVerbose != "fsu: uid 1001 is not in the fsurc file\nfortify: *cannot obtain uid from fsu: permission denied\n":
|
if denyOutputVerbose != "fsu: uid 1001 is not in the fsurc file\nfortify: *cannot obtain uid from fsu: permission denied\n":
|
||||||
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
|
raise Exception(f"unexpected deny verbose output:\n{denyOutputVerbose}")
|
||||||
|
|
||||||
# Check sandbox state:
|
|
||||||
swaymsg("exec check-sandbox")
|
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/sandbox-ok")
|
|
||||||
|
|
||||||
# Start fortify permissive defaults outside Wayland session:
|
# Start fortify permissive defaults outside Wayland session:
|
||||||
print(machine.succeed("sudo -u alice -i fortify -v run -a 0 touch /tmp/success-bare"))
|
print(machine.succeed("sudo -u alice -i fortify -v run -a 0 touch /tmp/success-bare"))
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-bare")
|
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/success-bare")
|
||||||
@ -117,55 +110,36 @@ output = machine.succeed("sudo -u alice -i fortify run -a 0 true &>/dev/stdout")
|
|||||||
if output != "":
|
if output != "":
|
||||||
raise Exception(f"unexpected output\n{output}")
|
raise Exception(f"unexpected output\n{output}")
|
||||||
|
|
||||||
# Verify silent output permissive defaults signal:
|
|
||||||
def silent_output_interrupt(flags):
|
|
||||||
swaymsg("exec foot")
|
|
||||||
wait_for_window("alice@machine")
|
|
||||||
# aid 0 does not have home-manager
|
|
||||||
machine.send_chars(f"exec fortify run {flags}-a 0 sh -c 'export PATH=/run/current-system/sw/bin:$PATH && touch /tmp/pd-silent-ready && sleep infinity' &>/tmp/pd-silent\n")
|
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/0/pd-silent-ready")
|
|
||||||
machine.succeed("rm /tmp/fortify.1000/tmpdir/0/pd-silent-ready")
|
|
||||||
machine.send_key("ctrl-c")
|
|
||||||
machine.wait_until_fails("pgrep foot")
|
|
||||||
machine.wait_until_fails(f"pgrep -u alice -f 'fortify run {flags}-a 0 '")
|
|
||||||
output = machine.succeed("cat /tmp/pd-silent && rm /tmp/pd-silent")
|
|
||||||
if output != "":
|
|
||||||
raise Exception(f"unexpected output\n{output}")
|
|
||||||
|
|
||||||
|
|
||||||
silent_output_interrupt("")
|
|
||||||
silent_output_interrupt("--dbus ") # this one is especially painful as it maintains a helper
|
|
||||||
silent_output_interrupt("--wayland -X --dbus --pulse ")
|
|
||||||
|
|
||||||
# Verify graceful failure on bad Wayland display name:
|
# Verify graceful failure on bad Wayland display name:
|
||||||
print(machine.fail("sudo -u alice -i fortify -v run --wayland true"))
|
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")
|
||||||
collect_state_ui("dbus_notify_exited")
|
collect_state_ui("dbus_notify_exited")
|
||||||
machine.succeed("pkill -9 mako")
|
machine.succeed("pkill -9 mako")
|
||||||
|
|
||||||
# Start app (foot) with Wayland enablement:
|
# Start app (foot) with Wayland enablement:
|
||||||
swaymsg("exec ne-foot")
|
swaymsg("exec ne-foot")
|
||||||
wait_for_window("u0_a2@machine")
|
wait_for_window("u0_a1@machine")
|
||||||
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
|
machine.send_chars("clear; wayland-info && touch /tmp/success-client\n")
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/2/success-client")
|
machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/success-client")
|
||||||
collect_state_ui("foot_wayland")
|
collect_state_ui("foot_wayland")
|
||||||
check_state("ne-foot", 1)
|
check_state("ne-foot", 1)
|
||||||
# Verify acl on XDG_RUNTIME_DIR:
|
# Verify acl on XDG_RUNTIME_DIR:
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000002"))
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000001"))
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
machine.wait_until_fails("pgrep foot")
|
machine.wait_until_fails("pgrep foot")
|
||||||
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
||||||
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000002")
|
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000001")
|
||||||
|
|
||||||
# Start app (foot) with Wayland enablement from a terminal:
|
# Start app (foot) with Wayland enablement from a terminal:
|
||||||
swaymsg(
|
swaymsg(
|
||||||
"exec foot $SHELL -c '(ne-foot) & sleep 1 && fortify show $(fortify ps --short) && touch /tmp/ps-show-ok && cat'")
|
"exec foot $SHELL -c '(ne-foot) & sleep 1 && fortify show $(fortify ps --short) && touch /tmp/ps-show-ok && cat'")
|
||||||
wait_for_window("u0_a2@machine")
|
wait_for_window("u0_a1@machine")
|
||||||
machine.send_chars("clear; wayland-info && touch /tmp/success-client-term\n")
|
machine.send_chars("clear; wayland-info && touch /tmp/success-client-term\n")
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/2/success-client-term")
|
machine.wait_for_file("/tmp/fortify.1000/tmpdir/1/success-client-term")
|
||||||
machine.wait_for_file("/tmp/ps-show-ok")
|
machine.wait_for_file("/tmp/ps-show-ok")
|
||||||
collect_state_ui("foot_wayland_term")
|
collect_state_ui("foot_wayland_term")
|
||||||
check_state("ne-foot", 1)
|
check_state("ne-foot", 1)
|
||||||
@ -176,9 +150,9 @@ machine.wait_until_fails("pgrep foot")
|
|||||||
|
|
||||||
# Test PulseAudio (fortify does not support PipeWire yet):
|
# Test PulseAudio (fortify does not support PipeWire yet):
|
||||||
swaymsg("exec pa-foot")
|
swaymsg("exec pa-foot")
|
||||||
wait_for_window("u0_a3@machine")
|
wait_for_window("u0_a2@machine")
|
||||||
machine.send_chars("clear; pactl info && touch /tmp/success-pulse\n")
|
machine.send_chars("clear; pactl info && touch /tmp/success-pulse\n")
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/3/success-pulse")
|
machine.wait_for_file("/tmp/fortify.1000/tmpdir/2/success-pulse")
|
||||||
collect_state_ui("pulse_wayland")
|
collect_state_ui("pulse_wayland")
|
||||||
check_state("pa-foot", 9)
|
check_state("pa-foot", 9)
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
@ -186,9 +160,9 @@ machine.wait_until_fails("pgrep foot")
|
|||||||
|
|
||||||
# Test XWayland (foot does not support X):
|
# Test XWayland (foot does not support X):
|
||||||
swaymsg("exec x11-alacritty")
|
swaymsg("exec x11-alacritty")
|
||||||
wait_for_window("u0_a4@machine")
|
wait_for_window("u0_a3@machine")
|
||||||
machine.send_chars("clear; glinfo && touch /tmp/success-client-x11\n")
|
machine.send_chars("clear; glinfo && touch /tmp/success-client-x11\n")
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/4/success-client-x11")
|
machine.wait_for_file("/tmp/fortify.1000/tmpdir/3/success-client-x11")
|
||||||
collect_state_ui("alacritty_x11")
|
collect_state_ui("alacritty_x11")
|
||||||
check_state("x11-alacritty", 2)
|
check_state("x11-alacritty", 2)
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
@ -196,23 +170,24 @@ machine.wait_until_fails("pgrep alacritty")
|
|||||||
|
|
||||||
# Start app (foot) with direct Wayland access:
|
# Start app (foot) with direct Wayland access:
|
||||||
swaymsg("exec da-foot")
|
swaymsg("exec da-foot")
|
||||||
wait_for_window("u0_a5@machine")
|
wait_for_window("u0_a4@machine")
|
||||||
machine.send_chars("clear; wayland-info && touch /tmp/success-direct\n")
|
machine.send_chars("clear; wayland-info && touch /tmp/success-direct\n")
|
||||||
machine.wait_for_file("/tmp/fortify.1000/tmpdir/5/success-direct")
|
machine.wait_for_file("/tmp/fortify.1000/tmpdir/4/success-direct")
|
||||||
collect_state_ui("foot_direct")
|
collect_state_ui("foot_direct")
|
||||||
check_state("da-foot", 1)
|
check_state("da-foot", 1)
|
||||||
# Verify acl on XDG_RUNTIME_DIR:
|
# Verify acl on XDG_RUNTIME_DIR:
|
||||||
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000005"))
|
print(machine.succeed("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000004"))
|
||||||
machine.send_chars("exit\n")
|
machine.send_chars("exit\n")
|
||||||
machine.wait_until_fails("pgrep foot")
|
machine.wait_until_fails("pgrep foot")
|
||||||
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
# Verify acl cleanup on XDG_RUNTIME_DIR:
|
||||||
machine.wait_until_fails("getfacl --absolute-names --omit-header --numeric /run/user/1000 | grep 1000005")
|
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"))
|
||||||
|
|
||||||
# Exit Sway and verify process exit status 0:
|
# Exit Sway and verify process exit status 0:
|
||||||
swaymsg("exit", succeed=False)
|
swaymsg("exit", succeed=False)
|
||||||
|
machine.wait_until_fails("pgrep -x sway")
|
||||||
machine.wait_for_file("/tmp/sway-exit-ok")
|
machine.wait_for_file("/tmp/sway-exit-ok")
|
||||||
|
|
||||||
# Print fortify runDir contents:
|
# Print fortify runDir contents:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user