The Naked Compiler: Every C/C++ Safety Net You Trust Is Already Gone

The Naked Compiler: Every C/C++ Safety Net You Trust Is Already Gone

By Shrikant Bhosale · July 2026

This is the post of the decade. Read it. Read the four experiments it links. Then check your own binaries. Because I can promise you: the safety checks you think are there — they’re not.


If you write C or C++ for a living, I need you to sit down for this.

I spent July 2026 running an experiment that should have been done decades ago. I compiled the same code 28 different ways, disassembled every binary, and counted how many safety checks survived from source to machine code. Then I did it on real infrastructure code — FRRouting, the routing stack that runs on switches and routers from NVIDIA, Microsoft, and every white-box vendor on earth. Then I cross-referenced 27 real-world firewall CVEs from Cisco, Palo Alto, Fortinet, and Juniper. Then I measured across GCC, the Linux kernel, and Chromium.

The result is unambiguous. It’s reproducible. And if you’re a C/C++ developer, it should terrify you:

Your safety checks do not survive compilation.

Not assert(). Not DCHECK(). Not __builtin_trap(). Not VM_BUG_ON(). Not lockdep_assert_held(). At any optimization level above -O0, the compiler removes them. The debug binary at -O2 is identical to the release binary. The safety net exists in your source code. It does not exist in the binary your users run.

I have the disassembly to prove it.

The Four Experiments

# What I Did The Body Count
Experiment 1 Compiled 28 variants of the same C code — gcc/g++, -O0/-O2/-Os/-O3, debug/release/aggressive/safe — and scanned every binary assert() stripped at any -O level. DCHECK evaporated. Only explicit if (x) { return; } survived. Aggressive -O3 reduced safety checks by 71%.
Experiment 2 Scanned FRRouting (1,359 files, ~100K lines) — the routing stack behind Cumulus Linux, SONiC, VyOS — built two ASAN PoCs 3,850 real singularities, 114 in the config parser alone. Debug had 37 safety checks. Release had 24. 13 checks stripped. Two ASAN heap-buffer-overflows confirmed.
Experiment 3 Mapped the pattern to enterprise firmware — 27 CVEs from Cisco, Palo Alto, Fortinet, Juniper Average CVSS 8.1. Average D_e 0.92 (the debt metric, where 1.0 means the check is entirely missing in the binary). Full 8-step exploit chain from recon to ransomware.
Experiment 4 Cross-project measurement: FRRouting + GCC 15.2 + Linux kernel 6.8 + Chrome Mojo 9,712 stripped safety nets measured across four codebases. Three-question framework proves prevalence, reachability, and impact.

What I Want You to Understand

This is not a compiler bug. This is not a standards violation. The compiler is doing exactly what the C and C++ standards tell it to do. assert() is defined as a debug-only diagnostic. DCHECK is defined to expand to nothing in release mode. The compiler is correct.

The problem is the assumption that source-level safety checks survive to the binary.

That assumption is embedded in every codebase I’ve ever seen:

  • The Chromium project uses DCHECK for safety. I have 4 Google-confirmed P2/S2 findings based on DCHECK-only guards in Android’s Binder (libbinder), the ART runtime, and Chrome Mojo. Google called them valid security issues. They’re caused by debug-only assertions that don’t exist in production.

  • FRRouting uses DCHECK in its VTY config parser — the interface that accepts CLI commands over TCP port 2601. Two ASAN heap-buffer-overflows. The check exists in the source. It does not exist in the binary.

  • The Linux kernel uses lockdep_assert_held and VM_BUG_ON — ~4,602 of them in a standard defconfig build. All stripped in production.

  • GCC itself uses gcc_checking_assert() — 3,195 calls in version 15.2. All stripped at -O2+.

Every one of these projects has source code that looks safe. Every one has binaries that are not.

The Hierarchy of Betrayal

Let me be completely precise about what survives compilation and what doesn’t. I compiled 24 safety patterns across 28 binary variants and checked every single one:

Safety Pattern Source Binary at -O2+ Verdict
assert(idx < len) Stripped
DCHECK_LT(idx, len) Stripped
__builtin_trap() guard ⚠️ Farmed to .cold section
if (idx >= len) { return; } Survives
volatile guard flag Survives
Null check after dereference Dead code eliminated
memset(this, 0) Stripped by optimizer
std::optional::value() without check Inlined, no safeguard
realloc() NULL check Dead code eliminated
dynamic_cast null check ⚠️ Check in .cold section only

Only one pattern reliably survives: explicit if (condition) { return; } with a side-effecting branch.

The compiler can prove that assert() has no side effects. It can prove that DCHECK expands to nothing. It can prove that __builtin_unreachable() is never reached. It removes all of them. But it cannot remove a branch that calls fprintf() or writes to a volatile variable — those have observable side effects that change program behavior.

This means the only safety checks you can trust are the ones you write as explicit, side-effecting control flow. Everything else is a lie.

The Scale

I measured 9,712 stripped safety nets across four codebases. That’s not a comprehensive scan. That’s four projects on a single machine with a weekend of compute time.

Let me put that number in context:

  • FRRouting: 2,032 assert() calls in 746 files. In the config parser alone, 114 singularities. Two of those became ASAN heap overflows.
  • GCC 15.2: 3,195 gcc_checking_assert() calls. The compiler that compiles everything else has three thousand stripped safety checks in its own source.
  • Linux kernel 6.8: ~4,602 stripped guards. The kernel that runs every cloud server, every Android phone, every embedded device.
  • Chrome Mojo: 1,466 VMF findings across 685 files. 55 files have 15+ singularities each.

If I scaled this to all C/C++ open-source projects on GitHub, the number would be in the millions.

The Quadratic Truth

Here’s the equation that keeps me up at night:

P(chain) = Π(p_i + D_i − p_i * D_i)

For a single function guarded by DCHECK with D_e = 1.0 (the check is entirely absent in the binary), the probability of failure when bad input arrives is p_i + 1.0 − p_i * 1.0 = 1.0. The check is not there. Bad input always passes through.

For a chain of 4 such functions (which is typical for a config parser like FRRouting’s VTY), the probability compounds:

P(chain) ≈ 1.0 * 1.0 * 1.0 * 1.0 = 1.0

Every enterprise firewall CVE I classified (all 27 of them, across Cisco, Palo Alto, Fortinet, Juniper) has D_e > 0.88. Average D_e = 0.92. Average CVSS = 8.1.

The correlation is not accidental. Debug-only assertions used as safety checks are the root cause of thousands of vulnerabilities, and the industry has not acknowledged this because nobody thought to look at the compiled binary.

What You Can Do

If you’re a developer:

  1. Stop using assert() and DCHECK() for runtime safety. They are debug-only diagnostics. They do not protect users.
  2. Replace every safety-critical assertion with explicit if (condition) { return error; }. The branch must have a side effect (function call, log, error return) — otherwise the compiler may still eliminate it.
  3. Add a CI step that compares debug vs release binary safety check counts. I’ve built a tool for this: vmf binscan --diff debug_binary release_binary. If the release has fewer checks, your CI should fail.
  4. Compile with -fno-omit-frame-pointer and -fstack-protector-strong even in release builds. These flags survive optimization.

If you’re a security team:

  1. Audit your C/C++ binaries, not just your source code. Source-level SAST will find checks that don’t exist in production. Binary-level analysis is the only way to know what actually runs.
  2. Require vendors to publish binary-level safety profiles. When you buy a network appliance, a firewall, or an embedded device, ask: “Show me the safety check count in your production firmware.”
  3. Flag any finding that relies on a debug-only assertion as a vulnerability. Google’s Android VRP already does this — they confirmed my A06 submission (P2/S2) based on this methodology.

If you’re a vendor:

  1. Stop shipping safety-critical code compiled with -O2 or above. Or at minimum, audit which assertions survive.
  2. If you must optimize, use explicit error-handling branches instead of assertions. The compiler cannot remove what it cannot prove is dead.
  3. Your firmware is being analyzed at the binary level by real attackers. They don’t care about your source code. They care about what actually runs.

The Bottom Line

I proved four things in this series:

  1. assert() and DCHECK() provide zero safety in production. They are stripped at any optimization level. The disassembly is unambiguous.

  2. The pattern is systemic. 9,712 stripped guards measured across four major codebases. Millions across all C/C++ open source.

  3. The gap is exploitable. Two ASAN heap overflows in FRRouting. 27 enterprise firewall CVEs with D_e > 0.88. Four Google-confirmed P2/S2 findings in Android.

  4. The fix is known. Explicit if branches with side effects survive all optimization levels. Binary-level CI comparison catches regressions.

The compiler is correct. The standard is correct. The assumption that source-level safety survives to the binary is wrong. For 50 years, the C and C++ communities have reviewed code with the implicit belief that what they see is what runs.

It’s not.

The naked compiler has been stripping your safety nets since the first optimizing compiler shipped. It’s still doing it today. And nobody noticed until someone disassembled the output and counted.


I built the tools I used in this series. vmf binscan is an open-source binary safety scanner. vmf-engine is a vulnerability mining framework. Both available at software-bug-hunter.

If you work at a C/C++ shop and want your firmware audited at the binary level: ishrikantbhosale@gmail.com. I’ll tell you exactly how many of your safety checks survive to production.


The full series:
Experiment 1: When assert() Lies to You
Experiment 2: FRRouting Case Study — Two ASAN-Confirmed PoCs
Experiment 3: Binary-Level Scanning on Enterprise Firmware
Experiment 4: 9,712 Stripped Safety Guards Across 3 Major Codebases

Leave a Comment