Skip to content

Division by zero in :file after failing to wipe buffer#19088

Closed
zeertzjq wants to merge 1 commit intovim:masterfrom
zeertzjq:buf-wipe
Closed

Division by zero in :file after failing to wipe buffer#19088
zeertzjq wants to merge 1 commit intovim:masterfrom
zeertzjq:buf-wipe

Conversation

@zeertzjq
Copy link
Member

@zeertzjq zeertzjq commented Jan 5, 2026

Problem: Division by zero in :file after failing to wipe buffer
(after 8.2.4631).
Solution: Still call buf_clear_file() when failing to wipe buffer.

tabfirst | wincmd t
close!
call assert_equal('Xb', bufname('%'))
tabnext
Copy link
Member Author

@zeertzjq zeertzjq Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the fix this :tabnext also causes ml_get errors. Using :silent! tabnext here will reach the :file below and trigger the division by zero. But the ml_get errors should also be caught, so I'm not adding :silent! here.

@zeertzjq zeertzjq force-pushed the buf-wipe branch 3 times, most recently from 5daaaa0 to a31457b Compare January 5, 2026 07:17

// Do not wipe out the buffer if it is used in a window.
if (buf->b_nwindows > 0)
return FALSE;
Copy link
Member Author

@zeertzjq zeertzjq Jan 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return value of close_buffer() is only checked at one place in do_ecmd(). This particular return statement doesn't matter as:

  • do_ecmd() only calls close_buffer() with buf != curbuf, and
  • do_ecmd() only checks did_decrement when buf == curbuf after close_buffer(), and
  • close_buffer() keeps curwin unchanged when closing curwin->w_buffer, and
  • close_buffer() sets buf->b_locked when triggering autocommands, and
  • when called by do_ecmd(), this return is only reached when curbuf has bufhidden=wipe, and
  • a buffer with bufhidden=wipe cannot be removed from a window when its b_locked is set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is true as of now, but on future changes, that may no longer be true. So shouldn't we still keep his condition just to be on the safe side?

Copy link
Member Author

@zeertzjq zeertzjq Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition is just moved to the if above. And I don't think returning FALSE is safer than returning TRUE.

@zeertzjq
Copy link
Member Author

zeertzjq commented Jan 5, 2026

Also found another case that causes heap-use-after-free:

file Xa
tabnew Xb
tabnew Xc
setlocal bufhidden=wipe
autocmd BufUnload Xa ++once ++nested tabonly
autocmd BufWinLeave Xc ++once tabnext
tabprevious
bwipe! Xa

This patch doesn't fix this case, as b_nwindows is already 0 when triggering BufUnload.

And there is an even trickier case where all buffers have b_locked_split when win_close_othertab() fails to close Xc:

file Xa
tabnew Xb
setlocal bufhidden=wipe
tabnew Xc
setlocal bufhidden=wipe
autocmd BufUnload Xb ++once ++nested bwipe! Xa
autocmd BufUnload Xa ++once ++nested tabonly
autocmd BufWinLeave Xc ++once tabnext
tabfirst
2tabclose

Regardless I think this patch is still an improvement.

Problem:  Division by zero in :file after failing to wipe buffer
          (after 8.2.4631).
Solution: Still call buf_clear_file() when failing to wipe buffer.
* Do not wipe out the buffer if it is used in a window.
*/
if (wipe_buf)
if (wipe_buf && buf->b_nwindows <= 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be:

Suggested change
if (wipe_buf && buf->b_nwindows <= 0)
if (wipe_buf && buf->b_nwindows > 0)
return FALSE;
else if (wipe_buf && buf->b_nwindows <= 0)

Copy link
Member Author

@zeertzjq zeertzjq Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think returning FALSE is safer than returning TRUE:

  • do_ecmd() calls close_buffer() to remove the buffer from the window.
  • When close_buffer() reaches here it has already decremented b_nwindows.
  • If b_nwindows is 0 before buf_freeall() and becomes 1 during buf_freeall() (because of the win->w_buffer = firstbuf stuff in win_close_othertab()), it's loaded in some other window, and close_buffer() returns here without wiping the buffer.
  • If do_ecmd() somehow doesn't need to remove the buffer anymore (which currently can't happen if close_buffer() reaches here), it puts the buffer back in the old window, and b_nwindows should be incremented to 2. do_ecmd() will only do that if close_buffer() returns TRUE.

@chrisbra
Copy link
Member

chrisbra commented Jan 6, 2026

where is the division by zero happening?

@zeertzjq
Copy link
Member Author

zeertzjq commented Jan 6, 2026

where is the division by zero happening?

In calc_percentage() called from fileinfo().

@chrisbra
Copy link
Member

chrisbra commented Jan 6, 2026

alright, thanks

@chrisbra chrisbra closed this in 1aa5ca4 Jan 6, 2026
@zeertzjq zeertzjq deleted the buf-wipe branch January 6, 2026 11:14
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jan 6, 2026
…uffer

Problem:  Division by zero in :file after failing to wipe buffer
          (after 8.2.4631).
Solution: Still call buf_clear_file() when failing to wipe buffer
          (zeertzjq).

closes: vim/vim#19088

vim/vim@1aa5ca4
zeertzjq added a commit to neovim/neovim that referenced this pull request Jan 6, 2026
…uffer (#37268)

Problem:  Division by zero in :file after failing to wipe buffer
          (after 8.2.4631).
Solution: Still call buf_clear_file() when failing to wipe buffer
          (zeertzjq).

closes: vim/vim#19088

vim/vim@1aa5ca4
github-actions bot pushed a commit to neovim/neovim that referenced this pull request Jan 6, 2026
…uffer (#37268)

Problem:  Division by zero in :file after failing to wipe buffer
          (after 8.2.4631).
Solution: Still call buf_clear_file() when failing to wipe buffer
          (zeertzjq).

closes: vim/vim#19088

vim/vim@1aa5ca4
(cherry picked from commit 97bfc0c)
github-actions bot pushed a commit to neovim/neovim that referenced this pull request Jan 6, 2026
…uffer (#37268)

Problem:  Division by zero in :file after failing to wipe buffer
          (after 8.2.4631).
Solution: Still call buf_clear_file() when failing to wipe buffer
          (zeertzjq).

closes: vim/vim#19088

vim/vim@1aa5ca4
(cherry picked from commit 97bfc0c)
chrisbra pushed a commit that referenced this pull request Jan 16, 2026
Problem:  Crash when using :tabonly in BufUnload.
Solution: Set curbuf when setting curwin->w_buffer. Don't wipe out a
          buffer if there are no other buffers. Don't decrement
          b_nwindows if it was 0 before buf_freeall() (zeertzjq).

fixes:  #19088#issuecomment-3710172769
closes: #19186

Signed-off-by: zeertzjq <zeertzjq@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jan 16, 2026
Problem:  Crash when using :tabonly in BufUnload.
Solution: Set curbuf when setting curwin->w_buffer. Don't wipe out a
          buffer if there are no other buffers. Don't decrement
          b_nwindows if it was 0 before buf_freeall() (zeertzjq).

fixes:  vim/vim#19088#issuecomment-3710172769
closes: vim/vim#19186

vim/vim@fa64f92
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jan 16, 2026
Problem:  Crash when using :tabonly in BufUnload.
Solution: Set curbuf when setting curwin->w_buffer. Don't wipe out a
          buffer if there are no other buffers. Don't decrement
          b_nwindows if it was 0 before buf_freeall() (zeertzjq).

fixes:  vim/vim#19088#issuecomment-3710172769
closes: vim/vim#19186

vim/vim@fa64f92
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jan 17, 2026
Problem:  Crash when using :tabonly in BufUnload.
Solution: Set curbuf when setting curwin->w_buffer. Don't wipe out a
          buffer if there are no other buffers. Don't decrement
          b_nwindows if it was 0 before buf_freeall() (zeertzjq).

fixes:  vim/vim#19088#issuecomment-3710172769
closes: vim/vim#19186

vim/vim@fa64f92
github-actions bot pushed a commit to neovim/neovim that referenced this pull request Jan 17, 2026
Problem:  Crash when using :tabonly in BufUnload.
Solution: Set curbuf when setting curwin->w_buffer. Don't wipe out a
          buffer if there are no other buffers. Don't decrement
          b_nwindows if it was 0 before buf_freeall() (zeertzjq).

fixes:  vim/vim#19088#issuecomment-3710172769
closes: vim/vim#19186

vim/vim@fa64f92
(cherry picked from commit eb5a7cc)
github-actions bot pushed a commit to neovim/neovim that referenced this pull request Jan 17, 2026
Problem:  Crash when using :tabonly in BufUnload.
Solution: Set curbuf when setting curwin->w_buffer. Don't wipe out a
          buffer if there are no other buffers. Don't decrement
          b_nwindows if it was 0 before buf_freeall() (zeertzjq).

fixes:  vim/vim#19088#issuecomment-3710172769
closes: vim/vim#19186

vim/vim@fa64f92
(cherry picked from commit eb5a7cc)
iurymarques pushed a commit to iurymarques/neovim that referenced this pull request Feb 7, 2026
Problem:  Crash when using :tabonly in BufUnload.
Solution: Set curbuf when setting curwin->w_buffer. Don't wipe out a
          buffer if there are no other buffers. Don't decrement
          b_nwindows if it was 0 before buf_freeall() (zeertzjq).

fixes:  vim/vim#19088#issuecomment-3710172769
closes: vim/vim#19186

vim/vim@fa64f92
HarshK97 pushed a commit to HarshK97/neovim that referenced this pull request Feb 13, 2026
…uffer (neovim#37268)

Problem:  Division by zero in :file after failing to wipe buffer
          (after 8.2.4631).
Solution: Still call buf_clear_file() when failing to wipe buffer
          (zeertzjq).

closes: vim/vim#19088

vim/vim@1aa5ca4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants