views:

3439

answers:

3

I need to run a bash script as root (passwordless sudo or su not viable) and since you cannot setuid a script in Linux, I thought about calling it from an executable and making it setuid:

$ cat wrapper.c
int main(void)
{
        system("/bin/bash ./should_run_as_root.sh");
}
$ gcc -o wrapper wrapper.c
$ sudo chown root wrapper
$ sudo chmod ug+s wrapper
$ ll wrapper
-rwsr-sr-x 1 root users 6667 2009-02-17 11:11 wrapper
$

This works - as in runs the script correctly - but the script runs as the user who executes "./wrapper".

Why? And how to correctly implement this?

Thanks!

+11  A: 

Since the suid bit on executables only changes the effective UID (EUID) the executable will run as, and not the real UID (RUID) which getuid() returns, and in addition to the restriction on suid interpreted scripts (any executable beginning with "#!"), some shells like bash as an extra safety measure will set the EUID back to the RUID in this case, you will need to use the call setuid(0) in the C code before executing the script.

See the man pages of the setuid, seteuid, getuid, and geteuid to learn the exact semantics of the real and effective UIDs.

(WARNING) Of course, this is an appropriate point to mention that the restriction on suid scripts in many Unix systems, shells and interpreters, are there for a reason, which is that if the script is not very careful about sanitizing its input and the state of environment when it is executed, they are dangerous and can be exploited for security escalation. So be very careful when doing this. Set the access to your script and wrapper as strict as you can, only allow this very specific script which you intend to be executed, and clear the environment within your C program before starting the script, setting environment variables such as PATH to contain exactly what is necessary in the right order and no directories that are writable to others.

Tom Alsberg
Is it safer to save the original UID and reset it at the end of the program?
skinp
If your program does other things after the script finishes, that do not need this, then yes. If all you do is return, then it doesn't really matter. In the typical case a script wrapper will use exec and not system(...), so anyway the wrapper will not run after the script ends anymore.
Tom Alsberg
+2  A: 

Another thing to note here is that the limitation here is from bash and not the *nix system itself. Bash actually make verifications on SUID scripts to only execute them with EUID root. If you take older shells, you will often get what you wanted out of the box. For example, sh doesn't make this kind of verifications:

$ cat wrapper.c
int main(void)
{
            system("/bin/sh -c whoami");
}

$ ls -l wrapper
-rwsr-sr-x 1 root users 8887 Feb 17 14:15 wrapper
$ ./wrapper
root

With bash:

$ cat wrapper.c
int main(void)
{
            system("/bin/bash -c whoami");
}

$ ls -l wrapper
-rwsr-sr-x 1 root users 8887 Feb 17 14:18 wrapper
$ ./wrapper
skinp

Still, Tom's answer is generally the way to go for making a wrapper for SUID root programs

skinp
You are right, of course. Linux handles restricting suid scripts by checking the executable's mode in the exec call, and this restriction is an additional safety measure of some interpreters. Not sure why I got this confusion when I wrote this. I corrected my answer. Thanks!
Tom Alsberg
And of course I missed that in the OP's code he is not actually exec'ing a script with a #! interpreter specification at all, but calling bash directly, so the kernel's restriction would not apply in this case anyway - but the kernel does have this restriction for #! scripts with the suid file mode.
Tom Alsberg
A: 

Add the setuid(0) in the script and complie it. It shoudl work after this.

$ cat wrapper.c 
int main(void) 
{ 
        setuid(0);
        system("/bin/bash ./should_run_as_root.sh"); 
} 
$ gcc -o wrapper wrapper.c 
$ sudo chown root wrapper 
$ sudo chmod ug+s wrapper 
$ ll wrapper 
-rwsr-sr-x 1 root users 6667 2009-02-17 11:11 wrapper 
$ 
Novice scripter