security issue with set-uid and a relative path for INTERP (dynamic linker) in ELF
Asked Answered
O

1

6

The combination of set-uid and a relative path in the INTERP section of an ELF binary is very dangerous.

I'm not quite sure how and where this problem should be reported, but it seems to me like a general security issue concerning how dynamic linking in linux/glibc works, so let me explain what it is:

Consider building a dynamically linked binary and specifying a relative path in the ELF INTERP section (using the --dynamic-linker gcc option) so you could redistribute a custom glibc version with your dynamically linked commercial application (where you are not allowed to link statically against the LGPL glibc, but still need to make your binary work across different linux distribution having different glibc versions).

If you chown the binary to root, and put the set-uid flag on your binary, it effectively becomes a rootkit. As executing it from a different directory, allows you to replace the dynamic linker executable, that will be executed with root permission.

To demonstrate this, take a look at the following C code (issue.c):

#include <stdio.h> 

// 
// build with: 
//   gcc -DNAME=\"vulnarable\" -o issue -Wl,--dynamic-linker,.lib64/ld-linux-x86-64.so.2 issue.c 
//   sudo chown root issue 
//   sudo chmod u+s issue 
// now build some code to be executed with root permissions (we use the same issue.c): 
//   mkdir -p .lib64/ 
//   gcc -DNAME=\"rootkit\" -o .lib64/ld-linux-x86-64.so.2 --static issue.c 
// 

int main(int argc, char* argv[]) 
{ 
    printf("(%s) euid:%d\n", NAME, geteuid()); 
} 

If you now execute the set-uid binary like this

./issue

or even just doing this

ldd issue

instead of getting what you might expect, e.g.:

(vulnarable) euid:0

you get:

(rootkit) euid:0

Now the point is you could replace the ld-linux-x86-64.so.6 binary with whatever you like.

Similar issues seems to have been addressed, by not resolving $ORIGIN in RPATH or ignoring LD_LIBRARY_PATH if the set-uid flag is set.

So I wonder if the INTERP in ELF has to be ignored, whenever the set-uid flag is set (i.e. by using the default dynamic linker - /lib32/ld-linux.so.2 or /lib64/ld-linux-x86-64.so.2)?

So what do you think, where should this be fixed or reported - in glibc or the kernel?

Overwhelming answered 26/1, 2012 at 13:50 Comment(5)
You're suggesting a way to get root access, which involves using sudo. Which means you need root access to do it.Ungraceful
On what kernel and distro did you test this? I'm having trouble reproducing the issue on Ubuntu 10.04 with kernel 2.6.32-38-generic: all I get is (./issue) euid:0, which is as expected for a setuid program.Throughout
@ugoren: Not really. Assuming the vulnerability is real, all an attacker would need is to find a vulnerable setuid program. The sudo commands in the instructions are just there to create one, so that the issue can be demonstrated.Throughout
@Ilmari:I've just updated the instruction, they were somewhat misleadingOverwhelming
You should make it clear who is the attacker and who is attacked. It seems, from the description, that building a setuid program is something the attacker does in order to exploit the problem.Ungraceful
A
6

Yes, it is not safe to have a setuid binary specifying an interpreter in a non-safe location (you mention relative path, but a world-writable fixed path has the same issue). But it's logical conclusion of the ELF design, and adding a special-case to the handling of INTERP for setuid binaries is not the way to go. It's a case of “Doctor, it hurts when I do this. — Don't do it.” Yes, this combination is unsafe, so simply don't use it! Meddling with the ELF interpreter is something rather advanced anyway, so you shouldn't do it unless you understand what you're doing, in which case you can logically conclude what is safe or not to do.

Advanced features of ELF/POSIX/Unix/whatever give you powerful ways to shoot yourself in the foot, because they are powerful. If you wanted to get rid of every possible bad situation, you would have a system much less flexible and much harder to program for, while still having some holes in it.

Avulsion answered 3/2, 2012 at 23:37 Comment(9)
Agreed. It's like calling system() in a setuid program with a command that is not an absolute path. I should add that, if one wants to use a custom version of libc in a program, don't use a relative INTERP to do that; just link it statically instead. The LGPL generally does allow that.Jampack
@F'x: good answer, I agree on being careful on where you put the set-uid flag, however your argument would also apply to RPATH/RUNPATH in ELF or the LD_LIBRARY_PATH env var - in this case (set-uid flag), RPATH/RUNPATH and LD_LIBRARY_PATH are all together ignored. So why not ignoring INTERP?Overwhelming
It does not apply to LD_LIBRARY_PATH, since it can be set by the user, rather than the creator of the executable. If anything, I think it is weird that RUNPATH is indeed handled, rather than the other way around.Jampack
@dolda2000: I don't think static linking is allowed, if you do not distribute your source code or object files. LGPL actually requires that someone else should be able to replace glibc, e.g. link a different one.Overwhelming
@dolda2000: setting LD_LIBRARY_PATH by the user corresponds in the above scenario to executing the vulnerable binary from a different directory (in the case of a relative path), or replacing the dyn-linker binary on the world-writable fixed path. In a similar way as RUNPATH in ELF is ignored it would be just consequent to also ignore INTERP.Overwhelming
It is true that the LGPL requires you to enable users to relink against another glibc. Is that actually a problem, though? If you distribute an executable binary, and you already have to distribute your changes to the glibc, do you have anything to lose from also distributing a relinkable object file of your program along with them?Jampack
It does not correspond very well, because the INTERP section is something that the creator of the binary has control over, while he cannot control the LD_LIBRARY_PATH of the environment it runs in.Jampack
@dolda2000:(we could add another topic on LGPL) I'm not sure - should I distribute all object files (from a really big project)? Actually I don't change glibc, just need a concrete version, so the binary could run everywhere.Overwhelming
You don't need to distribute all the object files -- Just link them together into one, big, intermediate object file with something like ld -r -o almost-executable.o a.o b.o c.o.... Of course, I don't know how it would related to your build process, but it shouldn't necessarily be harder than linking them all together to a final executable, I think.Jampack

© 2022 - 2024 — McMap. All rights reserved.