divide-by-zero · git:20261017.a78401f · 2026-10-17 · sha256 d99184f21b33654b

divide-by-zero git:20261017.a78401fA

Immutable. This exact content never changes and is served at /api/v1/blob/d99184f21b33654b.

---
name: divide-by-zero
description: Classify divide-by-zero / modulo-by-zero evidence — a divisor that is not a provably non-zero constant. Maps to CWE-369.
license: MIT
compatibility: opencode,claude code,DSH
metadata:
  cwe: CWE-369
  severity: HIGH
  domain: semantic
---

## Divide By Zero Analysis (CWE-369)

### Evidence Pattern
A divide-by-zero candidate has:
- **divide_by_zero**: A `/` or `%` binary expression whose right operand is a variable, call result, or compound expression (not a non-zero literal, not `sizeof`)
- **divisor_interval**: (when present) the range engine's own conclusion for the divisor — a tighter-than-top interval that still includes 0, e.g. `divisor d interval is [0, 1] (includes 0)`. Zero is reachable inside that interval; the classifier judges whether a reaching path is real
- **param_flow**: (when present) the call-site census for a parameter divisor the pipeline could not settle, e.g. `divisor d is a parameter: 2 direct call sites, 1 pass non-zero constants`. Judge the callers that pass a non-constant
- **call_path**: The function is reachable from an entry point

### Hint Tokens (Verdict Basis)

| Token | Meaning | Your action |
|-------|---------|-------------|
| `divisor@<shape>` | the divisor's syntactic shape (`bare`/`field`/`global`/`call`/`compound`) | a `divisor@bare` row settles from the `Source` column alone; `call`/`compound` need the Code Context |
| `range@[lo,hi]` | the range engine's proved interval for the divisor — it contains 0 (else the filter would have dropped the row) | judge whether the 0 endpoint is actually reachable |
| `callers@<summary>` | the direct call-site census for a parameter divisor | check the callers passing non-constants for a 0 argument |

The shape plus `range@` is the verdict basis: an interval whose only 0
endpoint is an unreachable constant bound is dismissed; a caller-counted
non-constant flow toward 0 is confirmed.

### Detection Logic
1. Find all `binary_expression` nodes with `/` or `%` operator
2. Take the right operand as the divisor
3. Skip a non-zero numeric literal or any `sizeof(...)` (compile-time constant)
4. Emit `DIVIDE_BY_ZERO` otherwise

### Classification Rules

| Condition | Classification |
|-----------|---------------|
| `x / (a - b)` where `a == b` is reachable | **confirmed** |
| `x / n` where `n` is attacker-controlled (argv / getenv / recv length) and unguarded | **confirmed** (the attacker can make n == 0) |
| `x / n` where `n` is a bounded local, a loop counter, or a checked length | **false-positive** (n cannot be zero on any reachable path) |
| `x / n` where `n`'s origin cannot be settled from the context | **dismissed** |
| `x / 2`, `x / sizeof(T)` | **false-positive** (constant, safe) |

> **Pipeline pre-confirms, so these never reach this classification step:**
> - `x / obj->field` or `x % obj.field` (a struct/object field chain) — a
>   config/capacity value whose zero-invariant is established at object
>   initialization, not at the use site. The range filter upgrades these to
>   `confirmed` (auto-confirm) — a defensive-check gap to fix, not an AI question.
> - `x / g_name` (a module-global divisor) — same reasoning, also auto-confirmed.
> - `x / d` where the interval analysis proves `d == 0` (`d = 0; x / d`) — a
>   certain divide-by-zero, auto-confirmed.

### Common False Positives
- `x / 100` — constant divisor (safe)
- `x % sizeof(int)` — compile-time constant (safe)
- A divisor that is checked `if (n == 0) return;` immediately before (needs flow verification)
- A divisor reassigned to a provably non-zero constant before the division (`d = 0; d = 1; x / d`) — the convergence range flow proves it non-zero and suppresses it

### Severity Matrix

`status` and `severity` are independent: `status` = evidence verdict
(confirmed/dismissed), `severity` = impact (low/medium/high/critical).
`metadata.severity` is the type default, not a ceiling. The verdict is binary (confirmed/dismissed); dismissed → `low`.

| Shape | Severity |
|-------|----------|
| Divisor attacker-controlled (argv/getenv/recv) and unguarded → reachable crash | HIGH |
| Provable certain divide-by-zero (`d = 0; x / d`) | HIGH |
| Auto-confirmed struct-field / global divisor (defensive-check gap) | MEDIUM |
| Divisor origin unsettled | MEDIUM (dismissed) |