[systemd-devel] [PATCH] nspawn: --populate with dynamic libs and one-file scripts

Shawn Landden shawn at churchofgit.com
Sun Dec 1 15:50:48 PST 2013


the whitelist of dynamic linker paths comes from clang
---
 man/systemd-nspawn.xml |   8 +--
 src/nspawn/elf.c       | 161 +++++++++++++++++++++++++++++++++++++----
 src/nspawn/elf.h       |  14 +++-
 src/nspawn/nspawn.c    | 191 ++++++++++++++++++++++++++++++++++++++++++++++---
 src/shared/util.c      |  80 +++++++++++++++++++++
 src/shared/util.h      |   2 +
 6 files changed, 430 insertions(+), 26 deletions(-)

diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml
index 24bc0d7..723ec09 100644
--- a/man/systemd-nspawn.xml
+++ b/man/systemd-nspawn.xml
@@ -214,11 +214,11 @@
                                 <term><option>-p</option></term>
                                 <term><option>--populate</option></term>
 
-                                <listitem><para>If COMMAND does not exist in
-                                target root directory, launch host COMMAND.</para>
+                                <listitem><para>Use COMMAND from host.</para>
 
-                                <para>Can be used on empty target directories
-                                (if COMMAND a static executable).</para></listitem>
+                                <para>Can be used on empty target directories,
+                                if COMMAND an ELF executable, or
+                                one-file script.</para></listitem>
                         </varlistentry>
 
                         <varlistentry>
diff --git a/src/nspawn/elf.c b/src/nspawn/elf.c
index f91b374..63ada56 100644
--- a/src/nspawn/elf.c
+++ b/src/nspawn/elf.c
@@ -26,44 +26,90 @@
 #include "elf.h"
 #include "util.h"
 #include "log.h"
+#include "strv.h"
 
 int analyze_executable(const char *path,
                        int *_fd,
                        bool *_elf64,
                        char **_linker,
-                       char **shebang) {
+                       char **shebang,
+                       char ***_libs) {
 
-        char e_ident[sizeof(Elf64_Ehdr)];
+        char e_ident[MAX(2u + PATH_MAX, sizeof(Elf64_Ehdr))];
         uint16_t e_type;
         off_t e_phoff;
         uint16_t e_phentsize, e_phnum;
         bool elf64;
-        int fd = -1;
+        _cleanup_close_ int fd = -1;
         bool have_interp = false;
         int r;
 
         assert(path);
+        assert(_fd);
         assert(_elf64);
         assert(_linker);
         assert(shebang);
+        assert(_libs);
 
         fd = open(path, O_RDONLY | O_CLOEXEC);
         if (fd < 0) {
-                log_error("open(\"%s\") failed: %m", path);
+                log_error("open(%s) failed: %m", path);
                 return -errno;
         }
 
-        r = read(fd, e_ident, sizeof(Elf64_Ehdr));
+        r = read(fd, e_ident, MAX(2u + PATH_MAX, sizeof(Elf64_Ehdr)));
         if (r < 0) {
-                log_error("read() on %s failed: %m", path);
+                log_error("read(%s) failed: %s", path, strerror(errno));
                 return -errno;
         }
 
         if (memcmp(e_ident, ELFMAG, SELFMAG) != 0) {
-                log_error("%s is not an ELF executable.", path);
-                return -ENOSYS;
-        } else
+                if (startswith(e_ident, "#!")) {
+                        _cleanup_close_ int shebang_fd = -1;
+                        char shebang_e_ident[sizeof(Elf64_Ehdr)];
+                        char *t;
+
+                                                  /* from fs/binfmt_script.c:42 */
+                        t = e_ident + strcspn(e_ident, " \t\n");
+                        t[0] = '\0';
+
+                        t = &e_ident[2];
+
+                        shebang_fd = open(t, O_RDONLY|O_CLOEXEC);
+                        if (shebang_fd < 0) {
+                                log_error("Cannot open interpreter %s: %m", t);
+                                return -errno;
+                        }
+
+                        r = read(shebang_fd, shebang_e_ident, sizeof(Elf64_Ehdr));
+                        if (r < SELFMAG) {
+                                log_error("read(%s) failed: %s", t, strerror(r < 0 ? errno : EIO));
+                                return -errno;
+                        }
+
+                        /* The kernel actually supports interpreters of interpreters
+                         * but we don't support that here. */
+                        if (memcmp(shebang_e_ident, ELFMAG, SELFMAG) != 0) {
+                                log_error("Interpreter %s is not an ELF executable.", t);
+                                return -EINVAL;
+                        }
+
+                        *_fd = fd;
+                        fd = shebang_fd; /* analyze and     */
+                        shebang_fd = -1; /* don't close ELF */
+                        *shebang = strdup(t);
+                        if (!*shebang)
+                                return log_oom();
+
+                        memcpy(e_ident, shebang_e_ident, sizeof(Elf64_Ehdr));
+                } else {
+                        log_error("%s is not an ELF executable or script starting with #!.", path);
+                        return -ENOSYS;
+                }
+        } else {
+                *_fd = fd;
                 *shebang = NULL;
+        }
 
         switch (e_ident[EI_CLASS]) {
         case ELFCLASS32:
@@ -103,14 +149,14 @@ int analyze_executable(const char *path,
         char *phdr;
 
         lseek(fd, e_phoff, SEEK_SET);
-        r = read(fd, &phdrs, e_phentsize * e_phnum);
+        r = read(fd, phdrs, e_phentsize * e_phnum);
         if (r < 0) {
                 log_error("read() on %s failed: %m", path);
                 return -errno;
         }
 
         for (int i = 0; i < e_phnum; i++) {
-                phdr = ((char *)&phdrs + i * e_phentsize);
+                phdr = (phdrs + i * e_phentsize);
 
                 /* p_type is the same offset and size regardless
                  * of 32 or 64-bit, so this works for 64-bit too */
@@ -123,9 +169,100 @@ int analyze_executable(const char *path,
         if (!have_interp) {
                 *_elf64 = elf64;
                 *_linker = NULL;
+                if (!*shebang)
+                        fd = -1; /* don't close */
                 return 0;
         }
 
         /* is dynamic executable */
-        return -ENOSYS;
+        _cleanup_free_ char *loput = NULL;
+        _cleanup_strv_free_ char **libs = NULL;
+        char linker[PATH_MAX], *t, *q, *w;
+        const char *c;
+        int n;
+
+        if (elf64)
+                lseek(fd, ((Elf64_Phdr *)phdr)->p_offset, SEEK_SET);
+        else
+                lseek(fd, ((Elf32_Phdr *)phdr)->p_offset, SEEK_SET);
+
+        r = read(fd, linker, PATH_MAX);
+        if (r < 0)
+                return -errno;
+        /* linker name is null terminated, but just in case */
+        linker[PATH_MAX - 1] = '\0';
+
+        /* Get the path of the dynamic linker from PT_INTERP of the executable.
+         * Check if it's a path well-known to be the GNU dynamic linker,
+         * that responds to our LD_TRACE_LOADED_OBJECTS=1.
+         *
+         * The other way to extract this information is "objdump -p", but no
+         * need to depend on that when ld.so does just fine and the target
+         * binary already depends on it. */
+        have_interp = false;
+        NULSTR_FOREACH(c, ld_whitelist)
+                if (strcmp(c, linker) == 0) {
+                        have_interp = true;
+                        break;
+                }
+
+        if (!have_interp) {
+                log_error("%s, ELF interpreter for %s, not a whitelisted path of the GNU dynamic linker.", linker, *shebang ? *shebang : path);
+                return -EPERM;
+        }
+
+        loput = popenve(linker, (char *const[]){linker, path, NULL}, (char *const[]){"LD_TRACE_LOADED_OBJECTS=1", NULL});
+        if (!loput)
+                return log_oom();
+
+        t = loput; /* This number is too high, but that is OK. */
+        for (n = 0; t = strchr(t, '\n'); n++)
+                t++;
+
+        libs = new0(char *, n + 1);
+        if (!libs)
+                return log_oom();
+
+        q = t = loput;
+        for (n = 0; t = strchr(q, '\n'); ) {
+                t++;
+                if (*t != '\t') {
+                        if (*t == '\0')
+                                break;
+                        else {
+                                log_error("Failed to parse linker output.");
+                                return -EINVAL;
+                        }
+                }
+
+                q = strstr(t, " (0x");
+                if (!q) {
+                        log_error("Failed to parse linker output.");
+                        return -EINVAL;
+                }
+                *q = '\0'; q++;
+
+                w = strstr(t, " => /");
+                if (!w) /* if library is an absolute path or otherwise
+                         * does not resolve, like linux-vdso.so.1, ignore it */
+                        continue;
+                w += strlen(" => /") - 1;
+
+                libs[n] = strdup(w);
+                if (!libs[n])
+                        return log_oom();
+
+                n++;
+        }
+
+        *_linker = strdup(linker);
+        if (!*_linker)
+                return log_oom();
+
+        *_libs = libs;
+        libs = NULL; /* do not free */
+
+        if (!*shebang)
+                fd = -1; /* do not close */
+        return 0;
 }
diff --git a/src/nspawn/elf.h b/src/nspawn/elf.h
index 55f8566..1ec54f3 100644
--- a/src/nspawn/elf.h
+++ b/src/nspawn/elf.h
@@ -23,8 +23,20 @@
 
 #include <stdbool.h>
 
+static const char ld_whitelist[] = "/lib64/ld-linux-x86-64.so.2\0"
+                                "/lib/ld-linux.so.2\0"
+                                "/lib/ld-linux-armhf.so.3\0"
+                                "/lib/ld-linux-aarch64.so.1\0"
+                                "/lib64/ld64.so.1\0"
+                                "/lib/ld.so.1\0"
+                                "/lib64/ld.so.1\0"
+                                "/lib32/ld.so.1\0"
+                                "/lib/ld.so.1\0"
+                                "/lib/ld-linux.so.3\0";
+
 int analyze_executable(const char *path,
                        int *_fd,
                        bool *_elf64,
                        char **linker,
-                       char **shebang);
+                       char **shebang,
+                       char ***_libs);
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index 24a48e8..6a30df7 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -114,6 +114,12 @@ static uint64_t arg_retain =
 static char **arg_bind = NULL;
 static char **arg_bind_ro = NULL;
 
+/* we set atime to this when we created the file only to bind mount onto it */
+#define BIND_FILE_MAGIC_TIME -0x6ddddddd
+static char *populate_shebang = NULL;
+static char *populate_linker = NULL;
+static char **populate_libraries = NULL;
+
 static int help(void) {
 
         printf("%s [OPTIONS...] [COMMAND [ARGS...]]\n\n"
@@ -194,7 +200,6 @@ static int parse_argv(int argc, char *argv[]) {
                         return 0;
 
                 case 'D':
-                        free(arg_directory);
                         arg_directory = canonicalize_file_name(optarg);
                         if (!arg_directory) {
                                 log_error("Invalid root directory: %m");
@@ -1058,6 +1063,124 @@ static bool audit_enabled(void) {
         return false;
 }
 
+static int mount_file_bind(const char *src, const char *dest, const char *dest_root) {
+        char *q, *j, p[PATH_MAX], t[PATH_MAX];
+        struct stat st;
+        int r;
+
+        if (strlen(dest_root) + strlen(dest) + 1 > PATH_MAX)
+                return -ENAMETOOLONG;
+
+        *t = '\0';
+        strcat(t, dest_root);
+        strcat(t, dest);
+
+        q = strchr(dest, '/');
+        if (q) {
+                q = strrchr(t, '/');
+                *q = '\0';
+
+                j = realpath(t, p);
+                if (!j)
+                        return -errno;
+
+                j += strlen(p);
+                *j = '/';
+                strcpy(j + 1, q + 1);
+        } else
+                strcpy(p, t);
+
+        r = touch_time_create_executable(p, BIND_FILE_MAGIC_TIME);
+        if (r < 0) {
+                if (errno == EEXIST) {
+                        log_warning("%s already exists in %s", dest, dest_root);
+                        return 0;
+                } else
+                        return -errno;
+        }
+
+
+        r = mount(src, p, NULL, MS_BIND|MS_RDONLY, NULL);
+        if (r < 0) {
+                if (stat(t, &st) == 0 && st.st_atime == BIND_FILE_MAGIC_TIME)
+                        (void)unlink(t);
+
+                return -errno;
+        }
+
+        return 0;
+}
+
+static int mount_file_bind_mkdir(const char *path) {
+        char t[PATH_MAX];
+        int z;
+
+        if (strlen(arg_directory) + strlen(path) + 1 > PATH_MAX)
+                return -ENAMETOOLONG;
+
+        *t = '\0';
+        strcat(t, arg_directory);
+        strcat(t, path);
+
+        z = mkdir_parents(t, 0755);
+        if (z < 0) {
+                log_error("Failed to create directories for %s: %s", path, strerror(-z));
+                return z;
+        }
+
+        z = mount_file_bind(path, path, arg_directory);
+        if (z < 0) {
+                log_error("Failed to bind mount %s: %s", path, strerror(-z));
+                return z;
+        }
+
+        return 0;
+}
+
+static void umount_populate(void) {
+        char t[PATH_MAX];
+        struct stat st;
+        char **lib;
+
+        *t = '\0';
+        strcat(t, arg_directory);
+
+        if (populate_shebang) {
+                assert(strlen(arg_directory) + strlen(populate_shebang) + 1 <= PATH_MAX);
+
+                memcpy(t + strlen(arg_directory), populate_shebang, strlen(populate_shebang) + 1);
+
+                (void)umount(t);
+
+                if (stat(t, &st) == 0 && st.st_atime == BIND_FILE_MAGIC_TIME)
+                        (void)unlink(t);
+        }
+
+        if (populate_linker) {
+                assert(strlen(arg_directory) + strlen(populate_linker) + 1 <= PATH_MAX);
+
+                memcpy(t + strlen(arg_directory), populate_linker, strlen(populate_linker) + 1);
+
+                (void)umount(t);
+
+                if (stat(t, &st) == 0 && st.st_atime == BIND_FILE_MAGIC_TIME)
+                        (void)unlink(t);
+        }
+
+        STRV_FOREACH(lib, populate_libraries) {
+                assert(strrchr(*lib, '/'));
+                assert(strlen(arg_directory) + strlen("/usr/lib") + strlen(strrchr(*lib, '/')) + 1 <= PATH_MAX);
+
+                memcpy(t + strlen(arg_directory), "/usr/lib", strlen("/usr/lib") + 1);
+                strcat(t, strrchr(*lib, '/'));
+
+                (void)umount(t);
+
+                if (stat(t, &st) == 0 && st.st_atime == BIND_FILE_MAGIC_TIME)
+                        (void)unlink(t);
+        }
+}
+
 int main(int argc, char *argv[]) {
         pid_t pid = 0;
         int r = EXIT_FAILURE, k;
@@ -1179,21 +1302,69 @@ int main(int argc, char *argv[]) {
                 goto finish;
         }
 
-        if (arg_populate && argc > optind && find_binary_path_root(argv[optind], DEFAULT_PATH_SPLIT_USR, arg_directory, NULL) == -ENOENT) {
-                _cleanup_free_ char *t = NULL, *linker = NULL, *shebang = NULL;
+        if (arg_populate) {
+                _cleanup_free_ char *t = NULL, *linker = NULL, *libroot = NULL;
                 bool elf64;
+                char **lib;
 
                 k = find_binary(argv[optind], &t);
                 if (k < 0) {
-                        log_error("COMMAND %s not available in host or target: %s", argv[optind], strerror(-r));
-                        goto finish;
+                        if (argc <= optind) {
+                                t = strdup("/bin/bash");
+                                if (!t) {
+                                        k = log_oom();
+                                        goto finish;
+                                }
+                        } else {
+                                log_error("COMMAND %s not available in host: %s", argv[optind], strerror(-k));
+                                goto finish;
+                        }
                 }
 
-                k = analyze_executable(t, &exec_fd, &elf64, &linker, &shebang);
+                if (find_binary_path_root(argv[optind], DEFAULT_PATH_SPLIT_USR, arg_directory, NULL) != -ENOENT)
+                        log_warning("%s already exists in %s.", argv[optind], arg_directory);
+
+                k = analyze_executable(t, &exec_fd, &elf64, &populate_linker, &populate_shebang, &populate_libraries);
                 if (k < 0) {
-                        printf("analyze failed: %m");/* nothing */
-                } else
-                        asprintf(&proc_exec, "/proc/self/fd/%d", exec_fd);
+                        log_error("--populate failed: %s", strerror(-k));
+                        goto finish;
+                }
+
+                asprintf(&proc_exec, "/proc/self/fd/%d", exec_fd);
+                if (populate_shebang)
+                        if ((k = mount_file_bind_mkdir(populate_shebang)) < 0)
+                                goto finish;
+
+                if (populate_linker)
+                        if ((k = mount_file_bind_mkdir(populate_linker)) < 0)
+                                goto finish;
+
+                if (populate_libraries) {
+                        libroot = strappend(arg_directory, "/usr/lib/");
+                        if (!libroot) {
+                                log_oom();
+                                goto finish;
+                        }
+
+                        k = mkdir_p(libroot, 0755);
+                        if (k < 0) {
+                                log_error("Failed to create directory %s: %s", libroot, strerror(-k));
+                                goto finish;
+                        }
+                }
+
+                STRV_FOREACH(lib, populate_libraries) {
+                        if (!strrchr(*lib, '/')) {
+                                log_error("Failed to parse linker output.");
+                                goto finish;
+                        }
+
+                        k = mount_file_bind(*lib, strrchr(*lib, '/') + 1, libroot);
+                        if (k < 0) {
+                                log_error("Failed to bind mount %s: %s", *lib, strerror(-k));
+                                goto finish;
+                        }
+                }
         }
 
         sd_notify(0, "READY=1");
@@ -1519,6 +1690,8 @@ int main(int argc, char *argv[]) {
                         break;
                 }
 
+                umount_populate();
+
                 if (status.si_code == CLD_EXITED) {
                         r = status.si_status;
                         if (status.si_status != 0) {
diff --git a/src/shared/util.c b/src/shared/util.c
index b4ed2c4..0d186e8 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -77,6 +77,7 @@
 #include "gunicode.h"
 #include "virt.h"
 #include "def.h"
+#include "mkdir.h"
 
 int saved_argc = 0;
 char **saved_argv = NULL;
@@ -3338,6 +3339,24 @@ char *ellipsize(const char *s, size_t length, unsigned percent) {
         return ellipsize_mem(s, strlen(s), length, percent);
 }
 
+int touch_time_create_executable(const char *path, time_t t) {
+        const struct timespec times[2] = { {t, 0}, {t, 0} };
+        int fd, r;
+
+        assert(path);
+
+        fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY, 0755);
+        if (fd < 0)
+                return -errno;
+
+        r = futimens(fd, times);
+        if (r < 0)
+                return r;
+
+        close_nointr_nofail(fd);
+        return 0;
+}
+
 int touch(const char *path) {
         int fd;
 
@@ -4966,6 +4985,67 @@ int fd_inc_rcvbuf(int fd, size_t n) {
         return 1;
 }
 
+/* popen() is insecure due to shell, reimplement w/o shell
+ * be careful about deadlocks where process is waiting for us to read()
+ * when we are blocked on waitpid() (do not use in PID 1) */
+#define POPENV_READ_SIZE 4096
+char *popenve(const char *path, char *const argv[], char *const envp[]) {
+        int fd[2];
+        pid_t child_pid;
+        int r;
+
+        char *s = NULL, *i;
+        int j;
+
+        r = pipe2(fd, O_CLOEXEC);
+        if(r < 0)
+                return NULL;
+
+        child_pid = fork();
+        if (child_pid == 0) {
+                int d;
+                /* child */
+                if (fd[1] != STDOUT_FILENO)
+                        dup2(fd[1], STDOUT_FILENO);
+
+                d = open("/dev/null", O_APPEND | O_CLOEXEC);
+                if (d < 0)
+                        _exit(1);
+
+                dup2(d, STDERR_FILENO);
+                fcntl(STDERR_FILENO, F_SETFD, 0);
+                fcntl(STDOUT_FILENO, F_SETFD, 0);
+
+                execve(path, argv, envp);
+                /* execve() failed */
+                _exit(127);
+        } else if (child_pid == -1) {
+                /* fork() failed */
+                close(fd[1]);
+                close(fd[0]);
+                return NULL;
+        }
+        close(fd[1]);
+
+        waitpid(child_pid, NULL, 0);
+        for (j=0;;j++) {
+                s = realloc(s, (j+1)*POPENV_READ_SIZE);
+                i = j*POPENV_READ_SIZE + s;
+                r = read(fd[0], i, POPENV_READ_SIZE);
+                if (r < 0) {
+                        free(s);
+                        kill(child_pid, SIGKILL);
+                        close(fd[0]);
+                        return NULL;
+                } if (r < POPENV_READ_SIZE) { // read to end
+                        close(fd[0]);
+                        /* read() does not NULL terminate */
+                        i[r] = '\0';
+                        return s;
+                }
+        }
+}
+
 int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) {
         pid_t parent_pid, agent_pid;
         int fd;
diff --git a/src/shared/util.h b/src/shared/util.h
index 504f63a..d582c74 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -409,6 +409,7 @@ char *ellipsize(const char *s, size_t length, unsigned percent);
                                    /* bytes                 columns */
 char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent);
 
+int touch_time_create_executable(const char *path, time_t t);
 int touch(const char *path);
 
 char *unquote(const char *s, const char *quotes);
@@ -539,6 +540,7 @@ int is_kernel_thread(pid_t pid);
 int fd_inc_sndbuf(int fd, size_t n);
 int fd_inc_rcvbuf(int fd, size_t n);
 
+char *popenve(const char *path, char *const argv[], char *const envp[]);
 int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...);
 
 int setrlimit_closest(int resource, const struct rlimit *rlim);
-- 
1.8.4.4



More information about the systemd-devel mailing list