From: Aki Tuomi Date: Sat, 18 Apr 2026 18:00:36 +0000 (+0300) Subject: lib-index: Fix dangling index->map pointer when fsck fails in mail_index_map_latest_f... X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;h=HEAD;p=thirdparty%2Fdovecot%2Fcore.git lib-index: Fix dangling index->map pointer when fsck fails in mail_index_map_latest_file() If mail_index_fsck() returns -1 (e.g. log lock failure), index->map was left pointing at new_map. The subsequent mail_index_unmap(&new_map) freed that object, leaving index->map as a dangling pointer. Fix by restoring index->map = old_map before breaking on fsck error, mirroring what the success path already does. --- diff --git a/src/lib-index/mail-index-map-read.c b/src/lib-index/mail-index-map-read.c index fe162d97db..ac2d22b619 100644 --- a/src/lib-index/mail-index-map-read.c +++ b/src/lib-index/mail-index-map-read.c @@ -386,6 +386,7 @@ mail_index_map_latest_file(struct mail_index *index, const char **reason_r) old_map = index->map; index->map = new_map; if (mail_index_fsck(index) < 0) { + index->map = old_map; ret = -1; break; } diff --git a/src/lib-index/test-mail-index.c b/src/lib-index/test-mail-index.c index e99c84f8d0..cb25f2211e 100644 --- a/src/lib-index/test-mail-index.c +++ b/src/lib-index/test-mail-index.c @@ -212,12 +212,67 @@ static void test_mail_index_corruption_message_count(void) test_end(); } +static void test_mail_index_map_fsck_fail_restores_map(void) +{ + struct mail_index *index; + struct mail_index_sync_ctx *sync_ctx; + struct mail_index_view *sync_view; + struct mail_index_transaction *sync_trans; + const char *idx_path; + uint32_t seen_count; + int fd; + + test_begin("mail index map: fsck fail restores old map"); + + /* Create a valid index and force the .index file to be written. */ + index = test_mail_index_init(TRUE); + int ret = mail_index_sync_begin(index, &sync_ctx, &sync_view, + &sync_trans, 0); + test_assert_cmp(ret, >, 0); + mail_index_write(index, FALSE, "test"); + ret = mail_index_sync_commit(&sync_ctx); + test_assert_cmp(ret, ==, 0); + test_mail_index_close(&index); + + /* Corrupt seen_messages_count > messages_count (0 messages, so 1 > 0). + * mail_index_map_check_header() returns 0 (soft error) for this, + * which triggers mail_index_fsck() inside mail_index_map_latest_file(). */ + idx_path = t_strconcat(test_mail_index_get_dir(), + "/test.dovecot.index", NULL); + fd = open(idx_path, O_RDWR); + if (fd == -1) + i_fatal("open(%s) failed: %m", idx_path); + seen_count = 1; + ssize_t len = pwrite(fd, &seen_count, sizeof(seen_count), + offsetof(struct mail_index_header, + seen_messages_count)); + test_assert_cmp(len, ==, (ssize_t)sizeof(seen_count)); + i_close_fd(&fd); + + /* Opening readonly causes fsck to fail immediately (can't write-lock a + * readonly index). Without the fix in mail_index_map_latest_file(), + * index->map is left pointing to the freed new_map, and the subsequent + * mail_index_unmap() in mail_index_close_nonopened() is a use-after-free + * caught by ASAN/valgrind. */ + test_expect_errors(2); + index = mail_index_alloc(NULL, test_mail_index_get_dir(), + "test.dovecot.index"); + ret = mail_index_open(index, MAIL_INDEX_OPEN_FLAG_READONLY); + test_assert_cmp(ret, ==, -1); + mail_index_free(&index); + test_expect_no_more_errors(); + + test_mail_index_delete(); + test_end(); +} + int main(void) { static void (*const test_functions[])(void) = { test_mail_index_rotate, test_mail_index_new_extension, test_mail_index_corruption_message_count, + test_mail_index_map_fsck_fail_restores_map, NULL }; test_dir_init("mail-index");