From 6456232164890dbf5aa20394ee24637feb4b7b9e Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Thu, 7 May 2020 22:49:20 +0200 Subject: 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. --- gnu/packages/aux-files/pack-audit.c | 85 ++++++++++++++++ gnu/packages/aux-files/run-in-namespace.c | 160 +++++++++++++++++++++++++++--- 2 files changed, 230 insertions(+), 15 deletions(-) create mode 100644 gnu/packages/aux-files/pack-audit.c (limited to 'gnu/packages/aux-files') diff --git a/gnu/packages/aux-files/pack-audit.c b/gnu/packages/aux-files/pack-audit.c new file mode 100644 index 0000000000..374787e8b9 --- /dev/null +++ b/gnu/packages/aux-files/pack-audit.c @@ -0,0 +1,85 @@ +/* GNU Guix --- Functional package management for GNU + Copyright (C) 2020 Ludovic Courtès + + This file is part of GNU Guix. + + GNU Guix is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or (at + your option) any later version. + + GNU Guix is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNU Guix. If not, see . */ + +/* This file implements part of the GNU ld.so audit interface. It is used by + the "fakechroot" engine of the 'guix pack -RR' wrappers to make sure the + loader looks for shared objects under the "fake" root directory. */ + +#define _GNU_SOURCE 1 + +#include + +#include +#include +#include +#include + +/* The pseudo root directory and store that we are relocating to. */ +static const char *root_directory; +static char *store; + +/* The original store, "/gnu/store" by default. */ +static const char original_store[] = "@STORE_DIRECTORY@"; + +/* Like 'malloc', but abort if 'malloc' returns NULL. */ +static void * +xmalloc (size_t size) +{ + void *result = malloc (size); + assert (result != NULL); + return result; +} + +unsigned int +la_version (unsigned int v) +{ + if (v != LAV_CURRENT) + error (1, 0, "cannot handle interface version %u", v); + + root_directory = getenv ("FAKECHROOT_BASE"); + if (root_directory == NULL) + error (1, 0, "'FAKECHROOT_BASE' is not set"); + + store = xmalloc (strlen (root_directory) + sizeof original_store); + strcpy (store, root_directory); + strcat (store, original_store); + + return v; +} + +/* Return NAME, a shared object file name, relocated under STORE. This + function is called by the loader whenever it looks for a shared object. */ +char * +la_objsearch (const char *name, uintptr_t *cookie, unsigned int flag) +{ + char *result; + + if (strncmp (name, original_store, + sizeof original_store - 1) == 0) + { + size_t len = strlen (name) - sizeof original_store + + strlen (store) + 1; + result = xmalloc (len); + strcpy (result, store); + strcat (result, name + sizeof original_store - 1); + } + else + result = strdup (name); + + return result; +} 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 #include +/* 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 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); @@ -340,6 +368,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. */ @@ -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,13 +484,29 @@ 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 } }; -- cgit v1.2.3