dlfcn-util: let's make our dlopen() code fail if we enter a container namespace

Now that we dlopen() so many deps, it might happen by accident that we
end up dlopen()ening stuff when we entered a container, which we should
really avoid, to not mix host and container libraries.

Let's add a global variable we can set when we want to block dlopen() to
ever succeed. This is then checked primarily in
dlopen_many_sym_or_warn(), where we'll generate EPERM plus a log
message.

There are a couple of other places we invoke dlopen(), without going
through dlopen_many_sym_or_warn(). This adds the same check there.
This commit is contained in:
Lennart Poettering
2025-11-24 09:19:33 +01:00
parent ab5a79ff5d
commit 2c7bdaf9f1
9 changed files with 113 additions and 31 deletions
+58 -6
View File
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "dlfcn-util.h"
#include "errno-util.h"
#include "log.h"
void* safe_dlclose(void *dl) {
@@ -47,18 +48,20 @@ int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) {
}
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);
@@ -73,3 +76,52 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l
*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;
}
+8
View File
@@ -72,3 +72,11 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l
* _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);