Linux Killswitch: Disable Vulnerable Kernel Functions Without Rebooting (2026)
What Is Linux Killswitch and Why It Matters
Linux Killswitch is a kernel primitive proposed by Sasha Levin (Microsoft/kernel.org) in May 2026 that lets a system administrator immediately disable a specific vulnerable kernel function—returning -EPERM to all callers—without building a new kernel, deploying a package, or rebooting the host. It solves the "patch-reboot gap": the window between a CVE going public and your fleet running a patched kernel. This tool is for security engineers, kernel developers, and SRE teams who operate Linux fleets where unplanned reboots are costly or where exposure windows must be measured in minutes rather than hours.
The Patch-Reboot Gap: The Core Problem Killswitch Solves
When a critical kernel vulnerability is disclosed, the typical remediation flow looks like this: the CVE is published, your security team triages it, a patched kernel is built (or pulled from a distro repo), the package is deployed to the fleet, a maintenance window is scheduled, and finally machines reboot into the fixed kernel. Each of those steps takes time. On a well-run enterprise fleet, the total elapsed time from CVE disclosure to every host running a patched kernel is rarely under four hours and commonly spans days for systems with strict change-management requirements.
| Stage | Typical Duration | |---|---| | CVE triage and severity assessment | 30–120 minutes | | Patched kernel available from distro | 2–48 hours | | Package rollout to fleet | 15–60 minutes | | Scheduled reboot window | 2–24 hours | | Total exposure window | ~4 hours to 3+ days | | Killswitch engagement | < 5 seconds |
How Traditional Kernel Vulnerability Mitigation Works (and Where It Fails)
Traditional mitigations fall into a few buckets: kernel live patching (kpatch, livepatch), disabling the affected kernel module via modprobe -r, or applying a seccomp/AppArmor/SELinux policy to block the specific syscall. Each has gaps. Live patching requires a pre-built patch binary for your exact kernel version—unavailable on day zero. Module removal only works if the vulnerability lives in a loadable module, not in the core kernel. Syscall-level blocking via seccomp or LSM policy is coarse-grained and often blocks legitimate workloads along with the vulnerable path.
Which Kernel Subsystems Benefit Most from Killswitch
The patch specifically calls out AF_ALG (kernel crypto API exposed via sockets), ksmbd (in-kernel SMB server), nf_tables (netfilter's nftables engine), vsock (VM socket), and ax25 (amateur radio networking). What these have in common is that they're active by default on many distributions but used by a small fraction of workloads. The operational cost of disabling af_alg_sendmsg for a few hours on a fleet where no application actually uses kernel-side crypto acceleration is effectively zero—while the security benefit is immediate and complete.
Core Concepts: How Killswitch Works Under the Hood
The Short-Circuit Mechanism: Intercepting Function Calls
Killswitch inserts a check at the entry point of annotated kernel functions. When a function is marked as killswitch-eligible (via a macro in include/linux/killswitch.h), its prologue is modified to consult the killswitch state table before executing any of the function body. If the function's killswitch is engaged, execution returns immediately with the configured return code—-EPERM by default, though the control interface lets you specify any int value. The check is implemented entirely in kernel/killswitch.c and adds negligible overhead in the disengaged state.
The securityfs Control Interface
Killswitch exposes its control plane at /sys/kernel/security/killswitch/control. This file accepts plain-text commands:
engage <function_name> <return_code>— arms the killswitch for the named functiondisengage <function_name>— removes the killswitch for the named function
Reading the control file lists all currently engaged killswitches, one per line, formatted as <function_name> <return_code>. The securityfs filesystem must be mounted (it is by default on all modern distributions at /sys/kernel/security). Writing to the control file requires CAP_SYS_ADMIN.
Return Code Semantics: Why -EPERM and What Callers See
The default return code of -EPERM (operation not permitted, errno 1) is a deliberate choice. Most kernel function call sites that handle errors treat -EPERM as a clean, non-fatal rejection. Using -EFAULT or -ENOMEM could trigger misleading error paths in userspace or in the kernel's own error-handling logic. You can specify any integer return value in the engage command—-ENOSYS (-38) is another reasonable choice for socket family operations since it signals "function not implemented" to userspace, which many applications handle gracefully.
Reboot-Reset Guarantee: Why Mitigations Are Not Permanent
All engaged killswitches are stored only in kernel memory. On reboot, the state table is empty and all previously annotated functions run normally. This is a safety feature, not a limitation: it prevents a misconfigured killswitch from permanently disabling critical functionality after an unattended restart. It also means killswitches must be re-engaged after every reboot via an init script, systemd service, or configuration management system until a proper patch is deployed.
Engaging a killswitch sets the TAINT_KILLSWITCH kernel taint flag, visible in /proc/sys/kernel/tainted and reported by dmesg. The Documentation/admin-guide/tainted-kernels.rst file was updated by the patch to document this taint. When filing a kernel bug report on a system with an active killswitch, kernel maintainers will see the taint flag and may ask you to reproduce without the killswitch engaged.
CONFIG_KILLSWITCH: The Kconfig Option
Killswitch support is gated by CONFIG_KILLSWITCH=y in the kernel Kconfig (defined in kernel/Kconfig.killswitch). In-kernel test support requires an additional CONFIG_TEST_KILLSWITCH=y option defined in lib/Kconfig.debug. Both options default to off in upstream, so you need to enable them explicitly in your kernel build config.
Quick Start: Enabling and Using Killswitch on a Running Kernel
Kernel Build Requirements
You need a kernel built with CONFIG_KILLSWITCH=y. Check your running kernel:
# Check if your running kernel was built with killswitch support
zcat /proc/config.gz 2>/dev/null | grep -E 'CONFIG_KILLSWITCH|CONFIG_SECURITYFS'
# Or from the installed config
grep -E 'CONFIG_KILLSWITCH|CONFIG_SECURITYFS' /boot/config-$(uname -r)
If you need to build from source:
# Add to your .config or use menuconfig
scripts/config --enable CONFIG_KILLSWITCH
scripts/config --enable CONFIG_SECURITYFS
make olddefconfig
make -j$(nproc) bzImage
Verifying Killswitch Is Available
# Prerequisites checklist
# 1. Kernel supports it
ls /sys/kernel/security/killswitch/control && echo "killswitch: available" || echo "killswitch: NOT available"
# 2. securityfs is mounted
mountpoint /sys/kernel/security || mount -t securityfs securityfs /sys/kernel/security
# 3. You have CAP_SYS_ADMIN (run as root or with the capability)
capsh --print | grep sys_admin
Engaging a Killswitch on a Specific Function
# Engage the killswitch on af_alg_sendmsg, returning -EPERM (-1) to all callers
echo 'engage af_alg_sendmsg -1' > /sys/kernel/security/killswitch/control
# Verify it took effect by reading back the active mitigations list
cat /sys/kernel/security/killswitch/control
Expected output:
af_alg_sendmsg -1
Confirming the Mitigation Is Active
# Check dmesg for confirmation
dmesg | tail -5
[ 1234.567890] killswitch: engaged af_alg_sendmsg (return -1)
[ 1234.567891] killswitch: kernel taint flag TAINT_KILLSWITCH set
# Confirm taint flag is set (bit value for TAINT_KILLSWITCH, check tainted-kernels.rst for exact bit)
cat /proc/sys/kernel/tainted
Disengaging Before Reboot
# Disengage when your patched kernel is ready and you're about to reboot
echo 'disengage af_alg_sendmsg' > /sys/kernel/security/killswitch/control
# Confirm it's cleared
cat /sys/kernel/security/killswitch/control
# (empty output means no active killswitches)
Use Case 1: Immediate CVE Mitigation for AF_ALG Socket Vulnerabilities
Identifying the Vulnerable Function and Applying a Safe Stop-Gap
When a CVE advisory drops for AF_ALG (the kernel's crypto API socket family), it typically names the exact vulnerable function—af_alg_sendmsg, af_alg_accept, or similar. Your first instinct might be to modprobe -r af_alg, but that fails if any process has an open socket. Killswitch sidesteps that: it doesn't remove the function, it just makes it return an error, so no unhealthy teardown of existing socket state occurs.
Before engaging, you need to know whether anything on this host actively uses AF_ALG. The following script checks for live users, logs the action to syslog, and supports a --force flag to engage unconditionally:
#!/usr/bin/env bash
# engage_af_alg_killswitch.sh
# Usage: ./engage_af_alg_killswitch.sh [--force]
# Requires: root / CAP_SYS_ADMIN, ss, lsof, logger
set -euo pipefail
CONTROL=/sys/kernel/security/killswitch/control
FUNC=af_alg_sendmsg
RETCODE=-1
FORCE=false
if [[ "${1:-}" == "--force" ]]; then
FORCE=true
fi
# Verify killswitch interface is available
if [[ ! -w "$CONTROL" ]]; then
echo "ERROR: $CONTROL is not writable. Is CONFIG_KILLSWITCH enabled and are you root?" >&2
exit 1
fi
# Check if already engaged
if grep -q "^${FUNC} " "$CONTROL" 2>/dev/null; then
echo "INFO: Killswitch for $FUNC is already engaged."
logger -t killswitch "INFO: $FUNC already engaged, no action taken"
exit 0
fi
# Detect active AF_ALG socket users
ALG_USERS=$(ss -a --family=alg 2>/dev/null | grep -v '^Netid' | wc -l || true)
LSOF_USERS=$(lsof -nP 2>/dev/null | grep -c 'AF_ALG\|alg:' || true)
if [[ "$ALG_USERS" -gt 0 || "$LSOF_USERS" -gt 0 ]]; then
echo "WARNING: Detected $ALG_USERS AF_ALG sockets and $LSOF_USERS lsof entries."
ss -a --family=alg 2>/dev/null || true
if [[ "$FORCE" != true ]]; then
echo "Aborting. Use --force to engage anyway."
logger -t killswitch "WARN: AF_ALG users detected, engagement aborted (use --force)"
exit 2
fi
echo "--force specified. Engaging despite active users."
fi
# Engage the killswitch
echo "engage ${FUNC} ${RETCODE}" > "$CONTROL"
TIMESTAMP=$(date -Iseconds)
logger -t killswitch "ENGAGED: ${FUNC} return=${RETCODE} at ${TIMESTAMP} by $(whoami)"
echo "SUCCESS: Killswitch engaged for $FUNC. Active mitigations:"
cat "$CONTROL"
This script mirrors the approach in the patch's cve_31431_test.c selftest: check state before acting, then verify state after acting. The --force path is your emergency override when the CVE severity justifies accepting application disruption.
Use Case 2: Fleet-Wide Mitigation with Ansible
Propagating a Killswitch Across Many Hosts Simultaneously
Engaging a killswitch on a single host takes five seconds. Across a thousand hosts, you want idempotency, audit logging, and no wasted writes. The following Ansible task block handles all three:
---
# roles/kernel_killswitch/tasks/engage.yml
# Variables expected:
# killswitch_function: "af_alg_sendmsg"
# killswitch_retcode: "-1"
# killswitch_cve: "CVE-2026-31431"
- name: "Verify killswitch control interface is present"
ansible.builtin.stat:
path: /sys/kernel/security/killswitch/control
register: ks_control_stat
failed_when: not ks_control_stat.stat.exists
- name: "Read current killswitch state"
ansible.builtin.slurp:
src: /sys/kernel/security/killswitch/control
register: ks_current_state
- name: "Set fact: is function already engaged"
ansible.builtin.set_fact:
ks_already_engaged: >
{{ (ks_current_state.content | b64decode)
is search('^' + killswitch_function + ' ', multiline=True) }}
- name: "Engage killswitch (skipped if already active)"
ansible.builtin.shell: |
echo 'engage {{ killswitch_function }} {{ killswitch_retcode }}' \
> /sys/kernel/security/killswitch/control
when: not ks_already_engaged
register: ks_engage_result
- name: "Record audit timestamp fact"
ansible.builtin.set_fact:
ks_audit_record:
function: "{{ killswitch_function }}"
retcode: "{{ killswitch_retcode }}"
cve: "{{ killswitch_cve }}"
engaged_at: "{{ ansible_date_time.iso8601 }}"
host: "{{ inventory_hostname }}"
already_was_active: "{{ ks_already_engaged }}"
when: ks_control_stat.stat.exists
- name: "Write audit record to /var/log/killswitch-audit.log"
ansible.builtin.lineinfile:
path: /var/log/killswitch-audit.log
line: >-
{{ ks_audit_record | to_json }}
create: true
mode: '0640'
when: not ks_already_engaged
- name: "Verify engagement via dmesg"
ansible.builtin.shell: dmesg | grep 'killswitch.*{{ killswitch_function }}' | tail -1
register: ks_dmesg_confirm
changed_when: false
- name: "Display confirmation"
ansible.builtin.debug:
msg: "{{ ks_dmesg_confirm.stdout }}"
The idempotency check (ks_already_engaged) uses b64decode because Ansible's slurp module base64-encodes file contents. The audit record written to /var/log/killswitch-audit.log gives you a JSON line per engagement event—importable into Splunk, Elastic, or any SIEM.
A note on kernel taint: once any killswitch is engaged, the TAINT_KILLSWITCH bit is set and remains set until reboot. Your monitoring system should alert on unexpected kernel taint changes. The tainted-kernels.rst documentation updated in the patch describes the exact bit position.
Use Case 3: Writing a Kernel Module That Uses Killswitch
Using the killswitch.h Header in Kernel Code
If you're a kernel developer adding a new subsystem or backporting killswitch support to an existing function, the API lives in include/linux/killswitch.h. Here's an annotated excerpt showing the essential macro usage:
/* include/linux/killswitch.h — annotated reference excerpt
*
* To make a function killswitch-eligible:
* 1. Include this header in your .c file.
* 2. Place the KILLSWITCH_CHECK() macro as the very first statement
* in the function body, before any local variable initialization
* that could have side effects.
* 3. The macro expands to a lookup in the killswitch state table;
* if engaged, it executes 'return <configured_retcode>;' directly,
* bypassing the rest of the function body entirely.
*/
#ifndef _LINUX_KILLSWITCH_H
#define _LINUX_KILLSWITCH_H
#include <linux/types.h>
#ifdef CONFIG_KILLSWITCH
/**
* KILLSWITCH_CHECK - short-circuit a function if its killswitch is engaged
* @fname: the exact symbol name of the current function (use __func__)
* @rettype: the return type of the enclosing function
*
* Place at the top of any function body you want to be killswitch-eligible.
* If the killswitch for this function is engaged, the function returns
* immediately with the value configured via the control interface.
* The check is a single hash-table lookup; overhead when disengaged is
* one branch-not-taken.
*/
#define KILLSWITCH_CHECK(fname, rettype) \
do { \
/* __killswitch_check() returns true + fills *_ret \
* if the named function is currently engaged. \
*/ \
rettype _ks_ret; \
if (unlikely(__killswitch_check((fname), (void *)&_ks_ret))) \
return _ks_ret; \
} while (0)
int __killswitch_check(const char *fname, void *ret_storage);
#else /* !CONFIG_KILLSWITCH */
/* When CONFIG_KILLSWITCH is disabled, the macro compiles away entirely.
* No overhead, no code generated. */
#define KILLSWITCH_CHECK(fname, rettype) do { } while (0)
#endif /* CONFIG_KILLSWITCH */
#endif /* _LINUX_KILLSWITCH_H */
A real instrumented function looks like this:
#include <linux/killswitch.h>
int af_alg_sendmsg(struct socket *sock, struct msghdr *msg,
size_t size, int flags)
{
/* Short-circuit check must be first — before lock acquisition,
* before any memory allocation, before touching msg or sock.
* If engaged, returns -EPERM (or configured code) immediately. */
KILLSWITCH_CHECK(__func__, int);
/* ... rest of function body unchanged ... */
}
Running the Upstream Selftests
The patch includes a complete selftest suite. Here's how to build and run it:
# Build and run the killswitch selftests against your running kernel
# Requires: kernel headers installed, gcc, make
cd tools/testing/selftests/killswitch
# Build the test binary and shell test
make
# Run the full suite (requires root)
sudo ./killswitch_test.sh
Expected output:
TAP version 13
1..8
ok 1 - killswitch control interface exists
ok 2 - engage af_alg_sendmsg succeeds
ok 3 - af_alg_sendmsg returns -EPERM when engaged
ok 4 - disengage af_alg_sendmsg succeeds
ok 5 - af_alg_sendmsg functions normally after disengage
ok 6 - engaging unknown function returns -ENOENT
ok 7 - cve_31431_test: AF_ALG sendmsg blocked when killswitch active
ok 8 - kernel taint flag set after engagement
# Totals: pass:8 fail:0 xfail:0 xpass:0 skip:0 error:0
The cve_31431_test.c binary (test 7) opens an AF_ALG socket, verifies sendmsg works normally, engages the killswitch, then asserts sendmsg returns EPERM. It's also the reference implementation for any new CVE-specific selftest you write: model your test on its structure of pre-check / engage / post-check / disengage / post-disengage-verify.
To run only the CVE test in isolation:
# Build and run just the CVE-specific test binary
gcc -o cve_31431_test cve_31431_test.c
sudo ./cve_31431_test
# Exit code 0 = mitigation works correctly
# Exit code 1 = mitigation did not block the call (test failure)
Limitations and When Not to Use It
Functions That Cannot Be Safely Killswitched
Not every kernel function is a safe target. The table below gives practical guidance:
| Function Category | Safe to Killswitch? | Reason | |---|---|---| | Optional socket family send/receive paths (AF_ALG, AF_VSOCK) | ✅ Yes | Clean error return, callers handle EPERM | | Rarely-used protocol handlers (ax25, ksmbd) | ✅ Yes | Most hosts don't use them | | VFS read/write for non-critical filesystems | ⚠️ Conditional | Test caller error handling first | | Core scheduler functions | ❌ No | System hang or panic likely | | Core memory allocator (kmalloc, etc.) | ❌ No | Immediate system failure | | IRQ handlers | ❌ No | Timing/hardware state corruption | | Functions called during boot/init | ❌ No | Use Kconfig to disable at build time instead |
The rule of thumb: if the function path is "hot" (called thousands of times per second by the system itself) or has no error-return path in its callers, do not killswitch it.
Race Conditions During Engagement on Busy Systems
Engagement is not atomic with respect to concurrent callers. Between writing to the control file and the state table update propagating to all CPUs, a small number of in-flight calls to the target function will execute normally. On a busy host with thousands of concurrent AF_ALG users, a handful of calls may slip through during the ~microsecond engagement window. For most CVE scenarios, this is acceptable. If you need a harder guarantee, combine the killswitch with a network policy rule or seccomp filter applied first.
Security Boundary: CAP_SYS_ADMIN and LSM Policy
Writing to /sys/kernel/security/killswitch/control requires CAP_SYS_ADMIN. In containerized environments, this means only privileged containers (or the host) can engage killswitches—container workloads cannot protect themselves. Additionally, SELinux and AppArmor policy must permit writes to the securityfs path. If your LSM policy is locked down, add an explicit allow rule for your admin tooling's security context before relying on killswitch in an incident.
Killswitch Is Not a Substitute for Patching
This is explicitly stated in the patch documentation. Killswitch buys time; it does not fix the vulnerability. The engaged function is still present in the kernel, still reachable, and will re-enable on next reboot unless you re-engage it via init. A killswitch with no follow-through patching plan is technical debt that becomes a liability. Treat it as an incident response tool with a mandatory ticket to deploy a patched kernel within a defined SLA.
Kernel Taint Implications
Once TAINT_KILLSWITCH is set, it stays set until reboot even if you disengage all killswitches. Upstream kernel maintainers will see this taint in bug reports and may decline to investigate without a reproduction on an untainted kernel. For production systems running automated kernel bug reporters (like kdump + crash + automated Bugzilla submission), add a check that filters or annotates reports generated while a killswitch taint is active. Use grep TAINT /proc/sys/kernel/tainted or parse /proc/sys/kernel/tainted against the bit table in tainted-kernels.rst in your tooling.
Recommended Tools
- AWSCloud computing services
- DigitalOceanCloud hosting built for developers — $200 free credit for new users
- Akamai Cloud (Linode)Developer-friendly cloud infrastructure