Можно ли выполнить атомарную замену файла в корневом каталоге (проблемы безопасности программы SUID?)
Для обеспечения согласованности при перебоях питания файлы можно обновлять следующим образом (псевдокод):
echo original_contents > file
echo new_contents > /tmp_dir_in_same_fs/file.tmp
mv /tmp_dir_in_same_fs/file.tmp file
Теперь дело в том, что если файлы находятся в корневой папке, эта операция переименования получает отказано в разрешении:
mkdir the_dir
echo original_contents > the_dir/file
echo new_contents > file.tmp
sudo chown root:root the_dir
sudo chmod 755 the_dir
mv file.tmp the_dir/file # permission denied
в то время как:
echo new_contents > the_dir/file
работает (но это не так безопасно, когда речь идет о согласованности при потере мощности).
Так что я думаю о написании небольшой программы SUID rename_suid
это просто проверит, что исходные и целевые файлы имеют правильное владение, так что замена может быть выполнена.
Вот предлагаемая реализация:
/*! \file rename_suid.c
\brief rename file with SUID, provided target file is owned
by calling user and source file mode mode matches the target file's.
\license https://creativecommons.org/licenses/by-sa/3.0/
© cJ-so-rs@zougloub.eu 2018
*/
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char ** argv)
{
int res;
if (argc != 3) {
fprintf(stderr, "Usage: %s <source> <target>\n", argv[0]);
return EINVAL;
}
char const * src = argv[1];
char const * dst = argv[2];
struct stat stat_src;
struct stat stat_dst;
int uid = getuid();
int euid = geteuid();
if (euid != 0) {
fprintf(stderr, "I am not root EUID, trouble ahead\n");
}
res = stat(src, &stat_src);
if (res != 0) {
perror("Cannot stat source file");
return errno;
}
res = stat(dst, &stat_dst);
if (res != 0) {
perror("Cannot stat target file");
return errno;
}
if (stat_src.st_uid != uid) {
perror("Bad source uid");
return EPERM
}
if (stat_dst.st_uid != uid) {
perror("Bad target uid");
return EPERM;
}
if (stat_src.st_mode != stat_dst.st_mode) {
fprintf(stderr, "Inconsistent source-target modes\n");
return EPERM;
}
res = rename(src, dst);
if (res != 0) {
perror("Cannot rename");
}
return errno;
}
Вопрос... я что-то упустил:
- Есть ли другое решение проблемы?
- Если нет, существует ли подобный инструмент?
- Если такой инструмент является единственным решением, может ли он по своему принципу действия вызвать непредвиденные проблемы безопасности?
- Любая (безопасность) проблема с предложенной реализацией?
Спасибо,