[PECL-CVS] [pecl-mail-mailparse] fix/gh44-segfault-mimepart-dtor: Clean up fix and add a potential test
[email protected] (Rasmus Lerdorf) Sun, 5 Apr 2026 10:32:20 +0000
| Newsgroups | php.pecl.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: Rasmus Lerdorf (rlerdorf)
Date: 2026-04-05T06:32:11-04:00
Commit: https://github.com/php/pecl-mail-mailparse/commit/b375b6bb0b62e5f2ed45549e583c9bf76dbac8ef
Raw diff: https://github.com/php/pecl-mail-mailparse/commit/b375b6bb0b62e5f2ed45549e583c9bf76dbac8ef.diff
Clean up fix and add a potential test
Changed paths:
A tests/gh44.phpt
M .github/workflows/ci.yml
M php_mailparse_mime.c
Diff:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c6de5e2..6c83e22 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,7 +46,7 @@ jobs:
uses: actions/checkout@v2
- name: Setup PHP
id: setup-php
- uses: cmb69/[email protected]
+ uses: php/setup-php-sdk@c391923f79202e73f64b8cb15844fee60cbc6c94
with:
version: ${{matrix.version}}
arch: ${{matrix.arch}}
diff --git a/php_mailparse_mime.c b/php_mailparse_mime.c
index 2fed283..e898919 100644
--- a/php_mailparse_mime.c
+++ b/php_mailparse_mime.c
@@ -326,18 +326,14 @@ PHP_MAILPARSE_API void php_mimepart_free(php_mimepart *part)
part->rsrc->ptr = NULL;
}
- /* Recursively free children, NULLing their resource pointers to prevent
- * double-free from the resource list cleanup, and releasing the resource
- * list entry so the refcount doesn't leak */
+ /* Release child resources via zval_ptr_dtor, which respects refcounts:
+ * children with no external references are freed immediately, while
+ * children held by userland (e.g. via mailparse_msg_get_part) stay
+ * alive until their own refcount reaches zero. The idempotent
+ * mimepart_dtor (res->ptr NULL guard) prevents double-free at shutdown. */
zend_hash_internal_pointer_reset_ex(&part->children, &pos);
while ((childpart_z = zend_hash_get_current_data_ex(&part->children, &pos)) != NULL) {
- zend_resource *child_res = Z_RES_P(childpart_z);
- php_mimepart *child = (php_mimepart *)child_res->ptr;
- if (child != NULL) {
- child_res->ptr = NULL;
- php_mimepart_free(child);
- }
- zend_list_delete(child_res);
+ zval_ptr_dtor(childpart_z);
zend_hash_move_forward_ex(&part->children, &pos);
}
diff --git a/tests/gh44.phpt b/tests/gh44.phpt
new file mode 100644
index 0000000..669d86d
--- /dev/null
+++ b/tests/gh44.phpt
@@ -0,0 +1,59 @@
+--TEST--
+GH issue #44 (Segmentation fault in mimepart resource destructor during shutdown)
+--SKIPIF--
+<?php
+if (!extension_loaded("mailparse")) die("skip mailparse extension not available");
+?>
+--FILE--
+<?php
+$mime = "Content-Type: multipart/mixed; boundary=\"outer\"\r\n" .
+ "\r\n" .
+ "--outer\r\n" .
+ "Content-Type: multipart/alternative; boundary=\"inner\"\r\n" .
+ "\r\n" .
+ "--inner\r\n" .
+ "Content-Type: text/plain\r\n" .
+ "\r\n" .
+ "Hello plain\r\n" .
+ "--inner\r\n" .
+ "Content-Type: text/html\r\n" .
+ "\r\n" .
+ "<b>Hello html</b>\r\n" .
+ "--inner--\r\n" .
+ "--outer\r\n" .
+ "Content-Type: application/octet-stream\r\n" .
+ "Content-Transfer-Encoding: base64\r\n" .
+ "\r\n" .
+ "SGVsbG8=\r\n" .
+ "--outer--\r\n";
+
+/* Test 1: Parse multipart message, let resource cleanup happen at shutdown */
+$msg1 = mailparse_msg_create();
+mailparse_msg_parse($msg1, $mime);
+$struct = mailparse_msg_get_structure($msg1);
+echo "structure: " . implode(", ", $struct) . "\n";
+
+/* Test 2: Parse and explicitly free */
+$msg2 = mailparse_msg_create();
+mailparse_msg_parse($msg2, $mime);
+mailparse_msg_free($msg2);
+
+/* Test 3: Get child parts (adds refcount), then let shutdown clean up */
+$msg3 = mailparse_msg_create();
+mailparse_msg_parse($msg3, $mime);
+$parts = [];
+foreach (mailparse_msg_get_structure($msg3) as $part_id) {
+ $parts[] = mailparse_msg_get_part($msg3, $part_id);
+}
+
+/* Test 4: Multiple messages with interleaved resource handles */
+for ($i = 0; $i < 5; $i++) {
+ $m = mailparse_msg_create();
+ mailparse_msg_parse($m, $mime);
+}
+
+echo "ok\n";
+?>
+--EXPECT--
+structure: 1, 1.1, 1.1.1, 1.1.2, 1.2
+ok