Search code examples
clinuxsetuidsysfs

Open a file as root, but drop privileges before reading from it?


TL;DR

I am writing a C program. I need to have root privileges to fopen a sysfs file, and I still need root privileges in order to read from it. However, since my program will need to continuously read the sysfs file, this implies that it will need to have elevated privileges the whole time. I would like to drop root privilege as soon as possible. What's the accepted way of approaching this problem?

Details

I am writing a program that interacts with sysfs. If I was running the commands on the shell, I would use:

myuser@mymachine:~$ sudo su
root@mymachine:/home/myhomedir# cd /sys/class/gpio
root@mymachine:/sys/class/gpio# echo 971 > export
root@mymachine:/sys/class/gpio# cat gpio971/value
0
root@mymachine:/sys/class/gpio# exit

I need to run these commands in a C program that is callable by a non-privileged user. One way to do this is to write the program in the usual way using fopen, fprintf, fscanf, etc and have the user run the program through sudo. However, this means the user needs to be a sudoer, and the program will have root privilege the whole time.

Another solution, which I strongly prefer (since the user will not have to be added to sudoers) is to change the program's owner to root, and set the setuid bit. (I learned this from here).

However, there's something I'm wondering about. What I would like to do is open the sysfs files while the program's euid is 0, but then drop all privileges right away (for safety). Then, now that the file has been opened, we simply setuid() to the user's UID. However, although I can't be entirely sure, this isn't working. Here is the relevant part of my code:

//At this point, due to the file permissions on the executable,
//euid = 0 and ruid = 1000. I know the following 4 lines work.
FILE *export = fopen("/sys/class/gpio/export", "wb");
fprintf(export, "971\n");
fclose(export);

FILE *sw_gpio = fopen("/sys/class/gpio971/value", "rb");

setuid(1000);
//Now euid = 1000 and ruid = 1000

int switch_val = -1;
fscanf(sw_gpio, "%d", &switch_val);
printf("Switch value: %d\n", switch_val); //-1
//Even though the only possible values in this sysfs file are 0 and 1,
//switch_val is still equal to -1

fclose(sw_gpio);

So it seems that I will need to keep elevated permissions to be able to read from /sys/class/gpio/gpio971/value. But this is exactly what I don't want! This program will need to poll the value throughout execution of the program, and I don't want root privileges the whole time.

Finally for the sake of completion, here are the permissions I've set on my executable:

-rwsr-xr-x 1 root myuser 10943 Jan 1 20:17 main*

So how does one drop root privilege, but continue to read from an access-controlled sysfs file?


Solution

  • I haven't tried this with /sysfs, but even with plain files my understanding is that file streams do not retain access permissions after a call to setuid(). File handles do, however, for reasons I don't understand. So, if your system behaves like mine (Fedora 20 on x64) you might be able to use open()/read() instead of fopen()/fscanf().