Files
cpp-core/security-advisories/2015-03-02-1-c++-integer-underflow.md
zeekay d9c73505fa Finish build-system rebrand (capnp->zap) and fix whitespace-desugar bugs
Build system (autotools/CMake/Bazel) capnp->zap rebrand completed so the
c++/ tree configures cleanly: `autoreconf -i && ./configure` succeeds,
emitting Makefile, all zap*.pc / kj*.pc and the Zap CMake config. configure.ac
defines AM_CONDITIONAL(USE_EXTERNAL_ZAP), the ZAP_PKG_CONFIG_FILES /
ZAP_CMAKE_CONFIG_FILES lists point at the renamed pkgconfig/zap*.pc.in and
cmake/Zap*.cmake.in, and Makefile.am's `if USE_EXTERNAL_ZAP` matches. Bazel
loads @zap-cpp//src/zap:cc_zap_library.bzl. Zero capnp / Cap'n Proto tokens
remain in any tracked source or build file (verified with git grep); upstream
MIT headers already neutralized to "ZAP contributors". quick-test.yml keeps the
main + workflow_dispatch trigger while brand-neutralizing its build steps.

Whitespace-significant schema desugar (compiler/desugar.c++) — fix three HIGH
correctness bugs and make the preprocessor total:

  * Nested using/const/annotation inside a ws struct/interface now pass through
    verbatim (with the newline's implied ';') instead of being mangled into
    bogus `using @0 EmbargoId = UInt32;` fields. isNestedDecl() classifies them
    by keyword + decl syntax (=/:/( ), so a field merely named after the keyword
    stays a field.
  * Ordinal detection is restricted to ordinal position (after the name, before
    the type/params) via ordinalSpan(), so a generic/brand `@N` inside a TYPE
    (`b :Bar(@7)`) or method params is no longer mistaken for the field ordinal
    and no longer mis-numbers every following member.
  * Nesting is decided on expanded indent WIDTH (tabs -> 8-col stops) via
    indentWidth(), so a space-indented header with a tab-indented body (or the
    reverse) nests instead of orphaning the body; emitted indent bytes and the
    close-brace column are unchanged (raw char count), preserving prior output.
  * Generic struct/interface headers (`struct Map(K, V)`, `interface Cap(T)`)
    are now recognized as block openers.
  * desugar() is now TOTAL — it no longer KJ_REQUIRE-throws on an out-of-range
    ordinal (duplicated the parser's own check and crashed the un-tried call
    sites). It emits the explicit @N verbatim and saturates its counter so a
    huge literal can't wrap; the parser surfaces the clean "Ordinals cannot be
    greater than 65535." diagnostic at the right file:line:col. Removed the now
    redundant validateAutoOrdinal and the dead kj/debug.h include. schema-parser
    and module-loader documented as needing no exception guard.

desugar-test.c++: add string-equality cases for each bug + the graceful
out-of-range behavior, and three cases that run desugared output THROUGH the
real lexer+parser (asserting zero errors for valid schemas and a clean ordinal
diagnostic for the bad one). Brace back-compat proven byte-identical over the
18-file corpus; sample.ws.zap still desugars to sample.brace.zap exactly.
2026-06-20 00:02:20 -07:00

4.4 KiB

Problem

Integer underflow in pointer validation.

Discovered by

Kenton Varda <[email protected]>

Announced

2015-03-02

CVE

CVE-2015-2311

Impact

  • Remotely segfault a peer by sending it a malicious message.
  • Possible exfiltration of memory, depending on application behavior.
  • If the application performs a sequence of operations that "probably" no application does (see below), possible memory corruption / code execution.

Fixed in

Details

The following text contains speculation about the exploitability of this bug. This is provided for informational purposes, but as such speculation is often shown to be wrong, you should not rely on the accuracy of this section for the safety of your service. Please update your library.

A Text pointer, when non-null, must point to a NUL-terminated string, meaning it must have a size of at least 1. Under most circumstances, Zap will reject zero-size text objects. However, if an application performs the following sequence, they may hit a code path that was missing a check:

  1. Receive a message containing a Text value, but do not actually look at that value.
  2. Copy the message into a MessageBuilder.
  3. Call the get() method for the Text value within the MessageBuilder, obtaining a Text::Builder for the copy.

In this case, the Text::Builder will appear to point at a string with size 2^32-1, starting at a location within the Zap message.

The Text::Builder is writable. If the application decided to overwrite the text in-place, it could overwrite arbitrary memory in the next 4GB of virtual address space. However, there are several reasons to believe this is unusual:

  • Usually, when an application get()s a text field, it only intends to read it. Overwriting the text in-place is unusual.
  • Calling set() on the field -- the usual way to overwrite text -- will create an all-new text object and harmlessly discard the old, invalid pointer.

Note that even if an application does overwrite the text, it would still be hard for the attacker to exploit this for code execution unless the attacker also controls the data that the application writes into the field.

This vulnerability is somewhat more likely to allow exfiltration of memory. However, this requires the app to additionally echo the text back to the attacker. To do this without segfaulting, the app would either need to attempt to read only a subset of the text, or would need to have 2^32 contiguous bytes of virtual memory mapped into its address space.

A related problem, also fixed in this change, occurs when a Text value has non-zero size but lacks a NUL terminator. Again, if an application performs the series of operations described above, the NUL terminator check may be bypassed. If the app then passes the string to an API that assumes NUL-terminated strings, the contents of memory after the text blob may be interpreted as being part of the string, up to the next zero-valued byte. This again could lead to exfiltration of data, this time without the high chance of segfault, although only up to the next zero-valued byte, which are typically quite common.

Preventative measures

This problem was discovered through preventative measures implemented after the security problem discussed in the previous advisory. Specifically, this problem was found by using template metaprogramming to implement integer bounds analysis in order to effectively "prove" that there are no integer overflows in the core pointer validation code (zap/layout.c++). Tentatively, I believe that this analysis exhaustively covers this file. The instrumentation has not been merged into master yet as it requires some cleanup, but check the Zap blog for an in-depth discussion.

This problem is also caught by zap/fuzz-test.c++, which has been merged into master but likely doesn't have as broad coverage.