35

A huge application needs, at one specific time, to perform a small number of writes to a file which requires root permissions. It is not really a file but a hardware interface which is exposed to Linux as a file.

To avoid giving root privileges to the whole application, I wrote a bash script which does the critical tasks. For example, the following script will enable port 17 of the hardware interface as output:

echo "17" > /sys/class/gpio/export
echo "out" > /sys/class/gpio/gpio17/direction

However, as suid is disabled for bash scripts on my system, I wonder what is the best way to achieve this.

  1. Use some workaround presented here

  2. Call the script with sudo from the main application, and edit the sudoers list accordingly, to avoid requiring a password when calling the script. I'm a little bit uncomfortable to give sudo privileges to echo.

  3. Just write a C program, with fprintf, and set it to suid root. Hardcode the strings and filenames and make sure only root can edit it. Or read the strings from a text file, similarly making sure that no one can edit the file.

  4. Some other solution which didn't occur to me and is safer or simpler then the ones presented above?

vsz
  • 547
  • 10
    Why don't you just start your program with root privileges, open the file, and drop privileges? That's how every webserver or such does it for sockets. Effectively, you are not running as root, nor is a helper necessary that does. – Damon Apr 15 '16 at 11:52

6 Answers6

33

You don't need to give sudo access to echo. In fact, that's pointless because, e.g. with sudo echo foo > bar, the redirection is done as the original user, not as root.

Call the small script with sudo, allowing NOPASSWD: access to ONLY that script (and any other similar scripts) by the user(s) who need access to it.

This is always the best/safest way to use sudo. Isolate the small number of commands that need root privileges into their own separate script(s) and allow the un-trusted or partially-trusted user to only run that script as root.

The small sudo-able script(s) should either not take args (or input) from the user (i.e. any other programs it calls should have hard-coded options and args) or it should very careful validate any arguments/input that it has to accept from the user.

Be paranoid in the validation - rather than look for 'known bad' things to exclude, allow only 'known good' things and abort on any mismatch or error or anything even remotely suspicious.

The validation should occur as early in the script as possible (preferably before it does anything else as root).


I really should have mentioned this when I first wrote this answer, but if your script is a shell script it MUST properly quote all variables. Be especially careful to quote variables containing input supplied by the user in any way, but don't assume some variables are safe, QUOTE THEM ALL.

That includes environment variables potentially controlled by the user (e.g. "$PATH", "$HOME", "$USER" etc. And definitely including "$QUERY_STRING" and "HTTP_USER_AGENT" etc in a CGI script). In fact, just quote them all. If you have to construct a command line with multiple arguments, use an array to build the args list and quote that - "${myarray[@]}".

Have I said "quote them all" often enough yet? remember it. do it.

cas
  • 78,579
  • 18
    You forgot to mention that the script itself should be owned by root, with permissions 500. – Wildcard Apr 15 '16 at 05:12
  • restricting perms on the sudo-able script(s) can be worth doing... but not strictly necessary unless they contain sensitive information (such as a password). – cas Apr 15 '16 at 05:17
  • 13
    At least remove the write permissions, for goodness sake. That was really my point. The rest is just hardening. – Wildcard Apr 15 '16 at 05:34
  • 10
    A thoughtfully-written C program will have a smaller attack surface than a shell script. – user253751 Apr 15 '16 at 07:06
  • @Wildcard, I can't see how removing write permission from root would make any real difference. If someone has root access they can chmod +w anyway. "Other" shouldn't have write access, of course. and probably "Group" too. – cas Apr 15 '16 at 10:48
  • 7
    @immibis, possibly. But it will take a lot longer to write and debug than a shell script. And requires having a C compiler (which are prohibited on some production servers to reduce security risk by making it harder for attackers to compile exploits). Also, IMO a shell script written by a novice to intermediate level sysadmin or programmer is less likely to be exploitable than a C program written by someone of similar skill - especially if it has to accept and validate user-supplied data. – cas Apr 15 '16 at 10:56
  • 1
    @cas A lot can go wrong with bash, especially with user input. It isn’t that much better than C, I think both are a bad idea unless you know what you’re doing. The damage when using bash can be constrained by doing things like set -euo pipefail and carefully quoting everything, while in C you have to take care with memory allocation. – Jonas Schäfer Apr 15 '16 at 11:00
  • 3
    @JonasWielicki - i think we're now well and truly into the realm of opinion rather than fact. You can make valid arguments for either shell or C (or perl or python or awk etc) being either more or less prone to exploitable mistakes. When, really, it depends mostly on the programmer's skill and attention to detail (and tiredness, haste, caution, etc). It is a fact, though, that lower level languages tend to require more code to be written to achieve what can be done in far fewer lines-of-code in higher-level languages....and each LoC is another opportunity for a mistake to be made. – cas Apr 15 '16 at 11:07
  • @cas, if you have given a user sudo access only to run a specific script, it should be very obvious that they should not be allowed to edit that script. Otherwise you may as well give them unrestricted sudo access; the effect would be the same. – Wildcard Apr 15 '16 at 17:13
  • 1
    @wildcard, well, duh! that's obvious. It doesn't, however, require perms of 500. 755 or 775 is adequate for that goal. 700 is fine, too. In fact, as I already said, any perms that don't allow Other to have write permissions is OK. root having write access to a script is NOT the same as a granting write access to a user who has sudo acesss to execute that script as root. You are imagining a problem that doesn't exist. – cas Apr 16 '16 at 00:05
16

Check the owner on the gpio files:

ls -l /sys/class/gpio/

Most likely, you'll find out that they are owned by group gpio:

-rwxrwx--- 1 root     gpio     4096 Mar  8 10:50 export
...

In that case, you can simply add your user to the gpio group to grant access without sudo:

sudo usermod -aG gpio myusername

You'll need to logout and log back in after that for the change to take effect.

jpa
  • 1,269
  • It doesn't work. Indeed, the group owner of everything in /sys/class/gpio/ is gpio, but even after adding myself to that group, I still get "permission denied" whenever I try to write anything there. – vsz Apr 15 '16 at 09:56
  • 1
    The problem is, that the files in /sys/class/gpio/ are actually just symlinks to /sys/devices/platform/soc/<some temporary name>/gpio where both the owner and group are root. – vsz Apr 15 '16 at 10:22
  • 4
    @vsz Did you try chgrp gpio /sys/devices/platform/soc/*/gpio? Perhaps something like that could be put in a startup file. – jpa Apr 15 '16 at 10:31
  • yes, but it's not that simple. As they are always generated in a different way, I had to use something like chgrp gpio \readlink -f /sys/class/gpio/gpio18`/*` – vsz Apr 19 '16 at 10:40
7

One solution for this (used particularly on the Linux desktop but also applicable in other cases) is to use D-Bus to activate a small service running as root and polkit to do the authorization. This is fundamentally what polkit was designed for; from its introductory documentation:

polkit provides an authorization API intended to be used by privileged programs (“MECHANISMS”) offering service to unprivileged programs (“CLIENTS”). See the polkit manual page for the system architecture and big picture.

Rather than execing your helper program, the big, unprivileged program would send a request on the bus. Your helper could either be running as a daemon started at system boot, or, better, be activated as needed by systemd. Then, that helper would use polkit to verify that the request is coming from an authorized place. (Or, in this case, if that feels like overkill, you could use some other hard-coded authentication/authorization mechanism.)

I found a good basic article on communicating via D-Bus, and while I haven't tested it, this appears to be a basic example of adding polkit to the mix.

In this approach, nothing needs to be marked setuid.

mattdm
  • 40,245
5

One way to do this is to make a setuid-root program written in C that does only what's needed and nothing more. In your case, it doesn't have to look at any user input at all.

#include <unistd.h>
#include <string.h>
#include <stdio.h>  // for perror(3)
// #include ...  more stuff for open(2)

static void write_str_to_file(const char*fn, const char*str) {
    int fd = open(fn, O_WRONLY)
    if (-1 == fd) {
        perror("opening device file");  // make this a CPP macro instead of function so you can use string concat to get the filename into the error msg
        exit(1);
    }
    int err = write(fd, str, strlen(str));
    // ... error check
    err = close(fd);
    // ... error check
}

int main(int argc, char**argv) {
    write_string_to_file("/sys/class/gpio/export", "17");
    write_string_to_file("/sys/class/gpio/gpio17/direction", "out");
    return 0;
}

There's no way to subvert this through environment variables or anything, because all it does is make a couple system calls.

Downside: you should check the return value of every system call.

Upside: error checking is really easy: if there's any error at all, just perror and bail out: exit with non-zero status. If there's an error, investigate with strace. You don't need this program itself to give really nice error messages.

muru
  • 72,889
Peter Cordes
  • 6,466
  • 2
    I might be tempted to open both files before writing anything to protect against the absence of the second. And I might run this program via sudo so it does not itself need to be setuid. Incidentally, it is <fcntl.h> for open(). – Jonathan Leffler Apr 16 '16 at 14:33
3

Bless tee instead of echo for sudo is one common way of approaching a situation where you need to limit the root perms. The redirect to /dev/null is to stop any output leaking out - the tee does what you want.

echo "17" | sudo tee /sys/class/gpio/export > /dev/null
echo "out" | sudo tee /sys/class/gpio/gpio17/direction > /dev/null
  • 5
    If you allow tee to be run with sudo, you are allowing ANY file to be overwritten or appended to (including, for example, /etc/passwd - just append a new uid=0 account). You may as well allow all commands to be run with sudo (BTW /etc/sudoers belongs in the set of 'ANY files' so can be overwritten with sudo tee) – cas Apr 15 '16 at 05:07
  • 2
    Apparently, you can limit the files which tee can write to, as described in this answer on a separate question. Just make sure you read the comments too, as some people had issues with the original syntax used and suggests fixes to that problem. – Alex Apr 15 '16 at 05:47
  • 1
    @alex, yes, you can do that with any command in sudo - restrict the permissible args. The config can get very long and complicated if you want to allow tee or whatever to operate on lots of files with sudo. – cas Apr 15 '16 at 10:56
0

You can make a script that performs your desired task. Then, create a new user account that can only log in by supplying a key used by OpenSSH.

Note: Anybody will be able to run that script if they have the key file, so make sure that the OpenSSH key file isn't readable by anyone that you wish to prevent from performing the task.

In the OpenSSH configuration (in the authorized_keys file), before specifying the key, specify a command (followed by a space), as described in this "example" text:

command="myscript" keydata optionalComment

This configuration option can restrict that OpenSSH key to only running a specific command. Now you have sudo granting the permissions, but the OpenSSH configuration is the piece of the solution that is really being used to restrict/limit what that user is able to do, so that the user isn't running other commands. This also doesn't have as complicated of a "sudo" configuration file, so if you use OpenBSD or if OpenBSD's new "doas" ("do as") starts to become more popular (with future versions of whatever operating system you use), you won't need to be challenged by much complexity in the sudo configuration.

TOOGAM
  • 208
  • 1
  • 7