September 24th, 2007

suid without eating environment?

So I want to write a setuid program. And, I want it to not eat the environment (namely, LD_LIBRARY_PATH). It seems that this is impossible to do in linux.

Now, you might reasonably ask why I would want to do such a thing?

Here's an outline of what I want:

a) User invokes setuid program, giving as arguments another program they want to execute.
b) setuid program does some stuff as root (let's say, for illustrative purposes, setting niceness level to -10)
c) setuid program drops privileges
d) setuid program calls exec, passing the user-specified program.

So, see, I'd really like LD_LIBRARY_PATH to pass through my setuid app, to the exec'd (as the originally invoking user) process.

Here's what I found:

ld.so eats all the linker environment variables before even starting my program. Okay, surely it'd be better to ignore them instead of removing them, but whatever, I'll just link my program statically, that ought to solve the problem, right?

NOPE. I lose. In the name of security, the statically-linked-program startup code also erases the environment variables. Apparently there was a security hole at one point with some statically linked suid program calling exec without passing an explicit sane environment. The program it exec'd, if dynamically linked, would of course use the LD_LIBRARY_PATH in the environment, since it wasn't suid. Oops, instant root vuln. So to fix this, even statically linked programs cleanse their environment.

But one silver lining: in the statically linked case, it's actually glibc startup code which is eating the environment, which theoretically I should be able to override in some fashion. All I have to do is take control of the startup sequence before glibc cleanses the environment, make a copy, and continue the normal startup sequence. I thought perhaps defining _start myself, or something along those lines, but I can't manage to get it to work.

Can anyone help me?