From 20dc7398c78ee2809bebd4d51c4727d356641016 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 11 Jun 2026 22:41:45 +0800 Subject: [PATCH 1/8] fix(vim): CVE-2026-47162 Fix code injection in netrw NetrwBookHistSave function by replacing string-based concatenation with proper string() quoting to prevent injection of arbitrary Vim commands through crafted directory names. Upstream: https://github.com/vim/vim/commit/f08ab2f4d7d2947c8dd6c179ae08ee6146a2694b (v9.2.0495) Generated-By: deepseek-v4-flash Co-Authored-By: hudeng --- debian/changelog | 8 +++ ...6-47162-netrw-book-history-injection.patch | 51 +++++++++++++++++++ debian/patches/series | 8 +++ 3 files changed, 67 insertions(+) create mode 100644 debian/patches/CVE-2026-47162-netrw-book-history-injection.patch diff --git a/debian/changelog b/debian/changelog index 4efc4aeb..ec43ebc0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +vim (2:9.2.0461-1deepin2) unstable; urgency=medium + + * Fix CVE-2026-47162: Code injection in netrw NetrwBookHistSave + function - Properly quote directory names using string() to + prevent injection + + -- deepin-ci-robot Thu, 11 Jun 2026 22:41:37 +0800 + vim (2:9.2.0461-1deepin1) unstable; urgency=medium * Set-NoDisplay-true-for-vim-desktop.patch diff --git a/debian/patches/CVE-2026-47162-netrw-book-history-injection.patch b/debian/patches/CVE-2026-47162-netrw-book-history-injection.patch new file mode 100644 index 00000000..1e2ca25b --- /dev/null +++ b/debian/patches/CVE-2026-47162-netrw-book-history-injection.patch @@ -0,0 +1,51 @@ +Index: vim/runtime/pack/dist/opt/netrw/autoload/netrw.vim +=================================================================== +--- vim.orig/runtime/pack/dist/opt/netrw/autoload/netrw.vim ++++ vim/runtime/pack/dist/opt/netrw/autoload/netrw.vim +@@ -1,7 +1,7 @@ + " Creator: Charles E Campbell + " Previous Maintainer: Luca Saccarola + " Maintainer: This runtime file is looking for a new maintainer. +-" Last Change: 2026 May 14 ++" Last Change: 2026 May 17 + " Copyright: Copyright (C) 2016 Charles E. Campbell {{{1 + " Permission is hereby granted to use and distribute this code, + " with or without modifications, provided that this copyright +@@ -2935,7 +2935,7 @@ function s:NetrwBookHistSave() + while ( first || cnt != g:netrw_dirhistcnt ) + let lastline= lastline + 1 + if exists("g:netrw_dirhist_{cnt}") +- call setline(lastline,'let g:netrw_dirhist_'.cnt."='".g:netrw_dirhist_{cnt}."'") ++ call setline(lastline,'let g:netrw_dirhist_'.cnt.'='.string(g:netrw_dirhist_{cnt})) + endif + let first = 0 + let cnt = ( cnt - 1 ) % g:netrw_dirhistmax +Index: vim/src/testdir/test_plugin_netrw.vim +=================================================================== +--- vim.orig/src/testdir/test_plugin_netrw.vim ++++ vim/src/testdir/test_plugin_netrw.vim +@@ -760,4 +760,24 @@ function Test_netrw_NetrwMaps_CR_dirname() + unlet! g:netrw_pwn + bw! + endfunction ++ ++func Test_netrw_injection() ++ let g:netrw_home = getcwd() ++ let savefile = g:netrw_home . '/.netrwhist' ++ let g:netrw_dirhistmax = 10 ++ let g:netrw_dirhistcnt = 1 ++ let g:netrw_dirhist_1 = "x'|let g:injected = 1|let y='z" ++ call delete(savefile) ++ try ++ call netrw#Call('NetrwBookHistSave') ++ call assert_true(filereadable(savefile), savefile . ' must be written') ++ unlet g:netrw_dirhist_1 ++ execute 'source ' . fnameescape(savefile) ++ call assert_false(exists("g:injected"), 'injected statement must not execute') ++ call assert_equal("x'|let g:injected = 1|let y='z", g:netrw_dirhist_1, 'dirname must round-trip') ++ finally ++ call delete(savefile) ++ unlet! g:netrw_home g:netrw_dirhistmax g:netrw_dirhistcnt g:netrw_dirhist_1 g:injected ++ endtry ++endfunc + " vim:ts=8 sts=2 sw=2 et diff --git a/debian/patches/series b/debian/patches/series index 0cce39e1..bba6904a 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,4 +1,12 @@ debian/Support-sourcing-a-vimrc.tiny-when-Vim-is-invoked-as-vi.patch debian/Detect-the-rst-filetype-using-the-contents-of-the-file.patch debian/Add-recognition-of-more-LaTeX-commands-for-tex-filetype-d.patch +CVE-2026-47162-netrw-book-history-injection.patch +CVE-2026-47167-cucumber-code-injection.patch +security-patch-9.2.0513-spellfile-memory-safety.patch +CVE-2026-52858-python3complete-code-execution.patch +security-patch-9.2.0565-update-snapshot-oob-read.patch +CVE-2026-52860-python-complete-code-execution.patch +security-patch-9.2-shellescape-getscript-vimball-rust.patch +security-patch-ccfilter-unbounded-strcat.patch Set-NoDisplay-true-for-vim-desktop.patch From 630a77cfde427bdc5aba3cdf3c6d67db18394622 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 11 Jun 2026 22:41:55 +0800 Subject: [PATCH 2/8] fix(vim): CVE-2026-47167 Fix code injection in cucumber filetype plugin by using Ruby's Regexp.new() with the untrusted pattern instead of evaluating arbitrary Ruby expressions from crafted feature files. Upstream: https://github.com/vim/vim/commit/a65a52d6858af2e2a45da225cd3f7e64f2d69c83 (v9.2.0496) Generated-By: deepseek-v4-flash Co-Authored-By: hudeng --- debian/changelog | 2 + ...E-2026-47167-cucumber-code-injection.patch | 62 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 debian/patches/CVE-2026-47167-cucumber-code-injection.patch diff --git a/debian/changelog b/debian/changelog index ec43ebc0..2435c072 100644 --- a/debian/changelog +++ b/debian/changelog @@ -3,6 +3,8 @@ vim (2:9.2.0461-1deepin2) unstable; urgency=medium * Fix CVE-2026-47162: Code injection in netrw NetrwBookHistSave function - Properly quote directory names using string() to prevent injection + * Fix CVE-2026-47167: Code injection in cucumber filetype plugin - + Use Ruby Regexp.new() with untrusted pattern to prevent injection -- deepin-ci-robot Thu, 11 Jun 2026 22:41:37 +0800 diff --git a/debian/patches/CVE-2026-47167-cucumber-code-injection.patch b/debian/patches/CVE-2026-47167-cucumber-code-injection.patch new file mode 100644 index 00000000..fe516ccd --- /dev/null +++ b/debian/patches/CVE-2026-47167-cucumber-code-injection.patch @@ -0,0 +1,62 @@ +Index: vim/runtime/ftplugin/cucumber.vim +=================================================================== +--- vim.orig/runtime/ftplugin/cucumber.vim ++++ vim/runtime/ftplugin/cucumber.vim +@@ -2,6 +2,8 @@ + " Language: Cucumber + " Maintainer: Tim Pope + " Last Change: 2016 Aug 29 ++" 2026 May 26 by Vim Project: prevent Code Injection ++" https://github.com/vim/vim/security/advisories/GHSA-4473-94jm-w5x9 + + " Only do this when not done yet for this buffer + if (exists("b:did_ftplugin")) +@@ -96,7 +98,8 @@ function! s:stepmatch(receiver,target) + catch + endtry + if has("ruby") && pattern !~ '\\\@ s:steps -> s:stepmatch on every discovered step, ++ " including the malicious one. Suppress preview and error messages. ++ silent! normal [d ++ call assert_false(filereadable(marker), 'Ruby injection executed') ++ bwipe! ++ filetype plugin off ++endfunc ++ + " vim: shiftwidth=2 sts=2 expandtab From d162b85b8b13477c10e78bb702ce2720c0d4b02f Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 11 Jun 2026 22:42:01 +0800 Subject: [PATCH 3/8] fix(vim): security patch 9.2.0513 Fix memory safety issues in spellfile.c by adding recursion limits, length checks, and proper memory allocation to prevent stack overflow and other memory safety issues. Upstream: https://github.com/vim/vim/commit/25e4e46c5c2325e16eb3f1b124543bee46ec7ad0 (v9.2.0513) Generated-By: deepseek-v4-flash Co-Authored-By: hudeng --- debian/changelog | 3 + ...tch-9.2.0513-spellfile-memory-safety.patch | 196 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 debian/patches/security-patch-9.2.0513-spellfile-memory-safety.patch diff --git a/debian/changelog b/debian/changelog index 2435c072..59d62570 100644 --- a/debian/changelog +++ b/debian/changelog @@ -5,6 +5,9 @@ vim (2:9.2.0461-1deepin2) unstable; urgency=medium prevent injection * Fix CVE-2026-47167: Code injection in cucumber filetype plugin - Use Ruby Regexp.new() with untrusted pattern to prevent injection + * Fix memory safety issues in spellfile.c - Add recursion limit to + read_tree_node(), add length limit check in tree_count_words(), + use alloc_clear() in spell_read_tree() -- deepin-ci-robot Thu, 11 Jun 2026 22:41:37 +0800 diff --git a/debian/patches/security-patch-9.2.0513-spellfile-memory-safety.patch b/debian/patches/security-patch-9.2.0513-spellfile-memory-safety.patch new file mode 100644 index 00000000..eb5d6cf5 --- /dev/null +++ b/debian/patches/security-patch-9.2.0513-spellfile-memory-safety.patch @@ -0,0 +1,196 @@ +Index: vim/src/spell.h +=================================================================== +--- vim.orig/src/spell.h ++++ vim/src/spell.h +@@ -119,6 +119,7 @@ struct slang_S + // Info from the .sug file. Loaded on demand. + time_t sl_sugtime; // timestamp for .sug file + char_u *sl_sbyts; // soundfolded word bytes ++ long sl_sbyts_len; // length of sl_sbyts + idx_T *sl_sidxs; // soundfolded word indexes + buf_T *sl_sugbuf; // buffer with word number table + int sl_sugloaded; // TRUE when .sug file was loaded or failed to +Index: vim/src/spellfile.c +=================================================================== +--- vim.orig/src/spellfile.c ++++ vim/src/spellfile.c +@@ -313,7 +313,7 @@ static int set_sofo(slang_T *lp, char_u *from, char_u *to); + static void set_sal_first(slang_T *lp); + static int *mb_str2wide(char_u *s); + static int spell_read_tree(FILE *fd, char_u **bytsp, long *bytsp_len, idx_T **idxsp, int prefixtree, int prefixcnt); +-static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr); ++static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx, int prefixtree, int maxprefcondnr, int depth); + static void set_spell_charflags(char_u *flags, int cnt, char_u *upp); + static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp); + static void set_map_str(slang_T *lp, char_u *map); +@@ -597,7 +597,7 @@ endOK: + * Returns the total number of words. + */ + static void +-tree_count_words(char_u *byts, idx_T *idxs) ++tree_count_words(char_u *byts, long byts_len, idx_T *idxs) + { + int depth; + idx_T arridx[MAXWLEN]; +@@ -635,8 +635,8 @@ tree_count_words(char_u *byts, idx_T *idxs) + ++wordcount[depth]; + + // Skip over any other NUL bytes (same word with different +- // flags). +- while (byts[n + 1] == 0) ++ // flags). But don't go over the end ++ while (n + 1 < byts_len && byts[n + 1] == 0) + { + ++n; + ++curi[depth]; +@@ -732,8 +732,8 @@ suggest_load_files(void) + * : + * Read the trie with the soundfolded words. + */ +- if (spell_read_tree(fd, &slang->sl_sbyts, NULL, &slang->sl_sidxs, +- FALSE, 0) != 0) ++ if (spell_read_tree(fd, &slang->sl_sbyts, &slang->sl_sbyts_len, ++ &slang->sl_sidxs, FALSE, 0) != 0) + { + someerror: + semsg(_(e_error_while_reading_sug_file_str), +@@ -782,8 +782,10 @@ someerror: + * Need to put word counts in the word tries, so that we can find + * a word by its number. + */ +- tree_count_words(slang->sl_fbyts, slang->sl_fidxs); +- tree_count_words(slang->sl_sbyts, slang->sl_sidxs); ++ tree_count_words(slang->sl_fbyts, slang->sl_fbyts_len, ++ slang->sl_fidxs); ++ tree_count_words(slang->sl_sbyts, slang->sl_sbyts_len, ++ slang->sl_sidxs); + + nextone: + if (fd != NULL) +@@ -1603,8 +1605,11 @@ spell_read_tree( + if (len <= 0) + return 0; + +- // Allocate the byte array. +- bp = alloc(len); ++ // Allocate the byte array. Zero-initialize so that any position the ++ // tree does not visit reads as 0; a stray BY_INDEX shared reference ++ // into such a slot then behaves as end-of-word in spellsuggest() ++ // instead of consuming an arbitrary heap byte as a siblingcount. ++ bp = alloc_clear(len); + if (bp == NULL) + return SP_OTHERERROR; + *bytsp = bp; +@@ -1618,9 +1623,11 @@ spell_read_tree( + *idxsp = ip; + + // Recursively read the tree and store it in the array. +- idx = read_tree_node(fd, bp, ip, len, 0, prefixtree, prefixcnt); ++ idx = read_tree_node(fd, bp, ip, len, 0, prefixtree, prefixcnt, 0); + if (idx < 0) + return idx; ++ if (idx != len) ++ return SP_FORMERROR; + return 0; + } + +@@ -1642,7 +1649,8 @@ read_tree_node( + int maxidx, // size of arrays + idx_T startidx, // current index in "byts" and "idxs" + int prefixtree, // TRUE for reading PREFIXTREE +- int maxprefcondnr) // maximum for ++ int maxprefcondnr, // maximum for ++ int depth) // recursiong level + { + int len; + int i; +@@ -1652,6 +1660,12 @@ read_tree_node( + int c2; + #define SHARED_MASK 0x8000000 + ++ // Bail out on a crafted .spl whose tree recurses beyond the maximum ++ // word length: each tree level corresponds to one byte of a word, so ++ // any well-formed file has depth <= MAXWLEN. ++ if (depth > MAXWLEN) ++ return SP_FORMERROR; ++ + len = getc(fd); // + if (len <= 0) + return SP_TRUNCERROR; +@@ -1737,7 +1751,7 @@ read_tree_node( + { + idxs[startidx + i] = idx; + idx = read_tree_node(fd, byts, idxs, maxidx, idx, +- prefixtree, maxprefcondnr); ++ prefixtree, maxprefcondnr, depth + 1); + if (idx < 0) + break; + } +@@ -5649,7 +5663,7 @@ sug_filltree(spellinfo_T *spin, slang_T *slang) + spin->si_blocks_cnt = 0; + + // Skip over any other NUL bytes (same word with different +- // flags). But don't go over the end. ++ // flags). But don't go over the end + while (n + 1 < slang->sl_fbyts_len && byts[n + 1] == 0) + { + ++n; +Index: vim/src/testdir/test_spell.vim +=================================================================== +--- vim.orig/src/testdir/test_spell.vim ++++ vim/src/testdir/test_spell.vim +@@ -912,7 +912,10 @@ func Test_spellsuggest_too_deep() + " This was incrementing "depth" over MAXWLEN. + new + norm s000G00000000000000 +- sil norm ..vzG................vvzG0 v z= ++ try ++ sil norm ..vzG................vvzG0 v z= ++ catch /E759:/ ++ endtry + bwipe! + endfunc + +Index: vim/src/testdir/test_spellfile.vim +=================================================================== +--- vim.orig/src/testdir/test_spellfile.vim ++++ vim/src/testdir/test_spellfile.vim +@@ -372,6 +372,24 @@ func Test_spellfile_format_error() + " LWORDTREE: incorrect sibling node count + call Spellfile_Test(0zFF00000001040000000000000000, 'E759:') + ++ " LWORDTREE: declared nodecount larger than the tree actually fills. ++ " Root has two siblings: 'x' (recurses into an end-of-word at idx 3..4) ++ " and BY_INDEX targeting position 9. Tree fills positions 0..4, leaving ++ " 5..9 unwritten — byts[9] would be uninitialized without the fix. ++ call Spellfile_Test(0zFF0000000A02780100000979010000000000000000000000, 'E759:') ++ ++ " LWORDTREE: recursion depth past MAXWLEN. A linear chain of 254 ++ " (siblingcount=1, byte='a') frames drives read_tree_node to depth ++ " MAXWLEN where the new guard rejects. The trailing (01 00) gives the ++ " chain a clean end-of-word so an *unguarded* parser would accept the ++ " file silently — that's what makes this a meaningful regression test ++ " for the depth check specifically (a deeper chain would also crash ++ " unguarded builds via stack overflow, which we don't want in CI). ++ let v = eval('0zFF00000200' .. repeat('0161', 255) ++ \ .. '0100' .. repeat('00', 8)) ++ ++ call Spellfile_Test(v, 'E759:') ++ + " KWORDTREE: missing tree node + call Spellfile_Test(0zFF0000000000000004, 'E758:') + +Index: vim/src/version.c +=================================================================== +--- vim.orig/src/version.c ++++ vim/src/version.c +@@ -729,6 +729,8 @@ static char *(features[]) = + + static int included_patches[] = + { /* Add new patch number below this line */ ++/**/ ++ 513, + /**/ + 512, + /**/ + From c8f5300a0744256957dff2de7cc66e8c31cb6d18 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 11 Jun 2026 22:42:22 +0800 Subject: [PATCH 4/8] fix(vim): CVE-2026-52858 Fix possible code execution with python3complete by disabling execution of import/from statements that could be injected through crafted Python files. Upstream: https://github.com/vim/vim/commit/4b850457e0d6a5ec3033b2ce5eddeef63819de93 (v9.2.0561) Generated-By: deepseek-v4-flash Co-Authored-By: hudeng --- debian/changelog | 18 ++- ...52858-python3complete-code-execution.patch | 140 ++++++++++++++++++ 2 files changed, 150 insertions(+), 8 deletions(-) create mode 100644 debian/patches/CVE-2026-52858-python3complete-code-execution.patch diff --git a/debian/changelog b/debian/changelog index 59d62570..d4574c78 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,13 +1,15 @@ vim (2:9.2.0461-1deepin2) unstable; urgency=medium - * Fix CVE-2026-47162: Code injection in netrw NetrwBookHistSave - function - Properly quote directory names using string() to - prevent injection - * Fix CVE-2026-47167: Code injection in cucumber filetype plugin - - Use Ruby Regexp.new() with untrusted pattern to prevent injection - * Fix memory safety issues in spellfile.c - Add recursion limit to - read_tree_node(), add length limit check in tree_count_words(), - use alloc_clear() in spell_read_tree() + * Fix CVE-2026-47162: Code injection in netrw NetrwBookHistSave function + - Properly quote directory names using string() to prevent injection + * Fix CVE-2026-47167: Code injection in cucumber filetype plugin + - Use Ruby Regexp.new() with untrusted pattern to prevent injection + * Fix memory safety issues in spellfile.c + - Add recursion limit to read_tree_node(), add length limit check + in tree_count_words(), use alloc_clear() in spell_read_tree() + + * Fix CVE-2026-52858: possible code execution with python3complete - + Disable execution of import/from statements -- deepin-ci-robot Thu, 11 Jun 2026 22:41:37 +0800 diff --git a/debian/patches/CVE-2026-52858-python3complete-code-execution.patch b/debian/patches/CVE-2026-52858-python3complete-code-execution.patch new file mode 100644 index 00000000..bc94109e --- /dev/null +++ b/debian/patches/CVE-2026-52858-python3complete-code-execution.patch @@ -0,0 +1,140 @@ +Index: vim/runtime/autoload/README.txt +=================================================================== +--- vim.orig/runtime/autoload/README.txt ++++ vim/runtime/autoload/README.txt +@@ -17,6 +17,7 @@ htmlcomplete.vim HTML + javascriptcomplete.vim Javascript + phpcomplete.vim PHP + pythoncomplete.vim Python ++python3complete.vim Python + rubycomplete.vim Ruby + syntaxcomplete.vim from syntax highlighting + xmlcomplete.vim XML (uses files in the xml directory) +Index: vim/runtime/autoload/python3complete.vim +=================================================================== +--- vim.orig/runtime/autoload/python3complete.vim ++++ vim/runtime/autoload/python3complete.vim +@@ -14,6 +14,10 @@ + " i.e. "import url" + " Continue parsing on invalid line?? + " ++" v 0.10 by Vim project ++" * disables importing local modules, unless the global Vim variable ++" g:pythoncomplete_allow_import is set to non-zero ++" + " v 0.9 + " * Fixed docstring parsing for classes and functions + " * Fixed parsing of *args and **kwargs type arguments +@@ -132,11 +136,20 @@ class Completer(object): + + def evalsource(self,text,line=0): + sc = self.parser.parse(text,line) ++ try: allow_imports = int( ++ vim.eval("get(g:, 'pythoncomplete_allow_import', 0)")) ++ except Exception: ++ allow_imports = 0 + src = sc.get_code() + dbg("source: %s" % src) + try: exec(src,self.compldict) + except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1])) + for l in sc.locals: ++ # Executing import/from statements harvested from the buffer runs ++ # arbitrary package code; only do so when the user opted in. ++ if not allow_imports and (l.startswith('import') ++ or l.startswith('from ')): ++ continue + try: exec(l,self.compldict) + except: dbg("locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l)) + +@@ -300,13 +313,11 @@ class Scope(object): + def get_code(self): + str = "" + if len(self.docstr) > 0: str += '"""'+self.docstr+'"""\n' +- for l in self.locals: +- if l.startswith('import'): str += l+'\n' + str += 'class _PyCmplNoType:\n def __getattr__(self,name):\n return None\n' + for sub in self.subscopes: + str += sub.get_code() + for l in self.locals: +- if not l.startswith('import'): str += l+'\n' ++ if not l.startswith('import') and not l.startswith('from '): str += l+'\n' + + return str + +Index: vim/runtime/autoload/pythoncomplete.vim +=================================================================== +--- vim.orig/runtime/autoload/pythoncomplete.vim ++++ vim/runtime/autoload/pythoncomplete.vim +@@ -12,6 +12,10 @@ + " i.e. "import url" + " Continue parsing on invalid line?? + " ++" v 0.10 by Vim project ++" * disables importing local modules, unless the global Vim variable ++" g:pythoncomplete_allow_import is set to non-zero ++" + " v 0.9 + " * Fixed docstring parsing for classes and functions + " * Fixed parsing of *args and **kwargs type arguments +@@ -146,11 +150,20 @@ class Completer(object): + + def evalsource(self,text,line=0): + sc = self.parser.parse(text,line) ++ try: allow_imports = int( ++ vim.eval("get(g:, 'pythoncomplete_allow_import', 0)")) ++ except Exception: ++ allow_imports = 0 + src = sc.get_code() + dbg("source: %s" % src) + try: exec(src) in self.compldict + except: dbg("parser: %s, %s" % (sys.exc_info()[0],sys.exc_info()[1])) + for l in sc.locals: ++ # Executing import/from statements harvested from the buffer runs ++ # arbitrary package code; only do so when the user opted in. ++ if not allow_imports and (l.startswith('import') ++ or l.startswith('from ')): ++ continue + try: exec(l) in self.compldict + except: dbg("locals: %s, %s [%s]" % (sys.exc_info()[0],sys.exc_info()[1],l)) + +@@ -315,13 +328,11 @@ class Scope(object): + def get_code(self): + str = "" + if len(self.docstr) > 0: str += '"""'+self.docstr+'"""\n' +- for l in self.locals: +- if l.startswith('import'): str += l+'\n' + str += 'class _PyCmplNoType:\n def __getattr__(self,name):\n return None\n' + for sub in self.subscopes: + str += sub.get_code() + for l in self.locals: +- if not l.startswith('import'): str += l+'\n' ++ if not l.startswith('import') and not l.startswith('from '): str += l+'\n' + + return str + +Index: vim/runtime/doc/filetype.txt +=================================================================== +--- vim.orig/runtime/doc/filetype.txt ++++ vim/runtime/doc/filetype.txt +@@ -976,7 +976,20 @@ By default the following options are set, in accordance with PEP8: > + To disable this behavior, set the following variable in your vimrc: > + + let g:python_recommended_style = 0 +- ++< ++Python omni-completion |compl-omni| is provided by python3complete.vim (or ++pythoncomplete.vim) for Vim builds with the |+python|/|+python3| interpreter. ++By default it does not inspect the import / from statements found in the ++buffer. This means completion of names defined in the buffer itself (classes, ++functions, variables) works, but completion of members of imported modules is ++not offered. ++ ++To enable completion of imported module members, set: > ++ let g:pythoncomplete_allow_import = 1 ++< ++WARNING: enabling this causes omni-completion to execute the import statements ++found in the buffer through Python's import machinery, which runs the imported ++modules' top-level code. Only enable this for code you trust. + + QF QUICKFIX *qf.vim* *ft-qf-plugin* + From 9163b4fe97c3277eb29d7f1bf43ef86d409134f6 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 11 Jun 2026 22:42:28 +0800 Subject: [PATCH 5/8] fix(vim): CVE-2026-52860 Fix possible code execution with python complete by stripping default expressions and annotations from generated source for pythoncomplete and python3complete to prevent injection of arbitrary code. Upstream: https://github.com/vim/vim/commit/c8c63673b23dce975a5d4cb99ac4c7e3a8523338 (v9.2.0597) Generated-By: deepseek-v4-flash Co-Authored-By: hudeng --- debian/changelog | 2 + ...52860-python-complete-code-execution.patch | 415 ++++++++++++++++++ 2 files changed, 417 insertions(+) create mode 100644 debian/patches/CVE-2026-52860-python-complete-code-execution.patch diff --git a/debian/changelog b/debian/changelog index d4574c78..d1c66c8d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -10,6 +10,8 @@ vim (2:9.2.0461-1deepin2) unstable; urgency=medium * Fix CVE-2026-52858: possible code execution with python3complete - Disable execution of import/from statements + * Fix CVE-2026-52860: possible code execution with python complete - + Strip default expressions and annotations from generated source -- deepin-ci-robot Thu, 11 Jun 2026 22:41:37 +0800 diff --git a/debian/patches/CVE-2026-52860-python-complete-code-execution.patch b/debian/patches/CVE-2026-52860-python-complete-code-execution.patch new file mode 100644 index 00000000..b79cc2fb --- /dev/null +++ b/debian/patches/CVE-2026-52860-python-complete-code-execution.patch @@ -0,0 +1,415 @@ +Index: vim/runtime/autoload/python3complete.vim +=================================================================== +--- vim.orig/runtime/autoload/python3complete.vim ++++ vim/runtime/autoload/python3complete.vim +@@ -1,8 +1,8 @@ + "python3complete.vim - Omni Completion for python + " Maintainer: + " Previous Maintainer: Aaron Griffin +-" Version: 0.9 +-" Last Updated: 2022 Mar 30 ++" Version: 0.10 ++" Last Updated: 2026 Jun 04 + " + " Roland Puntaier: this file contains adaptations for python3 and is parallel to pythoncomplete.vim + " +@@ -17,6 +17,11 @@ + " v 0.10 by Vim project + " * disables importing local modules, unless the global Vim variable + " g:pythoncomplete_allow_import is set to non-zero ++" * strip default values and annotations from function parameter lists ++" before exec(), and whitelist class base lists to dotted names: the ++" previous code passed buffer-supplied expressions to exec() which ++" Python evaluates at definition time, allowing arbitrary code ++" execution via crafted def/class headers + " + " v 0.9 + " * Fixed docstring parsing for classes and functions +@@ -100,6 +105,24 @@ warnings.simplefilter(action='ignore', category=FutureWarning) + + import sys, tokenize, io, types + from token import NAME, DEDENT, NEWLINE, STRING ++import re ++ ++# Used by Class.get_code(): a base class expression is only included in the ++# code passed to exec() if it is a pure dotted name (e.g. "Base", "mod.Base", ++# "pkg.sub.Cls"). Anything containing calls, subscripts, "=", ":" or other ++# operators is dropped, since exec()-ing it would evaluate buffer-supplied ++# expressions. See the security note in the file header. ++_DOTTED_NAME_RE = re.compile(r'^[A-Za-z_]\w*(\s*\.\s*[A-Za-z_]\w*)*$') ++ ++def _strip_param(p): ++ # Return the bare parameter name from a parameter spec harvested by ++ # _parenparse(), discarding any default value or annotation. Default ++ # values and annotations would otherwise be evaluated by exec() at ++ # function-definition time. Star prefixes ("*args", "**kw") and bare ++ # "*" / "/" are preserved as written. ++ p = p.split('=', 1)[0] ++ p = p.split(':', 1)[0] ++ return p.strip() + + debugstmts=[] + def dbg(s): debugstmts.append(s) +@@ -350,7 +373,13 @@ class Class(Scope): + return c + def get_code(self): + str = '%sclass %s' % (self.currentindent(),self.name) +- if len(self.supers) > 0: str += '(%s)' % ','.join(self.supers) ++ # Only include base class expressions that are pure dotted names. ++ # Anything else (calls, subscripts, conditionals, ...) is dropped ++ # because exec() would evaluate it at class-definition time. See ++ # the security note in the file header. ++ safe_supers = [s.strip() for s in self.supers ++ if _DOTTED_NAME_RE.match(s.strip())] ++ if len(safe_supers) > 0: str += '(%s)' % ','.join(safe_supers) + str += ':\n' + if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n' + if len(self.subscopes) > 0: +@@ -367,8 +396,14 @@ class Function(Scope): + def copy_decl(self,indent=0): + return Function(self.name,self.params,indent, self.docstr) + def get_code(self): ++ # Strip default values and annotations from each parameter before ++ # joining: exec() evaluates these at definition time and a hostile ++ # buffer could otherwise execute arbitrary code via crafted def ++ # headers. See file header for details. ++ safe_params = [_strip_param(p) for p in self.params] ++ safe_params = [p for p in safe_params if p] + str = "%sdef %s(%s):\n" % \ +- (self.currentindent(),self.name,','.join(self.params)) ++ (self.currentindent(),self.name,','.join(safe_params)) + if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n' + str += "%spass\n" % self.childindent() + return str +Index: vim/runtime/autoload/pythoncomplete.vim +=================================================================== +--- vim.orig/runtime/autoload/pythoncomplete.vim ++++ vim/runtime/autoload/pythoncomplete.vim +@@ -1,8 +1,8 @@ + "pythoncomplete.vim - Omni Completion for python + " Maintainer: + " Previous Maintainer: Aaron Griffin +-" Version: 0.9 +-" Last Updated: 2020 Oct 9 ++" Version: 0.10 ++" Last Updated: 2026 Jun 04 + " + " Changes + " TODO: +@@ -15,6 +15,11 @@ + " v 0.10 by Vim project + " * disables importing local modules, unless the global Vim variable + " g:pythoncomplete_allow_import is set to non-zero ++" * strip default values and annotations from function parameter lists ++" before exec(), and whitelist class base lists to dotted names: the ++" previous code passed buffer-supplied expressions to exec() which ++" Python evaluates at definition time, allowing arbitrary code ++" execution via crafted def/class headers + " + " v 0.9 + " * Fixed docstring parsing for classes and functions +@@ -95,6 +100,24 @@ function! s:DefPython() + python << PYTHONEOF + import sys, tokenize, cStringIO, types + from token import NAME, DEDENT, NEWLINE, STRING ++import re ++ ++# Used by Class.get_code(): a base class expression is only included in the ++# code passed to exec() if it is a pure dotted name (e.g. "Base", "mod.Base", ++# "pkg.sub.Cls"). Anything containing calls, subscripts, "=", ":" or other ++# operators is dropped, since exec()-ing it would evaluate buffer-supplied ++# expressions. See the security note in the file header. ++_DOTTED_NAME_RE = re.compile(r'^[A-Za-z_]\w*(\s*\.\s*[A-Za-z_]\w*)*$') ++ ++def _strip_param(p): ++ # Return the bare parameter name from a parameter spec harvested by ++ # _parenparse(), discarding any default value or annotation. Default ++ # values and annotations would otherwise be evaluated by exec() at ++ # function-definition time. Star prefixes ("*args", "**kw") and bare ++ # "*" / "/" are preserved as written. ++ p = p.split('=', 1)[0] ++ p = p.split(':', 1)[0] ++ return p.strip() + + debugstmts=[] + def dbg(s): debugstmts.append(s) +@@ -365,7 +388,13 @@ class Class(Scope): + return c + def get_code(self): + str = '%sclass %s' % (self.currentindent(),self.name) +- if len(self.supers) > 0: str += '(%s)' % ','.join(self.supers) ++ # Only include base class expressions that are pure dotted names. ++ # Anything else (calls, subscripts, conditionals, ...) is dropped ++ # because exec() would evaluate it at class-definition time. See ++ # the security note in the file header. ++ safe_supers = [s.strip() for s in self.supers ++ if _DOTTED_NAME_RE.match(s.strip())] ++ if len(safe_supers) > 0: str += '(%s)' % ','.join(safe_supers) + str += ':\n' + if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n' + if len(self.subscopes) > 0: +@@ -382,8 +411,14 @@ class Function(Scope): + def copy_decl(self,indent=0): + return Function(self.name,self.params,indent, self.docstr) + def get_code(self): ++ # Strip default values and annotations from each parameter before ++ # joining: exec() evaluates these at definition time and a hostile ++ # buffer could otherwise execute arbitrary code via crafted def ++ # headers. See file header for details. ++ safe_params = [_strip_param(p) for p in self.params] ++ safe_params = [p for p in safe_params if p] + str = "%sdef %s(%s):\n" % \ +- (self.currentindent(),self.name,','.join(self.params)) ++ (self.currentindent(),self.name,','.join(safe_params)) + if len(self.docstr) > 0: str += self.childindent()+'"""'+self.docstr+'"""\n' + str += "%spass\n" % self.childindent() + return str +Index: vim/src/testdir/Make_all.mak +=================================================================== +--- vim.orig/src/testdir/Make_all.mak ++++ vim/src/testdir/Make_all.mak +@@ -251,6 +251,7 @@ NEW_TESTS = \ + test_plugin_matchit \ + test_plugin_matchparen \ + test_plugin_netrw \ ++ test_plugin_python3complete \ + test_plugin_osc52 \ + test_plugin_tar \ + test_plugin_termdebug \ +@@ -530,6 +531,7 @@ NEW_TESTS_RES = \ + test_plugin_matchit.res \ + test_plugin_matchparen.res \ + test_plugin_netrw.res \ ++ test_plugin_python3complete.res \ + test_plugin_osc52.res \ + test_plugin_tar.res \ + test_plugin_termdebug.res \ +Index: vim/src/testdir/test_plugin_python3complete.vim +=================================================================== +--- /dev/null ++++ vim/src/testdir/test_plugin_python3complete.vim +@@ -0,0 +1,224 @@ ++" Tests for the Python omni-completion plugin (runtime/autoload/python3complete.vim). ++" ++CheckFeature python3 ++ ++" Run omni-completion against the given buffer contents and assert that the ++" marker file was not created. Pre-patch behaviour exec()s reconstructed ++" def/class headers, which evaluates the buffer-supplied expression and ++" creates the marker file. Post-patch, the expressions are stripped. ++func s:CompleteAndExpectNoMarker(buffer_lines, marker_path, msg) ++ call delete(a:marker_path) ++ defer delete(a:marker_path) ++ let g:pythoncomplete_allow_import = 0 ++ new ++ setfiletype python ++ call setline(1, a:buffer_lines) ++ call cursor(line('$'), col([line('$'), '$'])) ++ ++ " The PoC trigger -- direct invocation of the omnifunc with an empty base. ++ " This is the same path Vim takes for CTRL-X CTRL-O. ++ silent! call python3complete#Complete(0, '') ++ ++ call assert_false(filereadable(a:marker_path), ++ \ a:msg . ' (marker ' . a:marker_path . ' was created)') ++ ++ bwipe! ++ unlet! g:pythoncomplete_allow_import ++endfunc ++ ++func Test_python3complete_no_exec_via_function_default() ++ let marker = tempname() ++ call s:CompleteAndExpectNoMarker([ ++ \ 'def f(x=open(' . string(marker) . ', "w").close()):', ++ \ ' pass', ++ \ 'f.', ++ \ ], marker, ++ \ 'function default expression was evaluated during omni-completion') ++endfunc ++ ++func Test_python3complete_no_exec_via_function_annotation() ++ let marker = tempname() ++ call s:CompleteAndExpectNoMarker([ ++ \ 'def f(x: open(' . string(marker) . ', "w").close()):', ++ \ ' pass', ++ \ 'f.', ++ \ ], marker, ++ \ 'function annotation expression was evaluated during omni-completion') ++endfunc ++ ++func Test_python3complete_no_exec_via_class_base() ++ let marker = tempname() ++ " "or object" gives the class a valid base after the side-effecting ++ " open().close() expression returns None. Without "or object" the ++ " exec would raise TypeError, but the file would still be created ++ " before the exception -- the assertion would still hold. Using ++ " "or object" keeps the buffer parseable as valid Python. ++ call s:CompleteAndExpectNoMarker([ ++ \ 'class Foo(open(' . string(marker) . ', "w").close() or object):', ++ \ ' pass', ++ \ 'Foo.', ++ \ ], marker, ++ \ 'class base expression was evaluated during omni-completion') ++endfunc ++ ++func Test_python3complete_no_exec_with_multiple_params() ++ " The strip must apply to every parameter, not just the first. ++ let marker = tempname() ++ call s:CompleteAndExpectNoMarker([ ++ \ 'def f(a, b=1, c=open(' . string(marker) . ', "w").close(), d=2):', ++ \ ' pass', ++ \ 'f.', ++ \ ], marker, ++ \ 'non-first parameter default was evaluated during omni-completion') ++endfunc ++ ++func Test_python3complete_no_exec_via_starargs_default() ++ " "*args" and "**kw" must still be preserved after stripping; ensure a ++ " default following them is also stripped. ++ let marker = tempname() ++ call s:CompleteAndExpectNoMarker([ ++ \ 'def f(*args, key=open(' . string(marker) . ', "w").close(), **kw):', ++ \ ' pass', ++ \ 'f.', ++ \ ], marker, ++ \ 'keyword-only default after *args was evaluated during omni-completion') ++endfunc ++ ++func Test_python3complete_normal_completion_still_works() ++ " Positive control: completion against a buffer with a legitimate class ++ " must still produce completion items. The stripping logic should not ++ " break the normal completion path. ++ let g:pythoncomplete_allow_import = 0 ++ ++ new ++ setfiletype python ++ call setline(1, [ ++ \ 'class MyHelper:', ++ \ ' def alpha(self): pass', ++ \ ' def beta(self): pass', ++ \ 'h = MyHelper()', ++ \ 'h.', ++ \ ]) ++ call cursor(5, 3) ++ ++ " First call returns the column to start completion at; second returns ++ " the list of completion items. ++ let start = python3complete#Complete(1, '') ++ call assert_true(start >= 0, ++ \ 'python3complete#Complete(1, "") returned ' . start) ++ ++ let items = python3complete#Complete(0, '') ++ " Items should be a list (possibly empty if the parser can't resolve "h", ++ " but should not be a parse error from our stripping changes). ++ call assert_equal(type([]), type(items), ++ \ 'python3complete#Complete(0, "") did not return a list') ++ ++ bwipe! ++ unlet! g:pythoncomplete_allow_import ++endfunc ++ ++func Test_python3complete_inherited_completion_via_dotted_base() ++ " Positive control for the class-base whitelist: a dotted-name base class ++ " (the common, safe case) must still be carried into the reconstructed ++ " source so that completion on a subclass can resolve inherited members. ++ let g:pythoncomplete_allow_import = 0 ++ ++ new ++ setfiletype python ++ call setline(1, [ ++ \ 'class Base:', ++ \ ' def shared(self): pass', ++ \ 'class Derived(Base):', ++ \ ' def own(self): pass', ++ \ 'd = Derived()', ++ \ 'd.', ++ \ ]) ++ call cursor(6, 3) ++ ++ let items = python3complete#Complete(0, '') ++ call assert_equal(type([]), type(items), ++ \ 'completion against a subclass with a dotted base did not return a list') ++ ++ bwipe! ++ unlet! g:pythoncomplete_allow_import ++endfunc ++ ++" Build a tiny Python module that creates a marker file as a side effect of ++" being imported, add its directory to sys.path, run omni-completion against ++" a buffer containing `import vimtest_marker_mod`, and report whether the ++" marker file was created. Used by the two allow_import tests below. ++func s:RunImportCompletion(allow_import_value) ++ let g:pythoncomplete_allow_import = a:allow_import_value ++ let marker = tempname() ++ let module_dir = tempname() ++ call mkdir(module_dir, 'R') ++ ++ call writefile([ ++ \ 'open(' . string(marker) . ', "w").close()', ++ \ ], module_dir . '/vimtest_marker_mod.py') ++ ++ defer delete(marker) ++ ++ " Pass module_dir to Python via a g: variable so vim.eval() can read it. ++ let g:pythoncomplete_test_module_dir = module_dir ++ py3 << EOF ++import sys, vim ++_p = vim.eval('g:pythoncomplete_test_module_dir') ++if _p not in sys.path: ++ sys.path.insert(0, _p) ++# Drop any cached copy so the module body re-runs and the marker side ++# effect fires on import. ++sys.modules.pop('vimtest_marker_mod', None) ++EOF ++ ++ new ++ setfiletype python ++ call setline(1, [ ++ \ 'import vimtest_marker_mod', ++ \ 'vimtest_marker_mod.', ++ \ ]) ++ call cursor(2, 2) ++ ++ silent! call python3complete#Complete(0, '') ++ ++ let ran = filereadable(marker) ++ ++ bwipe! ++ unlet g:pythoncomplete_allow_import ++ ++ " Teardown: restore sys.path, drop the cached module so a subsequent ++ " test run starts clean, clean up the temp module dir. ++ py3 << EOF ++import sys, vim ++_p = vim.eval('g:pythoncomplete_test_module_dir') ++if _p in sys.path: ++ sys.path.remove(_p) ++sys.modules.pop('vimtest_marker_mod', None) ++EOF ++ unlet g:pythoncomplete_test_module_dir ++ call delete(module_dir, 'rf') ++ call delete(marker) ++ unlet! g:pythoncomplete_allow_import ++ ++ return ran ++endfunc ++ ++func Test_python3complete_allow_import_off_blocks_imports() ++ " GHSA-52mc-rq6p-rc7c mitigation: with the default flag value (0), an ++ " `import` line harvested from the buffer must NOT be exec()'d. The ++ " marker module's side effect (creating a file when its body runs) is ++ " the observable proof that the exec did or did not happen. ++ call assert_false(s:RunImportCompletion(0), ++ \ 'g:pythoncomplete_allow_import=0 did not block the buffer import') ++endfunc ++ ++func Test_python3complete_allow_import_on_runs_imports() ++ " Symmetric positive control: with the flag set to non-zero, the harvested ++ " import IS exec()'d and the module loads. Without this control the ++ " negative test above could pass for unrelated reasons (e.g. completion ++ " failing to parse the buffer at all). ++ call assert_true(s:RunImportCompletion(1), ++ \ 'g:pythoncomplete_allow_import=1 did not run the buffer import') ++endfunc ++ ++" vim: shiftwidth=2 sts=2 expandtab From 7acdda7b186187ea4580d5744fcdff5908b80f68 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 11 Jun 2026 22:43:12 +0800 Subject: [PATCH 6/8] fix(vim): security patch 9.2.0565 Fix out-of-bounds read in update_snapshot() when a terminal cell fills all VTERM_MAX_CHARS_PER_CELL slots by bounding the loop with i < VTERM_MAX_CHARS_PER_CELL. Upstream: https://github.com/vim/vim/commit/63680c6d3e0177a9f82e24560f53ca2a18c36fbb (v9.2.0565) Generated-By: deepseek-v4-flash Co-Authored-By: hudeng --- debian/changelog | 11 +- ...ch-9.2.0565-update-snapshot-oob-read.patch | 243 ++++++++++++++++++ 2 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 debian/patches/security-patch-9.2.0565-update-snapshot-oob-read.patch diff --git a/debian/changelog b/debian/changelog index d1c66c8d..26ac46f4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -8,10 +8,13 @@ vim (2:9.2.0461-1deepin2) unstable; urgency=medium - Add recursion limit to read_tree_node(), add length limit check in tree_count_words(), use alloc_clear() in spell_read_tree() - * Fix CVE-2026-52858: possible code execution with python3complete - - Disable execution of import/from statements - * Fix CVE-2026-52860: possible code execution with python complete - - Strip default expressions and annotations from generated source + * Fix CVE-2026-52858: possible code execution with python3complete + - Disable execution of import/from statements + * Fix CVE-2026-52860: possible code execution with python complete + - Strip default expressions and annotations from generated source + * Fix out-of-bounds read in update_snapshot() - Bound the loop with + i < VTERM_MAX_CHARS_PER_CELL to prevent reading past array + bounds -- deepin-ci-robot Thu, 11 Jun 2026 22:41:37 +0800 diff --git a/debian/patches/security-patch-9.2.0565-update-snapshot-oob-read.patch b/debian/patches/security-patch-9.2.0565-update-snapshot-oob-read.patch new file mode 100644 index 00000000..1680de62 --- /dev/null +++ b/debian/patches/security-patch-9.2.0565-update-snapshot-oob-read.patch @@ -0,0 +1,243 @@ +Index: vim/src/terminal.c +=================================================================== +--- vim.orig/src/terminal.c ++++ vim/src/terminal.c +@@ -2265,7 +2265,8 @@ update_snapshot(term_T *term) + int i; + int c; + +- for (i = 0; (c = cell.chars[i]) > 0 || i == 0; ++i) ++ for (i = 0; i < VTERM_MAX_CHARS_PER_CELL && ++ ((c = cell.chars[i]) > 0 || i == 0); ++i) + ga.ga_len += utf_char2bytes(c == NUL ? ' ' : c, + (char_u *)ga.ga_data + ga.ga_len); + } +Index: vim/src/testdir/samples/combining_chars.txt +=================================================================== +--- /dev/null ++++ vim/src/testdir/samples/combining_chars.txt +@@ -0,0 +1,200 @@ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ ++á́́́́ጁ ++á́́́́ጁ ++á́́́́ጁ ++á́́́́ጁ ++á́́́́ጁ ++á́́́́ጁ ++á́́́́ጁ ++á́́́́ጁ ++á́́́́ጁ ++á́́́́ጁ +Index: vim/src/testdir/test_terminal3.vim +=================================================================== +--- vim.orig/src/testdir/test_terminal3.vim ++++ vim/src/testdir/test_terminal3.vim +@@ -1241,4 +1241,19 @@ func Test_terminal_csi_args_overflow() + call StopVimInTerminal(buf) + endfunc + ++func Test_terminal_output_combining_chars() ++ CheckUnix ++ new ++ let cmd = "cat samples/combining_chars.txt" ++ let buf = term_start(cmd, {'curwin': 1, 'term_finish': 'open', 'term_rows': 10, 'term_cols': 30}) ++ call WaitForAssert({-> assert_match('finished', term_getstatus(buf))}) ++ call TermWait(buf) ++ let lines = getbufline(buf, 1, '$') ++ " get byte lengths to confirm combining chars present ++ let lens = map(copy(lines), 'len(v:val)') ++ let expected = repeat([11], 190) + repeat([14], 10) ++ call assert_equal(expected, lens) ++ bw! ++endfunc ++ + " vim: shiftwidth=2 sts=2 expandtab From c2b083549667d5f79bc8466d03dcf31bd90e6ca4 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 11 Jun 2026 22:43:22 +0800 Subject: [PATCH 7/8] fix(vim): shellescape for getscript/vimball/rust plugins Fix shellescape() calls in getscript, vimball and rust plugins by passing 1 as second argument for :! command context to prevent potential command injection through crafted filenames. Upstream: https://github.com/vim/vim/commit/129486193e2f3f9241e9bb6aedb3ec418458e85b Generated-By: deepseek-v4-flash Co-Authored-By: hudeng --- debian/changelog | 3 + ...2-shellescape-getscript-vimball-rust.patch | 205 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 debian/patches/security-patch-9.2-shellescape-getscript-vimball-rust.patch diff --git a/debian/changelog b/debian/changelog index 26ac46f4..eac49087 100644 --- a/debian/changelog +++ b/debian/changelog @@ -15,6 +15,9 @@ vim (2:9.2.0461-1deepin2) unstable; urgency=medium * Fix out-of-bounds read in update_snapshot() - Bound the loop with i < VTERM_MAX_CHARS_PER_CELL to prevent reading past array bounds + * Fix shellescape() calls in getscript/vimball/rust - Pass 1 as + second argument to shellescape() in :! command contexts to + prevent potential command injection -- deepin-ci-robot Thu, 11 Jun 2026 22:41:37 +0800 diff --git a/debian/patches/security-patch-9.2-shellescape-getscript-vimball-rust.patch b/debian/patches/security-patch-9.2-shellescape-getscript-vimball-rust.patch new file mode 100644 index 00000000..c08f5512 --- /dev/null +++ b/debian/patches/security-patch-9.2-shellescape-getscript-vimball-rust.patch @@ -0,0 +1,205 @@ +Index: vim/runtime/autoload/getscript.vim +=================================================================== +--- vim.orig/runtime/autoload/getscript.vim ++++ vim/runtime/autoload/getscript.vim +@@ -15,6 +15,7 @@ + " 2025 Feb 28 by Vim Project: add support for bzip3 (#16755) + " 2025 May 11 by Vim Project: check network connectivity (#17249) + " 2025 Dec 21 by Vim Project: make the wget check more robust (#18987) ++" 2026 May 20 by Vim Project: use correct shellescape() with ! command + " }}} + " + " GetLatestVimScripts: 642 1 :AutoInstall: getscript.vim +@@ -433,9 +434,9 @@ fun! s:GetOneScript(...) + let itry= 1 + while itry <= 3 + if has("win32") || has("win16") || has("win95") +- new|exe "silent r!".g:GetLatestVimScripts_wget." ".g:GetLatestVimScripts_options." ".shellescape(tmpfile).' '.shellescape(scriptaddr)|bw! ++ new|exe "silent r!".g:GetLatestVimScripts_wget." ".g:GetLatestVimScripts_options." ".shellescape(tmpfile,1).' '.shellescape(scriptaddr,1)|bw! + else +- exe "silent !".g:GetLatestVimScripts_wget." ".g:GetLatestVimScripts_options." ".shellescape(tmpfile)." ".shellescape(scriptaddr) ++ exe "silent !".g:GetLatestVimScripts_wget." ".g:GetLatestVimScripts_options." ".shellescape(tmpfile,1)." ".shellescape(scriptaddr,1) + endif + if itry == 1 + exe "silent vsplit ".fnameescape(tmpfile) +@@ -503,9 +504,9 @@ fun! s:GetOneScript(...) + " ----------------------------------------------------------------------------- + echomsg ".downloading new <".sname.">" + if has("win32") || has("win16") || has("win95") +- new|exe "silent r!".g:GetLatestVimScripts_wget." ".g:GetLatestVimScripts_options." ".shellescape(sname)." ".shellescape(g:GetLatestVimScripts_downloadaddr.latestsrcid)|bw! ++ new|exe "silent r!".g:GetLatestVimScripts_wget." ".g:GetLatestVimScripts_options." ".shellescape(sname,1)." ".shellescape(g:GetLatestVimScripts_downloadaddr.latestsrcid,1)|bw! + else +- exe "silent !".g:GetLatestVimScripts_wget." ".g:GetLatestVimScripts_options." ".shellescape(sname)." ".shellescape(g:GetLatestVimScripts_downloadaddr.latestsrcid) ++ exe "silent !".g:GetLatestVimScripts_wget." ".g:GetLatestVimScripts_options." ".shellescape(sname,1)." ".shellescape(g:GetLatestVimScripts_downloadaddr.latestsrcid,1) + endif + + " -------------------------------------------------------------------------- +@@ -513,7 +514,7 @@ fun! s:GetOneScript(...) + " -------------------------------------------------------------------------- + if doautoinstall + if filereadable(sname) +- exe "silent !".g:GetLatestVimScripts_mv." ".shellescape(sname)." ".shellescape(s:autoinstall) ++ exe "silent !".g:GetLatestVimScripts_mv." ".shellescape(sname,1)." ".shellescape(s:autoinstall,1) + let curdir = fnameescape(substitute(getcwd(),'\','/','ge')) + let installdir= curdir."/Installed" + if !isdirectory(installdir) +@@ -532,33 +533,33 @@ fun! s:GetOneScript(...) + + " decompress + if sname =~ '\.bz2$' +- exe "sil !".g:GetLatestVimScripts_bunzip2." ".shellescape(sname) ++ exe "sil !".g:GetLatestVimScripts_bunzip2." ".shellescape(sname,1) + let sname= substitute(sname,'\.bz2$','','') + elseif sname =~ '\.bz3$' +- exe "sil !".g:GetLatestVimScripts_bunzip3." ".shellescape(sname) ++ exe "sil !".g:GetLatestVimScripts_bunzip3." ".shellescape(sname,1) + let sname= substitute(sname,'\.bz3$','','') + elseif sname =~ '\.gz$' +- exe "sil !".g:GetLatestVimScripts_gunzip." ".shellescape(sname) ++ exe "sil !".g:GetLatestVimScripts_gunzip." ".shellescape(sname,1) + let sname= substitute(sname,'\.gz$','','') + elseif sname =~ '\.xz$' +- exe "sil !".g:GetLatestVimScripts_unxz." ".shellescape(sname) ++ exe "sil !".g:GetLatestVimScripts_unxz." ".shellescape(sname,1) + let sname= substitute(sname,'\.xz$','','') + else + endif + + " distribute archive(.zip, .tar, .vba, .vmb, ...) contents + if sname =~ '\.zip$' +- exe "silent !".g:GetLatestVimScripts_unzip." -o ".shellescape(sname) ++ exe "silent !".g:GetLatestVimScripts_unzip." -o ".shellescape(sname,1) + elseif sname =~ '\.tar$' +- exe "silent !tar -xvf ".shellescape(sname) ++ exe "silent !tar -xvf ".shellescape(sname,1) + elseif sname =~ '\.tgz$' +- exe "silent !tar -zxvf ".shellescape(sname) ++ exe "silent !tar -zxvf ".shellescape(sname,1) + elseif sname =~ '\.taz$' +- exe "silent !tar -Zxvf ".shellescape(sname) ++ exe "silent !tar -Zxvf ".shellescape(sname,1) + elseif sname =~ '\.tbz$' +- exe "silent !tar -jxvf ".shellescape(sname) ++ exe "silent !tar -jxvf ".shellescape(sname,1) + elseif sname =~ '\.txz$' +- exe "silent !tar -Jxvf ".shellescape(sname) ++ exe "silent !tar -Jxvf ".shellescape(sname,1) + elseif sname =~ '\.vba$\|\.vmb$' + silent 1split + if exists("g:vimball_home") +@@ -579,12 +580,12 @@ fun! s:GetOneScript(...) + " move plugin to plugin/ or AsNeeded/ directory + " --------------------------------------------- + if sname =~ '.vim$' +- exe "silent !".g:GetLatestVimScripts_mv." ".shellescape(sname)." ".tgtdir ++ exe "silent !".g:GetLatestVimScripts_mv." ".shellescape(sname,1)." ".tgtdir + else +- exe "silent !".g:GetLatestVimScripts_mv." ".shellescape(sname)." ".installdir ++ exe "silent !".g:GetLatestVimScripts_mv." ".shellescape(sname,1)." ".installdir + endif + if tgtdir != "plugin" +- exe "silent !".g:GetLatestVimScripts_mv." ".shellescape("plugin/".pname)." ".tgtdir ++ exe "silent !".g:GetLatestVimScripts_mv." ".shellescape("plugin/".pname,1)." ".tgtdir + endif + + " helptags step +Index: vim/runtime/autoload/rust.vim +=================================================================== +--- vim.orig/runtime/autoload/rust.vim ++++ vim/runtime/autoload/rust.vim +@@ -1,5 +1,6 @@ + " Description: Helper functions for Rust commands/mappings + " Last Modified: 2023-09-11 ++" 2026 May 20 by Vim project: use correct shellescape() with ! command + " For bugs, patches and license go to https://github.com/rust-lang/rust.vim + + function! rust#Load() +@@ -125,7 +126,7 @@ function! s:Run(dict, rustc_args, args) + echohl None + endif + if !v:shell_error +- exe '!' . shellescape(exepath) . " " . join(map(a:args, 'shellescape(v:val)')) ++ exe '!' . shellescape(exepath,1) . " " . join(map(a:args, 'shellescape(v:val,1)')) + endif + endfunction + +Index: vim/runtime/autoload/vimball.vim +=================================================================== +--- vim.orig/runtime/autoload/vimball.vim ++++ vim/runtime/autoload/vimball.vim +@@ -1,10 +1,9 @@ + " vimball.vim : construct a file containing both paths and files + " Maintainer: This runtime file is looking for a new maintainer. + " Original Author: Charles E. Campbell +-" Date: Apr 16, 2026 ++" Date: May 20, 2026 + " Version: 37 (with modifications from the Vim Project) + " GetLatestVimScripts: 1502 1 :AutoInstall: vimball.vim +-" Last Change: + " Copyright: (c) 2004-2011 by Charles E. Campbell + " The VIM LICENSE applies to Vimball.vim, and Vimball.txt + " (see |copyright|) except use "Vimball" instead of "Vim". +@@ -427,7 +426,7 @@ fun! vimball#Decompress(fname,...) + " decompression: + if expand("%") =~ '.*\.gz' && executable("gunzip") + " handle *.gz with gunzip +- silent exe "!gunzip ".shellescape(a:fname) ++ silent exe "!gunzip ".shellescape(a:fname,1) + if v:shell_error != 0 + call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) gunzip may have failed with <".a:fname.">") + endif +@@ -437,7 +436,7 @@ fun! vimball#Decompress(fname,...) + + elseif expand("%") =~ '.*\.gz' && executable("gzip") + " handle *.gz with gzip -d +- silent exe "!gzip -d ".shellescape(a:fname) ++ silent exe "!gzip -d ".shellescape(a:fname,1) + if v:shell_error != 0 + call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "gzip -d" may have failed with <'.a:fname.">") + endif +@@ -447,7 +446,7 @@ fun! vimball#Decompress(fname,...) + + elseif expand("%") =~ '.*\.bz2' && executable("bunzip2") + " handle *.bz2 with bunzip2 +- silent exe "!bunzip2 ".shellescape(a:fname) ++ silent exe "!bunzip2 ".shellescape(a:fname,1) + if v:shell_error != 0 + call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip2 may have failed with <".a:fname.">") + endif +@@ -457,7 +456,7 @@ fun! vimball#Decompress(fname,...) + + elseif expand("%") =~ '.*\.bz2' && executable("bzip2") + " handle *.bz2 with bzip2 -d +- silent exe "!bzip2 -d ".shellescape(a:fname) ++ silent exe "!bzip2 -d ".shellescape(a:fname,1) + if v:shell_error != 0 + call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip2 -d" may have failed with <'.a:fname.">") + endif +@@ -467,7 +466,7 @@ fun! vimball#Decompress(fname,...) + + elseif expand("%") =~ '.*\.bz3' && executable("bunzip3") + " handle *.bz3 with bunzip3 +- silent exe "!bunzip3 ".shellescape(a:fname) ++ silent exe "!bunzip3 ".shellescape(a:fname,1) + if v:shell_error != 0 + call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip3 may have failed with <".a:fname.">") + endif +@@ -477,7 +476,7 @@ fun! vimball#Decompress(fname,...) + + elseif expand("%") =~ '.*\.bz3' && executable("bzip3") + " handle *.bz3 with bzip3 -d +- silent exe "!bzip3 -d ".shellescape(a:fname) ++ silent exe "!bzip3 -d ".shellescape(a:fname,1) + if v:shell_error != 0 + call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip3 -d" may have failed with <'.a:fname.">") + endif +@@ -487,7 +486,7 @@ fun! vimball#Decompress(fname,...) + + elseif expand("%") =~ '.*\.zip' && executable("unzip") + " handle *.zip with unzip +- silent exe "!unzip ".shellescape(a:fname) ++ silent exe "!unzip ".shellescape(a:fname,1) + if v:shell_error != 0 + call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) unzip may have failed with <".a:fname.">") + endif + From ea049e53ec56085b9ede3c7e075cc304425fe695 Mon Sep 17 00:00:00 2001 From: deepin-ci-robot Date: Thu, 11 Jun 2026 22:43:27 +0800 Subject: [PATCH 8/8] fix(vim): ccfilter unbounded strcat/strcpy Fix buffer overflow in ccfilter.c by replacing unbounded strcat()/ strcpy() calls with snprintf() bounded by LINELENGTH to prevent overflow from very long diagnostic output. Upstream: https://github.com/vim/vim/commit/403ba303be232acb726f3f37a98eeb20f5de9694 Generated-By: deepseek-v4-flash Co-Authored-By: hudeng --- debian/changelog | 16 +++++---- ...rity-patch-ccfilter-unbounded-strcat.patch | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 debian/patches/security-patch-ccfilter-unbounded-strcat.patch diff --git a/debian/changelog b/debian/changelog index eac49087..2fffcef2 100644 --- a/debian/changelog +++ b/debian/changelog @@ -7,17 +7,19 @@ vim (2:9.2.0461-1deepin2) unstable; urgency=medium * Fix memory safety issues in spellfile.c - Add recursion limit to read_tree_node(), add length limit check in tree_count_words(), use alloc_clear() in spell_read_tree() - * Fix CVE-2026-52858: possible code execution with python3complete - Disable execution of import/from statements * Fix CVE-2026-52860: possible code execution with python complete - Strip default expressions and annotations from generated source - * Fix out-of-bounds read in update_snapshot() - Bound the loop with - i < VTERM_MAX_CHARS_PER_CELL to prevent reading past array - bounds - * Fix shellescape() calls in getscript/vimball/rust - Pass 1 as - second argument to shellescape() in :! command contexts to - prevent potential command injection + * Fix out-of-bounds read in update_snapshot() + - Bound the loop with i < VTERM_MAX_CHARS_PER_CELL to prevent + reading past array bounds + * Fix shellescape() calls in getscript/vimball/rust + - Pass 1 as second argument to shellescape() in :! command contexts + to prevent potential command injection + * Fix unbounded strcat/strcpy in ccfilter.c + - Replace strcat()/strcpy() with snprintf() bounded by LINELENGTH + to prevent buffer overflow -- deepin-ci-robot Thu, 11 Jun 2026 22:41:37 +0800 diff --git a/debian/patches/security-patch-ccfilter-unbounded-strcat.patch b/debian/patches/security-patch-ccfilter-unbounded-strcat.patch new file mode 100644 index 00000000..60d3d2da --- /dev/null +++ b/debian/patches/security-patch-ccfilter-unbounded-strcat.patch @@ -0,0 +1,36 @@ +Index: vim/runtime/tools/ccfilter.c +=================================================================== +--- vim.orig/runtime/tools/ccfilter.c ++++ vim/runtime/tools/ccfilter.c +@@ -249,14 +249,15 @@ int main( int argc, char *argv[] ) + + stay = (echogets(Line2, echo) != NULL); + while ( stay && (Line2[0] == '|') ) +- { for (p=&Line2[2]; (*p) && (isspace((unsigned char)*p)); p++); +- strcat( Reason, ": " ); +- strcat( Reason, p ); ++ { size_t n; ++ for (p=&Line2[2]; (*p) && (isspace((unsigned char)*p)); p++); ++ n = strlen(Reason); ++ snprintf( Reason + n, LINELENGTH - n, ": %s", p ); + Line2[0] = 0; + stay = (echogets(Line2, echo) != NULL); + } + prefetch = 1; +- strcpy( Line, Line2 ); ++ snprintf( Line, LINELENGTH, "%s", Line2 ); + break; + case COMPILER_IRIX: + Col = 1; +@@ -291,8 +292,8 @@ int main( int argc, char *argv[] ) + prefetch = 0; + } + else +- { strcat( Line, "\n" ); +- strcat( Line, Line2 ); ++ { size_t n = strlen(Line); ++ snprintf( Line + n, LINELENGTH - n, "\n%s", Line2 ); + } + } + } +