I wonder that an SUID program like passwd
is using the setuid()
function call. Why does it drop root privileges?
Against what types of attack does this help? Buffer-overflow?
privilegesSecuritysetuid
I wonder that an SUID program like passwd
is using the setuid()
function call. Why does it drop root privileges?
Against what types of attack does this help? Buffer-overflow?
As you correctly state a SUID bit, sets the effective user-id of the new process to the owner of the program file being run. There's no concept of raising or lowering privileges. Often the user to which one "suids" is root, but it can just as legitimately be any other, and doesn't really change the concept of SUID, when you choose another, apart from some users have access to certain resources. However the root user is a little bit special, and the man page for setuid, explains the subtle differences. I don't think there is a special name for changing to a non-root user, and I have never associated suid meaning going up in privilege, just changing user.
Since you are developing the master yourself which runs as root it can setuid to any user after forking but before exec-ing the new process, I dont see that suid bit has to be used on executable at all, and incidently if you don't, then if any-old user ran the player, since executable is not suid, they wouldn't be able to run it correctly since setuid will fail if they were not root. For me this seems like better security, than using suid bits on executable files.
--- edit 1
So your manager (running as real and effective uid=0) starts player and you code/control player (not manager). If you do nothing special, then player will run with real+effective uid=0 and you want to prevent this.
You could just switch uid in player, no SUID bits on player, no need.
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.
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.
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.
Best Answer
First I'll discuss the setuid bit, which passwd uses and is distinct from the
setuid()
system call (which passwd does not use). There is perhaps some confusion in the question in this regard.It is not a protection against a buffer overflow, it's vunerable to such, or basically anything which would allow an attacker to use a privileged process for some nefarious unintended purpose. This is because the setuid bit is the opposite of "dropping privileges"; it bestows root privileges, but only to the process and not the actual user. That includes
passwd
.That form of setuid requires the filesystem setuid bit set on the executable;
passwd
has this because it needs privileges to read and write/etc/passwd
. However, we hope passwd does not have any known security vulnerabilities (eg, potential overflow exploit) that would allow a nefarious person to get it do something other than reading and writing /etc/passwd (and other than doing that properly!), since it does run as root, and therefore could do anything -- except it is designed to do only one specific thing, and getting it to do anything else should (again, hopefully) be impossible.So using setuid in that sense is not protection against anything, but it is often discussed in relation to vulnerabilities because potential vunerabilities are very important WRT setuid executables.
BUT: the setuid bit sets the euid and not the actual uid, so it is actually parallel to the
seteuid()
system call and notsetuid()
.There is an opposite form of "setuid" that is about dropping privileges, which involves the actual
setuid()
system call and does not require the setuid bit. This is when a process running as root (because root or sudo started it) changes its uid to a less privileged user. Servers and daemons often do this if they need root privileges at start up (eg, to open a privileged port) but not subsequently. That way if the server subsequently gets jacked, it does not have superuser privileges. You cannot callsetuid(0)
and get root privileges back (but you can with set*e*uid).