There are a couple of things going on here, which cause this confusion. Setuid does work in both cases, it just doesn't work the way you think it would. First, there is the issue of real and effective user IDs. According to the setuid(2) man page
If the calling process is privileged (more precisely: if the process
has the CAP_SETUID
capability in its user namespace), the real
UID and saved set-user-ID are also set.
This also means that is the calling process does not have the CAP_SETUID
capability (which is the case of your ordinary users), then the UID will not change, only the EUID.
So we move on to the system()
call. Calling system("anything")
is the equivalent of calling this:
execl("/bin/sh", "sh", "-c", "anything", (char *) NULL);
So it spawns /bin/sh
, providing arguments so that a new shell will execute your command. I assume you use bash as your shell, because bash works so
If the shell is started with the effective user (group) id not equal
to the real user (group) id, and the -p option is not supplied, (...)
the effective user id is set to the real user id.
So if your process does not have the CAP_SETUID
capability, it won't change the real user ID, only the effective UID. Then, when bash is spawned, it drops the effective UID, since UID is not equals to EUID. You can confirm this by calling
system("touch /tmp/blah")
and adding
FILE *file = fopen("/tmp/blah2", "w+");
fclose(file);
to your program. You will see that the owner of /tmp/blah
will be "test" (since the EUID will be dropped on shell execution), but /tmp/blah2
will belong to "test2".