Можно ли выполнить атомарную замену файла в корневом каталоге (проблемы безопасности программы 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;
}

Вопрос... я что-то упустил:

  • Есть ли другое решение проблемы?
  • Если нет, существует ли подобный инструмент?
  • Если такой инструмент является единственным решением, может ли он по своему принципу действия вызвать непредвиденные проблемы безопасности?
  • Любая (безопасность) проблема с предложенной реализацией?

Спасибо,

0 ответов

Другие вопросы по тегам