From 574272a638bc751523b770095719f3ead37a3413 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 20:46:47 -0700 Subject: [PATCH 01/15] Fix use-after-free in the TSUrlParse regression test When TSUrlCreate fails the test destroys the buffer inside the error branch, then falls through to the shared cleanup that releases an MLoc against that buffer and destroys it a second time. The stale url_loc from the previous iteration is used as well. Skip to the next URL instead. Coverity CID 1497313. --- src/api/InkAPITest.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/InkAPITest.cc b/src/api/InkAPITest.cc index 3ba3d903a96..f4875fc4017 100644 --- a/src/api/InkAPITest.cc +++ b/src/api/InkAPITest.cc @@ -6401,6 +6401,7 @@ REGRESSION_TEST(SDK_API_TSUrlParse)(RegressionTest *test, int /* atype ATS_UNUSE if (TSMBufferDestroy(bufp) == TS_ERROR) { SDK_RPRINT(test, "TSUrlParse", url, TC_FAIL, "Error in Destroying MBuffer"); } + continue; } else { start = url; end = url + strlen(url); From b74920dce90f82928c5c20b54703cbb99c8b73ee Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 20:47:35 -0700 Subject: [PATCH 02/15] Fix use-after-free in the TSMimeHdrParse regression test When TSMimeHdrCreate fails the test destroys bufp1 and then keeps using it for the remaining thirteen test cases, ending with a second TSMBufferDestroy in the shared cleanup. The header location is never assigned on that path either. Report the failure and return instead. Coverity CID 1497445. --- src/api/InkAPITest.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/api/InkAPITest.cc b/src/api/InkAPITest.cc index f4875fc4017..17d8360e33f 100644 --- a/src/api/InkAPITest.cc +++ b/src/api/InkAPITest.cc @@ -6022,6 +6022,9 @@ REGRESSION_TEST(SDK_API_TSMimeHdrParse)(RegressionTest *test, int /* atype ATS_U if (TSMBufferDestroy(bufp1) == TS_ERROR) { SDK_RPRINT(test, "TSMimeHdrParse", "TestCase1", TC_FAIL, "Error in Destroying MBuffer"); } + TSMimeParserDestroy(parser); + *pstatus = REGRESSION_TEST_FAILED; + return; } else { start = parse_string; end = parse_string + strlen(parse_string) + 1; From 1c31724d31f0b17d302bb6183e71f3a591b92e15 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 20:51:28 -0700 Subject: [PATCH 03/15] Guard the AIO benchmark summary against zero elapsed time dump_summary divides the operation counts by measured seconds without checking them, so a run that records no elapsed time reports inf and nan rates instead of saying it has nothing to measure. Coverity CID 1591523. --- src/iocore/aio/test_AIO.cc | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/iocore/aio/test_AIO.cc b/src/iocore/aio/test_AIO.cc index 3ae808bbd17..44f60416524 100644 --- a/src/iocore/aio/test_AIO.cc +++ b/src/iocore/aio/test_AIO.cc @@ -187,7 +187,7 @@ dump_summary() double total_secs = 0.0; for (int i = 0; i < orig_n_accessors; i++) { double secs = (dev[i]->time_end - dev[i]->time_start) / 1000000000.0; - double ops_sec = (dev[i]->seq_reads + dev[i]->seq_writes + dev[i]->rand_reads) / secs; + double ops_sec = secs > 0.0 ? (dev[i]->seq_reads + dev[i]->seq_writes + dev[i]->rand_reads) / secs : 0.0; printf("%s: #sr:%d #sw:%d #rr:%d %0.1f secs %0.1f ops/sec\n", dev[i]->path, dev[i]->seq_reads, dev[i]->seq_writes, dev[i]->rand_reads, secs, ops_sec); total_secs += secs; @@ -198,20 +198,28 @@ dump_summary() printf("-----------------\n"); printf("aggregate results\n"); printf("-----------------\n"); - total_secs /= orig_n_accessors; - float sr = (total_seq_reads * seq_read_size) / total_secs; - sr /= 1024.0 * 1024.0; - float sw = (total_seq_writes * seq_write_size) / total_secs; - sw /= 1024.0 * 1024.0; - float rr = (total_rand_reads * rand_read_size) / total_secs; - rr /= 1024.0 * 1024.0; - printf("%f ops %0.2f mbytes/sec %0.1f ops/sec %0.1f ops/sec/disk seq_read\n", total_seq_reads, sr, total_seq_reads / total_secs, - total_seq_reads / total_secs / n_disk_path); - printf("%f ops %0.2f mbytes/sec %0.1f ops/sec %0.1f ops/sec/disk seq_write\n", total_seq_writes, sw, - total_seq_writes / total_secs, total_seq_writes / total_secs / n_disk_path); - printf("%f ops %0.2f mbytes/sec %0.1f ops/sec %0.1f ops/sec/disk rand_read\n", total_rand_reads, rr, - total_rand_reads / total_secs, total_rand_reads / total_secs / n_disk_path); - printf("%0.2f total mbytes/sec\n", sr + sw + rr); + if (orig_n_accessors > 0) { + total_secs /= orig_n_accessors; + } + + if (total_secs > 0.0 && n_disk_path > 0) { + float sr = (total_seq_reads * seq_read_size) / total_secs; + sr /= 1024.0 * 1024.0; + float sw = (total_seq_writes * seq_write_size) / total_secs; + sw /= 1024.0 * 1024.0; + float rr = (total_rand_reads * rand_read_size) / total_secs; + rr /= 1024.0 * 1024.0; + + printf("%f ops %0.2f mbytes/sec %0.1f ops/sec %0.1f ops/sec/disk seq_read\n", total_seq_reads, sr, total_seq_reads / total_secs, + total_seq_reads / total_secs / n_disk_path); + printf("%f ops %0.2f mbytes/sec %0.1f ops/sec %0.1f ops/sec/disk seq_write\n", total_seq_writes, sw, + total_seq_writes / total_secs, total_seq_writes / total_secs / n_disk_path); + printf("%f ops %0.2f mbytes/sec %0.1f ops/sec %0.1f ops/sec/disk rand_read\n", total_rand_reads, rr, + total_rand_reads / total_secs, total_rand_reads / total_secs / n_disk_path); + printf("%0.2f total mbytes/sec\n", sr + sw + rr); + } else { + printf("no measurable elapsed time, skipping aggregate rates\n"); + } printf("----------------------------------------------------------\n"); #if TS_USE_LINUX_IO_URING From 2aae7856a64e4ac7d3b2e9a5b27f8540466aa648 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:05:43 -0700 Subject: [PATCH 04/15] Copy the test client address through the IpEndpoint overload Passing &ep.sa hands ats_ip_copy a pointer whose static type promises only the 16 bytes of struct sockaddr, while the function memcpys up to sizeof(sockaddr_un) into it; the IpEndpoint overload names the union that actually backs the storage, as the rest of the tree does. Coverity CID 1660031. --- src/proxy/logging/unit-tests/test_LogAccess.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxy/logging/unit-tests/test_LogAccess.cc b/src/proxy/logging/unit-tests/test_LogAccess.cc index 0a33a6d8df7..40fc131d59c 100644 --- a/src/proxy/logging/unit-tests/test_LogAccess.cc +++ b/src/proxy/logging/unit-tests/test_LogAccess.cc @@ -122,7 +122,7 @@ populate_non_http_sm_data(NonHttpSmLogData &data, std::string_view method, std:: data.owned_path.assign(path.data(), path.size()); data.owned_url = synthesize_target(method, scheme, authority, path); set_socket_address(data.owned_client_addr, "192.0.2.10:4321"sv); - ats_ip_copy(&data.owned_client_src_addr.sa, &data.owned_client_addr.sa); + ats_ip_copy(&data.owned_client_src_addr, &data.owned_client_addr); data.m_client_port = ats_ip_port_host_order(&data.owned_client_addr.sa); add_header_field(data.owned_client_request, PSEUDO_HEADER_METHOD, method); From a1db3787d9522af3c81ab68c37e0a02c3177cfd3 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:02:58 -0700 Subject: [PATCH 05/15] Build story paths with std::string in the HPACK test runner prepare() strcat'd the caller-supplied input directory into a fixed PATH_MAX buffer and then indexed that buffer with the directory's length, neither of which is bounded by the buffer's size. Coverity CID 1523657. --- src/proxy/http2/test_HPACK.cc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/proxy/http2/test_HPACK.cc b/src/proxy/http2/test_HPACK.cc index cde31b7c13f..a1a3788cd43 100644 --- a/src/proxy/http2/test_HPACK.cc +++ b/src/proxy/http2/test_HPACK.cc @@ -331,13 +331,11 @@ prepare() cerr << "Cannot open " << input_dir << endl; return 1; } - struct stat st; - char name[PATH_MAX + 1] = ""; - strcat(name, input_dir.c_str()); while ((d = readdir(dir)) != nullptr) { - name[input_dir.length()] = '\0'; - ink_strlcat(name, d->d_name, sizeof(name)); - stat(name, &st); + struct stat st; + string name = input_dir + d->d_name; + + stat(name.c_str(), &st); if (!S_ISDIR(st.st_mode)) { ++last; } From e240f4d0222da27301e615799cec69091531a11b Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:03:10 -0700 Subject: [PATCH 06/15] Check stat() before reading st_mode in the HPACK test runner The story counter tested st_mode without checking stat(), so an entry that cannot be stat'd was classified from an uninitialized struct and could be miscounted as a test story. Coverity CID 1523644. --- src/proxy/http2/test_HPACK.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/proxy/http2/test_HPACK.cc b/src/proxy/http2/test_HPACK.cc index a1a3788cd43..94e9ae97d1f 100644 --- a/src/proxy/http2/test_HPACK.cc +++ b/src/proxy/http2/test_HPACK.cc @@ -335,7 +335,9 @@ prepare() struct stat st; string name = input_dir + d->d_name; - stat(name.c_str(), &st); + if (stat(name.c_str(), &st) != 0) { + continue; + } if (!S_ISDIR(st.st_mode)) { ++last; } From 0fd3f60bec7edd29fca7094489de0446ba4c755c Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:04:11 -0700 Subject: [PATCH 07/15] Bound the oversized indexed-index block length encode_oversized_hpack_index() returns the signed xpack_encode_integer() length, whose only guard is a REQUIRE inside the helper. The raw value was used to derive the decoder's buf_end, so a negative or oversized length would form a pointer outside the buffer. Check both bounds and convert once into a named unsigned local. Coverity CID 1644260. --- src/proxy/http2/unit_tests/test_HpackIndexingTable.cc | 7 ++++++- 1 file changed, 6 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..bb1bf944004 100644 --- a/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc +++ b/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc @@ -125,13 +125,18 @@ TEST_CASE("HPACK low level APIs", "[hpack]") uint8_t buf[BUFSIZE_FOR_REGRESSION_TEST]; int64_t encoded_len = encode_oversized_hpack_index(buf, sizeof(buf), 7, 0x80); + REQUIRE(encoded_len > 0); + REQUIRE(encoded_len <= static_cast(sizeof(buf))); + + size_t const block_len = static_cast(encoded_len); + HpackIndexingTable indexing_table(4096); std::unique_ptr headers(new HTTPHdr, destroy_http_hdr); headers->create(HTTPType::REQUEST); MIMEField *field = mime_field_create(headers->m_heap, headers->m_http->m_fields_impl); MIMEFieldWrapper header(field, headers->m_heap, headers->m_http->m_fields_impl); - int64_t len = decode_indexed_header_field(header, buf, buf + encoded_len, indexing_table); + int64_t len = decode_indexed_header_field(header, buf, buf + block_len, indexing_table); REQUIRE(len == HPACK_ERROR_COMPRESSION_ERROR); } From a9a459ffc022cfb03cc6b6448ffd3d1a71e5d208 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:04:32 -0700 Subject: [PATCH 08/15] Bound the oversized literal-name memcpy destination The value suffix was memcpy'd to buf plus the raw signed length from encode_oversized_hpack_index(), with nothing checking that the destination stays inside buf. Verify the suffix fits before copying and carry the offset in an unsigned local. Coverity CID 1644201. --- .../http2/unit_tests/test_HpackIndexingTable.cc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc b/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc index bb1bf944004..4f5ddb21433 100644 --- a/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc +++ b/src/proxy/http2/unit_tests/test_HpackIndexingTable.cc @@ -291,8 +291,14 @@ TEST_CASE("HPACK low level APIs", "[hpack]") uint8_t buf[BUFSIZE_FOR_REGRESSION_TEST]; int64_t encoded_len = encode_oversized_hpack_index(buf, sizeof(buf), i.prefix, i.flag); uint8_t value[] = {0x05, 'v', 'a', 'l', 'u', 'e'}; - memcpy(buf + encoded_len, value, sizeof(value)); - encoded_len += sizeof(value); + + REQUIRE(encoded_len > 0); + REQUIRE(encoded_len <= static_cast(sizeof(buf) - sizeof(value))); + + size_t block_len = static_cast(encoded_len); + + memcpy(buf + block_len, value, sizeof(value)); + block_len += sizeof(value); HpackIndexingTable indexing_table(4096); std::unique_ptr headers(new HTTPHdr, destroy_http_hdr); @@ -300,7 +306,7 @@ TEST_CASE("HPACK low level APIs", "[hpack]") MIMEField *field = mime_field_create(headers->m_heap, headers->m_http->m_fields_impl); MIMEFieldWrapper header(field, headers->m_heap, headers->m_http->m_fields_impl); - int64_t len = decode_literal_header_field(header, buf, buf + encoded_len, indexing_table, MAX_FIELD_SIZE); + int64_t len = decode_literal_header_field(header, buf, buf + block_len, indexing_table, MAX_FIELD_SIZE); REQUIRE(len == HPACK_ERROR_COMPRESSION_ERROR); } From c6e1113bac55e0c35316923a5ddc95449ca39208 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:04:53 -0700 Subject: [PATCH 09/15] Bound the PUSH_PROMISE frame length before reading it back write_to() returns -1 on failure and the old REQUIRE only excluded -1, so the signed length reached the read() and memcmp() sizes with neither a lower nor an upper bound against the 32-byte readback buffer or the expected-bytes array. Coverity CID 1644235. --- src/proxy/http2/unit_tests/test_Http2Frame.cc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/proxy/http2/unit_tests/test_Http2Frame.cc b/src/proxy/http2/unit_tests/test_Http2Frame.cc index 6973f9f3867..6ce95929aa8 100644 --- a/src/proxy/http2/unit_tests/test_Http2Frame.cc +++ b/src/proxy/http2/unit_tests/test_Http2Frame.cc @@ -41,12 +41,17 @@ TEST_CASE("Http2Frame", "[http2][Http2Frame]") Http2PushPromiseFrame frame(id, flags, pp, hdr_block, hdr_block_len); int64_t written = frame.write_to(miob); - REQUIRE(written != -1); + REQUIRE(written > 0); CHECK(written == static_cast(HTTP2_FRAME_HEADER_LEN + sizeof(Http2StreamId) + hdr_block_len)); CHECK(written == miob_r->read_avail()); uint8_t buf[32] = {0}; - int64_t read = miob_r->read(buf, written); + + REQUIRE(written <= static_cast(sizeof(buf))); + + size_t const frame_len = static_cast(written); + int64_t read = miob_r->read(buf, written); + CHECK(read == written); uint8_t expected[] = { @@ -58,7 +63,8 @@ TEST_CASE("Http2Frame", "[http2][Http2Frame]") 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef, 0xbe, 0xef ///< Header Block Fragment }; - CHECK(memcmp(buf, expected, written) == 0); + REQUIRE(frame_len == sizeof(expected)); + CHECK(memcmp(buf, expected, frame_len) == 0); } free_MIOBuffer(miob); From d8be8dae2683437648b0ff623ac0e8e5ad486e3d Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:04:54 -0700 Subject: [PATCH 10/15] Check the huffman_decode result before reading its output values_test compared dst_start[0] against the expected character before asserting that the decoder reported one byte written, so a decode that returned an error or zero left the test reading a stack buffer the decoder never wrote. Coverity CID 1660641. --- 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..22a89e12da0 100644 --- a/src/proxy/hdrs/unit_tests/test_Huffmancode.cc +++ b/src/proxy/hdrs/unit_tests/test_Huffmancode.cc @@ -155,8 +155,8 @@ TEST_CASE("values_test", "[proxy][huffman]") REQUIRE(bytes == -1); continue; } - REQUIRE(dst_start[0] == ascii_value); REQUIRE(bytes == 1); + REQUIRE(dst_start[0] == ascii_value); } } From 4b38d5364ef6be172ff8de681bbe9769fb3842f6 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:05:58 -0700 Subject: [PATCH 11/15] Bound the encoded length before decoding it back decoder_roundtrip_fuzz passed huffman_encode's int64_t return straight into the uint32_t src_len of huffman_decode, so a negative error return would have been read as a four gigabyte source length and an oversized one would have read past the encode buffer. Assert the length fits the buffer and narrow it explicitly. Coverity CID 1660644. --- src/proxy/hdrs/unit_tests/test_Huffmancode.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/proxy/hdrs/unit_tests/test_Huffmancode.cc b/src/proxy/hdrs/unit_tests/test_Huffmancode.cc index 22a89e12da0..b1d3ca75b4a 100644 --- a/src/proxy/hdrs/unit_tests/test_Huffmancode.cc +++ b/src/proxy/hdrs/unit_tests/test_Huffmancode.cc @@ -336,10 +336,11 @@ TEST_CASE("decoder_roundtrip_fuzz", "[proxy][huffman]") std::vector encoded(src_len * 4 + 8); int64_t enc_len = huffman_encode(encoded.data(), encoded.size(), src.data(), src_len); REQUIRE(enc_len >= 0); + REQUIRE(enc_len <= static_cast(encoded.size())); // One byte of headroom guarantees success (see require_decoder_parity). std::vector decoded(src_len + 1); - int64_t dec_len = huffman_decode(decoded.data(), src_len + 1, encoded.data(), enc_len); + int64_t dec_len = huffman_decode(decoded.data(), src_len + 1, encoded.data(), static_cast(enc_len)); REQUIRE(dec_len == static_cast(src_len)); REQUIRE(memcmp(decoded.data(), src.data(), src_len) == 0); } From d82a41dde7ae22bdc042c8674c4822cedbeabd00 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:05:09 -0700 Subject: [PATCH 12/15] Check for a null dup field before comparing it TSMimeHdrFieldNextDup can return TS_NULL_MLOC, and compare_field_names passes the handle straight to TSMimeHdrFieldNameGet, whose sdk_assert would abort the test process. The existing null test only ran later, when the handle was released. Coverity CID 1022107. --- src/api/InkAPITest.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/api/InkAPITest.cc b/src/api/InkAPITest.cc index 17d8360e33f..738bd2ad461 100644 --- a/src/api/InkAPITest.cc +++ b/src/api/InkAPITest.cc @@ -6090,7 +6090,9 @@ REGRESSION_TEST(SDK_API_TSMimeHdrParse)(RegressionTest *test, int /* atype ATS_U } field_loc2 = TSMimeHdrFieldNextDup(bufp1, mime_hdr_loc1, field_loc1); - if (compare_field_names(test, bufp1, mime_hdr_loc1, field_loc1, bufp1, mime_hdr_loc1, field_loc2) == TS_ERROR) { + if (field_loc2 == TS_NULL_MLOC) { + SDK_RPRINT(test, "TSMimeHdrFieldNextDup", "TestCase1", TC_FAIL, "TSMimeHdrFieldNextDup returns TS_NULL_MLOC"); + } else if (compare_field_names(test, bufp1, mime_hdr_loc1, field_loc1, bufp1, mime_hdr_loc1, field_loc2) == TS_ERROR) { SDK_RPRINT(test, "TSMimeHdrFieldNextDup", "TestCase1", TC_FAIL, "Incorrect Pointer"); } else { SDK_RPRINT(test, "TSMimeHdrFieldNextDup", "TestCase1", TC_PASS, "ok"); From fa61d788d6f5e4b5183de291fddafcfe03e01b81 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:12:23 -0700 Subject: [PATCH 13/15] Copy the caller's sockaddr_in using its own size in build_request build_request() copied the client address with sizeof(struct sockaddr) as the length even though the source object is the caller's struct sockaddr_in, taking the read size from a different type than the object being read. Copy into the IpEndpoint's sin member with sizeof(*ip) so both sides of the copy are provably in bounds. Coverity CID 1544438. --- src/proxy/http/remap/unit-tests/nexthop_test_stubs.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxy/http/remap/unit-tests/nexthop_test_stubs.cc b/src/proxy/http/remap/unit-tests/nexthop_test_stubs.cc index bf76965cf5d..758a1a1ccf5 100644 --- a/src/proxy/http/remap/unit-tests/nexthop_test_stubs.cc +++ b/src/proxy/http/remap/unit-tests/nexthop_test_stubs.cc @@ -105,7 +105,7 @@ build_request(int64_t sm_id, HttpSM *sm, sockaddr_in *ip, const char *os_hostnam } sm->t_state.request_data.api_info = new HttpApiInfo(); if (ip != nullptr) { - memcpy(&sm->t_state.request_data.src_ip.sa, ip, sizeof(sm->t_state.request_data.src_ip.sa)); + memcpy(&sm->t_state.request_data.src_ip.sin, ip, sizeof(*ip)); } sm->t_state.request_data.xact_start = time(0); From bd6eb50e3c02b3cb1b5031f433573893f6651f5d Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:13:08 -0700 Subject: [PATCH 14/15] Scope the plugin debug object to the plugin owning its DSO debugObject points at a static object inside the dlopen'd plugin, so it is only valid while the owning unique_ptr lives - ~PluginDso() calls dlclose() and unmaps it. Declaring it at scenario scope let the stale pointer stay live across the GIVEN boundary, where it is dereferenced again; declaring it after the plugin makes it die first instead. Coverity CID 1644274. --- src/proxy/http/remap/unit-tests/test_RemapPlugin.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/proxy/http/remap/unit-tests/test_RemapPlugin.cc b/src/proxy/http/remap/unit-tests/test_RemapPlugin.cc index d2aaf3d3bc7..df890a59334 100644 --- a/src/proxy/http/remap/unit-tests/test_RemapPlugin.cc +++ b/src/proxy/http/remap/unit-tests/test_RemapPlugin.cc @@ -358,13 +358,13 @@ SCENARIO("unloading the plugin", "[plugin][core]") { REQUIRE_FALSE(sandboxDir.empty()); - std::string error; - PluginDebugObject *debugObject = nullptr; + std::string error; GIVEN("a 'done' function") { fs::path pluginConfigPath = fs::path("plugin_testing_calls.so"); RemapPluginUnitTest::Ptr plugin{setupSandBox(pluginConfigPath)}; + PluginDebugObject *debugObject = nullptr; bool result = loadPlugin(plugin.get(), error, debugObject); CHECK(true == result); @@ -387,6 +387,7 @@ SCENARIO("unloading the plugin", "[plugin][core]") { fs::path pluginConfigPath = fs::path("plugin_testing_calls.so"); RemapPluginUnitTest::Ptr plugin{setupSandBox(pluginConfigPath)}; + PluginDebugObject *debugObject = nullptr; bool result = loadPlugin(plugin.get(), error, debugObject); CHECK(true == result); From b624de5f2450ebb2d0b14891839ced330547f780 Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Mon, 14 Sep 2026 21:12:04 -0700 Subject: [PATCH 15/15] Keep ownership of the OCSP chain cert until the call succeeds The test dropped the value of issuer.release() on the floor, which reads as a leak, and released the certificate even on the path where SSL_CTX_add_extra_chain_cert() fails and therefore does not adopt it. Coverity CID 1664287. --- src/iocore/net/unit_tests/test_OCSPStapling.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/iocore/net/unit_tests/test_OCSPStapling.cc b/src/iocore/net/unit_tests/test_OCSPStapling.cc index dbeb71e6cb1..26a9f5e411f 100644 --- a/src/iocore/net/unit_tests/test_OCSPStapling.cc +++ b/src/iocore/net/unit_tests/test_OCSPStapling.cc @@ -97,8 +97,14 @@ TEST_CASE("OCSP stapling keeps SSL_CTX certificate map after later init failure" REQUIRE(SSL_CTX_use_certificate(ctx.get(), good.get()) == 1); - REQUIRE(SSL_CTX_add_extra_chain_cert(ctx.get(), issuer.get()) == 1); - issuer.release(); + // SSL_CTX_add_extra_chain_cert() takes ownership of the certificate only when it succeeds, so + // hand the raw pointer over and give it back to the unique_ptr if the call fails. + X509 *issuer_raw = issuer.release(); + + if (SSL_CTX_add_extra_chain_cert(ctx.get(), issuer_raw) != 1) { + issuer.reset(issuer_raw); + FAIL("SSL_CTX_add_extra_chain_cert failed"); + } REQUIRE(ssl_stapling_init_cert(ctx.get(), good.get(), "server.ocsp.pem", nullptr)); CHECK_FALSE(ssl_stapling_init_cert(ctx.get(), bad.get(), "signed-foo.pem", nullptr));