From 075323d895f574424cfc4a5f491a1d388cdfda37 Mon Sep 17 00:00:00 2001 From: "R. Elliott Childre" Date: Mon, 18 May 2026 00:53:24 -0400 Subject: [PATCH] identification: Fix double-free when cloning empty IDs The clone() method was missing a branch when there is an encoded chunk of length 0 that still needed to be cloned. Otherwise, the destruction of the clone frees the same pointer that the original owns. This double free was found with an improved `fuzz_ids` fuzz harness and a two byte input to create an identification from "@#" or [0x40, 0x23]. It can also be triggered with `:#` e.g. `dns:#`. One of the problematic constructors is used to parse EAP-Identities, which are cloned before storing them in the auth-cfg. So this can be triggered by an unauthenticated attacker. Note that while the length check was already added with 418dbd624363 ("cloning %any ID without zero-byte memleak") and identities that trigger this can be created since 86ab5636c2c9 ("support for @#hex ID_KEY_ID identification_t"), it was the referenced commit that made the length check problematic. Fixes: 2147da40a5d7 ("simplified identification_t.clone() using memcpy") Fixes: CVE-2026-47895 --- fuzz/fuzz_ids.c | 27 +++++++++++++++++-- .../tests/suites/test_identification.c | 24 +++++++++++++++++ src/libstrongswan/utils/identification.c | 2 +- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/fuzz/fuzz_ids.c b/fuzz/fuzz_ids.c index aff916254..5c6b344a5 100644 --- a/fuzz/fuzz_ids.c +++ b/fuzz/fuzz_ids.c @@ -19,14 +19,37 @@ int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) { - identification_t *id; - chunk_t chunk; + identification_t *id, *clone; + enumerator_t *part_enum; + chunk_t chunk, data; + id_part_t part; + char str_buf[BUF_LEN], min_buf[3]; dbg_default_set_level(-1); library_init(NULL, "fuzz_ids"); chunk = chunk_create((u_char*)buf, len); id = identification_create_from_data(chunk); + if (id) + { + id->hash(id, 0U); + id->equals(id, id); + id->matches(id, id); + id->contains_wildcards(id); + + part_enum = id->create_part_enumerator(id); + if (part_enum) + { + while (part_enum->enumerate(part_enum, &part, &data)); + part_enum->destroy(part_enum); + } + + clone = id->clone(id); + clone->destroy(clone); + } + + snprintf(str_buf, sizeof(str_buf), "%Y", id); + snprintf(min_buf, sizeof(min_buf), "%Y", id); DESTROY_IF(id); library_deinit(); diff --git a/src/libstrongswan/tests/suites/test_identification.c b/src/libstrongswan/tests/suites/test_identification.c index e7a4d4493..b318fc659 100644 --- a/src/libstrongswan/tests/suites/test_identification.c +++ b/src/libstrongswan/tests/suites/test_identification.c @@ -1608,6 +1608,29 @@ START_TEST(test_clone) } END_TEST +START_TEST(test_clone_empty) +{ + identification_t *a, *b; + chunk_t a_enc, b_enc; + + /* this produces an empty but non-NULL encoding, which previously caused a + * double-free when destroying a clone */ + a = identification_create_from_string("@#"); + ck_assert(a != NULL); + a_enc = a->get_encoding(a); + + b = a->clone(a); + ck_assert(b != NULL); + ck_assert(a != b); + b_enc = b->get_encoding(b); + ck_assert(!a_enc.len && !b_enc.len); + ck_assert(a_enc.ptr != b_enc.ptr || (!a_enc.ptr && !b_enc.ptr)); + + b->destroy(b); + a->destroy(a); +} +END_TEST + Suite *identification_suite_create() { Suite *s; @@ -1670,6 +1693,7 @@ Suite *identification_suite_create() tc = tcase_create("clone"); tcase_add_test(tc, test_clone); + tcase_add_test(tc, test_clone_empty); suite_add_tcase(s, tc); return s; diff --git a/src/libstrongswan/utils/identification.c b/src/libstrongswan/utils/identification.c index 322c2c95e..35837237c 100644 --- a/src/libstrongswan/utils/identification.c +++ b/src/libstrongswan/utils/identification.c @@ -1722,7 +1722,7 @@ METHOD(identification_t, clone_, identification_t*, clone->encoded = chunk_from_str(strdup(this->encoded.ptr)); compile_regex(clone); } - else if (this->encoded.len) + else { clone->encoded = chunk_clone(this->encoded); }