Length arithmetic in OSPFv3: a source-review case study
A missing pair of parentheses in OpenBSD’s OSPFv3 daemon allowed an oversized Link State Advertisement to reach a downstream parser. The bug had survived years of code review and fuzzing. Source reading found it in an afternoon.
This is the first in a series of case studies from TriageForge. The intent is not to re-announce a finding — the technical disclosure for OSPF6D-001 already sits at stuart-thomas.com/research/ospf6d-001-prefix-length/ and is mirrored in the OpenBSD commit log — but to walk through what the work actually looked like, and what it tells us about how a small lab can usefully contribute to the security posture of widely-deployed software.
The bug
OpenBSD’s ospf6d implements the routing decision engine for OSPFv3, the link-state routing protocol used in IPv6 networks. Like any LSA-driven protocol implementation, ospf6d spends a substantial fraction of its time parsing structured packets received from peers on the network.
The defect lived in usr.sbin/ospf6d/rde_lsdb.c, in the function lsa_check(), on the code path that validates a LSA_TYPE_INTER_A_PREFIX advertisement before handing the payload to lsa_get_prefix(). The remaining-length argument was calculated as:
lsa_get_prefix(..., len - sizeof(lsa->hdr) + sizeof(lsa->data.pref_sum))
The intended meaning is obvious: subtract the combined size of the LSA header and prefix-summary header from the total length, giving the number of bytes that remain for the prefix payload. The expression as written, however, does not do that. C operator precedence groups the two operations left-to-right, so the actual evaluation is:
(len - sizeof(lsa->hdr)) + sizeof(lsa->data.pref_sum)
That value is larger than the true remaining length by twice the size of the prefix-summary header. lsa_get_prefix() consequently reads past the end of the LSA buffer as it walks through what it believes to be prefix entries. On a daemon that processes unauthenticated packets from peers on the same broadcast domain, that is enough to crash ospf6d with a single crafted advertisement.
The fix, applied by Claudio Jeker in commit 8d24b51, is exactly what the original author intended:
lsa_get_prefix(..., len - (sizeof(lsa->hdr) + sizeof(lsa->data.pref_sum)))
One pair of parentheses. Bug class: operator-precedence error in a network parser. The code had been in the tree for years.
Why source review caught what other techniques sometimes do not
It is tempting to ask why this bug had not been found earlier — by automated review, by fuzzing, by static analysis, by careful reading. The honest answer involves all four:
Static analysis
The expression is well-typed. Both subtraction and addition are on values of the same unsigned integer type. There is no integer overflow at the language level, no type confusion, no out-of-bounds index that a compiler or linter can flag from local information alone. A static analyser would have to understand that len denotes a remaining-bytes budget and that the surrounding code treats sizeof(hdr) + sizeof(pref_sum) as a single deduction. That is a semantic property, not a syntactic one, and current open-source static analysers do not reason about packet-length invariants of this kind.
Fuzzing
The LSA type space is large. To exercise the LSA_TYPE_INTER_A_PREFIX path, a fuzzer needs to generate packets that pass earlier validation checks — correct OSPFv3 header, correct LSA type byte, plausible length field — and then produce a payload that is short enough to expose the off-by-some-bytes read. A naive byte-flipping fuzzer would spend almost all its time on packets that are rejected long before they reach lsa_get_prefix(). Structure-aware fuzzing helps, but the harness has to know the LSA grammar in some detail. None of the publicly-known OSPFv3 fuzz corpora reached this site.
Code review by maintainers
The OpenBSD network code is reviewed carefully. Operator-precedence bugs of this kind are nonetheless exactly the class of defect that survives review, because the surrounding code reads correctly. The eye supplies the parentheses that the writer omitted. It takes a reading posture — sit down, read the function with the assumption that something is wrong, do the arithmetic by hand — to see it.
Source review by an outsider
Which is what this finding came out of. An afternoon of reading rde_lsdb.c with no prior assumption about correctness, focused on the length arithmetic at each LSA type branch, with a sheet of paper to keep track of what the byte budget ought to be at each call site. The bug was not subtle once read in that posture. It had simply not been read in that posture before.
The fix and the response
The report was sent to bugs@openbsd.org on 18 May 2026. Claudio Jeker replied the following day with the fix already committed to -current, acknowledged by Theo de Raadt and Theo Buehler. The reply — “Fixed in -current. Thanks for the report.” — was professional, prompt, and entirely characteristic of the OpenBSD project’s handling of network-daemon reports in our experience.
| Date | Event |
|---|---|
| 2026-05-18 | Reported to bugs@openbsd.org |
| 2026-05-19 | Fixed in −current by Claudio Jeker (cjeker@), OK tb@ deraadt@ |
| 2026-05-21 | Technical disclosure published at stuart-thomas.com |
| 2026-05-24 | This case study published |
What this changes for OpenBSD operators
The bug is present in OpenBSD 7.8 and earlier releases that ship ospf6d, and in -current prior to commit 8d24b51. The practical action for operators running OSPFv3 on OpenBSD is:
- If running
-current, update past commit8d24b51(19 May 2026). - If running a release branch, the fix will be in the next release. In the interim, OSPFv3 areas should be deployed only across trusted link-layer segments — which, for a routing protocol that has no authentication at the LSA level, is the operationally correct posture anyway.
- Sites that do not use OSPFv3 (no IPv6 dynamic routing, or static routing only) are not affected.
The defect is link-local: a remote attacker must already be on the OSPFv3 broadcast domain to send the trigger packet. The blast radius is one routing daemon process, which ospf6d’s own restart logic will bring back up; sustained denial-of-service requires sustained presence on the segment. The exposure is real but bounded.
What this changes for our methodology
Three things, in roughly increasing order of importance.
One. Source review remains a viable contribution from a small lab. The defect needed no specialised hardware, no fuzzing fleet, no proprietary corpora. It needed time, a printout of the relevant function, and a willingness to do the arithmetic by hand. That is the kind of work a single researcher can do well, and the kind of work that scales poorly when you try to industrialise it — which is why widely-deployed open-source network code retains these defects in spite of substantial industrial investment in automated discovery.
Two. The parser-arithmetic bug class is worth a focused pass. The same posture — read each LSA-type branch, do the byte budget by hand — turned up three further finds in adjacent OpenBSD daemons during the same audit window. Each one is being handled on its own coordination clock; the technique generalises.
Three. Maintainer-side responsiveness is part of the supply chain. The 18-hour turnaround from report to committed fix is, in our experience, characteristic of how OpenBSD handles network-daemon reports. It is also far from universal. The maintainer disposition — will they read the report carefully, will they respond quickly, will they credit the reporter — is itself a property of the software that operators are buying into. OpenBSD’s posture here is a feature.
What we did not find, and where the limits lie
It bears stating explicitly: this case study covers one finding, in one daemon, on one code branch. It does not say that OpenBSD network code is generally vulnerable. It does not say that source review will find every defect. It does not say that fuzzing or static analysis are inferior — the same audit campaign produced findings where the source-level defect was real but the shipped binary did not crash, because compiler-emitted stack protection contained the consequence. Those findings are written up separately and form part of an ongoing methodology note on the gap between source UB and live exploitability.
What this case study does say is that source reading, by an outsider, with a particular focus on parser arithmetic, remains a productive activity. That is the answer to the question we are most often asked: what does a small independent practice usefully do in a field where the large players have already done so much?
This.