From 9116f7c698305993d9399f90401007763bdd7755 Mon Sep 17 00:00:00 2001 From: Cassandra Comar Date: Thu, 8 Aug 2024 14:51:19 -0400 Subject: [PATCH 1/9] feat(arx): add macos support via fakedir fixes #17 see: [fakedir](https://github.com/nixie-dev/fakedir/tree/b0f70b805aa86fe881eeca642f12069c7983a8e7) for limitations and caveats. --- default.nix | 27 +++++++++++++++---- flake.lock | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 22 ++++++++++++---- 3 files changed, 115 insertions(+), 10 deletions(-) create mode 100644 flake.lock diff --git a/default.nix b/default.nix index da78a95..1d33096 100644 --- a/default.nix +++ b/default.nix @@ -1,4 +1,7 @@ -{nixpkgs ? import {}}: +{ + nixpkgs ? import {} +, fakedir ? import (fetchTarball "https://github.com/nixie-dev/fakedir/archive/b0f70b805aa86fe881eeca642f12069c7983a8e7.tar.gz") {} +}: with nixpkgs; @@ -32,15 +35,17 @@ in rec { maketar = { targets }: stdenv.mkDerivation { name = "maketar"; - buildInputs = [ perl ]; + buildInputs = [ perl gnutar ]; exportReferencesGraph = map (x: [("closure-" + baseNameOf x) x]) targets; - buildCommand = '' + buildCommand = let + additionalPaths = lib.optionalString stdenv.isDarwin "-C ${fakedir} lib"; + in '' storePaths=$(perl ${pathsFromGraph} ./closure-*) tar -cf - \ --owner=0 --group=0 --mode=u+rw,uga+r \ --hard-dereference \ - $storePaths | bzip2 -z > $out + $storePaths ${additionalPaths} | bzip2 -z > $out ''; }; @@ -88,11 +93,23 @@ in rec { let # Avoid re-adding a store path into the store path = toStorePath target; + script-linux = '' + .${nix-user-chroot'}/bin/nix-user-chroot -n ./nix ${nixUserChrootFlags} -- ${path}${run} "$@" + ''; + script-macos = '' + # use absolute paths so the environment variables don't get reinterpreted after a cd + cur_dir=$(pwd) + export DYLD_INSERT_LIBRARIES="''${cur_dir}/lib/libfakedir.dylib" + export FAKEDIR_PATTERN=/nix + export FAKEDIR_TARGET="''${cur_dir}/nix" + exec .${path}${run} "$@" + ''; + script = if stdenv.isDarwin then script-macos else script-linux; in writeScript "startup" '' #!/bin/sh ${initScript} - .${nix-user-chroot'}/bin/nix-user-chroot -n ./nix ${nixUserChrootFlags} -- ${path}${run} "$@" + ${script} ''; nix-bootstrap = { target, extraTargets ? [], run, nix-user-chroot' ? nix-user-chroot, nixUserChrootFlags ? "", initScript ? "" }: diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..c282b26 --- /dev/null +++ b/flake.lock @@ -0,0 +1,76 @@ +{ + "nodes": { + "fakedir-pkgs": { + "inputs": { + "nixpkgs": "nixpkgs", + "utils": "utils" + }, + "locked": { + "lastModified": 1719499522, + "narHash": "sha256-WxdRtXL5p6BMjLoXDGkur0c6DXNNr1dCajOmX1tUlr8=", + "owner": "nixie-dev", + "repo": "fakedir", + "rev": "b0f70b805aa86fe881eeca642f12069c7983a8e7", + "type": "github" + }, + "original": { + "owner": "nixie-dev", + "repo": "fakedir", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1676995868, + "narHash": "sha256-WlJapOhp/D/r8SjajToSBsrX9r0+jeKmZ6C8OZ3BJ94=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d412d7956754408c347291516d1d3e3ea469642b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1723070956, + "narHash": "sha256-bFOTvmkJ2c1ku+E0gvqmNEF2D1PSmujDFLofKAMF/pM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "7cec143201c32c4937f2c153af4a9f28a3d9bec1", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-24.05-small", + "type": "indirect" + } + }, + "root": { + "inputs": { + "fakedir-pkgs": "fakedir-pkgs", + "nixpkgs": "nixpkgs_2" + } + }, + "utils": { + "locked": { + "lastModified": 1676283394, + "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix index 6939647..e14233c 100644 --- a/flake.nix +++ b/flake.nix @@ -1,20 +1,32 @@ { description = "The purely functional package manager"; - inputs.nixpkgs.url = "nixpkgs/nixos-20.03-small"; + inputs.nixpkgs.url = "nixpkgs/nixos-24.05-small"; + inputs.fakedir-pkgs.url = "github:nixie-dev/fakedir"; - outputs = { self, nixpkgs }: let - systems = [ "x86_64-linux" "i686-linux" "aarch64-linux" ]; + outputs = { self, nixpkgs, fakedir-pkgs }: let + systems = [ "x86_64-linux" "i686-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); in { bundlers = { nix-bundle = { program, system }: let nixpkgs' = nixpkgs.legacyPackages.${system}; - nix-bundle = import self { nixpkgs = nixpkgs'; }; - script = nixpkgs'.writeScript "startup" '' + fakedir = fakedir-pkgs.packages.${system}.fakedir-universal; + nix-bundle = import self { nixpkgs = nixpkgs'; inherit fakedir; }; + script-linux = nixpkgs'.writeScript "startup" '' #!/bin/sh .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -- ${program} "$@" ''; + script-darwin = nixpkgs'.writeScript "startup" '' + #!/bin/sh + # use absolute paths so the environment variables don't get reinterpreted after a cd + cur_dir=$(pwd) + export DYLD_INSERT_LIBRARIES="''${cur_dir}/lib/libfakedir.dylib" + export FAKEDIR_PATTERN=/nix + export FAKEDIR_TARGET="''${cur_dir}/nix" + exec .${program} "$@" + ''; + script = if nixpkgs'.stdenv.isDarwin then script-darwin else script-linux; in nix-bundle.makebootstrap { targets = [ script ]; startup = ".${builtins.unsafeDiscardStringContext script} '\"$@\"'"; From 8a9cac1f3721b215893b2b354816769ad6c91132 Mon Sep 17 00:00:00 2001 From: Cassandra Comar Date: Sat, 10 Aug 2024 18:27:06 -0400 Subject: [PATCH 2/9] feat: execute binary specified by $0 thread argv0 through the arx call stack so we can select the binary in the target's bin path based on it. this also fixes an issue with fakedir where running `Dir.pwd` within ruby would cause a segfault due to a null ptr dereference. --- arx-multi-bin.patch | 44 +++++++++++++++++++++++++++++++++++++++ default.nix | 21 +++++++++---------- fakedir-null-buffer.patch | 23 ++++++++++++++++++++ flake.nix | 21 +++++++++---------- 4 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 arx-multi-bin.patch create mode 100644 fakedir-null-buffer.patch diff --git a/arx-multi-bin.patch b/arx-multi-bin.patch new file mode 100644 index 0000000..15d904e --- /dev/null +++ b/arx-multi-bin.patch @@ -0,0 +1,44 @@ +diff --git a/model-scripts/tmpx.sh b/model-scripts/tmpx.sh +index 9c2ec6f..aa6fea9 100755 +--- a/model-scripts/tmpx.sh ++++ b/model-scripts/tmpx.sh +@@ -4,6 +4,7 @@ unset rm_ dir + tmp=true ; run=true + tmpdir= ; rm0=true ; rm1=true ; shared=false ; hash="" # To be set by tool. + token=`date -u +%FT%TZ | tr -d :-`-`hexdump -n4 -e '"%08x"' + + /** + * @file trivial_replacements.c +@@ -309,8 +310,11 @@ ENDSUBST + + SUBST(char const *, getcwd, (char *buf, size_t size)) + pthread_mutex_unlock(&_lock); +- getcwd(buf, size); ++ char const *cwd = getcwd(buf, size); + pthread_mutex_lock(&_lock); ++ if (buf == NULL) { ++ buf = strdup(cwd); ++ } + strlcpy(buf, rewrite_path_rev(buf), size); + buf; + ENDSUBST diff --git a/flake.nix b/flake.nix index e14233c..e1cd9d7 100644 --- a/flake.nix +++ b/flake.nix @@ -4,32 +4,31 @@ inputs.nixpkgs.url = "nixpkgs/nixos-24.05-small"; inputs.fakedir-pkgs.url = "github:nixie-dev/fakedir"; - outputs = { self, nixpkgs, fakedir-pkgs }: let - systems = [ "x86_64-linux" "i686-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; - forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); - in { + outputs = { self, nixpkgs, fakedir-pkgs }: { bundlers = { nix-bundle = { program, system }: let nixpkgs' = nixpkgs.legacyPackages.${system}; - fakedir = fakedir-pkgs.packages.${system}.fakedir-universal; + fakedir = fakedir-pkgs.packages.${system}.fakedir.overrideAttrs (old: { patches = [./fakedir-null-buffer.patch]; }); nix-bundle = import self { nixpkgs = nixpkgs'; inherit fakedir; }; script-linux = nixpkgs'.writeScript "startup" '' #!/bin/sh - .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -- ${program} "$@" + exec .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -- "$(dirname ${program})/$(basename "$0")" "$@" ''; script-darwin = nixpkgs'.writeScript "startup" '' #!/bin/sh # use absolute paths so the environment variables don't get reinterpreted after a cd - cur_dir=$(pwd) - export DYLD_INSERT_LIBRARIES="''${cur_dir}/lib/libfakedir.dylib" + __TMPX_DAT_PATH=$(pwd) + cd "''${TMPX_RESTORE_PWD}" + export DYLD_INSERT_LIBRARIES="''${__TMPX_DAT_PATH}/lib/libfakedir.dylib" export FAKEDIR_PATTERN=/nix - export FAKEDIR_TARGET="''${cur_dir}/nix" - exec .${program} "$@" + export FAKEDIR_TARGET="''${__TMPX_DAT_PATH}/nix" + + exec "''${__TMPX_DAT_PATH}$(dirname ${program})/$(basename "$0")" "$@" ''; script = if nixpkgs'.stdenv.isDarwin then script-darwin else script-linux; in nix-bundle.makebootstrap { targets = [ script ]; - startup = ".${builtins.unsafeDiscardStringContext script} '\"$@\"'"; + startup = script; }; }; From f15e4558a14c153d37dfa52036fc35ff0ac87469 Mon Sep 17 00:00:00 2001 From: Cassandra Comar Date: Tue, 13 Aug 2024 12:28:31 -0400 Subject: [PATCH 3/9] fakedir patch upstreamed the null buffer issue has been resolved upstreamed with a PR. also, fix an issue with symlinks/shebangs not being rewritten for the initial program executed by the startup script -- we need to avoid that call to execvp happening in the process environment that existed prior to the injection of the fakedir libraries. to do that, run a new shell where the exported env vars are set and avoid that shell just execing by first testing if the fakedir library is present and loaded, then running the specified executable. --- fakedir-null-buffer.patch | 23 ----------------------- flake.lock | 6 +++--- flake.nix | 5 +++-- 3 files changed, 6 insertions(+), 28 deletions(-) delete mode 100644 fakedir-null-buffer.patch diff --git a/fakedir-null-buffer.patch b/fakedir-null-buffer.patch deleted file mode 100644 index b9289bc..0000000 --- a/fakedir-null-buffer.patch +++ /dev/null @@ -1,23 +0,0 @@ -diff --git a/trivial_replacements.c b/trivial_replacements.c -index 8de580f..c33ef25 100644 ---- a/trivial_replacements.c -+++ b/trivial_replacements.c -@@ -1,4 +1,5 @@ - #include "common.h" -+#include - - /** - * @file trivial_replacements.c -@@ -309,8 +310,11 @@ ENDSUBST - - SUBST(char const *, getcwd, (char *buf, size_t size)) - pthread_mutex_unlock(&_lock); -- getcwd(buf, size); -+ char const *cwd = getcwd(buf, size); - pthread_mutex_lock(&_lock); -+ if (buf == NULL) { -+ buf = strdup(cwd); -+ } - strlcpy(buf, rewrite_path_rev(buf), size); - buf; - ENDSUBST diff --git a/flake.lock b/flake.lock index c282b26..59b6ef3 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1719499522, - "narHash": "sha256-WxdRtXL5p6BMjLoXDGkur0c6DXNNr1dCajOmX1tUlr8=", + "lastModified": 1723473839, + "narHash": "sha256-krWOwpqjg83Y5e9ly8IJ81icqH4YUNbkIIW1jqiaYhY=", "owner": "nixie-dev", "repo": "fakedir", - "rev": "b0f70b805aa86fe881eeca642f12069c7983a8e7", + "rev": "a2bd8d0eb974bcbfbc4963e7c61a7162968cee6b", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index e1cd9d7..17e5961 100644 --- a/flake.nix +++ b/flake.nix @@ -8,7 +8,7 @@ bundlers = { nix-bundle = { program, system }: let nixpkgs' = nixpkgs.legacyPackages.${system}; - fakedir = fakedir-pkgs.packages.${system}.fakedir.overrideAttrs (old: { patches = [./fakedir-null-buffer.patch]; }); + fakedir = fakedir-pkgs.packages.${system}.fakedir-universal; nix-bundle = import self { nixpkgs = nixpkgs'; inherit fakedir; }; script-linux = nixpkgs'.writeScript "startup" '' #!/bin/sh @@ -23,7 +23,8 @@ export FAKEDIR_PATTERN=/nix export FAKEDIR_TARGET="''${__TMPX_DAT_PATH}/nix" - exec "''${__TMPX_DAT_PATH}$(dirname ${program})/$(basename "$0")" "$@" + # make sure the fakedir libraries are loaded and avoid sh execing without the libraries loaded first + sh -c "test -e $DYLD_INSERT_LIBRARIES && ''${__TMPX_DAT_PATH}$(dirname ${program})/$(basename "$0") $@" ''; script = if nixpkgs'.stdenv.isDarwin then script-darwin else script-linux; in nix-bundle.makebootstrap { From 16c20b3d0d754ed416791bb933c19e6620567a2c Mon Sep 17 00:00:00 2001 From: Cassandra Comar Date: Tue, 13 Aug 2024 15:32:04 -0400 Subject: [PATCH 4/9] tested working on clean macos VM --- flake.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 17e5961..5b231dc 100644 --- a/flake.nix +++ b/flake.nix @@ -23,8 +23,8 @@ export FAKEDIR_PATTERN=/nix export FAKEDIR_TARGET="''${__TMPX_DAT_PATH}/nix" - # make sure the fakedir libraries are loaded and avoid sh execing without the libraries loaded first - sh -c "test -e $DYLD_INSERT_LIBRARIES && ''${__TMPX_DAT_PATH}$(dirname ${program})/$(basename "$0") $@" + # make sure the fakedir libraries are loaded by running the command in bash within the bottle + exec "''${__TMPX_DAT_PATH}/${nixpkgs'.bash}/bin/bash" -c "exec ''${__TMPX_DAT_PATH}$(dirname ${program})/$(basename "$0") $@" ''; script = if nixpkgs'.stdenv.isDarwin then script-darwin else script-linux; in nix-bundle.makebootstrap { From 10bcbdfd596a338c29ad0152627712d525bd4934 Mon Sep 17 00:00:00 2001 From: Cassandra Comar Date: Fri, 6 Sep 2024 16:08:09 -0400 Subject: [PATCH 5/9] fix: prefer bash over sh running into errors with a deficient sh that lacks `exec -a` when running in GitHub actions. switching to bash as a) it's available everywhere except windows and b) ensures we can still pass the bin name through as $0. --- arx-multi-bin.patch | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/arx-multi-bin.patch b/arx-multi-bin.patch index 15d904e..5e67411 100644 --- a/arx-multi-bin.patch +++ b/arx-multi-bin.patch @@ -1,8 +1,12 @@ diff --git a/model-scripts/tmpx.sh b/model-scripts/tmpx.sh -index 9c2ec6f..aa6fea9 100755 +index 9c2ec6f..0d59c1f 100755 --- a/model-scripts/tmpx.sh +++ b/model-scripts/tmpx.sh -@@ -4,6 +4,7 @@ unset rm_ dir +@@ -1,9 +1,10 @@ +-#!/bin/sh ++#!/usr/bin/env bash + set -e -u + unset rm_ dir tmp=true ; run=true tmpdir= ; rm0=true ; rm1=true ; shared=false ; hash="" # To be set by tool. token=`date -u +%FT%TZ | tr -d :-`-`hexdump -n4 -e '"%08x"' Date: Fri, 6 Sep 2024 17:48:58 -0400 Subject: [PATCH 6/9] fix: remove unneeded echos --- arx-multi-bin.patch | 5 ++--- flake.nix | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/arx-multi-bin.patch b/arx-multi-bin.patch index 5e67411..2e7dbb0 100644 --- a/arx-multi-bin.patch +++ b/arx-multi-bin.patch @@ -1,5 +1,5 @@ diff --git a/model-scripts/tmpx.sh b/model-scripts/tmpx.sh -index 9c2ec6f..0d59c1f 100755 +index 9c2ec6f..21319d6 100755 --- a/model-scripts/tmpx.sh +++ b/model-scripts/tmpx.sh @@ -1,9 +1,10 @@ @@ -34,12 +34,11 @@ index 9c2ec6f..0d59c1f 100755 cd "$dir" fi # Call the command with the reassembled ARGV, options removed. -@@ -72,7 +74,11 @@ go () { +@@ -72,7 +74,10 @@ go () { fi if $run then - ( . ../env && exec ../run "$@" ) -+ echo "${prog_name}" + ( + . ../env + exec -a "${prog_name}" bash -c ". ../run" "${prog_name}" "$@" diff --git a/flake.nix b/flake.nix index 5b231dc..f77d819 100644 --- a/flake.nix +++ b/flake.nix @@ -11,11 +11,11 @@ fakedir = fakedir-pkgs.packages.${system}.fakedir-universal; nix-bundle = import self { nixpkgs = nixpkgs'; inherit fakedir; }; script-linux = nixpkgs'.writeScript "startup" '' - #!/bin/sh + #!/usr/bin/env bash exec .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -- "$(dirname ${program})/$(basename "$0")" "$@" ''; script-darwin = nixpkgs'.writeScript "startup" '' - #!/bin/sh + #!/usr/bin/env bash # use absolute paths so the environment variables don't get reinterpreted after a cd __TMPX_DAT_PATH=$(pwd) cd "''${TMPX_RESTORE_PWD}" From 6100bf404a779cea4023a6e9806f69d8da8bae44 Mon Sep 17 00:00:00 2001 From: Cassandra Comar Date: Sat, 7 Sep 2024 15:29:31 -0400 Subject: [PATCH 7/9] fix: make the linux chroot also have CWD set properly --- default.nix | 2 +- flake.nix | 2 +- nix-user-chroot/Makefile | 2 +- nix-user-chroot/main.cpp | 137 ++++++++++++++++++++++++++------------- 4 files changed, 95 insertions(+), 48 deletions(-) diff --git a/default.nix b/default.nix index 285ada0..85fa163 100644 --- a/default.nix +++ b/default.nix @@ -91,7 +91,7 @@ in rec { # Avoid re-adding a store path into the store path = toStorePath target; script-linux = '' - exec .${nix-user-chroot'}/bin/nix-user-chroot -n ./nix ${nixUserChrootFlags} -- ".$(basename "$0")/$(dirname ${path}${run})" "$@" + exec .${nix-user-chroot'}/bin/nix-user-chroot -n ./nix -w ''${TMPX_RESTORE_PWD} ${nixUserChrootFlags} -- ".$(basename "$0")/$(dirname ${path}${run})" "$@" ''; script-macos = '' # use absolute paths so the environment variables don't get reinterpreted after a cd diff --git a/flake.nix b/flake.nix index f77d819..d67081e 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,7 @@ nix-bundle = import self { nixpkgs = nixpkgs'; inherit fakedir; }; script-linux = nixpkgs'.writeScript "startup" '' #!/usr/bin/env bash - exec .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -- "$(dirname ${program})/$(basename "$0")" "$@" + exec .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -w "''${TMPX_RESTORE_PWD}" -- "$(dirname ${program})/$(basename "$0")" "$@" ''; script-darwin = nixpkgs'.writeScript "startup" '' #!/usr/bin/env bash diff --git a/nix-user-chroot/Makefile b/nix-user-chroot/Makefile index a9a97b8..3ff0fc4 100644 --- a/nix-user-chroot/Makefile +++ b/nix-user-chroot/Makefile @@ -1,4 +1,4 @@ ENV_PATH ?= "" nix-user-chroot: main.cpp - ${CXX} -o nix-user-chroot -DENV_PATH='$(ENV_PATH)' main.cpp + ${CXX} -std=c++20 -o nix-user-chroot -DENV_PATH='$(ENV_PATH)' main.cpp diff --git a/nix-user-chroot/main.cpp b/nix-user-chroot/main.cpp index 76f2d41..04a0367 100644 --- a/nix-user-chroot/main.cpp +++ b/nix-user-chroot/main.cpp @@ -5,57 +5,72 @@ * Usage: nix-user-chroot */ +#include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include #include -#include +#include #include +#include #include -#include -#include +#include #include -#include -#include #include -#include -#include -#include +#include +#include +#include using namespace std; -#define err_exit(format, ...) { fprintf(stderr, format ": %s\n", ##__VA_ARGS__, strerror(errno)); exit(EXIT_FAILURE); } -static int child_proc(const char *rootdir, const char *nixdir, uint8_t clear_env, list dirMappings, list envMappings, const char *executable, char * const new_argv[]); +#define err_exit(format, ...) \ + { \ + fprintf(stderr, format ": %s\n", ##__VA_ARGS__, strerror(errno)); \ + exit(EXIT_FAILURE); \ + } +static int child_proc(const char *rootdir, const char *nixdir, + const char *working_dir, uint8_t clear_env, + list dirMappings, + list envMappings, const char *executable, + char *const new_argv[]); volatile uint8_t child_died = 0; int child_pid = 0; static void usage(const char *pname) { - fprintf(stderr, "Usage: %s -n -- \n", pname); - fprintf(stderr, "\t-c\tclear all env vars\n"); - fprintf(stderr, "\t-m :\tmap src on the host to dest in the sandbox\n"); - fprintf(stderr, "\t-d\tdelete all default dir mappings, may break things\n"); - fprintf(stderr, "\t-p \tpreserve the value of a variable across the -c clear\n"); - fprintf(stderr, "\t-e\tadd an /escape-hatch to the sandbox, and run (outside the sandbox) any strings written to it\n"); - - exit(EXIT_FAILURE); + fprintf(stderr, "Usage: %s -n -- \n", pname); + fprintf(stderr, "\t-c\tclear all env vars\n"); + fprintf(stderr, + "\t-m :\tmap src on the host to dest in the sandbox\n"); + fprintf(stderr, "\t-d\tdelete all default dir mappings, may break things\n"); + fprintf(stderr, + "\t-p \tpreserve the value of a variable across the -c clear\n"); + fprintf(stderr, "\t-e\tadd an /escape-hatch to the sandbox, and run (outside " + "the sandbox) any strings written to it\n"); + fprintf(stderr, "\t-w \tset the working directory before " + "executing the command\n"); + + exit(EXIT_FAILURE); } static void update_map(const char *mapping, const char *map_file) { - int fd; + int fd; - fd = open(map_file, O_WRONLY); - if (fd < 0) { - err_exit("map open"); - } + fd = open(map_file, O_WRONLY); + if (fd < 0) { + err_exit("map open"); + } - int map_len = strlen(mapping); - if (write(fd, mapping, map_len) != map_len) { - err_exit("map write"); - } + int map_len = strlen(mapping); + if (write(fd, mapping, map_len) != map_len) { + err_exit("map write"); + } - close(fd); + close(fd); } static void add_path(string src, string dest, string rootdir) { @@ -71,8 +86,10 @@ static void add_path(string src, string dest, string rootdir) { if (S_ISDIR(statbuf.st_mode)) { mkdir(path_buf2.c_str(), statbuf.st_mode & ~S_IFMT); - if (mount(src.c_str(), path_buf2.c_str(), "none", MS_BIND | MS_REC, NULL) < 0) { - fprintf(stderr, "Cannot bind mount %s to %s: %s\n", src.c_str(), path_buf2.c_str(), strerror(errno)); + if (mount(src.c_str(), path_buf2.c_str(), "none", MS_BIND | MS_REC, NULL) < + 0) { + fprintf(stderr, "Cannot bind mount %s to %s: %s\n", src.c_str(), + path_buf2.c_str(), strerror(errno)); } } else if (S_ISREG(statbuf.st_mode)) { printf("bind-mounting file %s not supported", src.c_str()); @@ -93,11 +110,11 @@ struct DirMapping parseMapping(string input) { auto pos = input.find(":"); string src = input.substr(0, pos); string dest = input.substr(pos + 1); - return (struct DirMapping){ src, dest }; + return (struct DirMapping){src, dest}; } static void handle_child_death(int signo, siginfo_t *info, void *context) { - if ( (child_pid == 0) || (info->si_pid == child_pid) ) { + if ((child_pid == 0) || (info->si_pid == child_pid)) { child_died = 1; } } @@ -106,11 +123,12 @@ int main(int argc, char *argv[]) { uint8_t clear_env = 0; uint8_t enable_escape_hatch = 0; char *nixdir = NULL; + const char *working_dir = "/"; list dirMappings; list envMappings; const char *t; -#define x(y) dirMappings.push_back({ "/" y, y }) +#define x(y) dirMappings.push_back({"/" y, y}) x("dev"); x("proc"); x("sys"); @@ -124,7 +142,7 @@ int main(int argc, char *argv[]) { #undef x int opt; - while ((opt = getopt(argc, argv, "cen:m:dp:")) != -1) { + while ((opt = getopt(argc, argv, "cen:m:dp:w:")) != -1) { switch (opt) { case 'c': clear_env = 1; @@ -148,9 +166,12 @@ int main(int argc, char *argv[]) { case 'p': t = getenv(optarg); if (t) { - envMappings.push_back({ optarg, t }); + envMappings.push_back({optarg, t}); } break; + case 'w': + working_dir = realpath(optarg, NULL); + break; } } @@ -159,6 +180,21 @@ int main(int argc, char *argv[]) { exit(EXIT_FAILURE); } + auto is_not_ancestor = [working_dir](DirMapping dir) { + auto wd_path = filesystem::path(working_dir); + auto base_path = filesystem::path(dir.src); + auto mismatch_pair = mismatch(wd_path.begin(), wd_path.end(), + base_path.begin(), base_path.end()); + return mismatch_pair.second != base_path.end(); + }; + bool not_mapped = + all_of(dirMappings.begin(), dirMappings.end(), is_not_ancestor); + auto wd_path = filesystem::path(working_dir); + if (not_mapped && wd_path.string() != "/") { + filesystem::path first_parent = *++wd_path.begin(); + dirMappings.push_back({"/" + first_parent.string(), first_parent.string()}); + } + if (argc <= optind) { usage(argv[0]); } @@ -204,7 +240,8 @@ int main(int argc, char *argv[]) { char buf[10]; read(unrace[0], buf, 10); close(unrace[0]); - return child_proc(rootdir, nixdir, clear_env, dirMappings, envMappings, argv[optind], argv + optind); + return child_proc(rootdir, nixdir, working_dir, clear_env, dirMappings, + envMappings, argv[optind], argv + optind); } else { close(unrace[0]); char fifopath[PATH_MAX]; @@ -218,7 +255,8 @@ int main(int argc, char *argv[]) { while (!child_died) { int fd = open(fifopath, O_RDONLY); if (fd < 0) { - if (errno == EINTR) continue; + if (errno == EINTR) + continue; fprintf(stderr, "error opening escape-hatch: %s\n", strerror(errno)); continue; } @@ -234,7 +272,11 @@ int main(int argc, char *argv[]) { } } -static int child_proc(const char *rootdir, const char *nixdir, uint8_t clear_env, list dirMappings, list envMappings, const char *executable, char * const new_argv[]) { +static int child_proc(const char *rootdir, const char *nixdir, + const char *working_dir, uint8_t clear_env, + list dirMappings, + list envMappings, const char *executable, + char *const new_argv[]) { // get uid, gid before going to new namespace uid_t uid = getuid(); gid_t gid = getgid(); @@ -242,7 +284,11 @@ static int child_proc(const char *rootdir, const char *nixdir, uint8_t clear_env // "unshare" into new namespace if (unshare(CLONE_NEWNS | CLONE_NEWUSER) < 0) { if (errno == EPERM) { - fputs("Run the following to enable unprivileged namespace use:\nsudo bash -c \"sysctl -w kernel.unprivileged_userns_clone=1 ; echo kernel.unprivileged_userns_clone=1 > /etc/sysctl.d/nix-user-chroot.conf\"\n\n", stderr); + fputs("Run the following to enable unprivileged namespace use:\nsudo " + "bash -c \"sysctl -w kernel.unprivileged_userns_clone=1 ; echo " + "kernel.unprivileged_userns_clone=1 > " + "/etc/sysctl.d/nix-user-chroot.conf\"\n\n", + stderr); exit(EXIT_FAILURE); } else { err_exit("unshare()"); @@ -251,7 +297,7 @@ static int child_proc(const char *rootdir, const char *nixdir, uint8_t clear_env // add necessary system stuff to rootdir namespace for (list::iterator it = dirMappings.begin(); - it != dirMappings.end(); ++it) { + it != dirMappings.end(); ++it) { struct DirMapping m = *it; add_path(m.src, m.dest, rootdir); } @@ -290,9 +336,10 @@ static int child_proc(const char *rootdir, const char *nixdir, uint8_t clear_env err_exit("chroot(%s)", rootdir); } - chdir("/"); + chdir(working_dir); - if (clear_env) clearenv(); + if (clear_env) + clearenv(); setenv("PATH", ENV_PATH, 1); for (list::iterator it = envMappings.begin(); From 71db641c722f1e084292f3bbd9381f39df1f68c0 Mon Sep 17 00:00:00 2001 From: Cassandra Comar Date: Mon, 16 Sep 2024 19:33:00 -0400 Subject: [PATCH 8/9] fix: multi-argument commands --- flake.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index d67081e..8206cdb 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,7 @@ nix-bundle = import self { nixpkgs = nixpkgs'; inherit fakedir; }; script-linux = nixpkgs'.writeScript "startup" '' #!/usr/bin/env bash - exec .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -w "''${TMPX_RESTORE_PWD}" -- "$(dirname ${program})/$(basename "$0")" "$@" + exec .${nix-bundle.nix-user-chroot}/bin/nix-user-chroot -n ./nix -w "''${TMPX_RESTORE_PWD}" -- "$(dirname ${program})/$(basename $0)" "$@" ''; script-darwin = nixpkgs'.writeScript "startup" '' #!/usr/bin/env bash @@ -24,7 +24,9 @@ export FAKEDIR_TARGET="''${__TMPX_DAT_PATH}/nix" # make sure the fakedir libraries are loaded by running the command in bash within the bottle - exec "''${__TMPX_DAT_PATH}/${nixpkgs'.bash}/bin/bash" -c "exec ''${__TMPX_DAT_PATH}$(dirname ${program})/$(basename "$0") $@" + exec "''${__TMPX_DAT_PATH}/${nixpkgs'.bash}/bin/bash" <<-EOH + ''${__TMPX_DAT_PATH}$(dirname ${program})/$(basename $0) $@ + EOH ''; script = if nixpkgs'.stdenv.isDarwin then script-darwin else script-linux; in nix-bundle.makebootstrap { From 30c2ad34a5999251e9f08c157e0bd3c4d372a889 Mon Sep 17 00:00:00 2001 From: Cassandra Comar Date: Tue, 17 Sep 2024 12:37:18 -0400 Subject: [PATCH 9/9] fix: ensure stdin still gets passed through --- flake.nix | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 8206cdb..83a840b 100644 --- a/flake.nix +++ b/flake.nix @@ -24,9 +24,12 @@ export FAKEDIR_TARGET="''${__TMPX_DAT_PATH}/nix" # make sure the fakedir libraries are loaded by running the command in bash within the bottle - exec "''${__TMPX_DAT_PATH}/${nixpkgs'.bash}/bin/bash" <<-EOH - ''${__TMPX_DAT_PATH}$(dirname ${program})/$(basename $0) $@ + cmd="$(cat <<-EOH + ''${__TMPX_DAT_PATH}$(dirname ${program})/$(basename $0) EOH + )" + args="$@" + exec "''${__TMPX_DAT_PATH}/${nixpkgs'.bash}/bin/bash" -c "$cmd $args" ''; script = if nixpkgs'.stdenv.isDarwin then script-darwin else script-linux; in nix-bundle.makebootstrap {