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.
This commit is contained in:
parent
8b221b64a5
commit
c6c0d5a22c
3 changed files with 99 additions and 42 deletions
|
@ -371,24 +371,64 @@ exec_with_proot (const char *store, int argc, char *argv[])
|
||||||
|
|
||||||
#if HAVE_EXEC_WITH_LOADER
|
#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,
|
/* Execute the wrapped program by invoking the loader (ld.so) directly,
|
||||||
passing it the audit module and preloading libfakechroot.so. */
|
passing it the audit module and preloading libfakechroot.so. */
|
||||||
static void
|
static void
|
||||||
exec_with_loader (const char *store, int argc, char *argv[])
|
exec_with_loader (const char *store, int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
static const char *audit_library_path[] = LOADER_AUDIT_RUNPATH;
|
||||||
char *loader = concat (store,
|
char *loader = concat (store,
|
||||||
PROGRAM_INTERPRETER + sizeof original_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;
|
size_t loader_argc = argc + loader_specific_argc;
|
||||||
char *loader_argv[loader_argc + 1];
|
char *loader_argv[loader_argc + 1];
|
||||||
loader_argv[0] = argv[0];
|
loader_argv[0] = argv[0];
|
||||||
loader_argv[1] = "--audit";
|
loader_argv[1] = "--audit";
|
||||||
loader_argv[2] = concat (store,
|
loader_argv[2] = concat (store,
|
||||||
LOADER_AUDIT_MODULE + sizeof original_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);
|
FAKECHROOT_LIBRARY + sizeof original_store);
|
||||||
loader_argv[5] = concat (store,
|
loader_argv[7] = concat (store,
|
||||||
"@WRAPPED_PROGRAM@" + sizeof original_store);
|
"@WRAPPED_PROGRAM@" + sizeof original_store);
|
||||||
|
|
||||||
for (size_t i = 0; i < argc; i++)
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
|
|
@ -744,11 +744,13 @@ last resort for relocation."
|
||||||
(with-imported-modules (source-module-closure
|
(with-imported-modules (source-module-closure
|
||||||
'((guix build utils)
|
'((guix build utils)
|
||||||
(guix build union)
|
(guix build union)
|
||||||
|
(guix build gremlin)
|
||||||
(guix elf)))
|
(guix elf)))
|
||||||
#~(begin
|
#~(begin
|
||||||
(use-modules (guix build utils)
|
(use-modules (guix build utils)
|
||||||
((guix build union) #:select (relative-file-name))
|
((guix build union) #:select (relative-file-name))
|
||||||
(guix elf)
|
(guix elf)
|
||||||
|
(guix build gremlin)
|
||||||
(ice-9 binary-ports)
|
(ice-9 binary-ports)
|
||||||
(ice-9 ftw)
|
(ice-9 ftw)
|
||||||
(ice-9 match)
|
(ice-9 match)
|
||||||
|
@ -786,6 +788,14 @@ last resort for relocation."
|
||||||
bv 0 (bytevector-length bv))
|
bv 0 (bytevector-length bv))
|
||||||
(utf8->string 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)
|
(define (elf-loader-compile-flags program)
|
||||||
;; Return the cpp flags defining macros for the ld.so/fakechroot
|
;; Return the cpp flags defining macros for the ld.so/fakechroot
|
||||||
;; wrapper of PROGRAM.
|
;; wrapper of PROGRAM.
|
||||||
|
@ -807,6 +817,13 @@ last resort for relocation."
|
||||||
|
|
||||||
(string-append "-DLOADER_AUDIT_MODULE=\""
|
(string-append "-DLOADER_AUDIT_MODULE=\""
|
||||||
#$(audit-module) "\"")
|
#$(audit-module) "\"")
|
||||||
|
(string-append "-DLOADER_AUDIT_RUNPATH={ "
|
||||||
|
(string-join
|
||||||
|
(map object->string
|
||||||
|
(runpath
|
||||||
|
#$(audit-module)))
|
||||||
|
", " 'suffix)
|
||||||
|
"NULL }")
|
||||||
(if gconv
|
(if gconv
|
||||||
(string-append "-DGCONV_DIRECTORY=\""
|
(string-append "-DGCONV_DIRECTORY=\""
|
||||||
gconv "\"")
|
gconv "\"")
|
||||||
|
|
|
@ -38,51 +38,52 @@ then
|
||||||
exit 77
|
exit 77
|
||||||
fi
|
fi
|
||||||
|
|
||||||
STORE_PARENT="`dirname $NIX_STORE_DIR`"
|
# Attempt to run the given command in a namespace where the store is
|
||||||
export STORE_PARENT
|
# invisible. This makes sure the presence of the store does not hide
|
||||||
if test "$STORE_PARENT" = "/"; then exit 77; fi
|
# problems.
|
||||||
|
run_without_store ()
|
||||||
if unshare -mrf sh -c 'mount -t tmpfs none "$STORE_PARENT"'
|
{
|
||||||
then
|
if unshare -r true # Are user namespaces supported?
|
||||||
# Test the wrapper that relies on user namespaces.
|
then
|
||||||
relocatable_option="-R"
|
# Run that relocatable executable in a user namespace where we "erase"
|
||||||
else
|
# the store by mounting an empty file system on top of it. That way,
|
||||||
case "`uname -m`" in
|
# we exercise the wrapper code that creates the user namespace and
|
||||||
x86_64|i?86)
|
# bind-mounts the store.
|
||||||
# Test the wrapper that falls back to PRoot.
|
unshare -mrf sh -c 'mount -t tmpfs -o ro none "$NIX_STORE_DIR"; '"$*"
|
||||||
relocatable_option="-RR";;
|
else
|
||||||
*)
|
# Run the relocatable program in the current namespaces. This is a
|
||||||
# XXX: Our 'proot' package currently fails tests on non-Intel
|
# weak test because we're going to access store items from the host
|
||||||
# architectures, so skip this by default.
|
# store.
|
||||||
exit 77;;
|
$*
|
||||||
esac
|
fi
|
||||||
fi
|
}
|
||||||
|
|
||||||
test_directory="`mktemp -d`"
|
test_directory="`mktemp -d`"
|
||||||
export test_directory
|
export test_directory
|
||||||
trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT
|
trap 'chmod -Rf +w "$test_directory"; rm -rf "$test_directory"' EXIT
|
||||||
|
|
||||||
export relocatable_option
|
if unshare -r true
|
||||||
tarball="`guix pack $relocatable_option -S /Bin=bin sed`"
|
|
||||||
(cd "$test_directory"; tar xvf "$tarball")
|
|
||||||
|
|
||||||
if unshare -r true # Are user namespaces supported?
|
|
||||||
then
|
then
|
||||||
# Run that relocatable 'sed' in a user namespace where we "erase" the store by
|
# Test the 'userns' execution engine.
|
||||||
# mounting an empty file system on top of it. That way, we exercise the
|
tarball="`guix pack -R -S /Bin=bin sed`"
|
||||||
# wrapper code that creates the user namespace and bind-mounts the store.
|
(cd "$test_directory"; tar xvf "$tarball")
|
||||||
unshare -mrf sh -c 'mount -t tmpfs none "$STORE_PARENT"; echo "$STORE_PARENT"/*; "$test_directory/Bin/sed" --version > "$test_directory/output"'
|
|
||||||
|
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.
|
# 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
|
then false; else true; fi
|
||||||
|
|
||||||
|
chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/*
|
||||||
else
|
else
|
||||||
# Run the relocatable 'sed' in the current namespaces. This is a weak
|
echo "'userns' execution tests skipped" >&2
|
||||||
# test because we're going to access store items from the host store.
|
|
||||||
"$test_directory/Bin/sed" --version > "$test_directory/output"
|
|
||||||
fi
|
fi
|
||||||
grep 'GNU sed' "$test_directory/output"
|
|
||||||
chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/*
|
|
||||||
|
|
||||||
case "`uname -m`" in
|
case "`uname -m`" in
|
||||||
x86_64|i?86)
|
x86_64|i?86)
|
||||||
|
@ -90,20 +91,19 @@ case "`uname -m`" in
|
||||||
tarball="`guix pack -RR -S /Bin=bin sed`"
|
tarball="`guix pack -RR -S /Bin=bin sed`"
|
||||||
tar tvf "$tarball" | grep /bin/proot
|
tar tvf "$tarball" | grep /bin/proot
|
||||||
(cd "$test_directory"; tar xvf "$tarball")
|
(cd "$test_directory"; tar xvf "$tarball")
|
||||||
GUIX_EXECUTION_ENGINE="proot"
|
run_without_store GUIX_EXECUTION_ENGINE="proot" \
|
||||||
export GUIX_EXECUTION_ENGINE
|
|
||||||
"$test_directory/Bin/sed" --version > "$test_directory/output"
|
"$test_directory/Bin/sed" --version > "$test_directory/output"
|
||||||
grep 'GNU sed' "$test_directory/output"
|
grep 'GNU sed' "$test_directory/output"
|
||||||
|
|
||||||
# Now with fakechroot.
|
# Now with fakechroot.
|
||||||
GUIX_EXECUTION_ENGINE="fakechroot"
|
run_without_store GUIX_EXECUTION_ENGINE="fakechroot" \
|
||||||
"$test_directory/Bin/sed" --version > "$test_directory/output"
|
"$test_directory/Bin/sed" --version > "$test_directory/output"
|
||||||
grep 'GNU sed' "$test_directory/output"
|
grep 'GNU sed' "$test_directory/output"
|
||||||
|
|
||||||
chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/*
|
chmod -Rf +w "$test_directory"; rm -rf "$test_directory"/*
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "skipping PRoot test" >&2
|
echo "skipping PRoot and Fakechroot tests" >&2
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|
Reference in a new issue