I’m attempting this: I have an environment defined in /chroot/debian6.0/
where I have bound some directories and created other ones. One is libs/
which contains the library libOne.so
and its dependencies
So:
/chroot/debian6.0/ --- libs/ --- libOne.so --- other dependencies (*.so)
This library has been compiled in the chroot environment, and I want to open it with a process run from the containing environment.
This is the code:
remote.c
#include <unistd.h> #include <dlfcn.h> #include <stdio.h> int main(int argc, char **argv) { int res = 0; void* handle; /*chdir to first argument(path1)*/ res = chdir(argv[1]); if (res == 0) { printf("nchdir: %s", argv[1]); } else { printf("nError chdir %sn", argv[1]); return 1; } /*chroot to path in first argument*/ res = chroot(argv[1]); if (res == 0) { printf("nchroot: %s", argv[1]); } else { printf("nError chroot %sn", argv[1]); return 1; } /*Define path for dependencies*/ putenv("LD_LIBRARY_PATH=/libs/"); /*Opens library*/ handle = dlopen(argv[2], RTLD_NOW); if (handle == NULL) { printf("nError opening %sn", argv[2]); return 1; } else { printf("ndlopen: library %s openedn", argv[2]); } return 0; }
And I execute with the following command:
./remote “/chroot/debian6.0/Debian-6.0-chroot/” “/libs/libOne.so”
The result is an error when trying to dlopen the library. Last lines of strace:
read(3, "177ELF111331P#00"..., 1024) = 1024 fstat64(3, {st_mode=S_IFREG|0644, st_size=116600, ...}) = 0 old_mmap(NULL, 119656, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) = 0xb7644000 mprotect(0xb7661000, 872, PROT_NONE) = 0 old_mmap(0xb7661000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x1c000) = 0xb7661000 close(3) = 0 munmap(0xb7f01000, 6291) = 0 munmap(0xb7770000, 6496996) = 0 munmap(0xb7757000, 98784) = 0 munmap(0xb7662000, 1000332) = 0 munmap(0xb7644000, 119656) = 0 write(1, "chroot: /chroot/debian6.0/Deb"..., 48chroot: /chroot/debian6.0/Debian-6.0-chroot/ ) = 48 write(1, "Error opening /libs/libD"..., 50Error opening /libs/libOne.so ) = 50 munmap(0xb7f03000, 4096) = 0 _exit(1) = ?
The library appears to have all its dependencies – inside the chroot I can ldd libOne.so
and I get
linux-gate.so.1 => (0xb7f16000) libpthread.so.0 => /lib/libpthread.so.0 (0xb78c6000) libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb77d1000) libm.so.6 => /lib/libm.so.6 (0xb77aa000) libc.so.6 => /lib/libc.so.6 (0xb7665000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb7647000) /lib/ld-linux.so.2 (0xb7f17000)
Any idea why the dlopen
fails? Or how to make it work?
Added perror and dlerror and I get:
Error opening library /lib/libc.so.6: version `GLIBC_2.4' not found (required by /libs/libOne.so)) = 117
UPDATE:
Although I have copied the libc version which I use to compile the program, to my containing environment and chroot’ environment (both in /lib), i still get the same message on execution:
Error opening library /lib/libc.so.6: version `GLIBC_2.4′ not found (required by /libs/libOne.so)) = 117
So i went to each lib folder and that’s what i see:
Containing Env:
ll /lib libc-2.2.4.so libc.so.6 -> libc-2.2.4.so ldd libc.so.6 /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0xb7dcc000)
Chroot’s env:
ll /lib libc.so.6 -> libc-2.11.3.so libc-2.11.3.so ldd libc.so.6 /lib/ld-linux.so.2 (0xb7fb4000) linux-gate.so.1 => (0xb7fb3000)
In my Compiling Environment:
ll /lib libc.so.6 -> libc-2.11.3.so libc-2.11.3.so ldd libc.so.6 /lib/ld-linux.so.2 (0xf770f000) linux-gate.so.1 => (0xf770c000)
So it seems that my compiling and execution versions of libc.so.6 are the same, and linked to a second one which is the same, that is libc-2.11.3.so
So, I don’t get why I’m getting GLIBC_2.4 message
Last update:
right now, after Petesh’s guidance and using dlmopen
, my strace throws next messages (at the end):
writev(2, [{"./remote", 5}, {": ", 2}, {"/lib/libdl.so.2", 15}, {": ", 2}, {"version `GLIBC_2.3.4' not found "..., 51}, {"n", 1}], 6./remote: /lib/libdl.so.2: version `GLIBC_2.3.4' not found (required by ./remote) ) = 76
Advertisement
Answer
The error in relation to GLIBC_2.4
not being found is most likely caused because the invocation of dlopen
occurs in a process that has already mapped in a copy of libc that doesn’t expose that version.
So, we need to be a little bit creative, and use the secondary routine dlmopen
rather than simple old dlopen
.
dlmopen takes a parameter lm_id
which is a namespace where it loads all the relevant libraries – if you use the default (LM_ID_BASE
), it starts with the namespace that the program runs in; which for example would contain the libc
that was loaded already when the application was launched.
So, rather than just calling handle = dlopen(argv[2], RTLD_NOW);
we replace it with a call to:
handle = dlmopen(LM_ID_NEWLM, argv[2], RTLD_NOW | RTLD_LOCAL);
This will load fresh copies of all the libraries that argv[2]
needs into the link address space used by that handle.
You can then reference symbols in the library using:
symbol = dlsym(handle, "symname");
and it should work.
I’ve changed your example slightly to make use of this:
#define _GNU_SOURCE #include <dlfcn.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { int res = 0; void* handle; /*chdir to first argument(path1)*/ res = chdir(argv[1]); if (res == 0) { printf("nchdir: %s", argv[1]); } else { printf("nError chdir %sn", argv[1]); return 1; } /*chroot to path in first argument*/ res = chroot(argv[1]); if (res == 0) { printf("nchroot: %s", argv[1]); } else { printf("nError chroot %sn", argv[1]); return 1; } /*Define path for dependencies*/ putenv("LD_LIBRARY_PATH=/libs/"); /*Opens library*/ handle = dlmopen(LM_ID_NEWLM, argv[2], RTLD_NOW); // needs _GNU_SOURCE if (handle == NULL) { printf("nError opening %s: %sn", argv[2], dlerror()); return 1; } else { printf("ndlopen: library %s openedn", argv[2]); } int (*foo)(void); foo = (int (*)(void))dlsym(handle, argv[3]); if (foo == NULL) { printf("nError referencing %s: %sn", argv[3], dlerror()); return 1; } printf("%dn", foo()); return 0; }
Now secondsly, the putenv("LD_LIBRARY_PATH=/libs/");
doesn’t do the needful – you cannot alter LD_LIBRARY_PATH
at the run-time of a program; the change will simply not be detected. Because your program is so simple we could do something at the start of the main like:
if (0 == getenv("RE_EXEC")) { putenv("RE_EXEC=1"); putenv("LD_LIBRARY_PATH=/libs/"); execvp(argv[0], argv); perror("execvp failed"); exit(1); }
which re-executes with LD_LIBRARY_PATH
set to /libs
so it will search there.
There are a few other potential solutions – if the path setting is not suitable with the re-exec
You could make sure that libOne.so
has $ORIGIN
in the rpath for the binary. You can do this at compile/link time with:
-Wl,-rpath,'$ORIGIN'
The quoting is to prevent the $ORIGIN
from getting escaped by the shell. If this is in a makefile
then you need to use the $$
syntax for the target actions.
You can post-hoc patch the library with a tool like patchelf
(on my system I did a simple sudo apt-get install patchelf
):
patchelf --set-rpath '$ORIGIN' libOne.so
This, of course relies on support for $ORIGIN
in your ld.so
(I cannot remember when it was added to ld.so
; but it’s been a few years); if your ld.so
does not support $ORIGIN
then you can always replace it with /libs/
, and if you can’t find patchelf
for your distro, then you can always build it yourself