[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