浅析一下用来修改当前进程 cred 的函数 commit_creds

源码版本:Linux kernel 5.9.9

首先来看 cred 结构

/*

* The security context of a task

*

* The parts of the context break down into two categories:

*

* (1) The objective context of a task. These parts are used when some other

*task is attempting to affect this one.

*

* (2) The subjective context. These details are used when the task is acting

*upon another object, be that a file, a task, a key or whatever.

*

* Note that some members of this structure belong to both categories - the

* LSM security pointer for instance.

*

* A task has two security pointers. task->real_cred points to the objective

* context that defines that task's actual details. The objective part of this

* context is used whenever that task is acted upon.

*

* task->cred points to the subjective context that defines the details of how

* that task is going to act upon another object. This may be overridden

* temporarily to point to another security context, but normally points to the

* same context as task->real_cred.

*/

struct cred {

atomic_tusage;

#ifdef CONFIG_DEBUG_CREDENTIALS

atomic_tsubscribers;/* number of processes subscribed 使用这个 cred 的进程数*/

void*put_addr;

unsignedmagic;

#define CRED_MAGIC0x43736564

#define CRED_MAGIC_DEAD0x44656144

#endif

kuid_tuid;/* real UID of the task 创建进程的用户的 id ,不是创建可执行程序的用户 id*/

kgid_tgid;/* real GID of the task */

kuid_tsuid;/* saved UID of the task 保存的 euid 切换之前的 id,用于 euid 切换回来*/

kgid_tsgid;/* saved GID of the task */

kuid_teuid;/* effective UID of the task euid 是进程运行过程中实时的 id*/

kgid_tegid;/* effective GID of the task */

kuid_tfsuid;/* UID for VFS ops */

kgid_tfsgid;/* GID for VFS ops */

unsignedsecurebits;/* SUID-less security management */

kernel_cap_tcap_inheritable; /* caps our children can inherit */

kernel_cap_tcap_permitted;/* caps we're permitted */

kernel_cap_tcap_effective;/* caps we can actually use */

kernel_cap_tcap_bset;/* capability bounding set */

kernel_cap_tcap_ambient;/* Ambient capability set */

#ifdef CONFIG_KEYS

unsigned charjit_keyring;/* default keyring to attach requested

* keys to */

struct key*session_keyring; /* keyring inherited over fork */

struct key*process_keyring; /* keyring private to this process */

struct key*thread_keyring; /* keyring private to this thread */

struct key*request_key_auth; /* assumed request_key authority */

#endif

#ifdef CONFIG_SECURITY

void*security;/* subjective LSM security */

#endif

struct user_struct *user;/* real user ID subscription 创建进程的用户的 id 描述符*/

struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */

struct group_info *group_info;/* supplementary groups for euid/fsgid */

/* RCU deletion */

union {

int non_rcu;/* Can we skip RCU deletion? */

struct rcu_headrcu;/* RCU deletion hook */

};

} __randomize_layout;

commit_creds

/**

* 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)

{

// 获取当前进程的 task_struct

struct task_struct *task = current;

// 保存当前进程的 real_cred

const struct cred *old = task->real_cred;

kdebug("commit_creds(%p{%d,%d})", new,

atomic_read(&new->usage),

read_cred_subscribers(new));

// cred != real_cred 通常这个两个是一样的 当进程试图对一个其他对象(文件,进程,或者任何东西)进行操作的时候就是访问 real_cred

BUG_ON(task->cred != old);

#ifdef CONFIG_DEBUG_CREDENTIALS

// 使用 task->real_cred 的进程数小于 2,也就是说只能有一个进程使用这个 cred(real_cred 也是 cred 结构)

BUG_ON(read_cred_subscribers(old) < 2);

// 检查 cred 有没有被破坏,其实就是检查 cred 的魔数头 magic 字段是不是 CRED_MAGIC(默认 magic 是 CRED_MAGIC 值: 0x43736564),如果不是则认为 cred 可能被内存溢出覆盖

validate_creds(old);

validate_creds(new);

#endif

// 对 new 的引用数不小于 1

BUG_ON(atomic_read(&new->usage) < 1);

// get_cred 这个函数会先用 validate_creds 检查 cred 是否有效,

// 然后把 non_rcu 置 0

// 然后让 usage 加 1 表示引用这个 cred 的进程数 加 1

get_cred(new); /* we will require a ref for the subj creds too */

/* dumpability changes */

// 检查当前进程的 real_cred 和 new cred(要修改成的那个 cred)的各个字段是否一样,_eq 结尾的函数其实就是 比较两个值是否相等

if (!uid_eq(old->euid, new->euid) || // 检查 uid 是否相等

!gid_eq(old->egid, new->egid) || // 检查 egid 是否相等

!uid_eq(old->fsuid, new->fsuid) || // 检查 fsuid 是否相等

!gid_eq(old->fsgid, new->fsgid) || // 检查 fsgid 是否相等

!cred_cap_issubset(old, new)) { // 检查 new cres 的 namespace 是不是 old cred 的 namespace 的子集

// 如果当前进程的 mm_struct 不是 NULL

if (task->mm)

set_dumpable(task->mm, suid_dumpable); // 设置 mm_struct 的 flag,加上 suid_dumpable 标志,表示 接受到 coredump 信号时生成 coredump

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().

*/

// sfence 内存屏障

smp_wmb();

}

/* alter the thread keyring */

// 如果 new cred 的文件系统的 uid 和 gid 和目前进程的文件系统的 uid 和 gid 不一样则

if (!uid_eq(new->fsuid, old->fsuid))

key_fsuid_changed(new); // 更新 new 的 thread_keyring->uid 为 new->fsuid

if (!gid_eq(new->fsgid, old->fsgid))

key_fsgid_changed(new); // // 更新 new 的 thread_keyring->gid 为 new->fsgid

/* do it

* RLIMIT_NPROC limits on user->processes have already been checked

* in set_user().

*/

// new 的订阅进程 ubscribers 加 2

alter_cred_subscribers(new, 2);

// 如果 new cres 和 old cred 所属的 用户 不一样(对,就是你理解的系统里面的那个用户,每个 uid 就是一个 用户),user 是一个 user_struct,每个用户都有一个,里面记录的 processes 表示这个用户有多少个进程

if (new->user != old->user)

atomic_inc(&new->user->processes); // 既然 old cred 和 new cred 不是属于同一个用户,那么当前进程 使用 new cred 的时候 cred 对应的用户所有的进程数肯定要加 1(如果有点绕,仔细想想就能想通了)

// cred 和 real_cred 是 rcu 变量,是个指针,所以需要用 rcu_assign_pointer 去更新

rcu_assign_pointer(task->real_cred, new); // task->real_cred = new

rcu_assign_pointer(task->cred, new); // task->cred = new

// 这里检查有没有设置成功,因为 old 是指向当前进程的 real_cred 的,上面我们更新了 real_cred 为 new,所以这两个是一样的现在,都是指向 new cred

// 如果没有更新成功

if (new->user != old->user)

atomic_dec(&old->user->processes); // 用户进程数 减 1 ,因为上面我们加 1

// 操作结束 new 的订阅进程 ubscribers 减 2(或者说是加上 -2),对应上面那个加 2

alter_cred_subscribers(old, -2);

/* send notifications */

// 现在检查各个 uid 字段,还不一样就见鬼了

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 */

// 释放 old cred

put_cred(old); // 对 old cred 的引用 减 1

put_cred(old); // 对 old cred 的引用 减 1

return 0;

}

EXPORT_SYMBOL(commit_creds);

get_cred

/**

* get_cred - Get a reference on a set of credentials

* @cred: The credentials to reference

*

* Get a reference on the specified set of credentials. The caller must

* release the reference. If %NULL is passed, it is returned with no action.

*

* This is used to deal with a committed set of credentials. Although the

* pointer is const, this will temporarily discard the const and increment the

* usage count. The purpose of this is to attempt to catch at compile time the

* accidental alteration of a set of credentials that should be considered

* immutable.

*/

static inline const struct cred *get_cred(const struct cred *cred)

{

struct cred *nonconst_cred = (struct cred *) cred;

// 检查是不是 cred 一个有效的地址

if (!cred)

return cred;

// 验证 cred 的 magic

validate_creds(cred);

nonconst_cred->non_rcu = 0;

// usage 字段加 1

return get_new_cred(nonconst_cred);

}

get_new_cred

/**

* get_new_cred - Get a reference on a new set of credentials

* @cred: The new credentials to reference

*

* Get a reference on the specified set of new credentials. The caller must

* release the reference.

*/

static inline struct cred *get_new_cred(struct cred *cred)

{

// usage 字段加 1

atomic_inc(&cred->usage);

return cred;

}

cred_cap_issubset

static bool cred_cap_issubset(const struct cred *set, const struct cred *subset)

{

// 获取 cred 的 namespace

const struct user_namespace *set_ns = set->user_ns;

const struct user_namespace *subset_ns = subset->user_ns;

/* If the two credentials are in the same user namespace see if

* the capabilities of subset are a subset of set.

*/

// 如果这两个 cred 位于相同的 namespace

if (set_ns == subset_ns)

return cap_issubset(subset->cap_permitted, set->cap_permitted);

/* The credentials are in a different user namespaces

* therefore one is a subset of the other only if a set is an

* ancestor of subset and set->euid is owner of subset or one

* of subsets ancestors.

*/

// 遍历 namespace

for (;subset_ns != &init_user_ns; subset_ns = subset_ns->parent) {

// 如果 old cred 的 namespace 是 new cred 的 namespace 的先祖,并且 new 的 namespace 的实际所有者是 ord

if ((set_ns == subset_ns->parent) &&

uid_eq(subset_ns->owner, set->euid))

return true; // 也可以判定 new cred 的 namespace 是 ord cred 的 namespace 的子集

}

return false; // 如果遍历完所有的 namespace 没有符合的,说明 new cred 的 namespace 不是 old cred 的 namespace 的子集

}

put_cred

/**

* put_cred - Release a reference to a set of credentials

* @cred: The credentials to release

*

* Release a reference to a set of credentials, deleting them when the last ref

* is released. If %NULL is passed, nothing is done.

*

* This takes a const pointer to a set of credentials because the credentials

* on task_struct are attached by const pointers to prevent accidental

* alteration of otherwise immutable credential sets.

*/

static inline void put_cred(const struct cred *_cred)

{

struct cred *cred = (struct cred *) _cred;

if (cred) {

// 验证 cred 没有被破坏

validate_creds(cred);

// usage 减 1,如果 usage 为 0 则条件为真陷入 if

if (atomic_dec_and_test(&(cred)->usage))

__put_cred(cred); // 因为 usage 为 0,表示没有进程在使用这个 cred,直接销毁 cred

}

}

__put_cred

/**

* __put_cred - Destroy a set of credentials

* @cred: The record to release

*

* Destroy a set of credentials on which no references remain.

*/

void __put_cred(struct cred *cred)

{

kdebug("__put_cred(%p{%d,%d})", cred,

atomic_read(&cred->usage),

read_cred_subscribers(cred));

// 再次检查要销毁的 cred 的 usage

BUG_ON(atomic_read(&cred->usage) != 0);

#ifdef CONFIG_DEBUG_CREDENTIALS

// 检查查要销毁的 cred 的 subscribers

BUG_ON(read_cred_subscribers(cred) != 0);

// 把 cred 的 magic 更改成 CRED_MAGIC_DEAD 表示 cred 不可用

cred->magic = CRED_MAGIC_DEAD;

cred->put_addr = __builtin_return_address(0);

#endif

// 要销毁的 cred 当然不能是当前进程使用的 cred

BUG_ON(cred == current->cred);

BUG_ON(cred == current->real_cred);

// 如果是使用 RCU deletion hook 的话 ,则可以直接调用 put_cred_rcu 函数

if (cred->non_rcu)

put_cred_rcu(&cred->rcu);

else

call_rcu(&cred->rcu, put_cred_rcu); // 不然需要使用 call_rcu 去找 put_cred_rcu 函数( rcu 函数是串在一条 RCU deletion hook 链表上每个节点都是一个 rcu_head )(大概是这样的,我也没深究,其实还是调用 put_cred_rcu 函数,反正就是在申请 cred 的时候有没有设置 hook 了,设置了可以直接调用,不然要使用 call_rcu 去找,毕竟 cred 是 rcu 变量,需要特定的方式去销毁)

}

EXPORT_SYMBOL(__put_cred);

其实后面的也没什么好分析的了

void security_cred_free(struct cred *cred)

{

/*

* There is a failure case in prepare_creds() that

* may result in a call here with ->security being NULL.

*/

if (unlikely(cred->security == NULL))

return;

call_void_hook(cred_free, cred);

kfree(cred->security);

cred->security = NULL;

}

/*

* The RCU callback to actually dispose of a set of credentials

*/

static void put_cred_rcu(struct rcu_head *rcu)

{

// 通过 rcu 字段的地址去找包含这个 rcu 的 cred 结构,这个 container_of 实际很巧妙我以前分析过就不展开了

struct cred *cred = container_of(rcu, struct cred, rcu);

kdebug("put_cred_rcu(%p)", cred);

#ifdef CONFIG_DEBUG_CREDENTIALS

if (cred->magic != CRED_MAGIC_DEAD ||

atomic_read(&cred->usage) != 0 ||

read_cred_subscribers(cred) != 0)

panic("CRED: put_cred_rcu() sees %p with"

" mag %x, put %p, usage %d, subscr %d\n",

cred, cred->magic, cred->put_addr,

atomic_read(&cred->usage),

read_cred_subscribers(cred));

#else

// 检查 usage,还有进程使用这个 cred 直接就 panic

if (atomic_read(&cred->usage) != 0)

panic("CRED: put_cred_rcu() sees %p with usage %d\n",

cred, atomic_read(&cred->usage));

#endif

// 使用 kfree 释放 cred->security,并置 cred->security 为 NULL,防止 UAF

security_cred_free(cred);

// 先检查 keyring 的有效性,然后让 keyring 的 usage 减 1,跟 cred 一样,如果 usage 为 0 ,则销毁 keyring,因为 keyring 可以被多个 cred 使用(一个 keyring 对应多个 cred),所以才会有一个 usage 字段,现在销毁 这个 cred 如果是 最后一个使用这个 keyring 的,则销毁 cred 后销毁 keyring

key_put(cred->session_keyring);

key_put(cred->process_keyring);

key_put(cred->thread_keyring);

key_put(cred->request_key_auth);

// 一样,对 group_info 的 usage 减 1,跟 cred 一样,如果 usage 为 0 ,则销毁 group_info,因为 group_info 可以被多个 cred 使用(一个 group_info 对应多个 cred),所以才会有一个 usage 字段。。。。。。跟上面的 keyring 一样

if (cred->group_info)

put_group_info(cred->group_info);

// 释放 user_struct

free_uid(cred->user);

// 跟上面的 keyring group_info 一样

put_user_ns(cred->user_ns);

// 完成这些工作后,把 cred 放入 cred_jar,因为 cred 是个高频使用的数据结构,所以不是释放内存,而是把 cred 放入 一个缓存 cred_jar

kmem_cache_free(cred_jar, cred);

}

现在快凌晨 4 点了,困死了

# r00t @ FakeLinux in ~ [3:53:16]

$ date

Tue 23 Feb 2021 03:53:19 AM CST

后面的 检查释放 keyring,group_info,user_ns 不想一句一句分析代码了,道理都一样,先引用计数器减 1,为 0 说明这个结构没有在使用,就释放掉

over!

标签:task,old,struct,--,rcu,源码,Linux,new,cred

来源: https://www.cnblogs.com/crybaby/p/14433930.html

linux内核态real cred,Linux内核源码分析 -- 更新当前进程的 cred -- commit_creds相关推荐

  1. Linux中mknod命令实现原理以及源码分析

    本篇文章以mknod创建字符设备文件进行讲解 字符设备驱动的Demo例子可参考该篇文章 Linux 编写简单驱动并测试 1. mknod 命令 mknod /dev/hello c 520 0 该命令 ...

  2. Linux内核学习笔记——内核页表隔离KPTI机制(源码分析)

    KPTI(Kernel PageTable Isolation)全称内核页表隔离,它通过完全分离用户空间与内核空间页表来解决页表泄露. KPTI中每个进程有两套页表--内核态页表与用户态页表(两个地址 ...

  3. 【分析笔记】Linux gpio_wdt.c 看门狗设备驱动源码分析

    基本原理 该看门狗的设备驱动实现原理很简单,比较主要的有两点: 一.定时器喂狗 通过定时器根据配置文件配置的喂狗方式(如脉冲切换.电平切换),对指定的 gpio 进行脉冲切换或电平切换实现喂狗. 脉冲 ...

  4. Linux项目实战C++轻量级Web服务器源码分析TinyWebServer

    目录 文章简介 一. 先跑起来项目 二.再看项目核心 三.逐个击破!立下flag 文章简介 TinyWebServer是Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的 ...

  5. android6.0源码分析之Zygote进程分析

    在android6.0源码分析之Runtime的初始化一文中,对Zygote进程的初期的Runtime初始化过程进行了分析,在Runtime启动结束后,会对Zygote进程进行初始化,其它Java进程 ...

  6. 【Linux 内核 内存管理】mmap 系统调用源码分析 ④ ( do_mmap 函数执行流程 | do_mmap 函数源码 )

    文章目录 一.do_mmap 函数执行流程 二.do_mmap 函数源码 调用 mmap 系统调用 , 先检查 " 偏移 " 是否是 " 内存页大小 " 的 & ...

  7. Linux下存储多路径软件MultiPath源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 全局概览 测试环境为CentOS 7 X64 从RPM获取源码 $ cd ~/rpmbuild/ $ yumdownloade ...

  8. Linux设备驱动简析—PC重启源码分析

    Linux在PC上的关机和重启可能由两种行为引发,一是通过用户编程,一是系统自己产生的消息.用户和系统进行交互的方式也有两个,一个是系统调用:sys_reboot,另一个就是apm或acpi的设备文件 ...

  9. Linux内核源码分析《进程管理》

    Linux内核源码分析<进程管理> 前言 1. Linux 内核源码分析架构 2. 进程原理分析 2.1 进程基础知识 2.2 Linux进程四要素 2.3 进程描述符 task_stru ...

最新文章

  1. 廊坊学院报名计算机二级,2021上半年廊坊市计算机二级报名时间|网上报名入口【1月6日开通】...
  2. Websocket实现Java后台主动推送消息到前台
  3. 测试mysql连接服务器_实现服务器与数据库的连接
  4. python元组添加元素_2分钟学会Python的元组使用
  5. zookeeper简单命令
  6. 移动开发--移动web特别样式处理
  7. 多目标跟踪全解析,全网最全
  8. 暂别ACM,转移阵地
  9. MySQL入门学习笔记
  10. LDA算法——线性判别
  11. 人性的弱点 - 把握人际交往的关键
  12. 【多校联赛】The Crime-solving Plan of Groundhog
  13. Android HAL层
  14. VMware ESXi添加NVMe硬盘扩容
  15. (解决)org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)问题的办法
  16. opengl编程基础篇
  17. 8种基本数据类型的分类
  18. 人工智能改变人力资源的11种方式
  19. Javascript函数内静态变量与实例变量
  20. android listview替代,Android笔记——RecyclerView替代ListView

热门文章

  1. web、app跳转微信支付解决方案
  2. linux中管道的概念,浅谈Linux管道
  3. 在字节跳动干了 2 年后端开发,太真实…
  4. 看苹果Metal支持c++开发
  5. 面试宝典之高分回答面试题(一)
  6. 12小时制(AM PM)字符串转换为24时制
  7. storm trident mysql,Storm Trident(一)官方Tutorial
  8. 《MongoDB》 数组操作
  9. Go 每日一库之 testify
  10. [kernel exploit] Dirty Cred: 一种新的无地址依赖漏洞利用方案