dlsym() + RTLD_NEXT doesn't work as expected on Ubuntu 20.04
Asked Answered
A

2

3

I'm faced a strange runtime behavior on Ubuntu 20.04 (gcc v 9.3.0) when using dlsym() call.

Please, see below a simple example:

  • file test.cpp:
#include <iostream>
#include <dlfcn.h>
#include <execinfo.h>
#include <typeinfo>
#include <string>
#include <memory>
#include <cxxabi.h>
#include <cstdlib>

extern "C"
{
    void __cxa_throw(void *ex, void *info, void (*dest)(void *))
    {
        std::cout << "__cxa_throw() invoked \n";

        static void (*const rethrow)(void *, void *, void (*)(void *)) __attribute__((noreturn))
            = (void (*)(void *, void *, void (*)(void *)))dlsym(RTLD_NEXT, "__cxa_throw");

        std::cout << "addr in lib=" << &rethrow << "\n";

        rethrow(ex, info, dest);

        std::terminate();
    }
}

  • file main.cpp:
#include <iostream>

void foo()
{
  throw std::runtime_error("error");
}

int main()
{
    foo();
    return 0;
}

Build these 2 files as following:

g++ -fPIC -std=c++17 test.cpp -g -c -o test.o
g++ -shared ./test.o -o libtest.so
g++ main.cpp -std=c++17 -g -pedantic -L./ -ltest -ldl

Feeding ldd to ./a.out gives :

ldd a.out 
    linux-vdso.so.1 (0x00007ffe01186000)
    /usr/local/lib/AppProtection/libAppProtection.so (0x00007f1dbd738000)
    libtest.so (0x00007f1dbd733000)
    libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f1dbd708000)
    libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1dbd526000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1dbd50b000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1dbd319000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f1dbd2f4000)
    libX11.so.6 => /lib/x86_64-linux-gnu/libX11.so.6 (0x00007f1dbd1b7000)
    libxcb.so.1 => /lib/x86_64-linux-gnu/libxcb.so.1 (0x00007f1dbd18d000)
    libXi.so.6 => /lib/x86_64-linux-gnu/libXi.so.6 (0x00007f1dbd17b000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f1dbd964000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f1dbd02c000)
    libXau.so.6 => /lib/x86_64-linux-gnu/libXau.so.6 (0x00007f1dbd024000)
    libXdmcp.so.6 => /lib/x86_64-linux-gnu/libXdmcp.so.6 (0x00007f1dbd01c000)
    libXext.so.6 => /lib/x86_64-linux-gnu/libXext.so.6 (0x00007f1dbd007000)
    libbsd.so.0 => /lib/x86_64-linux-gnu/libbsd.so.0 (0x00007f1dbcfed000)

We can see that libtest.so is resolved before libstdc++.so. My expectation how this code should work :

  • __cxa_throw() is defined in 2 shared libraries : libtest.so and libstdc++.so
  • when exception is being thrown in foo() call, the __cxa_throw is resolved from libtest.so and invoked.
  • invoking dlsym(RTLD_NEXT, "__cxa_throw"); makes lookup for __cxa_throw further in the list of shared libraries, found in libstdc++.so and invoked.

This works as expected on all platforms except Ubuntu 20.04 where rethrow is referencing the __cxa_throw from libtest.so (but not libstc++.so) and thus causing endless recursion.

Please assist as I'm puzzled with runtime behavior.

Amato answered 25/6, 2021 at 9:20 Comment(4)
have you tried calling dlsym( RTLD_DEFAULT, "__cxa_throw" ); before dlsym( RTLD_NEXT, ... ); ?Therm
What is /usr/local/lib/AppProtection/libAppProtection.so? Does that exist on systems that don't have problems with dlsym()? You might want to see which dlsym() you're calling...Apostrophe
Works for me on Ubuntu 20.04. Does sudo apt-get purge icaclient followed by rebooting make it work? Does it work if you try it on a fresh install of Ubuntu 20.04?Uttica
Uninstalling icaclient helped. This is very unexpected hook made by citrix client. Thank for help!Amato
U
4

The "app protection" component of the Citrix ICA client installs the library /usr/local/lib/AppProtection/libAppProtection.so and adds an entry for it to /etc/ld.so.preload, causing it to be loaded into every dynamically-linked process. Among other things, this library replaces the dlsym function with its own. (If you're curious how this doesn't just always break everything by going into an infinite loop, see How can I intercept dlsym calls using LD_PRELOAD?. It actually seems like Citrix's code may have been copied and pasted straight from that answer.) The problem is that since RTLD_NEXT depends on being able to inspect the return address, special care is required to avoid breaking that when hooking dlsym, and they didn't take that special care. As a result, RTLD_NEXT will look for the symbol in the next library after libAppProtection.so, instead of in the next library after your code, which causes exactly the problem you ran into.

Here's some choices for what to do about it:

  • Complain to Citrix support until they fix their buggy library
  • sudo apt-get purge icaclient (and optionally reinstall it afterwards, but choosing no when asked about the app protection component)
  • Modify your program to load the real dlsym from libdl.so and then use it for any calls that use RTLD_NEXT
Uttica answered 27/6, 2021 at 6:58 Comment(1)
THB, I was very surprised to see that citrix client hooks dlsym and the hook itself does wrong job. Thanks a lot for answering!Amato
A
1

As mentioned in comments, the issue was caused by hook done by Citrix ICA client that hooks dlsym() and thus causes wrong dlsym() to be invoked.

Amato answered 27/6, 2021 at 5:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.