]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0461: Corrupted undofile causes use-after-free v9.2.0461
authorChristian Brabandt <cb@256bit.org>
Sat, 9 May 2026 14:41:37 +0000 (14:41 +0000)
committerChristian Brabandt <cb@256bit.org>
Sat, 9 May 2026 14:41:37 +0000 (14:41 +0000)
Problem:  The four pointer-resolution loops in u_read_undo() lack
          an i != j guard, so a header whose uh_next.seq equals
          its own uh_seq resolves uh_next.ptr to itself.  On
          buffer close, u_freeheader() sees uhp->uh_next.ptr !=
          NULL and skips updating b_u_oldhead, so u_blockfree()
          dereferences the freed header on the next iteration.
          The same pattern applies to uh_prev, uh_alt_next and
          uh_alt_prev.  A crafted .un~ file in the same directory
          as a text file can trigger the use-after-free and
          subsequent double-free when the buffer is closed.
          (Daniel Cervera)
Solution: Add an i != j guard to each of the four resolution
          loops, matching the guard already present in the
          duplicate-detection loop above.

closes: #20168

Supported by AI

Signed-off-by: Christian Brabandt <cb@256bit.org>
src/testdir/test_undo.vim
src/undo.c
src/version.c

index f03d3ef09167ea53ec67030f745711cd1b8e92ac..21b9dcfdab1614a02d77ee90a3c903b7c946345a 100644 (file)
@@ -967,4 +967,41 @@ func Test_undo_line_backspace_after_insert_cmd_edit()
   bwipe!
 endfunc
 
+" Corrupted undo file via cyclic cross-references caused
+" double free
+func Test_corrupted_undofile()
+  CheckFeature persistent_undo
+  let _uf = &undofile
+  set undofile
+  new
+  call setline(1, 'hello')
+  let b=eval('0z56696D9F556E446FE50002F3AEFE62965A91903610' ..
+           \ 'F0E23CC8A69D5B87CEA6D28E75489B0D2CA02ED7993C' ..
+           \ '00000001000000000000000000000000000000010000' ..
+           \ '00010000000100000001000000010000000100000000' ..
+           \ '3B9ACA00005FD0000000010000000000000000000000' ..
+           \ '00000000010000000100000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '00000000000000000000000000000000000000000000' ..
+           \ '000000000000000000000000000000000000003B9ACA' ..
+           \ '00003581E7AA')
+  call writefile(b, 'Xundo', 'bD')
+  rundo Xundo
+  bw!
+  let &undofile = _uf
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
index ac38a8bf21c7be52ea3750737750ba811e28c3ab..06efd9230124b0537c253d3cd9a88ab80b3361c7 100644 (file)
@@ -2081,38 +2081,54 @@ u_read_undo(char_u *name, char_u *hash, char_u *orig_name UNUSED)
                corruption_error("duplicate uh_seq", file_name);
                goto error;
            }
-       for (j = 0; j < num_head; j++)
-           if (uhp_table[j] != NULL
-                                 && uhp_table[j]->uh_seq == uhp->uh_next.seq)
-           {
-               uhp->uh_next.ptr = uhp_table[j];
-               SET_FLAG(j);
-               break;
-           }
-       for (j = 0; j < num_head; j++)
-           if (uhp_table[j] != NULL
-                                 && uhp_table[j]->uh_seq == uhp->uh_prev.seq)
-           {
-               uhp->uh_prev.ptr = uhp_table[j];
-               SET_FLAG(j);
-               break;
-           }
-       for (j = 0; j < num_head; j++)
-           if (uhp_table[j] != NULL
-                             && uhp_table[j]->uh_seq == uhp->uh_alt_next.seq)
-           {
-               uhp->uh_alt_next.ptr = uhp_table[j];
-               SET_FLAG(j);
-               break;
-           }
-       for (j = 0; j < num_head; j++)
-           if (uhp_table[j] != NULL
-                             && uhp_table[j]->uh_seq == uhp->uh_alt_prev.seq)
-           {
-               uhp->uh_alt_prev.ptr = uhp_table[j];
-               SET_FLAG(j);
-               break;
-           }
+       {
+           int seq = uhp->uh_next.seq;
+           uhp->uh_next.ptr = NULL;
+           for (j = 0; j < num_head; j++)
+               if (uhp_table[j] != NULL && i != j
+                                   && uhp_table[j]->uh_seq == seq)
+               {
+                   uhp->uh_next.ptr = uhp_table[j];
+                   SET_FLAG(j);
+                   break;
+               }
+       }
+       {
+           int seq = uhp->uh_prev.seq;
+           uhp->uh_prev.ptr = NULL;
+           for (j = 0; j < num_head; j++)
+               if (uhp_table[j] != NULL && i != j
+                                   && uhp_table[j]->uh_seq == seq)
+               {
+                   uhp->uh_prev.ptr = uhp_table[j];
+                   SET_FLAG(j);
+                   break;
+               }
+       }
+       {
+           int seq = uhp->uh_alt_next.seq;
+           uhp->uh_alt_next.ptr = NULL;
+           for (j = 0; j < num_head; j++)
+               if (uhp_table[j] != NULL && i != j
+                               && uhp_table[j]->uh_seq == seq)
+               {
+                   uhp->uh_alt_next.ptr = uhp_table[j];
+                   SET_FLAG(j);
+                   break;
+               }
+       }
+       {
+           int seq = uhp->uh_alt_prev.seq;
+           uhp->uh_alt_prev.ptr = NULL;
+           for (j = 0; j < num_head; j++)
+               if (uhp_table[j] != NULL && i != j
+                               && uhp_table[j]->uh_seq == seq)
+               {
+                   uhp->uh_alt_prev.ptr = uhp_table[j];
+                   SET_FLAG(j);
+                   break;
+               }
+       }
        if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq)
        {
            old_idx = i;
index 6ac372ac5d6038dda71ed0f2bab49f7a848f16c4..6b5b0e7e1187706009915920ce38ca7eb49b16de 100644 (file)
@@ -729,6 +729,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    461,
 /**/
     460,
 /**/