dbb0850c7935dec65d3394ef1ddcdc10dafac5cf braney Tue Aug 18 10:03:38 2026 -0700 cheapcgi: parse %hh escapes directly instead of with sscanf, refs #37262 cgiDecode and cgiDecodeFull read each %hh escape with sscanf(in, "%2x", &code). glibc builds a stream over the whole remaining string on every sscanf call, so each call scans to the terminating null. That makes the cost of a decode quadratic in the length of one variable's value. The cost is real on data we already have. A saved session in hgcentraltest holds a single 699 KB hgFind.matches value with 58,930 escapes; decoding it takes 0.29 s of CPU, and cart.c loadHash does it on every load of that session. The database cart has no size cap, so this is not bounded by the 1 MB limit on request input that went in for #37452. At that 1 MB limit a single request still costs over 2 s. Reading the two hex digits directly is a few hundred times faster (430x on the 699 KB value) and never walks past them. Behavior is unchanged for well-formed input: verified byte-identical over the top 200 carts of namedSessionDb, sessionDb and userDb (2.4 million values, 14.7 MB), over an exhaustive sweep of every "%" plus two arbitrary bytes, and over cgiEncode/cgiDecode round trips of all 256 byte values. Decoding now differs only where a "%" is not followed by two hex digits, which nothing legitimate produces - none of the 659,624 escapes in those carts are malformed. The old code was worse there anyway: sscanf skips leading whitespace, so "% 0Z" decoded to a null byte in the middle of the value and silently truncated it. Malformed escapes now yield '?' like other bad input. Also removes the FAST_CGI_DECODE ifdef added earlier on this ticket. It never touched cgiDecode, so it does not describe anything now that the real cost is fixed. Its per-variable caps are superseded by the total input cap from #37452, which aborts with a message rather than dropping a variable silently, and cgiParseNext's variant silently skipped oversized variables for the ENCODE/CIRM tag tools that are its only callers. diff --git src/inc/cheapcgi.h src/inc/cheapcgi.h index d0f3124b87f..8c24bed5668 100644 --- src/inc/cheapcgi.h +++ src/inc/cheapcgi.h @@ -1,39 +1,30 @@ /* cheapcgi.h - turns variables passed from the web form into * something that C understands. * * This file is copyright 2000 Jim Kent, but license is hereby * granted for all use - public, private or commercial. */ #ifndef CHEAPCGI_H #define CHEAPCGI_H #include "dystring.h" #ifndef HASH_H #include "hash.h" #endif -// #define FAST_CGI_DECODE -#ifdef FAST_CGI_DECODE -// 50kB per-variable limit on content length to prevent egregious -// cart-stuffing, whether intentional or accidental. 5kB limit -// on variable names for similar reasons. -#define CGI_VAR_SIZE_LIMIT 50000 -#define CGI_VAR_NAME_LIMIT 5000 -#endif - //============ javascript inline-separation routines =============== void jsInlineFinish(); /* finish outputting accumulated inline javascript */ void jsInline(char *javascript); /* Add text to output file or memory structure */ void jsInlineF(char *format, ...) /* Add javascript text to output file or memory structure */ #if defined(__GNUC__) __attribute__((format(printf, 1, 2))) #endif ;