# 添加每函数短路缓解原语 [LWN.net]
来源:https://lwn.net/ml/all/
[email protected]/
**邮件线索信息**\[搜索所有归档 (https://lwn.net/ml/all/)\]**发件人**:Sasha Levin **收件人**:corbet\-AT\-lwn\.net, akpm\-AT\-linux\-foundation\.org**主题**:\[PATCH\] killswitch: 添加每函数短路缓解原语**日期**:Thu, 07 May 2026 03:05:45 \-0400**Message\-ID**:<20260507070547\.2268452\-1\-sashal@kernel\.org\>**抄送**:skhan\-AT\-linuxfoundation\.org, linux\-doc\-AT\-vger\.kernel\.org, linux\-kernel\-AT\-vger\.kernel\.org, linux\-kselftest\-AT\-vger\.kernel\.org, gregkh\-AT\-linuxfoundation\.org, Sasha Levin
当某个(安全)问题公开披露后,在构建、分发并重启至已打补丁的内核之前,整个机群将持续暴露于风险之中。对于许多此类问题,最简单的缓解方案就是停止调用有漏洞的函数。Killswitch 正是为此而生。
管理员只需执行:
```
echo "engage af_alg_sendmsg -1" \
> /sys/kernel/security/killswitch/control
```
此后,`af_alg_sendmsg()` 在每次调用时均返回 `-EPERM`,而不执行其函数体。该缓解措施立即生效,并在下次重启时自动失效。
许多近期的内核问题都存在于以下代码路径中:这些功能在大多数部署中仅为少数用户启用,例如 AF_ALG、ksmbd、nf_tables、vsock、ax25 等。对大多数用户而言,"某个 socket 协议族停止工作一天"的代价,远小于在修复落地之前继续运行已知存在漏洞的内核的代价。
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Sasha Levin
---
```
Documentation/admin-guide/index.rst | 1 +
Documentation/admin-guide/killswitch.rst | 159 ++++
Documentation/admin-guide/tainted-kernels.rst| 8 +
MAINTAINERS | 11 +
include/linux/killswitch.h | 19 +
include/linux/panic.h | 3 +-
init/Kconfig | 2 +
kernel/Kconfig.killswitch | 31 +
kernel/Makefile | 1 +
kernel/killswitch.c | 798 ++++++++++++++++++
kernel/panic.c | 1 +
lib/Kconfig.debug | 13 +
lib/Makefile | 1 +
lib/test_killswitch.c | 85 ++
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/killswitch/.gitignore| 1 +
tools/testing/selftests/killswitch/Makefile | 8 +
.../selftests/killswitch/cve_31431_test.c | 162 ++++
.../selftests/killswitch/killswitch_test.sh | 147 ++++
19 files changed, 1451 insertions(+), 1 deletion(-)
create mode 100644 Documentation/admin-guide/killswitch.rst
create mode 100644 include/linux/killswitch.h
create mode 100644 kernel/Kconfig.killswitch
create mode 100644 kernel/killswitch.c
create mode 100644 lib/test_killswitch.c
create mode 100644 tools/testing/selftests/killswitch/.gitignore
create mode 100644 tools/testing/selftests/killswitch/Makefile
create mode 100644 tools/testing/selftests/killswitch/cve_31431_test.c
create mode 100755 tools/testing/selftests/killswitch/killswitch_test.sh
```
```diff
diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
index cd28dfe91b060..ca37dd70f108d 100644
--- a/Documentation/admin-guide/index.rst
+++ b/Documentation/admin-guide/index.rst
@@ -70,6 +70,7 @@ problems and bugs in particular.
bug-hunting
bug-bisect
tainted-kernels
+ killswitch
ramoops
dynamic-debug-howto
init
```
```diff
diff --git a/Documentation/admin-guide/killswitch.rst b/Documentation/admin-guide/killswitch.rst
new file mode 100644
index 0000000000000..cb967ec348fdc
--- /dev/null
+++ b/Documentation/admin-guide/killswitch.rst
@@ -0,0 +1,159 @@
+.. SPDX-License-Identifier: GPL-2.0
+..
+.. Copyright (C) 2026 Sasha Levin
+
+============
+Killswitch
+============
+
+Killswitch 允许特权操作员使指定的内核函数直接返回固定值而不执行其函数体,
+作为在准备真正修复方案期间针对安全漏洞的临时缓解措施。
+
+该函数返回操作员指定的值,且不执行任何替代逻辑。没有白名单,也不检查
+返回类型;只要 kprobe 层接受该符号,killswitch 即可介入。一旦介入,
+该变更将在所有 CPU 上持续生效,直到写入 ``disengage`` 或系统重启为止。
+
+配置
+============
+
+``CONFIG_KILLSWITCH``
+ 启用此功能。依赖 ``SECURITYFS``、``KPROBES``(需支持 ftrace)
+ 以及 ``FUNCTION_ERROR_INJECTION``。
+
+接口说明
+============
+
+::
+
+ /sys/kernel/security/killswitch/
+ engaged RO 当前已介入的函数列表
+ control WO 命令写入口
+ taint RO 0 或 1
+ fn/<symbol>/ 每函数目录,介入时创建
+ retval RW 返回值
+ hits RO 各 CPU 累计调用次数
+
+``control`` 接受三条命令::
+
+ engage <symbol> <retval>
+ disengage <symbol>
+ disengage_all
+
+每次介入和撤销介入都会向 dmesg 输出一行 ``KERN_WARNING`` 日志,包含
+符号名、返回值、命中次数(撤销时)以及操作员身份信息
+(uid/auid/sessionid/comm,或 ``source=cmdline``)。
+
+以下情况将拒绝介入:
+
+* 符号未知、位于不可追踪的节、在 kprobe 黑名单中,或被
+ ``register_kprobe`` 拒绝(kprobe 层的错误信息将记录日志并返回给用户空间);
+* 该符号已被介入(``-EBUSY``);
+* 操作员不具备 ``CAP_SYS_ADMIN`` 权限。
+
+操作员写入的值即为函数的返回值。写入错误的类型或错误的值,调用方将
+原样收到该值。
+
+启动参数
+============
+
+``killswitch=fn1=<retval1>,fn2=<retval2>,...``
+
+在早期阶段解析;介入操作在内核初始化结束、kprobe 子系统就绪后应用。
+解析失败时输出警告并跳过对应条目;不会触发 panic。
+
+适用于机群快速部署:当漏洞公开时,将缓解措施写入 bootloader / PXE
+配置,在准备真正修复方案期间滚动重启机群即可生效。
+
+内核污点标记
+============
+
+首次成功介入(运行时或启动时)将设置 ``TAINT_KILLSWITCH``(第 20 位,
+字符 ``H``)。该污点在 ``disengage`` 后仍持续存在直至重启,因此若
+在使用了 killswitch 的内核上发生 oops,可从 banner 中识别:
+``Tainted: ... H`` 提示维护者在进一步分析前先查阅 ``engaged``。
+
+模块卸载
+============
+
+若包含已介入目标函数的模块被卸载,killswitch 将自动撤销该条目的
+介入状态,并输出 ``KERN_WARNING`` 使缓解措施的失效可见。重新加载
+模块不会自动重新介入;操作员需手动重新执行 engage 操作。
+
+选择合适的目标
+========================
+
+看似可跳过的函数,其调用方可能依赖它产生某种副作用(调用方需要释放
+的锁、调用方需要减少的引用计数、调用方需要消费的 scatterlist)。
+经验法则如下:
+
+ 选取包含该漏洞的**最高层级**入口点。
+
+这样调用方就没有机会解引用函数体被跳过后处于半初始化状态的数据。
+以 ``crypto/af_alg.c`` 中的两个示例加以说明:
+
+反模式:``af_alg_count_tsgl``
+-------------------------------------------
+
+``af_alg_count_tsgl()`` 返回 ``unsigned int``(TX SG 条目数)。
+将其介入并设置返回值为 ``0``,会导致 ``algif_aead.c`` 中的调用方
+分配一个只有 1 个条目的 scatterlist(其 ``if (!entries) entries = 1``
+保护逻辑),然后通过 ``af_alg_pull_tsgl`` 将真实的 TX SGL 写入这个
+过小的目标,产生越界写入。**在此处介入 killswitch 引入的漏洞比
+正在缓解的漏洞更严重。**
+
+反模式:``af_alg_pull_tsgl``
+------------------------------------------
+
+``af_alg_pull_tsgl()`` 返回 ``void``,因此任何返回值都会被接受。
+但其调用方依赖于填充完成的每请求 SGL。跳过函数体后,每请求 SGL
+中的页指针为 NULL;下一阶段的 ``memcpy_sglist`` 会解引用这些 NULL
+指针,导致内核 oops。
+
+正确模式:``af_alg_sendmsg``
+-------------------------------------------
+
+``af_alg_sendmsg()`` 是进入 AF_ALG 发送路径的最高层级入口点。
+将其介入并设置返回值为 ``-EPERM``,会导致每次发送尝试都向用户空间
+返回 -EPERM;调用方永远不会看到半初始化的状态,并且在撤销介入之前,
+``sendmsg`` 下游所有可通过 AF_ALG 触达的漏洞均不可达。
+
+标准模式:选取一个形似 syscall 处理器的函数,其返回值本身就能表达
+"此操作未发生"的语义,让用户空间像处理其他失败的 syscall 一样处理
+该错误。
+
+安全说明
+============
+
+* 在向 ``control`` 执行 ``write()`` 期间,正在运行的调用可能执行
+ 原始函数体,也可能执行覆盖逻辑。覆盖逻辑即 ``return X``,没有任何
+ 前提条件需要满足。
+* SMP 可见性由 ``text_poke_bp()`` 保证。向 ``control`` 执行的
+ ``write()`` 仅在每个 CPU 都看到新路径后才返回。
+* ftrace ops 注销操作会等待正在执行的 pre-handler 完成,因此撤销
+ 介入时释放 engagement 属性是安全的。
+* 内联函数、已释放的 ``__init`` 符号以及所有被编译器消除的函数均无法
+ 被 killswitch 介入。``register_kprobe`` 会以 kprobe 层自行决定的
+ 错误码拒绝它们。
+
+诊断信息
+============
+
+每次调用的命中次数通过每 CPU 计数器累计,可在
+``/sys/kernel/security/killswitch/fn/<symbol>/hits`` 读取。
+不提供每次命中的日志记录,以避免在热路径上产生日志风暴。
+
+一旦有任何介入成功,内核污点向量中即出现 ``KILLSWITCH`` 条目
+(在 oops banner 中也可见 ``H``)。
```
```diff
diff --git a/Documentation/admin-guide/tainted-kernels.rst b/Documentation/admin-guide/tainted-kernels.rst
index 9ead927a37c0f..71a6e3364eddc 100644
--- a/Documentation/admin-guide/tainted-kernels.rst
+++ b/Documentation/admin-guide/tainted-kernels.rst
@@ -102,6 +102,7 @@ Bit Log Number Reason that got the kernel tainted
17 _/T 131072 内核使用结构体随机化插件构建
18 _/N 262144 已运行内核内测试
19 _/J 524288 用户空间在 fwctl 中使用了变更性调试操作
+ 20 _/H 1048576 killswitch 覆盖已介入(函数被短路)
=== === ====== ========================================================
注意:本表中字符 ``_`` 代表空格,以便阅读。
@@ -189,3 +190,10 @@ More detailed explanation for tainting
19) 若用户空间打开了 /dev/fwctl/* 并执行了 FWTCL_RPC_DEBUG_WRITE 以
使用设备调试功能,则标记 ``J``。设备调试功能可能以不可预期的方式
导致设备故障。
+
+ 20) 若 killswitch 原语(参见
+ Documentation/admin-guide/killswitch.rst)已在至少一个函数上介入,
+ 则标记 ``H``。内核不再按源码运行:至少有一个函数被短路,只返回
+ 固定值。该污点在 ``disengage`` 后持续存在直至下次重启——一旦运行
+ 中的内核镜像被修改,oops 分析必须将此情况纳入考量。
```
```diff
diff --git a/MAINTAINERS b/MAINTAINERS
index 882214b0e7db5..61851ef1d9b1c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14347,6 +14347,17 @@ F: lib/Kconfig.kmsan
F: mm/kmsan/
F: scripts/Makefile.kmsan
+KILLSWITCH(函数短路缓解)
+M: Sasha Levin <
[email protected]>
+L:
[email protected]
+S: Maintained
+F: Documentation/admin-guide/killswitch.rst
+F: include/linux/killswitch.h
+F: kernel/Kconfig.killswitch
+F: kernel/killswitch.c
+F: lib/test_killswitch.c
+F: tools/testing/selftests/killswitch/
+
KPROBES
M: Naveen N Rao <
[email protected]>
M: "David S. Miller" <
[email protected]>
```
```diff
diff --git a/include/linux/killswitch.h b/include/linux/killswitch.h
new file mode 100644
index 0000000000000..c18515bec09f1
--- /dev/null
+++ b/include/linux/killswitch.h
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2026 Sasha Levin <
[email protected]>
+ */
+#ifndef _LINUX_KILLSWITCH_H
+#define _LINUX_KILLSWITCH_H
+
+#ifdef CONFIG_KILLSWITCH
+int killswitch_engage(const char *symbol, long retval);
+int killswitch_disengage(const char *symbol);
+bool killswitch_is_engaged(const char *symbol);
+#else
+static inline int killswitch_engage(const char *symbol, long retval)
+{ return -ENOSYS; }
+static inline int killswitch_disengage(const char *symbol) { return -ENOSYS; }
+static inline bool killswitch_is_engaged(const char *symbol) { return false; }
+#endif
+
+#endif /* _LINUX_KILLSWITCH_H */
```
```diff
diff --git a/include/linux/panic.h b/include/linux/panic.h
index f1dd417e54b29..6699261a61f13 100644
--- a/include/linux/panic.h
+++ b/include/linux/panic.h
@@ -88,7 +88,8 @@ static inline void set_arch_panic_timeout(int timeout, int arch_default_timeout)
#define TAINT_RANDSTRUCT 17
#define TAINT_TEST 18
#define TAINT_FWCTL 19
-#define TAINT_FLAGS_COUNT 20
+#define TAINT_KILLSWITCH 20
+#define TAINT_FLAGS_COUNT 21
#define TAINT_FLAGS_MAX ((1UL << TAINT_FLAGS_COUNT) - 1)
struct taint_flag {
```
```diff
diff --git a/init/Kconfig b/init/Kconfig
index 2937c4d308aec..5368dd4b5c65b 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2278,6 +2278,8 @@ config ASN1
source "kernel/Kconfig.locks"
+source "kernel/Kconfig.killswitch"
+
config ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE
bool
```
```diff
diff --git a/kernel/Kconfig.killswitch b/kernel/Kconfig.killswitch
new file mode 100644
index 0000000000000..067d41087e8da
--- /dev/null
+++ b/kernel/Kconfig.killswitch
@@ -0,0 +1,31 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Killswitch:每函数短路缓解原语。
+#
+# Copyright (C) 2026 Sasha Levin <
[email protected]>
+#
+
+config KILLSWITCH
+ bool "Killswitch: 将内核函数短路作为 CVE 缓解手段"
+ depends on SECURITYFS
+ depends on KPROBES && HAVE_KPROBES_ON_FTRACE
+ depends on HAVE_FUNCTION_ERROR_INJECTION
+ select FUNCTION_ERROR_INJECTION
+ help
+ 提供一种面向管理员的机制,使指定的内核函数直接返回固定值而不执行
+ 其函数体,作为在真正修复可用之前针对安全漏洞的临时缓解手段。
+
+ 操作员向 /sys/kernel/security/killswitch/control 写入
+ "engage <symbol> <retval> [reason]"。函数入口通过 kprobe 重定向,
+ 其 pre-handler 设置所选返回值并短路调用。没有白名单、黑名单或
+ 返回类型校验:如果 kprobe 层接受该符号则介入成功,否则将其错误
+ 返回给用户空间。
+
+ 这 *不是* livepatch:没有替代实现,函数仅返回所选值。介入
+ killswitch 会污点标记内核(TAINT_KILLSWITCH,'H')。需要
+ CAP_SYS_ADMIN 权限。
+
+ 如不确定,请选 N。
```
```diff
diff --git a/kernel/Makefile b/kernel/Makefile
index 6785982013dce..b3e408d9f275e 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -100,6 +100,7 @@ obj-$(CONFIG_GCOV_KERNEL) += gcov/
obj-$(CONFIG_KCOV) += kcov.o
obj-$(CONFIG_KPROBES) += kprobes.o
obj-$(CONFIG_FAIL_FUNCTION) += fail_function.o
+obj-$(CONFIG_KILLSWITCH) += killswitch.o
obj-$(CONFIG_KGDB) += debug/
obj-$(CONFIG_DETECT_HUNG_TASK) += hung_task.o
obj-$(CONFIG_LOCKUP_DETECTOR) += watchdog.o
```
```diff
diff --git a/kernel/killswitch.c b/kernel/killswitch.c
new file mode 100644
index 0000000000000..6b3e2982e1c5c
--- /dev/null
+++ b/kernel/killswitch.c
@@ -0,0 +1,798 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 每函数短路缓解。
+ *
+ * Copyright (C) 2026 Sasha Levin <
[email protected]>
+ *
+ * 介入 killswitch 会在函数入口安装一个 kprobe,其 pre-handler 设置
+ * 返回寄存器并通过 override_function_with_return() 跳过函数体。
+ * 操作员接口位于 /sys/kernel/security/killswitch/。
+ */
+
+#include <linux/atomic.h>
+#include <linux/audit.h>
+#include <linux/ctype.h>
+#include <linux/init.h>
+#include <linux/kprobes.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/percpu.h>
+#include <linux/refcount.h>
+#include <linux/security.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/uidgid.h>
+#include <linux/panic.h>
+#include <linux/killswitch.h>
+
+struct ks_attr {
+ struct list_head list;
+ struct kprobe kp;
+ atomic_long_t retval;
+ /* 撤销介入后置为 false;每函数文件操作随即返回 -EIDRM */
+ bool engaged;
+ unsigned long __percpu *hits;
+ struct dentry *dir;
+ /* engaged_list 持有一个引用;每个打开的每函数 fd 持有一个引用 */
+ refcount_t refcnt;
+};
+
+static DEFINE_MUTEX(ks_lock);
+static LIST_HEAD(ks_engaged_list);
+static struct dentry *ks_root_dir;
+static struct dentry *ks_fn_dir; /* 每函数目录的父目录 */
+
+/* ------------------------------------------------------------------ *
+ * Pre-handler:实际的覆盖逻辑 *
+ * ------------------------------------------------------------------ */
```