In the next patch, we want to get rid of the requirement for the user to supply LD_LIBRARY_PATH pointing to vddk libs, if we can derive it ourselves from libdir. However, VDDK itself requires LD_LIBRARY_PATH to be set (because it tries to load libraries that in turn depend on a bare library name, which no manner of dlopen() hacking can work around, and implementing la_objsearch() is no better for requiring LD_AUDIT to be set). And since ld.so caches the value of LD_LIBRARY_PATH at startup (for security reasons), the ONLY way to set it for loading vddk, while clearing it again before --run spawns a child process, is to re-exec nbdkit with slight alterations. Since VDDK only runs on Linux, we can assume the presence of /proc/self/{exe,cmdline}, and parse off everything we need (provided nbdkit didn't muck with argv[], which we just fixed) to recursively exec with a munged environment that still has enough breadcrumbs to undo the munging. This patch does not quite set LD_LIBRARY_PATH correctly in all cases (since vddk expects libdir= to point to vmware-vix-disklib-distrib/, but LD_LIBRARY_PATH to vmware-vix-disklib-distrib/lib64), but that will be cleaned up in the next patch; in the meantime, the re-exec in this patch fires but has no ill effects. While making the change, clean up static variables that were explicitly zero-initialized to instead rely on .bss initial value. Signed-off-by: Eric Blake <eblake@redhat.com> --- plugins/vddk/vddk.c | 172 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 147 insertions(+), 25 deletions(-) diff --git a/plugins/vddk/vddk.c b/plugins/vddk/vddk.c index 0abec68e..5ae41547 100644 --- a/plugins/vddk/vddk.c +++ b/plugins/vddk/vddk.c @@ -1,5 +1,5 @@ /* nbdkit - * Copyright (C) 2013-2019 Red Hat Inc. + * Copyright (C) 2013-2020 Red Hat Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -40,6 +40,7 @@ #include <string.h> #include <unistd.h> #include <dlfcn.h> +#include <fcntl.h> #define NBDKIT_API_VERSION 2 @@ -71,25 +72,26 @@ int vddk_debug_extents; #define VDDK_MAJOR 5 #define VDDK_MINOR 1 -static void *dl = NULL; /* dlopen handle */ -static int init_called = 0; /* was InitEx called */ - -static char *config = NULL; /* config */ -static const char *cookie = NULL; /* cookie */ -static const char *filename = NULL; /* file */ -static char *libdir = NULL; /* libdir */ -static uint16_t nfc_host_port = 0; /* nfchostport */ -static char *password = NULL; /* password */ -static uint16_t port = 0; /* port */ -static const char *server_name = NULL; /* server */ -static bool single_link = false; /* single-link */ -static const char *snapshot_moref = NULL; /* snapshot */ -static const char *thumb_print = NULL; /* thumbprint */ -static const char *transport_modes = NULL; /* transports */ -static bool unbuffered = false; /* unbuffered */ -static const char *username = NULL; /* user */ -static const char *vmx_spec = NULL; /* vm */ -static bool is_remote = false; +static void *dl; /* dlopen handle */ +static bool init_called; /* was InitEx called */ +static char *reexeced; /* orig LD_LIBRARY_PATH on reexec */ + +static char *config; /* config */ +static const char *cookie; /* cookie */ +static const char *filename; /* file */ +static char *libdir; /* libdir */ +static uint16_t nfc_host_port; /* nfchostport */ +static char *password; /* password */ +static uint16_t port; /* port */ +static const char *server_name; /* server */ +static bool single_link; /* single-link */ +static const char *snapshot_moref; /* snapshot */ +static const char *thumb_print; /* thumbprint */ +static const char *transport_modes; /* transports */ +static bool unbuffered; /* unbuffered */ +static const char *username; /* user */ +static const char *vmx_spec; /* vm */ +static bool is_remote; #define VDDK_ERROR(err, fs, ...) \ do { \ @@ -162,6 +164,8 @@ vddk_unload (void) static int vddk_config (const char *key, const char *value) { + int r; + if (strcmp (key, "config") == 0) { /* See FILENAMES AND PATHS in nbdkit-plugin(3). */ free (config); @@ -199,12 +203,15 @@ vddk_config (const char *key, const char *value) if (nbdkit_parse_uint16_t ("port", value, &port) == -1) return -1; } + else if (strcmp (key, "reexeced_") == 0) { + /* Special name because it is only for internal use. */ + reexeced = (char *)value; + } else if (strcmp (key, "server") == 0) { server_name = value; } else if (strcmp (key, "single-link") == 0) { - int r = nbdkit_parse_bool (value); - + r = nbdkit_parse_bool (value); if (r == -1) return -1; single_link = r; @@ -219,8 +226,7 @@ vddk_config (const char *key, const char *value) transport_modes = value; } else if (strcmp (key, "unbuffered") == 0) { - int r = nbdkit_parse_bool (value); - + r = nbdkit_parse_bool (value); if (r == -1) return -1; unbuffered = r; @@ -242,6 +248,92 @@ vddk_config (const char *key, const char *value) return 0; } +/* Perform a re-exec that temporarily modifies LD_LIBRARY_PATH. Does + * not return on success; on failure, problems have been logged, but + * the caller prefers to proceed as if this had not been attempted. + * Thus, no return value is needed. + */ +static void +perform_reexec (const char *prepend) +{ + CLEANUP_FREE char *library = NULL; + const char *env = getenv ("LD_LIBRARY_PATH"); + int argc = 0; + CLEANUP_FREE char **argv = NULL; + int fd; + size_t len = 0, buflen = 512; + CLEANUP_FREE char *buf = NULL; + + /* In order to re-exec, we need our original command line. The + * Linux kernel does not make it easy to know in advance how large + * it was, so we just slurp in the whole file, doubling our reads + * until we get a short read. This assumes nbdkit did not alter its + * original argv[]. + */ + fd = open ("/proc/self/cmdline", O_RDONLY); + if (fd == -1) { + nbdkit_debug ("failure to parse original argv: %m"); + return; + } + + do { + char *p = realloc (buf, buflen * 2); + ssize_t r; + + if (!p) { + nbdkit_debug ("failure to parse original argv: %m"); + return; + } + buf = p; + buflen *= 2; + r = read (fd, buf + len, buflen - len); + if (r == -1) { + nbdkit_debug ("failure to parse original argv: %m"); + return; + } + len += r; + } while (len == buflen); + nbdkit_debug ("original command line occupies %zu bytes", len); + + /* Split cmdline into argv, then append one more arg. */ + buflen = len; + len = 0; + while (len < buflen) { + char **tmp = realloc (argv, sizeof *argv * (argc + 2)); + + if (!tmp) { + nbdkit_debug ("failure to parse original argv: %m"); + return; + } + argv = tmp; + argv[argc++] = buf + len; + len += strlen (buf + len) + 1; + } + if (!env) + env = ""; + nbdkit_debug ("original argc == %d, adding reexeced_=%s", argc, env); + if (asprintf (&reexeced, "reexeced_=%s", env) == -1) { + nbdkit_debug ("failure to re-exec: %m"); + return; + } + argv[argc++] = reexeced; + argv[argc] = NULL; + + if (env[0]) + asprintf (&library, "%s:%s", prepend, env); + else + library = strdup (prepend); + if (!library || setenv ("LD_LIBRARY_PATH", library, 1) == -1) { + nbdkit_debug ("failure to set LD_LIBRARY_PATH: %m"); + return; + } + + nbdkit_debug ("re-executing with updated LD_LIBRARY_PATH=%s", library); + fflush (NULL); + execvp ("/proc/self/exe", argv); + nbdkit_debug ("failure to execvp: %m"); +} + /* Load the VDDK library. */ static void load_library (void) @@ -266,6 +358,8 @@ load_library (void) } } if (dl == NULL) { + if (!reexeced && libdir) + perform_reexec (libdir); /* TODO: Use correct dir */ nbdkit_error ("%s\n\n" "If '%s' is located on a non-standard path you may need to\n" "set $LD_LIBRARY_PATH or edit /etc/ld.so.conf.\n\n" @@ -301,6 +395,34 @@ vddk_config_complete (void) return -1; } + /* If load_library caused a re-execution with an expanded + * LD_LIBRARY_PATH, restore it back to its original contents, passed + * as the value of "reexeced_". dlopen uses the value of + * LD_LIBRARY_PATH cached at program startup; our change is for the + * sake of child processes (such as --run) to see the same + * environment as the original nbdkit saw before re-exec. + */ + if (reexeced) { + char *env = getenv ("LD_LIBRARY_PATH"); + + nbdkit_debug ("cleaning up after re-exec"); + if (!env || strstr (env, reexeced) != 0 || + (libdir && strncmp (env, libdir, strlen (libdir)) != 0)) { + nbdkit_error ("'reexeced_' set with garbled environment"); + return -1; + } + if (reexeced[0]) { + if (setenv ("LD_LIBRARY_PATH", reexeced, 1) == -1) { + nbdkit_error ("setenv: %m"); + return -1; + } + } + else if (unsetenv ("LD_LIBRARY_PATH") == -1) { + nbdkit_error ("unsetenv: %m"); + return -1; + } + } + /* For remote connections, check all the parameters have been * passed. Note that VDDK will segfault if parameters that it * expects are NULL (and there's no real way to tell what parameters @@ -347,7 +469,7 @@ vddk_config_complete (void) VDDK_ERROR (err, "VixDiskLib_InitEx"); exit (EXIT_FAILURE); } - init_called = 1; + init_called = true; return 0; } -- 2.24.1