diff options
author | Ludovic Courtès <ludovic.courtes@inria.fr> | 2020-07-28 10:48:50 +0200 |
---|---|---|
committer | Ludovic Courtès <ludo@gnu.org> | 2020-07-28 14:41:04 +0200 |
commit | c6c0d5a22c2ee3d7164dab0129b2e4852a4ae76c (patch) | |
tree | 366c2a3fccf91bc032e50692bf1dcf62ffa9ad1b | |
parent | 8b221b64a552d31e241701aa5c6d339287a7a15b (diff) | |
download | guix-c6c0d5a22c2ee3d7164dab0129b2e4852a4ae76c.tar.gz guix-c6c0d5a22c2ee3d7164dab0129b2e4852a4ae76c.zip |
pack: "fakechroot" execution engine can load its audit module.
Fixes <https://bugs.gnu.org/42558>.
Until now, loading 'pack-audit.so' in a truly non-Guix environment would
usually fail because 'pack-audit.so' depends on 'libgcc_s.so' and
'libc.so', none of which could be found.
Furthermore, the test was not working as expected: the trick
unshare -mrf sh -c 'mount -t tmpfs none /gnu ; ...'
would allow the fakechroot engine to make its store available as
/gnu/store as a result of another bug.
* gnu/packages/aux-files/run-in-namespace.c (relocated_search_path): New
function.
(exec_with_loader): Pass "--library-path" to the loader.
* guix/scripts/pack.scm (wrapped-package)[build](runpath): New procedure.
(elf-loader-compile-flags): Pass "-DLOADER_AUDIT_RUNPATH".
* tests/guix-pack-relocatable.sh: Remove 'STORE_PARENT'.
(run_without_store): New function. Erase $NIX_STORE_DIR instead of
$STORE_PARENT.
Use 'run_without_store' throughout.
-rw-r--r-- | gnu/packages/aux-files/run-in-namespace.c | 48 | ||||
-rw-r--r-- | guix/scripts/pack.scm | 17 | ||||
-rw-r--r-- | tests/guix-pack-relocatable.sh | 76 |
3 files changed, 99 insertions, 42 deletions
diff --git a/gnu/packages/aux-files/run-in-namespace.c b/gnu/packages/aux-files/run-in-namespace.c index 5a6b932b87..7f7e5c6885 100644 --- a/gnu/packages/aux-files/run-in-namespace.c +++ b/gnu/packages/aux-files/run-in-namespace.c @@ -371,24 +371,64 @@ exec_with_proot (const char *store, int argc, char *argv[]) #if HAVE_EXEC_WITH_LOADER +/* Traverse PATH, a NULL-terminated string array, and return a colon-separated + search path where each item of PATH has been relocated to STORE. The + result is malloc'd. */ +static char * +relocated_search_path (const char *path[], const char *store) +{ + char *new_path; + size_t size = 0; + + for (size_t i = 0; path[i] != NULL; i++) + size += strlen (store) + strlen (path[i]) + 1; /* upper bound */ + + new_path = xmalloc (size + 1); + new_path[0] = '\0'; + + for (size_t i = 0; path[i] != NULL; i++) + { + if (strncmp (path[i], original_store, + sizeof original_store - 1) == 0) + { + strcat (new_path, store); + strcat (new_path, path[i] + sizeof original_store - 1); + } + else + strcat (new_path, path[i]); /* possibly $ORIGIN */ + + strcat (new_path, ":"); + } + + new_path[strlen (new_path) - 1] = '\0'; /* Remove trailing colon. */ + + return new_path; +} + /* Execute the wrapped program by invoking the loader (ld.so) directly, passing it the audit module and preloading libfakechroot.so. */ static void exec_with_loader (const char *store, int argc, char *argv[]) { + static const char *audit_library_path[] = LOADER_AUDIT_RUNPATH; char *loader = concat (store, PROGRAM_INTERPRETER + sizeof original_store); - size_t loader_specific_argc = 6; + size_t loader_specific_argc = 8; size_t loader_argc = argc + loader_specific_argc; char *loader_argv[loader_argc + 1]; loader_argv[0] = argv[0]; loader_argv[1] = "--audit"; loader_argv[2] = concat (store, LOADER_AUDIT_MODULE + sizeof original_store); - loader_argv[3] = "--preload"; - loader_argv[4] = concat (store, + + /* The audit module depends on libc.so and libgcc_s.so. */ + loader_argv[3] = "--library-path"; + loader_argv[4] = relocated_search_path (audit_library_path, store); + + loader_argv[5] = "--preload"; + loader_argv[6] = concat (store, FAKECHROOT_LIBRARY + sizeof original_store); - loader_argv[5] = concat (store, + loader_argv[7] = concat (store, "@WRAPPED_PROGRAM@" + sizeof original_store); for (size_t i = 0; i < argc; i++) diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm index 5fb6aaae0c..75386deee7 100644 --- a/guix/scripts/pack.scm +++ b/guix/scripts/pack.scm @@ -744,11 +744,13 @@ last resort for relocation." (with-imported-modules (source-module-closure '((guix build utils) (guix build union) + (guix build gremlin) (guix elf))) #~(begin (use-modules (guix build utils) ((guix build union) #:select (relative-file-name)) (guix elf) + (guix build gremlin) (ice-9 binary-ports) (ice-9 ftw) (ice-9 match) @@ -786,6 +788,14 @@ last resort for relocation." bv 0 (bytevector-length bv)) (utf8->string bv))))) + (define (runpath file) + ;; Return the RUNPATH of FILE as a list of directories. + (let* ((bv (call-with-input-file file get-bytevector-all)) + (elf (parse-elf bv)) + (dyninfo (elf-dynamic-info elf))) + (or (and=> dyninfo elf-dynamic-info-runpath) + '()))) + (define (elf-loader-compile-flags program) ;; Return the cpp flags defining macros for the ld.so/fakechroot ;; wrapper of PROGRAM. @@ -807,6 +817,13 @@ last resort for relocation." (string-append "-DLOADER_AUDIT_MODULE=\"" #$(audit-module) "\"") + (string-append "-DLOADER_AUDIT_RUNPATH={ " + (string-join + (map object->string + (runpath + #$(audit-module))) + ", " 'suffix) + "NULL }") (if gconv (string-append "-DGCONV_DIRECTORY=\"" gconv "\"") diff --git a/tests/guix-pack-relocatable.sh b/tests/guix-pack-relocatable.sh index 52d7212594..1ba3889036 100644 --- a/tests/guix-pack-relocatable.sh +++ b/tests/guix-pack-relocatable.sh @@ -38,51 +38,52 @@ then exit 77 fi -STORE_PARENT="`dirname $NIX_STORE_DIR`" -export STORE_PARENT -if test "$STORE_PARENT" = "/"; then exit 77; fi - -if unshare -mrf sh -c 'mount -t tmpfs none "$STORE_PARENT"' -then - # Test the wrapper that relies on user namespaces. - relocatable_option="-R" -else - case "`uname -m`" in - x86_64|i?86) - # Test the wrapper that falls back to PRoot. - relocatable_option="-RR";; - *) - # XXX: Our 'proot' package currently fails tests on non-Intel - # architectures, so skip this by default. - exit 77;; - esac -fi +# Attempt to run the given command in a namespace where the store is +# invisible. This makes sure the presence of the store does not hide +# problems. +run_without_store () +{ + if unshare -r true # Are user namespaces supported? + then + # Run that relocatable executable in a user namespace where we "erase" + # the store by mounting an empty file system on top of it. That way, + # we exercise the wrapper code that creates the user namespace and + # bind-mounts the store. + unshare -mrf sh -c 'mount -t tmpfs -o ro none "$NIX_STORE_DIR"; '"$*" + else + # Run the relocatable program in the current namespaces. This is a + # weak test because we're going to access store items from the host + # store. + $* + fi +} test_directory="`mktemp -d`" export test_directory trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT -export relocatable_option -tarball="`guix pack $relocatable_option -S /Bin=bin sed`" -(cd "$test_directory"; tar xvf "$tarball") - -if unshare -r true # Are user namespaces supported? +if unshare -r true then - # Run that relocatable 'sed' in a user namespace where we "erase" the store by - # mounting an empty file system on top of it. That way, we exercise the - # wrapper code that creates the user namespace and bind-mounts the store. - unshare -mrf sh -c 'mount -t tmpfs none "$STORE_PARENT"; echo "$STORE_PARENT"/*; "$test_directory/Bin/sed" --version > "$test_directory/output"' + # Test the 'userns' execution engine. + tarball="`guix pack -R -S /Bin=bin sed`" + (cd "$test_directory"; tar xvf "$tarball") + + run_without_store "$test_directory/Bin/sed" --version > "$test_directory/output" + grep 'GNU sed' "$test_directory/output" + + # Same with an explicit engine. + run_without_store GUIX_EXECUTION_ENGINE="userns" \ + "$test_directory/Bin/sed" --version > "$test_directory/output" + grep 'GNU sed' "$test_directory/output" # Check whether the exit code is preserved. - if unshare -mrf sh -c 'mount -t tmpfs none "$STORE_PARENT"; echo "$STORE_PARENT"/*; "$test_directory/Bin/sed" --does-not-exist'; + if run_without_store "$test_directory/Bin/sed" --does-not-exist; then false; else true; fi + + chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/* else - # Run the relocatable 'sed' in the current namespaces. This is a weak - # test because we're going to access store items from the host store. - "$test_directory/Bin/sed" --version > "$test_directory/output" + echo "'userns' execution tests skipped" >&2 fi -grep 'GNU sed' "$test_directory/output" -chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/* case "`uname -m`" in x86_64|i?86) @@ -90,20 +91,19 @@ case "`uname -m`" in tarball="`guix pack -RR -S /Bin=bin sed`" tar tvf "$tarball" | grep /bin/proot (cd "$test_directory"; tar xvf "$tarball") - GUIX_EXECUTION_ENGINE="proot" - export GUIX_EXECUTION_ENGINE + run_without_store GUIX_EXECUTION_ENGINE="proot" \ "$test_directory/Bin/sed" --version > "$test_directory/output" grep 'GNU sed' "$test_directory/output" # Now with fakechroot. - GUIX_EXECUTION_ENGINE="fakechroot" + run_without_store GUIX_EXECUTION_ENGINE="fakechroot" \ "$test_directory/Bin/sed" --version > "$test_directory/output" grep 'GNU sed' "$test_directory/output" chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/* ;; *) - echo "skipping PRoot test" >&2 + echo "skipping PRoot and Fakechroot tests" >&2 ;; esac |