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,6 +3,7 @@ package internal
 | 
				
			|||||||
import "path"
 | 
					import "path"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
 | 
						Fortify = compPoison
 | 
				
			||||||
	Fsu     = compPoison
 | 
						Fsu     = compPoison
 | 
				
			||||||
	Finit   = compPoison
 | 
						Finit   = compPoison
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										34
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								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 {
 | 
					 | 
				
			||||||
			fmsg.Fatal("show requires 1 argument")
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			name = set.Args()[0]
 | 
					 | 
				
			||||||
			config, instance = tryShort(name)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if config == nil {
 | 
								if config == nil {
 | 
				
			||||||
				config = tryPath(name)
 | 
									config = tryPath(name)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								printShowInstance(instance, config, short)
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								fmsg.Fatal("show requires 1 argument")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		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
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										37
									
								
								print.go
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								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 {
 | 
						// get fid by querying uid of aid 0
 | 
				
			||||||
			v = instance
 | 
						if uid, err := sys.Uid(0); err != nil {
 | 
				
			||||||
 | 
							fmsg.Fatalf("cannot obtain uid from fsu: %v", err)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							info.User = (uid / 10000) - 100
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		printJSON(v)
 | 
						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 {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										17
									
								
								test.nix
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								test.nix
									
									
									
									
									
								
							@ -19,12 +19,20 @@ nixosTest {
 | 
				
			|||||||
  nodes.machine =
 | 
					  nodes.machine =
 | 
				
			||||||
    { lib, pkgs, ... }:
 | 
					    { lib, pkgs, ... }:
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      users.users.alice = {
 | 
					      users.users = {
 | 
				
			||||||
 | 
					        alice = {
 | 
				
			||||||
          isNormalUser = true;
 | 
					          isNormalUser = true;
 | 
				
			||||||
          description = "Alice Foobar";
 | 
					          description = "Alice Foobar";
 | 
				
			||||||
          password = "foobar";
 | 
					          password = "foobar";
 | 
				
			||||||
          uid = 1000;
 | 
					          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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user