From e1c1bd3722d4fda27e7fba07445f917522327fb1 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 13 Jul 2026 05:46:46 +0200 Subject: [PATCH] fix(coverity): RESOURCE_LEAK, USE_AFTER_FREE /src/nvim/ex_docmd.c: 5033 in ex_restart() 5027 // The address after --listen may be in use by the current server. 5028 if (i > 0 && strequal(arg, "--listen")) { 5029 const listitem_T *next_li = li->li_next; 5030 if (next_li != NULL) { 5031 const char *addr = tv_get_string(TV_LIST_ITEM_TV(next_li)); 5032 if (strstr(addr, ":") || strstr(addr, "/") || strstr(addr, "\\")) { >>> CID 649201: Resource leaks (RESOURCE_LEAK) >>> Overwriting "listen_arg" in "listen_arg = xstrdup(addr)" leaks the storage that "listen_arg" points to. 5033 listen_arg = TO_SLASH_SAVE(addr); 5034 #ifdef MSWIN 5035 // On Windows, don't pass --listen to new server (named pipe can't be reused immediately). 5036 // Instead pass the address via RPC; new server rebinds after startup. 5037 li = next_li; 5038 continue; *** CID 649200: Memory - illegal accesses (USE_AFTER_FREE) /src/nvim/eval/fs.c: 228 in modify_fname() 222 223 // ":h" - head, remove "/file_name", can be repeated 224 // Don't remove the logical root, see `FileInfo`. 225 while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') { 226 valid |= VALID_HEAD; 227 *usedlen += 2; >>> CID 649200: Memory - illegal accesses (USE_AFTER_FREE) >>> Using freed pointer "s". 228 while (tail > s && after_pathsep(s, tail)) { 229 MB_PTR_BACK(*fnamep, tail); 230 } 231 *fnamelen = tail <= s ? (size_t)(s - *fnamep) : (size_t)(tail - *fnamep); 232 if (*fnamelen == 0) { 233 // Result is empty. Turn it into "." to make ":cd %:h" work. --- src/nvim/eval/fs.c | 1 + src/nvim/ex_docmd.c | 1 + test/functional/vimscript/fnamemodify_spec.lua | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c index d320229e2b..dfa86541e2 100644 --- a/src/nvim/eval/fs.c +++ b/src/nvim/eval/fs.c @@ -233,6 +233,7 @@ repeat: // Result is empty. Turn it into "." to make ":cd %:h" work. xfree(*bufp); *bufp = *fnamep = tail = xstrdup("."); + s = *fnamep; // s pointed into the freed buffer. *fnamelen = 1; } else { while (tail > s && !after_pathsep(s, tail)) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 838f340de1..fea7eafc9d 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5030,6 +5030,7 @@ static void ex_restart(exarg_T *eap) if (next_li != NULL) { const char *addr = tv_get_string(TV_LIST_ITEM_TV(next_li)); if (strstr(addr, ":") || strstr(addr, "/") || strstr(addr, "\\")) { + XFREE_CLEAR(listen_arg); listen_arg = TO_SLASH_SAVE(addr); #ifdef MSWIN // On Windows, don't pass --listen to new server (named pipe can't be reused immediately). diff --git a/test/functional/vimscript/fnamemodify_spec.lua b/test/functional/vimscript/fnamemodify_spec.lua index f2b4b4950f..999921c218 100644 --- a/test/functional/vimscript/fnamemodify_spec.lua +++ b/test/functional/vimscript/fnamemodify_spec.lua @@ -104,6 +104,10 @@ describe('fnamemodify()', function() it('handles :h', function() -- generic path eq('.', fnamemodify('hello.txt', ':h')) + -- Repeated ":h" on a bare name: the first ":h" replaces the buffer with ".", the + -- next must re-anchor into it and not read the freed buffer (Coverity CID 649200). + eq('.', fnamemodify('hello.txt', ':h:h')) + eq('.', fnamemodify('hello.txt', ':h:h:h')) eq('path/to', fnamemodify('path/to/hello.txt', ':h')) eq('/', fnamemodify('/', ':h')) eq('/', fnamemodify('/foo', ':h'))