Linux 安全 – SUID机制

Linux 安全 – SUID机制文章介绍了 Linux 中的 SUID 机制 包括 SUID 的简介和源码解析 SUID 允许用户以文件所有者的权限执行程序 提高了权限管理的灵活性 详细内容请参考原文

大家好,欢迎来到IT知识分享网。

一、简介

1.1 简介

最初 UNIX 为文件分配了九个允许位,对应三类用户(同主、同组、其他),三种操作(读、写、执行)。后来,UNIX 又增加了三个允许位:set-user-bit(又称 set-user-id 或 setuid)、set-group-bit(又称 set-group-id 或 setgid)、set-other-bit(又称 sticky bit)。看起来似乎是为文件新增加了一种set 操作,但实际上不是这样,这三个允许位与操作许可无关。先说 setuid。进程调用 execve 执行了一个允许位 setuid 为 1 的文件后,进程的 euid,还有Linux 特有的 fsuid,被改变为所执行文件的属主 id。效果就是进程执行文件不仅将文件的内容读入进程的代码区内存和数据区内存,还将文件属性中的部分数据读入进程凭证。于是,进程可以操作一些以前不能操作的客体。

引入 setuid 更深层的目的是特权提升。

1.2 文件权限位

(1)

$ ls -l text.txt -rw-rw-r-- 1 yl yl 0 Sep 28 16:25 text.txt 

其中第一个字段-rw-rw-r–,我们可以把它分为四部分看:

-rw-rw-r-- 
1- :第一个字符表示文件的类型 (2)rw- :第 2-4 个字符表示文件所有者权限位 (3)rw- :5-7 个字符表示所属组权限位 (4)r-- :8 -10 个字符表示其他用户权限位 

每个文件和目录都有一组权限位,用于控制对其的访问和操作权限。文件权限由九个位组成,分为三组,每组三个位,分别代表文件所有者、所属组和其他用户的权限。

(2)所属组权限位:这组权限位控制与文件属于相同组的其他用户对文件的权限。它们的顺序和符号表示与文件所有者权限位相同。

(3)其他用户权限位:这组权限位控制所有其他用户对文件的权限,即除了文件所有者和所属组之外的用户。同样,它们的顺序和符号表示与文件所有者权限位相同。

内核代码采用一个 bit 来表示一个操作许可,对于文件就需要 9 个 bit 来表示文件的操作许可:同主读、同主写、同主执行、同组读、同组写、同组执行、其他读、其他写、其他执行。这些表示操作许可的比特位合在一起就成为权限位。

在文件权限中,每个用户类型可以具有读取、写入和执行的权限组合。对于目录而言,读取权限允许查看目录内容,写入权限允许创建、删除和重命名目录中的文件,执行权限允许进入目录并访问其内容。

当文件设置了SUID权限时,文件的权限位会显示为包含”S”的特殊形式。具体来说,文件所有者权限位中的执行权限位 “x” 将被替换为 “s”。这表示当执行该文件时,它将以文件所有者的权限运行。

要设置SUID权限,可以使用chmod命令与数字或符号表示法。例如,chmod 4755 file将文件的权限设置为-rwsr-xr-x,其中SUID权限被设置为文件所有者的执行权限。

二、SUID简介

在Linux中,SUID(Set User ID)是一种权限机制,用于赋予程序在执行时临时获取文件所有者的权限。当一个可执行文件具有SUID权限时,无论是哪个用户执行该文件,该程序都会以文件所有者的权限运行,而不是执行者自身的权限。

SUID机制允许普通用户执行特定的程序或命令,以便执行特权操作,例如修改系统配置或访问受限资源。一个典型的例子是/usr/bin/passwd命令,它具有SUID权限,因此普通用户可以在不具备修改密码文件权限的情况下,通过执行该命令来更改自己的密码。

可以使用chmod命令的数字符号(“4”)或符号符号(“u+s”)来设置SUID位。

#define S_ISUID 0004000 

执行上下文:当用户执行一个启用了SUID的程序时,该程序以文件所有者的有效用户ID(EUID)运行。这暂时提升了运行程序的用户的权限级别到文件所有者的级别。

SUID权限仅对二进制程序(binary program)有效,不能用在脚本上(script)。

这个SUID机制就是专门为提升/切换用户权限而设计的,切换用户也必须先提升到root用户才能切换到其他用户。在这类文件被执行后,不需要验证密码,进程的euid被设置成文件属主的uid,如果文件属主是root用户当前进程就有了root权限,同时这时进程的uid和euid也不相等了。

因此当我们以普通用户在终端上执行 passwd,sudo 等命令时,比如普通用户的uid是1000,那么终端上的bash进程是普通用户的uid1000,当执行 passwd,sudo 等命令时, passwd,sudo 等二进制文件设置了SUID位,这么bash进程的euid以文件所有者的有效用户ID(EUID)运行,而 passwd,sudo 等二进制文件是由root用户管理的,其文件属主的用户名是 root 用户,因此passwd,sudo 等二进制文件的 EUID 等于 root 用户的uid = 0,因此bash进程的euid以文件所有者的有效用户ID = 0运行,这暂时提升了bash程序的用户的权限级别到文件所有者的级别。这样就达到了提升用户权限的作用了。

在这里插入图片描述

三、源码解析

execve -->do_execve -->do_execveat_common /* * sys_execve() executes a new program. */ -->__do_execve_file -->prepare_bprm_creds -->prepare_binprm 

3.1 prepare_bprm_creds

/* * This structure is used to hold the arguments that are used when loading binaries. */ struct linux_binprm { 
    struct cred *cred; /* new credentials */ } 
/* * Prepare credentials and lock ->cred_guard_mutex. * install_exec_creds() commits the new creds and drops the lock. * Or, if exec fails before, free_bprm() should release ->cred and * and unlock. */ static int prepare_bprm_creds(struct linux_binprm *bprm) { 
    if (mutex_lock_interruptible(&current->signal->cred_guard_mutex)) return -ERESTARTNOINTR; bprm->cred = prepare_exec_creds(); if (likely(bprm->cred)) return 0; mutex_unlock(&current->signal->cred_guard_mutex); return -ENOMEM; } 

prepare_bprm_creds函数用于准备执行新的二进制程序所需的凭证(credentials),确保在执行新程序时具备正确的身份验证和权限信息。

prepare_bprm_creds -->prepare_exec_creds -->prepare_creds 
/ * prepare_creds - Prepare a new set of credentials for modification * * Prepare a new set of task credentials for modification. A task's creds * shouldn't generally be modified directly, therefore this function is used to * prepare a new copy, which the caller then modifies and then commits by * calling commit_creds(). * * Preparation involves making a copy of the objective creds for modification. * * Returns a pointer to the new creds-to-be if successful, NULL otherwise. * * Call commit_creds() or abort_creds() to clean up. */ struct cred *prepare_creds(void) { 
    struct task_struct *task = current; const struct cred *old; struct cred *new; validate_process_creds(); new = kmem_cache_alloc(cred_jar, GFP_KERNEL); if (!new) return NULL; kdebug("prepare_creds() alloc %p", new); old = task->cred; memcpy(new, old, sizeof(struct cred)); new->non_rcu = 0; atomic_set(&new->usage, 1); set_cred_subscribers(new, 0); get_group_info(new->group_info); get_uid(new->user); get_user_ns(new->user_ns); #ifdef CONFIG_KEYS key_get(new->session_keyring); key_get(new->process_keyring); key_get(new->thread_keyring); key_get(new->request_key_auth); #endif #ifdef CONFIG_SECURITY new->security = NULL; #endif if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0) goto error; validate_creds(new); return new; error: abort_creds(new); return NULL; } EXPORT_SYMBOL(prepare_creds); 

为对任务的凭证进行修改准备一个新的副本。在一般情况下,任务的凭证不应直接被修改,而是通过先准备一个新的副本,然后由调用者对副本进行修改,最后通过调用 commit_creds() 来提交修改。

然后,它使用 kmem_cache_alloc 分配内存来创建一个新的 struct cred 对象,并将其赋值给 new。

接着,它通过调用 memcpy 将当前任务的凭证内容复制到新的凭证对象中。

设置新的凭证对象的一些属性,如 non_rcu、usage、subscribers 等。

获取相关的用户组信息、用户ID以及用户命名空间。

如果系统启用了 CONFIG_KEYS,则增加对会话、进程、线程和请求密钥的引用计数。

如果系统启用了 CONFIG_SECURITY,则将 security 字段设置为 NULL。

最后,如果 security_prepare_creds() 失败,它将通过 abort_creds() 清理并释放新凭证对象的内存,并返回 NULL。否则,它将验证新凭证对象的有效性,并返回指向新凭证的指针。

这段代码用于创建一个新的任务凭证的副本,以备修改和提交。它确保了在修改任务凭证时能够进行适当的拷贝和处理,并提供了必要的清理机制以防出现错误。

3.2 prepare_binprm

/* * Fill the binprm structure from the inode. * Check permissions, then read the first BINPRM_BUF_SIZE bytes * * This may be called multiple times for binary chains (scripts for example). */ int prepare_binprm(struct linux_binprm *bprm) { 
    int retval; loff_t pos = 0; bprm_fill_uid(bprm); /* fill in binprm security blob */ retval = security_bprm_set_creds(bprm); if (retval) return retval; bprm->called_set_creds = 1; memset(bprm->buf, 0, BINPRM_BUF_SIZE); return kernel_read(bprm->file, bprm->buf, BINPRM_BUF_SIZE, &pos); } EXPORT_SYMBOL(prepare_binprm); 

prepare_binprm函数作用是填充 struct linux_binprm 结构,该结构用于执行二进制程序。调用 bprm_fill_uid() 函数填充 bprm 结构的用户ID信息。

3.2.1 bprm_fill_uid

static void bprm_fill_uid(struct linux_binprm *bprm) { 
    struct inode *inode; unsigned int mode; kuid_t uid; kgid_t gid; /* * Since this can be called multiple times (via prepare_binprm), * we must clear any previous work done when setting set[ug]id * bits from any earlier bprm->file uses (for example when run * first for a setuid script then again for its interpreter). */ bprm->cred->euid = current_euid(); bprm->cred->egid = current_egid(); if (!mnt_may_suid(bprm->file->f_path.mnt)) return; if (task_no_new_privs(current)) return; inode = bprm->file->f_path.dentry->d_inode; mode = READ_ONCE(inode->i_mode); if (!(mode & (S_ISUID|S_ISGID))) return; /* Be careful if suid/sgid is set */ inode_lock(inode); /* reload atomically mode/uid/gid now that lock held */ mode = inode->i_mode; uid = inode->i_uid; gid = inode->i_gid; inode_unlock(inode); /* We ignore suid/sgid if there are no mappings for them in the ns */ if (!kuid_has_mapping(bprm->cred->user_ns, uid) || !kgid_has_mapping(bprm->cred->user_ns, gid)) return; if (mode & S_ISUID) { 
    bprm->per_clear |= PER_CLEAR_ON_SETID; bprm->cred->euid = uid; } if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { 
    bprm->per_clear |= PER_CLEAR_ON_SETID; bprm->cred->egid = gid; } } 

bprm_fill_uid函数根据文件的权限和设置,填充 struct linux_binprm 结构的用户ID信息。

/* * Keep mostly read-only and often accessed (especially for * the RCU path lookup and 'stat' data) fields at the beginning * of the 'struct inode' */ struct inode { 
    umode_t i_mode; ...... kuid_t i_uid; kgid_t i_gid; } 
/* * This structure is used to hold the arguments that are used when loading binaries. */ struct linux_binprm { 
    struct file * file; } 
struct file { 
    struct path f_path; } 
struct path { 
    struct dentry *dentry; } __randomize_layout; 
struct dentry { 
    struct inode *d_inode; /* Where the name belongs to - NULL is negative */ } 

这段代码用于根据文件的权限模式和设置,将适当的用户ID和组ID填充到 struct linux_binprm 结构中。它会检查文件的 setuid 和 setgid 位,并根据情况设置相关的用户ID和组ID。

3.2.2 security_bprm_set_creds

static inline int security_bprm_set_creds(struct linux_binprm *bprm) { 
    return cap_bprm_set_creds(bprm); } 
/ * cap_bprm_set_creds - Set up the proposed credentials for execve(). * @bprm: The execution parameters, including the proposed creds * * Set up the proposed credentials for a new execution context being * constructed by execve(). The proposed creds in @bprm->cred is altered, * which won't take effect immediately. Returns 0 if successful, -ve on error. */ int cap_bprm_set_creds(struct linux_binprm *bprm) { 
    //把bash普通进程的suid和sgid替换为passwd,sudo 等可执行二进制文件的uid,gid new->suid = new->fsuid = new->euid; new->sgid = new->fsgid = new->egid; } 

新凭证中的用户和组ID设置为有效用户和组ID。

3.3 install_exec_creds

/* * sys_execve() executes a new program. */ __do_execve_file -->exec_binprm -->search_binary_handler { 
    struct linux_binfmt *fmt; fmt->load_binary(bprm); } 

对于加载 elf 二进制可执行文件来说:

static struct linux_binfmt elf_format = { 
    .load_binary = load_elf_binary, }; 
/* * These are the functions used to load ELF style executables and shared * libraries. There is no binary dependent code anywhere else. */ static int load_elf_binary(struct linux_binprm *bprm) { 
    install_exec_creds(bprm); } 
/* * install the new credentials for this executable */ void install_exec_creds(struct linux_binprm *bprm) { 
    security_bprm_committing_creds(bprm); commit_creds(bprm->cred); bprm->cred = NULL; /* * Disable monitoring for regular users * when executing setuid binaries. Must * wait until new credentials are committed * by commit_creds() above */ if (get_dumpable(current->mm) != SUID_DUMP_USER) perf_event_exit_task(current); /* * cred_guard_mutex must be held at least to this point to prevent * ptrace_attach() from altering our determination of the task's * credentials; any time after this it may be unlocked. */ security_bprm_committed_creds(bprm); mutex_unlock(&current->signal->cred_guard_mutex); } EXPORT_SYMBOL(install_exec_creds); 

commit_creds替换当前进程的凭证来安装新的执行凭证。

总结

除了以用户和用户组控制权限,还有以下凭证机制:

Capabilities Secure management flags (securebits) Keys and keyrings LSM AF_KEY 

详细请参考:Linux 安全 – Credentials

参考资料

Linux 5.4.18

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/112019.html

(0)
上一篇 2026-01-21 10:33
下一篇 2026-01-21 11:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信