diff --git a/src/tools/clu_scgi.c b/src/tools/clu_scgi.c index 85890d68..88bf2beb 100644 --- a/src/tools/clu_scgi.c +++ b/src/tools/clu_scgi.c @@ -62,7 +62,6 @@ #include #include #include - #include #endif /* Read exactly n bytes from socket, handling partial reads */ @@ -178,9 +177,14 @@ int wolfCLU_ScgiReadRequest(SOCKET_T sockfd, byte* buffer, int bufferSz, int headerLen; int pos = 0; byte comma; - + + if (buffer == NULL || req == NULL || bufferSz <= 0) { + WOLFCLU_LOG(WOLFCLU_E0, "Invalid SCGI buffer or arguments"); + return -1; + } + XMEMSET(req, 0, sizeof(ScgiRequest)); - + if (parseNetstringLength(sockfd, &headerLen) != 0) { WOLFCLU_LOG(WOLFCLU_E0, "Failed to parse SCGI netstring length"); return -1; @@ -207,8 +211,8 @@ int wolfCLU_ScgiReadRequest(SOCKET_T sockfd, byte* buffer, int bufferSz, return -1; } - if (req->contentLength < 0 || pos + req->contentLength > bufferSz) { - WOLFCLU_LOG(WOLFCLU_E0, "Invalid SCGI content length: %d", + if (req->contentLength < 0 || req->contentLength > bufferSz - pos) { + WOLFCLU_LOG(WOLFCLU_E0, "Invalid SCGI content length: %d", req->contentLength); return -1; } diff --git a/tests/ocsp-scgi/ocsp-scgi-test.py b/tests/ocsp-scgi/ocsp-scgi-test.py index 3148fa97..16a6d550 100644 --- a/tests/ocsp-scgi/ocsp-scgi-test.py +++ b/tests/ocsp-scgi/ocsp-scgi-test.py @@ -43,10 +43,16 @@ def _find_free_port(): ) -def _scgi_request(host, port, body, path="/ocsp"): - """Send an SCGI request and return the raw response body.""" +def _scgi_request(host, port, body, path="/ocsp", content_length=None): + """Send an SCGI request and return the raw response body. + + content_length overrides the declared CONTENT_LENGTH header without + changing the bytes actually sent, allowing the bounds check in + wolfCLU_ScgiReadRequest() to be exercised with hostile values. + """ + declared = len(body) if content_length is None else content_length headers = ( - "CONTENT_LENGTH\x00" + str(len(body)) + "\x00" + "CONTENT_LENGTH\x00" + str(declared) + "\x00" "SCGI\x001\x00" "REQUEST_METHOD\x00POST\x00" "REQUEST_URI\x00" + path + "\x00" @@ -64,7 +70,10 @@ def _scgi_request(host, port, body, path="/ocsp"): # Read full response chunks = [] while True: - data = sock.recv(4096) + try: + data = sock.recv(4096) + except ConnectionResetError: + break if not data: break chunks.append(data) @@ -291,6 +300,29 @@ def test_08_graceful_shutdown(self): log = f.read() self.assertIn("wolfssl exiting gracefully", log) + def test_09_oversized_content_length(self): + """ Regression: Hostile CONTENT_LENGTH values must be rejected, + not crash. + """ + self._start_responder(INDEX_VALID) + + # bufferSz is 16384; each value exceeds bufferSz - pos and so must + # be rejected. 2147483647 (INT_MAX) exercises the overflow path. + for declared in (16384, 16385, 65535, 2147483647): + with self.subTest(content_length=declared): + raw = _scgi_request("127.0.0.1", self._scgi_port, b"", + content_length=declared) + # Rejected: connection closed with no CGI Status header. + self.assertNotIn(b"Status:", raw, + f"content_length {declared} not rejected") + + # The responder must still be alive and serving valid requests. + self.assertIsNone(self._wolfclu_proc.poll(), + "responder crashed on hostile CONTENT_LENGTH") + rc, out = self._ocsp_query() + self.assertEqual(rc, 0, out) + self.assertIn("good", out.lower()) + if __name__ == "__main__": test_main()