Security

That Time a Zero (could have) Broke the Internet's Plumbing (CVE-2026-0915)

An AI-assisted analysis uncovered a 30-year-old uninitialized buffer vulnerability in glibc's _nss_dns_getnetbyaddr_r function. This case study details how a zero-input edge case bypasses loop logic, causing the library to transmit raw stack memory to external DNS servers, and benchmarks how various AI models succeeded in identifying this subtle logic error where human review failed.

Wed 21 January 2026

That Time a Zero (could have) Broke the Internet's Plumbing (CVE-2026-0915)

Let me tell you about a bug that's been sitting in one of the most critical pieces of software on the planet. We're talking about glibc, the GNU C Library, which is essentially the foundation that almost everything on Linux sits on top of.

Your web servers? Running on glibc.
Your cloud infrastructure? glibc.
That IoT device in your kitchen? Probably glibc.

And for 30 years, there's been a bug that would happily send whatever happened to be lying around in memory straight out to DNS servers, in cleartext on the internet. Passwords, keys, session tokens, whatever was on the stack. Just... out the door.

Here's how it worked.

"But What Does Zero Even Mean Here?"

Right, let's back up a bit. There's a function in glibc called _nss_dns_getnetbyaddr_r. What it does is pretty straightforward: you give it a network address as a number, and it goes off to DNS to find the name associated with that network. Reverse lookup. Simple!

The code takes your network number and breaks it apart into its component bytes. If you pass in something representing "192.168.1.0", it extracts 192, 168, 1, and 0 as separate values, then constructs a DNS query string from them.

Here's a simplified version of what that looks like:

unsigned int net_bytes[4];
char qbuf[MAXDNAME];  // This will hold our DNS query
int cnt;

uint32_t net2 = (uint32_t) net;

for (cnt = 4; net2 != 0; net2 >>= 8)
    net_bytes[--cnt] = net2 & 0xff;

It starts cnt at 4, then for each byte it extracts from the network number, it decrements cnt and stores the byte. When it's done, cnt tells you how many bytes were in the original net_bytes, which determines what "class" of network address you're dealing with.

Then there's a switch statement:

switch (cnt)
{
    case 3:  // One byte - Class A
        sprintf(qbuf, "0.0.0.%u.in-addr.arpa", net_bytes[3]);
        break;
    case 2:  // Two bytes - Class B
        sprintf(qbuf, "0.0.%u.%u.in-addr.arpa", ...);
        break;
    case 1:  // Three bytes - Class C
        sprintf(qbuf, "0.%u.%u.%u.in-addr.arpa", ...);
        break;
    case 0:  // Four bytes - Class D/E
        sprintf(qbuf, "%u.%u.%u.%u.in-addr.arpa", ...);
        break;
}

Now, here's where I want you to stop and think for a moment. What happens if someone passes in zero? Not "0.0.0.1" or "10.0.0.0", just plain zero. Nothing.

Go on, trace through that loop in your head.

The Moment Everything Goes Wrong

Got it? Here's what happens:

  1. net is 0
  2. net2 becomes 0
  3. The loop condition is net2 != 0
  4. That's immediately false
  5. The loop never executes. Not even once.
  6. cnt stays at 4

And what case handles cnt == 4 in that switch statement? Nothing.

There's no case 4. There's no default. The switch statement just... doesn't match anything. Which means qbuf,our DNS query buffer,never gets written to.

But here's the thing about C: when you declare a local variable like char qbuf[MAXDNAME], the language doesn't initialize it for you. It just points at a chunk of stack memory and says "this is yours now." Whatever was in that memory before? Still there. Old function return addresses, bits of strings, fragments of data from previous operations,it's all just sitting there like yesterday's lunch in the break room fridge.

And then this happens:

anslen = __res_context_query(ctx, qbuf, C_IN, T_PTR, ...);

That line sends qbuf off to a DNS server. Uninitialized. Full of garbage. Across the network. To infrastructure you don't control.

"Hang On, Who's Actually Calling This with Zero?"

Fair question. Under what circumstances would this function get called with a network value of zero? The honest answer is: it probably doesn't happen often in normal operation.

The Fix Is Almost Embarrassingly Simple

Here's what the patch looks like:

switch (cnt)
{
    case 4:
        // Actually handle zero!
        sprintf(qbuf, "0.in-addr.arpa");
        break;
    case 3:
        sprintf(qbuf, "0.0.0.%u.in-addr.arpa", net_bytes[3]);
        break;
    // ... rest of cases
}

That's it. Add a case for 4. Handle the zero input. Done.

Or alternatively, just initialize the buffer when you declare it:

char qbuf[MAXDNAME] = {0};

Either way, we're talking about a one-line fix for a vulnerability that's been sitting in critical infrastructure for 30 years. That's the fun part.

Here's Where It Gets Really Interesting: AI is likely the one who found this

This vulnerability was likely discovered using AI-assisted code analysis, to find this bug:

  1. You need to trace control flow through a loop
  2. You need to recognize that zero is a special case that causes the loop to not execute
  3. You need to notice that the switch statement doesn't handle the resulting value of cnt
  4. You need to understand that this leaves a buffer uninitialized
  5. You need to connect that to the buffer being sent over a network

That's a lot of steps. It's exactly the kind of multi-hop reasoning that's easy to miss during human code review, especially in a codebase as large and mature as glibc. But it's also exactly the kind of thing that modern AI models are getting genuinely good at.

So We Ran a Benchmark

I was curious how different AI models perform at finding this vulnerability? So I took the vulnerable code and threw it at 10 different models with a simple prompt: "Find the vulnerability in this code."

Here are the results:

Model Found It?
GPT 5.2 ✅ Yes
GPT 5.1 ✅ Yes
Claude Opus 4.5 ✅ Yes
Grok 4 ✅ Yes
Deepseek R3 ✅ Yes
Deepseek v3.2 ✅ Yes
Deepseek v3 ❌ No
Gemini 3 ❌ No
Gemini 2.5 ❌ No
Kimi k2 ❌ No

60% success rate. Six out of ten models correctly identified the uninitialized buffer issue with net == 0.

The Diagram (Because I Know You Want One)

I'm a visual person. Here's how I'd illustrate this bug:

Proposed Flowchart: "The Two Paths"

flowchart TD A["Function gets network value"] --> B{Is net == 0?} B -->|No| C["Loop executes
cnt = 0,1,2,3"] B -->|Yes| D["Loop SKIPPED
cnt = 4"] C --> E["Switch case
matches"] D --> F["No matching
case"] E --> G["Safe: DNS query sent"] F --> H["Risk: Memory leaked"]

Wrapping Up

CVE-2026-0915 is a beautiful example of why security is hard. This isn't complicated code. There's no clever exploit chain, no exotic technique. It's just... a missing edge case. A zero that nobody thought to handle. And that oversight meant sensitive memory contents could leak across the internet.

The fact that AI likely found this bug is, I think, a glimpse of the future. We're going to see more of this. AI models trawling through open-source codebases, finding bugs that human eyes have skipped over for years. That's exciting and valuable and also a little bit terrifying when you think about who else might be running those same analyses.

But for now: patch your systems, initialize your buffers.