A kernel vulnerability caused by synchronization of the ptrace interface and execve

Source: Internet
Author: User

In Linux, remote threads cannot be attached to other processes, and remote threads cannot be created. However, this can be done in an informal way. This is the ptrace interface, ptrace can be attached to any user process, and can even change the registers and memory ing of any process with special parameters. This capability is comparable to or even more flexible than creating remote threads, if you understand the memory layout of the elf image, you can use ptrace to modify any memory of the debugged process. However, there is a restriction that the ptrace interface can only debug processes of its own users, that is, it cannot debug processes of other users. An obvious restriction is that ptrace of ordinary users cannot debug root processes, however, 2.6.29 has an obvious kernel vulnerability that allows common users to gain local privileges.

To put it simply, when you call exec, you need to go through a series of UID and EUID calculations, especially when exec has a SUID program. This calculation will become more complex, when ptrace is used, a series of UID and EUID judgments are required. If the synchronization between the two is difficult to handle, ptrace misjudgment will inevitably occur, that is to say, ptrace has debugged a process that does not belong to itself, and may even be a root process. If the calling process of ptrace changes the memory of the called process with SUID, make it exec a shell, then the shell will belong to the root, because the exec call process, that is, the EUID of the SUID process is 0.

The earlier kernel vulnerabilities became more obvious. Let's look at ptrace_attach in the 2.4.9 kernel.

Int ptrace_attach (struct task_struct * task)

{

Task_lock (task );

...

If (current-> uid! = Task-> EUID) |

(Current-> uid! = Task-> SUID) |

(Current-> uid! = Task-> UID) |

(Current-> Gid! = Task-> EGID) |

(Current-> Gid! = Task-> SGID) |

(! Cap_issubset (Task-> cap_permitted, current-> cap_permitted) |

(Current-> Gid! = Task-> GID ))&&! Capable (cap_sys_ptrace ))

Goto bad;

...

Task-> ptrace | = pt_ptraced;

Task_unlock (task );

Write_lock_irq (& tasklist_lock );

If (Task-> p_pptr! = Current ){

Remove_links (task );

Task-> p_pptr = current;

Set_links (task );

}

Write_unlock_irq (& tasklist_lock );

Send_sig (sigstop, task, 1 );

Return 0;

Bad:

Task_unlock (task );

Return-eperm;

}

This early kernel looks very neat, because there are a few locks, the granularity of the lock is naturally very rough, but fortunately, look at the first line to lock this process, then let's make a series of judgments. The purpose of locking a process is to prevent other executors from changing the attributes of the process during the judgment. This is a good idea, but let's look at another function:

Void compute_creds (struct linux_binprm * bprm)

{

Kernel_cap_t new_permitted, working;

Int do_unlock = 0;

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 = 0;

Lock_kernel (); // only locks the kernel and does not lock the task. No one will wait as long as there is no thread competing with the kernel lock.

If (must_not_trace_exec (current) // pay attention to this function, as shown below. Set this location to

| Atomic_read (& Current-> FS-> count)> 1

| Atomic_read (& Current-> files-> count)> 1

| Atomic_read (& Current-> sig-> count)> 1 ){

If (! Capable (cap_setuid) {// do not use this if statement. Otherwise, the attack is more likely to succeed than the advantage.

Bprm-> e_uid = Current-> uid;

Bprm-> e_gid = Current-> GID;

}

...

}

Do_unlock = 1;

}

... // Note: You should set the IDs of the new process as follows. Set the following position to B.

Current-> SUID = Current-> EUID = Current-> fsuid = bprm-> e_uid;

Current-> SGID = Current-> EGID = Current-> fsgid = bprm-> e_gid;

If (do_unlock)

Unlock_kernel ();

Current-> keep_capabilities = 0;

}

Static inline int must_not_trace_exec (struct task_struct * P)

{

Return (p-> ptrace & pt_ptraced )&&! Cap_raised (p-> p_pptr-> cap_valid tive, cap_sys_ptrace );

}

Note that tasks are not protected between A and B. At least, ptrace is not mutually exclusive with ptrace. ptrace is designed for security reasons to be unable to track processes other than its user ID, for example, a common user process cannot trace the SUID process, but look at the 2.4.9 code. If ptrace comes in between A and B and wants to track the exec process, at this time, the exec process that is being tracked has not set a new EUID and EGID, so the ptrace process is easy to succeed and passes the long judgment in ptrace_attach, this allows a common user process to track a SUID process. You can use the ptrace interface to modify the memory of the SUID process to be tracked for injection. If a root shell is injected, then everything is successful! In later kernels, The compute_creds also used task_lock (task) to lock the process, so exec and ptrace cannot enter the competitive state at the same time. If Exec goes first, then, a series of ptrace_attach judgments will drive out attempted processes. If ptrace goes first, must_not_trace_exec will set the EUID of the new exec process to the current uid, thus reducing its permissions, take a closer look at must_not_trace_exec and find it also has a vulnerability. If the SUID program itself has a vulnerability, then! Cap_raised (p-> p_pptr-> cap_valid tive, cap_sys_ptrace) can be exploited to bypass bprm-> e_uid = Current-> uid, therefore, in later versions, the last condition of the function is removed. If a process is tracked, do not grant it the privilege as much as possible, unless its caller is a privileged program, no one can stop the Emperor's suicide.

Although the later versions added locks in two places, the granularity of locking a process is too large, so the kernel of 2.6 reduced the lock granularity in the later stages, the introduction of ptrace and exec exclusive private locks, so that the kernel efficiency will be higher, and operations irrelevant to the two will not be blocked by the lock.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.