/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "dlfcn-util.h"
+#include "errno-util.h"
#include "log.h"
void* safe_dlclose(void *dl) {
}
int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) {
- _cleanup_(dlclosep) void *dl = NULL;
int r;
if (*dlp)
return 0; /* Already loaded */
- dl = dlopen(filename, RTLD_NOW|RTLD_NODELETE);
- if (!dl)
- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "%s is not installed: %s", filename, dlerror());
+ _cleanup_(dlclosep) void *dl = NULL;
+ const char *dle = NULL;
+ r = dlopen_safe(filename, &dl, &dle);
+ if (r < 0) {
+ log_debug_errno(r, "Shared library '%s' is not available: %s", filename, dle ?: STRERROR(r));
+ return -EOPNOTSUPP; /* Turn into recognizable error */
+ }
- log_debug("Loaded '%s' via dlopen()", filename);
+ log_debug("Loaded shared library '%s' via dlopen().", filename);
va_list ap;
va_start(ap, log_level);
*dlp = TAKE_PTR(dl);
return 1;
}
+
+static bool dlopen_blocked = false;
+
+void block_dlopen(void) {
+ dlopen_blocked = true;
+}
+
+int dlopen_safe(const char *filename, void **ret, const char **reterr_dlerror) {
+ int r;
+
+ assert(filename);
+
+ /* A wrapper around dlopen(), that takes dlopen_blocked into account, and tries to normalize the
+ * error reporting a bit. */
+
+ int flags = RTLD_NOW|RTLD_NODELETE; /* Always set RTLD_NOW + RTLD_NODELETE, for security reasons */
+
+ /* If dlopen() is blocked we'll still try it, but set RTLD_NOLOAD, so that it will still work if
+ * already loaded (for example because the binary linked to things regularly), but fail if not. */
+ if (dlopen_blocked)
+ flags |= RTLD_NOLOAD;
+
+ errno = 0;
+ void *p = dlopen(filename, flags);
+ if (!p) {
+ if (dlopen_blocked) {
+ (void) dlerror(); /* consume error, so that no later call will return it */
+
+ if (reterr_dlerror)
+ *reterr_dlerror = NULL;
+
+ return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Refusing loading of '%s', as loading further dlopen() modules has been blocked.", filename);
+ }
+
+ r = errno_or_else(ENOPKG);
+
+ if (reterr_dlerror)
+ *reterr_dlerror = dlerror();
+ else
+ (void) dlerror(); /* consume error, so that no later call will return it */
+
+ return r;
+ }
+
+ if (ret)
+ *ret = TAKE_PTR(p);
+
+ return 0;
+}
* _SONAME_ARRAY<X+1> will need to be added). */
#define ELF_NOTE_DLOPEN(feature, description, priority, ...) \
_ELF_NOTE_DLOPEN("[{\"feature\":\"" feature "\",\"description\":\"" description "\",\"priority\":\"" priority "\",\"soname\":" _SONAME_ARRAY(__VA_ARGS__) "}]", UNIQ_T(s, UNIQ))
+
+/* If called dlopen_many_sym_or_warn() will fail with EPERM. This can be used to block lazy loading of shared
+ * libs, if we transfer a process into a different namespace. Note that this does not work for all calls of
+ * dlopen(), just those through our dlopen_safe() wrapper (which we use comprehensively in our
+ * codebase). This hence has *no* effect on NSS. (Would be great if we could change that...) */
+void block_dlopen(void);
+
+int dlopen_safe(const char *filename, void **ret, const char **reterr_dlerror);
}
}
+ /* Let's now disable further dlopen()ing of libraries, since we are about to do namespace
+ * shenanigans, and do not want to mix resources from host and namespace */
+ block_dlopen();
+
if (needs_sandboxing && !have_cap_sys_admin && exec_needs_cap_sys_admin(context, params)) {
/* If we're unprivileged, set up the user namespace first to enable use of the other namespaces.
* Users with CAP_SYS_ADMIN can set up user namespaces last because they will be able to
if (pid == 0) {
fd_outer_socket = safe_close(fd_outer_socket);
+ /* In the child refuse dlopen(), so that we never mix shared libraries from payload and parent */
+ block_dlopen();
+
/* The inner child has all namespaces that are requested, so that we all are owned by the
* user if user namespaces are turned on. */
#include "bpf-dlopen.h"
#include "dlfcn-util.h"
+#include "errno-util.h"
#include "initrd-util.h"
#include "log.h"
}
int dlopen_bpf_full(int log_level) {
- _cleanup_(dlclosep) void *dl = NULL;
static int cached = 0;
int r;
DISABLE_WARNING_DEPRECATED_DECLARATIONS;
- dl = dlopen("libbpf.so.1", RTLD_NOW|RTLD_NODELETE);
- if (!dl) {
+ _cleanup_(dlclosep) void *dl = NULL;
+ r = dlopen_safe("libbpf.so.1", &dl, /* reterr_dlerror= */ NULL);
+ if (r < 0) {
/* libbpf < 1.0.0 (we rely on 0.1.0+) provide most symbols we care about, but
* unfortunately not all until 0.7.0. See bpf-compat.h for more details.
* Once we consider we can assume 0.7+ is present we can just use the same symbol
* list for both files, and when we assume 1.0+ is present we can remove this dlopen */
- dl = dlopen("libbpf.so.0", RTLD_NOW|RTLD_NODELETE);
- if (!dl)
- return cached = log_full_errno(in_initrd() ? LOG_DEBUG : log_level, SYNTHETIC_ERRNO(EOPNOTSUPP),
- "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled: %s",
- dlerror());
+ const char *dle = NULL;
+ r = dlopen_safe("libbpf.so.0", &dl, &dle);
+ if (r < 0) {
+ log_full_errno(in_initrd() ? LOG_DEBUG : log_level, r,
+ "Neither libbpf.so.1 nor libbpf.so.0 are installed, cgroup BPF features disabled: %s", dle ?: STRERROR(r));
+ return (cached = -EOPNOTSUPP); /* turn into recognizable error */
+ }
log_debug("Loaded 'libbpf.so.0' via dlopen()");
DLSYM_PROTOTYPE(stringprep_utf8_to_ucs4) = NULL;
int dlopen_idn(void) {
- _cleanup_(dlclosep) void *dl = NULL;
int r;
ELF_NOTE_DLOPEN("idn",
if (idn_dl)
return 0; /* Already loaded */
- dl = dlopen("libidn.so.12", RTLD_NOW|RTLD_NODELETE);
- if (!dl) {
+ r = check_dlopen_blocked("libidn.so.12");
+ if (r < 0)
+ return r;
+
+ _cleanup_(dlclosep) void *dl = NULL;
+ r = dlopen_safe("libidn.so.12", &dl, /* reterr_dlerror= */ NULL);
+ if (r < 0) {
/* libidn broke ABI in 1.34, but not in a way we care about (a new field got added to an
* open-coded struct we do not use), hence support both versions. */
- dl = dlopen("libidn.so.11", RTLD_NOW|RTLD_NODELETE);
- if (!dl)
- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
- "libidn support is not installed: %s", dlerror());
+ const char *dle = NULL;
+ r = dlopen_safe("libidn.so.11", &dl, &dle);
+ if (r < 0) {
+ log_debug_errno(r, "libidn support is not available: %s", dle ?: STRERROR(r));
+ return -EOPNOTSUPP; /* turn into recognizable error */
+ }
log_debug("Loaded 'libidn.so.11' via dlopen()");
} else
log_debug("Loaded 'libidn.so.12' via dlopen()");
#include "dirent-util.h"
#include "dlfcn-util.h"
#include "efi-api.h"
+#include "errno-util.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fileio.h"
if (!filename_is_valid(fn))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name '%s' not valid, refusing.", driver);
- context->tcti_dl = dlopen(fn, RTLD_NOW|RTLD_NODELETE);
- if (!context->tcti_dl)
- return log_debug_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to load %s: %s", fn, dlerror());
+ const char *dle = NULL;
+ r = dlopen_safe(fn, &context->tcti_dl, &dle);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to load %s: %s", fn, dle ?: STRERROR(r));
+ return -ENOPKG; /* Turn into recognizable error */
+ }
log_debug("Loaded '%s' via dlopen()", fn);
}
int userdb_block_nss_systemd(int b) {
- _cleanup_(dlclosep) void *dl = NULL;
- int (*call)(bool b);
+ int r;
/* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
- dl = dlopen(LIBDIR "/libnss_systemd.so.2", RTLD_NOW|RTLD_NODELETE);
- if (!dl) {
+ _cleanup_(dlclosep) void *dl = NULL;
+ const char *dle;
+ r = dlopen_safe(LIBDIR "/libnss_systemd.so.2", &dl, &dle);
+ if (r < 0) {
/* If the file isn't installed, don't complain loudly */
- log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
+ log_debug_errno(r, "Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dle ?: STRERROR(r));
return 0;
}
log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2");
- call = dlsym(dl, "_nss_systemd_block");
+ int (*call)(bool b) = dlsym(dl, "_nss_systemd_block");
if (!call)
/* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
#include <dlfcn.h>
#include <stdlib.h>
+#include "dlfcn-util.h"
#include "shared-forward.h"
int main(int argc, char **argv) {
int i;
for (i = 0; i < argc - 1; i++)
- assert_se(handles[i] = dlopen(argv[i + 1], RTLD_NOW|RTLD_NODELETE));
+ assert_se(dlopen_safe(argv[i + 1], handles + i, /* reterr_dlerror= */ NULL) >= 0);
for (i--; i >= 0; i--)
assert_se(dlclose(handles[i]) == 0);