Открыть файл как root, но сбросить привилегии перед чтением из него?

TL;DR

Пишу программу на Си. Мне нужны привилегии root для fopen файла sysfs, и мне все еще нужны привилегии root, чтобы читать из него. Однако, поскольку моя программа должна будет постоянно читать файл sysfs, это означает, что ей все время нужно будет иметь повышенные привилегии. Я хотел бы отказаться от привилегий root как можно скорее. Каков принятый способ решения этой проблемы?

Подробности

Я пишу программу, которая взаимодействует с sysfs. Если бы я запускал команды в оболочке, я бы использовал:

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

Мне нужно запустить эти команды в программе C, которую может вызывать непривилегированный пользователь. Один из способов сделать это — написать программу обычным способом, используя fopen, fprintf, fscanf и т. д., и заставить пользователя запустить программу через sudo. Однако это означает, что пользователь должен быть sudoer, и программа все время будет иметь привилегии root.

Еще одно решение, которое я настоятельно предпочитаю (поскольку пользователя не нужно будет добавлять в sudoers), — изменить владельца программы на root и установить бит setuid. (Я узнал об этом из здесь).

Однако есть кое-что, о чем я задаюсь вопросом. Что я хотел бы сделать, так это открыть файлы sysfs, пока программа euid равна 0, но затем сразу же отказаться от всех привилегий (для безопасности). Затем, когда файл был открыт, мы просто setuid() получаем UID пользователя. Однако, хотя я не могу быть полностью уверен, это не работает. Вот соответствующая часть моего кода:

//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);

Так что, похоже, мне нужно будет сохранить повышенные разрешения, чтобы иметь возможность читать из /sys/class/gpio/gpio971/value. Но это именно то, чего я не хочу! Эта программа должна будет опрашивать значение во время выполнения программы, и мне не нужны привилегии root все время.

Наконец, для завершения, вот разрешения, которые я установил для своего исполняемого файла:

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

Итак, как отказаться от привилегий root, но продолжить чтение из файла sysfs с контролируемым доступом?


person Marco Merlini    schedule 30.08.2017    source источник
comment
Это работает, как и ожидалось для меня. Попробуйте проверить возвращаемое значение fscanf, и если оно равно EOF, вызовите perror, чтобы сообщить вам, почему это не удалось. Вы также должны выполнить проверку ошибок для вызовов fopen.   -  person dbush    schedule 30.08.2017
comment
@dbush У меня есть perror по всему моему реальному коду (я не загромождал ими свой исходный пост). Все вызовы вернули успех.   -  person Marco Merlini    schedule 30.08.2017
comment
Давно я не играл с SUID-битами и тому подобным, но будет ли seteuid() (установка эффективного идентификатора пользователя) делать то, что вы хотите? С установленным битом setuid двоичного файла, я думаю, вы можете переключаться между реальным (непривилегированным) пользователем и пользователем-владельцем (suid).   -  person TripeHound    schedule 30.08.2017
comment
Это своего рода проблема, которая очень хорошо решается при разработке vsftpd (см. обзор в этом видео). По сути, если программа имеет привилегии root, она вообще не должна напрямую взаимодействовать с ненадежными клиентами. Вместо этого делегируйте все взаимодействие с клиентом отдельному дочернему процессу с более низкими привилегиями и используйте IPC для передать это взаимодействие в тщательно контролируемой манере.   -  person r3mainer    schedule 30.08.2017
comment
@squeamishossifrage Спасибо, что указали на это. Это выглядит как чистое решение таких проблем   -  person Marco Merlini    schedule 30.08.2017
comment
Обычно (например, для обычного файла на диске) разрешения проверяются при открытии файла, и после этого разрешения больше не проверяются. Это означает, что вы обычно можете открыть файл с повышенными привилегиями, отказаться от привилегий и продолжить чтение с удовольствием. Было бы немного удивительно, если бы тип файловой системы не подчинялся этой семантике; это подрывает продуманное проектное решение в UNIX. Однако ваша проблема заключается не в постоянном доступе к файлу, а в многократном открытии и закрытии файла. Разрешения проверяются open; вам нужно разрешение (повышенные привилегии) ​​для каждого открытия.   -  person Jonathan Leffler    schedule 30.08.2017
comment
Правильным решением основной проблемы является установка правила udev, которое разрешает доступ к псевдофайлам gpio для подходящей группы (обычно gpio), а затем добавление пользователей, которым разрешено возиться с контактами gpio, в эту группу. Для самого правила посмотрите, например. последний пост в этом обсуждении на форумах Raspberry Pi — должен быть два правила, две линии; хотя сам не проверял. Таким образом, вашей программе вообще не нужно иметь setuid/setgid или заботиться о разрешениях.   -  person Nominal Animal    schedule 30.08.2017
comment
Mahkoe › Я второй NominalAnimal по этому поводу: правильный способ делать то, что вы хотите, — это не требовать разрешений администратора, а исправить разрешения устройства, чтобы предоставить доступ тем, кто в нем нуждается.   -  person spectras    schedule 31.08.2017
comment
@Mahkoe› из любопытства, можете ли вы запустить strace нерабочей версии и опубликовать результат где-нибудь? Вам нужно прикрепить запущенный процесс, иначе SUID не будет работать: 1) добавьте sleep(60) в начале вашей программы. 2) запустите его и найдите его PID. 3) запустить strace -p pid в другой консоли. Когда программа выйдет из спящего режима, вы должны увидеть появление трассировки.   -  person spectras    schedule 31.08.2017


Ответы (1)


Я не пробовал это с /sysfs, но даже с обычными файлами, как я понимаю, файловые потоки не сохраняют права доступа после вызова setuid(). Однако дескрипторы файлов работают по непонятным мне причинам. Итак, если ваша система ведет себя как моя (Fedora 20 на x64), вы можете использовать open()/read() вместо fopen()/fscanf().

person Kevin Boone    schedule 30.08.2017
comment
Не могли бы вы провести некоторое исследование, лично мне не нравится если ваша система ведет себя как моя, это не надежное решение. - person Iharob Al Asimi; 30.08.2017
comment
Я могу подтвердить, что это решение работает в моей системе! (Для процветания обратите внимание, что дескриптор файла sysfs должен быть fflush()ed и rewind()ed, чтобы ваша программа могла отслеживать текущее содержимое файла. В противном случае она будет буферизовать старое значение в файле). Кстати, кто-нибудь знает, почему права доступа к потокам и файловым дескрипторам обрабатываются по-разному? - person Marco Merlini; 30.08.2017
comment
@Mahkoe Теперь это интересный вопрос. Я предполагаю, что потоки могут закрыть дескриптор и открыть его снова, если это необходимо, так что это может быть связано с этим. Но я не знаю, требует ли стандарт, чтобы реализации этого не делали. Помните, что fflush() необходим, потому что потоки буферизуются по умолчанию, тогда как, если вы используете файловые дескрипторы напрямую, вам не нужно ничего сбрасывать, если вы не буферизуете что-то самостоятельно. - person Iharob Al Asimi; 30.08.2017
comment
Потоки или дескрипторы ничего не знают о разрешениях. Разрешения устанавливаются для файла и определяют вашу способность открывать его (т. е. создавать дескриптор). Поток обычно представляет собой тонкую обертку вокруг дескриптора. Ваше объяснение звучит довольно подозрительно. - person n. 1.8e9-where's-my-share m.; 30.08.2017
comment
Я был достаточно заинтересован в вопросе, чтобы потратить десять минут, проверяя его на своем ноутбуке. Я разместил то, что нашел, в надежде, что это будет кому-то полезно. Если кто-то хочет, чтобы я провел исчерпывающий обзор различий между дескрипторами и потоками для ряда платформ, компиляторов и библиотек, моя плата за консультации составляет 150 фунтов стерлингов в час плюс НДС :) - person Kevin Boone; 30.08.2017
comment
Нет необходимости опрашивать компиляторы и библиотеки, достаточно открыть документацию POSIX и прочитать (или, для такого дорогого консультанта, как вы, просто процитируйте наизусть :D). fread устанавливает совершенно другой диапазон статусов errno, чем fopen. Невозможно сообщить об ошибке, когда поток неожиданно закрывается и снова открывается. Таким образом, ваша предполагаемая лицензия на это маловероятна. - person n. 1.8e9-where's-my-share m.; 30.08.2017
comment
Я пойду еще дальше: структура FILE, определенная и используемая внутри glibc, которая является единственной полной, широко используемой реализацией libc, работающей на ядрах Linux, не хранит путь, используемый для открытия файла, так что, не углубляясь, это уверен, что он не может open() это снова, как только fopen вернулся. Что бы ни происходило в системе ОП, предполагаемое объяснение просто невозможно. - person spectras; 31.08.2017