setuidraspberry-pi-zeroshutdown-script

Why does SETUID not work on Raspberry Pi?


I am trying to create a shutdown command that can be run as a user, i.c., a flask web page that is not running as root. Sounds simple, just put a shutdown command in a SETUID script. Because SETUID does not work on shell scripts, I created an executable from a C program.

The problem is that this does not work on the target machine, a Raspberry Pi Zero W. I tested the same stuff on my Ubuntu 20.4 pc, and there it runs flawlessly. So the method in itself seems correct, but there is a raspberry pi issue.

The Pi runs this OS:

cat /etc/issue
-->
Raspbian GNU/Linux 10 \n \l

This is usershutdown.c :

#include <stdio.h>
#include <stdlib.h>

int main(){
   system("/sbin/poweroff");
}

These are the permissions of the executable:

-rwsr-xr-x 1 root   root   7988 Dec 20 23:59 usershutdown

I checked the mount options of the root disk in /etc/fstab, where I added ,suid and rebooted:

PARTUUID=738a4d67-02  /               ext4    defaults,noatime,suid  0       1

And these are the error messages on the Pi when calling the exec as intended:

$ ./usershutdown 
Failed to set wall message, ignoring: Interactive authentication required.
Failed to power off system via logind: Interactive authentication required.
Failed to open initctl fifo: Permission denied
Failed to talk to init daemon.
$

This is what does work on the Pi, when calling the exec as root/sudo, the ssh connection to it is closed and the device shuts down without error:

$ sudo ./usershutdown 
$ Connection to picamhq closed by remote host.
Connection to picamhq closed.
$ 

How do I fix this?


Solution

  • system() runs the program via a shell, and running a shell from any setuid program is extremely dangerous from a security perspective; there are lots of ways a user can hijack it to do other things (look up ways of abusing IFS, for instance). To mitigate this, commonly used shells will try to notice when they are being run under setuid (by noticing that the real and effective uids are different) and drop privileges.

    So a safer alternative is to run poweroff via execle instead. (execl will also work, but it is risky to pass through environment variables that you don't control, so better to have your program set an environment that's known to be safe.)

    You still have a problem in that your setuid program is runnable by anyone, so any unprivileged local user on the server would be able to shut it down. You'll want to make sure that it can only be executed by the user your app runs as. Since the owner of the file has to be root for setuid to work, this will require some juggling with groups.

    You can avoid all this hassle and risk by instead setting up sudo to allow your app's user to run the poweroff command without a password, using the NOPASSWD directive in /etc/sudoers. This will:

    See https://unix.stackexchange.com/questions/18830/how-to-run-a-specific-program-as-root-without-a-password-prompt for more.