diff options
author | Ludovic Courtès <ludovic.courtes@inria.fr> | 2020-05-07 22:49:20 +0200 |
---|---|---|
committer | Ludovic Courtès <ludo@gnu.org> | 2020-05-14 17:21:27 +0200 |
commit | 6456232164890dbf5aa20394ee24637feb4b7b9e (patch) | |
tree | 8fbdad7a851dd1762756c7178864d6919a62c00f /gnu/packages/aux-files/run-in-namespace.c | |
parent | 4449e7c5e4c8b746c786fc9a5ea82eab60f6c846 (diff) | |
download | guix-6456232164890dbf5aa20394ee24637feb4b7b9e.tar guix-6456232164890dbf5aa20394ee24637feb4b7b9e.tar.gz |
pack: Add relocation via ld.so and fakechroot.
* gnu/packages/aux-files/run-in-namespace.c (HAVE_EXEC_WITH_LOADER): New
macro.
(bind_mount): Rename to...
(mirror_directory): ... this. Add 'firmlink' argument and use it
instead of calling mkdir/open/close/mount directly.
(bind_mount, make_symlink): New functions.
(exec_in_user_namespace): Adjust accordingly.
(exec_with_loader) [HAVE_EXEC_WITH_LOADER]: New function.
(exec_performance): New function.
(engines): Add them.
* guix/scripts/pack.scm (wrapped-package)[fakechroot-library]
[audit-module]: New procedures.
[audit-source]: New variable.
[build](elf-interpreter, elf-loader-compile-flags): New procedures.
(build-wrapper): Use them.
* tests/guix-pack-relocatable.sh: Test with
'GUIX_EXECUTION_ENGINE=fakechroot'.
* doc/guix.texi (Invoking guix pack): Document the 'performance' and
'fakechroot' engines.
* gnu/packages/aux-files/pack-audit.c: New file.
* Makefile.am (AUX_FILES): Add it.
Diffstat (limited to 'gnu/packages/aux-files/run-in-namespace.c')
-rw-r--r-- | gnu/packages/aux-files/run-in-namespace.c | 160 |
1 files changed, 145 insertions, 15 deletions
diff --git a/gnu/packages/aux-files/run-in-namespace.c b/gnu/packages/aux-files/run-in-namespace.c index 6e97359078..5a6b932b87 100644 --- a/gnu/packages/aux-files/run-in-namespace.c +++ b/gnu/packages/aux-files/run-in-namespace.c @@ -42,6 +42,11 @@ #include <dirent.h> #include <sys/syscall.h> +/* Whether we're building the ld.so/libfakechroot wrapper. */ +#define HAVE_EXEC_WITH_LOADER \ + (defined PROGRAM_INTERPRETER) && (defined LOADER_AUDIT_MODULE) \ + && (defined FAKECHROOT_LIBRARY) + /* The original store, "/gnu/store" by default. */ static const char original_store[] = "@STORE_DIRECTORY@"; @@ -117,9 +122,42 @@ rm_rf (const char *directory) assert_perror (errno); } -/* Bind mount all the top-level entries in SOURCE to TARGET. */ +/* Make TARGET a bind-mount of SOURCE. Take into account ENTRY's type, which + corresponds to SOURCE. */ +static int +bind_mount (const char *source, const struct dirent *entry, + const char *target) +{ + if (entry->d_type == DT_DIR) + { + int err = mkdir (target, 0700); + if (err != 0) + return err; + } + else + close (open (target, O_WRONLY | O_CREAT)); + + return mount (source, target, "none", + MS_BIND | MS_REC | MS_RDONLY, NULL); +} + +#if HAVE_EXEC_WITH_LOADER + +/* Make TARGET a symlink to SOURCE. */ +static int +make_symlink (const char *source, const struct dirent *entry, + const char *target) +{ + return symlink (source, target); +} + +#endif + +/* Mirror with FIRMLINK all the top-level entries in SOURCE to TARGET. */ static void -bind_mount (const char *source, const char *target) +mirror_directory (const char *source, const char *target, + int (* firmlink) (const char *, const struct dirent *, + const char *)) { DIR *stream = opendir (source); @@ -154,17 +192,7 @@ bind_mount (const char *source, const char *target) else { /* Create the mount point. */ - if (entry->d_type == DT_DIR) - { - int err = mkdir (new_entry, 0700); - if (err != 0) - assert_perror (errno); - } - else - close (open (new_entry, O_WRONLY | O_CREAT)); - - int err = mount (abs_source, new_entry, "none", - MS_BIND | MS_REC | MS_RDONLY, NULL); + int err = firmlink (abs_source, entry, new_entry); /* It used to be that only directories could be bind-mounted. Thus, keep going if we fail to bind-mount a non-directory entry. @@ -248,7 +276,7 @@ exec_in_user_namespace (const char *store, int argc, char *argv[]) /* Note: Due to <https://bugzilla.kernel.org/show_bug.cgi?id=183461> we cannot make NEW_ROOT a tmpfs (which would have saved the need for 'rm_rf'.) */ - bind_mount ("/", new_root); + mirror_directory ("/", new_root, bind_mount); mkdir_p (new_store); err = mount (store, new_store, "none", MS_BIND | MS_REC | MS_RDONLY, NULL); @@ -341,6 +369,92 @@ exec_with_proot (const char *store, int argc, char *argv[]) #endif +#if HAVE_EXEC_WITH_LOADER + +/* 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[]) +{ + char *loader = concat (store, + PROGRAM_INTERPRETER + sizeof original_store); + size_t loader_specific_argc = 6; + 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, + FAKECHROOT_LIBRARY + sizeof original_store); + loader_argv[5] = concat (store, + "@WRAPPED_PROGRAM@" + sizeof original_store); + + for (size_t i = 0; i < argc; i++) + loader_argv[i + loader_specific_argc] = argv[i + 1]; + + loader_argv[loader_argc] = NULL; + + /* Set up the root directory. */ + int err; + char *new_root = mkdtemp (strdup ("/tmp/guix-exec-XXXXXX")); + mirror_directory ("/", new_root, make_symlink); + + char *new_store = concat (new_root, original_store); + char *new_store_parent = dirname (strdup (new_store)); + mkdir_p (new_store_parent); + symlink (store, new_store); + +#ifdef GCONV_DIRECTORY + /* Tell libc where to find its gconv modules. This is necessary because + gconv uses non-interposable 'open' calls. */ + char *gconv_path = concat (store, + GCONV_DIRECTORY + sizeof original_store); + setenv ("GCONV_PATH", gconv_path, 1); + free (gconv_path); +#endif + + setenv ("FAKECHROOT_BASE", new_root, 1); + + pid_t child = fork (); + switch (child) + { + case 0: + err = execv (loader, loader_argv); + if (err < 0) + assert_perror (errno); + exit (EXIT_FAILURE); + break; + + case -1: + assert_perror (errno); + exit (EXIT_FAILURE); + break; + + default: + { + int status; + waitpid (child, &status, 0); + chdir ("/"); /* avoid EBUSY */ + rm_rf (new_root); + free (new_root); + + close (2); /* flushing stderr should be silent */ + + if (WIFEXITED (status)) + exit (WEXITSTATUS (status)); + else + /* Abnormal termination cannot really be reproduced, so exit + with 255. */ + exit (255); + } + } +} + +#endif + + /* Execution engines. */ struct engine @@ -356,7 +470,7 @@ buffer_stderr (void) setvbuf (stderr, stderr_buffer, _IOFBF, sizeof stderr_buffer); } -/* The default engine. */ +/* The default engine: choose a robust method. */ static void exec_default (const char *store, int argc, char *argv[]) { @@ -370,14 +484,30 @@ exec_default (const char *store, int argc, char *argv[]) #endif } +/* The "performance" engine: choose performance over robustness. */ +static void +exec_performance (const char *store, int argc, char *argv[]) +{ + buffer_stderr (); + + exec_in_user_namespace (store, argc, argv); +#if HAVE_EXEC_WITH_LOADER + exec_with_loader (store, argc, argv); +#endif +} + /* List of supported engines. */ static const struct engine engines[] = { { "default", exec_default }, + { "performance", exec_performance }, { "userns", exec_in_user_namespace }, #ifdef PROOT_PROGRAM { "proot", exec_with_proot }, #endif +#if HAVE_EXEC_WITH_LOADER + { "fakechroot", exec_with_loader }, +#endif { NULL, NULL } }; |