From f9f259628808a149e19048a5eb7ec75ab94ff2ce Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 4 May 2026 07:20:16 +0800 Subject: [PATCH] vim-patch:9.2.0436: Buffer overflow when parsing overlong errorformat lines (#39578) Problem: When an error line in a file passed to :cfile / :cgetfile is longer than IOSIZE, qf_parse_file_pfx() copies the tail into the fixed-size IObuff with STRMOVE(), overflowing the heap buffer. The same code path can also loop indefinitely because qf_parse_file_pfx() always returns QF_MULTISCAN when a tail is present, and qf_init_ext() unconditionally goes to "restofline" without bounding the tail length (Nabih). Solution: Remove the STRMOVE() into IObuff. In the QF_MULTISCAN branch, alias linebuf into the tail directly and update linelen, requiring strict progress (new length less than the previous length) before retrying; otherwise ignore the line. closes: vim/vim#20126 Supported by AI https://github.com/vim/vim/commit/77677c33dec485aadd371da75cce55d449b51798 Co-authored-by: Christian Brabandt (cherry picked from commit 0e69a380263f5a78d11cd05c65cc224a3c74b53e) --- src/nvim/quickfix.c | 10 ++++++++-- test/old/testdir/test_quickfix.vim | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index f7eae6f825..5071741751 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -1019,6 +1019,13 @@ restofline: // global file names status = qf_parse_file_pfx(idx, fields, qfl, tail); if (status == QF_MULTISCAN) { + char *s = skipwhite(tail); + size_t new_linelen = strlen(s); + if (new_linelen >= linelen) { + return QF_IGNORE_LINE; + } + linebuf = s; + linelen = new_linelen; goto restofline; } } @@ -1665,7 +1672,7 @@ static int qf_parse_dir_pfx(int idx, qffields_T *fields, qf_list_T *qfl) } /// Parse global file name error format prefixes (%O, %P and %Q). -static int qf_parse_file_pfx(int idx, qffields_T *fields, qf_list_T *qfl, char *tail) +static int qf_parse_file_pfx(int idx, qffields_T *fields, qf_list_T *qfl, const char *tail) { fields->valid = false; if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) { @@ -1676,7 +1683,6 @@ static int qf_parse_file_pfx(int idx, qffields_T *fields, qf_list_T *qfl, char * } *fields->namebuf = NUL; if (tail && *tail) { - STRMOVE(IObuff, skipwhite(tail)); qfl->qf_multiscan = true; return QF_MULTISCAN; } diff --git a/test/old/testdir/test_quickfix.vim b/test/old/testdir/test_quickfix.vim index 5c12e8592a..62940ce627 100644 --- a/test/old/testdir/test_quickfix.vim +++ b/test/old/testdir/test_quickfix.vim @@ -7028,4 +7028,26 @@ func Test_quickfix_longline_noeol() call assert_equal(['okay'], readfile("XDONE")) endfunc +func Test_efm_overlongline() + let save_efm = &efm + " %r captures the tail. + set efm=%+O(%.%#)%r,%f:%l:%m + + " First line is longer than IOSIZE (1025) so the parser puts it in + " growbuf; the %r tail then far exceeds IObuff. + let lines = ['(short)' .. repeat('x', 4000), 'Xfile:10:msg'] + call writefile(lines, 'Xqferrlong', 'D') + + " Must complete without hanging or crashing. + cgetfile Xqferrlong + + " The well-formed line that follows is still parsed. + let well_formed = filter(getqflist(), 'v:val.lnum == 10') + call assert_equal(1, len(well_formed)) + call assert_equal('msg', well_formed[0].text) + + let &efm = save_efm + call setqflist([], 'f') +endfunc + " vim: shiftwidth=2 sts=2 expandtab