zoukankan      html  css  js  c++  java
  • Linux操作系统--进程/线程(2)

    前言

    在本系列的上一篇博文里,我已经介绍了进程/线程的基本含义以及一些相关数据结构,现在我们来看看Linux中进程的管理。

    进程链表

    Linux内核定义了一个list_head结构,数据结构定义

    struct list_head {
    	struct list_head *next;
    	struct list_head *prev;
    };
    

    字段next 和 prev 分别表示通用双向链表向前和向后的指针元素!list_head字段的指针中存放的是另一个list_head字段的元素,而不是本身的数据结构地址。如图

    在我们上一篇博客介绍到的进程描述符(task_struct)也有这个结构体,称为进程链表。进程链表是一个双向循环链表,它把所有进程的描述符链接起来。每个task_struct结构都包含一个list_head类型的字段tasks,这个结构的prev和next分别指向前面和后面的task_struct元素。

    这个链表是一个循环的双向链表,开始的时候只有init_task这一个进程,它是内核的第一个进程,它的初始化是通过静态分配内存,"手动"(其它的进程初始化都是通过动态分配内存初始化的)进行的,每新建一个进程,就通过SET_LINKS宏将该进程的task_struct结构加入到这条双向链表中,不过要注意的是如果一个进程新建一个线程(不包括主线程),也就是轻量级进程,它是不会加入该链表的。通过宏for_each_process可以从init_task开始遍历所有的进程。

    #define for_each_task(p)
    for (p = &init_task ; (p = p->next_task) != &init_task ; )
    

    可运行队列(runqueue)

    当内核寻找一个新进程在CPU上运行时,必须只考虑可运行进程(即处在TASK_RUNNING状态的进程)。把可运行状态的进程组成一个双向循环链表,也叫可运行队列(runqueue)。
    在task_struct结构中定义了两个指针。

    struct task_struct *next_run, *prev_run;
    

    由正在运行或是可以运行的,其进程状态均为TASK_RUNNING的进程所组成的一个双向循环链表,即run_queue就绪队列。该链表的前后向指针用next_run和prev_run,链表的头和尾都是init_task(即0号
    进程)。
    但是,为了实现在固定的时间内选出“最佳”的可运行程序,内核将可运行进程的优先级划分为0-139,并为此建立了140个可运行进程链表,用以组织处于TASK_RUNNING状态的进程,每个进程优先权对应一个不同的链表
    linux内核定义了一个prio_array_t类型的结构体来管理这140个链表。每个可运行的进程都在这140个链表中的一个,通过进程描述符结构中的run_list来实现,它也是一个list_head类型。enqueue_task是把进程描述符插入到某个可运行链表中,dequeue_task则从某个可运行链表中删除该进程描述符。TASK_RUNNING状态的prio_array_t类型的结构体是runqueue结构的arrays[1]成员。

    pidhash链表

    为了通过pid找到进程的描述符,如果直接遍历进程间互联的链表来查找进程id为pid的进程描述符显然是低效的,所以为了更为高效的查找,linux内核使用了4个hash散列表来加快查找,之所以使用4个散列表,是为了能根据不同的pid类型来查找进程描述符,它们分别是进程的pid,线程组领头进程的pid,进程组领头进程的pid,会话领头进程的pid。每个类型的散列表中是通过宏pid_hashfn(x)来进行散列值的计算的。每个进程都可能同时处于这是个散列表中,所以在进程描述符中有一个类型为pid结构的pids成员,通过它可以将进程加入散列表中,pid结构中包含解决散列冲突的pid_chain成员,它是hlist_node类型的,还有一个是将相同pid链起来的pid_list,它是list_head类型。

    struct pid_link {
        int nr;  // pid的数值
        struct hlist_node pid_chain;
        struct list_head pid_list;
    }
    
    struct task_struct {
        …
        struct pid_link pids[4];
        …
    }
    

    Linux 进程安全上下文 struct cred

    内核2.6,定义一个新的 struct task_security_struct,然后挂接到task_struct的void *security指针上,但是,内核3.x 在task_struct找不到security成员了,原来是将安全相关的信息剥离到一个叫做 cred 的结构体中,由cred负责保存进程安全上下文

    The security context of a task
       95  *
       96  * The parts of the context break down into two categories:
       97  *
       98  *  (1) The objective context of a task.  These parts are used when some other
       99  *      task is attempting to affect this one.
      100  *
      101  *  (2) The subjective context.  These details are used when the task is acting
      102  *      upon another object, be that a file, a task, a key or whatever.
      103  *
      104  * Note that some members of this structure belong to both categories - the
      105  * LSM security pointer for instance.
      106  *
      107  * A task has two security pointers.  task->real_cred points to the objective
      108  * context that defines that task's actual details.  The objective part of this
      109  * context is used whenever that task is acted upon.
      110  *
      111  * task->cred points to the subjective context that defines the details of how
      112  * that task is going to act upon another object.  This may be overridden
      113  * temporarily to point to another security context, but normally points to the
      114  * same context as task->real_cred.
      115  */
      116 struct cred {
      117         atomic_t        usage;
      118 #ifdef CONFIG_DEBUG_CREDENTIALS
      119         atomic_t        subscribers;    /* number of processes subscribed */
      120         void            *put_addr;
      121         unsigned        magic;
      122 #define CRED_MAGIC      0x43736564
      123 #define CRED_MAGIC_DEAD 0x44656144
      124 #endif
      125         uid_t           uid;            /* real UID of the task */
      126         gid_t           gid;            /* real GID of the task */
      127         uid_t           suid;           /* saved UID of the task */
      128         gid_t           sgid;           /* saved GID of the task */
      129         uid_t           euid;           /* effective UID of the task */
      130         gid_t           egid;           /* effective GID of the task */
      131         uid_t           fsuid;          /* UID for VFS ops */
      132         gid_t           fsgid;          /* GID for VFS ops */
      133         unsigned        securebits;     /* SUID-less security management */
      134         kernel_cap_t    cap_inheritable; /* caps our children can inherit */
      135         kernel_cap_t    cap_permitted;  /* caps we're permitted */
      136         kernel_cap_t    cap_effective;  /* caps we can actually use */
      137         kernel_cap_t    cap_bset;       /* capability bounding set */
      138 #ifdef CONFIG_KEYS
      139         unsigned char   jit_keyring;    /* default keyring to attach requested
      140                                          * keys to */
      141         struct key      *thread_keyring; /* keyring private to this thread */
      142         struct key      *request_key_auth; /* assumed request_key authority */
      143         struct thread_group_cred *tgcred; /* thread-group shared credentials */
      144 #endif
      145 #ifdef CONFIG_SECURITY
      146         void            *security;      /* subjective LSM security */
      147 #endif
      148         struct user_struct *user;       /* real user ID subscription */
      149         struct user_namespace *user_ns; /* cached user->user_ns */
      150         struct group_info *group_info;  /* supplementary groups for euid/fsgid */
      151         struct rcu_head rcu;            /* RCU deletion hook */
      152 };
    

    正如uid,euid的关系一样,task_struct也有两种身份cred

    struct task_struct{
     ...
     /* process credentials */
     const struct cred __rcu *real_cred; /* objective and real subjective task credentials (COW) */
     const struct cred __rcu *cred;  /* effective (overridable) subjective task credentials (COW) */
     ...
     }
    

    这里详细说明以下这个安全上下文的作用。
    linux系统中,一个对象操作另一个对象时通常要做安全性检查。如一个进程操作一个文件,要检查进程是否有权限操作该文件。
    linux内核中,credential机制的引入,正是对象间访问所需权限的抽象;主体提供自己权限的证书,客体提供访问自己所需权限的证书,根据主客体提供的证书及操作做安全性检查。
    证书管理术语:
    客体:指用户空间程序直接可以操作的系统对象,如进程、文件、消息队列、信号量、共享内存等;每个客体都有一组凭证,每种客体有不同的凭证集
    客体所有者:客体凭证集有一部分表示客体所有者;如文件中uid表示文件的所有者
    主体:操作客体的对象;除进程外大多数系统对象都不是主体,但在特殊环境下某些对象是主体,如文件在设置F_SETOWN后可以发送SIGIO信号到进程,这时文件就是主体,进程就是客体
    行为:主体怎样操作客体,如读写执行文件等
    客体上下文:客体被访问时所需权限凭证集
    主体上下文:主体的权限凭证集
    规则:主体操作客体时,用于安全检查
    当主体操作客体时,根据主体上下文、客体上下文、操作来做安全计算,查找规则看主体是否有权限操作客体。
    进程描述符中cred和real_cred字段分别指向主体与客体的证书

    • usage:表于证书引用管理
    • uid:实际用户id(real UID of the task,进程真正的uid,即为创建该进程的用户的uid)
    • gid:实际用户组id
    • suid:保存的用户uid(saved UID of the task,保留的UID,例如,当一个特权进程需要临时降低其权限时,将其euid更改为非特权的UID,然后将原来的EUID保存到SUID,当需要恢复权限时,将EUID改为SUID中保存的UID即可)
    • sgid;保存的用户组gid
    • euid:真正有效的用户id(effective UID of the task,有效的UID,用于进程访问资源时的访问检查,大多数情况下,EUID是同于UID的,但是也可以不同,或者说动态获取的ID)
    • egid:真正有效的用户组id
    • securebits:安全管理标识;用来控制凭证的操作与继承
    • cap_inheritable:execve时可以继承的权限
    • cap_permitted:可以(通过capset)赋予cap_effective的权限
    • cap_effective:进程实际使用的权限
    • cap_bset:主要用于uid=0或euid=0时,execve可以继承的权限,cap_permitted=cap_inheritable+cap_bset,cap_effective=cap_permitted。可以将cap_bset中的权限通过调用capset赋给cap_inheritable
    • user:主要表示用户信息,如用户进程数、打开文件数等
    • rcu:RCU删除用

    struct cred在kernel pwn的利用

    注:笔者还没有学习内核pwn的相关知识,所以这里只是简单介绍一下cred这个结构体在内核pwn中提权的作用,没有具体例子说明
    可以通过执行commit_creds(prepare_kernel_cred(0))来获得root权限(root的uid、gid均为0)
    源码如下:

    /* /kernel/cred.c */
    /**
     * prepare_kernel_cred - Prepare a set of credentials for a kernel service
     * @daemon: A userspace daemon to be used as a reference
     *
     * Prepare a set of credentials for a kernel service.  This can then be used to
     * override a task's own credentials so that work can be done on behalf of that
     * task that requires a different subjective context.
     *
     * @daemon is used to provide a base for the security record, but can be NULL.
     * If @daemon is supplied, then the security data will be derived from that;
     * otherwise they'll be set to 0 and no groups, full capabilities and no keys.
     *
     * The caller may change these controls afterwards if desired.
     *
     * Returns the new credentials or NULL if out of memory.
     *
     * Does not take, and does not return holding current->cred_replace_mutex.
     */
    struct cred *prepare_kernel_cred(struct task_struct *daemon)
    {
    	const struct cred *old;
    	struct cred *new;
    
    	new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
    	if (!new)
    		return NULL;
    
    	kdebug("prepare_kernel_cred() alloc %p", new);
    
    	if (daemon)
    		old = get_task_cred(daemon);
    	else
    		old = get_cred(&init_cred);
    
    	validate_creds(old);
    
    	*new = *old;
    	new->non_rcu = 0;
    	atomic_set(&new->usage, 1);
    	set_cred_subscribers(new, 0);
    	get_uid(new->user);
    	get_user_ns(new->user_ns);
    	get_group_info(new->group_info);
    
    #ifdef CONFIG_KEYS
    	new->session_keyring = NULL;
    	new->process_keyring = NULL;
    	new->thread_keyring = NULL;
    	new->request_key_auth = NULL;
    	new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
    #endif
    
    #ifdef CONFIG_SECURITY
    	new->security = NULL;
    #endif
    	if (security_prepare_creds(new, old, GFP_KERNEL) < 0)
    		goto error;
    
    	put_cred(old);
    	validate_creds(new);
    	return new;
    
    error:
    	put_cred(new);
    	put_cred(old);
    	return NULL;
    }
    EXPORT_SYMBOL(prepare_kernel_cred);
    

    prepare_kernel_cred()
    根据源码注释中的描述,这个函数返回一个cred结构体,可以用于代替进程原来的cred以便能够完成需要不同subjective context的任务。如果提供了参数@daemon,那么security data将来源于此,而这个参数也可为空,然后内容字段会被设置成0(uid/gid都是0,就是root权限咯?)

    /* /kernel/cred.c */
    /**
     * commit_creds - Install new credentials upon the current task
     * @new: The credentials to be assigned
     *
     * Install a new set of credentials to the current task, using RCU to replace
     * the old set.  Both the objective and the subjective credentials pointers are
     * updated.  This function may not be called if the subjective credentials are
     * in an overridden state.
     *
     * This function eats the caller's reference to the new credentials.
     *
     * Always returns 0 thus allowing this function to be tail-called at the end
     * of, say, sys_setgid().
     */
    int commit_creds(struct cred *new)
    {
    	struct task_struct *task = current;
    	const struct cred *old = task->real_cred;
    
    	kdebug("commit_creds(%p{%d,%d})", new,
    	       atomic_read(&new->usage),
    	       read_cred_subscribers(new));
    
    	BUG_ON(task->cred != old);
    #ifdef CONFIG_DEBUG_CREDENTIALS
    	BUG_ON(read_cred_subscribers(old) < 2);
    	validate_creds(old);
    	validate_creds(new);
    #endif
    	BUG_ON(atomic_read(&new->usage) < 1);
    
    	get_cred(new); /* we will require a ref for the subj creds too */
    
    	/* dumpability changes */
    	if (!uid_eq(old->euid, new->euid) ||
    	    !gid_eq(old->egid, new->egid) ||
    	    !uid_eq(old->fsuid, new->fsuid) ||
    	    !gid_eq(old->fsgid, new->fsgid) ||
    	    !cred_cap_issubset(old, new)) {
    		if (task->mm)
    			set_dumpable(task->mm, suid_dumpable);
    		task->pdeath_signal = 0;
    		/*
    		 * If a task drops privileges and becomes nondumpable,
    		 * the dumpability change must become visible before
    		 * the credential change; otherwise, a __ptrace_may_access()
    		 * racing with this change may be able to attach to a task it
    		 * shouldn't be able to attach to (as if the task had dropped
    		 * privileges without becoming nondumpable).
    		 * Pairs with a read barrier in __ptrace_may_access().
    		 */
    		smp_wmb();
    	}
    
    	/* alter the thread keyring */
    	if (!uid_eq(new->fsuid, old->fsuid))
    		key_fsuid_changed(task);
    	if (!gid_eq(new->fsgid, old->fsgid))
    		key_fsgid_changed(task);
    
    	/* do it
    	 * RLIMIT_NPROC limits on user->processes have already been checked
    	 * in set_user().
    	 */
    	alter_cred_subscribers(new, 2);
    	if (new->user != old->user)
    		atomic_inc(&new->user->processes);
    	rcu_assign_pointer(task->real_cred, new);
    	rcu_assign_pointer(task->cred, new);
    	if (new->user != old->user)
    		atomic_dec(&old->user->processes);
    	alter_cred_subscribers(old, -2);
    
    	/* send notifications */
    	if (!uid_eq(new->uid,   old->uid)  ||
    	    !uid_eq(new->euid,  old->euid) ||
    	    !uid_eq(new->suid,  old->suid) ||
    	    !uid_eq(new->fsuid, old->fsuid))
    		proc_id_connector(task, PROC_EVENT_UID);
    
    	if (!gid_eq(new->gid,   old->gid)  ||
    	    !gid_eq(new->egid,  old->egid) ||
    	    !gid_eq(new->sgid,  old->sgid) ||
    	    !gid_eq(new->fsgid, old->fsgid))
    		proc_id_connector(task, PROC_EVENT_GID);
    
    	/* release the old obj and subj refs both */
    	put_cred(old);
    	put_cred(old);
    	return 0;
    }
    EXPORT_SYMBOL(commit_creds);
    

    根据源码注释的描述,这个函数会将当前进程的real_cred和cred都设置成一组新的cred。
    综上,通过prepare_kernel_cred(0)获得一个root的cred,然后再用commit_creds()将其安装到当前进程,即commit_creds(prepare_kernel_cred(0)),这样就可以提权啦!

  • 相关阅读:
    Oracle-学习笔记(==》集合函数与分组四)
    Mysql--学习笔记(==》简单查询三)
    Mysql-学习笔记(==》插入修改数据二)
    Mysql-学习笔记(==》建表修改一)
    EasyUI的DataGrid 打印导出
    SQL 中ROLLUP 用法
    easyui commbox嵌入一个checkbox的实现
    Easyui Layout Center 全屏方法扩展
    Datagrid扩展方法InitEditGrid{支持单元格编辑}
    Datagrid扩展方法onClickCell{easyui-datagrid-扩充-支持单元格编辑}
  • 原文地址:https://www.cnblogs.com/T1e9u/p/13658686.html
Copyright © 2011-2022 走看看