7

In my custom environment an interceptor library is preloaded which runs a special implementation of bind(), connect(), etc. calls.

The problem I see is whenever an application is explicitly enabled capabilities using command setcap, executing the application fails to preload interceptor library and calls default libc connect().

Is it an expected behavior? If yes, what could be the reason to disable LD_PRELOAD?

Is there any tweak or method one can use to successfully preload library with capabilities enabled?

sat63k
  • 333
  • 1
  • 2
  • 13
Sunil Bojanapally
  • 12,528
  • 4
  • 33
  • 46
  • 2
    See [this question](http://stackoverflow.com/questions/9843178/linux-capabilities-setcap-seems-to-disable-ld-library-path) for answers. – scai Aug 05 '13 at 12:54
  • 1
    You can write a wrapper program for the target binary. It will more or less have to be setuid root. It will fork a child process, then execute the target binary (with `LD_PRELOAD` set); the target binary not having any file capabilities set. Your preload library then communicates with the child process (via e.g. a socket pair on say fd 3), with the child process granting the necessary capabilities to the target process, then exiting (and the preload library reaping the child). Let me know if you want an example. – Nominal Animal Aug 06 '13 at 00:49
  • @NominalAnimal Glad if you could show me an example. – Sunil Bojanapally Aug 06 '13 at 08:18

2 Answers2

5

Like Oliver Matthews answered, LD_PRELOAD is disabled for both setuid binaries, and for binaries having file capabilities, for security reasons.

To preload a library while still enabling file capabilities, you have two options:

  1. Set the preloaded library setuid root

    (The Linux dynamic linker ld.so does preload libraries even for setuid/file-capability-enabled binaries, if the libraries are owned by root and marked set-uid.)

  2. Use a setuid root wrapper

    The wrapper obtains full root privileges (both real and effective user and group IDs zero), and stores the original real user and group ID to e.g. environment variable(s).

    The preloaded library has a constructor, e.g.

    static void my_library_init(void) __attribute__((constructor));
    static void my_library_init(void)
    {
        /* ... */
    }
    

    which is automatically run prior to main() (but possibly after other constructors in other preloaded libraries, or in libraries that the preloaded libraries depend on).

    This constructor obtains the desired capabilities, either designated via environment variables (getenv(), cap_from_text()) or the binary executable file itself (cap_from_file("/proc/self/exe")).

    The constructor must temporarily use prctl(PR_SET_KEEPCAPS, 1) to keep capabilities over an identity change, and retain CAP_SETUID and CAP_SETGID capabilities to be able to change identity from root to the user and group specified in the environment variables, before limiting itself to the final capability set.

Both options have obvious security considerations. I recommend sanity checking (and clearing LD_PRELOAD) in the preloaded library constructor. If anything seems suspicious, use _exit() to abort the process immediately.

In general, I recommend the first option for simplicity (both implementation and security issues), but if there is some reason it cannot be used, I can provide a proof of concept code for the second case too. (I have verified both options work on Ubuntu 12.04.2 LTS running a 3.8.0-27-generic x86-64 kernel, using ext4 file system.)

Hope this helps.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • Option 1, setting setuid root to preload library did help me in preloading. However when I set capabilities programmatility which has couple of boundary changes after `exec`, `prctl(PR_SET_KEEPCAPS, 1L)` didn't retain capabilities. My test program execution ran with `strace/gdb ./executable` failed to retain capabilities and succeeded when it ran as `./executable`. This is because it has a boundary change from shell to `strace/gdb` followed by `./executable`. Would like to know what is causing capabilities not to retain over boundary change. – Sunil Bojanapally Aug 22 '13 at 09:12
  • @SunEric: `prctl(PR_SET_KEEPCAPS, 1L)` is only effective for the next `exec*()`, as the `PR_SET_KEEPCAPS` is always reset to 0 after an `exec*()` call. This is mentioned in the `man 2 prctl` manpage. – Nominal Animal Aug 22 '13 at 10:51
  • The first approach doesn't seem to work. Running `LD_PRELOAD=./mylib.so ./myexec` after `sudo setcap [...] ./myexec && sudo chown root:root ./mylib.so && sudo chmod +s ./mylib.so` results in `LD_PRELOAD` being unset and the library not being loaded – Filipe Rodrigues Apr 01 '23 at 14:45
4

Yes, it's for security reasons (see man sudo).

You would have to work around it by explicitly opening the library from within your code at the start of main() using dlopen (or by wrapping main or similar).

Oliver Matthews
  • 7,497
  • 3
  • 33
  • 36
  • Correct me if am wrong in understanding about `ld_preload`: shared object library is preloaded very before any standard library functions. If this is true, required library is preloaded followed by `libc.so`; As you suggested opening a library using `dlopen` in application `main()`, isn't symbols are already resolved by preloaded library or libc by the time `dlopen` is opened ? – Sunil Bojanapally Aug 08 '13 at 16:10