September 6, 2024·4 min read·engineeringpythonsecurityparsinghttp

Python 3.12.6 removes repeated scans from cookie decoding

Python 3.12.6 is an expedited security release. Its cookie parser fix shows how two simple searches can create quadratic work even without a complicated regular expression.

Two searches through the same string can be individually reasonable and collectively expensive. Python's cookie decoder searched for an octal escape and a general backslash escape on each loop iteration. When one kind of escape was absent, the unsuccessful search could traverse the remaining string repeatedly. Python 3.12.6, released September 6, replaces that arrangement as part of an expedited security release.

The release page identifies the cookie issue as CVE-2024-7592 and describes quadratic complexity when parsing double-quoted values containing backslashes. It also lists fixes involving archive parsing and bundled Expat. These are security corrections in a maintained interpreter branch, rather than new Python language features. The cookie change is useful to examine because the defect follows directly from how two search results were combined.

I rely on Python's libraries for work that should not require another private implementation. That makes small standard-library parsers worth understanding. Their convenience concentrates parsing behavior in one place, including behavior exercised on externally supplied text. A resource-consumption defect in that shared implementation can matter even when application code contains no explicit expensive loop.

The old loop searched twice before choosing an escape

The Python 3.12.5 source defines separate patterns for the two escape forms. _OctalPatt finds a backslash followed by a restricted three-digit octal sequence. _QuotePatt finds a backslash followed by a character. _unquote first checks for surrounding double quotes, removes them, and then repeatedly searches the remaining input with both patterns.

After obtaining both results, the code chooses whichever applicable match starts first. It copies the preceding text into a result list and advances past the escape. An octal match becomes the corresponding character. A general escape contributes the escaped character. This preserves a meaningful decoding rule, but it performs searches for both alternatives before deciding which one to consume.

Consider a long value containing many general backslash escapes and no octal escapes. Searching for the next general escape can succeed near the current position. Searching for an octal escape can examine the whole remaining suffix and fail. Advancing past the general escape then repeats the unsuccessful octal search over a slightly shorter suffix. The total work accumulates across those overlapping searches.

The important point is that no single iteration needs an unusually complex expression. Repeated linear searches over successively shorter suffixes can produce quadratic total work. This is an algorithmic interaction between the loop and its searches. Describing it only as a regular-expression problem would omit where the repeated work comes from.

The replacement matches both forms together

The 3.12.6 implementation replaces the separate searches with one compiled substitution pattern. Its alternatives recognize the octal form or a general escaped character. A replacement function checks which group matched. For octal escapes, it converts the captured digits using base eight. Otherwise, it returns the captured character.

This keeps the decoding choices together in one traversal mechanism. The code no longer needs an outer loop that asks each pattern to search independently from the same offset. It still removes surrounding quotes before substitution and leaves unquoted values outside this decoding path. The behavioral purpose remains decoding quoted cookie values, while the work organization changes.

This is a useful repair pattern because it removes redundant search responsibility. A loop coordinating competing searches has to reason about what each unsuccessful search already inspected. A combined pattern can identify the next applicable escape without repeatedly rediscovering the absence of another kind. The improvement comes from eliminating that repeated examination of the suffix.

The tagged release notes also remove backtracking in tarfile header parsing for hdrcharset, PAX, and GNU sparse headers. They describe a more targeted correction for malformed names handled through zipfile.Path, preserving legitimate characters while avoiding infinite loops. These changes address different parsing mechanisms, but each concerns the work performed while interpreting an input.

A parser's output can be correct for ordinary examples while its behavior on unusual input remains unsuitable. Cookie decoding illustrates the repeated-work case. Infinite iteration is another failure mode, and overbroad sanitization can introduce a compatibility defect while trying to fix it. The release's archive changes show why the correction needs to preserve useful inputs as well as stop the pathological behavior.

I consider this release more consequential than its maintenance version number suggests. It repairs resource behavior in library operations that callers may treat as routine transformations. For cookie decoding, the mechanism is specific and visible in the source comparison. Replacing two independently repeated searches with a combined substitution removes the redundant suffix scans while retaining the distinction between octal and ordinary escaped characters.