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:
+58
-6
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user