Black Hat USA 2026:Linux容器错误取反
原创
Max Luo
Max Luo
白帽子罗棋琛
2026年8月26日 08:18
广东
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
一个错误取反,如何击穿 Linux 与托管容器
Black Hat USA 2026 议题《!secure: A Single Wrong Negation to Root Linux and Escape Managed Kubernetes》课件解读。本文依据公开课件、Linux 修复提交与发行版公告整理,讨论 CVE-2026-23111 的根因、风险判断和防守落地;不复现完整利用代码。
安全团队经常把内核漏洞按“是否有竞态、是否能稳定提权、容器里是否可达”来分级。CVE-2026-23111 恰好把这三道筛选都绕了过去:它不依赖争抢时间窗,触发条件来自一次可重复的事务回滚;在允许非特权用户命名空间和 nf\_tables 的环境里,本地普通用户可以把引用计数稳定耗尽;而容器与宿主机共享内核,默认 Pod 也可能把同一原语带到节点上。
最值得复盘的不是后半段的堆布局,而是最前面的一个 !:一段看上去与 deactivate 路径“完全对称”的代码,实际破坏了 abort 必须恢复所有副作用的约束。
1、先看结论:这是事务一致性漏洞,不只是一个 UAF
漏洞位于 Linux Netfilter 的 nf\_tables 子系统,函数为 nft_map_catchall_activate()。公开修复只改了一个布尔判断:
diff
static void nft_map_catchall_activate(...) { list_for_each_entry(catchall, &set->catchall_list, list) { ext = nft_set_elem_ext(set, catchall->elem); - if (!nft_set_elem_active(ext, genmask))+ if (nft_set_elem_active(ext, genmask)) continue; nft_clear(ctx->net, ext); nft_setelem_data_activate(ctx->net, set, &elem); } }
这行代码处在失败事务的回滚路径。删除 set 时,catch-all verdict element 会被暂时停用,并释放其指向 chain 的一次引用;如果同一批事务随后失败,abort 路径必须重新激活该元素,同时把引用补回来。
错误判断却跳过了 inactive 元素。结果不是“状态显示错了一下”,而是对象仍被引用、引用计数却少了一次。重复失败事务可把 chain->use 降到零,之后删除 chain 会释放对象,但 catch-all element 仍保留指向它的指针,由此形成 use-after-free。
Ubuntu 将 CVE-2026-23111 定为 High,CVSS 3.1 为 7.8,并明确描述为:在启用 CONFIG_USER_NS 和 CONFIG_NF_TABLES 的发行版上,非特权本地用户可通过 user namespace 与 nftables 实现提权。这个前提比“所有 Linux 都能打”精确得多。
2、为什么复制对称代码反而制造了错误
课件把 activate 与 deactivate 放在一起,对代码评审很有启发。
nft_set_elem_active() 的语义容易读反:genmask 对应位为 clear 时元素处于 active,函数返回 true;对应位为 set 时处于 inactive,函数返回 false。
c
/* 用命名变量展开,避免在条件中二次取反 */constbool is_active = nft_set_elem_active(ext, genmask); /* deactivate:只处理 active 元素 */if (!is_active) continue; deactivate_element(ext); /* activate/rollback:只处理 inactive 元素 */if (is_active) continue; activate_element(ext);
问题不只是开发者“少看了一个感叹号”。这里至少叠加了三种可维护性风险:
- 函数名表达状态查询,底层实现却又对位掩码取反;
- activate 与 deactivate 的循环骨架相似,复制后很容易保留同一条件;
- 正常提交路径容易覆盖,失败事务的补偿路径却很少进入常规测试。
这类代码应按状态转换来审,而不是逐行看“像不像”。更稳妥的评审问题是:进入回滚前后,每个对象的 active 状态、引用计数和可达关系是否恢复为同一组不变量?
3、确定性来自“计数循环”,不是高超的竞态技巧
很多内核 UAF 需要让释放与重用在很窄的时间窗相遇。这个漏洞的第一阶段不同:每次构造“先删除 set、后让批事务失败”的序列,回滚就稳定漏掉一次引用恢复。攻击者不是等待概率,而是在执行一个有限循环。
可以把根因抽象成下面的状态机。这里故意不保留真实 Netlink 消息细节,只展示防守方需要验证的不变量:
python
from dataclasses import dataclass @dataclassclassModel: element_active: bool = True chain_refs: int = 1defprepare_delete(m: Model) -> None: assert m.element_active and m.chain_refs > 0 m.element_active = False m.chain_refs -= 1defabort_delete(m: Model) -> None: # 正确回滚:inactive 元素必须重新激活并恢复引用if m.element_active: return m.element_active = True m.chain_refs += 1definvariant(m: Model) -> None: # 仍有活动元素指向 chain 时,引用数不能为零if m.element_active: assert m.chain_refs >= 1deftest_abort_is_idempotent() -> None: m = Model() before = Model(m.element_active, m.chain_refs) for _ inrange(1000): prepare_delete(m) abort_delete(m) invariant(m) assert m == before
这个测试比“执行一次删除是否成功”有价值,因为它直接检查重复 abort 后的守恒关系。课件展示的 UAF 时间线本质上就是这条不变量被破坏后的对象生命周期。
4、为什么普通用户和默认 Pod 能碰到内核 Netfilter
漏洞可利用性取决于入口是否可达。普通进程通常没有宿主网络命名空间里的 CAP_NET_ADMIN,但某些发行版允许非特权用户创建 user namespace,再在新的 network namespace 中获得相对于该 namespace 的管理能力。这样,进程便可能合法调用 nf\_tables 接口。
容器场景也不能只看 Pod spec 中有没有 privileged: true。需要同时回答四个问题:
- 容器的 seccomp profile 是否允许创建相关 namespace 的系统调用;
- 节点是否允许非特权 user namespace;
- 容器进程能否到达 nf\_tables 所需的内核配置和模块;
- 节点实际运行的 kernel flavor 是否已经包含修复。
课件中的攻击模型是“已取得普通 Pod 内代码执行”,而不是从互联网直接打节点,也不是只凭创建一个 Pod 就自动逃逸。
可以用准入策略先压缩明显不必要的 namespace 面。下面示例只允许受控 namespace 中、带例外标签的工作负载使用显式 Unconfined seccomp;生产规则还应加入镜像身份、ServiceAccount 与变更单约束。
rego
package kubernetes.admission deny[msg] { input.request.kind.kind == \"Pod\" c := input.request.object.spec.containers[_] not c.securityContext.seccompProfile.type msg := sprintf(\"container %q must declare a seccomp profile\", [c.name]) } deny[msg] { input.request.kind.kind == \"Pod\" c := input.request.object.spec.containers[_] c.securityContext.seccompProfile.type == \"Unconfined\" input.request.object.metadata.labels[\"security.example.com/seccomp-exception\"] != \"approved\" msg := sprintf(\"container %q requests unapproved seccomp Unconfined\", [c.name]) }
5、利用工程的意义:逐层缓解都没有修复对象生命周期
公开课件描述了 KASLR、随机 kmalloc cache、RETHUNK、SMEP/SMAP、AppArmor 等缓解措施,以及研究者如何分别处理这些障碍。本文不展开可复用的地址计算、堆喷布局和调用链参数,但防守方必须理解一个事实:这些机制提高了利用成本,没有把错误引用关系变回正确关系。
以 CONFIG_RANDOM_KMALLOC_CACHES 为例,它按分配调用点映射随机子缓存,能破坏很多“从另一个调用点喷同尺寸对象”的传统堆布局。不过映射是按 callsite 稳定的;如果研究者能找到经过同一分配路径的对象,仍有机会落入相同子缓存。
RETHUNK 则把编译器生成的 ret 替换为返回跳板,主要约束经典的 return-oriented programming。课件转而利用内核已有的前向间接调用路径组织 call-oriented programming。这再次说明,控制流缓解的安全结论必须写成“约束了哪些边”,而不是笼统地写成“阻止代码执行”。
课件对各层缓解的总结很完整,但从防守工程角度还要补一列:这些缓解能否切断入口、能否阻止对象释放、能否阻止控制流接管,以及失效后是否有告警。否则一张“已启用缓解”清单很容易变成错误的安全证明。
6、容器逃逸不是“容器毫无安全性”,而是边界选错了
namespace、cgroup、capability、LSM 与 seccomp 能显著缩小进程权限和攻击面,但普通 OCI 容器仍与节点共享内核。只要某条路径允许容器内进程到达有漏洞的内核子系统,内核提权就可能跨过 namespace 隔离。
课件展示的后续目标包括进入宿主 mount namespace、访问节点文件与 kubelet 材料。真正需要被纳入风险模型的不是“攻击者得到容器内 uid 0”,而是以下资产是否与发生内核失陷的节点同生共死:
- 同节点其他 Pod 的 Secret、服务账号令牌与应用数据;
- kubelet 客户端证书、bootstrap 配置和云实例身份;
- 宿主机容器运行时 socket、镜像缓存和审计配置;
- 节点可访问的控制面、元数据服务与东西向网段。
如果业务确实执行不可信租户代码,边界应上移到独立内核:gVisor 通过用户态内核减少直接触达宿主 syscall 的范围;Kata Containers 等 microVM 方案把工作负载放进独立 guest kernel。它们仍需更新与隔离设计,但故障域不再等同于宿主共享内核。选择哪一种,要结合 syscall 兼容性、I/O 性能、启动延迟和租户风险,而不是一句“全部改 microVM”。
7、补丁核查必须识别 kernel flavor,不能只比版本字符串
截至 2026 年 8 月 10 日,Canonical 的 CVE 页面显示:Ubuntu 24.04 linux 已在 6.8.0-107.107 修复,linux-azure 为 6.8.0-1052.58,linux-gcp 为 6.8.0-1053.56,linux-gke 为 6.8.0-1049.54。同一发行版的不同 flavor 使用不同 ABI 和包版本;此外该页面仍列出少数 flavor 为 Vulnerable 或 work in progress。运维脚本不能把“数字看起来更大”当作修复证据。
下面的采集脚本只生成事实清单,不擅自判断所有衍生内核是否安全。判定基线应来自发行版公告或云厂商节点镜像公告,并在每次同步时记录来源时间。
bash
#!/usr/bin/env bashset -euo pipefail printf'host=%s\
'\"$(hostname)\"printf'kernel_release=%s\
'\"$(uname -r)\"printf'kernel_version=%s\
'\"$(uname -v)\"printf'os_release=%s\
'\"$(. /etc/os-release; printf '%s %s' \"$ID\"\"$VERSION_ID\")\" dpkg-query -W \\ -f='package=${binary:Package}\\tversion=${Version}\\tstatus=${db:Status-Abbrev}\
' \\ 'linux-image-*' 2>/dev/null | awk '$0 ~ /ii $/' || true sysctl kernel.unprivileged_userns_clone 2>/dev/null || true sysctl kernel.apparmor_restrict_unprivileged_userns 2>/dev/null || true sysctl kernel.apparmor_restrict_unprivileged_unconfined 2>/dev/null || true
集群侧至少要同时导出节点镜像、kernel、运行时与可调度工作负载:
bash
kubectl get nodes -o json | jq -r ' .items[] | [ .metadata.name, .status.nodeInfo.kernelVersion, .status.nodeInfo.osImage, .status.nodeInfo.containerRuntimeVersion, (.metadata.labels[\"kubernetes.azure.com/node-image-version\"] // \"-\"), (.metadata.labels[\"cloud.google.com/gke-nodepool\"] // \"-\") ] | @tsv'
发布判定建议采用精确映射,而不是自行解析任意版本:
yaml
advisory:CVE-2026-23111checked_at:\"2026-08-10T00:00:00Z\"source:\"https://ubuntu.com/security/CVE-2026-23111\"minimum_fixed:ubuntu-24.04/linux:\"6.8.0-107.107\"ubuntu-24.04/linux-azure:\"6.8.0-1052.58\"ubuntu-24.04/linux-gcp:\"6.8.0-1053.56\"ubuntu-24.04/linux-gke:\"6.8.0-1049.54\"unknown_flavor_policy:block-and-reviewreboot_required:true
注意:软件包已安装不等于正在运行的内核已切换。升级后必须滚动重启或重建节点,再以 uname -r 和节点镜像版本回证。
8、SeccompDefault、Pod Security 与 AppArmor 应该怎样落地
课件建议启用 SeccompDefault、Restricted Pod Security,并收紧 Ubuntu AppArmor 的非特权 unconfined 行为。方向正确,但“启用 SeccompDefault 就杀掉所有 userns-based exploits”过于绝对。
Kubernetes 文档明确说明 RuntimeDefault 由容器运行时定义;不同运行时、版本和平台的默认 syscall 列表可能不同。因此,安全团队应把预期写成测试,而不是配置截图:
yaml
apiVersion:v1kind:Podmetadata:name:userns-negative-testnamespace:security-verificationspec:restartPolicy:NeverautomountServiceAccountToken:falsesecurityContext:seccompProfile:type:RuntimeDefaultcontainers:-name:probeimage:registry.example.com/security/userns-probe@sha256:REPLACE_MEargs: [\"--assert-userns-denied\", \"--assert-netlink-nft-denied\"] securityContext:allowPrivilegeEscalation:falserunAsNonRoot:truerunAsUser:65532capabilities:drop: [\"ALL\"] readOnlyRootFilesystem:true
探针应只验证系统调用和 Netlink 访问是否被拒绝,不创建真实 nftables 对象,更不触发漏洞。节点池升级、containerd 更新或 profile 变更后自动重跑。对于不能阻断 user namespace 的兼容性场景,至少保证修复内核已运行,并把高风险租户放入独立内核沙箱。
Pod Security restricted 能统一约束 privileged、host namespace、capabilities、seccomp 等常见风险项,但它不是内核补丁,也不会自动隔离共享内核。AppArmor sysctl 同样是纵深措施:变更前应盘点依赖非特权 user namespace 的浏览器沙箱、构建工具与桌面应用,先在专用节点池灰度。
9、检测重点不是猜 exploit,而是发现入口与失败事务异常
公开利用链细节可能变体很多,生产检测不应绑定某个进程名或固定次数。更可操作的三层信号是:
第一层看暴露面:哪些节点允许非特权 user namespace,哪些 Pod 使用 Unconfined seccomp,哪些节点仍运行受影响 flavor。第二层看行为:非网络管理工作负载是否创建 user/network namespace,是否异常高频访问 NETLINK_NETFILTER。第三层看结果:kernel warning、refcount 异常、nf\_tables UAF、节点突然重启、容器进入宿主 namespace 等。
Falco 风格规则可以先抓明显偏离基线的 namespace 行为。字段支持情况随版本与驱动而变,上线前需用本环境的 falco --list 校验:
yaml
-rule:UnexpectedUserNamespaceCreationInWorkloaddesc:非构建类容器尝试创建usernamespacecondition:> container and evt.type in (unshare, clone, clone3) and evt.dir = < and not proc.name in (buildkitd, rootlesskit) and not k8s.ns.name in (ci-builders, sandbox-lab) output:> user namespace related syscall in workload (proc=%proc.name cmd=%proc.cmdline container=%container.id pod=%k8s.pod.name ns=%k8s.ns.name) priority:WARNINGtags: [container, kernel, userns]
内核日志则要进入节点级采集,而不是只收容器 stdout:
yaml
sources:-journald:units: [kernel] matches:-\"general protection fault\"-\"use-after-free\"-\"refcount_t:\"-\"nf_tables\"-\"KASAN:\"pipeline:enrich: [node_name, kernel_release, node_image, cluster, cloud_provider] route:critical_when:-contains_any: [\"use-after-free\", \"KASAN:\"] -all_of: [\"nf_tables\", \"general protection fault\"] response:-cordon_node-snapshot_volatile_telemetry-preserve_cloud_audit_logs-replace_node_from_known_good_image
不要在发现内核内存破坏后尝试“清理并继续运行”。节点完整性已经无法证明,安全动作应是隔离、保全证据、轮换节点可接触的凭据,然后从可信镜像重建。
10、把一个 ! 变成可执行的内核安全门禁
课件最后用“一行 diff 泄露完整研究路径”解释补丁部署窗口的危险。对安全工程团队而言,更重要的是把同类错误提前拦在代码和交付链里。
第一道门是代码结构:避免在关键状态判断上叠加否定,显式命名 is_active;activate/deactivate 不要只靠复制的循环骨架表达对称性。第二道门是模型测试:每个 prepare/commit/abort 序列都要验证引用计数、列表可达性和状态位守恒,并对重复 abort 做 property-based 测试。第三道门是内核动态检测:在测试内核上启用 KASAN、KFENCE、KCSAN 与 refcount 检查,使用 syzkaller 一类状态化模糊测试覆盖失败事务,而不是只 fuzz 单条合法消息。
一个适合 CI 的回滚属性可以写成:
rust
proptest! { #[test]fnaborted_transaction_restores_observable_state( ops inarb_reversible_operation_sequence() ) { letmut store = ModelStore::new(); letbefore = store.snapshot(); lettx = store.prepare(&ops)?; store.abort(tx)?; prop_assert_eq!(store.snapshot(), before); prop_assert!(store.references_are_balanced()); prop_assert!(store.no_dangling_edges()); } }
第四道门是资产交付:以发行版和云厂商公告维护 flavor-aware 修复矩阵;节点升级后重启回证;例外节点设置到期时间。第五道门才是运行时缓解:默认 seccomp、Restricted Pod Security、LSM、最小 capability 与独立内核沙箱共同缩小爆炸半径。
CVE-2026-23111 的真正警示不是“一字符可以攻破一切”这种口号,而是三个具体工程事实:失败路径也是安全边界;引用计数必须用状态不变量验证;共享内核场景下,Pod 权限低不等于内核攻击面不可达。补丁是当前漏洞的终点,这三条才是下一类漏洞的起点。
资料来源
- Black Hat 官方 Session 页面
- Ubuntu:CVE-2026-23111
- Kubernetes:Restrict a Container's Syscalls with seccomp
- Kubernetes:Linux kernel security constraints for Pods and containers
原始会议材料(仓库内)
- 演讲课件 PDF
开源资料与原始议题 PDF
本文对应的 Markdown 原稿、Black Hat 原始议题 PDF 与配图已整理到 GitHub,可按文章编号查找和下载。
https://github.com/cybermaxluo/black-hat-usa-2026-talks
也可以点击文末“阅读原文”进入仓库。欢迎 Star、提交 Issue 或参与勘误。
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论