0

Is there a way to determine at runtime which shared libraries have been loaded into the global symbol namespace of the current process? I'm primarily interested in anything that was loaded as a result of a dlopen() call that used the RTLD_GLOBAL flag.

I'm wanting to do this for auditing purposes -- it's important for an application I work on that dynamically-loaded shared libraries are loaded with dlopen's RTLD_LOCAL wherever possible so as not to conflict with third-party code; anything that's loaded into the global symbol namespace needs to be tightly controlled.

I've looked at the dl_iterate_phdr() API, but it doesn't seem to include this information.

Nick Hutchinson
  • 5,044
  • 5
  • 33
  • 34
  • Well, you can check if library is already loaded (if you have the list) using for example dlopen with RTLD_NOLOAD, that will return null or handle, depending if it has been already loaded or not. Something that may help you is probably /proc/PID/maps parsing/analysis. That may also help you [link](https://stackoverflow.com/questions/5103443/how-to-check-what-shared-libraries-are-loaded-at-run-time-for-a-given-process) – Tomasz Andel Oct 11 '17 at 22:38

2 Answers2

1

You can try with

#define _GNU_SOURCE
#include <dlfcn.h>

typedef void *(*orig_dl)(const char *file, int mode);
void *dlopen(const char *file, int mode)
{
    orig_dl o_dlopen;
    o_dlopen = (orig_dl)dlsym(RTLD_NEXT, "dlopen");
    return o_dlopen(file, mode);
}

Compile it using gcc -shared -fPIC dlo.c -o dlo.so -ldl add LD_PRELOAD=dlo.so and here you go. You can log/trace/print any dlopen usage with specific mode

Tomasz Andel
  • 173
  • 4
0

I think the suggestion to replace dlopen() using LD_PRELOAD is only a partial solution -- you won't catch dependencies of a library loaded with dlopen() that way.

In the end, I couldn't see any way of doing this without scraping the internal state of the dynamic linker itself. It found that there's a _rtld_global symbol exported from ld.so that has the information, but that you have to use private Glibc headers to interpret it.

The following is a Python snippet that will (assuming my reading of the Glibc sources is correct) print all the shared libraries in the global namespace in the order that they will be searched. Libraries loaded with RTLD_LOCAL will not be printed.

The fact that it relies on implementation details of Glibc means this approach is fraught with peril, but for my testing/auditing purposes I think it'll do nicely.

import ctypes

# Abridged type declarations pillaged from Glibc. See:
# - https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/generic/ldsodefs.h
# - https://sourceware.org/git/?p=glibc.git;a=blob;f=include/link.h

class link_map(ctypes.Structure):
    _fields_ = [
        ("l_addr", ctypes.c_size_t),
        ("l_name", ctypes.c_char_p),
    ]


class r_scope_elem(ctypes.Structure):
    _fields_ = [
        ("r_list", ctypes.POINTER(ctypes.POINTER(link_map))),
        ("r_nlist", ctypes.c_uint),
    ]


class rtld_global(ctypes.Structure):
    _fields_ = [
        ("_ns_loaded", ctypes.POINTER(link_map)),
        ("_ns_nloaded", ctypes.c_uint),
        ("_ns_main_searchlist", ctypes.POINTER(r_scope_elem)),
    ]

_rtld_global = rtld_global.in_dll(ctypes.CDLL(None), "_rtld_global")
searchlist = _rtld_global._ns_main_searchlist[0]

print [searchlist.r_list[n][0].l_name for n in xrange(searchlist.r_nlist)]

On my CentOS 7 system, this prints:

['', '/lib64/libpython2.7.so.1.0', '/lib64/libpthread.so.0', '/lib64/libdl.so.2',
 '/lib64/libutil.so.1', '/lib64/libm.so.6', '/lib64/libc.so.6',
 '/lib64/ld-linux-x86-64.so.2']
Nick Hutchinson
  • 5,044
  • 5
  • 33
  • 34