[vim/vim] Heap corruption (invalid free -> SIGABRT) on window creation with vim-airline branch extension (Issue #21169)

4 views
Skip to first unread message

Brian E Clow

unread,
Aug 27, 2026, 2:13:52 PM (5 days ago) Aug 27
to vim/vim, Subscribed
brianeclow created an issue (vim/vim#21169)

Steps to reproduce

  1. Install vim-airline (8f0c931, 2026-07-28) and vim-fugitive (3b753cf, 2026-03-07).

  2. Run:
    vim --cmd 'let g:airline_extensions=["branch"]' file.txt -c 'split' -c 'vsplit' -c 'qall!'

  3. Repeat about 8 times. The crash is non-deterministic and does not fire on
    every run.

  4. Vim aborts with SIGABRT (exit status 134):
    ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED
    -> malloc_report -> abort

Observed rate: 60-100% of runs across many trials (12/12, 8/8, 7/8, 5/8 in
separate batches). Startup alone never crashes; a window must be created.

Expected behaviour

Creating a window should not abort Vim.

No sequence of Vim script should be able to make Vim call free() on a pointer
that was never allocated. vim-airline and vim-fugitive are pure Vim script and
cannot corrupt the heap directly, so this is being reported as a Vim
memory-safety defect that those plugins merely trigger.

Vim 9.1 is not affected by the identical configuration, so this appears to be a
9.2 regression.

Version of Vim

VIM - Vi IMproved 9.2 (2026 Feb 14, compiled Aug 23 2026 19:45:42) macOS version - arm64 Included patches: 1-1000 Huge version without GUI Compiled by Homebrew

Environment

Operating system: macOS 26.6.2 (25G83), Apple Silicon (arm64)
Terminal: iTerm2
Value of $TERM: xterm-256color
Shell: GNU bash 5.3.15(1)-release (aarch64-apple-darwin25.4.0)

Vim build: Homebrew bottle, vim 9.2.1000
Plugins: vim-airline 8f0c9312dd7db148c67d6ba281087d0e10bbab92 (2026-07-28)
vim-fugitive 3b753cf8c6a4dcde6edee8827d464ba9b8c4a6f0 (2026-03-07)
managed by Vundle

Logs and stack traces

Crash signature (macOS .ips report, identical across 26+ reports):

  exception: EXC_CRASH (SIGABRT)
  asi: libsystem_c.dylib: "abort() called"

  Faulting thread (com.apple.main-thread):
    libsystem_kernel.dylib   __kill
    vim                      +1192304
    vim                      +2484188
    libsystem_platform.dylib _sigtramp
    libsystem_pthread.dylib  pthread_kill
    libsystem_c.dylib        abort
    libsystem_malloc.dylib   malloc_vreport
    libsystem_malloc.dylib   malloc_report
    libsystem_malloc.dylib   ___BUG_IN_CLIENT_OF_LIBMALLOC_POINTER_BEING_FREED_WAS_NOT_ALLOCATED
    vim                      +1163580
    vim                      +1162988
    ... nested user-function evaluation, ~3 levels deep ...
    dyld                     start

All frames in the corrupting path are Vim's own. The crashing process loads only
vim, libncursesw, libsodium, libintl and libsystem -- no plugin dylib is
involved.

Bisection (same machine, same .vimrc, N = 6-12 runs per row):

  Configuration                                 SIGABRT
  --------------------------------------------  ---------------------
  baseline, window creation                     12/12, 8/8, 7/8, 5/8
  g:airline_extensions=['branch']               6/6
  g:airline_extensions=[]                       0/6
  all extensions EXCEPT branch                  0/8
  g:airline#extensions#branch#enabled=0         0/8
  g:loaded_airline=1                            0/12
  g:loaded_fugitive=1                           0/6
  startup only, no window created               0/12
  Apple system vim 9.1, identical config        0/20

The tabline, whitespace, syntastic and ctrlp extensions are each individually
clean. Only 'branch' triggers the crash, and only with fugitive loaded.

Regression evidence:
Apple's /usr/bin/vim 9.1 (2024 Jan 02, compiled Jul 31 2026) runs the identical
.vimrc and plugin set with zero crashes across 20 runs. Caveat: that is a
-python3 -ruby -lua build, so the comparison is not perfectly controlled.
However, removing the python3 block from the vimrc does not change the 9.2 crash
rate, so the embedded interpreters are not implicated.

Ruled out:
  - Python/Ruby/Lua interpreters: removing the python3 heredoc changes nothing.
  - ctrlp: appears at the top of the verbose log at crash time, but is the
    victim of the corruption rather than the trigger; disabling it changes
    nothing.
  - autocmds: removing all 21 augroup blocks changes nothing.
  - MallocScribble: raises the hit rate but is not required to reproduce.

Not yet minimized:
This could not be reduced to a standalone minimal vimrc. Vundle + fugitive +
airline['branch'] alone is clean (0/6), so a further interaction in the full
config is required. Full .vimrc available on request.

No symbolicated backtrace is available -- the Homebrew bottle is stripped (nm
yields a single symbol). An ASan build configured with --enable-fail-if-missing
should name the offending free() directly.


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/21169@github.com>

Christian Brabandt

unread,
Aug 27, 2026, 4:11:18 PM (5 days ago) Aug 27
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#21169)

cannot reproduce. Can you get a backtrace or ASAN trace?


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/21169/5444653304@github.com>

Brian E Clow

unread,
Aug 27, 2026, 4:55:27 PM (5 days ago) Aug 27
to vim/vim, Subscribed
brianeclow left a comment (vim/vim#21169)

Here's an ASan trace. It's a double-free of 'eventignore', and the culprit
is undo_cmdmod() restoring 'ei' after a :noautocmd command modifier.

Build

Built from tag v9.2.1000, all interpreters disabled to rule them out:

./configure --with-features=huge --enable-fail-if-missing --disable-gui --without-x \
  --disable-netbeans \
  --disable-pythoninterp --disable-python3interp \
  --disable-rubyinterp --disable-luainterp --disable-perlinterp \
  CFLAGS="-g -O1 -fsanitize=address -fno-omit-frame-pointer" \
  LDFLAGS="-fsanitize=address"

Reproduces 6/6 runs under ASan, with byte-identical reports each time. Note this
build has no Python/Ruby/Lua at all, which rules out the embedded interpreters.

Summary

ERROR: AddressSanitizer: attempting double-free on 0x6020002b6c10 in thread T0
SUMMARY: AddressSanitizer: double-free optionstr.c:5570 in did_set_string_option

The buffer is a 1-byte region — an empty 'eventignore' string.

Location
allocated optionstr.c:470 (vim_strsave) in set_string_option_direct, called from undo_cmdmod at ex_docmd.c:3408
freed optionstr.c:477 (free_string_option(*varp)) in set_string_option_direct
freed again optionstr.c:5570 (free_string_option(oldval)) in did_set_string_option

undo_cmdmod() restores 'ei' after :noautocmd:

if (cmod->cmod_save_ei != NULL)
{
    // Restore 'eventignore' to the value before ":noautocmd".
    set_string_option_direct((char_u *)"ei", -1, cmod->cmod_save_ei,
                                               OPT_FREE, SID_NONE);
    free_string_option(cmod->cmod_save_ei);
    cmod->cmod_save_ei = NULL;
}

Meanwhile set_string_option() captures oldval = *varp and hands that raw
pointer to did_set_string_option(), which frees it at the end. If a nested
undo_cmdmod() frees that same buffer in between, the outer frame frees it a
second time.

Trigger path

:splitwin_split_inswin_enter_extWinEnter autocmd → plugin
function → :let &<option> = ..., while a nested :noautocmd let x = <expr> is
unwinding on the same stack.

In practice this is vim-airline's branch extension,
autoload/airline/extensions/branch.vim:132:

noautocmd let ref = fugitive#repo().git_chomp('describe', '--all', '--exact-match', commit)

That is :noautocmd let x = <expr> where the RHS calls further user functions —
matching the "freed by" stack, which shows the free happening during RHS
evaluation at evalvars.c:1206.

Double-free stack

    #1 did_set_string_option        optionstr.c:5570
    #2 set_string_option            optionstr.c:626
    #3 set_option_value             option.c:6178
    #4 ex_let_one                   evalvars.c:1975
    #5 ex_let_vars                  evalvars.c:1258
    #6 ex_let                       evalvars.c:1216
    #7 do_cmdline                   ex_docmd.c:1041
    #8 call_user_func_check         userfunc.c:3485
    #9 call_func                    userfunc.c:4158
   #10 get_func_tv                  userfunc.c:2183
   #11 ex_call                      userfunc.c:6891
   #12 do_cmdline                   ex_docmd.c:1041
   #13 apply_autocmds_group         autocmd.c:2501
   #14 win_enter_ext                window.c:5858
   #15 win_split_ins                window.c:1541
   #16 ex_splitview                 ex_docmd.c:7394
   #17 do_cmdline                   ex_docmd.c:1041
   #18 vim_main2                    main.c:866
   #19 main                         main.c:453

Freed-by stack (first free)

    #1 set_string_option_direct     optionstr.c:477
    #2 do_cmdline                   ex_docmd.c:1041
    #3 call_user_func_check         userfunc.c:3485
    ...
   #26 ex_let                       evalvars.c:1206   <- RHS evaluation
   #27 do_cmdline                   ex_docmd.c:1041

Allocation stack

    #1 lalloc                       alloc.c:246
    #2 vim_strsave                  strings.c:27
    #3 set_string_option_direct     optionstr.c:470
    #4 undo_cmdmod                  ex_docmd.c:3408   <- restoring 'ei' after :noautocmd
    #5 do_cmdline                   ex_docmd.c:1041

Still not minimized

I could not reduce this to a standalone vimrc. Hand-written nestings of
:noautocmd let x = F() inside a WinEnter autocmd, with and without
let &eventignore = ..., do not trip it (0/6 each). It currently needs the real
airline + fugitive call graph. Happy to run further instrumented builds on
request — I have the ASan build set up and the failure is reliable at 6/6.

Regression

vim 9.1 (Apple's system build, compiled Jul 2026) does not crash with the
identical config across 20 runs.


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/21169/5445104094@github.com>

Christian Brabandt

unread,
Aug 27, 2026, 5:26:11 PM (5 days ago) Aug 27
to vim/vim, Subscribed
chrisbra left a comment (vim/vim#21169)

I think this might be a fallout of #21074 (patch v9.2.0991)
ping @h-east


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/21169/5445407513@github.com>

h_east

unread,
Aug 27, 2026, 11:55:10 PM (5 days ago) Aug 27
to vim/vim, Subscribed
h-east left a comment (vim/vim#21169)

Confirmed, this is a fallout of #21074 (patch 9.2.0991).

While triggering the deferred events, did_set_eventignore() leaves the option
pointing at oldval, which the caller frees afterwards. Anything that changes
'eventignore' in between frees it first, hence the double free. The airline
branch extension gets there through :noautocmd.

A plain :set does it too, so no plugin is needed:

set eventignore=WinNew
autocmd CursorMoved * set eventignore=WinLeave

func F()
  normal! j
  set eventignore=all
endfunc
call F()

Thanks for the ASan trace, it pointed straight at the double free. This is my
regression, I have fixed it.


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/21169/5448190642@github.com>

Reply all
Reply to author
Forward
0 new messages