683eb89abf89e45cb554c489382803442e93bc0b jcasper Sun Apr 26 19:22:03 2026 -0700 Adding restrictions on the size of cart content we accept in one request, refs #37452 diff --git src/lib/cheapcgi.c src/lib/cheapcgi.c index 5f120570779..0aaa8ebd1db 100644 --- src/lib/cheapcgi.c +++ src/lib/cheapcgi.c @@ -234,30 +234,51 @@ /* These three variables hold the parsed version of cgi variables. */ static char *inputString = NULL; static unsigned long inputSize; static struct hash *inputHash = NULL; static struct cgiVar *inputList = NULL; static boolean haveCookiesHash = FALSE; static struct hash *cookieHash = NULL; static struct cgiVar *cookieList = NULL; // maximum length of CGI variables to dump to stderr, 0 = switch off static int logCgiVarMaxLen = 0; +/* Default cap on total size of CGI input we'll accept, excluding + * uploaded files. Used if the GB_CGI_INPUT_SIZE_LIMIT env var + * (typically supplied via Apache SetEnv) is unset or invalid. + * See initCgiInput. */ +#define CGI_INPUT_SIZE_LIMIT_DEFAULT (1024*1024) + +static long cgiInputSizeLimit() +/* Return the active CGI input size cap. */ +{ +static long cached = -1; +if (cached < 0) + { + char *s = getenv("GB_CGI_INPUT_SIZE_LIMIT"); + char *end = NULL; + long v = (s != NULL) ? strtol(s, &end, 10) : 0; + cached = (s != NULL && end != s && *end == '\0' && v > 0) + ? v : CGI_INPUT_SIZE_LIMIT_DEFAULT; + } +return cached; +} + /* should cheapcgi use temp files to store uploaded files */ static boolean doUseTempFile = FALSE; void dumpCookieList() /* Print out the cookie list. */ { struct cgiVar *v; for (v=cookieList; v != NULL; v = v->next) printf("%s=%s (%d)\n", v->name, v->val, v->saved); } void useTempFile() /* tell cheapcgi to use temp files */ { doUseTempFile = TRUE; @@ -1148,54 +1169,90 @@ * if dumpStack is TRUE, attempt to dump the stack. */ { if (cgiIsOnWeb()) { // SIGKILL is not trappable or ignorable signal(SIGTERM, catchSignal); signal(SIGHUP, catchSignal); signal(SIGABRT, catchSignal); signal(SIGSEGV, catchSignal); signal(SIGFPE, catchSignal); signal(SIGBUS, catchSignal); dumpStackOnSignal = dumpStack; } } +static void cgiInputTooLargeAbort() +/* Bail out on oversized CGI input with a generic error message */ +{ +htmlPushEarlyHandlers(); +errAbort("Bad input"); +} static void initCgiInput() /* Initialize CGI input stuff. After this CGI vars are * stored in an internal hash/list regardless of how they * were passed to the program. */ { char* s; if (inputString != NULL) return; _cgiFindInput(NULL); +long sizeLimit = cgiInputSizeLimit(); + +// strnlen so we don't walk the whole thing just to learn it's over the cap +if (strnlen(inputString, sizeLimit + 1) > sizeLimit) + cgiInputTooLargeAbort(); + +// A couple of raw content checks to reject the simpler bot attacks +if (stringIn("xp_cmdshell", inputString) || stringIn("information_schema", inputString)) + cgiInputTooLargeAbort(); + #ifndef GBROWSE /* check to see if the input is a multipart form */ s = getenv("CONTENT_TYPE"); if (s != NULL && startsWith("multipart/form-data", s)) { cgiParseMultipart(&inputHash, &inputList); } #endif /* GBROWSE */ cgiParseInputAbort(inputString, &inputHash, &inputList); +/* Total-size check across all parsed variables, including multipart bodies + * (which are placed directly in the hash/list). Skip entries whose value is + * file-upload content so custom tracks still work; cgiParseMultipart adds a + * sibling "<name>__filename" hash entry for every uploaded file part, so + * that's how we'll detect upload bytes that don't count against the cap. */ +unsigned long totalSize = 0; +struct cgiVar *v; +for (v = inputList; v != NULL; v = v->next) + { + char filenameKey[1024]; + safef(filenameKey, sizeof(filenameKey), "%s__filename", v->name); + if (hashLookup(inputHash, filenameKey) != NULL) + continue; + totalSize += strlen(v->name); + if (isNotEmpty(v->val)) + totalSize += strlen(v->val); + if (totalSize > sizeLimit) + cgiInputTooLargeAbort(); + } + /* now parse the cookies */ parseCookies(&cookieHash, &cookieList); /* Set environment variables CGIs to enable sql tracing and/or profiling */ s = cgiOptionalString("JKSQL_TRACE"); if (s != NULL) envUpdate("JKSQL_TRACE", s); s = cgiOptionalString("JKSQL_PROF"); if (s != NULL) envUpdate("JKSQL_PROF", s); } struct cgiVar *cgiVarList() /* return the list of cgiVar's */