What is the safest way for programmatically writing to a file with root privileges?

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?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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.

Method 2

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.

Method 3

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.

Method 4

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.

Method 5

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

Method 6

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.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x