Commit 075323d895 for strongswan.org
commit 075323d895f574424cfc4a5f491a1d388cdfda37
Author: R. Elliott Childre <elliottchildre329@gmail.com>
Date: Mon May 18 00:53:24 2026 -0400
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 `<type>:#` 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
diff --git a/fuzz/fuzz_ids.c b/fuzz/fuzz_ids.c
index aff9162544..5c6b344a58 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 e7a4d4493e..b318fc659b 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 322c2c95ed..35837237c6 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);
}