From 67f420ab8aedaa638de0e93d026e84d1831c0870 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:02:51 -0700 Subject: [PATCH 01/16] Narrow the decoded JSON length in the v3 round-trip test log_entry_to_json returns -1 on failure, and Catch2's REQUIRE does not constrain the value for static analysis, so the int length reached std::string's size_t parameter still possibly negative. Convert once, after the check, through a named size_t. Coverity CID 1660665. --- src/traffic_logcat/unit-tests/test_LogEntryJson.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc index c3ef245ca97..fd6a776c6d6 100644 --- a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc +++ b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc @@ -112,7 +112,9 @@ TEST_CASE("v3 generic decode round-trip with IPv4, STRING, INT", "[logcat][v3]") char out[256]; int n = log_entry_to_json(entry, seg.header(), out, sizeof(out)); REQUIRE(n > 0); - CHECK(std::string(out, n) == R"({"chi":"192.0.2.10","cqu":"GET /index.html","pssc":200})"); + const size_t len = static_cast(n); + + CHECK(std::string(out, len) == R"({"chi":"192.0.2.10","cqu":"GET /index.html","pssc":200})"); } TEST_CASE("v3 generic decode handles IPv6 and unspecified IP", "[logcat][v3]") From 848933b49ae5d4007c1b19424c67053649f63602 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:03:02 -0700 Subject: [PATCH 02/16] Narrow the decoded JSON length in the single-IP decode helper The decode_single_ip lambda returned std::string(out, n) where n is the int result of log_entry_to_json, which is -1 on failure; Catch2's REQUIRE does not constrain it for static analysis. Convert once, after the check, through a named size_t. Coverity CID 1660670. --- src/traffic_logcat/unit-tests/test_LogEntryJson.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc index fd6a776c6d6..ce3d20d5927 100644 --- a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc +++ b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc @@ -131,7 +131,9 @@ TEST_CASE("v3 generic decode handles IPv6 and unspecified IP", "[logcat][v3]") char out[256]; int n = log_entry_to_json(entry, seg.header(), out, sizeof(out)); REQUIRE(n > 0); - return std::string(out, n); + const size_t len = static_cast(n); + + return std::string(out, len); }; SECTION("IPv6") From 114ad0a4fe7bb92e522ab8ac8bf17d8407e1f222 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:03:11 -0700 Subject: [PATCH 03/16] Narrow the decoded JSON length in the structural-escape test The int returned by log_entry_to_json is -1 on failure and Catch2's REQUIRE does not constrain it for static analysis, so it reached std::string's size_t parameter still possibly negative. Convert once, after the check, through a named size_t. Coverity CID 1660666. --- src/traffic_logcat/unit-tests/test_LogEntryJson.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc index ce3d20d5927..1ef6353cb21 100644 --- a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc +++ b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc @@ -185,7 +185,9 @@ TEST_CASE("v3 generic decode escapes JSON structural characters in strings", "[l char out[256]; int n = log_entry_to_json(entry, seg.header(), out, sizeof(out)); REQUIRE(n > 0); - CHECK(std::string(out, n) == R"({"msg":"he\"llo\\x"})"); + const size_t len = static_cast(n); + + CHECK(std::string(out, len) == R"({"msg":"he\"llo\\x"})"); } TEST_CASE("v3 generic decode escapes control characters in strings", "[logcat][v3]") From 839fe45fe80e1121c9a887716d4dd107f2f576f3 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:03:35 -0700 Subject: [PATCH 04/16] Narrow the decoded JSON length in the control-character test The int returned by log_entry_to_json is -1 on failure and Catch2's REQUIRE does not constrain it for static analysis, so it reached std::string's size_t parameter still possibly negative. Convert once, after the check, through a named size_t. Coverity CID 1660668. --- src/traffic_logcat/unit-tests/test_LogEntryJson.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc index 1ef6353cb21..a40857f0bb3 100644 --- a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc +++ b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc @@ -211,7 +211,9 @@ TEST_CASE("v3 generic decode escapes control characters in strings", "[logcat][v char out[256]; int n = log_entry_to_json(entry, seg.header(), out, sizeof(out)); REQUIRE(n > 0); - CHECK(std::string(out, n) == R"({"msg":"a\nb\tc\u0001d"})"); + const size_t len = static_cast(n); + + CHECK(std::string(out, len) == R"({"msg":"a\nb\tc\u0001d"})"); } TEST_CASE("v3 generic decode escapes JSON structural characters in symbol keys", "[logcat][v3]") From 8a398bcd9751bb9b8bd8f57ab6521bf01434fae7 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:03:44 -0700 Subject: [PATCH 05/16] Narrow the decoded JSON length in the symbol-key escape test The int returned by log_entry_to_json is -1 on failure and Catch2's REQUIRE does not constrain it for static analysis, so it reached std::string's size_t parameter still possibly negative. Convert once, after the check, through a named size_t. Coverity CID 1660669. --- src/traffic_logcat/unit-tests/test_LogEntryJson.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc index a40857f0bb3..9c00f8541ac 100644 --- a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc +++ b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc @@ -234,7 +234,9 @@ TEST_CASE("v3 generic decode escapes JSON structural characters in symbol keys", char out[256]; int n = log_entry_to_json(entry, seg.header(), out, sizeof(out)); REQUIRE(n > 0); - CHECK(std::string(out, n) == R"({"a\"b\\c":7})"); + const size_t len = static_cast(n); + + CHECK(std::string(out, len) == R"({"a\"b\\c":7})"); } TEST_CASE("v3 generic decode rejects an unknown type code", "[logcat][v3]") From 23f6072044fa1bba475a7fb7f32cd90765791b43 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:03:54 -0700 Subject: [PATCH 06/16] Narrow the decoded JSON length in the dINT decode test The int returned by log_entry_to_json is -1 on failure and Catch2's REQUIRE does not constrain it for static analysis, so it reached std::string's size_t parameter still possibly negative. Convert once, after the check, through a named size_t. Coverity CID 1660667. --- src/traffic_logcat/unit-tests/test_LogEntryJson.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc index 9c00f8541ac..46e1306f027 100644 --- a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc +++ b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc @@ -319,7 +319,9 @@ TEST_CASE("v3 generic decode reads a dINT field (16 bytes)", "[logcat][v3]") char out[256]; int n = log_entry_to_json(entry, seg.header(), out, sizeof(out)); REQUIRE(n > 0); - CHECK(std::string(out, n) == R"({"pair":[1,1]})"); + const size_t len = static_cast(n); + + CHECK(std::string(out, len) == R"({"pair":[1,1]})"); } TEST_CASE("v3 generic decode rejects a truncated dINT field", "[logcat][v3]") From bf535a781c4efdcf49c15f300d05e3681d7c62af Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:04:04 -0700 Subject: [PATCH 07/16] Narrow the decoded JSON length in the raw-values test The int returned by log_entry_to_json is -1 on failure and Catch2's REQUIRE does not constrain it for static analysis, so it reached std::string's size_t parameter still possibly negative. Convert once, after the check, through a named size_t. Coverity CID 1660671. --- src/traffic_logcat/unit-tests/test_LogEntryJson.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc index 46e1306f027..ee96c8feafe 100644 --- a/src/traffic_logcat/unit-tests/test_LogEntryJson.cc +++ b/src/traffic_logcat/unit-tests/test_LogEntryJson.cc @@ -373,5 +373,7 @@ TEST_CASE("v3 generic decode emits raw values, never field semantics", "[logcat] char out[256]; int n = log_entry_to_json(entry, seg.header(), out, sizeof(out)); REQUIRE(n > 0); - CHECK(std::string(out, n) == R"({"crc":2,"cqts":1700000000})"); + const size_t len = static_cast(n); + + CHECK(std::string(out, len) == R"({"crc":2,"cqts":1700000000})"); } From 7f279f2b5817d31aa6bb3fbc1da9e8ccc29a8ff7 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:04:38 -0700 Subject: [PATCH 08/16] Narrow the unmarshalled HTTP version length before use LogAccess::unmarshal_http_version returns -1 when the destination is too small, and Catch2's REQUIRE does not constrain the value for static analysis, so the int length reached std::string's size_t parameter still possibly negative. Convert once, after the check. Coverity CID 1685407. --- src/proxy/logging/unit-tests/test_LogAccess.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/proxy/logging/unit-tests/test_LogAccess.cc b/src/proxy/logging/unit-tests/test_LogAccess.cc index 0a33a6d8df7..a4c622cbadf 100644 --- a/src/proxy/logging/unit-tests/test_LogAccess.cc +++ b/src/proxy/logging/unit-tests/test_LogAccess.cc @@ -236,7 +236,9 @@ TEST_CASE("LogAccess marshals response HTTP versions as compact strings", "[LogA int len = LogAccess::unmarshal_http_version(&src, dest, sizeof(dest)); REQUIRE(len > 0); - CHECK(std::string(dest, len) == "HTTP/0.0"); + const size_t version_len = static_cast(len); + + CHECK(std::string(dest, version_len) == "HTTP/0.0"); CHECK(src == marshalled + INK_MIN_ALIGN); }; From 496339cb0859955703cd8e6690b54ec376c86636 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:03:44 -0700 Subject: [PATCH 09/16] Narrow the indexed-field encode length before memcmp encode_indexed_header_field() returns a signed length that is -1 on failure, and Catch2's REQUIRE does not constrain it, so the raw value reached memcmp's size_t parameter. Convert it once, after the checks, into a named unsigned local. Coverity CID 1644291. --- src/proxy/http2/unit_tests/test_HpackIndexingTable.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc b/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc index 291549135b4..4ee9b207732 100644 --- a/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc +++ b/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc @@ -94,7 +94,10 @@ TEST_CASE("HPACK low level APIs", "[hpack]") REQUIRE(len > 0); REQUIRE(len == i.encoded_field_len); - REQUIRE(memcmp(buf, i.encoded_field, len) == 0); + + size_t const encoded_len = static_cast(len); + + REQUIRE(memcmp(buf, i.encoded_field, encoded_len) == 0); } } From 7bc90b468165e2a21c06d575482250fe99ee8406 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:03:56 -0700 Subject: [PATCH 10/16] Narrow the literal-field encode length before memcmp The encode_literal_header_field_with_* helpers return -1 on failure, and the preceding REQUIRE does not constrain the value for the analyzer, so the signed length reached memcmp's size_t parameter. Convert it once, after the checks, into a named unsigned local. Coverity CID 1644272. --- src/proxy/http2/unit_tests/test_HpackIndexingTable.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc b/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc index 4ee9b207732..e2378095495 100644 --- a/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc +++ b/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc @@ -243,8 +243,11 @@ TEST_CASE("HPACK low level APIs", "[hpack]") REQUIRE(len > 0); REQUIRE(len == literal_test_case[i].encoded_field_len); + + size_t const encoded_len = static_cast(len); + // coverity[overrun-buffer-arg] - len is validated positive above - REQUIRE(memcmp(buf, literal_test_case[i].encoded_field, len) == 0); + REQUIRE(memcmp(buf, literal_test_case[i].encoded_field, encoded_len) == 0); } } } From d097959b4d79c5d966311ff5c79fac23d1ef5682 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:05:10 -0700 Subject: [PATCH 11/16] Bound the encode_test comparison to the expected length encode_test handed huffman_encode's raw return value to memcmp as the length, so an error return would be read as a huge unsigned count past the end of both the malloc'd destination and the expected literal. The return value is already asserted equal to the table's expected length, so compare that many bytes instead. Coverity CID 1644220, 1644238, 1644269. --- src/proxy/hdrs/unit_tests/test_Huffmancode.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxy/hdrs/unit_tests/test_Huffmancode.cc b/src/proxy/hdrs/unit_tests/test_Huffmancode.cc index 6a7c2cb22a3..4731e40f8d9 100644 --- a/src/proxy/hdrs/unit_tests/test_Huffmancode.cc +++ b/src/proxy/hdrs/unit_tests/test_Huffmancode.cc @@ -184,7 +184,7 @@ TEST_CASE("encode_test", "[proxy][huffman]") int64_t encoded_len = huffman_encode(dst, i.expect_len, i.src, i.src_len); REQUIRE(encoded_len == i.expect_len); - REQUIRE(memcmp(i.expect, dst, encoded_len) == 0); + REQUIRE(memcmp(i.expect, dst, static_cast(i.expect_len)) == 0); free(dst); } From 0937cb87e275d3859b628420e1ab60c37bc3bd29 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:05:26 -0700 Subject: [PATCH 12/16] Bound the decode_known_vectors comparison to the source length decode_known_vectors used huffman_decode's raw return value as the memcmp length, so the documented negative error return would be read as a huge unsigned count past the end of the 64 byte destination. The return value is already asserted equal to the table's source length. Coverity CID 1660642. --- src/proxy/hdrs/unit_tests/test_Huffmancode.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxy/hdrs/unit_tests/test_Huffmancode.cc b/src/proxy/hdrs/unit_tests/test_Huffmancode.cc index 4731e40f8d9..5c2716e1b47 100644 --- a/src/proxy/hdrs/unit_tests/test_Huffmancode.cc +++ b/src/proxy/hdrs/unit_tests/test_Huffmancode.cc @@ -219,7 +219,7 @@ TEST_CASE("decode_known_vectors", "[proxy][huffman]") int64_t len = huffman_decode(dst, sizeof(dst), i.expect, i.expect_len); REQUIRE(len == i.src_len); - REQUIRE(memcmp(dst, i.src, len) == 0); + REQUIRE(memcmp(dst, i.src, static_cast(i.src_len)) == 0); } } From 3408ebf29303b28e0ecfd12958cb4463caf725f2 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:06:13 -0700 Subject: [PATCH 13/16] Bound the integer encoding comparison to the expected length The XPACK_Integer encoding test used xpack_encode_integer's return value as the memcmp length, and that value is XPACK_ERROR_COMPRESSION_ERROR on failure, which memcmp would read as a huge unsigned count past the end of both buffers. The expected length is already asserted equal. Coverity CID 1644253. --- src/proxy/hdrs/unit_tests/test_XPACK.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxy/hdrs/unit_tests/test_XPACK.cc b/src/proxy/hdrs/unit_tests/test_XPACK.cc index 78911fc8b37..cd5f72477f2 100644 --- a/src/proxy/hdrs/unit_tests/test_XPACK.cc +++ b/src/proxy/hdrs/unit_tests/test_XPACK.cc @@ -66,7 +66,7 @@ TEST_CASE("XPACK_Integer", "[xpack]") REQUIRE(len > 0); REQUIRE(len == i.encoded_field_len); - REQUIRE(memcmp(buf, i.encoded_field, len) == 0); + REQUIRE(memcmp(buf, i.encoded_field, static_cast(i.encoded_field_len)) == 0); } } From f06c8b8392a6213264e63a04ff4c852fc460d78a Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:06:28 -0700 Subject: [PATCH 14/16] Bound the string encoding comparison to the expected length The XPACK_String encoding test used xpack_encode_string's int64_t return as the memcmp length, so the negative error return would be read as a huge unsigned count past the end of both the stack buffer and the expected literal. The expected length is already asserted equal. Coverity CID 1644266, 1644292. --- src/proxy/hdrs/unit_tests/test_XPACK.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxy/hdrs/unit_tests/test_XPACK.cc b/src/proxy/hdrs/unit_tests/test_XPACK.cc index cd5f72477f2..c121387ae2b 100644 --- a/src/proxy/hdrs/unit_tests/test_XPACK.cc +++ b/src/proxy/hdrs/unit_tests/test_XPACK.cc @@ -136,7 +136,7 @@ TEST_CASE("XPACK_String", "[xpack]") REQUIRE(len > 0); REQUIRE(len == string_test_case[i].encoded_field_len); - REQUIRE(memcmp(buf, string_test_case[i].encoded_field, len) == 0); + REQUIRE(memcmp(buf, string_test_case[i].encoded_field, static_cast(string_test_case[i].encoded_field_len)) == 0); } } From c71e2cb6e9a8b03ab15b9c073ab66feaa331fd7f Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:06:43 -0700 Subject: [PATCH 15/16] Bound the string decoding comparison to the expected length The XPACK_String decoding test used the length xpack_decode_string writes back as the memcmp length, which is attacker-controlled in production and unconstrained here, so it could read past the end of the raw string literal. The expected length is already asserted equal. Coverity CID 1644236. --- src/proxy/hdrs/unit_tests/test_XPACK.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxy/hdrs/unit_tests/test_XPACK.cc b/src/proxy/hdrs/unit_tests/test_XPACK.cc index c121387ae2b..f97844151e4 100644 --- a/src/proxy/hdrs/unit_tests/test_XPACK.cc +++ b/src/proxy/hdrs/unit_tests/test_XPACK.cc @@ -151,7 +151,7 @@ TEST_CASE("XPACK_String", "[xpack]") REQUIRE(len == i.encoded_field_len); REQUIRE(actual_len == i.raw_string_len); - REQUIRE(memcmp(actual, i.raw_string, actual_len) == 0); + REQUIRE(memcmp(actual, i.raw_string, i.raw_string_len) == 0); } } From cdaf655607b3dca0bc9507e84734d3acf36dc003 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Tue, 15 Sep 2026 08:53:36 -0700 Subject: [PATCH 16/16] Bound the HPACK encode comparisons by the table length Both sites still derived the memcmp length from the encoder's return value, which REQUIRE does not constrain for the analyzer, so a modeled large positive return still reads as an oversized access. Compare the table's own expected length and keep the REQUIRE as the runtime check, matching the other files in this change. Coverity CID 1644291, 1644272. --- src/proxy/http2/unit_tests/test_HpackIndexingTable.cc | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc b/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc index e2378095495..8a1cc2fb930 100644 --- a/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc +++ b/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc @@ -95,9 +95,7 @@ TEST_CASE("HPACK low level APIs", "[hpack]") REQUIRE(len > 0); REQUIRE(len == i.encoded_field_len); - size_t const encoded_len = static_cast(len); - - REQUIRE(memcmp(buf, i.encoded_field, encoded_len) == 0); + REQUIRE(memcmp(buf, i.encoded_field, static_cast(i.encoded_field_len)) == 0); } } @@ -244,10 +242,9 @@ TEST_CASE("HPACK low level APIs", "[hpack]") REQUIRE(len > 0); REQUIRE(len == literal_test_case[i].encoded_field_len); - size_t const encoded_len = static_cast(len); - // coverity[overrun-buffer-arg] - len is validated positive above - REQUIRE(memcmp(buf, literal_test_case[i].encoded_field, encoded_len) == 0); + REQUIRE(memcmp(buf, literal_test_case[i].encoded_field, static_cast(literal_test_case[i].encoded_field_len)) == + 0); } } }