Security 是否可以在根目录中执行原子文件替换(SUID程序安全问题?)

Security 是否可以在根目录中执行原子文件替换(SUID程序安全问题?),security,unix,file-rename,data-consistency,suid,Security,Unix,File Rename,Data Consistency,Suid,为确保断电时的一致性,可以按如下方式更新文件(伪代码): 现在的问题是,如果文件位于root拥有的文件夹中,则重命名操作会被拒绝: 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

为确保断电时的一致性,可以按如下方式更新文件(伪代码):

现在的问题是,如果文件位于root拥有的文件夹中,则重命名操作会被拒绝:

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;
}
/*\文件重命名_suid.c
\使用SUID简要重命名文件,前提是目标文件为所有者
通过调用用户和源文件模式匹配目标文件的。
\许可证https://creativecommons.org/licenses/by-sa/3.0/
©苏家杰-rs@zougloub.eu2018
*/
#包括
#包括
#包括
#包括
#包括
int main(int argc,字符**argv)
{
国际关系;
如果(argc!=3){
fprintf(stderr,“用法:%s\n”,argv[0]);
返回EINVAL;
}
char const*src=argv[1];
char const*dst=argv[2];
结构stat\u src;
结构统计;
int uid=getuid();
int-euid=geteuid();
如果(euid!=0){
fprintf(stderr,“我不是根EUID,前面有麻烦”\n);
}
res=stat(src和stat_src);
如果(res!=0){
perror(“无法统计源文件”);
返回errno;
}
res=统计(dst和统计单位dst);
如果(res!=0){
perror(“无法统计目标文件”);
返回errno;
}
如果(stat\u src.st\u uid!=uid){
perror(“坏源uid”);
返回项
}
如果(stat_dst.st_uid!=uid){
perror(“坏目标uid”);
返回EPERM;
}
if(stat_src.st_模式!=stat_dst.st_模式){
fprintf(stderr,“源-目标模式不一致”);
返回EPERM;
}
res=重命名(src,dst);
如果(res!=0){
perror(“无法重命名”);
}
返回errno;
}
问题是。。。我是否遗漏了什么:

  • 这个问题还有别的解决办法吗
  • 如果没有,是否存在类似的工具
  • 如果这种工具是唯一的解决方案,那么根据其操作原则,它是否会导致不可预见的安全问题
  • 提议的实施有任何(安全)问题吗

谢谢,

目录上的setfacl对您有帮助吗?无论如何,您将通过二进制文件向非root用户授予类似的权限。我并不真正理解这个场景,但是写这个似乎是冒险的,而且对于这个任务来说是过度的。我个人会尝试用备用电源来避免停电,停电时,如果UPS无法再保持,我会优雅地关闭服务。无论如何,您可能会遇到一些问题,如更改尚未同步到磁盘等,并且可能会发生数据损坏。用户应该无法在此文件夹中创建新文件。777+粘性位解决方案也不起作用。一位更专业的朋友告诉我,A)我的SUID代码有TOCTOU问题(尽管不清楚如何利用它),b)他建议绕过这个问题,让文件夹中的文件与用户文件进行符号链接,这样用户就可以在其他地方进行原子移动。这个问题可以通过该解决方案解决,但假设我们出于某种原因不想这样做;)
/*! \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;
}