Kernel CVEs and backports: why uname -r tells you nothing about your patch level
A scanner that sees 6.8.0 and reports 40 kernel CVEs doesn't know about Ubuntu and Debian backports. Here's how to read your kernel package's changelog yourself, compare the running kernel with the installed one, check hardware mitigations in /sys and decide when livepatch is worth it. Using the free sources: USN, Debian Security Tracker and the package changelog.
Contents
- Step 1: which kernel is running, and which is installed?
- Step 2: which CVEs are fixed in my kernel package?
- Step 3: what does the distribution say about open kernel CVEs?
- Step 4: hardware mitigations — the CVEs that have no package
- Step 5: when livepatch is worth it
- Step 6: the weekly report
- Pitfalls
- What you still don't have
- How monsys does it
- FAQ
uname -r says 6.8.0-45-generic. A vulnerability scanner sees "6.8.0", looks up everything fixed in 6.8.x in the NVD, and reports forty CVEs. Meanwhile Ubuntu has backported those forty into 6.8.0-45.45 without changing the upstream number. The reverse exists too: the kernel you installed is patched, but the kernel that's running is the one from before the reboot three weeks ago. Two questions, and uname -r answers neither.
Step 1: which kernel is running, and which is installed?
# Running
uname -r # 6.8.0-45-generic
cat /proc/version # includes build date and compiler — useful for custom builds
# Installed (may be newer than running)
dpkg -l 'linux-image-*' | awk '/^ii/{print $2, $3}'
# linux-image-6.8.0-45-generic 6.8.0-45.45
# linux-image-6.8.0-47-generic 6.8.0-47.47 ← newer, not booted yet
# linux-image-generic 6.8.0-47.47 ← metapackage points to the newest
# Does the system itself say a reboot is needed?
cat /var/run/reboot-required 2>/dev/null && cat /var/run/reboot-required.pkgs
# Or, more complete (also services on old libraries):
sudo needrestart -b | grep -E 'KSTA|KCUR|KEXP'
# NEEDRESTART-KCUR: 6.8.0-45-generic
# NEEDRESTART-KEXP: 6.8.0-47-generic
# NEEDRESTART-KSTA: 3 ← 1 = ok, 2 = ABI-compatible update, 3 = reboot needed
The Debian variant is identical, with 6.1.0-25-amd64-style versions. needrestart is installed by default on Ubuntu servers; on Debian: apt install needrestart.
If KCUR ≠ KEXP, any CVE analysis of the installed kernel is irrelevant: you're running the old one. That's the first and most common mistake.
Step 2: which CVEs are fixed in my kernel package?
The authority isn't the NVD but the changelog of the package you're running. Ubuntu and Debian list every CVE number explicitly.
# Ubuntu: the changelog of the running kernel package, all CVEs.
# CAUTION: on a Secure Boot machine linux-image-<ver> is the *signed* package, and THAT
# changelog only contains "Packaging resync" lines — zero CVEs. So ask for the changelog
# of the unsigned package (source: linux), which is where the fixes are.
apt changelog "linux-image-unsigned-$(uname -r)" 2>/dev/null | grep -oE 'CVE-[0-9]{4}-[0-9]+' | sort -u > /tmp/fixed-in-running.txt
wc -l < /tmp/fixed-in-running.txt # hundreds on an LTS kernel that's a year old
# Gives 0? Then the unsigned package isn't in your cache; linux-modules-<ver> shares the same source:
apt changelog "linux-modules-$(uname -r)" 2>/dev/null | grep -oE 'CVE-[0-9]{4}-[0-9]+' | sort -u > /tmp/fixed-in-running.txt
# Debian: the changelog is local
zcat "/usr/share/doc/linux-image-$(uname -r)/changelog.Debian.gz" | grep -oE 'CVE-[0-9]{4}-[0-9]+' | sort -u > /tmp/fixed-in-running.txt
# Is a specific CVE in it?
grep -q CVE-2024-1086 /tmp/fixed-in-running.txt && echo "fixed in running kernel" || echo "NOT fixed (or not applicable)"
"Not in the changelog" doesn't automatically mean "vulnerable". It could also be that the CVE concerns a subsystem not compiled into this kernel, or that the vulnerable code was only added in a newer upstream version. The distribution tracker settles that.
Step 3: what does the distribution say about open kernel CVEs?
Ubuntu publishes a status per CVE per release. The handiest source is the OSV feed (the same as in the OS package guide), with linux as the source package:
. /etc/os-release
# Source package of the running kernel. Signed kernels are called "linux-signed[-aws|-azure|…]"
# in dpkg, but OSV knows them as "linux[-aws|-azure|…]" — hence the sed.
SRC=$(dpkg-query -W -f='${source:Package}' "linux-image-$(uname -r)" | sed 's/-signed//')
VER=$(dpkg-query -W -f='${source:Version}' "linux-image-$(uname -r)")
echo "$SRC $VER" # linux 6.8.0-45.45
curl -s -X POST https://api.osv.dev/v1/query -H 'Content-Type: application/json' \
-d "{\"package\":{\"name\":\"$SRC\",\"ecosystem\":\"Ubuntu:$VERSION_ID\"},\"version\":\"$VER\"}" \
| jq -r '.vulns[]? | .id' | sort -u > /tmp/open-kernel.txt
wc -l < /tmp/open-kernel.txt
That's the list of CVEs for which Ubuntu considers your exact kernel version vulnerable — after accounting for all backports. On a kernel updated this month: usually a few dozen "medium/low" that Ubuntu is still working on or has marked deferred. On a kernel six months old: hundreds.
Debian has the Security Tracker with a JSON export. It's big (tens of MB) but complete:
curl -s https://security-tracker.debian.org/tracker/data/json -o /tmp/debsec.json
jq -r --arg rel "$VERSION_CODENAME" '
.linux | to_entries[]
| select(.value.releases[$rel].status == "open")
| [.key, .value.releases[$rel].urgency, (.value.description // "")[0:80]] | @tsv' /tmp/debsec.json \
| sort | column -t -s $'\t' | head -30
The urgency column (unimportant, low, medium, high) is Debian's own assessment; unimportant usually means "doesn't affect the Debian build".
Step 4: hardware mitigations — the CVEs that have no package
Spectre, Meltdown, MDS, Retbleed, Downfall, Zenbleed: those are CVEs in the CPU, mitigated by kernel and microcode. Whether your machine is protected isn't in a changelog but in /sys:
grep -H . /sys/devices/system/cpu/vulnerabilities/* | sed 's|.*/vulnerabilities/||'
# spectre_v2:Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; RSB filling; ...
# mds:Not affected
# gather_data_sampling:Vulnerable: No microcode ← this is what you're looking for
# retbleed:Mitigation: Enhanced IBRS
Anything that says Vulnerable is an open item. The fix is usually microcode (apt install intel-microcode or amd64-microcode, then reboot) — on a VPS only the hoster can do that, and then it's a question to the hoster, not an action on the server. Include the output in your evidence; an auditor asking about "Spectre" gets a factual answer.
Step 5: when livepatch is worth it
Livepatch (Ubuntu Pro / canonical-livepatch, or kpatch on RHEL) patches critical kernel CVEs in running memory, without a reboot. It covers only the CVEs Canonical rates high/critical and that are livepatchable — so not everything.
# Ubuntu Pro is free for 5 machines (personal account)
sudo pro attach <token>
sudo pro enable livepatch
canonical-livepatch status --verbose
# ...
# patchState: applied
# fixes: CVE-2024-xxxx, CVE-2024-yyyy
The trade-off is simple: livepatch buys time between "CVE published" and "planned reboot window". It doesn't replace the reboot; needrestart keeps reporting KSTA 3 until you actually restart. For a server you reboot monthly in a maintenance window anyway, livepatch is mainly useful for the rare critical exploited on day 0. For a database that may only go down twice a year, it's essential.
Step 6: the weekly report
Everything together in one script that yields one line per server:
sudo tee /usr/local/sbin/kernel-status.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
. /etc/os-release
RUN=$(uname -r)
INST=$(dpkg -l 'linux-image-[0-9]*' | awk '/^ii/{print $2}' | sed 's/linux-image-//' | sort -V | tail -1)
KSTA=$(needrestart -b 2>/dev/null | awk -F': ' '/KSTA/{print $2}')
VULN=$(grep -l -E '^Vulnerable' /sys/devices/system/cpu/vulnerabilities/* 2>/dev/null | xargs -rn1 basename | paste -sd, -)
SRC=$(dpkg-query -W -f='${source:Package}' "linux-image-$RUN" 2>/dev/null | sed 's/-signed//')
VER=$(dpkg-query -W -f='${source:Version}' "linux-image-$RUN" 2>/dev/null)
OPEN=$(curl -s -m 20 -X POST https://api.osv.dev/v1/query -H 'Content-Type: application/json' \
-d "{\"package\":{\"name\":\"${SRC:-linux}\",\"ecosystem\":\"Ubuntu:$VERSION_ID\"},\"version\":\"$VER\"}" \
| jq -r '.vulns // [] | length')
printf 'host=%s running=%s installed=%s reboot_state=%s cpu_vulnerable=%s open_kernel_cves=%s\n' \
"$(hostname -s)" "$RUN" "$INST" "${KSTA:-?}" "${VULN:--}" "${OPEN:-?}"
EOF
sudo chmod 0755 /usr/local/sbin/kernel-status.sh
sudo /usr/local/sbin/kernel-status.sh
# host=web-01 running=6.8.0-45-generic installed=6.8.0-47-generic reboot_state=3 cpu_vulnerable=gather_data_sampling open_kernel_cves=23
That one line per host, weekly into a file or to ntfy, answers both questions from the introduction: am I running what I installed, and how much is still open for what I'm running.
Pitfalls
- Cloud and signed kernels.
-aws,-azure,-gcp,-kvmare separate source packages (linux-aws, …) with their own changelogs and their own USNs. And on any machine with Secure Boot the source package in dpkg islinux-signed(orlinux-signed-aws), while OSV and the USNs uselinux. Always ask dpkg for the source package and strip-signed; hardcodedlinuxis wrong on AWS, hardcodedlinux-signedreturns zero results. - Custom and vendor kernels. A hoster's kernel (
5.15.0-hoster1) or a self-built one (6.9.0-custom) has no changelog with CVEs and no tracker entry./proc/versionwithoutbuildd@is the tell. Treat those kernels as "unknown patch level", which in an audit is the same as "vulnerable". - The changelog of the wrong package.
apt changelog linux-image-$(uname -r)on a Secure Boot machine returns the changelog oflinux-signed, which contains not a single CVE. You'd conclude "nothing fixed" while 200 CVEs are in there. Uselinux-image-unsigned-…orlinux-modules-…. /bootfull. Every kernel takes 100–150 MB. If/bootis full, the new kernel's installation fails halfway and you silently keep running on the old one.apt autoremove --purgecleans up; always keep two.- Scanners comparing version numbers. If you run Nessus, OpenVAS or Trivy on hosts, filter kernel CVEs out of their report and use the distribution tracker. Otherwise you argue about forty false positives every month.
- Reboot-required isn't a kernel-only signal.
/var/run/reboot-requiredalso appears for glibc, systemd and dbus. The.pkgsfile tells you which.
What you still don't have
- Fleet overview. "Which hosts run a kernel with an open KEV CVE?" is
for h in $(cat hosts); do ssh $h kernel-status.sh; done— and then laying the KEV list next to it. - Reboot orchestration. Knowing that 14 servers need a reboot is step one; rebooting them in the right order, in the right window, with a rollback plan is the real work. See the guide on kernel updates in production.
- Evidence with dates. For NIS2 and ISO 27001 A.8.8 you want to show: CVE published on X, kernel installed on Y, rebooted on Z. Those are three timestamps currently in three different logs.
How monsys does it
The monsys agent reports the running kernel, the installed kernels, reboot_required, is_custom_build and the loaded modules. The hub's kernel CVE pipeline checks those against Ubuntu USNs, the Debian Security Tracker, RHEL CSAF and Gentoo GLSA — backport-aware, per source package (including linux-aws and friends). Open kernel CVEs are enriched with EPSS and KEV and weighed into the Trust Score. Kernel updates are planned in batches with reboot windows, where the requester and the approver must be different people (segregation of duty) — and every stage is logged with a signature.
FAQ
My scanner says kernel 6.8.0 has forty CVEs. Is that right?
Almost certainly not. Ubuntu and Debian backport fixes without changing the upstream version number. Check the changelog of your kernel package (apt changelog linux-image-unsigned-$(uname -r)) or the OSV feed with your exact package version; those do know about the backports.
Do I have to reboot after every kernel update?
Yes, to run the new kernel. Without a reboot the patched kernel sits on disk while the old one keeps working in memory. Livepatch is the exception for a limited set of critical CVEs, but even then a reboot is eventually needed.
Is Ubuntu Pro livepatch free?
For up to five machines with a personal Ubuntu One account, yes. Beyond that it's a paid subscription. Debian has no official livepatch equivalent; there, a planned reboot window is the practice.
Written by the monsys team — sysadmins who do this every day.
Done it by hand? Let monsys keep it running.
Everything in this guide runs in monsys as a continuous check, with history, alerts and audit evidence. 5 servers free, EU-hosted in Belgium, installed in 60 seconds.