Override libc functions called from another libc function with LD_PRELOAD - c

I've a project aiming to run php-cgi chrooted for mass virtual hosting (more than 10k virtual host), with each virtual host having their own chroot, under Ubuntu Lucid x86_64.
I would like to avoid creating the necessary environment inside each chroot for things like /dev/null, /dev/zero, locales, icons... and whatever which could be needed by php modules thinking that they run outside chroot.
The goal is to make php-cgi run inside a chroot, but allowing him access to files outside the chroot as long as those files are (for most of them) opened in read-only mode, and on an allowed list (/dev/log, /dev/zero, /dev/null, path to the locales...)
The obvious way seems to create (or use if it exists) a kernel module, which could hook and redirect trusted open() paths, outside of the chroot.
But I don't think it's the easiest way:
I've never done a kernel module, so I do not correctly estimate the difficulty.
There seems to be multiple syscall to hook file "open" (open, connect, mmap...), but I guess there is a common kernel function for everything related to file opening.
I do want to minimize the number of patchs to php or it's module, to minimize the amount of work needed each time I will update our platform to the latest stable PHP release (and so update from upstream PHP releases more often and quickly), so I find better to patch the behavior of PHP from the outside (because we have a particular setup, so patching PHP and propose patch to upstream is not relevant).
Instead, I'm currently trying an userland solution : hook libc functions with LD_PRELOAD, which works well in most cases and is really quick to implement, but I've encountered a problem which I'm unable to resolve alone.
(The idea is to talk to a daemon running outside the chroot, and get file descriptor from it using ioctl SENDFD and RECVFD).
When I call syslog() (without openlog() first), syslog() calls connect() to open a file.
Example:
folays#phenix:~/ldpreload$ strace logger test 2>&1 | grep connect
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
connect(1, {sa_family=AF_FILE, path="/dev/log"}, 110) = 0
So far so good, I've tried to hook the connect() function of libc, without success.
I've also tried to put some flags to dlopen() inside the _init() function of my preload library to test if some of them could make this work, without success
Here is the relevant code of my preload library:
void __attribute__((constructor)) my_init(void)
{
printf("INIT preloadz %s\n", __progname);
dlopen(getenv("LD_PRELOAD"), RTLD_NOLOAD | RTLD_DEEPBIND | RTLD_GLOBAL |
RTLD_NOW);
}
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
printf("HOOKED connect\n");
int (*f)() = dlsym(RTLD_NEXT, "connect");
int ret = f(sockfd, addr, addrlen);
return ret;
}
int __connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
printf("HOOKED __connect\n");
int (*f)() = dlsym(RTLD_NEXT, "connect");
int ret = f(sockfd, addr, addrlen);
return ret;
}
But the connect() function of the libc still takes precedence over mine:
folays#phenix:~/ldpreload$ LD_PRELOAD=./lib-preload.so logger test
INIT preloadz logger
[...] no lines with "HOOKED connect..." [...]
folays#phenix:~/ldpreload$
Looking at the code of syslog() (apt-get source libc6 , glibc-2.13/misc/syslog.c), it seems to call openlog_internal, which in turn call __connect(), at misc/syslog.c line 386:
if (LogFile != -1 && !connected)
{
int old_errno = errno;
if (__connect(LogFile, &SyslogAddr, sizeof(SyslogAddr))
== -1)
{
Well, objdump shows me connect and __connect in the dynamic symbol table of libc:
folays#phenix:~/ldpreload$ objdump -T /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
00000000000e6d00 w DF .text 000000000000005e GLIBC_2.2.5 connect
00000000000e6d00 w DF .text 000000000000005e GLIBC_2.2.5 __connect
But no connect symbol in the dynamic relocation entries, so I guess that it explains why I cannot successfully override the connect() used by openlog_internal(), it probably does not use dynamic symbol relocation, and probably has the address of the __connect() function in hard (a relative -fPIC offset?).
folays#phenix:~/ldpreload$ objdump -R /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
folays#phenix:~/ldpreload$
connect is a weak alias to __connect:
eglibc-2.13/socket/connect.c:weak_alias (__connect, connect)
gdb is still able to breakpoint on the libc connect symbol of the libc:
folays#phenix:~/ldpreload$ gdb logger
(gdb) b connect
Breakpoint 1 at 0x400dc8
(gdb) r test
Starting program: /usr/bin/logger
Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
82 ../sysdeps/unix/syscall-template.S: No such file or directory.
in ../sysdeps/unix/syscall-template.S
(gdb) c 2
Will ignore next crossing of breakpoint 1. Continuing.
Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
82 in ../sysdeps/unix/syscall-template.S
(gdb) bt
#0 connect () at ../sysdeps/unix/syscall-template.S:82
#1 0x00007ffff7b28974 in openlog_internal (ident=<value optimized out>, logstat=<value optimized out>, logfac=<value optimized out>) at ../misc/syslog.c:386
#2 0x00007ffff7b29187 in __vsyslog_chk (pri=<value optimized out>, flag=1, fmt=0x40198e "%s", ap=0x7fffffffdd40) at ../misc/syslog.c:274
#3 0x00007ffff7b293af in __syslog_chk (pri=<value optimized out>, flag=<value optimized out>, fmt=<value optimized out>) at ../misc/syslog.c:131
Of course, I could completely skip this particular problem by doing an openlog() myself, but I guess that I will encounter the same type of problem with some others functions.
I don't really understand why openlog_internal does not use dynamic symbol relocation to call __connect(), and if it's even possible to hook this __connect() call by using simple LD_PRELOAD mechanism.
The others way I see how it could be done:
Load libc.so from an LD_PRELOAD with dlopen, get the address of the libc's __connect with dlsym() and then patch the function (ASM wise) to get the hook working. It seems really overkill and error prone.
Use a modified custom libc for PHP to fix those problems directly at the source (open / connect / mmap functions...)
Code a LKM, to redirect file access where I want. Pros : no need of ioctl(SENDFD) and no daemon outside the chroot.
I would really appreciate to learn, if it is ever possible, how I could still hook the call to __connect() issued by openlog_internal, suggestions, or links to kernel documentation related to syscall hooking and redirection.
My google searches related to "hook syscalls" found lot of references to LSM, but it seems to only allow ACLs answering "yes" or "no", but no redirection of open() paths.
Thanks for reading.

It's definitely not possible with LD_PRELOAD without building your own heavily-modified libc, in which case you might as well just put the redirection hacks directly inside. There are not necessarily calls to open, connect, etc. whatsoever. Instead there may be calls to a similar hidden function bound at library-creation time (not dynamically rebindable) or even inline syscalls, and this can of course change unpredictably with the version.
Your options are either a kernel module, or perhaps using ptrace on everything inside the "chroot" and modifying the arguments to syscalls whenever the tracing process encounters one that needs patching up. Neither sounds easy...
Or you could just accept that you need a minimal set of critical device nodes and files to exist inside a chroot for it to work. Using a different libc in place of glibc, if possible, would help you minimize the number of additional files needed.

Related

Attaching to a process and call `dup2` on aarch64?

I tried attaching to a running process with gdb to redirect its stdout to an external file with these commands:
#Attaching
gdb -p 123456
#Redirecting (within GDB)
(gdb) p dup2(open("/tmp/my_stdout", 1089, 0777), 1)
I used the number 1089 because it represents O_WRONLY | O_CREAT | O_APPEND.
Firts, GDB just complained about some missing return types:
'open64' has unknown return type; cast the call to its declared return type
So I modified my command to
#Redirecting (within GDB)
(gdb) p (int)dup2((int)open("/tmp/my_stdout", 1089, 0777), 1)
This was successfully executed, and also works.
I'm trying to figure out how can I write a small utility that does the exact same thing as the above:
attaches to a process by PID
calls this (int)dup2((int)open("/tmp/my_stdout", 1089, 0777), 1)
Part2 seems easy, however part1 doesn't seem to work on aarch64. I could manage to work it on arm though.
There are a quite a few solutions which tries to solve this problem:
reptyr (doesn't work on process started by systemctl)
reredirect (doesn't support aarch64 at all)
injcode (doesn't support 64bit at all)
neercs (for sure no support for aarch64)
retty (for sure no support for aarch64)
If GDB can work, this is surely possible, but GDB is huge to analyze, and I hope I have some better solution which would not take weeks or months, like digging myself into GDB's source.

Backtracing linux socket with gdb - functions not being shown, buffer not updating

As mentioned in the title, gdb behaves weirdly when I try to set a breakpoint at linux socket functions such as send etc. I've read through similar threads where it's been suggested to use the debug argument, but I can't set it as I'm just messing around with different Linux programs/video games and I've noticed the same behaviour - for the most part, send can't be backtraced. Only the familiar "< memory address > in ??" messages are shown, and the addresses themselves don't point to anything (can't be retrieved). At the same time, the message buffer in send (or sendto etc) is stuck at one value and not updating (while all the other values such as len are, in real time). I suppose these are simply limitations of gdb, but I'd appreciate it if someone more knowledgeable could shed some light on the issue.
EDIT:
First I'll list my steps:
As an example, I'm trying to backtrace the sendto in openarena (free linux quake-based game). Openarena in particular is not ELF readable, but I get the same results with other ELF-readable files. Because it isn't ELF-readable, I can only attach to a running process. So I type gdb /usr/games/openarena -p < process name > , though I'm pretty sure the binary path is redundant in this case
(it still says "0x7ffc8a81ebe0s": not in executable format: file format not recognized, but I'm able to list functions and everything anyway) As a side note, attaching produces this bug:
((( https://forum.manjaro.org/t/critical-bug-gdb-broken-with-last-stable-update/53155
"Error while reading shared library symbols for /lib/x86_64-linux-gnu/libpthread.so.0:"
However, in my case, I'm still able to attach, but the program eventually crashes after complaining about not being able to find a thread. This also often happens when joining a server from a lobby, but this is a side note, as I've tested programs by running them directly from gdb as well which doesn't produce the error, but still led to this weird socket behaviour. )))
So after attaching to the process, I type source script, the script containing:
break sendto
commands 1
backtrace -raw-frame-arguments on
continue
end
I then resume the program and it's firing backtraces in realtime. This is sample output after joining a server:
Thread 1 "ioquake3" hit Breakpoint 1, __libc_sendto (fd=43, buf=0x7ffefc5028f0, len=32, flags=0, addr=..., addrlen=16) at ../sysdeps/unix/sysv/linux/sendto.c:25
25 in ../sysdeps/unix/sysv/linux/sendto.c
#0 __libc_sendto (fd=43, buf=0x7ffefc5028f0, len=32, flags=0, addr=..., addrlen=16) at ../sysdeps/unix/sysv/linux/sendto.c:25
#1 0x00005642f88bc5fc in ?? ()
#2 0x00005642f88baf94 in ?? ()
#3 0x00005642f888b1ee in ?? ()
#4 0x00005642f88779cf in ?? ()
#5 0x00005642f8886572 in ?? ()
#6 0x00005642f88a58bf in ?? ()
#7 0x00005642f886e3f5 in main ()
Thread 1 "ioquake3" hit Breakpoint 1, __libc_sendto (fd=43, buf=0x7ffefc5028f0, len=34, flags=0, addr=..., addrlen=16) at ../sysdeps/unix/sysv/linux/sendto.c:25
25 in ../sysdeps/unix/sysv/linux/sendto.c
#0 __libc_sendto (fd=43, buf=0x7ffefc5028f0, len=34, flags=0, addr=..., addrlen=16) at ../sysdeps/unix/sysv/linux/sendto.c:25
#1 0x00005642f88bc5fc in ?? ()
#2 0x00005642f88baf94 in ?? ()
#3 0x00005642f888b1ee in ?? ()
#4 0x00005642f88779cf in ?? ()
#5 0x00005642f8886572 in ?? ()
#6 0x00005642f88a58bf in ?? ()
#7 0x00005642f886e3f5 in main ()
As you can see, there is nothing between main and the send, the socket buffer is stuck at the same message, while len is updating correctly. I can perform any kind of actions, jump, shoot, and the output still stays the same. As I mentioned, I get pretty much the same output with other applications. There's some main function/loop, then nothing and then just the send function.
As for my system specs, I'm on Kubuntu 21.04,
GDB version is: GNU gdb (Ubuntu 10.1-2ubuntu2) 10.1.90.20210411-git
Glibc: Ubuntu GLIBC 2.33-0ubuntu5
I've migrated recently from an earlier LTS release, the upgrade might not have been entirely clean, I suppose...
As you can see, there is nothing between main and the send, the socket buffer is stuck at the same message, while len is updating correctly.
A few points:
There are no function names (which isn't the same as "nothing"). That is expected IF there is no symbol table. GDB uses symbol table(s) from loaded binaries to translate addresses into function names.
If the binary is fully stripped, or if the code generated into memory directly, or if the binary is decompressed or decrypted into memory, then you would need to teach GDB where it can get the symbol table from (if the symbol table exists at all, which isn't a given).
The socket buffer being "stuck" is not necessarily unexpected either: the program is very likely to be doing repeated sendto calls using the same stack buffer. Like this:
while (!error) {
char buf[4096];
int n = copy_to(buf); // fill buf[] with data
if (sendto(fd, buf, n, ...) != n) // handle error
}
Update:
I still don't quite understand why I can't see buffer values change.
You are not looking at the buffer contents, you are looking at the buffer address (i.e. &buf[0] given the code above).
If you want to look at the buffer contents, you need to print / examine it. E.g. to examine the first 8 bytes being sent, add this to your breakpoint command: x/8cx buf. But also note that it is common to have a fixed prefix on all the packets being sent, and it's not guaranteed that the 8 leading bytes will change on every packet either.

Intercepting file operations on Linux

I'm working on a cloud platform for rendering visual effects and animation. We accept various formats of scene descriptions render them, and return the image outputs to the user. The processing backend is Linux. Sometimes we receive scene descriptions generated on Windows so we get paths that look like 'C:\path\to\file.mb'. I've written a Linux shared library to intercept various filesystem calls and alter the paths to something Linux can understand '/C/path/to/file'. I use the LD_PRELOAD mechanism to insert my functions before the "real" ones and it works great... except when it doesn't.
For example this command:
LD_PRELOAD=/home/robert/path_fix.so Render -s 1 -e 1 C:\path\to\test_scene_03_vray.ma
Will not locate test_scene_03_vray.ma. This also doesn't work:
LD_PRELOAD=/home/robert/path_fix.so echo test > C:\path\to\test.txt
I've been using ltrace to figure out which functions are called with references to path names, but these examples don't show up in my traces:
ltrace -f -C -S -o ltrace.out Render C:\path\to\test_scene_03_vray.ma
Is there a tool other than ltrace that will let me see which function calls are invoked?
Here's the list of what I already have overrides for:
fopen
freopen
opendir
open
creat
openat
stat
lstat
fstatat
__lxstat
__xstat
mkdir
mkdirat
unlink
unlinkat
access
faccessat
rename
renameat
renameat2
chmod
fchmodat
chown
lchown
fchownat
link
linkat
name_to_handle_at
readlink
readlinkat
symlink
symlinkat
rmdir
chdir
Are there more functions that I'm missing here? I tried to implement everything that takes
const char *filepath
as an argument.
Side question: This seems like it might already be a solved problem... Is there a library or other approach that converts Windows paths to unix friendly paths? Keep in mind that the rendering applications are 3rd party proprietary binaries so I can't modify them.
Thanks for any suggestions!
Yes, there are other functions, such as the 64-bit functions open64(). There are also functions such as __open().
On a 64-bit Centos 7 server, for just open I get:
nm -D /lib64/libc.so.6 | grep open
0000000000033d70 T catopen
00000000003c0b80 B _dl_open_hook
000000000006b140 T fdopen
00000000000ba4c0 W fdopendir
00000000000755d0 T fmemopen
000000000006ba00 T fopen
000000000006ba00 W fopen64
000000000006bb60 T fopencookie
0000000000073700 T freopen
0000000000074b60 T freopen64
00000000000eb7a0 T fts_open
0000000000022220 T iconv_open
000000000006b140 T _IO_fdopen
0000000000077230 T _IO_file_fopen
0000000000077180 T _IO_file_open
000000000006ba00 T _IO_fopen
000000000006d1d0 T _IO_popen
000000000006cee0 T _IO_proc_open
0000000000131580 T __libc_dlopen_mode
00000000000e82a0 W open
00000000000e82a0 W __open
00000000000ed0f0 T __open_2
00000000000e82a0 W open64
00000000000e82a0 W __open64
00000000000ed110 T __open64_2
00000000000e8330 W openat
00000000000e8410 T __openat_2
00000000000e8330 W openat64
00000000000e8410 W __openat64_2
00000000000f7860 T open_by_handle_at
00000000000340b0 T __open_catalog
00000000000b9f70 W opendir
00000000000f12b0 T openlog
0000000000073ea0 T open_memstream
00000000000731c0 T open_wmemstream
000000000006d1d0 T popen
0000000000130630 W posix_openpt
00000000000e6ec0 T posix_spawn_file_actions_addopen
For example, on Linux this C code is quite likely to work just fine:
int fd = __open( path, O_RDONLY );
You're also not going to catch statically-linked processes, or those that issue a system call such as open directly.
You can technically use kprobes to intercept the calls but if thr numbrr of files arent too many or dynamicaly generated, i suggest creating symlinks. It is much safer and simpler.

what happens if an application open more than one connection to syslog?

I have an application that uses syslog for logging. another library within this application explicitly calls openlog() for its own usage, in this case something strange happens: stderr output is sent to a tcp socket I already opened.
When I change the lib's output log to stderr or stdout everything works fine.
I was wondering if this a problem with two syslog connection or is it just a mess-up somewhere in the code?
This is syslog initialisation of the main app:
openlog( "app", LOG_PID|LOG_NDELAY, LOG_LOCAL1 );
This is syslog initialisation of the lib:
openlog("lib", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
It is probably implementation dependent. If you use MUSL libc the code is here in syslog.c an you can see that only one fd is used for syslog (so two openlog-s are sharing the same log_fd). Look into GNU libc source code to see what happens on most Linux implementations. You might also investigate with strace or ltrace

linker issue or other? dynamically loaded lib

My program loads a dynamic library, but after it tries to load it (it doesn't seem to, or at least something's amiss with the loading. A free() throws an error, and I commented out that line.)
I get the following in gdb.
Program received signal SIGSEGV, Segmentation fault.
__strlen_ia32 () at ../sysdeps/i386/i686/multiarch/../../i586/strlen.S:99
99 ../sysdeps/i386/i686/multiarch/../../i586/strlen.S: No such file or directory.
in ../sysdeps/i386/i686/multiarch/../../i586/strlen.S
How would I go about addressing this?
EDIT1:
The above issue was due to me not having an xml file where it should have been.
Here's the first error that I covered up to get to the initial error I showed.
(gdb) s
__dlopen (file=0xbfffd03c "/usr/lib/libvisual-0.5/actor/actor_AVS.so", mode=1)
at dlopen.c:76
76 dlopen.c: No such file or directory.
in dlopen.c
(gdb) bt
#0 __dlopen (file=0xbfffd03c "/usr/lib/libvisual-0.5/actor/actor_AVS.so",
mode=1) at dlopen.c:76
#1 0xb7f8680d in visual_plugin_get_references (
pluginpath=0xbfffd03c "/usr/lib/libvisual-0.5/actor/actor_AVS.so",
count=0xbfffd020) at lv_plugin.c:834
#2 0xb7f86168 in plugin_add_dir_to_list (list=0x804e428,
dir=0x804e288 "/usr/lib/libvisual-0.5/actor") at lv_plugin.c:609
#3 0xb7f86b2b in visual_plugin_get_list (paths=0x804e3d8,
ignore_non_existing=1) at lv_plugin.c:943
#4 0xb7f9c5db in visual_init (argc=0xbffff170, argv=0xbffff174)
at lv_libvisual.c:370
#5 0x080494b7 in main (argc=2, argv=0xbffff204) at client.c:32
(gdb) quit
A debugging session is active.
Inferior 1 [process 3704] will be killed.
Quit anyway? (y or n) y
starlon#lyrical:client$ ls /usr/lib/libvisual-0.5/actor/actor_AVS.so
/usr/lib/libvisual-0.5/actor/actor_AVS.so
starlon#lyrical:client$
The file exists. Not sure what's up. Not sure what code to provide either.
Edit2: More info on the file. Permissions are ok.
816K -rwxr-xr-x 1 root root 814K 2011-11-08 15:06 /usr/lib/libvisual-0.5/actor/actor_AVS.so
You didn't tell what dynamic library it is.
If it is a free dynamic library -or a library whose source is accessible to you- you can compile it and use it with debugging enabled.
Several Linux distributions -notably Debian & Ubuntu- provide debugging variant of many libraries (e.g. GLibc, GTK, Qt, etc...), so you don't need to rebuild them. For example, Debian has libgtk-3-0 package (the binary libraries mostly), libgtk-3-dev the development files for it (headers, etc...) and libgtk-3-0-dbg (the debugging variant of the library). You need to set LD_LIBRARY_PATH appropriately to use it (since it is in /usr/lib/debug/usr/lib/libgdk-3.so.0.200.1).
Sometimes, using the debugging variants of system libraries help you to find bugs in your own code. (Of course, you also need to compile with -g -Wall your own code)
Turned out this was due to a faulty hard drive. Looks like I need a new one.

Resources