Compare commits
34 Commits
2e3bb1893e
...
be4d8b6300
Author | SHA1 | Date | |
---|---|---|---|
be4d8b6300 | |||
3e11ce6868 | |||
562f5ed797 | |||
db03565614 | |||
7d99e45b88 | |||
1651eb06df | |||
ac543a1ce8 | |||
e2489059c1 | |||
2e3f6a4c51 | |||
2162029f46 | |||
a1148edd00 | |||
6acd0d4e88 | |||
35b7142317 | |||
c4d6651cae | |||
22a4b99674 | |||
1464ef774b | |||
66ba4cea5c | |||
f8d0786509 | |||
56a73bb019 | |||
fb8abf63db | |||
63802c5f0d | |||
aff80b6b00 | |||
a98a176907 | |||
5302879b88 | |||
891b3cbde7 | |||
c795293f36 | |||
42e1043300 | |||
5416b07daa | |||
e57a0e9bf2 | |||
ab48706ebe | |||
c1a459a0b1 | |||
5125e96ecf | |||
e0e2f40e84 | |||
bf8094c6ca |
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@
|
|||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
*.pkg
|
||||||
/fortify
|
/fortify
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
|
202
bundle.nix
Normal file
202
bundle.nix
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
{
|
||||||
|
nixpkgsFor,
|
||||||
|
system,
|
||||||
|
nixpkgs,
|
||||||
|
home-manager,
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
lib,
|
||||||
|
writeScript,
|
||||||
|
runtimeShell,
|
||||||
|
writeText,
|
||||||
|
symlinkJoin,
|
||||||
|
vmTools,
|
||||||
|
runCommand,
|
||||||
|
fetchFromGitHub,
|
||||||
|
|
||||||
|
nix,
|
||||||
|
|
||||||
|
name ? throw "name is required",
|
||||||
|
version ? throw "version is required",
|
||||||
|
pname ? "${name}-${version}",
|
||||||
|
modules ? [ ],
|
||||||
|
script ? ''
|
||||||
|
exec "$SHELL" "$@"
|
||||||
|
'',
|
||||||
|
|
||||||
|
id ? name,
|
||||||
|
app_id ? throw "app_id is required",
|
||||||
|
groups ? [ ],
|
||||||
|
userns ? false,
|
||||||
|
net ? true,
|
||||||
|
dev ? false,
|
||||||
|
no_new_session ? false,
|
||||||
|
map_real_uid ? false,
|
||||||
|
direct_wayland ? false,
|
||||||
|
system_bus ? null,
|
||||||
|
session_bus ? null,
|
||||||
|
|
||||||
|
allow_wayland ? true,
|
||||||
|
allow_x11 ? false,
|
||||||
|
allow_dbus ? true,
|
||||||
|
allow_pulse ? true,
|
||||||
|
gpu ? allow_wayland || allow_x11,
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) optionals;
|
||||||
|
|
||||||
|
homeManagerConfiguration = home-manager.lib.homeManagerConfiguration {
|
||||||
|
pkgs = nixpkgsFor.${system};
|
||||||
|
modules = modules ++ [
|
||||||
|
{
|
||||||
|
home = {
|
||||||
|
username = "fortify";
|
||||||
|
homeDirectory = "/data/data/${id}";
|
||||||
|
stateVersion = "22.11";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
launcher = writeScript "fortify-${pname}" ''
|
||||||
|
#!${runtimeShell} -el
|
||||||
|
${script}
|
||||||
|
'';
|
||||||
|
|
||||||
|
extraNixOSConfig =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
environment = {
|
||||||
|
etc.nixpkgs.source = nixpkgs.outPath;
|
||||||
|
systemPackages = [ pkgs.nix ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
nixos = nixpkgs.lib.nixosSystem {
|
||||||
|
inherit system;
|
||||||
|
modules = [
|
||||||
|
extraNixOSConfig
|
||||||
|
{ nix.settings.experimental-features = [ "flakes" ]; }
|
||||||
|
{ nix.settings.experimental-features = [ "nix-command" ]; }
|
||||||
|
{ boot.isContainer = true; }
|
||||||
|
{ system.stateVersion = "22.11"; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
etc = vmTools.runInLinuxVM (
|
||||||
|
runCommand "etc" { } ''
|
||||||
|
mkdir -p /etc
|
||||||
|
${nixos.config.system.build.etcActivationCommands}
|
||||||
|
|
||||||
|
# remove unused files
|
||||||
|
rm -rf /etc/sudoers
|
||||||
|
|
||||||
|
mkdir -p $out
|
||||||
|
tar -C /etc -cf "$out/etc.tar" .
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
extendSessionDefault = id: ext: {
|
||||||
|
filter = true;
|
||||||
|
|
||||||
|
talk = [ "org.freedesktop.Notifications" ] ++ ext.talk;
|
||||||
|
own =
|
||||||
|
(optionals (id != null) [
|
||||||
|
"${id}.*"
|
||||||
|
"org.mpris.MediaPlayer2.${id}.*"
|
||||||
|
])
|
||||||
|
++ ext.own;
|
||||||
|
|
||||||
|
inherit (ext) call broadcast;
|
||||||
|
};
|
||||||
|
|
||||||
|
nixGL = fetchFromGitHub {
|
||||||
|
owner = "nix-community";
|
||||||
|
repo = "nixGL";
|
||||||
|
rev = "310f8e49a149e4c9ea52f1adf70cdc768ec53f8a";
|
||||||
|
hash = "sha256-lnzZQYG0+EXl/6NkGpyIz+FEOc/DSEG57AP1VsdeNrM=";
|
||||||
|
};
|
||||||
|
|
||||||
|
mesaWrappers =
|
||||||
|
let
|
||||||
|
isIntelX86Platform = system == "x86_64-linux";
|
||||||
|
nixGLPackages = import (nixGL + "/default.nix") {
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
enable32bits = isIntelX86Platform;
|
||||||
|
enableIntelX86Extensions = isIntelX86Platform;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
symlinkJoin {
|
||||||
|
name = "nixGL-mesa";
|
||||||
|
paths = with nixGLPackages; [
|
||||||
|
nixGLIntel
|
||||||
|
nixVulkanIntel
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
info = builtins.toJSON {
|
||||||
|
inherit
|
||||||
|
name
|
||||||
|
version
|
||||||
|
id
|
||||||
|
app_id
|
||||||
|
launcher
|
||||||
|
groups
|
||||||
|
userns
|
||||||
|
net
|
||||||
|
dev
|
||||||
|
no_new_session
|
||||||
|
map_real_uid
|
||||||
|
direct_wayland
|
||||||
|
system_bus
|
||||||
|
gpu
|
||||||
|
;
|
||||||
|
|
||||||
|
session_bus =
|
||||||
|
if session_bus != null then
|
||||||
|
(session_bus (extendSessionDefault id))
|
||||||
|
else
|
||||||
|
(extendSessionDefault id {
|
||||||
|
talk = [ ];
|
||||||
|
own = [ ];
|
||||||
|
call = { };
|
||||||
|
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);
|
||||||
|
|
||||||
|
mesa = if gpu then mesaWrappers else null;
|
||||||
|
nix_gl = if gpu then nixGL else null;
|
||||||
|
current_system = nixos.config.system.build.toplevel;
|
||||||
|
activation_package = homeManagerConfiguration.activationPackage;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
|
||||||
|
writeScript "fortify-${pname}-bundle-prelude" ''
|
||||||
|
#!${runtimeShell} -el
|
||||||
|
OUT="$(mktemp -d)"
|
||||||
|
TAR="$(mktemp -u)"
|
||||||
|
set -x
|
||||||
|
|
||||||
|
nix copy --no-check-sigs --to "$OUT" "${nix}" "${nixos.config.system.build.toplevel}"
|
||||||
|
nix store --store "$OUT" optimise
|
||||||
|
chmod -R +r "$OUT/nix/var"
|
||||||
|
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"
|
||||||
|
|
||||||
|
# creating an intermediate file improves zstd performance
|
||||||
|
tar -C "$OUT" -cf "$TAR" .
|
||||||
|
chmod +w -R "$OUT" && rm -rf "$OUT"
|
||||||
|
|
||||||
|
zstd -T0 -19 -fo "${pname}.pkg" "$TAR"
|
||||||
|
rm "$TAR"
|
||||||
|
''
|
83
cmd/fpkg/bundle.go
Normal file
83
cmd/fpkg/bundle.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bundleInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
ID string `json:"id"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
AppID int `json:"app_id"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
Groups []string `json:"groups,omitempty"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
UserNS bool `json:"userns,omitempty"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
Net bool `json:"net,omitempty"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
Dev bool `json:"dev,omitempty"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
NoNewSession bool `json:"no_new_session,omitempty"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
MapRealUID bool `json:"map_real_uid,omitempty"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
DirectWayland bool `json:"direct_wayland,omitempty"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
SystemBus *dbus.Config `json:"system_bus,omitempty"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
SessionBus *dbus.Config `json:"session_bus,omitempty"`
|
||||||
|
// passed through to [fst.Config]
|
||||||
|
Enablements system.Enablements `json:"enablements"`
|
||||||
|
|
||||||
|
// allow gpu access within sandbox
|
||||||
|
GPU bool `json:"gpu"`
|
||||||
|
// store path to nixGL mesa wrappers
|
||||||
|
Mesa string `json:"mesa,omitempty"`
|
||||||
|
// store path to nixGL source
|
||||||
|
NixGL string `json:"nix_gl,omitempty"`
|
||||||
|
// store path to activate-and-exec script
|
||||||
|
Launcher string `json:"launcher"`
|
||||||
|
// store path to /run/current-system
|
||||||
|
CurrentSystem string `json:"current_system"`
|
||||||
|
// store path to home-manager activation package
|
||||||
|
ActivationPackage string `json:"activation_package"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadBundleInfo(name string, beforeFail func()) *bundleInfo {
|
||||||
|
bundle := new(bundleInfo)
|
||||||
|
if f, err := os.Open(name); err != nil {
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Fatalf("cannot open bundle: %v", err)
|
||||||
|
} else if err = json.NewDecoder(f).Decode(&bundle); err != nil {
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Fatalf("cannot parse bundle metadata: %v", err)
|
||||||
|
} else if err = f.Close(); err != nil {
|
||||||
|
fmsg.Printf("cannot close bundle metadata: %v", err)
|
||||||
|
// not fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
if bundle.ID == "" {
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Fatal("application identifier must not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundle
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatHostname(name string) string {
|
||||||
|
if h, err := os.Hostname(); err != nil {
|
||||||
|
fmsg.Printf("cannot get hostname: %v", err)
|
||||||
|
return "fortify-" + name
|
||||||
|
} else {
|
||||||
|
return h + "-" + name
|
||||||
|
}
|
||||||
|
}
|
195
cmd/fpkg/install.go
Normal file
195
cmd/fpkg/install.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"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 {
|
||||||
|
fmsg.Fatal("invalid argument")
|
||||||
|
}
|
||||||
|
pkgPath := args[0]
|
||||||
|
if !path.IsAbs(pkgPath) {
|
||||||
|
if dir, err := os.Getwd(); err != nil {
|
||||||
|
fmsg.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 {
|
||||||
|
fmsg.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()
|
||||||
|
fmsg.Fatalf("cannot access %q: %v", pathSet.metaPath, err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
// did not modify app, clean installation condition met later
|
||||||
|
} else if s.IsDir() {
|
||||||
|
cleanup()
|
||||||
|
fmsg.Fatalf("metadata path %q is not a file", pathSet.metaPath)
|
||||||
|
panic("unreachable")
|
||||||
|
} else {
|
||||||
|
app = loadBundleInfo(pathSet.metaPath, cleanup)
|
||||||
|
if app.ID != bundle.ID {
|
||||||
|
cleanup()
|
||||||
|
fmsg.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()
|
||||||
|
fmsg.Printf("package %q is identical to local application %q", pkgPath, app.ID)
|
||||||
|
fmsg.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppID determines uid
|
||||||
|
if app.AppID != bundle.AppID {
|
||||||
|
cleanup()
|
||||||
|
fmsg.Fatalf("package %q app id %d differs from installed %d", pkgPath, bundle.AppID, app.AppID)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// sec: should compare version string
|
||||||
|
fmsg.VPrintf("installing application %q version %q over local %q", bundle.ID, bundle.Version, app.Version)
|
||||||
|
} else {
|
||||||
|
fmsg.VPrintf("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()
|
||||||
|
fmsg.Fatalf("cannot create metadata file: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
} else if err = json.NewEncoder(f).Encode(bundle); err != nil {
|
||||||
|
cleanup()
|
||||||
|
fmsg.Fatalf("cannot write metadata: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
} else if err = f.Close(); err != nil {
|
||||||
|
fmsg.Printf("cannot close metadata file: %v", err)
|
||||||
|
// not fatal
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(pathSet.metaPath+"~", pathSet.metaPath); err != nil {
|
||||||
|
cleanup()
|
||||||
|
fmsg.Fatalf("cannot rename metadata file: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
}
|
48
cmd/fpkg/main.go
Normal file
48
cmd/fpkg/main.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shell = "/run/current-system/sw/bin/bash"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := os.Setenv("SHELL", shell); err != nil {
|
||||||
|
fmsg.Fatalf("cannot set $SHELL: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagVerbose bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&flagVerbose, "v", false, "Verbose output")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmsg.SetPrefix("fpkg")
|
||||||
|
|
||||||
|
flag.Parse()
|
||||||
|
fmsg.SetVerbose(flagVerbose)
|
||||||
|
|
||||||
|
args := flag.Args()
|
||||||
|
if len(args) < 1 {
|
||||||
|
fmsg.Fatal("invalid argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
case "install":
|
||||||
|
actionInstall(args[1:])
|
||||||
|
case "start":
|
||||||
|
actionStart(args[1:])
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmsg.Fatal("invalid argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmsg.Exit(0)
|
||||||
|
}
|
71
cmd/fpkg/paths.go
Normal file
71
cmd/fpkg/paths.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dataHome string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// dataHome
|
||||||
|
if p, ok := os.LookupEnv("FORTIFY_DATA_HOME"); ok {
|
||||||
|
dataHome = p
|
||||||
|
} else {
|
||||||
|
dataHome = "/var/lib/fortify/" + strconv.Itoa(os.Getuid())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookPath(file string) string {
|
||||||
|
if p, err := exec.LookPath(file); err != nil {
|
||||||
|
fmsg.Fatalf("%s: command not found", file)
|
||||||
|
panic("unreachable")
|
||||||
|
} else {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var beforeRunFail = new(atomic.Pointer[func()])
|
||||||
|
|
||||||
|
func mustRun(name string, arg ...string) {
|
||||||
|
fmsg.VPrintf("spawning process: %q %q", name, arg)
|
||||||
|
cmd := exec.Command(name, arg...)
|
||||||
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
if f := beforeRunFail.Swap(nil); f != nil {
|
||||||
|
(*f)()
|
||||||
|
}
|
||||||
|
fmsg.Fatalf("%s: %v", name, err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type appPathSet struct {
|
||||||
|
// ${dataHome}/${id}
|
||||||
|
baseDir string
|
||||||
|
// ${baseDir}/app
|
||||||
|
metaPath string
|
||||||
|
// ${baseDir}/files
|
||||||
|
homeDir string
|
||||||
|
// ${baseDir}/cache
|
||||||
|
cacheDir string
|
||||||
|
// ${baseDir}/cache/nix
|
||||||
|
nixPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathSetByApp(id string) *appPathSet {
|
||||||
|
pathSet := new(appPathSet)
|
||||||
|
pathSet.baseDir = path.Join(dataHome, id)
|
||||||
|
pathSet.metaPath = path.Join(pathSet.baseDir, "app")
|
||||||
|
pathSet.homeDir = path.Join(pathSet.baseDir, "files")
|
||||||
|
pathSet.cacheDir = path.Join(pathSet.baseDir, "cache")
|
||||||
|
pathSet.nixPath = path.Join(pathSet.cacheDir, "nix")
|
||||||
|
return pathSet
|
||||||
|
}
|
64
cmd/fpkg/proc.go
Normal file
64
cmd/fpkg/proc.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fortifyApp(config *fst.Config, beforeFail func()) {
|
||||||
|
var (
|
||||||
|
cmd *exec.Cmd
|
||||||
|
st io.WriteCloser
|
||||||
|
)
|
||||||
|
if p, ok := internal.Check(internal.Fortify); !ok {
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Fatal("invalid fortify path, this copy of fpkg is not compiled correctly")
|
||||||
|
panic("unreachable")
|
||||||
|
} else if r, w, err := os.Pipe(); err != nil {
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Fatalf("cannot pipe: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
} else {
|
||||||
|
if fmsg.Verbose() {
|
||||||
|
cmd = exec.Command(p, "-v", "app", "3")
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command(p, "app", "3")
|
||||||
|
}
|
||||||
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
cmd.ExtraFiles = []*os.File{r}
|
||||||
|
st = w
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := json.NewEncoder(st).Encode(config); err != nil {
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Fatalf("cannot send configuration: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Fatalf("cannot start fortify: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
var exitError *exec.ExitError
|
||||||
|
if errors.As(err, &exitError) {
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Exit(exitError.ExitCode())
|
||||||
|
panic("unreachable")
|
||||||
|
} else {
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Fatalf("cannot wait: %v", err)
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
175
cmd/fpkg/start.go
Normal file
175
cmd/fpkg/start.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
fmsg.Fatal("invalid argument")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parse app metadata.
|
||||||
|
*/
|
||||||
|
|
||||||
|
id := args[0]
|
||||||
|
pathSet := pathSetByApp(id)
|
||||||
|
app := loadBundleInfo(pathSet.metaPath, func() {})
|
||||||
|
if app.ID != id {
|
||||||
|
fmsg.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] = shell
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
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() {})
|
||||||
|
fmsg.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},
|
||||||
|
}...)
|
||||||
|
}
|
98
cmd/fpkg/with.go
Normal file
98
cmd/fpkg/with.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/fst"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func withNixDaemon(
|
||||||
|
action string, command []string, net bool, updateConfig func(config *fst.Config) *fst.Config,
|
||||||
|
app *bundleInfo, pathSet *appPathSet, dropShell bool, beforeFail func(),
|
||||||
|
) {
|
||||||
|
fortifyAppDropShell(updateConfig(&fst.Config{
|
||||||
|
ID: app.ID,
|
||||||
|
Command: []string{shell, "-lc", "rm -f /nix/var/nix/daemon-socket/socket && " +
|
||||||
|
// start nix-daemon
|
||||||
|
"nix-daemon --store / & " +
|
||||||
|
// wait for socket to appear
|
||||||
|
"(while [ ! -S /nix/var/nix/daemon-socket/socket ]; do sleep 0.01; done) && " +
|
||||||
|
// create directory so nix stops complaining
|
||||||
|
"mkdir -p /nix/var/nix/profiles/per-user/root/channels && " +
|
||||||
|
strings.Join(command, " && ") +
|
||||||
|
// terminate nix-daemon
|
||||||
|
" && pkill nix-daemon",
|
||||||
|
},
|
||||||
|
Confinement: fst.ConfinementConfig{
|
||||||
|
AppID: app.AppID,
|
||||||
|
Username: "fortify",
|
||||||
|
Inner: path.Join("/data/data", app.ID),
|
||||||
|
Outer: pathSet.homeDir,
|
||||||
|
Sandbox: &fst.SandboxConfig{
|
||||||
|
Hostname: formatHostname(app.Name) + "-" + action,
|
||||||
|
UserNS: true, // nix sandbox requires userns
|
||||||
|
Net: net,
|
||||||
|
NoNewSession: dropShell,
|
||||||
|
Filesystem: []*fst.FilesystemConfig{
|
||||||
|
{Src: pathSet.nixPath, Dst: "/nix", Write: true, Must: true},
|
||||||
|
},
|
||||||
|
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},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}), dropShell, beforeFail)
|
||||||
|
}
|
||||||
|
|
||||||
|
func withCacheDir(action string, command []string, workDir string, app *bundleInfo, pathSet *appPathSet, dropShell bool, beforeFail func()) {
|
||||||
|
fortifyAppDropShell(&fst.Config{
|
||||||
|
ID: app.ID,
|
||||||
|
Command: []string{shell, "-lc", strings.Join(command, " && ")},
|
||||||
|
Confinement: fst.ConfinementConfig{
|
||||||
|
AppID: app.AppID,
|
||||||
|
Username: "nixos",
|
||||||
|
Inner: path.Join("/data/data", app.ID, "cache"),
|
||||||
|
Outer: pathSet.cacheDir, // this also ensures cacheDir via fshim
|
||||||
|
Sandbox: &fst.SandboxConfig{
|
||||||
|
Hostname: formatHostname(app.Name) + "-" + action,
|
||||||
|
NoNewSession: dropShell,
|
||||||
|
Filesystem: []*fst.FilesystemConfig{
|
||||||
|
{Src: path.Join(workDir, "nix"), Dst: "/nix", Must: true},
|
||||||
|
{Src: workDir, Dst: path.Join(fst.Tmp, "bundle"), Must: true},
|
||||||
|
},
|
||||||
|
Link: [][2]string{
|
||||||
|
{app.CurrentSystem, "/run/current-system"},
|
||||||
|
{"/run/current-system/sw/bin", "/bin"},
|
||||||
|
{"/run/current-system/sw/bin", "/usr/bin"},
|
||||||
|
},
|
||||||
|
Etc: path.Join(workDir, "etc"),
|
||||||
|
AutoEtc: true,
|
||||||
|
},
|
||||||
|
ExtraPerms: []*fst.ExtraPermConfig{
|
||||||
|
{Path: dataHome, Execute: true},
|
||||||
|
{Ensure: true, Path: pathSet.baseDir, Read: true, Write: true, Execute: true},
|
||||||
|
{Path: workDir, Execute: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, dropShell, beforeFail)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fortifyAppDropShell(config *fst.Config, dropShell bool, beforeFail func()) {
|
||||||
|
if dropShell {
|
||||||
|
config.Command = []string{shell, "-l"}
|
||||||
|
fortifyApp(config, beforeFail)
|
||||||
|
beforeFail()
|
||||||
|
fmsg.Exit(0)
|
||||||
|
}
|
||||||
|
fortifyApp(config, beforeFail)
|
||||||
|
}
|
186
dbus/address.go
Normal file
186
dbus/address.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddrEntry struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
Values [][2]string `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses D-Bus address according to
|
||||||
|
// https://dbus.freedesktop.org/doc/dbus-specification.html#addresses
|
||||||
|
func Parse(addr []byte) ([]AddrEntry, error) {
|
||||||
|
// Look for a semicolon
|
||||||
|
address := bytes.Split(bytes.TrimSuffix(addr, []byte{';'}), []byte{';'})
|
||||||
|
|
||||||
|
// Allocate for entries
|
||||||
|
v := make([]AddrEntry, len(address))
|
||||||
|
|
||||||
|
for i, s := range address {
|
||||||
|
var pairs [][]byte
|
||||||
|
|
||||||
|
// Look for the colon :
|
||||||
|
if method, list, ok := bytes.Cut(s, []byte{':'}); !ok {
|
||||||
|
return v, &BadAddressError{ErrNoColon, i, s, -1, nil}
|
||||||
|
} else {
|
||||||
|
pairs = bytes.Split(list, []byte{','})
|
||||||
|
v[i].Method = string(method)
|
||||||
|
v[i].Values = make([][2]string, len(pairs))
|
||||||
|
}
|
||||||
|
|
||||||
|
for j, pair := range pairs {
|
||||||
|
key, value, ok := bytes.Cut(pair, []byte{'='})
|
||||||
|
if !ok {
|
||||||
|
return v, &BadAddressError{ErrBadPairSep, i, s, j, pair}
|
||||||
|
}
|
||||||
|
if len(key) == 0 {
|
||||||
|
return v, &BadAddressError{ErrBadPairKey, i, s, j, pair}
|
||||||
|
}
|
||||||
|
if len(value) == 0 {
|
||||||
|
return v, &BadAddressError{ErrBadPairVal, i, s, j, pair}
|
||||||
|
}
|
||||||
|
v[i].Values[j][0] = string(key)
|
||||||
|
|
||||||
|
if val, errno := unescapeValue(value); errno != errSuccess {
|
||||||
|
return v, &BadAddressError{errno, i, s, j, pair}
|
||||||
|
} else {
|
||||||
|
v[i].Values[j][1] = string(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
||||||
|
if l := len(v) - (bytes.Count(v, []byte{'%'}) * 2); l < 0 {
|
||||||
|
errno = ErrBadValLength
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
val = make([]byte, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
var i, skip int
|
||||||
|
for iu, b := range v {
|
||||||
|
if skip > 0 {
|
||||||
|
skip--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\*
|
||||||
|
goto opt
|
||||||
|
} else if b >= '0' && b <= '9' { // 0-9
|
||||||
|
goto opt
|
||||||
|
} else if b >= 'A' && b <= 'Z' { // A-Z
|
||||||
|
goto opt
|
||||||
|
} else if b >= 'a' && b <= 'z' { // a-z
|
||||||
|
goto opt
|
||||||
|
}
|
||||||
|
|
||||||
|
if b != '%' {
|
||||||
|
errno = ErrBadValByte
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
skip += 2
|
||||||
|
if iu+2 >= len(v) {
|
||||||
|
errno = ErrBadValHexLength
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
|
||||||
|
if errors.As(err, new(hex.InvalidByteError)) {
|
||||||
|
errno = ErrBadValHexByte
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// unreachable
|
||||||
|
panic(err.Error())
|
||||||
|
} else if c != 1 {
|
||||||
|
// unreachable
|
||||||
|
panic(fmt.Sprintf("invalid decode length %d", c))
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
|
||||||
|
opt:
|
||||||
|
val[i] = b
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseError uint8
|
||||||
|
|
||||||
|
func (e ParseError) Error() string {
|
||||||
|
switch e {
|
||||||
|
case errSuccess:
|
||||||
|
panic("attempted to return success as error")
|
||||||
|
case ErrNoColon:
|
||||||
|
return "address does not contain a colon"
|
||||||
|
case ErrBadPairSep:
|
||||||
|
return "'=' character not found"
|
||||||
|
case ErrBadPairKey:
|
||||||
|
return "'=' character has no key preceding it"
|
||||||
|
case ErrBadPairVal:
|
||||||
|
return "'=' character has no value following it"
|
||||||
|
case ErrBadValLength:
|
||||||
|
return "unescaped value has impossible length"
|
||||||
|
case ErrBadValByte:
|
||||||
|
return "in D-Bus address, characters other than [-0-9A-Za-z_/.\\*] should have been escaped"
|
||||||
|
case ErrBadValHexLength:
|
||||||
|
return "in D-Bus address, percent character was not followed by two hex digits"
|
||||||
|
case ErrBadValHexByte:
|
||||||
|
return "in D-Bus address, percent character was followed by characters other than hex digits"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("parse error %d", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
errSuccess ParseError = iota
|
||||||
|
ErrNoColon
|
||||||
|
ErrBadPairSep
|
||||||
|
ErrBadPairKey
|
||||||
|
ErrBadPairVal
|
||||||
|
ErrBadValLength
|
||||||
|
ErrBadValByte
|
||||||
|
ErrBadValHexLength
|
||||||
|
ErrBadValHexByte
|
||||||
|
)
|
||||||
|
|
||||||
|
type BadAddressError struct {
|
||||||
|
// error type
|
||||||
|
Type ParseError
|
||||||
|
|
||||||
|
// bad entry position
|
||||||
|
EntryPos int
|
||||||
|
// bad entry value
|
||||||
|
EntryVal []byte
|
||||||
|
|
||||||
|
// bad pair position
|
||||||
|
PairPos int
|
||||||
|
// bad pair value
|
||||||
|
PairVal []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BadAddressError) Is(err error) bool {
|
||||||
|
var b *BadAddressError
|
||||||
|
return errors.As(err, &b) && a.Type == b.Type &&
|
||||||
|
a.EntryPos == b.EntryPos && slices.Equal(a.EntryVal, b.EntryVal) &&
|
||||||
|
a.PairPos == b.PairPos && slices.Equal(a.PairVal, b.PairVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BadAddressError) Error() string {
|
||||||
|
return a.Type.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *BadAddressError) Unwrap() error {
|
||||||
|
return a.Type
|
||||||
|
}
|
55
dbus/address_escape_test.go
Normal file
55
dbus/address_escape_test.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnescapeValue(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
value string
|
||||||
|
want string
|
||||||
|
wantErr ParseError
|
||||||
|
}{
|
||||||
|
// upstream test cases
|
||||||
|
{value: "abcde", want: "abcde"},
|
||||||
|
{value: "", want: ""},
|
||||||
|
{value: "%20%20", want: " "},
|
||||||
|
{value: "%24", want: "$"},
|
||||||
|
{value: "%25", want: "%"},
|
||||||
|
{value: "abc%24", want: "abc$"},
|
||||||
|
{value: "%24abc", want: "$abc"},
|
||||||
|
{value: "abc%24abc", want: "abc$abc"},
|
||||||
|
{value: "/", want: "/"},
|
||||||
|
{value: "-", want: "-"},
|
||||||
|
{value: "_", want: "_"},
|
||||||
|
{value: "A", want: "A"},
|
||||||
|
{value: "I", want: "I"},
|
||||||
|
{value: "Z", want: "Z"},
|
||||||
|
{value: "a", want: "a"},
|
||||||
|
{value: "i", want: "i"},
|
||||||
|
{value: "z", want: "z"},
|
||||||
|
/* Bug: https://bugs.freedesktop.org/show_bug.cgi?id=53499 */
|
||||||
|
{value: "%c3%b6", want: "\xc3\xb6"},
|
||||||
|
|
||||||
|
{value: "%a", wantErr: ErrBadValHexLength},
|
||||||
|
{value: "%q", wantErr: ErrBadValHexLength},
|
||||||
|
{value: "%az", wantErr: ErrBadValHexByte},
|
||||||
|
{value: "%%", wantErr: ErrBadValLength},
|
||||||
|
{value: "%$$", wantErr: ErrBadValHexByte},
|
||||||
|
{value: "abc%a", wantErr: ErrBadValHexLength},
|
||||||
|
{value: "%axyz", wantErr: ErrBadValHexByte},
|
||||||
|
{value: "%", wantErr: ErrBadValLength},
|
||||||
|
{value: "$", wantErr: ErrBadValByte},
|
||||||
|
{value: " ", wantErr: ErrBadValByte},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run("unescape "+tc.value, func(t *testing.T) {
|
||||||
|
if got, errno := unescapeValue([]byte(tc.value)); errno != tc.wantErr {
|
||||||
|
t.Errorf("unescapeValue() errno = %v, wantErr %v", errno, tc.wantErr)
|
||||||
|
} else if tc.wantErr == errSuccess && string(got) != tc.want {
|
||||||
|
t.Errorf("unescapeValue() = %q, want %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
119
dbus/address_test.go
Normal file
119
dbus/address_test.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package dbus_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
addr string
|
||||||
|
want []dbus.AddrEntry
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple session unix",
|
||||||
|
addr: "unix:path=/run/user/1971/bus",
|
||||||
|
want: []dbus.AddrEntry{{
|
||||||
|
Method: "unix",
|
||||||
|
Values: [][2]string{{"path", "/run/user/1971/bus"}},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simple upper escape",
|
||||||
|
addr: "debug:name=Test,cat=cute,escaped=%c3%b6",
|
||||||
|
want: []dbus.AddrEntry{{
|
||||||
|
Method: "debug",
|
||||||
|
Values: [][2]string{
|
||||||
|
{"name", "Test"},
|
||||||
|
{"cat", "cute"},
|
||||||
|
{"escaped", "\xc3\xb6"},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simple bad escape",
|
||||||
|
addr: "debug:name=%",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadValLength,
|
||||||
|
EntryPos: 0, EntryVal: []byte("debug:name=%"), PairPos: 0, PairVal: []byte("name=%")},
|
||||||
|
},
|
||||||
|
|
||||||
|
// upstream test cases
|
||||||
|
{
|
||||||
|
name: "full address success",
|
||||||
|
addr: "unix:path=/tmp/foo;debug:name=test,sliff=sloff;",
|
||||||
|
want: []dbus.AddrEntry{
|
||||||
|
{Method: "unix", Values: [][2]string{{"path", "/tmp/foo"}}},
|
||||||
|
{Method: "debug", Values: [][2]string{{"name", "test"}, {"sliff", "sloff"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty address",
|
||||||
|
addr: "",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
|
||||||
|
EntryVal: []byte{}, PairPos: -1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no body",
|
||||||
|
addr: "foo",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
|
||||||
|
EntryPos: 0, EntryVal: []byte("foo"), PairPos: -1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no pair separator",
|
||||||
|
addr: "foo:bar",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||||
|
EntryPos: 0, EntryVal: []byte("foo:bar"), PairPos: 0, PairVal: []byte("bar")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no pair separator multi pair",
|
||||||
|
addr: "foo:bar,baz",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||||
|
EntryPos: 0, EntryVal: []byte("foo:bar,baz"), PairPos: 0, PairVal: []byte("bar")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no pair separator single valid pair",
|
||||||
|
addr: "foo:bar=foo,baz",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||||
|
EntryPos: 0, EntryVal: []byte("foo:bar=foo,baz"), PairPos: 1, PairVal: []byte("baz")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no body single valid address",
|
||||||
|
addr: "foo:bar=foo;baz",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrNoColon,
|
||||||
|
EntryPos: 1, EntryVal: []byte("baz"), PairPos: -1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no key",
|
||||||
|
addr: "foo:=foo",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairKey,
|
||||||
|
EntryPos: 0, EntryVal: []byte("foo:=foo"), PairPos: 0, PairVal: []byte("=foo")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no value",
|
||||||
|
addr: "foo:foo=",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairVal,
|
||||||
|
EntryPos: 0, EntryVal: []byte("foo:foo="), PairPos: 0, PairVal: []byte("foo=")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no pair separator single valid pair trailing",
|
||||||
|
addr: "foo:foo,bar=baz",
|
||||||
|
wantErr: &dbus.BadAddressError{Type: dbus.ErrBadPairSep,
|
||||||
|
EntryPos: 0, EntryVal: []byte("foo:foo,bar=baz"), PairPos: 0, PairVal: []byte("foo")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got, err := dbus.Parse([]byte(tc.addr)); !errors.Is(err, tc.wantErr) {
|
||||||
|
t.Errorf("Parse() error = %v, wantErr %v", err, tc.wantErr)
|
||||||
|
} else if tc.wantErr == nil && !reflect.DeepEqual(got, tc.want) {
|
||||||
|
t.Errorf("Parse() = %#v, want %#v", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestConfig_Args(t *testing.T) {
|
func TestConfig_Args(t *testing.T) {
|
||||||
for _, tc := range testCases() {
|
for _, tc := range makeTestCases() {
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
// args does not check for nulls
|
// args does not check for nulls
|
||||||
continue
|
continue
|
||||||
@ -30,7 +30,7 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewConfigFromFile(t *testing.T) {
|
func TestNewConfigFromFile(t *testing.T) {
|
||||||
for _, tc := range testCases() {
|
for _, tc := range makeTestCases() {
|
||||||
name := new(strings.Builder)
|
name := new(strings.Builder)
|
||||||
name.WriteString("parse configuration file for application ")
|
name.WriteString("parse configuration file for application ")
|
||||||
name.WriteString(tc.id)
|
name.WriteString(tc.id)
|
||||||
|
@ -82,10 +82,10 @@ var samples = []dbusTestCase{
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"moe.ophivana.CrashTestDummy", &dbus.Config{
|
"uk.gensokyo.CrashTestDummy", &dbus.Config{
|
||||||
See: []string{"moe.ophivana.CrashTestDummy1"},
|
See: []string{"uk.gensokyo.CrashTestDummy1"},
|
||||||
Talk: []string{"org.freedesktop.Notifications"},
|
Talk: []string{"org.freedesktop.Notifications"},
|
||||||
Own: []string{"moe.ophivana.CrashTestDummy.*", "org.mpris.MediaPlayer2.moe.ophivana.CrashTestDummy.*"},
|
Own: []string{"uk.gensokyo.CrashTestDummy.*", "org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy.*"},
|
||||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||||
Log: true,
|
Log: true,
|
||||||
@ -96,19 +96,19 @@ var samples = []dbusTestCase{
|
|||||||
"unix:path=/run/user/1971/bus",
|
"unix:path=/run/user/1971/bus",
|
||||||
"/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus",
|
"/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus",
|
||||||
"--filter",
|
"--filter",
|
||||||
"--see=moe.ophivana.CrashTestDummy1",
|
"--see=uk.gensokyo.CrashTestDummy1",
|
||||||
"--talk=org.freedesktop.Notifications",
|
"--talk=org.freedesktop.Notifications",
|
||||||
"--own=moe.ophivana.CrashTestDummy.*",
|
"--own=uk.gensokyo.CrashTestDummy.*",
|
||||||
"--own=org.mpris.MediaPlayer2.moe.ophivana.CrashTestDummy.*",
|
"--own=org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy.*",
|
||||||
"--call=org.freedesktop.portal.*=*",
|
"--call=org.freedesktop.portal.*=*",
|
||||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||||
"--log"},
|
"--log"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moe.ophivana.CrashTestDummy1", &dbus.Config{
|
"uk.gensokyo.CrashTestDummy1", &dbus.Config{
|
||||||
See: []string{"moe.ophivana.CrashTestDummy"},
|
See: []string{"uk.gensokyo.CrashTestDummy"},
|
||||||
Talk: []string{"org.freedesktop.Notifications"},
|
Talk: []string{"org.freedesktop.Notifications"},
|
||||||
Own: []string{"moe.ophivana.CrashTestDummy1.*", "org.mpris.MediaPlayer2.moe.ophivana.CrashTestDummy1.*"},
|
Own: []string{"uk.gensokyo.CrashTestDummy1.*", "org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy1.*"},
|
||||||
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
Call: map[string]string{"org.freedesktop.portal.*": "*"},
|
||||||
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
Broadcast: map[string]string{"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"},
|
||||||
Log: true,
|
Log: true,
|
||||||
@ -119,10 +119,10 @@ var samples = []dbusTestCase{
|
|||||||
"unix:path=/run/user/1971/bus",
|
"unix:path=/run/user/1971/bus",
|
||||||
"/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus",
|
"/tmp/fortify.1971/5da7845287a936efbc2fa75d7d81e501/bus",
|
||||||
"--filter",
|
"--filter",
|
||||||
"--see=moe.ophivana.CrashTestDummy",
|
"--see=uk.gensokyo.CrashTestDummy",
|
||||||
"--talk=org.freedesktop.Notifications",
|
"--talk=org.freedesktop.Notifications",
|
||||||
"--own=moe.ophivana.CrashTestDummy1.*",
|
"--own=uk.gensokyo.CrashTestDummy1.*",
|
||||||
"--own=org.mpris.MediaPlayer2.moe.ophivana.CrashTestDummy1.*",
|
"--own=org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy1.*",
|
||||||
"--call=org.freedesktop.portal.*=*",
|
"--call=org.freedesktop.portal.*=*",
|
||||||
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
"--broadcast=org.freedesktop.portal.*=@/org/freedesktop/portal/*",
|
||||||
"--log"},
|
"--log"},
|
||||||
@ -145,7 +145,7 @@ var (
|
|||||||
testCaseOnce sync.Once
|
testCaseOnce sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
func testCases() []dbusTestCase {
|
func makeTestCases() []dbusTestCase {
|
||||||
testCaseOnce.Do(testCaseGenerate)
|
testCaseOnce.Do(testCaseGenerate)
|
||||||
return testCasesV
|
return testCasesV
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"see": [
|
"see": [
|
||||||
"moe.ophivana.CrashTestDummy1"
|
"uk.gensokyo.CrashTestDummy1"
|
||||||
],
|
],
|
||||||
"talk":[
|
"talk":[
|
||||||
"org.freedesktop.Notifications"
|
"org.freedesktop.Notifications"
|
||||||
],
|
],
|
||||||
"own":[
|
"own":[
|
||||||
"moe.ophivana.CrashTestDummy.*",
|
"uk.gensokyo.CrashTestDummy.*",
|
||||||
"org.mpris.MediaPlayer2.moe.ophivana.CrashTestDummy.*"
|
"org.mpris.MediaPlayer2.uk.gensokyo.CrashTestDummy.*"
|
||||||
],
|
],
|
||||||
"call":{
|
"call":{
|
||||||
"org.freedesktop.portal.*":"*"
|
"org.freedesktop.portal.*":"*"
|
2
dist/install.sh
vendored
2
dist/install.sh
vendored
@ -2,6 +2,8 @@
|
|||||||
cd "$(dirname -- "$0")" || exit 1
|
cd "$(dirname -- "$0")" || exit 1
|
||||||
|
|
||||||
install -vDm0755 "bin/fortify" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fortify"
|
install -vDm0755 "bin/fortify" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fortify"
|
||||||
|
install -vDm0755 "bin/fpkg" "${FORTIFY_INSTALL_PREFIX}/usr/bin/fpkg"
|
||||||
|
|
||||||
install -vDm0755 "bin/fshim" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fshim"
|
install -vDm0755 "bin/fshim" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fshim"
|
||||||
install -vDm0755 "bin/finit" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/finit"
|
install -vDm0755 "bin/finit" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/finit"
|
||||||
install -vDm0755 "bin/fuserdb" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fuserdb"
|
install -vDm0755 "bin/fuserdb" "${FORTIFY_INSTALL_PREFIX}/usr/libexec/fortify/fuserdb"
|
||||||
|
1
dist/release.sh
vendored
1
dist/release.sh
vendored
@ -10,6 +10,7 @@ cp -rv "comp" "${out}"
|
|||||||
|
|
||||||
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
|
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
|
||||||
-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.Fortify=/usr/bin/fortify
|
||||||
-X git.gensokyo.uk/security/fortify/internal.Fsu=/usr/bin/fsu
|
-X git.gensokyo.uk/security/fortify/internal.Fsu=/usr/bin/fsu
|
||||||
-X git.gensokyo.uk/security/fortify/internal.Finit=/usr/libexec/fortify/finit
|
-X git.gensokyo.uk/security/fortify/internal.Finit=/usr/libexec/fortify/finit
|
||||||
-X main.Fmain=/usr/bin/fortify
|
-X main.Fmain=/usr/bin/fortify
|
||||||
|
12
flake.lock
generated
12
flake.lock
generated
@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1733951536,
|
"lastModified": 1735344290,
|
||||||
"narHash": "sha256-Zb5ZCa7Xj+0gy5XVXINTSr71fCfAv+IKtmIXNrykT54=",
|
"narHash": "sha256-oJDtWPH1oJT34RJK1FSWjwX4qcGOBRkcNQPD0EbSfNM=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "1318c3f3b068cdcea922fa7c1a0a1f0c96c22f5f",
|
"rev": "613691f285dad87694c2ba1c9e6298d04736292d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -23,11 +23,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1734298236,
|
"lastModified": 1735326919,
|
||||||
"narHash": "sha256-aWhhqY44xBjMoO9r5fyPp5u8tqUNWRZ/m/P+abMSs5c=",
|
"narHash": "sha256-BZlgs4l9CXAauo78giGCZdazMMk5VZNro7o5SHFUuyE=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "eb919d9300b6a18f8583f58aef16db458fbd7bec",
|
"rev": "8f0aa155aa29f7d2b471aa2ffd322745bf2b2036",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
14
flake.nix
14
flake.nix
@ -29,6 +29,20 @@
|
|||||||
{
|
{
|
||||||
nixosModules.fortify = import ./nixos.nix;
|
nixosModules.fortify = import ./nixos.nix;
|
||||||
|
|
||||||
|
buildPackage = forAllSystems (
|
||||||
|
system:
|
||||||
|
nixpkgsFor.${system}.callPackage (
|
||||||
|
import ./bundle.nix {
|
||||||
|
inherit
|
||||||
|
nixpkgsFor
|
||||||
|
system
|
||||||
|
nixpkgs
|
||||||
|
home-manager
|
||||||
|
;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
checks = forAllSystems (
|
checks = forAllSystems (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
|
125
fst/config.go
125
fst/config.go
@ -1,11 +1,7 @@
|
|||||||
package fst
|
package fst
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/dbus"
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/internal/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,37 +45,6 @@ type ConfinementConfig struct {
|
|||||||
Enablements system.Enablements `json:"enablements"`
|
Enablements system.Enablements `json:"enablements"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SandboxConfig describes resources made available to the sandbox.
|
|
||||||
type SandboxConfig struct {
|
|
||||||
// unix hostname within sandbox
|
|
||||||
Hostname string `json:"hostname,omitempty"`
|
|
||||||
// allow userns within sandbox
|
|
||||||
UserNS bool `json:"userns,omitempty"`
|
|
||||||
// share net namespace
|
|
||||||
Net bool `json:"net,omitempty"`
|
|
||||||
// share all devices
|
|
||||||
Dev bool `json:"dev,omitempty"`
|
|
||||||
// do not run in new session
|
|
||||||
NoNewSession bool `json:"no_new_session,omitempty"`
|
|
||||||
// map target user uid to privileged user uid in the user namespace
|
|
||||||
MapRealUID bool `json:"map_real_uid"`
|
|
||||||
// direct access to wayland socket
|
|
||||||
DirectWayland bool `json:"direct_wayland,omitempty"`
|
|
||||||
|
|
||||||
// final environment variables
|
|
||||||
Env map[string]string `json:"env"`
|
|
||||||
// sandbox host filesystem access
|
|
||||||
Filesystem []*FilesystemConfig `json:"filesystem"`
|
|
||||||
// symlinks created inside the sandbox
|
|
||||||
Link [][2]string `json:"symlink"`
|
|
||||||
// read-only /etc directory
|
|
||||||
Etc string `json:"etc,omitempty"`
|
|
||||||
// automatically set up /etc symlinks
|
|
||||||
AutoEtc bool `json:"auto_etc"`
|
|
||||||
// paths to override by mounting tmpfs over them
|
|
||||||
Override []string `json:"override"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExtraPermConfig struct {
|
type ExtraPermConfig struct {
|
||||||
Ensure bool `json:"ensure,omitempty"`
|
Ensure bool `json:"ensure,omitempty"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
@ -121,96 +86,6 @@ type FilesystemConfig struct {
|
|||||||
Must bool `json:"require,omitempty"`
|
Must bool `json:"require,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bwrap returns the address of the corresponding bwrap.Config to s.
|
|
||||||
// Note that remaining tmpfs entries must be queued by the caller prior to launch.
|
|
||||||
func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
|
|
||||||
if s == nil {
|
|
||||||
return nil, errors.New("nil sandbox config")
|
|
||||||
}
|
|
||||||
|
|
||||||
var uid int
|
|
||||||
if !s.MapRealUID {
|
|
||||||
uid = 65534
|
|
||||||
} else {
|
|
||||||
uid = os.Geteuid()
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := (&bwrap.Config{
|
|
||||||
Net: s.Net,
|
|
||||||
UserNS: s.UserNS,
|
|
||||||
Hostname: s.Hostname,
|
|
||||||
Clearenv: true,
|
|
||||||
SetEnv: s.Env,
|
|
||||||
NewSession: !s.NoNewSession,
|
|
||||||
DieWithParent: true,
|
|
||||||
AsInit: true,
|
|
||||||
|
|
||||||
// initialise map
|
|
||||||
Chmod: make(bwrap.ChmodConfig),
|
|
||||||
}).
|
|
||||||
SetUID(uid).SetGID(uid).
|
|
||||||
Procfs("/proc").
|
|
||||||
Tmpfs(Tmp, 4*1024)
|
|
||||||
|
|
||||||
if !s.Dev {
|
|
||||||
conf.DevTmpfs("/dev").Mqueue("/dev/mqueue")
|
|
||||||
} else {
|
|
||||||
conf.Bind("/dev", "/dev", false, true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.AutoEtc {
|
|
||||||
if s.Etc == "" {
|
|
||||||
conf.Dir("/etc")
|
|
||||||
} else {
|
|
||||||
conf.Bind(s.Etc, "/etc")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range s.Filesystem {
|
|
||||||
if c == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
src := c.Src
|
|
||||||
dest := c.Dst
|
|
||||||
if c.Dst == "" {
|
|
||||||
dest = c.Src
|
|
||||||
}
|
|
||||||
conf.Bind(src, dest, !c.Must, c.Write, c.Device)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, l := range s.Link {
|
|
||||||
conf.Symlink(l[0], l[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.AutoEtc {
|
|
||||||
etc := s.Etc
|
|
||||||
if etc == "" {
|
|
||||||
etc = "/etc"
|
|
||||||
}
|
|
||||||
conf.Bind(etc, Tmp+"/etc")
|
|
||||||
|
|
||||||
// link host /etc contents to prevent passwd/group from being overwritten
|
|
||||||
if d, err := os.ReadDir(etc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
for _, ent := range d {
|
|
||||||
name := ent.Name()
|
|
||||||
switch name {
|
|
||||||
case "passwd":
|
|
||||||
case "group":
|
|
||||||
|
|
||||||
case "mtab":
|
|
||||||
conf.Symlink("/proc/mounts", "/etc/"+name)
|
|
||||||
default:
|
|
||||||
conf.Symlink(Tmp+"/etc/"+name, "/etc/"+name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Template returns a fully populated instance of Config.
|
// Template returns a fully populated instance of Config.
|
||||||
func Template() *Config {
|
func Template() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
|
5
fst/info.go
Normal file
5
fst/info.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package fst
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
User int `json:"user"`
|
||||||
|
}
|
11
fst/path.go
Normal file
11
fst/path.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package fst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func deepContainsH(basepath, targpath string) (bool, error) {
|
||||||
|
rel, err := filepath.Rel(basepath, targpath)
|
||||||
|
return err == nil && rel != ".." && !strings.HasPrefix(rel, string([]byte{'.', '.', filepath.Separator})), err
|
||||||
|
}
|
85
fst/path_test.go
Normal file
85
fst/path_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package fst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeepContainsH(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
basepath string
|
||||||
|
targpath string
|
||||||
|
want bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "equal abs",
|
||||||
|
basepath: "/run",
|
||||||
|
targpath: "/run",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "equal rel",
|
||||||
|
basepath: "./run",
|
||||||
|
targpath: "run",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contains abs",
|
||||||
|
basepath: "/run",
|
||||||
|
targpath: "/run/dbus",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inverse contains abs",
|
||||||
|
basepath: "/run/dbus",
|
||||||
|
targpath: "/run",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contains rel",
|
||||||
|
basepath: "../run",
|
||||||
|
targpath: "../run/dbus",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inverse contains rel",
|
||||||
|
basepath: "../run/dbus",
|
||||||
|
targpath: "../run",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "weird abs",
|
||||||
|
basepath: "/run/dbus",
|
||||||
|
targpath: "/run/dbus/../current-system",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "weird rel",
|
||||||
|
basepath: "../run/dbus",
|
||||||
|
targpath: "../run/dbus/../current-system",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "invalid mix",
|
||||||
|
basepath: "/run",
|
||||||
|
targpath: "./run",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got, err := deepContainsH(tc.basepath, tc.targpath); (err != nil) != tc.wantErr {
|
||||||
|
t.Errorf("deepContainsH() error = %v, wantErr %v", err, tc.wantErr)
|
||||||
|
} else if got != tc.want {
|
||||||
|
t.Errorf("deepContainsH() = %v, want %v", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
222
fst/sandbox.go
Normal file
222
fst/sandbox.go
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
package fst
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/dbus"
|
||||||
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SandboxConfig describes resources made available to the sandbox.
|
||||||
|
type SandboxConfig struct {
|
||||||
|
// unix hostname within sandbox
|
||||||
|
Hostname string `json:"hostname,omitempty"`
|
||||||
|
// allow userns within sandbox
|
||||||
|
UserNS bool `json:"userns,omitempty"`
|
||||||
|
// share net namespace
|
||||||
|
Net bool `json:"net,omitempty"`
|
||||||
|
// share all devices
|
||||||
|
Dev bool `json:"dev,omitempty"`
|
||||||
|
// do not run in new session
|
||||||
|
NoNewSession bool `json:"no_new_session,omitempty"`
|
||||||
|
// map target user uid to privileged user uid in the user namespace
|
||||||
|
MapRealUID bool `json:"map_real_uid"`
|
||||||
|
// direct access to wayland socket
|
||||||
|
DirectWayland bool `json:"direct_wayland,omitempty"`
|
||||||
|
|
||||||
|
// final environment variables
|
||||||
|
Env map[string]string `json:"env"`
|
||||||
|
// sandbox host filesystem access
|
||||||
|
Filesystem []*FilesystemConfig `json:"filesystem"`
|
||||||
|
// symlinks created inside the sandbox
|
||||||
|
Link [][2]string `json:"symlink"`
|
||||||
|
// read-only /etc directory
|
||||||
|
Etc string `json:"etc,omitempty"`
|
||||||
|
// automatically set up /etc symlinks
|
||||||
|
AutoEtc bool `json:"auto_etc"`
|
||||||
|
// paths to override by mounting tmpfs over them
|
||||||
|
Override []string `json:"override"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bwrap returns the address of the corresponding bwrap.Config to s.
|
||||||
|
// Note that remaining tmpfs entries must be queued by the caller prior to launch.
|
||||||
|
func (s *SandboxConfig) Bwrap(os linux.System) (*bwrap.Config, error) {
|
||||||
|
if s == nil {
|
||||||
|
return nil, errors.New("nil sandbox config")
|
||||||
|
}
|
||||||
|
|
||||||
|
var uid int
|
||||||
|
if !s.MapRealUID {
|
||||||
|
uid = 65534
|
||||||
|
} else {
|
||||||
|
uid = os.Geteuid()
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := (&bwrap.Config{
|
||||||
|
Net: s.Net,
|
||||||
|
UserNS: s.UserNS,
|
||||||
|
Hostname: s.Hostname,
|
||||||
|
Clearenv: true,
|
||||||
|
SetEnv: s.Env,
|
||||||
|
|
||||||
|
/* this is only 4 KiB of memory on a 64-bit system,
|
||||||
|
permissive defaults on NixOS results in around 100 entries
|
||||||
|
so this capacity should eliminate copies for most setups */
|
||||||
|
Filesystem: make([]bwrap.FSBuilder, 0, 256),
|
||||||
|
|
||||||
|
NewSession: !s.NoNewSession,
|
||||||
|
DieWithParent: true,
|
||||||
|
AsInit: true,
|
||||||
|
|
||||||
|
// initialise unconditionally as Once cannot be justified
|
||||||
|
// for saving such a miniscule amount of memory
|
||||||
|
Chmod: make(bwrap.ChmodConfig),
|
||||||
|
}).
|
||||||
|
SetUID(uid).SetGID(uid).
|
||||||
|
Procfs("/proc").
|
||||||
|
Tmpfs(Tmp, 4*1024)
|
||||||
|
|
||||||
|
if !s.Dev {
|
||||||
|
conf.DevTmpfs("/dev").Mqueue("/dev/mqueue")
|
||||||
|
} else {
|
||||||
|
conf.Bind("/dev", "/dev", false, true, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.AutoEtc {
|
||||||
|
if s.Etc == "" {
|
||||||
|
conf.Dir("/etc")
|
||||||
|
} else {
|
||||||
|
conf.Bind(s.Etc, "/etc")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve paths and hide them if they're made available in the sandbox
|
||||||
|
var hidePaths []string
|
||||||
|
sc := os.Paths()
|
||||||
|
hidePaths = append(hidePaths, sc.RuntimePath, sc.SharePath)
|
||||||
|
_, systemBusAddr := dbus.Address()
|
||||||
|
if entries, err := dbus.Parse([]byte(systemBusAddr)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
// there is usually only one, do not preallocate
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.Method != "unix" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, pair := range entry.Values {
|
||||||
|
if pair[0] == "path" {
|
||||||
|
if path.IsAbs(pair[1]) {
|
||||||
|
// get parent dir of socket
|
||||||
|
dir := path.Dir(pair[1])
|
||||||
|
if dir == "." || dir == "/" {
|
||||||
|
fmsg.VPrintf("dbus socket %q is in an unusual location", pair[1])
|
||||||
|
}
|
||||||
|
hidePaths = append(hidePaths, dir)
|
||||||
|
} else {
|
||||||
|
fmsg.VPrintf("dbus socket %q is not absolute", pair[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hidePathMatch := make([]bool, len(hidePaths))
|
||||||
|
for i := range hidePaths {
|
||||||
|
if err := evalSymlinks(os, &hidePaths[i]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range s.Filesystem {
|
||||||
|
if c == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !path.IsAbs(c.Src) {
|
||||||
|
return nil, fmt.Errorf("src path %q is not absolute", c.Src)
|
||||||
|
}
|
||||||
|
|
||||||
|
dest := c.Dst
|
||||||
|
if c.Dst == "" {
|
||||||
|
dest = c.Src
|
||||||
|
} else if !path.IsAbs(dest) {
|
||||||
|
return nil, fmt.Errorf("dst path %q is not absolute", dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcH := c.Src
|
||||||
|
if err := evalSymlinks(os, &srcH); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range hidePaths {
|
||||||
|
// skip matched entries
|
||||||
|
if hidePathMatch[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := deepContainsH(srcH, hidePaths[i]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if ok {
|
||||||
|
hidePathMatch[i] = true
|
||||||
|
fmsg.VPrintf("hiding paths from %q", c.Src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.Bind(c.Src, dest, !c.Must, c.Write, c.Device)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide marked paths before setting up shares
|
||||||
|
for i, ok := range hidePathMatch {
|
||||||
|
if ok {
|
||||||
|
conf.Tmpfs(hidePaths[i], 8192)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, l := range s.Link {
|
||||||
|
conf.Symlink(l[0], l[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.AutoEtc {
|
||||||
|
etc := s.Etc
|
||||||
|
if etc == "" {
|
||||||
|
etc = "/etc"
|
||||||
|
}
|
||||||
|
conf.Bind(etc, Tmp+"/etc")
|
||||||
|
|
||||||
|
// link host /etc contents to prevent passwd/group from being overwritten
|
||||||
|
if d, err := os.ReadDir(etc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
for _, ent := range d {
|
||||||
|
name := ent.Name()
|
||||||
|
switch name {
|
||||||
|
case "passwd":
|
||||||
|
case "group":
|
||||||
|
|
||||||
|
case "mtab":
|
||||||
|
conf.Symlink("/proc/mounts", "/etc/"+name)
|
||||||
|
default:
|
||||||
|
conf.Symlink(Tmp+"/etc/"+name, "/etc/"+name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalSymlinks(os linux.System, v *string) error {
|
||||||
|
if p, err := os.EvalSymlinks(*v); err != nil {
|
||||||
|
if !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmsg.VPrintf("path %q does not yet exist", *v)
|
||||||
|
} else {
|
||||||
|
*v = p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -20,7 +20,7 @@ type bubblewrap struct {
|
|||||||
name string
|
name string
|
||||||
|
|
||||||
// bwrap pipes
|
// bwrap pipes
|
||||||
p *pipes
|
control *pipes
|
||||||
// sync pipe
|
// sync pipe
|
||||||
sync *os.File
|
sync *os.File
|
||||||
// returns an array of arguments passed directly
|
// returns an array of arguments passed directly
|
||||||
@ -29,7 +29,7 @@ type bubblewrap struct {
|
|||||||
|
|
||||||
// pipes received by the child
|
// pipes received by the child
|
||||||
// nil if no pipes are required
|
// nil if no pipes are required
|
||||||
cp *pipes
|
controlPt *pipes
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
*exec.Cmd
|
*exec.Cmd
|
||||||
@ -39,7 +39,7 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
|||||||
b.lock.Lock()
|
b.lock.Lock()
|
||||||
defer b.lock.Unlock()
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
if ready != nil && b.cp == nil {
|
if ready != nil && b.controlPt == nil {
|
||||||
panic("attempted to start with status monitoring on a bwrap child initialised without pipes")
|
panic("attempted to start with status monitoring on a bwrap child initialised without pipes")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,16 +50,16 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prepare bwrap pipe and args
|
// prepare bwrap pipe and args
|
||||||
if argsFD, _, err := b.p.prepareCmd(b.Cmd); err != nil {
|
if argsFD, _, err := b.control.prepareCmd(b.Cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
b.Cmd.Args = append(b.Cmd.Args, "--args", strconv.Itoa(argsFD), "--", b.name)
|
b.Cmd.Args = append(b.Cmd.Args, "--args", strconv.Itoa(argsFD), "--", b.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare child args and pipes if enabled
|
// prepare child args and pipes if enabled
|
||||||
if b.cp != nil {
|
if b.controlPt != nil {
|
||||||
b.cp.ready = ready
|
b.controlPt.ready = ready
|
||||||
if argsFD, statFD, err := b.cp.prepareCmd(b.Cmd); err != nil {
|
if argsFD, statFD, err := b.controlPt.prepareCmd(b.Cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
b.Cmd.Args = append(b.Cmd.Args, b.argF(argsFD, statFD)...)
|
b.Cmd.Args = append(b.Cmd.Args, b.argF(argsFD, statFD)...)
|
||||||
@ -70,7 +70,7 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
|||||||
|
|
||||||
if ready != nil {
|
if ready != nil {
|
||||||
b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=1")
|
b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=1")
|
||||||
} else if b.cp != nil {
|
} else if b.controlPt != nil {
|
||||||
b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=0")
|
b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=0")
|
||||||
} else {
|
} else {
|
||||||
b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=-1")
|
b.Cmd.Env = append(b.Cmd.Env, FortifyHelper+"=1", FortifyStatus+"=-1")
|
||||||
@ -85,13 +85,13 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// write bwrap args first
|
// write bwrap args first
|
||||||
if err := b.p.readyWriteArgs(); err != nil {
|
if err := b.control.readyWriteArgs(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// write child args if enabled
|
// write child args if enabled
|
||||||
if b.cp != nil {
|
if b.controlPt != nil {
|
||||||
if err := b.cp.readyWriteArgs(); err != nil {
|
if err := b.controlPt.readyWriteArgs(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,11 +100,11 @@ func (b *bubblewrap) StartNotify(ready chan error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *bubblewrap) Close() error {
|
func (b *bubblewrap) Close() error {
|
||||||
if b.cp == nil {
|
if b.controlPt == nil {
|
||||||
panic("attempted to close bwrap child initialised without pipes")
|
panic("attempted to close bwrap child initialised without pipes")
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.cp.closeStatus()
|
return b.controlPt.closeStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bubblewrap) Start() error {
|
func (b *bubblewrap) Start() error {
|
||||||
@ -136,14 +136,14 @@ func NewBwrap(conf *bwrap.Config, wt io.WriterTo, name string, argF func(argsFD,
|
|||||||
if args, err := NewCheckedArgs(conf.Args()); err != nil {
|
if args, err := NewCheckedArgs(conf.Args()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
b.p = &pipes{args: args}
|
b.control = &pipes{args: args}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.sync = conf.Sync()
|
b.sync = conf.Sync()
|
||||||
b.argF = argF
|
b.argF = argF
|
||||||
b.name = name
|
b.name = name
|
||||||
if wt != nil {
|
if wt != nil {
|
||||||
b.cp = &pipes{args: wt}
|
b.controlPt = &pipes{args: wt}
|
||||||
}
|
}
|
||||||
b.Cmd = execCommand(BubblewrapName)
|
b.Cmd = execCommand(BubblewrapName)
|
||||||
|
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package bwrap
|
|
||||||
|
|
||||||
const (
|
|
||||||
Tmpfs = iota
|
|
||||||
Dir
|
|
||||||
Symlink
|
|
||||||
)
|
|
||||||
|
|
||||||
var awkwardArgs = [...]string{
|
|
||||||
Tmpfs: "--tmpfs",
|
|
||||||
Dir: "--dir",
|
|
||||||
Symlink: "--symlink",
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package bwrap
|
|
||||||
|
|
||||||
const (
|
|
||||||
UnshareAll = iota
|
|
||||||
UnshareUser
|
|
||||||
UnshareIPC
|
|
||||||
UnsharePID
|
|
||||||
UnshareNet
|
|
||||||
UnshareUTS
|
|
||||||
UnshareCGroup
|
|
||||||
ShareNet
|
|
||||||
|
|
||||||
UserNS
|
|
||||||
Clearenv
|
|
||||||
|
|
||||||
NewSession
|
|
||||||
DieWithParent
|
|
||||||
AsInit
|
|
||||||
)
|
|
||||||
|
|
||||||
var boolArgs = [...][]string{
|
|
||||||
UnshareAll: {"--unshare-all", "--unshare-user"},
|
|
||||||
UnshareUser: {"--unshare-user"},
|
|
||||||
UnshareIPC: {"--unshare-ipc"},
|
|
||||||
UnsharePID: {"--unshare-pid"},
|
|
||||||
UnshareNet: {"--unshare-net"},
|
|
||||||
UnshareUTS: {"--unshare-uts"},
|
|
||||||
UnshareCGroup: {"--unshare-cgroup"},
|
|
||||||
ShareNet: {"--share-net"},
|
|
||||||
|
|
||||||
UserNS: {"--disable-userns", "--assert-userns-disabled"},
|
|
||||||
Clearenv: {"--clearenv"},
|
|
||||||
|
|
||||||
NewSession: {"--new-session"},
|
|
||||||
DieWithParent: {"--die-with-parent"},
|
|
||||||
AsInit: {"--as-pid-1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) boolArgs() Builder {
|
|
||||||
b := boolArg{
|
|
||||||
UserNS: !c.UserNS,
|
|
||||||
Clearenv: c.Clearenv,
|
|
||||||
|
|
||||||
NewSession: c.NewSession,
|
|
||||||
DieWithParent: c.DieWithParent,
|
|
||||||
AsInit: c.AsInit,
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Unshare == nil {
|
|
||||||
b[UnshareAll] = true
|
|
||||||
b[ShareNet] = c.Net
|
|
||||||
} else {
|
|
||||||
b[UnshareUser] = c.Unshare.User
|
|
||||||
b[UnshareIPC] = c.Unshare.IPC
|
|
||||||
b[UnsharePID] = c.Unshare.PID
|
|
||||||
b[UnshareNet] = c.Unshare.Net
|
|
||||||
b[UnshareUTS] = c.Unshare.UTS
|
|
||||||
b[UnshareCGroup] = c.Unshare.CGroup
|
|
||||||
}
|
|
||||||
|
|
||||||
return &b
|
|
||||||
}
|
|
||||||
|
|
||||||
type boolArg [len(boolArgs)]bool
|
|
||||||
|
|
||||||
func (b *boolArg) Len() (l int) {
|
|
||||||
for i, v := range b {
|
|
||||||
if v {
|
|
||||||
l += len(boolArgs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *boolArg) Append(args *[]string) {
|
|
||||||
for i, v := range b {
|
|
||||||
if v {
|
|
||||||
*args = append(*args, boolArgs[i]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package bwrap
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
|
|
||||||
const (
|
|
||||||
UID = iota
|
|
||||||
GID
|
|
||||||
Perms
|
|
||||||
Size
|
|
||||||
)
|
|
||||||
|
|
||||||
var intArgs = [...]string{
|
|
||||||
UID: "--uid",
|
|
||||||
GID: "--gid",
|
|
||||||
Perms: "--perms",
|
|
||||||
Size: "--size",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) intArgs() Builder {
|
|
||||||
// Arg types:
|
|
||||||
// Perms
|
|
||||||
// are handled by the sequential builder
|
|
||||||
|
|
||||||
return &intArg{
|
|
||||||
UID: c.UID,
|
|
||||||
GID: c.GID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type intArg [len(intArgs)]*int
|
|
||||||
|
|
||||||
func (n *intArg) Len() (l int) {
|
|
||||||
for _, v := range n {
|
|
||||||
if v != nil {
|
|
||||||
l += 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *intArg) Append(args *[]string) {
|
|
||||||
for i, v := range n {
|
|
||||||
if v != nil {
|
|
||||||
*args = append(*args, intArgs[i], strconv.Itoa(*v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package bwrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SetEnv = iota
|
|
||||||
|
|
||||||
Bind
|
|
||||||
BindTry
|
|
||||||
DevBind
|
|
||||||
DevBindTry
|
|
||||||
ROBind
|
|
||||||
ROBindTry
|
|
||||||
|
|
||||||
Chmod
|
|
||||||
)
|
|
||||||
|
|
||||||
var pairArgs = [...]string{
|
|
||||||
SetEnv: "--setenv",
|
|
||||||
|
|
||||||
Bind: "--bind",
|
|
||||||
BindTry: "--bind-try",
|
|
||||||
DevBind: "--dev-bind",
|
|
||||||
DevBindTry: "--dev-bind-try",
|
|
||||||
ROBind: "--ro-bind",
|
|
||||||
ROBindTry: "--ro-bind-try",
|
|
||||||
|
|
||||||
Chmod: "--chmod",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) pairArgs() Builder {
|
|
||||||
var n pairArg
|
|
||||||
n[SetEnv] = make([][2]string, len(c.SetEnv))
|
|
||||||
keys := make([]string, 0, len(c.SetEnv))
|
|
||||||
for k := range c.SetEnv {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
slices.Sort(keys)
|
|
||||||
for i, k := range keys {
|
|
||||||
n[SetEnv][i] = [2]string{k, c.SetEnv[k]}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arg types:
|
|
||||||
// Bind
|
|
||||||
// BindTry
|
|
||||||
// DevBind
|
|
||||||
// DevBindTry
|
|
||||||
// ROBind
|
|
||||||
// ROBindTry
|
|
||||||
// Chmod
|
|
||||||
// are handled by the sequential builder
|
|
||||||
|
|
||||||
return &n
|
|
||||||
}
|
|
||||||
|
|
||||||
type pairArg [len(pairArgs)][][2]string
|
|
||||||
|
|
||||||
func (p *pairArg) Len() (l int) {
|
|
||||||
for _, v := range p {
|
|
||||||
l += len(v) * 3
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pairArg) Append(args *[]string) {
|
|
||||||
for i, arg := range p {
|
|
||||||
for _, v := range arg {
|
|
||||||
*args = append(*args, pairArgs[i], v[0], v[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package bwrap
|
|
||||||
|
|
||||||
const (
|
|
||||||
Hostname = iota
|
|
||||||
Chdir
|
|
||||||
UnsetEnv
|
|
||||||
LockFile
|
|
||||||
|
|
||||||
RemountRO
|
|
||||||
Procfs
|
|
||||||
DevTmpfs
|
|
||||||
Mqueue
|
|
||||||
)
|
|
||||||
|
|
||||||
var stringArgs = [...]string{
|
|
||||||
Hostname: "--hostname",
|
|
||||||
Chdir: "--chdir",
|
|
||||||
UnsetEnv: "--unsetenv",
|
|
||||||
LockFile: "--lock-file",
|
|
||||||
|
|
||||||
RemountRO: "--remount-ro",
|
|
||||||
Procfs: "--proc",
|
|
||||||
DevTmpfs: "--dev",
|
|
||||||
Mqueue: "--mqueue",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) stringArgs() Builder {
|
|
||||||
n := stringArg{
|
|
||||||
UnsetEnv: c.UnsetEnv,
|
|
||||||
LockFile: c.LockFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Hostname != "" {
|
|
||||||
n[Hostname] = []string{c.Hostname}
|
|
||||||
}
|
|
||||||
if c.Chdir != "" {
|
|
||||||
n[Chdir] = []string{c.Chdir}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Arg types:
|
|
||||||
// RemountRO
|
|
||||||
// Procfs
|
|
||||||
// DevTmpfs
|
|
||||||
// Mqueue
|
|
||||||
// are handled by the sequential builder
|
|
||||||
|
|
||||||
return &n
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringArg [len(stringArgs)][]string
|
|
||||||
|
|
||||||
func (s *stringArg) Len() (l int) {
|
|
||||||
for _, arg := range s {
|
|
||||||
l += len(arg) * 2
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *stringArg) Append(args *[]string) {
|
|
||||||
for i, arg := range s {
|
|
||||||
for _, v := range arg {
|
|
||||||
*args = append(*args, stringArgs[i], v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -39,46 +39,60 @@ func (c *Config) Bind(src, dest string, opts ...bool) *Config {
|
|||||||
|
|
||||||
if dev {
|
if dev {
|
||||||
if try {
|
if try {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[DevBindTry], src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{DevBindTry.Unwrap(), src, dest})
|
||||||
} else {
|
} else {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[DevBind], src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{DevBind.Unwrap(), src, dest})
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
} else if write {
|
} else if write {
|
||||||
if try {
|
if try {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[BindTry], src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{BindTry.Unwrap(), src, dest})
|
||||||
} else {
|
} else {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[Bind], src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{Bind.Unwrap(), src, dest})
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
} else {
|
} else {
|
||||||
if try {
|
if try {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[ROBindTry], src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{ROBindTry.Unwrap(), src, dest})
|
||||||
} else {
|
} else {
|
||||||
c.Filesystem = append(c.Filesystem, &pairF{pairArgs[ROBind], src, dest})
|
c.Filesystem = append(c.Filesystem, &pairF{ROBind.Unwrap(), src, dest})
|
||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dir create dir in sandbox
|
||||||
|
// (--dir DEST)
|
||||||
|
func (c *Config) Dir(dest string) *Config {
|
||||||
|
c.Filesystem = append(c.Filesystem, &stringF{Dir.Unwrap(), dest})
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// RemountRO remount path as readonly; does not recursively remount
|
// RemountRO remount path as readonly; does not recursively remount
|
||||||
// (--remount-ro DEST)
|
// (--remount-ro DEST)
|
||||||
func (c *Config) RemountRO(dest string) *Config {
|
func (c *Config) RemountRO(dest string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{stringArgs[RemountRO], dest})
|
c.Filesystem = append(c.Filesystem, &stringF{RemountRO.Unwrap(), dest})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Procfs mount new procfs in sandbox
|
// Procfs mount new procfs in sandbox
|
||||||
// (--proc DEST)
|
// (--proc DEST)
|
||||||
func (c *Config) Procfs(dest string) *Config {
|
func (c *Config) Procfs(dest string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{stringArgs[Procfs], dest})
|
c.Filesystem = append(c.Filesystem, &stringF{Procfs.Unwrap(), dest})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// DevTmpfs mount new dev in sandbox
|
// DevTmpfs mount new dev in sandbox
|
||||||
// (--dev DEST)
|
// (--dev DEST)
|
||||||
func (c *Config) DevTmpfs(dest string) *Config {
|
func (c *Config) DevTmpfs(dest string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{stringArgs[DevTmpfs], dest})
|
c.Filesystem = append(c.Filesystem, &stringF{DevTmpfs.Unwrap(), dest})
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mqueue mount new mqueue in sandbox
|
||||||
|
// (--mqueue DEST)
|
||||||
|
func (c *Config) Mqueue(dest string) *Config {
|
||||||
|
c.Filesystem = append(c.Filesystem, &stringF{Mqueue.Unwrap(), dest})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,17 +110,28 @@ func (c *Config) Tmpfs(dest string, size int, perm ...os.FileMode) *Config {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mqueue mount new mqueue in sandbox
|
// Overlay mount overlayfs on DEST, with writes going to an invisible tmpfs
|
||||||
// (--mqueue DEST)
|
// (--tmp-overlay DEST)
|
||||||
func (c *Config) Mqueue(dest string) *Config {
|
func (c *Config) Overlay(dest string, src ...string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{stringArgs[Mqueue], dest})
|
c.Filesystem = append(c.Filesystem, &OverlayConfig{Src: src, Dest: dest})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dir create dir in sandbox
|
// Join mount overlayfs read-only on DEST
|
||||||
// (--dir DEST)
|
// (--ro-overlay DEST)
|
||||||
func (c *Config) Dir(dest string) *Config {
|
func (c *Config) Join(dest string, src ...string) *Config {
|
||||||
c.Filesystem = append(c.Filesystem, &stringF{awkwardArgs[Dir], dest})
|
c.Filesystem = append(c.Filesystem, &OverlayConfig{Src: src, Dest: dest, Persist: new([2]string)})
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist mount overlayfs on DEST, with RWSRC as the host path for writes and
|
||||||
|
// WORKDIR an empty directory on the same filesystem as RWSRC
|
||||||
|
// (--overlay RWSRC WORKDIR DEST)
|
||||||
|
func (c *Config) Persist(dest, rwsrc, workdir string, src ...string) *Config {
|
||||||
|
if rwsrc == "" || workdir == "" {
|
||||||
|
panic("persist called without required paths")
|
||||||
|
}
|
||||||
|
c.Filesystem = append(c.Filesystem, &OverlayConfig{Src: src, Dest: dest, Persist: &[2]string{rwsrc, workdir}})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +1,9 @@
|
|||||||
package bwrap
|
package bwrap
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
gob.Register(new(PermConfig[SymlinkConfig]))
|
|
||||||
gob.Register(new(PermConfig[*TmpfsConfig]))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// unshare every namespace we support by default if nil
|
// unshare every namespace we support by default if nil
|
||||||
// (--unshare-all)
|
// (--unshare-all)
|
||||||
@ -52,7 +45,7 @@ type Config struct {
|
|||||||
LockFile []string `json:"lock_file,omitempty"`
|
LockFile []string `json:"lock_file,omitempty"`
|
||||||
|
|
||||||
// ordered filesystem args
|
// ordered filesystem args
|
||||||
Filesystem []FSBuilder
|
Filesystem []FSBuilder `json:"filesystem,omitempty"`
|
||||||
|
|
||||||
// change permissions (must already exist)
|
// change permissions (must already exist)
|
||||||
// (--chmod OCTAL PATH)
|
// (--chmod OCTAL PATH)
|
||||||
@ -78,6 +71,8 @@ type Config struct {
|
|||||||
--userns FD Use this user namespace (cannot combine with --unshare-user)
|
--userns FD Use this user namespace (cannot combine with --unshare-user)
|
||||||
--userns2 FD After setup switch to this user namespace, only useful with --userns
|
--userns2 FD After setup switch to this user namespace, only useful with --userns
|
||||||
--pidns FD Use this pid namespace (as parent namespace if using --unshare-pid)
|
--pidns FD Use this pid namespace (as parent namespace if using --unshare-pid)
|
||||||
|
--bind-fd FD DEST Bind open directory or path fd on DEST
|
||||||
|
--ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST
|
||||||
--exec-label LABEL Exec label for the sandbox
|
--exec-label LABEL Exec label for the sandbox
|
||||||
--file-label LABEL File label for temporary sandbox content
|
--file-label LABEL File label for temporary sandbox content
|
||||||
--file FD DEST Copy from FD to destination DEST
|
--file FD DEST Copy from FD to destination DEST
|
||||||
@ -121,85 +116,3 @@ type UnshareConfig struct {
|
|||||||
// create new cgroup namespace
|
// create new cgroup namespace
|
||||||
CGroup bool `json:"cgroup"`
|
CGroup bool `json:"cgroup"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PermConfig[T FSBuilder] struct {
|
|
||||||
// set permissions of next argument
|
|
||||||
// (--perms OCTAL)
|
|
||||||
Mode *os.FileMode `json:"mode,omitempty"`
|
|
||||||
// path to get the new permission
|
|
||||||
// (--bind-data, --file, etc.)
|
|
||||||
Inner T `json:"path"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PermConfig[T]) Path() string {
|
|
||||||
return p.Inner.Path()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PermConfig[T]) Len() int {
|
|
||||||
if p.Mode != nil {
|
|
||||||
return p.Inner.Len() + 2
|
|
||||||
} else {
|
|
||||||
return p.Inner.Len()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PermConfig[T]) Append(args *[]string) {
|
|
||||||
if p.Mode != nil {
|
|
||||||
*args = append(*args, intArgs[Perms], strconv.FormatInt(int64(*p.Mode), 8))
|
|
||||||
}
|
|
||||||
p.Inner.Append(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
type TmpfsConfig struct {
|
|
||||||
// set size of tmpfs
|
|
||||||
// (--size BYTES)
|
|
||||||
Size int `json:"size,omitempty"`
|
|
||||||
// mount point of new tmpfs
|
|
||||||
// (--tmpfs DEST)
|
|
||||||
Dir string `json:"dir"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TmpfsConfig) Path() string {
|
|
||||||
return t.Dir
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TmpfsConfig) Len() int {
|
|
||||||
if t.Size > 0 {
|
|
||||||
return 4
|
|
||||||
} else {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TmpfsConfig) Append(args *[]string) {
|
|
||||||
if t.Size > 0 {
|
|
||||||
*args = append(*args, intArgs[Size], strconv.Itoa(t.Size))
|
|
||||||
}
|
|
||||||
*args = append(*args, awkwardArgs[Tmpfs], t.Dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
type SymlinkConfig [2]string
|
|
||||||
|
|
||||||
func (s SymlinkConfig) Path() string {
|
|
||||||
return s[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SymlinkConfig) Len() int {
|
|
||||||
return 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SymlinkConfig) Append(args *[]string) {
|
|
||||||
*args = append(*args, awkwardArgs[Symlink], s[0], s[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChmodConfig map[string]os.FileMode
|
|
||||||
|
|
||||||
func (c ChmodConfig) Len() int {
|
|
||||||
return len(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c ChmodConfig) Append(args *[]string) {
|
|
||||||
for path, mode := range c {
|
|
||||||
*args = append(*args, pairArgs[Chmod], strconv.FormatInt(int64(mode), 8), path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,19 +1,176 @@
|
|||||||
package bwrap
|
package bwrap_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.gensokyo.uk/security/fortify/helper/bwrap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfig_Args(t *testing.T) {
|
func TestConfig_Args(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
conf *Config
|
conf *bwrap.Config
|
||||||
want []string
|
want []string
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "bind",
|
||||||
|
conf: (new(bwrap.Config)).
|
||||||
|
Bind("/etc", "/.fortify/etc").
|
||||||
|
Bind("/etc", "/.fortify/etc", true).
|
||||||
|
Bind("/run", "/.fortify/run", false, true).
|
||||||
|
Bind("/sys/devices", "/.fortify/sys/devices", true, true).
|
||||||
|
Bind("/dev/dri", "/.fortify/dev/dri", false, true, true).
|
||||||
|
Bind("/dev/dri", "/.fortify/dev/dri", true, true, true),
|
||||||
|
want: []string{
|
||||||
|
"--unshare-all", "--unshare-user",
|
||||||
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
|
// Bind("/etc", "/.fortify/etc")
|
||||||
|
"--ro-bind", "/etc", "/.fortify/etc",
|
||||||
|
// Bind("/etc", "/.fortify/etc", true)
|
||||||
|
"--ro-bind-try", "/etc", "/.fortify/etc",
|
||||||
|
// Bind("/run", "/.fortify/run", false, true)
|
||||||
|
"--bind", "/run", "/.fortify/run",
|
||||||
|
// Bind("/sys/devices", "/.fortify/sys/devices", true, true)
|
||||||
|
"--bind-try", "/sys/devices", "/.fortify/sys/devices",
|
||||||
|
// Bind("/dev/dri", "/.fortify/dev/dri", false, true, true)
|
||||||
|
"--dev-bind", "/dev/dri", "/.fortify/dev/dri",
|
||||||
|
// Bind("/dev/dri", "/.fortify/dev/dri", true, true, true)
|
||||||
|
"--dev-bind-try", "/dev/dri", "/.fortify/dev/dri",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dir remount-ro proc dev mqueue",
|
||||||
|
conf: (new(bwrap.Config)).
|
||||||
|
Dir("/.fortify").
|
||||||
|
RemountRO("/home").
|
||||||
|
Procfs("/proc").
|
||||||
|
DevTmpfs("/dev").
|
||||||
|
Mqueue("/dev/mqueue"),
|
||||||
|
want: []string{
|
||||||
|
"--unshare-all", "--unshare-user",
|
||||||
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
|
// Dir("/.fortify")
|
||||||
|
"--dir", "/.fortify",
|
||||||
|
// RemountRO("/home")
|
||||||
|
"--remount-ro", "/home",
|
||||||
|
// Procfs("/proc")
|
||||||
|
"--proc", "/proc",
|
||||||
|
// DevTmpfs("/dev")
|
||||||
|
"--dev", "/dev",
|
||||||
|
// Mqueue("/dev/mqueue")
|
||||||
|
"--mqueue", "/dev/mqueue",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tmpfs",
|
||||||
|
conf: (new(bwrap.Config)).
|
||||||
|
Tmpfs("/run/user", 8192).
|
||||||
|
Tmpfs("/run/dbus", 8192, 0755),
|
||||||
|
want: []string{
|
||||||
|
"--unshare-all", "--unshare-user",
|
||||||
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
|
// Tmpfs("/run/user", 8192)
|
||||||
|
"--size", "8192", "--tmpfs", "/run/user",
|
||||||
|
// Tmpfs("/run/dbus", 8192, 0755)
|
||||||
|
"--perms", "755", "--size", "8192", "--tmpfs", "/run/dbus",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "symlink",
|
||||||
|
conf: (new(bwrap.Config)).
|
||||||
|
Symlink("/.fortify/sbin/init", "/sbin/init").
|
||||||
|
Symlink("/.fortify/sbin/init", "/sbin/init", 0755),
|
||||||
|
want: []string{
|
||||||
|
"--unshare-all", "--unshare-user",
|
||||||
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
|
// Symlink("/.fortify/sbin/init", "/sbin/init")
|
||||||
|
"--symlink", "/.fortify/sbin/init", "/sbin/init",
|
||||||
|
// Symlink("/.fortify/sbin/init", "/sbin/init", 0755)
|
||||||
|
"--perms", "755", "--symlink", "/.fortify/sbin/init", "/sbin/init",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "overlayfs",
|
||||||
|
conf: (new(bwrap.Config)).
|
||||||
|
Overlay("/etc", "/etc").
|
||||||
|
Join("/.fortify/bin", "/bin", "/usr/bin", "/usr/local/bin").
|
||||||
|
Persist("/nix", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/data/app/org.chromium.Chromium/nix"),
|
||||||
|
want: []string{
|
||||||
|
"--unshare-all", "--unshare-user",
|
||||||
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
|
// Overlay("/etc", "/etc")
|
||||||
|
"--overlay-src", "/etc", "--tmp-overlay", "/etc",
|
||||||
|
// Join("/.fortify/bin", "/bin", "/usr/bin", "/usr/local/bin")
|
||||||
|
"--overlay-src", "/bin", "--overlay-src", "/usr/bin",
|
||||||
|
"--overlay-src", "/usr/local/bin", "--ro-overlay", "/.fortify/bin",
|
||||||
|
// Persist("/nix", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/data/app/org.chromium.Chromium/nix")
|
||||||
|
"--overlay-src", "/data/app/org.chromium.Chromium/nix",
|
||||||
|
"--overlay", "/data/data/org.chromium.Chromium/overlay/rwsrc", "/data/data/org.chromium.Chromium/workdir", "/nix",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unshare",
|
||||||
|
conf: &bwrap.Config{Unshare: &bwrap.UnshareConfig{
|
||||||
|
User: false,
|
||||||
|
IPC: false,
|
||||||
|
PID: false,
|
||||||
|
Net: false,
|
||||||
|
UTS: false,
|
||||||
|
CGroup: false,
|
||||||
|
}},
|
||||||
|
want: []string{"--disable-userns", "--assert-userns-disabled"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "uid gid sync",
|
||||||
|
conf: (new(bwrap.Config)).
|
||||||
|
SetUID(1971).
|
||||||
|
SetGID(100).
|
||||||
|
SetSync(os.Stdin),
|
||||||
|
want: []string{
|
||||||
|
"--unshare-all", "--unshare-user",
|
||||||
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
|
// SetUID(1971)
|
||||||
|
"--uid", "1971",
|
||||||
|
// SetGID(100)
|
||||||
|
"--gid", "100",
|
||||||
|
// SetSync(os.Stdin)
|
||||||
|
// this is set when the process is created
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hostname chdir setenv unsetenv lockfile chmod",
|
||||||
|
conf: &bwrap.Config{
|
||||||
|
Hostname: "fortify",
|
||||||
|
Chdir: "/.fortify",
|
||||||
|
SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"},
|
||||||
|
UnsetEnv: []string{"HOME", "HOST"},
|
||||||
|
LockFile: []string{"/.fortify/lock"},
|
||||||
|
Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755},
|
||||||
|
},
|
||||||
|
want: []string{
|
||||||
|
"--unshare-all", "--unshare-user",
|
||||||
|
"--disable-userns", "--assert-userns-disabled",
|
||||||
|
// Hostname: "fortify"
|
||||||
|
"--hostname", "fortify",
|
||||||
|
// Chdir: "/.fortify"
|
||||||
|
"--chdir", "/.fortify",
|
||||||
|
// UnsetEnv: []string{"HOME", "HOST"}
|
||||||
|
"--unsetenv", "HOME",
|
||||||
|
"--unsetenv", "HOST",
|
||||||
|
// LockFile: []string{"/.fortify/lock"},
|
||||||
|
"--lock-file", "/.fortify/lock",
|
||||||
|
// SetEnv: map[string]string{"FORTIFY_INIT": "/.fortify/sbin/init"}
|
||||||
|
"--setenv", "FORTIFY_INIT", "/.fortify/sbin/init",
|
||||||
|
// Chmod: map[string]os.FileMode{"/.fortify/sbin/init": 0755}
|
||||||
|
"--chmod", "755", "/.fortify/sbin/init",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: "xdg-dbus-proxy constraint sample",
|
name: "xdg-dbus-proxy constraint sample",
|
||||||
conf: (&Config{
|
conf: (&bwrap.Config{
|
||||||
Unshare: nil,
|
Unshare: nil,
|
||||||
UserNS: false,
|
UserNS: false,
|
||||||
Clearenv: true,
|
Clearenv: true,
|
||||||
@ -69,148 +226,6 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
"--ro-bind", "/etc", "/etc",
|
"--ro-bind", "/etc", "/etc",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "fortify permissive default nixos",
|
|
||||||
conf: (&Config{
|
|
||||||
Unshare: nil,
|
|
||||||
Net: true,
|
|
||||||
UserNS: true,
|
|
||||||
Clearenv: true,
|
|
||||||
SetEnv: map[string]string{
|
|
||||||
"HOME": "/home/chronos",
|
|
||||||
"TERM": "xterm-256color",
|
|
||||||
"FORTIFY_INIT": "3",
|
|
||||||
"XDG_RUNTIME_DIR": "/run/user/150",
|
|
||||||
"XDG_SESSION_CLASS": "user",
|
|
||||||
"XDG_SESSION_TYPE": "tty",
|
|
||||||
"SHELL": "/run/current-system/sw/bin/zsh",
|
|
||||||
"USER": "chronos",
|
|
||||||
},
|
|
||||||
DieWithParent: true,
|
|
||||||
AsInit: true,
|
|
||||||
}).SetUID(65534).SetGID(65534).
|
|
||||||
Procfs("/proc").DevTmpfs("/dev").Mqueue("/dev/mqueue").
|
|
||||||
Bind("/bin", "/bin", false, true).
|
|
||||||
Bind("/boot", "/boot", false, true).
|
|
||||||
Bind("/etc", "/etc", false, true).
|
|
||||||
Bind("/home", "/home", false, true).
|
|
||||||
Bind("/lib", "/lib", false, true).
|
|
||||||
Bind("/lib64", "/lib64", false, true).
|
|
||||||
Bind("/nix", "/nix", false, true).
|
|
||||||
Bind("/root", "/root", false, true).
|
|
||||||
Bind("/srv", "/srv", false, true).
|
|
||||||
Bind("/sys", "/sys", false, true).
|
|
||||||
Bind("/usr", "/usr", false, true).
|
|
||||||
Bind("/var", "/var", false, true).
|
|
||||||
Bind("/run/NetworkManager", "/run/NetworkManager", false, true).
|
|
||||||
Bind("/run/agetty.reload", "/run/agetty.reload", false, true).
|
|
||||||
Bind("/run/binfmt", "/run/binfmt", false, true).
|
|
||||||
Bind("/run/booted-system", "/run/booted-system", false, true).
|
|
||||||
Bind("/run/credentials", "/run/credentials", false, true).
|
|
||||||
Bind("/run/cryptsetup", "/run/cryptsetup", false, true).
|
|
||||||
Bind("/run/current-system", "/run/current-system", false, true).
|
|
||||||
Bind("/run/host", "/run/host", false, true).
|
|
||||||
Bind("/run/keys", "/run/keys", false, true).
|
|
||||||
Bind("/run/libvirt", "/run/libvirt", false, true).
|
|
||||||
Bind("/run/libvirtd.pid", "/run/libvirtd.pid", false, true).
|
|
||||||
Bind("/run/lock", "/run/lock", false, true).
|
|
||||||
Bind("/run/log", "/run/log", false, true).
|
|
||||||
Bind("/run/lvm", "/run/lvm", false, true).
|
|
||||||
Bind("/run/mount", "/run/mount", false, true).
|
|
||||||
Bind("/run/nginx", "/run/nginx", false, true).
|
|
||||||
Bind("/run/nscd", "/run/nscd", false, true).
|
|
||||||
Bind("/run/opengl-driver", "/run/opengl-driver", false, true).
|
|
||||||
Bind("/run/pppd", "/run/pppd", false, true).
|
|
||||||
Bind("/run/resolvconf", "/run/resolvconf", false, true).
|
|
||||||
Bind("/run/sddm", "/run/sddm", false, true).
|
|
||||||
Bind("/run/syncoid", "/run/syncoid", false, true).
|
|
||||||
Bind("/run/systemd", "/run/systemd", false, true).
|
|
||||||
Bind("/run/tmpfiles.d", "/run/tmpfiles.d", false, true).
|
|
||||||
Bind("/run/udev", "/run/udev", false, true).
|
|
||||||
Bind("/run/udisks2", "/run/udisks2", false, true).
|
|
||||||
Bind("/run/utmp", "/run/utmp", false, true).
|
|
||||||
Bind("/run/virtlogd.pid", "/run/virtlogd.pid", false, true).
|
|
||||||
Bind("/run/wrappers", "/run/wrappers", false, true).
|
|
||||||
Bind("/run/zed.pid", "/run/zed.pid", false, true).
|
|
||||||
Bind("/run/zed.state", "/run/zed.state", false, true).
|
|
||||||
Bind("/tmp/fortify.1971/tmpdir/150", "/tmp", false, true).
|
|
||||||
Tmpfs("/tmp/fortify.1971", 1048576).
|
|
||||||
Tmpfs("/run/user", 1048576).
|
|
||||||
Tmpfs("/run/user/150", 8388608).
|
|
||||||
Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd").
|
|
||||||
Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group").
|
|
||||||
Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/etc/passwd").
|
|
||||||
Bind("/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/etc/group").
|
|
||||||
Tmpfs("/var/run/nscd", 8192),
|
|
||||||
want: []string{
|
|
||||||
"--unshare-all", "--unshare-user", "--share-net",
|
|
||||||
"--clearenv", "--die-with-parent", "--as-pid-1",
|
|
||||||
"--uid", "65534",
|
|
||||||
"--gid", "65534",
|
|
||||||
"--setenv", "FORTIFY_INIT", "3",
|
|
||||||
"--setenv", "HOME", "/home/chronos",
|
|
||||||
"--setenv", "SHELL", "/run/current-system/sw/bin/zsh",
|
|
||||||
"--setenv", "TERM", "xterm-256color",
|
|
||||||
"--setenv", "USER", "chronos",
|
|
||||||
"--setenv", "XDG_RUNTIME_DIR", "/run/user/150",
|
|
||||||
"--setenv", "XDG_SESSION_CLASS", "user",
|
|
||||||
"--setenv", "XDG_SESSION_TYPE", "tty",
|
|
||||||
"--proc", "/proc", "--dev", "/dev",
|
|
||||||
"--mqueue", "/dev/mqueue",
|
|
||||||
"--bind", "/bin", "/bin",
|
|
||||||
"--bind", "/boot", "/boot",
|
|
||||||
"--bind", "/etc", "/etc",
|
|
||||||
"--bind", "/home", "/home",
|
|
||||||
"--bind", "/lib", "/lib",
|
|
||||||
"--bind", "/lib64", "/lib64",
|
|
||||||
"--bind", "/nix", "/nix",
|
|
||||||
"--bind", "/root", "/root",
|
|
||||||
"--bind", "/srv", "/srv",
|
|
||||||
"--bind", "/sys", "/sys",
|
|
||||||
"--bind", "/usr", "/usr",
|
|
||||||
"--bind", "/var", "/var",
|
|
||||||
"--bind", "/run/NetworkManager", "/run/NetworkManager",
|
|
||||||
"--bind", "/run/agetty.reload", "/run/agetty.reload",
|
|
||||||
"--bind", "/run/binfmt", "/run/binfmt",
|
|
||||||
"--bind", "/run/booted-system", "/run/booted-system",
|
|
||||||
"--bind", "/run/credentials", "/run/credentials",
|
|
||||||
"--bind", "/run/cryptsetup", "/run/cryptsetup",
|
|
||||||
"--bind", "/run/current-system", "/run/current-system",
|
|
||||||
"--bind", "/run/host", "/run/host",
|
|
||||||
"--bind", "/run/keys", "/run/keys",
|
|
||||||
"--bind", "/run/libvirt", "/run/libvirt",
|
|
||||||
"--bind", "/run/libvirtd.pid", "/run/libvirtd.pid",
|
|
||||||
"--bind", "/run/lock", "/run/lock",
|
|
||||||
"--bind", "/run/log", "/run/log",
|
|
||||||
"--bind", "/run/lvm", "/run/lvm",
|
|
||||||
"--bind", "/run/mount", "/run/mount",
|
|
||||||
"--bind", "/run/nginx", "/run/nginx",
|
|
||||||
"--bind", "/run/nscd", "/run/nscd",
|
|
||||||
"--bind", "/run/opengl-driver", "/run/opengl-driver",
|
|
||||||
"--bind", "/run/pppd", "/run/pppd",
|
|
||||||
"--bind", "/run/resolvconf", "/run/resolvconf",
|
|
||||||
"--bind", "/run/sddm", "/run/sddm",
|
|
||||||
"--bind", "/run/syncoid", "/run/syncoid",
|
|
||||||
"--bind", "/run/systemd", "/run/systemd",
|
|
||||||
"--bind", "/run/tmpfiles.d", "/run/tmpfiles.d",
|
|
||||||
"--bind", "/run/udev", "/run/udev",
|
|
||||||
"--bind", "/run/udisks2", "/run/udisks2",
|
|
||||||
"--bind", "/run/utmp", "/run/utmp",
|
|
||||||
"--bind", "/run/virtlogd.pid", "/run/virtlogd.pid",
|
|
||||||
"--bind", "/run/wrappers", "/run/wrappers",
|
|
||||||
"--bind", "/run/zed.pid", "/run/zed.pid",
|
|
||||||
"--bind", "/run/zed.state", "/run/zed.state",
|
|
||||||
"--bind", "/tmp/fortify.1971/tmpdir/150", "/tmp",
|
|
||||||
"--size", "1048576", "--tmpfs", "/tmp/fortify.1971",
|
|
||||||
"--size", "1048576", "--tmpfs", "/run/user",
|
|
||||||
"--size", "8388608", "--tmpfs", "/run/user/150",
|
|
||||||
"--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd",
|
|
||||||
"--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group",
|
|
||||||
"--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/passwd", "/etc/passwd",
|
|
||||||
"--ro-bind", "/tmp/fortify.1971/67a97cc824a64ef789f16b20ca6ce311/group", "/etc/group",
|
|
||||||
"--size", "8192", "--tmpfs", "/var/run/nscd",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@ -220,4 +235,21 @@ func TestConfig_Args(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test persist validation
|
||||||
|
t.Run("invalid persist", func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
wantPanic := "persist called without required paths"
|
||||||
|
if r := recover(); r != wantPanic {
|
||||||
|
t.Errorf("Persist() panic = %v; wantPanic %v", r, wantPanic)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
(new(bwrap.Config)).Persist("/run", "", "")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("sync file", func(t *testing.T) {
|
||||||
|
if s := (new(bwrap.Config)).SetSync(os.Stdout).Sync(); s != os.Stdout {
|
||||||
|
t.Errorf("Sync() = %v", s)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
223
helper/bwrap/sequential.go
Normal file
223
helper/bwrap/sequential.go
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
package bwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
gob.Register(new(PermConfig[SymlinkConfig]))
|
||||||
|
gob.Register(new(PermConfig[*TmpfsConfig]))
|
||||||
|
gob.Register(new(OverlayConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
type PositionalArg int
|
||||||
|
|
||||||
|
func (p PositionalArg) Unwrap() string {
|
||||||
|
return positionalArgs[p]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Tmpfs PositionalArg = iota
|
||||||
|
Symlink
|
||||||
|
|
||||||
|
Bind
|
||||||
|
BindTry
|
||||||
|
DevBind
|
||||||
|
DevBindTry
|
||||||
|
ROBind
|
||||||
|
ROBindTry
|
||||||
|
|
||||||
|
Chmod
|
||||||
|
Dir
|
||||||
|
RemountRO
|
||||||
|
Procfs
|
||||||
|
DevTmpfs
|
||||||
|
Mqueue
|
||||||
|
|
||||||
|
Perms
|
||||||
|
Size
|
||||||
|
|
||||||
|
OverlaySrc
|
||||||
|
Overlay
|
||||||
|
TmpOverlay
|
||||||
|
ROOverlay
|
||||||
|
)
|
||||||
|
|
||||||
|
var positionalArgs = [...]string{
|
||||||
|
Tmpfs: "--tmpfs",
|
||||||
|
Symlink: "--symlink",
|
||||||
|
|
||||||
|
Bind: "--bind",
|
||||||
|
BindTry: "--bind-try",
|
||||||
|
DevBind: "--dev-bind",
|
||||||
|
DevBindTry: "--dev-bind-try",
|
||||||
|
ROBind: "--ro-bind",
|
||||||
|
ROBindTry: "--ro-bind-try",
|
||||||
|
|
||||||
|
Chmod: "--chmod",
|
||||||
|
Dir: "--dir",
|
||||||
|
RemountRO: "--remount-ro",
|
||||||
|
Procfs: "--proc",
|
||||||
|
DevTmpfs: "--dev",
|
||||||
|
Mqueue: "--mqueue",
|
||||||
|
|
||||||
|
Perms: "--perms",
|
||||||
|
Size: "--size",
|
||||||
|
|
||||||
|
OverlaySrc: "--overlay-src",
|
||||||
|
Overlay: "--overlay",
|
||||||
|
TmpOverlay: "--tmp-overlay",
|
||||||
|
ROOverlay: "--ro-overlay",
|
||||||
|
}
|
||||||
|
|
||||||
|
type PermConfig[T FSBuilder] struct {
|
||||||
|
// set permissions of next argument
|
||||||
|
// (--perms OCTAL)
|
||||||
|
Mode *os.FileMode `json:"mode,omitempty"`
|
||||||
|
// path to get the new permission
|
||||||
|
// (--bind-data, --file, etc.)
|
||||||
|
Inner T `json:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PermConfig[T]) Path() string {
|
||||||
|
return p.Inner.Path()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PermConfig[T]) Len() int {
|
||||||
|
if p.Mode != nil {
|
||||||
|
return p.Inner.Len() + 2
|
||||||
|
} else {
|
||||||
|
return p.Inner.Len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PermConfig[T]) Append(args *[]string) {
|
||||||
|
if p.Mode != nil {
|
||||||
|
*args = append(*args, Perms.Unwrap(), strconv.FormatInt(int64(*p.Mode), 8))
|
||||||
|
}
|
||||||
|
p.Inner.Append(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TmpfsConfig struct {
|
||||||
|
// set size of tmpfs
|
||||||
|
// (--size BYTES)
|
||||||
|
Size int `json:"size,omitempty"`
|
||||||
|
// mount point of new tmpfs
|
||||||
|
// (--tmpfs DEST)
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TmpfsConfig) Path() string {
|
||||||
|
return t.Dir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TmpfsConfig) Len() int {
|
||||||
|
if t.Size > 0 {
|
||||||
|
return 4
|
||||||
|
} else {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TmpfsConfig) Append(args *[]string) {
|
||||||
|
if t.Size > 0 {
|
||||||
|
*args = append(*args, Size.Unwrap(), strconv.Itoa(t.Size))
|
||||||
|
}
|
||||||
|
*args = append(*args, Tmpfs.Unwrap(), t.Dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
type OverlayConfig struct {
|
||||||
|
/*
|
||||||
|
read files from SRC in the following overlay
|
||||||
|
(--overlay-src SRC)
|
||||||
|
*/
|
||||||
|
Src []string `json:"src,omitempty"`
|
||||||
|
|
||||||
|
/*
|
||||||
|
mount overlayfs on DEST, with RWSRC as the host path for writes and
|
||||||
|
WORKDIR an empty directory on the same filesystem as RWSRC
|
||||||
|
(--overlay RWSRC WORKDIR DEST)
|
||||||
|
|
||||||
|
if nil, mount overlayfs on DEST, with writes going to an invisible tmpfs
|
||||||
|
(--tmp-overlay DEST)
|
||||||
|
|
||||||
|
if either strings are empty, mount overlayfs read-only on DEST
|
||||||
|
(--ro-overlay DEST)
|
||||||
|
*/
|
||||||
|
Persist *[2]string `json:"persist,omitempty"`
|
||||||
|
|
||||||
|
/*
|
||||||
|
--overlay RWSRC WORKDIR DEST
|
||||||
|
|
||||||
|
--tmp-overlay DEST
|
||||||
|
|
||||||
|
--ro-overlay DEST
|
||||||
|
*/
|
||||||
|
Dest string `json:"dest"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OverlayConfig) Path() string {
|
||||||
|
return o.Dest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OverlayConfig) Len() int {
|
||||||
|
// (--tmp-overlay DEST) or (--ro-overlay DEST)
|
||||||
|
p := 2
|
||||||
|
// (--overlay RWSRC WORKDIR DEST)
|
||||||
|
if o.Persist != nil && o.Persist[0] != "" && o.Persist[1] != "" {
|
||||||
|
p = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return p + len(o.Src)*2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *OverlayConfig) Append(args *[]string) {
|
||||||
|
// --overlay-src SRC
|
||||||
|
for _, src := range o.Src {
|
||||||
|
*args = append(*args, OverlaySrc.Unwrap(), src)
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.Persist != nil {
|
||||||
|
if o.Persist[0] != "" && o.Persist[1] != "" {
|
||||||
|
// --overlay RWSRC WORKDIR
|
||||||
|
*args = append(*args, Overlay.Unwrap(), o.Persist[0], o.Persist[1])
|
||||||
|
} else {
|
||||||
|
// --ro-overlay
|
||||||
|
*args = append(*args, ROOverlay.Unwrap())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// --tmp-overlay
|
||||||
|
*args = append(*args, TmpOverlay.Unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEST
|
||||||
|
*args = append(*args, o.Dest)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SymlinkConfig [2]string
|
||||||
|
|
||||||
|
func (s SymlinkConfig) Path() string {
|
||||||
|
return s[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SymlinkConfig) Len() int {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SymlinkConfig) Append(args *[]string) {
|
||||||
|
*args = append(*args, Symlink.Unwrap(), s[0], s[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChmodConfig map[string]os.FileMode
|
||||||
|
|
||||||
|
func (c ChmodConfig) Len() int {
|
||||||
|
return len(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ChmodConfig) Append(args *[]string) {
|
||||||
|
for path, mode := range c {
|
||||||
|
*args = append(*args, Chmod.Unwrap(), strconv.FormatInt(int64(mode), 8), path)
|
||||||
|
}
|
||||||
|
}
|
249
helper/bwrap/static.go
Normal file
249
helper/bwrap/static.go
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
package bwrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
static boolean args
|
||||||
|
*/
|
||||||
|
|
||||||
|
type BoolArg int
|
||||||
|
|
||||||
|
func (b BoolArg) Unwrap() []string {
|
||||||
|
return boolArgs[b]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnshareAll BoolArg = iota
|
||||||
|
UnshareUser
|
||||||
|
UnshareIPC
|
||||||
|
UnsharePID
|
||||||
|
UnshareNet
|
||||||
|
UnshareUTS
|
||||||
|
UnshareCGroup
|
||||||
|
ShareNet
|
||||||
|
|
||||||
|
UserNS
|
||||||
|
Clearenv
|
||||||
|
|
||||||
|
NewSession
|
||||||
|
DieWithParent
|
||||||
|
AsInit
|
||||||
|
)
|
||||||
|
|
||||||
|
var boolArgs = [...][]string{
|
||||||
|
UnshareAll: {"--unshare-all", "--unshare-user"},
|
||||||
|
UnshareUser: {"--unshare-user"},
|
||||||
|
UnshareIPC: {"--unshare-ipc"},
|
||||||
|
UnsharePID: {"--unshare-pid"},
|
||||||
|
UnshareNet: {"--unshare-net"},
|
||||||
|
UnshareUTS: {"--unshare-uts"},
|
||||||
|
UnshareCGroup: {"--unshare-cgroup"},
|
||||||
|
ShareNet: {"--share-net"},
|
||||||
|
|
||||||
|
UserNS: {"--disable-userns", "--assert-userns-disabled"},
|
||||||
|
Clearenv: {"--clearenv"},
|
||||||
|
|
||||||
|
NewSession: {"--new-session"},
|
||||||
|
DieWithParent: {"--die-with-parent"},
|
||||||
|
AsInit: {"--as-pid-1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) boolArgs() Builder {
|
||||||
|
b := boolArg{
|
||||||
|
UserNS: !c.UserNS,
|
||||||
|
Clearenv: c.Clearenv,
|
||||||
|
|
||||||
|
NewSession: c.NewSession,
|
||||||
|
DieWithParent: c.DieWithParent,
|
||||||
|
AsInit: c.AsInit,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Unshare == nil {
|
||||||
|
b[UnshareAll] = true
|
||||||
|
b[ShareNet] = c.Net
|
||||||
|
} else {
|
||||||
|
b[UnshareUser] = c.Unshare.User
|
||||||
|
b[UnshareIPC] = c.Unshare.IPC
|
||||||
|
b[UnsharePID] = c.Unshare.PID
|
||||||
|
b[UnshareNet] = c.Unshare.Net
|
||||||
|
b[UnshareUTS] = c.Unshare.UTS
|
||||||
|
b[UnshareCGroup] = c.Unshare.CGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
type boolArg [len(boolArgs)]bool
|
||||||
|
|
||||||
|
func (b *boolArg) Len() (l int) {
|
||||||
|
for i, v := range b {
|
||||||
|
if v {
|
||||||
|
l += len(boolArgs[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolArg) Append(args *[]string) {
|
||||||
|
for i, v := range b {
|
||||||
|
if v {
|
||||||
|
*args = append(*args, BoolArg(i).Unwrap()...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static integer args
|
||||||
|
*/
|
||||||
|
|
||||||
|
type IntArg int
|
||||||
|
|
||||||
|
func (i IntArg) Unwrap() string {
|
||||||
|
return intArgs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
UID IntArg = iota
|
||||||
|
GID
|
||||||
|
)
|
||||||
|
|
||||||
|
var intArgs = [...]string{
|
||||||
|
UID: "--uid",
|
||||||
|
GID: "--gid",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) intArgs() Builder {
|
||||||
|
return &intArg{
|
||||||
|
UID: c.UID,
|
||||||
|
GID: c.GID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type intArg [len(intArgs)]*int
|
||||||
|
|
||||||
|
func (n *intArg) Len() (l int) {
|
||||||
|
for _, v := range n {
|
||||||
|
if v != nil {
|
||||||
|
l += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *intArg) Append(args *[]string) {
|
||||||
|
for i, v := range n {
|
||||||
|
if v != nil {
|
||||||
|
*args = append(*args, IntArg(i).Unwrap(), strconv.Itoa(*v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static string args
|
||||||
|
*/
|
||||||
|
|
||||||
|
type StringArg int
|
||||||
|
|
||||||
|
func (s StringArg) Unwrap() string {
|
||||||
|
return stringArgs[s]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Hostname StringArg = iota
|
||||||
|
Chdir
|
||||||
|
UnsetEnv
|
||||||
|
LockFile
|
||||||
|
)
|
||||||
|
|
||||||
|
var stringArgs = [...]string{
|
||||||
|
Hostname: "--hostname",
|
||||||
|
Chdir: "--chdir",
|
||||||
|
UnsetEnv: "--unsetenv",
|
||||||
|
LockFile: "--lock-file",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) stringArgs() Builder {
|
||||||
|
n := stringArg{
|
||||||
|
UnsetEnv: c.UnsetEnv,
|
||||||
|
LockFile: c.LockFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Hostname != "" {
|
||||||
|
n[Hostname] = []string{c.Hostname}
|
||||||
|
}
|
||||||
|
if c.Chdir != "" {
|
||||||
|
n[Chdir] = []string{c.Chdir}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &n
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringArg [len(stringArgs)][]string
|
||||||
|
|
||||||
|
func (s *stringArg) Len() (l int) {
|
||||||
|
for _, arg := range s {
|
||||||
|
l += len(arg) * 2
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stringArg) Append(args *[]string) {
|
||||||
|
for i, arg := range s {
|
||||||
|
for _, v := range arg {
|
||||||
|
*args = append(*args, StringArg(i).Unwrap(), v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static pair args
|
||||||
|
*/
|
||||||
|
|
||||||
|
type PairArg int
|
||||||
|
|
||||||
|
func (p PairArg) Unwrap() string {
|
||||||
|
return pairArgs[p]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SetEnv PairArg = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
var pairArgs = [...]string{
|
||||||
|
SetEnv: "--setenv",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Config) pairArgs() Builder {
|
||||||
|
var n pairArg
|
||||||
|
n[SetEnv] = make([][2]string, len(c.SetEnv))
|
||||||
|
keys := make([]string, 0, len(c.SetEnv))
|
||||||
|
for k := range c.SetEnv {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
slices.Sort(keys)
|
||||||
|
for i, k := range keys {
|
||||||
|
n[SetEnv][i] = [2]string{k, c.SetEnv[k]}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &n
|
||||||
|
}
|
||||||
|
|
||||||
|
type pairArg [len(pairArgs)][][2]string
|
||||||
|
|
||||||
|
func (p *pairArg) Len() (l int) {
|
||||||
|
for _, v := range p {
|
||||||
|
l += len(v) * 3
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *pairArg) Append(args *[]string) {
|
||||||
|
for i, arg := range p {
|
||||||
|
for _, v := range arg {
|
||||||
|
*args = append(*args, PairArg(i).Unwrap(), v[0], v[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -62,45 +62,14 @@ var testCasesPd = []sealTestCase{
|
|||||||
Bind("/lib64", "/lib64", false, true).
|
Bind("/lib64", "/lib64", false, true).
|
||||||
Bind("/nix", "/nix", false, true).
|
Bind("/nix", "/nix", false, true).
|
||||||
Bind("/root", "/root", false, true).
|
Bind("/root", "/root", false, true).
|
||||||
|
Bind("/run", "/run", false, true).
|
||||||
Bind("/srv", "/srv", false, true).
|
Bind("/srv", "/srv", false, true).
|
||||||
Bind("/sys", "/sys", false, true).
|
Bind("/sys", "/sys", false, true).
|
||||||
Bind("/usr", "/usr", false, true).
|
Bind("/usr", "/usr", false, true).
|
||||||
Bind("/var", "/var", false, true).
|
Bind("/var", "/var", false, true).
|
||||||
Bind("/run/agetty.reload", "/run/agetty.reload", false, true).
|
|
||||||
Bind("/run/binfmt", "/run/binfmt", false, true).
|
|
||||||
Bind("/run/booted-system", "/run/booted-system", false, true).
|
|
||||||
Bind("/run/credentials", "/run/credentials", false, true).
|
|
||||||
Bind("/run/cryptsetup", "/run/cryptsetup", false, true).
|
|
||||||
Bind("/run/current-system", "/run/current-system", false, true).
|
|
||||||
Bind("/run/host", "/run/host", false, true).
|
|
||||||
Bind("/run/keys", "/run/keys", false, true).
|
|
||||||
Bind("/run/libvirt", "/run/libvirt", false, true).
|
|
||||||
Bind("/run/libvirtd.pid", "/run/libvirtd.pid", false, true).
|
|
||||||
Bind("/run/lock", "/run/lock", false, true).
|
|
||||||
Bind("/run/log", "/run/log", false, true).
|
|
||||||
Bind("/run/lvm", "/run/lvm", false, true).
|
|
||||||
Bind("/run/mount", "/run/mount", false, true).
|
|
||||||
Bind("/run/NetworkManager", "/run/NetworkManager", false, true).
|
|
||||||
Bind("/run/nginx", "/run/nginx", false, true).
|
|
||||||
Bind("/run/nixos", "/run/nixos", false, true).
|
|
||||||
Bind("/run/nscd", "/run/nscd", false, true).
|
|
||||||
Bind("/run/opengl-driver", "/run/opengl-driver", false, true).
|
|
||||||
Bind("/run/pppd", "/run/pppd", false, true).
|
|
||||||
Bind("/run/resolvconf", "/run/resolvconf", false, true).
|
|
||||||
Bind("/run/sddm", "/run/sddm", false, true).
|
|
||||||
Bind("/run/store", "/run/store", false, true).
|
|
||||||
Bind("/run/syncoid", "/run/syncoid", false, true).
|
|
||||||
Bind("/run/system", "/run/system", false, true).
|
|
||||||
Bind("/run/systemd", "/run/systemd", false, true).
|
|
||||||
Bind("/run/tmpfiles.d", "/run/tmpfiles.d", false, true).
|
|
||||||
Bind("/run/udev", "/run/udev", false, true).
|
|
||||||
Bind("/run/udisks2", "/run/udisks2", false, true).
|
|
||||||
Bind("/run/utmp", "/run/utmp", false, true).
|
|
||||||
Bind("/run/virtlogd.pid", "/run/virtlogd.pid", false, true).
|
|
||||||
Bind("/run/wrappers", "/run/wrappers", false, true).
|
|
||||||
Bind("/run/zed.pid", "/run/zed.pid", false, true).
|
|
||||||
Bind("/run/zed.state", "/run/zed.state", false, true).
|
|
||||||
Bind("/dev/kvm", "/dev/kvm", true, true, true).
|
Bind("/dev/kvm", "/dev/kvm", true, true, true).
|
||||||
|
Tmpfs("/run/user/1971", 8192).
|
||||||
|
Tmpfs("/run/dbus", 8192).
|
||||||
Bind("/etc", fst.Tmp+"/etc").
|
Bind("/etc", fst.Tmp+"/etc").
|
||||||
Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa").
|
Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa").
|
||||||
Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
|
Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
|
||||||
@ -317,46 +286,15 @@ var testCasesPd = []sealTestCase{
|
|||||||
Bind("/lib64", "/lib64", false, true).
|
Bind("/lib64", "/lib64", false, true).
|
||||||
Bind("/nix", "/nix", false, true).
|
Bind("/nix", "/nix", false, true).
|
||||||
Bind("/root", "/root", false, true).
|
Bind("/root", "/root", false, true).
|
||||||
|
Bind("/run", "/run", false, true).
|
||||||
Bind("/srv", "/srv", false, true).
|
Bind("/srv", "/srv", false, true).
|
||||||
Bind("/sys", "/sys", false, true).
|
Bind("/sys", "/sys", false, true).
|
||||||
Bind("/usr", "/usr", false, true).
|
Bind("/usr", "/usr", false, true).
|
||||||
Bind("/var", "/var", false, true).
|
Bind("/var", "/var", false, true).
|
||||||
Bind("/run/agetty.reload", "/run/agetty.reload", false, true).
|
|
||||||
Bind("/run/binfmt", "/run/binfmt", false, true).
|
|
||||||
Bind("/run/booted-system", "/run/booted-system", false, true).
|
|
||||||
Bind("/run/credentials", "/run/credentials", false, true).
|
|
||||||
Bind("/run/cryptsetup", "/run/cryptsetup", false, true).
|
|
||||||
Bind("/run/current-system", "/run/current-system", false, true).
|
|
||||||
Bind("/run/host", "/run/host", false, true).
|
|
||||||
Bind("/run/keys", "/run/keys", false, true).
|
|
||||||
Bind("/run/libvirt", "/run/libvirt", false, true).
|
|
||||||
Bind("/run/libvirtd.pid", "/run/libvirtd.pid", false, true).
|
|
||||||
Bind("/run/lock", "/run/lock", false, true).
|
|
||||||
Bind("/run/log", "/run/log", false, true).
|
|
||||||
Bind("/run/lvm", "/run/lvm", false, true).
|
|
||||||
Bind("/run/mount", "/run/mount", false, true).
|
|
||||||
Bind("/run/NetworkManager", "/run/NetworkManager", false, true).
|
|
||||||
Bind("/run/nginx", "/run/nginx", false, true).
|
|
||||||
Bind("/run/nixos", "/run/nixos", false, true).
|
|
||||||
Bind("/run/nscd", "/run/nscd", false, true).
|
|
||||||
Bind("/run/opengl-driver", "/run/opengl-driver", false, true).
|
|
||||||
Bind("/run/pppd", "/run/pppd", false, true).
|
|
||||||
Bind("/run/resolvconf", "/run/resolvconf", false, true).
|
|
||||||
Bind("/run/sddm", "/run/sddm", false, true).
|
|
||||||
Bind("/run/store", "/run/store", false, true).
|
|
||||||
Bind("/run/syncoid", "/run/syncoid", false, true).
|
|
||||||
Bind("/run/system", "/run/system", false, true).
|
|
||||||
Bind("/run/systemd", "/run/systemd", false, true).
|
|
||||||
Bind("/run/tmpfiles.d", "/run/tmpfiles.d", false, true).
|
|
||||||
Bind("/run/udev", "/run/udev", false, true).
|
|
||||||
Bind("/run/udisks2", "/run/udisks2", false, true).
|
|
||||||
Bind("/run/utmp", "/run/utmp", false, true).
|
|
||||||
Bind("/run/virtlogd.pid", "/run/virtlogd.pid", false, true).
|
|
||||||
Bind("/run/wrappers", "/run/wrappers", false, true).
|
|
||||||
Bind("/run/zed.pid", "/run/zed.pid", false, true).
|
|
||||||
Bind("/run/zed.state", "/run/zed.state", false, true).
|
|
||||||
Bind("/dev/dri", "/dev/dri", true, true, true).
|
Bind("/dev/dri", "/dev/dri", true, true, true).
|
||||||
Bind("/dev/kvm", "/dev/kvm", true, true, true).
|
Bind("/dev/kvm", "/dev/kvm", true, true, true).
|
||||||
|
Tmpfs("/run/user/1971", 8192).
|
||||||
|
Tmpfs("/run/dbus", 8192).
|
||||||
Bind("/etc", fst.Tmp+"/etc").
|
Bind("/etc", fst.Tmp+"/etc").
|
||||||
Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa").
|
Symlink(fst.Tmp+"/etc/alsa", "/etc/alsa").
|
||||||
Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
|
Symlink(fst.Tmp+"/etc/bashrc", "/etc/bashrc").
|
||||||
|
@ -2,7 +2,6 @@ package app_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -128,12 +127,12 @@ func (s *stubNixOS) Open(name string) (fs.File, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Exit(code int) {
|
func (s *stubNixOS) EvalSymlinks(path string) (string, error) {
|
||||||
panic("called exit on stub with code " + strconv.Itoa(code))
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Stdout() io.Writer {
|
func (s *stubNixOS) Exit(code int) {
|
||||||
panic("requested stdout")
|
panic("called exit on stub with code " + strconv.Itoa(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stubNixOS) Paths() linux.Paths {
|
func (s *stubNixOS) Paths() linux.Paths {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package app_test
|
package app_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@ -48,14 +49,22 @@ func TestApp(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("compare bwrap", func(t *testing.T) {
|
t.Run("compare bwrap", func(t *testing.T) {
|
||||||
if !reflect.DeepEqual(gotBwrap, tc.wantBwrap) {
|
if !reflect.DeepEqual(gotBwrap, tc.wantBwrap) {
|
||||||
t.Errorf("seal: bwrap = %#v, want %#v",
|
t.Errorf("seal: bwrap =\n%s\n, want\n%s",
|
||||||
gotBwrap, tc.wantBwrap)
|
mustMarshal(gotBwrap), mustMarshal(tc.wantBwrap))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mustMarshal(v any) string {
|
||||||
|
if b, err := json.Marshal(v); err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
} else {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func stubDirEntries(names ...string) (e []fs.DirEntry, err error) {
|
func stubDirEntries(names ...string) (e []fs.DirEntry, err error) {
|
||||||
e = make([]fs.DirEntry, len(names))
|
e = make([]fs.DirEntry, len(names))
|
||||||
for i, name := range names {
|
for i, name := range names {
|
||||||
|
@ -194,7 +194,6 @@ func (a *app) Seal(config *fst.Config) error {
|
|||||||
switch p {
|
switch p {
|
||||||
case "/proc":
|
case "/proc":
|
||||||
case "/dev":
|
case "/dev":
|
||||||
case "/run":
|
|
||||||
case "/tmp":
|
case "/tmp":
|
||||||
case "/mnt":
|
case "/mnt":
|
||||||
case "/etc":
|
case "/etc":
|
||||||
@ -205,23 +204,7 @@ func (a *app) Seal(config *fst.Config) error {
|
|||||||
}
|
}
|
||||||
conf.Filesystem = append(conf.Filesystem, b...)
|
conf.Filesystem = append(conf.Filesystem, b...)
|
||||||
}
|
}
|
||||||
// bind entries in /run
|
|
||||||
if d, err := a.os.ReadDir("/run"); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
b := make([]*fst.FilesystemConfig, 0, len(d))
|
|
||||||
for _, ent := range d {
|
|
||||||
name := ent.Name()
|
|
||||||
switch name {
|
|
||||||
case "user":
|
|
||||||
case "dbus":
|
|
||||||
default:
|
|
||||||
p := "/run/" + name
|
|
||||||
b = append(b, &fst.FilesystemConfig{Src: p, Write: true, Must: true})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conf.Filesystem = append(conf.Filesystem, b...)
|
|
||||||
}
|
|
||||||
// hide nscd from sandbox if present
|
// hide nscd from sandbox if present
|
||||||
nscd := "/var/run/nscd"
|
nscd := "/var/run/nscd"
|
||||||
if _, err := a.os.Stat(nscd); !errors.Is(err, fs.ErrNotExist) {
|
if _, err := a.os.Stat(nscd); !errors.Is(err, fs.ErrNotExist) {
|
||||||
|
@ -170,7 +170,7 @@ func (seal *appSeal) setupShares(bus [2]*dbus.Config, os linux.System) error {
|
|||||||
appID := seal.fid
|
appID := seal.fid
|
||||||
if appID == "" {
|
if appID == "" {
|
||||||
// use instance ID in case app id is not set
|
// use instance ID in case app id is not set
|
||||||
appID = "moe.ophivana.fortify." + seal.id
|
appID = "uk.gensokyo.fortify." + seal.id
|
||||||
}
|
}
|
||||||
seal.sys.Wayland(wt, wp, appID, seal.id)
|
seal.sys.Wayland(wt, wp, appID, seal.id)
|
||||||
seal.sys.bwrap.Bind(wt, w)
|
seal.sys.bwrap.Bind(wt, w)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package linux
|
package linux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
@ -30,10 +29,10 @@ type System interface {
|
|||||||
Stat(name string) (fs.FileInfo, error)
|
Stat(name string) (fs.FileInfo, error)
|
||||||
// Open provides [os.Open]
|
// Open provides [os.Open]
|
||||||
Open(name string) (fs.File, error)
|
Open(name string) (fs.File, error)
|
||||||
|
// EvalSymlinks provides [filepath.EvalSymlinks]
|
||||||
|
EvalSymlinks(path string) (string, error)
|
||||||
// Exit provides [os.Exit].
|
// Exit provides [os.Exit].
|
||||||
Exit(code int)
|
Exit(code int)
|
||||||
// Stdout provides [os.Stdout].
|
|
||||||
Stdout() io.Writer
|
|
||||||
|
|
||||||
// Paths returns a populated [Paths] struct.
|
// Paths returns a populated [Paths] struct.
|
||||||
Paths() Paths
|
Paths() Paths
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package linux
|
package linux
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"errors"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"git.gensokyo.uk/security/fortify/internal"
|
"git.gensokyo.uk/security/fortify/internal"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
@ -35,8 +37,8 @@ func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.Lookup
|
|||||||
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
|
||||||
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
|
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
|
||||||
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
|
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
|
||||||
|
func (s *Std) EvalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
|
||||||
func (s *Std) Exit(code int) { fmsg.Exit(code) }
|
func (s *Std) Exit(code int) { fmsg.Exit(code) }
|
||||||
func (s *Std) Stdout() io.Writer { return os.Stdout }
|
|
||||||
|
|
||||||
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
|
||||||
@ -79,9 +81,15 @@ func (s *Std) Uid(aid int) (int, error) {
|
|||||||
cmd.Stderr = os.Stderr // pass through fatal messages
|
cmd.Stderr = os.Stderr // pass through fatal messages
|
||||||
cmd.Env = []string{"FORTIFY_APP_ID=" + strconv.Itoa(aid)}
|
cmd.Env = []string{"FORTIFY_APP_ID=" + strconv.Itoa(aid)}
|
||||||
cmd.Dir = "/"
|
cmd.Dir = "/"
|
||||||
var p []byte
|
var (
|
||||||
|
p []byte
|
||||||
|
exitError *exec.ExitError
|
||||||
|
)
|
||||||
|
|
||||||
if p, u.err = cmd.Output(); u.err == nil {
|
if p, u.err = cmd.Output(); u.err == nil {
|
||||||
u.uid, u.err = strconv.Atoi(string(p))
|
u.uid, u.err = strconv.Atoi(string(p))
|
||||||
|
} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
||||||
|
u.err = syscall.EACCES
|
||||||
}
|
}
|
||||||
return u.uid, u.err
|
return u.uid, u.err
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,9 @@ package internal
|
|||||||
import "path"
|
import "path"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Fsu = compPoison
|
Fortify = compPoison
|
||||||
Finit = compPoison
|
Fsu = compPoison
|
||||||
|
Finit = compPoison
|
||||||
)
|
)
|
||||||
|
|
||||||
func Path(p string) (string, bool) {
|
func Path(p string) (string, bool) {
|
||||||
|
36
main.go
36
main.go
@ -4,6 +4,7 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -16,7 +17,6 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/internal/app"
|
"git.gensokyo.uk/security/fortify/internal/app"
|
||||||
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
"git.gensokyo.uk/security/fortify/internal/fmsg"
|
||||||
"git.gensokyo.uk/security/fortify/internal/linux"
|
"git.gensokyo.uk/security/fortify/internal/linux"
|
||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
|
||||||
"git.gensokyo.uk/security/fortify/internal/system"
|
"git.gensokyo.uk/security/fortify/internal/system"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ func init() {
|
|||||||
flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable")
|
flag.BoolVar(&flagJSON, "json", false, "Format output in JSON when applicable")
|
||||||
}
|
}
|
||||||
|
|
||||||
var os = new(linux.Std)
|
var sys linux.System = new(linux.Std)
|
||||||
|
|
||||||
type gl []string
|
type gl []string
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ func main() {
|
|||||||
fmt.Println("Usage:\tfortify [-v] [--json] COMMAND [OPTIONS]")
|
fmt.Println("Usage:\tfortify [-v] [--json] COMMAND [OPTIONS]")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Commands:")
|
fmt.Println("Commands:")
|
||||||
w := tabwriter.NewWriter(os.Stdout(), 0, 1, 4, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 1, 4, ' ', 0)
|
||||||
commands := [][2]string{
|
commands := [][2]string{
|
||||||
{"app", "Launch app defined by the specified config file"},
|
{"app", "Launch app defined by the specified config file"},
|
||||||
{"run", "Configure and start a permissive default sandbox"},
|
{"run", "Configure and start a permissive default sandbox"},
|
||||||
@ -128,24 +128,20 @@ func main() {
|
|||||||
// Ignore errors; set is set for ExitOnError.
|
// Ignore errors; set is set for ExitOnError.
|
||||||
_ = set.Parse(args[1:])
|
_ = set.Parse(args[1:])
|
||||||
|
|
||||||
var (
|
switch len(set.Args()) {
|
||||||
config *fst.Config
|
case 0: // system
|
||||||
instance *state.State
|
printShowSystem(short)
|
||||||
name string
|
case 1: // instance
|
||||||
)
|
name := set.Args()[0]
|
||||||
|
config, instance := tryShort(name)
|
||||||
if len(set.Args()) != 1 {
|
if config == nil {
|
||||||
|
config = tryPath(name)
|
||||||
|
}
|
||||||
|
printShowInstance(instance, config, short)
|
||||||
|
default:
|
||||||
fmsg.Fatal("show requires 1 argument")
|
fmsg.Fatal("show requires 1 argument")
|
||||||
} else {
|
|
||||||
name = set.Args()[0]
|
|
||||||
config, instance = tryShort(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if config == nil {
|
|
||||||
config = tryPath(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
printShow(instance, config, short)
|
|
||||||
fmsg.Exit(0)
|
fmsg.Exit(0)
|
||||||
case "app": // launch app from configuration file
|
case "app": // launch app from configuration file
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
@ -211,7 +207,7 @@ func main() {
|
|||||||
passwdOnce sync.Once
|
passwdOnce sync.Once
|
||||||
passwdFunc = func() {
|
passwdFunc = func() {
|
||||||
var us string
|
var us string
|
||||||
if uid, err := os.Uid(aid); err != nil {
|
if uid, err := sys.Uid(aid); err != nil {
|
||||||
fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
|
fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
|
||||||
} else {
|
} else {
|
||||||
us = strconv.Itoa(uid)
|
us = strconv.Itoa(uid)
|
||||||
@ -292,7 +288,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runApp(config *fst.Config) {
|
func runApp(config *fst.Config) {
|
||||||
a, err := app.New(os)
|
a, err := app.New(sys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmsg.Fatalf("cannot create app: %s\n", err)
|
fmsg.Fatalf("cannot create app: %s\n", err)
|
||||||
} else if err = a.Seal(config); err != nil {
|
} else if err = a.Seal(config); err != nil {
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
buildGoModule rec {
|
buildGoModule rec {
|
||||||
pname = "fortify";
|
pname = "fortify";
|
||||||
version = "0.2.8";
|
version = "0.2.9";
|
||||||
|
|
||||||
src = builtins.path {
|
src = builtins.path {
|
||||||
name = "fortify-src";
|
name = "fortify-src";
|
||||||
@ -45,6 +45,7 @@ buildGoModule rec {
|
|||||||
Version = "v${version}";
|
Version = "v${version}";
|
||||||
Fsu = "/run/wrappers/bin/fsu";
|
Fsu = "/run/wrappers/bin/fsu";
|
||||||
Finit = "${placeholder "out"}/libexec/finit";
|
Finit = "${placeholder "out"}/libexec/finit";
|
||||||
|
Fortify = "${placeholder "out"}/bin/fortify";
|
||||||
};
|
};
|
||||||
|
|
||||||
# nix build environment does not allow acls
|
# nix build environment does not allow acls
|
||||||
|
8
parse.go
8
parse.go
@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
direct "os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
@ -38,7 +38,7 @@ func tryPath(name string) (config *fst.Config) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
r = direct.Stdin
|
r = os.Stdin
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.NewDecoder(r).Decode(&config); err != nil {
|
if err := json.NewDecoder(r).Decode(&config); err != nil {
|
||||||
@ -61,7 +61,7 @@ func tryFd(name string) io.ReadCloser {
|
|||||||
}
|
}
|
||||||
fmsg.Fatalf("cannot get fd %d: %v", fd, errno)
|
fmsg.Fatalf("cannot get fd %d: %v", fd, errno)
|
||||||
}
|
}
|
||||||
return direct.NewFile(fd, strconv.Itoa(v))
|
return os.NewFile(fd, strconv.Itoa(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ func tryShort(name string) (config *fst.Config, instance *state.State) {
|
|||||||
if likePrefix && len(name) >= 8 {
|
if likePrefix && len(name) >= 8 {
|
||||||
fmsg.VPrintln("argument looks like prefix")
|
fmsg.VPrintln("argument looks like prefix")
|
||||||
|
|
||||||
s := state.NewMulti(os.Paths().RunDirPath)
|
s := state.NewMulti(sys.Paths().RunDirPath)
|
||||||
if entries, err := state.Join(s); err != nil {
|
if entries, err := state.Join(s); err != nil {
|
||||||
fmsg.Printf("cannot join store: %v", err)
|
fmsg.Printf("cannot join store: %v", err)
|
||||||
// drop to fetch from file
|
// drop to fetch from file
|
||||||
|
39
print.go
39
print.go
@ -16,14 +16,37 @@ import (
|
|||||||
"git.gensokyo.uk/security/fortify/internal/state"
|
"git.gensokyo.uk/security/fortify/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
func printShow(instance *state.State, config *fst.Config, short bool) {
|
func printShowSystem(short bool) {
|
||||||
if flagJSON {
|
info := new(fst.Info)
|
||||||
v := any(config)
|
|
||||||
if instance != nil {
|
|
||||||
v = instance
|
|
||||||
}
|
|
||||||
|
|
||||||
printJSON(v)
|
// get fid by querying uid of aid 0
|
||||||
|
if uid, err := sys.Uid(0); err != nil {
|
||||||
|
fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
|
||||||
|
} else {
|
||||||
|
info.User = (uid / 10000) - 100
|
||||||
|
}
|
||||||
|
|
||||||
|
if flagJSON {
|
||||||
|
printJSON(info)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w := tabwriter.NewWriter(direct.Stdout, 0, 1, 4, ' ', 0)
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "User:\t%d\n", info.User)
|
||||||
|
|
||||||
|
if err := w.Flush(); err != nil {
|
||||||
|
fmsg.Fatalf("cannot flush tabwriter: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printShowInstance(instance *state.State, config *fst.Config, short bool) {
|
||||||
|
if flagJSON {
|
||||||
|
if instance != nil {
|
||||||
|
printJSON(instance)
|
||||||
|
} else {
|
||||||
|
printJSON(config)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +194,7 @@ func printPs(short bool) {
|
|||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
|
|
||||||
var entries state.Entries
|
var entries state.Entries
|
||||||
s := state.NewMulti(os.Paths().RunDirPath)
|
s := state.NewMulti(sys.Paths().RunDirPath)
|
||||||
if e, err := state.Join(s); err != nil {
|
if e, err := state.Join(s); err != nil {
|
||||||
fmsg.Fatalf("cannot join store: %v", err)
|
fmsg.Fatalf("cannot join store: %v", err)
|
||||||
} else {
|
} else {
|
||||||
|
25
test.nix
25
test.nix
@ -19,11 +19,19 @@ nixosTest {
|
|||||||
nodes.machine =
|
nodes.machine =
|
||||||
{ lib, pkgs, ... }:
|
{ lib, pkgs, ... }:
|
||||||
{
|
{
|
||||||
users.users.alice = {
|
users.users = {
|
||||||
isNormalUser = true;
|
alice = {
|
||||||
description = "Alice Foobar";
|
isNormalUser = true;
|
||||||
password = "foobar";
|
description = "Alice Foobar";
|
||||||
uid = 1000;
|
password = "foobar";
|
||||||
|
uid = 1000;
|
||||||
|
};
|
||||||
|
untrusted = {
|
||||||
|
isNormalUser = true;
|
||||||
|
description = "Untrusted user";
|
||||||
|
password = "foobar";
|
||||||
|
uid = 1001;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
home-manager.users.alice.home.stateVersion = "24.11";
|
home-manager.users.alice.home.stateVersion = "24.11";
|
||||||
@ -198,8 +206,11 @@ nixosTest {
|
|||||||
machine.wait_for_file("/run/user/1000/wayland-1")
|
machine.wait_for_file("/run/user/1000/wayland-1")
|
||||||
machine.wait_for_file("/tmp/sway-ipc.sock")
|
machine.wait_for_file("/tmp/sway-ipc.sock")
|
||||||
|
|
||||||
# Create fortify aid 0 home directory:
|
# Deny unmapped uid:
|
||||||
machine.succeed("install -dm 0700 -o 1000000 -g 1000000 /var/lib/fortify/u0/a0")
|
print(machine.fail("sudo -u untrusted -i ${self.packages.${system}.fortify}/bin/fortify -v run"))
|
||||||
|
|
||||||
|
# Create fortify uid 0 state directory:
|
||||||
|
machine.succeed("install -dm 0755 -o u0_a0 -g users /var/lib/fortify/u0")
|
||||||
|
|
||||||
# Start fortify outside Wayland session:
|
# Start fortify 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"))
|
||||||
|
2
wl/c.go
2
wl/c.go
@ -74,7 +74,7 @@ static int32_t bind_wayland_fd(char *socket_path, int fd, const char *app_id, co
|
|||||||
|
|
||||||
struct wp_security_context_v1 *security_context;
|
struct wp_security_context_v1 *security_context;
|
||||||
security_context = wp_security_context_manager_v1_create_listener(security_context_manager, listen_fd, sync_fd);
|
security_context = wp_security_context_manager_v1_create_listener(security_context_manager, listen_fd, sync_fd);
|
||||||
wp_security_context_v1_set_sandbox_engine(security_context, "moe.ophivana.fortify");
|
wp_security_context_v1_set_sandbox_engine(security_context, "uk.gensokyo.fortify");
|
||||||
wp_security_context_v1_set_app_id(security_context, app_id);
|
wp_security_context_v1_set_app_id(security_context, app_id);
|
||||||
wp_security_context_v1_set_instance_id(security_context, instance_id);
|
wp_security_context_v1_set_instance_id(security_context, instance_id);
|
||||||
wp_security_context_v1_commit(security_context);
|
wp_security_context_v1_commit(security_context);
|
||||||
|
Loading…
Reference in New Issue
Block a user