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