From cecf06eba21195fb7a5cf55ac75cd6bd8a36ceef Mon Sep 17 00:00:00 2001 From: Weixie Cui Date: Sun, 1 Mar 2026 09:59:33 +0800 Subject: [PATCH 1/4] Fix memory leak on invalid blocksize in pcre2_deserialize_decode Signed-off-by: Weixie Cui --- src/pcre2_serialize.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/pcre2_serialize.c b/src/pcre2_serialize.c index f10a3fa0d..4fb38db1d 100644 --- a/src/pcre2_serialize.c +++ b/src/pcre2_serialize.c @@ -206,7 +206,15 @@ for (i = 0; i < number_of_codes; i++) memcpy(&blocksize, src_bytes + offsetof(pcre2_real_code, blocksize), sizeof(CODE_BLOCKSIZE_TYPE)); if (blocksize <= sizeof(pcre2_real_code)) + { + memctl->free(tables, memctl->memory_data); + for (j = 0; j < i; j++) + { + memctl->free(codes[j], memctl->memory_data); + codes[j] = NULL; + } return PCRE2_ERROR_BADSERIALIZEDDATA; + } /* The allocator provided by gcontext replaces the original one. */ From 4965a2ab5268b7129a3bf04c9252a88b18d9f20c Mon Sep 17 00:00:00 2001 From: Weixie Cui Date: Mon, 2 Mar 2026 09:15:23 +0800 Subject: [PATCH 2/4] update as mention in review comment --- src/pcre2_serialize.c | 40 +++++++++++++++------------- src/pcre2test_inc.h | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 19 deletions(-) diff --git a/src/pcre2_serialize.c b/src/pcre2_serialize.c index 4fb38db1d..58abb6630 100644 --- a/src/pcre2_serialize.c +++ b/src/pcre2_serialize.c @@ -166,9 +166,10 @@ const pcre2_memctl *memctl = (gcontext != NULL) ? &gcontext->memctl : &PRIV(default_compile_context).memctl; const uint8_t *src_bytes; -pcre2_real_code *dst_re; +pcre2_real_code *dst_re = NULL; uint8_t *tables; int32_t i, j; +int32_t error; /* Sanity checks. */ @@ -207,13 +208,8 @@ for (i = 0; i < number_of_codes; i++) sizeof(CODE_BLOCKSIZE_TYPE)); if (blocksize <= sizeof(pcre2_real_code)) { - memctl->free(tables, memctl->memory_data); - for (j = 0; j < i; j++) - { - memctl->free(codes[j], memctl->memory_data); - codes[j] = NULL; - } - return PCRE2_ERROR_BADSERIALIZEDDATA; + error = PCRE2_ERROR_BADSERIALIZEDDATA; + goto cleanup; } /* The allocator provided by gcontext replaces the original one. */ @@ -222,13 +218,8 @@ for (i = 0; i < number_of_codes; i++) (pcre2_memctl *)gcontext); if (dst_re == NULL) { - memctl->free(tables, memctl->memory_data); - for (j = 0; j < i; j++) - { - memctl->free(codes[j], memctl->memory_data); - codes[j] = NULL; - } - return PCRE2_ERROR_NOMEMORY; + error = PCRE2_ERROR_NOMEMORY; + goto cleanup; } /* The new allocator must be preserved. */ @@ -238,10 +229,10 @@ for (i = 0; i < number_of_codes; i++) if (dst_re->magic_number != MAGIC_NUMBER || dst_re->name_entry_size > MAX_NAME_SIZE + IMM2_SIZE + 1 || dst_re->name_count > MAX_NAME_COUNT) - { - memctl->free(dst_re, memctl->memory_data); - return PCRE2_ERROR_BADSERIALIZEDDATA; - } + { + error = PCRE2_ERROR_BADSERIALIZEDDATA; + goto cleanup; + } /* At the moment only one table is supported. */ @@ -254,6 +245,17 @@ for (i = 0; i < number_of_codes; i++) } return number_of_codes; + +cleanup: +if (dst_re != NULL) + memctl->free(dst_re, memctl->memory_data); +memctl->free(tables, memctl->memory_data); +for (j = 0; j < i; j++) + { + memctl->free(codes[j], memctl->memory_data); + codes[j] = NULL; + } +return error; } diff --git a/src/pcre2test_inc.h b/src/pcre2test_inc.h index 0d9616397..d2d2ae49b 100644 --- a/src/pcre2test_inc.h +++ b/src/pcre2test_inc.h @@ -6297,6 +6297,68 @@ rc = pcre2_substitute(subs_other_code, substitute_subject, NULL, 0, replace_buf, &sizeval); ASSERT(rc == PCRE2_ERROR_DIFFSUBSPATTERN, "pcre2_substitute(pattern)"); +/* -------------- pcre2_serialize_decode: three goto cleanup branches -------- */ + +{ + pcre2_code *serialize_code = pcre2_compile(pattern, PCRE2_ZERO_TERMINATED, + 0, &errorcode, &erroroffset, NULL); + uint8_t *serialized_bytes = NULL; + PCRE2_SIZE serialized_size = 0; + pcre2_code *decode_codes[1] = { NULL }; + + ASSERT(serialize_code != NULL, "serialize setup"); + rc = pcre2_serialize_encode((const pcre2_code **)&serialize_code, 1, + &serialized_bytes, &serialized_size, NULL); + pcre2_code_free(serialize_code); + ASSERT(rc == 1 && serialized_bytes != NULL, "serialize setup"); + + /* goto 1: blocksize <= sizeof(pcre2_real_code) */ + { + size_t blocksize_offset = sizeof(pcre2_serialized_data) + TABLES_LENGTH + + offsetof(pcre2_real_code, blocksize); + uint8_t saved_blocksize[sizeof(PCRE2_SIZE)]; + memcpy(saved_blocksize, serialized_bytes + blocksize_offset, + sizeof(saved_blocksize)); + memset(serialized_bytes + blocksize_offset, 0, sizeof(PCRE2_SIZE)); + + rc = pcre2_serialize_decode(decode_codes, 1, serialized_bytes, NULL); + ASSERT(rc == PCRE2_ERROR_BADSERIALIZEDDATA && + decode_codes[0] == NULL, "pcre2_serialize_decode(bad blocksize)"); + + memcpy(serialized_bytes + blocksize_offset, saved_blocksize, + sizeof(saved_blocksize)); + } + + /* goto 2: dst_re malloc failure */ + mallocs_until_failure = 2; + test_gen_context = pcre2_general_context_create(&my_malloc, &my_free, NULL); + ASSERT(test_gen_context != NULL, "general_context for serialize test"); + rc = pcre2_serialize_decode(decode_codes, 1, serialized_bytes, + test_gen_context); + ASSERT(rc == PCRE2_ERROR_NOMEMORY && decode_codes[0] == NULL, + "pcre2_serialize_decode(malloc failure)"); + mallocs_until_failure = INT_MAX; + pcre2_general_context_free(test_gen_context); + test_gen_context = NULL; + + /* goto 3: magic_number / name_entry_size / name_count validation */ + { + size_t off = sizeof(pcre2_serialized_data) + TABLES_LENGTH + + offsetof(pcre2_real_code, magic_number); + uint8_t saved[4]; + memcpy(saved, serialized_bytes + off, 4); + memset(serialized_bytes + off, 0, 4); + + decode_codes[0] = NULL; + rc = pcre2_serialize_decode(decode_codes, 1, serialized_bytes, NULL); + memcpy(serialized_bytes + off, saved, 4); + ASSERT(rc == PCRE2_ERROR_BADSERIALIZEDDATA && + decode_codes[0] == NULL, "pcre2_serialize_decode(goto 3)"); + } + + pcre2_serialize_free(serialized_bytes); +} + /* ------------------------------------------------------------------------- */ #undef ASSERT From bcbc4b029191936abb0fbba1c4ace2e5b0962db5 Mon Sep 17 00:00:00 2001 From: Weixie Cui Date: Mon, 2 Mar 2026 19:34:18 +0800 Subject: [PATCH 3/4] fix: ci failed --- src/pcre2test_inc.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/pcre2test_inc.h b/src/pcre2test_inc.h index d2d2ae49b..c016fcf9a 100644 --- a/src/pcre2test_inc.h +++ b/src/pcre2test_inc.h @@ -6331,15 +6331,17 @@ ASSERT(rc == PCRE2_ERROR_DIFFSUBSPATTERN, "pcre2_substitute(pattern)"); /* goto 2: dst_re malloc failure */ mallocs_until_failure = 2; - test_gen_context = pcre2_general_context_create(&my_malloc, &my_free, NULL); - ASSERT(test_gen_context != NULL, "general_context for serialize test"); - rc = pcre2_serialize_decode(decode_codes, 1, serialized_bytes, - test_gen_context); - ASSERT(rc == PCRE2_ERROR_NOMEMORY && decode_codes[0] == NULL, - "pcre2_serialize_decode(malloc failure)"); - mallocs_until_failure = INT_MAX; - pcre2_general_context_free(test_gen_context); - test_gen_context = NULL; + { + pcre2_general_context *serialize_test_context = + pcre2_general_context_create(&my_malloc, &my_free, NULL); + ASSERT(serialize_test_context != NULL, "general_context for serialize test"); + rc = pcre2_serialize_decode(decode_codes, 1, serialized_bytes, + serialize_test_context); + ASSERT(rc == PCRE2_ERROR_NOMEMORY && decode_codes[0] == NULL, + "pcre2_serialize_decode(malloc failure)"); + mallocs_until_failure = INT_MAX; + pcre2_general_context_free(serialize_test_context); + } /* goto 3: magic_number / name_entry_size / name_count validation */ { From 53b0ad130cda90025b0cb5bb61fb069e3927b38f Mon Sep 17 00:00:00 2001 From: Weixie Cui Date: Tue, 3 Mar 2026 12:01:27 +0800 Subject: [PATCH 4/4] fix: double free --- src/pcre2_serialize.c | 1 + src/pcre2test_inc.h | 48 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/pcre2_serialize.c b/src/pcre2_serialize.c index 58abb6630..725b54099 100644 --- a/src/pcre2_serialize.c +++ b/src/pcre2_serialize.c @@ -241,6 +241,7 @@ for (i = 0; i < number_of_codes; i++) dst_re->flags |= PCRE2_DEREF_TABLES; codes[i] = dst_re; + dst_re = NULL; src_bytes += blocksize; } diff --git a/src/pcre2test_inc.h b/src/pcre2test_inc.h index c016fcf9a..5d282435b 100644 --- a/src/pcre2test_inc.h +++ b/src/pcre2test_inc.h @@ -6358,6 +6358,54 @@ ASSERT(rc == PCRE2_ERROR_DIFFSUBSPATTERN, "pcre2_substitute(pattern)"); decode_codes[0] == NULL, "pcre2_serialize_decode(goto 3)"); } + /* Regression: avoid stale dst_re double free on later iteration failure. */ + { + pcre2_code *multi_codes[2]; + pcre2_code *multi_decode_codes[2] = { NULL, NULL }; + uint8_t *multi_serialized_bytes = NULL; + PCRE2_SIZE multi_serialized_size = 0; + CODE_BLOCKSIZE_TYPE first_blocksize; + size_t first_blocksize_offset = sizeof(pcre2_serialized_data) + + TABLES_LENGTH + offsetof(pcre2_real_code, blocksize); + size_t second_blocksize_offset; + uint8_t saved_second_blocksize[sizeof(CODE_BLOCKSIZE_TYPE)]; + + multi_codes[0] = pcre2_compile(pattern, PCRE2_ZERO_TERMINATED, 0, + &errorcode, &erroroffset, NULL); + multi_codes[1] = pcre2_compile(pattern, PCRE2_ZERO_TERMINATED, 0, + &errorcode, &erroroffset, NULL); + ASSERT(multi_codes[0] != NULL && multi_codes[1] != NULL, + "serialize setup (multi-code compile)"); + + rc = pcre2_serialize_encode((const pcre2_code **)multi_codes, 2, + &multi_serialized_bytes, &multi_serialized_size, NULL); + pcre2_code_free(multi_codes[0]); + pcre2_code_free(multi_codes[1]); + ASSERT(rc == 2 && multi_serialized_bytes != NULL, + "serialize setup (multi-code encode)"); + + memcpy(&first_blocksize, multi_serialized_bytes + first_blocksize_offset, + sizeof(first_blocksize)); + second_blocksize_offset = sizeof(pcre2_serialized_data) + TABLES_LENGTH + + first_blocksize + offsetof(pcre2_real_code, blocksize); + + memcpy(saved_second_blocksize, + multi_serialized_bytes + second_blocksize_offset, + sizeof(saved_second_blocksize)); + memset(multi_serialized_bytes + second_blocksize_offset, 0, + sizeof(saved_second_blocksize)); + + rc = pcre2_serialize_decode(multi_decode_codes, 2, multi_serialized_bytes, + NULL); + memcpy(multi_serialized_bytes + second_blocksize_offset, + saved_second_blocksize, sizeof(saved_second_blocksize)); + ASSERT(rc == PCRE2_ERROR_BADSERIALIZEDDATA && + multi_decode_codes[0] == NULL && multi_decode_codes[1] == NULL, + "pcre2_serialize_decode(regression stale dst_re)"); + + pcre2_serialize_free(multi_serialized_bytes); + } + pcre2_serialize_free(serialized_bytes); }