vim-patch:9.2.0461: Corrupted undofile causes use-after-free (#39707)

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: vim/vim#20168

Supported by AI

4f610f07b7

Co-authored-by: Christian Brabandt <cb@256bit.org>
(cherry picked from commit 2d5f56c0aa)
This commit is contained in:
zeertzjq
2026-05-10 08:06:07 +08:00
committed by github-actions[bot]
parent 2902ec0541
commit 647b6be489
2 changed files with 75 additions and 24 deletions

View File

@@ -1565,36 +1565,48 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
goto error;
}
}
for (int 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;
{
const int seq = uhp->uh_next.seq;
uhp->uh_next.ptr = NULL;
for (int 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;
}
}
}
for (int 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;
{
const int seq = uhp->uh_prev.seq;
uhp->uh_prev.ptr = NULL;
for (int 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;
}
}
}
for (int 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;
{
const int seq = uhp->uh_alt_next.seq;
uhp->uh_alt_next.ptr = NULL;
for (int 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;
}
}
}
for (int 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;
{
const int seq = uhp->uh_alt_prev.seq;
uhp->uh_alt_prev.ptr = NULL;
for (int 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) {

View File

@@ -924,4 +924,43 @@ func Test_restore_cursor_position_after_undo()
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')
" Nvim: convert undo file format
let b = b->slice(0, -2) + 0z3581 + b->slice(-2) | let b[10] = 0x03
call writefile(b, 'Xundo', 'bD')
rundo Xundo
bw!
let &undofile = _uf
endfunc
" vim: shiftwidth=2 sts=2 expandtab