Question

In Linux. Per the dlsym(3) Linux man page,

    *Since the value of the symbol could actually be NULL
    (so that a NULL return from dlsym() need not indicate an error),*

Why is this, when can a symbol (for a function, specifically) be actually NULL? I am reviewing code and found a piece using dlerror to clean first, dlsym next, and dlerror to check for errors. But it does not check the resulting function from being null before calling it:

  • dlerror();
  • a_func_name = ...dlsym(...);
  • if (dlerror()) goto end;
  • a_func_name(...); // Never checked if a_func_name == NULL;

I am just a reviewer so don't have the option to just add the check. And perhaps the author knows NULL can never be returned. My job is to challenge that but don't know what could make this return a valid NULL so I can then check if such a condition could be met in this code's context. Have not found the right thing to read with Google, a pointer to good documentation would be enough unless you want to explain explicitly which would be great.

Was it helpful?

Solution 2

Well, if it's returned with no errors, then pointer is valid and NULL is about as illegal as any random pointer from the shared object. Like the wrong function, data or whatever.

OTHER TIPS

I know of one particular case where the symbol value returned by dlsym() can be NULL, which is when using GNU indirection functions (IFUNCs). However, there are presumably other cases, since the text in the dlsym(3) manual page pre-dates the invention of IFUNCs.

Here's an example using IFUNCs. First, a file that will be used to create a shared library:

$ cat foo.c 
/* foo.c */

#include <stdio.h>

/* This is a 'GNU indirect function' (IFUNC) that will be called by
   dlsym() to resolve the symbol "foo" to an address. Typically, such
   a function would return the address of an actual function, but it
   can also just return NULL.  For some background on IFUNCs, see
   https://willnewton.name/uncategorized/using-gnu-indirect-functions/ */

asm (".type foo, @gnu_indirect_function");

void *
foo(void)
{
    fprintf(stderr, "foo called\n");
    return NULL;
}

Now the main program, which will look up the symbol foo in the shared library:

$ cat main.c
/* main.c */

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char *argv[])
{
    void *handle;
    void (*funcp)(void);

    handle  = dlopen("./foo.so", RTLD_LAZY);
    if (handle == NULL) {
        fprintf(stderr, "dlopen: %s\n", dlerror());
        exit(EXIT_FAILURE);
    }

    dlerror();      /* Clear any outstanding error */

    funcp = dlsym(handle, "foo");

    printf("Results after dlsym(): funcp = %p; dlerror = %s\n",
            (void *) funcp, dlerror());

    exit(EXIT_SUCCESS);
}

Now build and run to see a case where dlsym() returns NULL, while dlerror() also returns NULL:

$ cc -Wall -fPIC -shared -o libfoo.so foo.c
$ cc -Wall -o main main.c libfoo.so -ldl
$ LD_LIBRARY_PATH=. ./main
foo called
Results after dlsym(): funcp = (nil); dlerror = (null)

It can't be if the library/PIE is a product of normal C compilation, as C won't ever put a global object at the NULL address, but you can get a symbol to resolve to NULL using special linker tricks:

null.c:

#include <stdio.h>
extern char null_addressed_char;
int main(void) 
{
    printf("&null_addressed_char=%p\n", &null_addressed_char);
}

Compile, link, and run:

$ clang null.c -Xlinker --defsym -Xlinker null_addressed_char=0 && ./a.out
&null_addressed_char=(nil)

If you don't allow any such weirdness, you can treat NULL returns from dlsym as errors.

dlerror() returns the last error, not the status of the last call. So if nothing else the code you show may potentially get a valid result from dlsym() and fool itself into thinking there was an error (because there was still one in the queue). The purpose behind dlerror is to provide human-readable error messages. If you aren't printing the result, you are using it wrong.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top