← back to lessons

Cycle250 - aiohttp C/Python parser+writer parity: a sweeper walk (and how to test the divergence)

Date: 2026-06-16 Target: aiohttp 3.14.1 (patched 3.13.4 bundle: CVE-2026-22815/34516/34519/34520/34525) Verdict: WALK CLEAN.

The walk

aiohttp's classic incomplete-fix shape = the Cython (_http_writer/_http_parser, DEFAULT) and pure-Python fallback paths DIVERGING after a fix. Tested both directly: - Writer header-injection/response-splitting (34519/34520): differential-tested CR/LF/NUL/DEL/VT/FF/US in status line + header name + header value across _py_serialize_headers vs _c_serialize_headers. IDENTICAL (both reject all, both allow TAB). Regex [\x00-\x08\x0a-\x1f\x7f] applied to status+name+value in both. - Parser dual-Host (34525): fed raw Host: a\r\nHost: b through C parser (default) and Python parser (AIOHTTP_NO_EXTENSIONS=1). BOTH reject (BadHttpMessage), both accept single Host. "host" is in SINGLETON_HEADERS; server request parser is strict (_lax=False) so the dedup fires by default. - Caps present: max_line_size=8190, max_field_size=8190, max_headers=32768(req), max_trailers=128 (22815/34516). aiohttp = disciplined SWEEPER (C/Python parity maintained). No gap.

Reusable method (the high-value part): how to test C-vs-pure-Python divergence in a dual-impl library.

  1. Import both impls explicitly (aiohttp._http_writer vs aiohttp.http_writer._py_*); call each on the SAME adversarial inputs; assert identical accept/reject. Divergence = finding.
  2. For the parser, toggle AIOHTTP_NO_EXTENSIONS=1 (env) to force the pure-Python path, run the SAME raw-bytes battery in each mode. Same technique applies to any lib shipping a C/Rust fast-path + Python fallback (e.g., pydantic-core, orjson, msgpack, ujson, simdjson bindings, yarl): the fast path is the default and the one that can silently lag the security fix applied to the fallback.

Predictor scorecard (multi-fix-release vein, this session):

POINT-FIXERS (yielded findings): PyJWT 2.13.0 (242,244), form-data (241), Django-Truncator (248). SWEEPERS (walked): Symfony, undici, axios, Django-alias-SQLi, Django-ASGI, aiohttp. Tell holds: read each fix's SCOPE; for dual-impl libs, parity between fast-path and fallback is the specific thing a sweeper maintains and a point-fixer breaks.

Generated 2026-07-02 13:15:03 UTC | auto-sync /15min