I have already said more than once about linux single-point verification. The overall design of Linux is the separation of mechanisms and policies. Single-point verification is obviously a policy aspect, therefore, verification itself does not involve kernel intervention. What is verification itself? In fact, it is like the simplest password verification and a slightly more complex fingerprint, sound or pupil verification. In any case, these are policies and the kernel should not be involved, therefore, you cannot know how to store and verify that the user's password is correct in the kernel. These are all completed in the user space. This fact seems to surprise Linux beginners, how can we avoid kernel intervention for important things such as security verification? This is because of the mechanism and policy separation of Linux. Linux relies on UID, EUID, and SUID mechanisms to achieve this. it can be proved that these three are indispensable and they are very compact. The following analysis does not consider the concept of group or the new SELinux policy.
UID is the UID of the owner of the executable file or the UID of the caller of the executable file. It is a static concept, while EUID is a valid uid, which is a dynamic concept, in fact, the behavior judgment of the execution thread is based on EUID. uid is just a reference. Just like the concept of process priority, the dynamic priority is calculated and finally determined by the dynamic priority, static Priority is just a reference. EUID is the actual valid uid of the process and takes effect as the judgment standard. SUID has special meanings. It may be the UID of executable files, and EUID is not root, however, the executable file must have the root permission to perform some special operations, so the executable file will temporarily have the root permission. Such an executable file has the SUID attribute, this SUID attribute is required. Otherwise, common users cannot su root users, because the UID and EUID of common user processes are not 0. According to the traditional capability model, non-zero UID/EUID cannot change its UID/EUID. After a while, I will explain why the UID and EUID processes without zero change their EUID, first look at the following code (2.6.24 kernel ):
Asmlinkage long sys_setuid (uid_t UID)
{
... // See the following
}
Since a process with a uid other than 0 cannot change its uid, EUID 0 becomes the final lifeline for normal users to become root. In fact, a running process can only change its EUID, UID is the internal attribute of the executable file, and it does not make sense to change it. In a sense, EUID makes sense. If there is no EUID attribute, the general user process basically has no chance to upgrade to the root process. The sub-process of the general process is still a common process. This repetition will never lead to a chance, but SUID makes the general process a root process, /bin/Su is an example. To become a root process, verification is required. This is the verification logic of the user space. When a common user process calls Su, in fact, the UID of Su is still a common process, but because Su has the SUID attribute, its EUID is root, so as long as the user space passes the verification, the shell generated by exec will be root, which is the essence of single-point authentication. If the su program is not well written, for example, it does not pass verification, but only exec a shell, obviously, this shell is root, But no matter what the kernel is, the kernel only knows EUID, the real verification and the logic for how to verify the logic left to the user space. Although Su is SUID, as long as it exec a shell, the shell is root, however, Su won't be so stupid. It implements the password verification logic internally. Only the verification can be performed to Exec a root shell.
EUID indicates that the privilege level of a process belongs to root and non-root, while the new process of exec with EUID as root still belongs to root, only root suid programs have the above functions. A common user program does not use this function even if the SUID bit is set. In fact, the true meaning of SUID is that it does not inherit EUID from the parent process, the ID of the owner of the executable file is used as the EUID. Linux implements a simple user verification and later capability model based on traditional Unix. The two are actually combined, and only the root user will be given the ability, this is actually introduced to prevent system vulnerabilities caused by excessive root power. It is actually another level of authentication system located at the root layer. First, we will explain the implementation of setuid:
Asmlinkage long sys_setuid (uid_t UID)
{
Int old_euid = Current-> EUID;
Int old_ruid, old_suid, new_ruid, new_suid;
Int retval;
Retval = security_task_setuid (UID, (uid_t)-1, (uid_t)-1, lsm_setid_id );
If (retval)
Return retval;
Old_ruid = new_ruid = Current-> uid;
Old_suid = Current-> SUID;
New_suid = old_suid;
If (capable (cap_setuid) {// EUID non-root-level processes do not have the setuid capability
If (UID! = Old_ruid & set_user (UID, old_euid! = UID)
Return-eagain;
New_suid = uid;
} Else if (UID! = Current-> UID) & (UID! = New_suid) // EUID non-root processes can only be controlled in their own name set
Return-eperm;
...
Current-> fsuid = Current-> EUID = uid;
Current-> SUID = new_suid;
... // The post_setuid below completes the final work, which is actually to clear some capabilities
Return security_task_post_setuid (old_ruid, old_euid, old_suid, lsm_setid_id );
}
Static inline void cap_emulate_setxuid (INT old_ruid, int old_euid, int old_suid)
{
If (old_ruid = 0 | old_euid = 0 | old_suid = 0 )&&
(Current-> uid! = 0 & Current-> EUID! = 0 & Current-> SUID! = 0) // note that as long as one of uid, EUID, and SUID is 0, the process will return to the root capacity level, because the process can change the UID or other IDs at any time to become a member of another level.
&&! Current-> keep_capabilities) {// if the process is not at the root level, the capacity set is cleared.
Cap_clear (current-> cap_permitted );
Cap_clear (current-> cap_valid tive );
}
If (old_euid = 0 & Current-> EUID! = 0) {// EUID indicates the privilege level of a process. In the capability model, cap_valid is also the most critical, indicating the current capability of the process, cap_permitted indicates a complete set, indicating all capabilities that may be granted.
Cap_clear (current-> cap_valid tive );
}
If (old_euid! = 0 & Current-> EUID = 0) {// if the process is not at the root level but is now, all the permitted capabilities are allowed, this happens after the process revokes the abandoned privilege.
Current-> cap_effective = Current-> cap_permitted;
}
}
From the pairing of EUID and cap_effective of a process, we can see that as long as the EUID of a process is not 0, it does not have any capacity. In turn, once the EUID of a process becomes 0, then it should wait until all the capabilities are available, and it can select the current required capabilities, that is, change cap_effective but keep the cap_permitted set. Every time a function such as setuid is called, the above cap_emulate_setxuid function will be called for ID judgment and necessary capability clearing, every time you execute a new binary image, the following function is called for ID judgment and capability granting. This is a pair operation.
Int cap_bprm_set_security (struct linux_binprm * bprm)
{
Cap_clear (bprm-> cap_inheritable );
Cap_clear (bprm-> cap_permitted );
Cap_clear (bprm-> cap_valid tive );
If (! Issecure (secure_noroot )){
If (bprm-> e_uid = 0 | current-> uid = 0) {// if either the EUID or uid is 0, all capabilities are provided but not effective.
Cap_set_full (bprm-> cap_inheritable );
Cap_set_full (bprm-> cap_permitted );
}
If (bprm-> e_uid = 0) // It takes effect only when EUID is 0. UID 0 is static, indicating a potential capability, only when the EUID is 0 Can these capabilities be truly enabled.
Cap_set_full (bprm-> cap_effective );
}
Return 0;
}
Let's take a look at the call functions of the above functions:
Int prepare_binprm (struct linux_binprm * bprm)
{
Int mode;
Struct inode * inode = bprm-> file-> f_dentry-> d_inode;
Int retval;
Mode = inode-> I _mode;
If (bprm-> file-> f_op = NULL)
Return-eacces;
Bprm-> e_uid = Current-> EUID; // The EUID of the current process to be executed
Bprm-> e_gid = Current-> EGID; // same reason as above
If (! (Bprm-> file-> f_vfsmnt-> mnt_flags & mnt_nosuid )){
If (Mode & s_isuid) {// whether the SUID flag exists
Current-> personality & = ~ Per_clear_on_setid;
Bprm-> e_uid = inode-> I _uid; // If the SUID flag exists, the bprm image runs at the root level after execution, of course, its children will also run at the root level. For details, we can see that fork is copied during writing.
}
If (Mode & (s_isgid | s_ixgrp) = (s_isgid | s_ixgrp )){
Current-> personality & = ~ Per_clear_on_setid;
Bprm-> e_gid = inode-> I _gid;
}
}
Retval = security_bprm_set (bprm); // cap_bprm_set_security is called
If (retval)
Return retval;
Memset (bprm-> Buf, 0, binprm_buf_size );
Return kernel_read (bprm-> file, 0, bprm-> Buf, binprm_buf_size );
}
The following function will be called at the end of image loading:
Void cap_bprm_apply_creds (struct linux_binprm * bprm, int unsafe)
{
Kernel_cap_t new_permitted, working;
New_permitted = cap_intersect (bprm-> cap_permitted, cap_bset );
Working = cap_intersect (bprm-> cap_inheritable, current-> cap_inheritable );
New_permitted = cap_combine (new_permitted, working );
If (bprm-> e_uid! = Current-> uid | bprm-> e_gid! = Current-> GID |
! Cap_issubset (new_permitted, current-> cap_permitted )){
Current-> MM-> dumpable = suid_dumpable;
If (unsafe &~ Lsm_unsafe_ptrace_cap) {// if it is not in ptrace, the following logic is entered.
If (! Capable (cap_setuid) {// This indicates that the current EUID is not 0, that is, the current process is not at the root capacity level.
Bprm-> e_uid = Current-> uid; // assign the UID of the current process to the EUID of bprm. After bprm is executed, it will no longer be at the root capacity level, if the UID is 0 and the EUID is not 0, the EUID of bprm is still 0.
Bprm-> e_gid = Current-> GID;
}
If (! Capable (cap_setpcap )){
New_permitted = cap_intersect (new_permitted, current-> cap_permitted );
}
}
} // The following two lines formally set the EUID to the current process. Now the current process is a new process. The call stack of this function starts from the end of load_elf_binary.
Current-> SUID = Current-> EUID = Current-> fsuid = bprm-> e_uid;
Current-> SGID = Current-> EGID = Current-> fsgid = bprm-> e_gid;
If (current-> PID! = 1 ){
Current-> cap_permitted = new_permitted;
Current-> cap_effective = cap_intersect (new_permitted, bprm-> cap_effective );
}
Current-> keep_capabilities = 0;
}
Void compute_creds (struct linux_binprm * bprm)
{
Int unsafe;
If (bprm-> e_uid! = Current-> UID)
Suid_keys (current );
Exec_keys (current );
Task_lock (current );
Unsafe = unsafe_exec (current );
Security_bprm_apply_creds (bprm, unsafe );
Task_unlock (current );
Security_bprm_post_apply_creds (bprm );
}
The Linux capability model is a complex model with many capabilities and many configuration rules.
The above is the kernel Implementation of the fusion of the traditional UNIX root user's two-level mechanism and capability model. The authentication policy must be implemented in the user space, in fact, password verification, fingerprint verification and other policies. It is precisely because of SUID that a common user process can Su a root shell or another user's shell. If the SUID attribute does not exist, therefore, no common user can become a root user, because the kernel is not verified, and only the kernel has the right to change the UID of the user process, there must be a process outside the kernel to proxy the kernel for verification, this process must be absolutely trustable, and Su is a SUID program. It acts as the root agent and maintains the highest permissions. Only in this way can the verification result be trusted, and it performs actual verification, fork-exec will generate a root user's shell or another user's shell, so the su attack will be fatal, be sure to protect Su without being replaced. In fact, as long as you get the root privilege, you can write a SUID program by yourself, and then its sub-processes will be privileged, the following is an example:
The following is a SUID program. After compilation, use chmod 7777 test to set it as the most open SUID program:
# Include
# Include
Int main ()
{
Printf ("uid: % d/neuid: % d/negid: % d/N", getuid (), geteuid (), getegid ());
Setuid (0 );
Printf ("New uid: % d/N", getuid ());
Execve ("/home/Zhaoya/Temp", null, envp );
}
The following program is compiled into temp under the/home/Zhaoya directory by the Zhaoya User:
# Include
# Include
# Include
Int main ()
{
Printf ("uid: % d/neuid: % d/N", getuid (), geteuid ());
Int FD = open ("/root/install. log", o_wronly );
Perror ("open ");
}
If you run temp directly, no permission error is obtained. If you start with test, the file in the root directory is successfully opened. The output shows the above kernel behavior. The output of running temp directly is 500,500, the output started through test is 0, 0. if you comment out the setuid (0); in test, the result of starting temp is also the root directory file successfully opened, but the output is, 0. In short, the result of geteuid is 0, in this case, its permissions are root, while the root permissions of modern Linux correspond directly to the complete capability set. the root user can use the system call interface of the Capability Model to remove the capabilities that are not currently required from cap_effective, cap_permitted does not change. The root user can set some capabilities through the interface. In fact, the capability model mainly limits the root user. For other users themselves, they do not have any permissions and do not need to restrict them any more, restrictions on root users can improve security.