What a CVE doesn’t say

A CVE entry is, at best, a summary of a summary. The interesting information — the actual code path, the real-world trigger, the fix that shipped — is almost never in the NVD description. Here is how to find it.


The NVD description of a significant kernel vulnerability often reads something like this: “A use-after-free vulnerability in the [component] subsystem of [OS] before version [X] allows a local attacker to cause a denial of service or possibly execute arbitrary code.” It is accurate. It is also nearly useless as a starting point for understanding what actually happened, where in the code it happened, and what the patch did to fix it.

CVE entries are written for a general audience, after the fact, under constraints that have nothing to do with helping security researchers understand the underlying issue. They are a notification system, not a technical record. Learning to use them well means learning to read around them — to treat the CVE as a key that unlocks a richer set of sources, rather than as the source itself.

The five things a CVE actually tells you

Strip away the boilerplate and a CVE entry reliably gives you five useful things: an identifier, an affected version range, a CWE classification, a CVSS score vector, and a list of references. The description is largely decorative. Everything interesting is in the other five.

The identifier is your search key. Use it to find the vendor's advisory, the commit that fixed it, the researcher's blog post, and any conference talks that followed.

The affected version range tells you when the bug was introduced (approximately) and when it was fixed. If the range starts at version 1.0, the bug may be architectural. If it starts at a specific recent version, there is probably a commit that introduced it, and finding that commit is often more instructive than reading the fix.

The CWE classifies the bug type: use-after-free, integer overflow, improper access control, missing authentication. This is where the BSD cross-reference technique I described in an earlier post gets its traction — if a CVE is CWE-416 (use-after-free) in a BSD subsystem, you have a specific class of bug to look for in the Darwin equivalent.

The CVSS vector encodes the attack conditions: local or network, requires authentication or not, user interaction required or not, confidentiality/integrity/availability impact. Read the vector, not just the score. A 9.8 CVSS that requires network access and no authentication is a very different beast from a 9.8 that requires local access and a specific hardware configuration.

The references are where the real work begins.

Following the references

A well-referenced CVE will point to: the vendor's security advisory (which usually names the affected function); the patch commit (which shows you exactly what changed); and sometimes a researcher's disclosure or conference talk (which explains the discovery path and often the full technical detail).

The commit is the most valuable reference if you can find it. A commit diff shows you the exact code that was changed, in context, with the surrounding logic visible. From the commit you can: identify the vulnerable function by name; understand what the fix did; check whether the same fix is present in a related codebase (Darwin, in my case); and read the commit message, which sometimes — if the developer who wrote it was thorough — explains the root cause more clearly than the CVE ever will.

The CVE is the door. The commit is the room. Most people stop at the door. The interesting material is inside.

What to do when the references are thin

Not every CVE has a clean commit reference. Older CVEs, in particular, may point only to a mailing list archive or a vendor advisory that has since gone offline. In those cases, the version range is your friend: you know the bug existed before version X and was fixed in version X. If the project uses a public version control system, you can look at the diff between the last vulnerable version and the first fixed version, narrowing to the affected component using the CWE and the component name from the description.

This is slower, but it is a reliable method. The version control history is honest in a way that post-hoc descriptions sometimes are not. Code does not editorialize.

Reading CVEs as a research input

I use CVE reading as a research input in two ways. The first is the cross-reference technique: finding CVEs in BSD-lineage code and checking whether the Darwin equivalent was patched. The second is pattern recognition: reading CVEs in a subsystem I am about to investigate to understand what classes of bug that subsystem has historically been vulnerable to, which shapes where I look and what I look for.

Neither use requires the CVE description to be detailed. Both use the identifier, the version range, the CWE, and the references. The description is, at most, a prompt. The real reading happens elsewhere.


A CVE is an echo of something that happened somewhere earlier. The original sound — the bug, the discovery, the fix — is in the commit history, the advisory, the researcher's notes. The echo tells you something happened. Following it back to the source tells you what.

Read the references. Find the commit. Read the diff. That is where the CVE keeps its secrets.