aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludovic.courtes@inria.fr>2020-07-28 10:48:50 +0200
committerLudovic Courtès <ludo@gnu.org>2020-07-28 14:41:04 +0200
commitc6c0d5a22c2ee3d7164dab0129b2e4852a4ae76c (patch)
tree366c2a3fccf91bc032e50692bf1dcf62ffa9ad1b
parent8b221b64a552d31e241701aa5c6d339287a7a15b (diff)
downloadguix-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.c48
-rw-r--r--guix/scripts/pack.scm17
-rw-r--r--tests/guix-pack-relocatable.sh76
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