[PECL-CVS] [pecl-mail-mailparse] fix/gh44-segfault-mimepart-dtor: Get the test closer to the actual crash env and fix Windows CI
[email protected] (Rasmus Lerdorf) Sun, 5 Apr 2026 11:05:28 +0000
| Newsgroups | php.pecl.cvs |
|---|---|
| Message-ID | <[email protected]> |
Author: Rasmus Lerdorf (rlerdorf)
Date: 2026-04-05T07:05:22-04:00
Commit: https://github.com/php/pecl-mail-mailparse/commit/cbe3675b44dd9873cff8728522cb37b3b510ac3f
Raw diff: https://github.com/php/pecl-mail-mailparse/commit/cbe3675b44dd9873cff8728522cb37b3b510ac3f.diff
Get the test closer to the actual crash env and fix Windows CI
Changed paths:
A .gitattributes
M .github/workflows/ci.yml
M tests/gh44.phpt
Diff:
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..bd8af52
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,4 @@
+# Keep test files and test data with LF line endings on all platforms
+# to ensure byte offsets and string lengths match expected values.
+*.phpt text eol=lf
+tests/testdata/* -text
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6c94734..431ae7d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -62,4 +62,6 @@ jobs:
- name: make
run: nmake
- name: test
- run: nmake test TESTS="-d extension=${{steps.setup-php.outputs.prefix}}\ext\php_mbstring.dll --show-diff tests"
+ run: |
+ set PATH=%PATH%;${{steps.setup-php.outputs.prefix}}\ext
+ nmake test TESTS="-d extension=${{steps.setup-php.outputs.prefix}}\ext\php_mbstring.dll --show-diff tests"
diff --git a/tests/gh44.phpt b/tests/gh44.phpt
index 669d86d..d8f185d 100644
--- a/tests/gh44.phpt
+++ b/tests/gh44.phpt
@@ -6,54 +6,51 @@ 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";
+/* Generate a multipart message with >300 parts to trigger MAXPARTS.
+ * This exercises the mailparse_msg_parse_file error path which calls
+ * php_mimepart_free() directly, leaving child resources in the resource
+ * list. Without the fix, these dangling resources cause a use-after-free
+ * during shutdown in zend_close_rsrc_list. */
-/* 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";
+$boundary = "test_boundary";
+$mime = "Content-Type: multipart/mixed; boundary=\"$boundary\"\r\n\r\n";
+for ($i = 0; $i < 302; $i++) {
+ $mime .= "--$boundary\r\nContent-Type: text/plain\r\n\r\npart $i\r\n";
+}
+$mime .= "--$boundary--\r\n";
-/* Test 2: Parse and explicitly free */
-$msg2 = mailparse_msg_create();
-mailparse_msg_parse($msg2, $mime);
-mailparse_msg_free($msg2);
+$tmpfile = tempnam(sys_get_temp_dir(), 'mp_gh44_');
+file_put_contents($tmpfile, $mime);
-/* 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);
-}
+/* Parse the oversized message - triggers "MIME message too complex" warning
+ * and the error path calls php_mimepart_free(part) directly, freeing the
+ * mimepart struct but leaving child zend_resource entries in the list */
+$result = @mailparse_msg_parse_file($tmpfile);
+echo "parse_file returned: " . var_export($result, true) . "\n";
-/* Test 4: Multiple messages with interleaved resource handles */
-for ($i = 0; $i < 5; $i++) {
+/* Allocate many messages to reuse freed memory from the failed parse above.
+ * This increases the chance that the dangling resource pointers from the
+ * failed parse will reference reused/corrupted memory at shutdown. */
+$msgs = [];
+for ($i = 0; $i < 50; $i++) {
$m = mailparse_msg_create();
- mailparse_msg_parse($m, $mime);
+ mailparse_msg_parse($m, "Content-Type: multipart/mixed; boundary=\"b\"\r\n\r\n" .
+ "--b\r\nContent-Type: text/plain\r\n\r\nhello\r\n" .
+ "--b\r\nContent-Type: text/html\r\n\r\n<b>hi</b>\r\n--b--\r\n");
+ $msgs[] = $m;
}
+/* Also test normal multipart parsing and cleanup */
+$msg = mailparse_msg_create();
+mailparse_msg_parse($msg, "Content-Type: multipart/mixed; boundary=\"x\"\r\n\r\n" .
+ "--x\r\nContent-Type: text/plain\r\n\r\nhello\r\n--x--\r\n");
+$struct = mailparse_msg_get_structure($msg);
+echo "structure: " . implode(", ", $struct) . "\n";
+
+unlink($tmpfile);
echo "ok\n";
?>
--EXPECT--
-structure: 1, 1.1, 1.1.1, 1.1.2, 1.2
+parse_file returned: false
+structure: 1, 1.1
ok