MEDIUM: debug: detect redefinition of symbols upon dlopen()

In order to better detect the danger caused by extra shared libraries
which replace some symbols, upon dlopen() we now compare a few critical
symbols such as malloc(), free(), and some OpenSSL symbols, to see if
the loaded library comes with its own version. If this happens, a
warning is emitted and TAINTED_REDEFINITION is set. This is important
because some external libs might be linked against different libraries
than the ones haproxy was linked with, and most often this will end
very badly (e.g. an OpenSSL object is allocated by haproxy and freed
by such libs).

Since the main source of dlopen() calls is the Lua lib, via a "require"
statement, it's worth trying to show a Lua call trace when detecting a
symbol redefinition during dlopen(). As such we emit a Lua backtrace if
Lua is detected as being in use.
This commit is contained in:
Willy Tarreau 2022-06-19 16:49:51 +02:00
parent 40dde2d5c1
commit 177aed56dc
2 changed files with 44 additions and 0 deletions

View File

@ -201,6 +201,7 @@ enum tainted_flags {
TAINTED_WARN = 0x00000010, /* a WARN_ON triggered */
TAINTED_BUG = 0x00000020, /* a BUG_ON triggered */
TAINTED_SHARED_LIBS = 0x00000040, /* a shared library was loaded */
TAINTED_REDEFINITION = 0x00000080, /* symbol redefinition detected */
};
/* this is a bit field made of TAINTED_*, and is declared in haproxy.c */

View File

@ -5810,7 +5810,21 @@ int openssl_compare_current_name(const char *name)
void *dlopen(const char *filename, int flags)
{
static void *(*_dlopen)(const char *filename, int flags);
struct {
const char *name;
void *curr, *next;
} check_syms[] = {
{ .name = "malloc", },
{ .name = "free", },
{ .name = "SSL_library_init", },
{ .name = "X509_free", },
/* insert only above, 0 must be the last one */
{ 0 },
};
const char *trace;
void *addr;
void *ret;
int sym = 0;
if (!_dlopen) {
_dlopen = get_sym_next_addr("dlopen");
@ -5820,6 +5834,16 @@ void *dlopen(const char *filename, int flags)
}
}
/* save a few pointers to critical symbols. We keep a copy of both the
* current and the next value, because we might already have replaced
* some of them (e.g. malloc/free with DEBUG_MEM_STATS), and we're only
* interested in verifying that a loaded library doesn't come with a
* completely different definition that would be incompatible.
*/
for (sym = 0; check_syms[sym].name; sym++) {
check_syms[sym].curr = get_sym_curr_addr(check_syms[sym].name);
check_syms[sym].next = get_sym_next_addr(check_syms[sym].name);
}
/* now open the requested lib */
ret = _dlopen(filename, flags);
@ -5828,6 +5852,25 @@ void *dlopen(const char *filename, int flags)
mark_tainted(TAINTED_SHARED_LIBS);
/* and check that critical symbols didn't change */
for (sym = 0; check_syms[sym].name; sym++) {
if (!check_syms[sym].curr && !check_syms[sym].next)
continue;
addr = dlsym(ret, check_syms[sym].name);
if (!addr || addr == check_syms[sym].curr || addr == check_syms[sym].next)
continue;
/* OK it's clear that this symbol was redefined */
mark_tainted(TAINTED_REDEFINITION);
trace = hlua_show_current_location("\n ");
ha_warning("dlopen(): shared library '%s' brings a different definition of symbol '%s'. The process cannot be trusted anymore!%s%s\n",
filename, check_syms[sym].name,
trace ? " Suspected call location: \n " : "",
trace ? trace : "");
}
return ret;
}
#endif