From: Timo Sirainen Date: Tue, 14 Apr 2026 10:58:08 +0000 (+0200) Subject: [PATCH 1/3] lib-sieve: util: edit-mail - Fix writing to freed memory X-Git-Tag: archive/raspbian/1%2.4.1+dfsg1-6+rpi1+deb13u7^2~28 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=422a634f35113b1facec248dbaaf617b76084a69;p=dovecot.git [PATCH 1/3] lib-sieve: util: edit-mail - Fix writing to freed memory Gbp-Pq: Name 0001-lib-sieve-util-edit-mail-Fix-writing-to-freed-memory.patch --- diff --git a/pigeonhole/Makefile.am b/pigeonhole/Makefile.am index 1bf864f..09e67d3 100644 --- a/pigeonhole/Makefile.am +++ b/pigeonhole/Makefile.am @@ -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 \ diff --git a/pigeonhole/src/lib-sieve/util/edit-mail.c b/pigeonhole/src/lib-sieve/util/edit-mail.c index eae775a..50ba14f 100644 --- a/pigeonhole/src/lib-sieve/util/edit-mail.c +++ b/pigeonhole/src/lib-sieve/util/edit-mail.c @@ -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 index 0000000..b547dd7 --- /dev/null +++ b/pigeonhole/tests/extensions/editheader/deleteheader-snapshot.svtest @@ -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"; + } +}