Kernel vulnerabilities arrive constantly; since the kernel project became its own CVE Numbering Authority in 2024, it assigns CVEs to a large share of fixes. Most of them do not need a weekend. A few do. The difference is rarely the CVSS score alone; it is whether the vulnerable code is reachable on your machines, by whom, and how quickly you can get a fixed kernel running.

This is the playbook we use, worked through on a real example: CVE-2025-21756, a use-after-free in the Linux vsock subsystem, fixed upstream in early 2025 and publicly analysed later that spring. It is a local privilege escalation, rated high severity, and it is a good teaching case because the vulnerable module is not needed on many machines that have it. Everything here is defensive: we cover how to assess and close exposure, not how the bug is exploited.

Step 1: understand the CVE in your own terms

Before touching hosts, we answer four questions from primary sources: the kernel commit, the CVE record, and our distributions' security trackers.

Question CVE-2025-21756 answer
Which code? The vsock (AF_VSOCK) socket layer, used for host-guest communication in virtual machines
Who can trigger it? A local, unprivileged user who can create vsock sockets
What is the impact? Memory corruption that can lead to privilege escalation to root
Where is it fixed? Upstream and stable kernels from early 2025; check your distribution's tracker for the exact package version

The distribution trackers are the source of truth for the fixed package version, not upstream version numbers, because distributions backport fixes into older kernel series. Ubuntu, Debian, Red Hat and SUSE all publish a per-CVE page with status per release.

"Local user" matters for prioritisation. A single-tenant database server with no interactive users is a lower priority than a CI runner executing untrusted jobs, a shared build host, or a container host running workloads from many teams.

Step 2: find exposed hosts

We need two facts per host: is the running kernel vulnerable, and is the vulnerable code reachable.

The running kernel, which is what matters, not the installed package:

uname -r

Whether vsock is present and loaded:

# Built in (=y), module (=m) or absent?
grep -E 'CONFIG_VSOCKETS=|CONFIG_VIRTIO_VSOCKETS=|CONFIG_VMWARE_VMCI_VSOCKETS=|CONFIG_HYPERV_VSOCKETS=' /boot/config-$(uname -r)

# Currently loaded modules
lsmod | grep -E '^(vsock|vmw_vsock|vhost_vsock|hv_sock)'

# Device node present (guest side)
ls -l /dev/vsock 2>/dev/null

If vsock is built as a module and not loaded, the code is not reachable until something loads it. That is important, because the kernel can autoload a socket family module when a program asks for it. "Not loaded right now" is not the same as "cannot be loaded".

At fleet scale we run the checks through our configuration management and collect results centrally. A minimal ad hoc version over SSH:

#!/usr/bin/env bash
# usage: ./vsock-exposure.sh hosts.txt
while read -r h; do
  ssh -o BatchMode=yes "$h" '
    k=$(uname -r)
    cfg=$(grep -E "^CONFIG_VSOCKETS=" /boot/config-$k 2>/dev/null | cut -d= -f2)
    loaded=$(lsmod | grep -cE "^(vsock|vmw_vsock|vhost_vsock|hv_sock)")
    echo "$(hostname) kernel=$k vsock_config=${cfg:-none} vsock_modules_loaded=$loaded"
  ' < /dev/null
done < "$1"

Then we ask what actually uses vsock. Legitimate users include VM guest agents configured to use vsock, Firecracker and Kata Containers guests, confidential computing and enclave tooling, hypervisor hosts running vhost_vsock, and Hyper-V integration such as WSL 2. On our general-purpose cloud VMs and bare-metal hosts, nothing did.

Step 3: mitigate where the module is unused

Where vsock is a loadable module and nothing needs it, we prevent it from loading. A plain blacklist line only stops automatic loading by alias; an explicit modprobe or a dependency can still load it. The install directive pointing at /bin/false blocks all loading through modprobe:

sudo tee /etc/modprobe.d/disable-vsock.conf >/dev/null <<'EOF'
# Mitigation for CVE-2025-21756 until patched kernel is running
install vsock /bin/false
install vmw_vsock_virtio_transport /bin/false
install vmw_vsock_virtio_transport_common /bin/false
install vmw_vsock_vmci_transport /bin/false
install vhost_vsock /bin/false
install hv_sock /bin/false
blacklist vsock
EOF

If modules are already loaded and unused, unload them; modprobe -r fails cleanly if something is using them, which itself tells you the host needs vsock:

sudo modprobe -r vmw_vsock_virtio_transport vmw_vsock_virtio_transport_common vsock 2>&1 || true

Some caveats we learned to check:

  • Initramfs. If a module is included in the initramfs, it can load before the root filesystem's modprobe configuration applies. Regenerate the initramfs after adding the file (update-initramfs -u on Debian and Ubuntu, dracut -f on RHEL family).
  • Built-in code. If CONFIG_VSOCKETS=y, there is nothing to unload or block. Those hosts go straight to the patch or livepatch track.
  • Containers. Containers share the host kernel, so a container host is exposed if its kernel is. Docker's default seccomp profile refuses creation of AF_VSOCK sockets, which reduces exposure from containers that use that profile. Containers running unconfined, privileged or with a custom profile do not get that protection.

Mitigation buys time; it is not the fix. We track every mitigated host until it runs a fixed kernel and then remove the modprobe file if vsock is needed later.

Step 4: livepatch where available

Livepatching replaces vulnerable kernel functions in memory without a reboot. Coverage depends on the vendor; livepatches are produced for selected high-impact CVEs and supported kernel versions, not for everything. Check whether your vendor shipped one for the CVE and your kernel version.

On Ubuntu with Livepatch enabled through Ubuntu Pro:

canonical-livepatch status --verbose

On RHEL with kpatch:

sudo dnf install "kpatch-patch = $(uname -r)"
sudo kpatch list

Generic check that works on any distribution using kernel livepatching:

ls /sys/kernel/livepatch/
cat /sys/kernel/livepatch/*/enabled

A livepatch fixes the running kernel only. The next reboot loads whatever kernel is installed on disk, so the package update is still required.

Step 5: patch and reboot without drama

The actual fix is a kernel package that contains the backport, followed by a reboot into it. We install the package everywhere immediately, then schedule reboots in rings:

  1. Canary: a handful of hosts from each role, rebooted the same day.
  2. Stateless tiers: application servers and workers, rolling, behind load balancers.
  3. Stateful tiers: databases and queues, during maintenance windows, replicas first, then failover and the former primary.

For Kubernetes nodes we use kured, which watches for the reboot-required sentinel file, takes a cluster-wide lock, drains one node at a time, reboots and uncordons it. We restrict it to a reboot window:

# kured DaemonSet container args (excerpt)
args:
  - --reboot-sentinel=/var/run/reboot-required
  - --start-time=02:00
  - --end-time=05:00
  - --time-zone=UTC
  - --period=10m

On Debian and Ubuntu, /var/run/reboot-required appears after a kernel package installs. On RHEL family, needs-restarting -r reports whether a reboot is needed; we have a small timer that creates the sentinel file from it so kured works the same way on both.

Step 6: verify and close

Verification is per host and recorded:

# Running kernel matches the fixed package
uname -r

# The changelog of the installed kernel mentions the CVE (Debian/Ubuntu)
apt changelog "linux-image-$(uname -r)" 2>/dev/null | grep -m1 CVE-2025-21756

# RHEL family
rpm -q --changelog "kernel-$(uname -r)" | grep -m1 CVE-2025-21756

We close the ticket only when every in-scope host runs a fixed kernel or a confirmed livepatch, and when mitigated hosts have either been patched or have a documented exception. Our vulnerability scanner then confirms from the outside that the CVE is no longer reported for those hosts.

Common mistakes

Mistake Why it hurts Do this instead
Checking the installed package, not the running kernel A host can have the fix installed and still run the old kernel for months Compare uname -r with the fixed version
Relying on blacklist alone Explicit loads and dependencies still work Use install <module> /bin/false and rebuild the initramfs
Treating livepatch as the fix The next boot loads the unpatched on-disk kernel if packages were not updated Livepatch and update packages
Rebooting everything at once Capacity loss and correlated failures Reboot in rings with drains and locks
Ignoring container hosts Containers share the host kernel Prioritise multi-tenant and CI hosts

Key takeaways

  • Read the CVE in terms of reachability: which code, which users, which of your hosts.
  • Check the running kernel and whether the vulnerable module is built in, loadable or loaded.
  • Block unused modules with modprobe install rules as a temporary mitigation.
  • Use livepatch to close the window quickly, but always install the fixed kernel package.
  • Reboot in rings with automation, then verify every host and record the result.

References