Ld_Preload with Setuid Binary

trying to understand LD_PRELOAD and SUID/SGID with checkinstall or porg

  1. In case coreutils (mkdir, mv, etc. ) on your system are statically linked (i.e. running file on them reports "statically linked") porg will not be able to track their operations and thus some installed files may go untracked. Statically linked executables are second-class citizens in Linux and LD_PRELOAD does not support them.

  2. Setuid executables indeed sanitize LD_PRELOAD before usage - they ignore all files which have slashes in name (so that only files from standard system paths can be loaded) and also require that shared library itself has setuid bit set. So in your case you'll need to locate porg's preloaded library and set setuid bit on it (via chmod a+s libxyz.so). BTW it may make sense to ask porg authors to do this change in their distro. I don't think this will cause any problems in a typical package as installers typically don't need to run setuid programs (like mount, passwd, sudo).

Does using linux capabilities disable LD_PRELOAD

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.

stdbuf with setuid/capabilities

From looking at the stdbuf source code it looks like it works by setting LD_PRELOAD. There are of course security concerns using LD_PRELOAD with setuid executables or sudo.

One suggestion I found was to disable the noatsecure selinux attribute for your executable.

Another, simpler, option would be to avoid stdbuf and simply call fflush(stdout) from your source code directly.

Howto capture posix_spawnp() with LD_PRELOAD?

Works alright with my linux/glibc. Here's a working example:

#!/bin/sh -eu

cat > main.c <<EOF
#include <spawn.h>
#include <sys/wait.h>
int main(int C, char **V)
{
pid_t pid;
if(0!=posix_spawnp(&pid,V[1],NULL,NULL,V+1,NULL))
return 1;
wait(0);
}
EOF
gcc main.c

cat > libspawn.c <<EOF
#define _GNU_SOURCE
#include <spawn.h>
#include <dlfcn.h>
#include <stdio.h>
typedef int spawnp_signature(pid_t *pid, const char *file,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const argv[], char *const envp[]);
spawnp_signature posix_spwanp;
int posix_spawnp(pid_t *pid, const char *file,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const argv[], char *const envp[])

{
fprintf(stderr, "OVERRIDE\n");
spawnp_signature *real = dlsym(RTLD_NEXT,__func__);
return real(pid,file,file_actions,attrp,argv,envp);

}
EOF
gcc -shared -fpic -o libspawn.so libspawn.c -ldl

./a.out echo hello world
LD_PRELOAD=$PWD/libspawn.so ./a.out echo hello world

The second output is printed with the word OVERRIDE as expected.

EDIT:
After making your example compilable, it works for me too. Perhaps some of the unchecked calls are failing on your machine or perhaps you've forgotten to link the dl library.

#!/bin/sh -eu

cat > main.c <<EOF
#include <spawn.h>
#include <sys/wait.h>
int main(int C, char **V)
{
pid_t pid;
if(0!=posix_spawnp(&pid,V[1],NULL,NULL,V+1,NULL))
return 1;
wait(0);
}
EOF
gcc main.c

cat > libspawn.c <<EOF
#define _GNU_SOURCE
#include <spawn.h>
#include <dlfcn.h>
#include <stdio.h>
typedef int spawnp_signature(pid_t *pid, const char *file,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const argv[], char *const envp[]);
spawnp_signature posix_spwanp;
int posix_spawnp(pid_t *pid, const char *file,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const argv[], char *const envp[])

{
fprintf(stderr, "OVERRIDE\n");
spawnp_signature *real = dlsym(RTLD_NEXT,__func__);
return real(pid,file,file_actions,attrp,argv,envp);

}
EOF
cat >libspawn.c <<EOF
#define _GNU_SOURCE
#include <spawn.h>
#include <dlfcn.h>
#include <stdio.h>
typedef int (*orig_posix_spawnp_t)(pid_t *pid, const char *file,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const argv[], char *const envp[]);
typedef FILE *(*orig_fopen_t)(char const*, char const*);
int posix_spawnp(pid_t * pid, const char * file, const posix_spawn_file_actions_t * file_actions, const posix_spawnattr_t * attrp, char * const argv[], char * const envp[])
{
orig_fopen_t orig_fopen = (orig_fopen_t)dlsym(RTLD_NEXT, "fopen");
FILE * fp = orig_fopen("/tmp/DiagnosticsAgent.log", "a");
fprintf(fp, "*** posix_spawnp(..., {");
int i;
for (i = 0; argv[i] != 0; ++ i)
fprintf(fp, "%s, ", argv[i]);
fprintf(fp, "}, ...)\n");
fclose(fp);

void * handle = dlopen("libpthread.so.0", RTLD_LAZY);
if(!handle) { perror(0); return -1; }
orig_posix_spawnp_t orig_posix_spawnp = (orig_posix_spawnp_t)dlsym(handle, "posix_spawnp");
fprintf(stderr,"OVERRIDE\t");
return orig_posix_spawnp(pid, file, file_actions, attrp, argv, envp);
}
EOF
gcc -shared -fpic -o libspawn.so libspawn.c -ldl

./a.out echo hello world
LD_PRELOAD=$PWD/libspawn.so ./a.out echo hello world

Is it possible to override main method using LD_PRELOAD?

No, you cannot use LD_PRELOAD to override the main function of a binary.

   LD_PRELOAD
A whitespace-separated list of additional, user-specified, ELF
shared libraries to be loaded before all others. This can be
used to selectively override functions in other shared
libraries. For setuid/setgid ELF binaries, only libraries in
the standard search directories that are also setgid will be
loaded.

What LD_PRELOAD gives you is the ability to inject symbols that are dynamically linked so that when the runtime linker goes to resolve them, it finds your replacement instead of the one it'd normally find. Let's take this example:

main.c:

#include <stdio.h>

int main (void)
{
puts("Hello, world!");
return 0;
}

puts.c

#include <stdio.h>

int puts (const char *s)
{
return printf("Hijacked puts: %s\n", s);
}

If compile main.c, check out its symbols:

$ gcc -o main main.c
$ objdump -t main | grep 'main\|puts'
main: file format elf64-x86-64
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 F *UND* 0000000000000000 puts@@GLIBC_2.2.5
0000000000000000 F *UND* 0000000000000000 __libc_start_main@@GLIBC_2.2.5
00000000004004f4 g F .text 0000000000000015 main

Notice that the main() function is listed here with a known address, whereas puts(), which will be pulled from glibc, is unknown.

Thus, we can force the runtime linker to use our puts instead:

$ gcc -o puts.so -shared -fPIC puts.c
$ LD_PRELOAD=./puts.so ./main
Hijacked puts: Hello, world!

In contrast, if we statically link our original binary:

$ gcc -o main -static main.c
$ objdump -t main | grep 'main\|puts'
main: file format elf64-x86-64
00000000006c27c0 l O .data 0000000000000888 main_arena
0000000000000000 l df *ABS* 0000000000000000 main.c
00000000006c5580 l O .bss 0000000000000008 _nl_loaded_domains
00000000004957d0 g F __libc_freeres_fn 00000000000000d6 _nl_unload_domain
000000000041bcb0 g F .text 000000000000170c _nl_load_domain
00000000006c60e0 g O .bss 0000000000000008 _nl_domain_bindings
0000000000402050 w F .text 0000000000000189 puts
...

$ LD_PRELOAD=./puts.so ./main
Hello, world!

Our override no longer worked because puts() was statically linked, which caused the symbol to be resolved at (static) link time.



Related Topics



Leave a reply



Submit