Spanification and the Unsafe Buffers Discipline
Replace raw buffer-pointer arithmetic with size-carrying containers and views, then let Clang’s -Wunsafe-buffer-usage warning make the remaining unsafe operations visible at compile time.
Also known as: Safe Buffers, Unsafe Buffers errors, bounds-safe C++ migration
Spanification is Chromium’s name for a specific memory-safety move: stop treating a pointer and a size as separate facts. A raw T* can point at the first element of a buffer, but it doesn’t carry the buffer’s length. base::span<T>, std::vector<T>, std::array<T, N>, and base::HeapArray<T> carry the extent with the access path, so the compiler and library hardening can keep more buffer operations inside their known bounds.
Context
Chromium’s memory-safety program has several layers because memory unsafety is not one bug class. The Chromium security team says around 70 percent of serious security bugs are memory-safety problems, and about half of those are use-after-free bugs. MiraclePtr and BackupRefPtr reduce the temporal class: a pointer remains after the object it names has been freed. Spanification reduces the spatial class: code reads or writes outside the buffer it meant to address.
The campaign became public in 2024 through the chromium-dev announcement “Introducing Spanification.” The announcement framed the practice as a project-wide conversion from raw array pointers to base::span, std::vector, and base::HeapArray. A globally enabled Clang warning backed the conversion. The important detail is the opt-out shape. -Wunsafe-buffer-usage is enabled for Chromium, but much of the codebase began on an opt-out list. The work is to remove paths from that list, fix the warnings, and leave the converted code under the compiler’s gate.
For downstream teams, this is not an upstream style preference. A Chromium fork, Electron integration, or embedded runtime with custom C++ near untrusted bytes inherits the same hazard. If that code keeps pointer arithmetic outside Safe Buffers, it can reintroduce the out-of-bounds exposure upstream is trying to remove.
Problem
The recurring failure is not that C++ developers forget arrays have lengths. It’s that the length and the pointer travel through the program as separate values. A function receives uint8_t* data and size_t size; another helper advances data + offset; a third helper copies count bytes. Each step may look reasonable in isolation. The vulnerability appears when one step trusts a stale size, wraps an offset, or keeps using a pointer after the operation that proved its bounds has disappeared.
Code review is a weak defense against that shape. Reviewers can catch obvious ptr[i] mistakes, but they cannot reliably reconstruct every pointer and length pair across a large patch. Tests and fuzzers find individual bugs after they exist. Chromium needed a discipline that makes the unsafe operation unusual, visible, and mechanically rejected.
Forces
- The existing codebase is mostly C++. A memory-safe rewrite cannot happen at the scale of Chromium’s production surface in one release cycle.
- Raw pointer operations are cheap and familiar. The pattern has to preserve ordinary performance where possible and avoid turning every read into a bespoke helper call.
- Not every pointer is a buffer. A
T*to one object is a different risk from aT*used with arithmetic or indexing. The gate focuses on usage, not all pointers. - Conversions have to be incremental. Chromium must keep shipping while directories are removed from the opt-out list one at a time.
- C and third-party APIs still exist. Some interfaces require pointer and size pairs, so the discipline needs explicit, auditable escape hatches instead of pretending the ecosystem is already converted.
Solution
Move buffer operations onto types that carry bounds, then make raw buffer use a build-visible exception. A converted Chromium path should pass data as base::span<T> or another size-carrying view when another object owns the storage. When the code owns the storage, use std::vector<T>, std::array<T, N>, or base::HeapArray<T>. The access path now knows both the starting address and the extent. Library hardening can check indexing, and Clang can warn when code falls back to pointer arithmetic or array indexing on raw pointers.
The compiler warning is the forcing function. Clang’s Safe Buffers model emits -Wunsafe-buffer-usage for operations such as pointer arithmetic, raw-pointer indexing, bounds-unsafe C library calls, and array-like smart-pointer operations that cannot be made fully safe under the C++ standard rules. Chromium enables that warning globally, then uses opt-outs and local migration to keep the work tractable. A converted directory is safer not because every contributor remembers spanification, but because the next unsafe buffer operation fails where the contributor can see it. Calls such as memcpy(), std::copy(), and std::ranges::copy() should move behind span-based APIs such as base::span::copy_from(), while fill operations should use range-aware helpers instead of raw pointer loops.
The migration rule is not “wrap every pointer in a span at the nearest call site.” A span built from an arbitrary pointer and size improves the local access. It cannot prove the size was true. The better conversion keeps bounds information from the original container as long as possible. If the code owns the allocation, prefer an owning container. If it passes a window into existing storage, pass a span derived from the container. If a C API boundary forces the manual pair, isolate it, annotate it with UNSAFE_BUFFERS(), and keep the unsafe region small enough for review.
UNSAFE_BUFFERS() and #pragma clang unsafe_buffer_usage are not design alternatives. They mark a boundary the code has not yet converted, a C interop case, or a temporary migration point. Treat each one as reviewable security debt.
How It Plays Out
A renderer-facing parser receives a byte range from a Mojo message. The old shape passes const uint8_t* bytes and size_t length through several helpers, then one helper reads bytes[offset + i] after an earlier bounds check. The converted shape passes base::span<const uint8_t> through the helper chain. Each helper receives a view whose extent travels with it, and the final read uses span indexing or a subspan derived from a checked offset. The review question becomes: where did this span come from, and is the offset calculation checked?
A C-like library still requires void* and byte length. The Chromium wrapper does not let raw pointers spread through the caller. It accepts a span, checks or narrows the length at the wrapper boundary, calls the API inside the smallest possible UNSAFE_BUFFERS() region, and returns to span-shaped code immediately. The unsafe operation exists, but it has a label and a boundary. A reviewer can audit it without searching the whole call graph.
A downstream browser fork carries custom enterprise-policy parsing code. Upstream has removed the relevant directory from the unsafe-buffer opt-out list, but the fork’s local patch still indexes through char* cursor. The next merge starts failing under -Wunsafe-buffer-usage. The failure is doing useful work: it tells the downstream team that their patch is not merely in conflict with formatting preference. It is outside Chromium’s current spatial-memory-safety discipline and needs a container or view conversion before it should land.
Consequences
Benefits. Spanification changes spatial-memory-safety review from a best-effort reading exercise into a compiler-enforced convention. It doesn’t prove every buffer access is correct, but it reduces the number of places where a missing length check can hide. The bounds information stays closer to the memory access. That helps human reviewers, static analysis, fuzzing triage, and AI coding agents operating on local patches.
The pattern also narrows the exploit-chain surface. Out-of-bounds reads and writes are useful first-link primitives because they turn a parser, decoder, or renderer-internal data structure into attacker-shaped memory access. When a path uses a span derived from a real container, the attacker has fewer chances to separate the pointer from its length. That pairs naturally with IPC Integer Type Discipline: checked arithmetic decides which slice is legal, and spanification keeps the slice tied to the data it names.
For organizations shipping Chromium-derived products, the discipline gives a concrete audit question. Do your custom C++ patches run under upstream’s unsafe-buffer warning, or are they living behind local suppressions? A fork that keeps broad opt-outs, broad UNSAFE_BUFFERS() regions, or raw pointer arithmetic near hostile input is preserving a bug class upstream is trying to retire.
Liabilities. The migration has cost. Converting pointer-heavy code can spread through interfaces because the safer type wants to appear at the boundary, not only at the line that indexes the buffer. That can turn a small fix into a local API change. It also exposes mismatches where a function did not really know the buffer length it claimed to know. Those are good findings, but they are still work.
The discipline can also create false confidence if teams treat any span as proof of safety. A base::span built from the wrong pointer and size is only a better-shaped lie. It gives the bounds checker a number, not an oracle. The gain comes from keeping bounds attached from the owning container or a validated boundary, and from making every manual reconstruction rare enough to review.
Notes for Agent Context
When changing Chromium C++ that reads or writes a buffer, don’t introduce raw pointer arithmetic, raw-pointer indexing, or pointer-and-size plumbing where a size-carrying type can express the same data. Prefer base::span<T> or base::span<const T> for non-owning views and std::vector, std::array, or base::HeapArray for owned buffers; derive spans from real containers instead of reconstructing them from unrelated pointer and size values. Replace raw copy and fill calls with span- or range-aware APIs when the called function can express the same operation. If a C-like or third-party API forces a pointer/length pair, keep the unsafe region in a small wrapper, use the documented UNSAFE_BUFFERS() or pragma only there, and explain why the boundary isn’t yet span-shaped. Treat a -Wunsafe-buffer-usage diagnostic as a security-relevant design signal, not as a warning to suppress.
Related Articles
Sources
Chromium’s Preventing OOB through Unsafe Buffers errors document is the contributor-facing reference for spanification recipes, UNSAFE_BUFFERS(), and the set of safe containers and view types the project expects. Dana Jansens’s chromium-dev announcement “Introducing Spanification”, posted June 28, 2024, records the project rollout: global -Wunsafe-buffer-usage, broad initial opt-outs, and the migration from array pointers to base::span, std::vector, and base::HeapArray.
Clang’s C++ Safe Buffers documentation defines the compiler-side model behind -Wunsafe-buffer-usage: unsafe raw-pointer buffer operations are warnings, bounds information should travel through safe containers and views, and suppressions should mark interop or migration boundaries. Chromium’s Memory safety strategy page supplies the broader security rationale: around 70 percent of high- and critical-severity Chrome security bugs in the measured corpus are memory-safety bugs, which is why the project invests in C++ developer-experience changes, process isolation, and safer-language adoption at the same time.
Technical Drill-Down
docs/unsafe_buffers.md(pinned631f4e1) — Chromium’s Unsafe Buffers guide: spanification recipes, conversion macros, safe-container recommendations, and migration notes.base/containers/span.h(pinned631f4e1) — Chromium’sbase::spanimplementation surface and helper APIs.base/containers/heap_array.h(pinned631f4e1) — the owning heap-backed array type the spanification announcement names alongsidebase::spanandstd::vector.base/compiler_specific.h(pinned631f4e1) — macro definitions for compiler-specific annotations, including unsafe-buffer migration markers.- chromium-dev: “Introducing Spanification,” June 28, 2024 — rollout announcement for the project-wide conversion and the global warning plus opt-out model.
- Clang C++ Safe Buffers documentation — upstream compiler model for
-Wunsafe-buffer-usage, safe containers and views, and suppressions. - Chromium Memory Safety strategy — project-level rationale for reducing memory-safety bugs at the source rather than relying only on process isolation.