]> dgit.raspbian.org Git - dovecot.git/commitdiff
[PATCH 1/3] lib-sieve: util: edit-mail - Fix writing to freed memory
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 14 Apr 2026 10:58:08 +0000 (12:58 +0200)
committerNoah Meyerhans <noahm@debian.org>
Wed, 16 Sep 2026 19:06:35 +0000 (15:06 -0400)
Gbp-Pq: Name 0001-lib-sieve-util-edit-mail-Fix-writing-to-freed-memory.patch

pigeonhole/Makefile.am
pigeonhole/src/lib-sieve/util/edit-mail.c
pigeonhole/tests/extensions/editheader/deleteheader-snapshot.svtest [new file with mode: 0644]

index 1bf864ffd8e4d7f9cb0649fdb0b56cd7bf2572a2..09e67d39ca120ab989ff17e7fcbf7d49a84f61d2 100644 (file)
@@ -164,6 +164,7 @@ test_cases = \
        tests/extensions/ihave/restrictions.svtest \
        tests/extensions/editheader/addheader.svtest \
        tests/extensions/editheader/deleteheader.svtest \
+       tests/extensions/editheader/deleteheader-snapshot.svtest \
        tests/extensions/editheader/alternating.svtest \
        tests/extensions/editheader/utf8.svtest \
        tests/extensions/editheader/protected.svtest \
index eae775a70ce76f1473c667d4d5350d2be9618790..50ba14fa20661c04d55bd3fb18d7a1279f37c570 100644 (file)
@@ -890,7 +890,17 @@ static int edit_mail_headers_parse(struct edit_mail *edmail)
                }
        }
 
-       /* Rebuild header index */
+       /* Rebuild header index. Reset first pointers before rebuilding so that
+          the actual first occurrence in the final list is used. Without this,
+          a snapshot's pre-set first pointer may refer to an appended field
+          that sits later in the list than a parsed field with the same header,
+          causing deleteheader to miss the parsed fields while still freeing
+          the header_index — leaving stale field_idx->header pointers. */
+       struct _header_index *hidx = edmail->headers_head;
+       while (hidx != NULL) {
+               hidx->first = NULL;
+               hidx = hidx->next;
+       }
        current = edmail->header_fields_head;
        while (current != NULL) {
                if (current->header->first == NULL)
diff --git a/pigeonhole/tests/extensions/editheader/deleteheader-snapshot.svtest b/pigeonhole/tests/extensions/editheader/deleteheader-snapshot.svtest
new file mode 100644 (file)
index 0000000..b547dd7
--- /dev/null
@@ -0,0 +1,76 @@
+require "vnd.dovecot.testsuite";
+require "editheader";
+require "fileinto";
+require "mailbox";
+
+/*
+ * Regression test: heap use-after-free in edit_mail_snapshot when
+ * deleteheader is called after a fileinto snapshot on an edit_mail
+ * whose headers have not yet been parsed.
+ */
+
+test_set "message" text:
+From: sender@example.com
+To: recipient@example.com
+Cc: original@example.com
+Subject: Test
+
+Body.
+.
+;
+
+test "deleteheader all: snapshot stale first pointer after parse" {
+       /* Add a Cc header while headers are not yet parsed.  The snapshot
+        * created by the next fileinto has H_clone_cc->first pointing at
+        * this appended field. */
+       addheader :last "Cc" "appended@example.com";
+
+       /* fileinto sets edit_snapshot=TRUE.  The next call to
+        * sieve_message_edit creates a snapshot (edmail_snap) and the
+        * fileinto action retains the pre-snapshot version for delivery. */
+       fileinto :create "folder1";
+
+       /* sieve_message_edit creates edmail_snap here.  edit_mail_headers_parse
+        * inserts the parsed "Cc: original@example.com" before the appended
+        * field in the list.  Without the fix the rebuild skips resetting
+        * ->first; the delete loop then only removes the appended field,
+        * frees H_clone_cc (index==0), and leaves the parsed field_idx with
+        * a dangling ->header pointer. */
+       deleteheader "Cc";
+
+       /* A second fileinto + edit operation takes another snapshot of the
+        * same edmail_snap.  edit_mail_snapshot walks header_fields_head and
+        * reads field_idx->header->header on the orphaned entry.
+        * Without the fix: use-after-free crash.
+        * With the fix: clean run, both Cc occurrences are correctly gone. */
+       fileinto :create "folder2";
+       deleteheader "Subject";
+
+       if not test_result_execute {
+               test_fail "failed to execute result";
+       }
+
+       /* folder1 was snapshotted before deleteheader "Cc": both Cc headers
+        * must be present. */
+       if not test_message :folder "folder1" 0 {
+               test_fail "message not stored in folder1";
+       }
+
+       if not header :is "Cc" "original@example.com" {
+               test_fail "original Cc missing in folder1 snapshot";
+       }
+
+       if not header :is "Cc" "appended@example.com" {
+               test_fail "appended Cc missing in folder1 snapshot";
+       }
+
+       /* folder2 was snapshotted after deleteheader "Cc": both Cc headers
+        * must be absent. */
+       if not test_message :folder "folder2" 0 {
+               test_fail "message not stored in folder2";
+       }
+
+       if exists "Cc" {
+               test_fail "Cc header not fully deleted in folder2 snapshot";
+       }
+}