48

I have a process that needs root privileges when run by a normal user. Apparently I can use the "setuid bit" to accomplish this. What is the proper way of doing this on a POSIX system?

Also, how can I do this with a script that uses an interpreter (bash, perl, python, php, etc)?

3 Answers 3

58

The setuid bit can be set on an executable file so that when run, the program will have the privileges of the owner of the file instead of the real user, if they are different. This is the difference between effective uid (user id) and real uid.

Some common utilities, such as passwd, are owned root and configured this way out of necessity (passwd needs to access /etc/shadow which can only be read by root).

The best strategy when doing this is to do whatever you need to do as superuser right away then lower privileges so that bugs or misuse are less likely to happen while running root. To do this, you set the process's effective uid to its real uid. In POSIX C:

#define _POSIX_C_SOURCE 200112L // Needed with glibc (e.g., linux).
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

void report (uid_t real) {
    printf (
        "Real UID: %d Effective UID: %d\n",
        real,
        geteuid()
    );
}

int main (void) {
    uid_t real = getuid();
    report(real);
    seteuid(real);
    report(real);
    return 0;
}

The relevant functions, which should have an equivalent in most higher level languages if they are used commonly on POSIX systems:

  • getuid(): Get the real uid.
  • geteuid(): Get the effective uid.
  • seteuid(): Set the effective uid.

You can't do anything with the last one inappropriate to the real uid except in so far as the setuid bit was set on the executable. So to try this, compile gcc test.c -o testuid. You then need to, with privileges:

chown root testuid
chmod u+s testuid

The last one sets the setuid bit. If you now run ./testuid as a normal user you'll see the process by default runs with effective uid 0, root.

What about scripts?

This varies from platform to platform, but on Linux, things that require an interpreter, including bytecode, can't make use of the setuid bit unless it is set on the interpreter (which would be very very stupid). Here's a simple perl script that mimics the C code above:

#!/usr/bin/perl
use strict;
use warnings FATAL => qw(all);

print "Real UID: $< Effective UID: $>\n";
$> = $<; # Not an ASCII art greedy face, but genuine perl...
print "Real UID: $< Effective UID: $>\n"; 

True to it's *nixy roots, perl has build in special variables for effective uid ($>) and real uid ($<). But if you try the same chown and chmod used with the compiled (from C, previous example) executable, it won't make any difference. The script can't get privileges.

The answer to this is to use a setuid binary to execute the script:

#include <stdio.h>
#include <unistd.h> 

int main (int argc, char *argv[]) {
    if (argc < 2) {
        puts("Path to perl script required.");
        return 1;
    }
    const char *perl = "perl";
    argv[0] = (char*)perl;
    return execv("/usr/bin/perl", argv);
}

Compile this gcc --std=c99 whatever.c -o perlsuid, then chown root perlsuid && chmod u+s perlsuid. You can now execute any perl script with with an effective uid of 0, regardless of who owns it.

A similar strategy will work with php, python, etc. But...

# Think hard, very important:
>_< # Genuine ASCII art "Oh tish!" face

PLEASE PLEASE DO NOT leave this kind of thing lying around. Most likely, you actually want to compile in the name of the script as an absolute path, i.e., replace all the code in main() with:

    const char *args[] = { "perl", "/opt/suid_scripts/whatever.pl" }
    return execv("/usr/bin/perl", (char * const*)args);

Them make sure /opt/suid_scripts and everything in it are read-only for non-root users. Otherwise, someone could swap in anything for whatever.pl.

In addition, beware that many scripting languages allow environment variables to change the way they execute a script. For example, an environment variable might cause a library supplied by the caller to be loaded, allowing the caller to execute arbitrary code as root. Thus, unless you know that both the interpreter and the script itself are robust against all possible environment variables, DON'T DO THIS.

So what should I do instead?

A safer way to allow a non-root user to run a script as root is to add a sudo rule and have the user run sudo /path/to/script. Sudo strips most environment variables, and also allows the administrator to finely select who can run the command and with what arguments. See How to run a specific program as root without a password prompt? for an example.

7
  • 1
    many shells will behave differently if called as a -privileged shell and, if called from a responsible script, can also safely be invoked suid root that way. I'm afraid that the notion of a responsible script is a bit of a pipedream in the real world, though. Anyway, it is probably worth mentioning how important it is to avoid writes of all kinds if at all possible while permissions are elevated...
    – mikeserv
    Nov 8, 2014 at 17:05
  • @mikeserv I'm not familiar with the concepts of "-privileged shell" and "responsible script". Do you want to do another answer about shells? I can't promise you the tick of course ;) There are not many good reasons to do this -- sudo is a much better option if possible -- I wrote it as a generic answer stemming from an early question about system password authentication in php.
    – goldilocks
    Nov 8, 2014 at 17:22
  • 1
    I don't really want to do that - responsible scripts are really hard - beyond me, probably. But I must say I disagree with your analysis of sudo - I regard sudo as psychotic in that it pretends not to be root. You can even put shell globs in your sudoers. su is better in my opinion for scripting purposes, though sudo is good for live applications. But neither is as explicit as a script with a #!/bin/ksh -p bangline or whatever the suid ksh in $PATH is.
    – mikeserv
    Nov 8, 2014 at 17:45
  • While this answer does address the question, setuid scripts are a recipe for opening yourself to a local privilege escalation vulnerability. There is a reason why scripts cannot be made setuid easily. The correct answer here is sudo.
    – mdadm
    Nov 8, 2014 at 18:35
  • I took the liberty to edit your answer to at least mention the huge vulnerability with environment variables. I don't like this much as a canonical answer because it goes into a lot of digressions on an insecure method (the setuid wrapper for scripts). It would be best to recommend sudo and leave the extended discussion for another thread (I covered it there, by the way). Nov 8, 2014 at 23:20
11

Yes you can, but it is probably a very bad idea. Usually, you would not set SUID bit directly on your executable, but use a sudo(8) or su(1) to execute it (and limit who can execute it)

Note however, there are many many security problems with allowing regular users to run programs (and especially scripts!) as root. Most of them having to do that unless the program is specifically and very very carefully written to handle that, malicious users could use it to obtain root shell easily.

So even if you were to do it, it would be very good idea to first sanitize ALL input to the program you want to run as root, including its environment, command line parameters, STDIN and any files it processes, data coming from network connections it opens, etc etc. It is extremely hard to do, and even harder to do right.

For example, you may allow users that only edit some file with editor. But the editor allows command execution of external helpers or suspending, thus giving users root shell to do as they wish. Or you might be executing shell scripts which look very security-tight to you, but user can run it with modified $IFS or other environment variables completely changing it's behavior. Or it might be PHP script that's supposed to execute "ls", but user runs it with $PATH modified and thus makes it run a shell which he name "ls". Or dozens of other security problems.

If the program really must do part of its work as root, it would probably be best to modify it so it runs as normal users, but just execute (after sanitizing as much as possible) the part that absolutely needs root through suid helper program.

Note that even if you completely trust all your local users (like, you give them root password), it is a bad idea, as any unintentional security oversight by ANY of them (like, having a PHP script online, or weak password, or whatever) suddenly allows remote attacker to completely compromise whole machine.

1
  • 1
    seconded. see the safe invocation example in the POSIX programmer's series for man sh - and even that fails to do an command -p env -i .... It's pretty hard to do after all. Still, if it is done correctly, for explicit purposes, it can be better to suid a capable interpreter than to use su or sudo - as the behavior of either one will always be outside the script's control (though su is less likely to throw curve balls as it tends to be a little less insane). Bundling the whole package is the only sure bet - it had just better be sure is all.
    – mikeserv
    Nov 8, 2014 at 21:28
0

I have this kind of solution.

  1. Make a C wrapper, intended to be used in the shebang line. Setuid it root.
  2. Just after start, wrapper will check if script given as an argument is setuid/setgid.
    • if no - drop root priv.
    • if yes - change effective uid/gid to reflect the setuid/setgid bits.
  3. Check if the given script is a regular file - exit if not.
  4. exec perl via "perl -x script".

More strict/limited version could also do this (and/or):

  • forbid to run scripts that are setuid/setgid root,
  • run perl in tainted mode ("perl -x -T script").

A yet more advanced version could start perl via sudo ("exec sudo ... perl -x script").

1
  • The problem is, that on some platforms (modern ones) it doesn't work for the root. Perl detects the root user and bails out with the error.
    – user404221
    Apr 5, 2020 at 17:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .