On Mon Jun 22, 2026 at 2:27 PM CEST, Hitendra Prajapati via lists.openembedded.org wrote: > Pick patch from [1], [2] & [3] also mentioned at NVD report in [4,5 & 6] > > [1] https://github.com/vim/vim/commit/4b850457e12e1a678dd209f2868154f7553cbf8d > [2] https://github.com/vim/vim/commit/63680c6d3d52477817b49cd1a66e7aabe8a7aa19 > [3] https://github.com/vim/vim/commit/c8c63673bc4253212820626aeeb75999d9a539d2 > [4] https://nvd.nist.gov/vuln/detail/CVE-2026-52858
Hello, CVE-2026-52858 impacts wrynose and a patch must be sent for wrynose before the fix can be merged on scarthgap. I'm sorry but your whole set of vim patches is not acceptable on scarthgap :( Regards, > [5] https://nvd.nist.gov/vuln/detail/CVE-2026-52859 > [6] https://nvd.nist.gov/vuln/detail/CVE-2026-52860 > > Signed-off-by: Hitendra Prajapati <[email protected]> > --- > .../vim/files/CVE-2026-52858.patch | 167 +++++++ > .../vim/files/CVE-2026-52859.patch | 274 +++++++++++ > .../vim/files/CVE-2026-52860.patch | 446 ++++++++++++++++++ > meta/recipes-support/vim/vim.inc | 3 + > 4 files changed, 890 insertions(+) > create mode 100644 meta/recipes-support/vim/files/CVE-2026-52858.patch > create mode 100644 meta/recipes-support/vim/files/CVE-2026-52859.patch > create mode 100644 meta/recipes-support/vim/files/CVE-2026-52860.patch > > diff --git a/meta/recipes-support/vim/files/CVE-2026-52858.patch > b/meta/recipes-support/vim/files/CVE-2026-52858.patch > new file mode 100644 > index 0000000000..c7474f792b > --- /dev/null > +++ b/meta/recipes-support/vim/files/CVE-2026-52858.patch > @@ -0,0 +1,167 @@ > +From 4b850457e12e1a678dd209f2868154f7553cbf8d Mon Sep 17 00:00:00 2001 > +From: Christian Brabandt <[email protected]> > +Date: Fri, 29 May 2026 19:05:53 +0000 > +Subject: [PATCH] patch 9.2.0561: [security]: possible code execution with > + python3complete > + > +Problem: [security]: possible code execution with python3complete > +Solution: Disable execution of import/from statements > + > +Github Security Advisory: > +https://github.com/vim/vim/security/advisories/GHSA-52mc-rq6p-rc7c > + > +Signed-off-by: Christian Brabandt <[email protected]> > + > +Upstream-Status: Backport from > [https://github.com/vim/vim/commit/4b850457e12e1a678dd209f2868154f7553cbf8d] > +CVE: CVE-2026-52858 > +Signed-off-by: Hitendra Prajapati <[email protected]> > +--- > + runtime/autoload/README.txt | 1 + > + runtime/autoload/python3complete.vim | 17 ++++++++++++++--- > + runtime/autoload/pythoncomplete.vim | 17 ++++++++++++++--- > + runtime/doc/filetype.txt | 15 ++++++++++++++- > + 4 files changed, 43 insertions(+), 7 deletions(-) > + > +diff --git a/runtime/autoload/README.txt b/runtime/autoload/README.txt > +index 3b18d3d..b225819 100644 > +--- a/runtime/autoload/README.txt > ++++ b/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) > +diff --git a/runtime/autoload/python3complete.vim > b/runtime/autoload/python3complete.vim > +index ea0a331..aba3412 100644 > +--- a/runtime/autoload/python3complete.vim > ++++ b/runtime/autoload/python3complete.vim > +@@ -14,6 +14,10 @@ > + " i.e. "import url<c-x,c-o>" > + " 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 > + > +diff --git a/runtime/autoload/pythoncomplete.vim > b/runtime/autoload/pythoncomplete.vim > +index aa28bb7..1014776 100644 > +--- a/runtime/autoload/pythoncomplete.vim > ++++ b/runtime/autoload/pythoncomplete.vim > +@@ -12,6 +12,10 @@ > + " i.e. "import url<c-x,c-o>" > + " 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 > + > +diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt > +index 597141d..c8572fe 100644 > +--- a/runtime/doc/filetype.txt > ++++ b/runtime/doc/filetype.txt > +@@ -739,7 +739,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* > + > +-- > +2.34.1 > + > diff --git a/meta/recipes-support/vim/files/CVE-2026-52859.patch > b/meta/recipes-support/vim/files/CVE-2026-52859.patch > new file mode 100644 > index 0000000000..f734d7efe2 > --- /dev/null > +++ b/meta/recipes-support/vim/files/CVE-2026-52859.patch > @@ -0,0 +1,274 @@ > +From 63680c6d3d52477817b49cd1a66e7aabe8a7aa19 Mon Sep 17 00:00:00 2001 > +From: Christian Brabandt <[email protected]> > +Date: Sat, 30 May 2026 16:34:40 +0000 > +Subject: [PATCH] patch 9.2.0565: [security]: out-of-bounds read in > + update_snapshot() > + > +Problem: Out-of-bounds read in update_snapshot() when a terminal cell > + fills all VTERM_MAX_CHARS_PER_CELL slots (a base character > + plus five combining marks): the loop over cell.chars[] has no > + upper bound and libvterm leaves the array unterminated when full, > so > + it reads past the array and appends out-of-bounds values to a > + buffer sized for only VTERM_MAX_CHARS_PER_CELL characters. > +Solution: Bound the loop with i < VTERM_MAX_CHARS_PER_CELL, mirroring > + the loop in handle_pushline() (Christian Brabandt). > + > +Signed-off-by: Christian Brabandt <[email protected]> > + > +Upstream-Status: Backport from > [https://github.com/vim/vim/commit/63680c6d3d52477817b49cd1a66e7aabe8a7aa19] > +CVE: CVE-2026-52859 > +Signed-off-by: Hitendra Prajapati <[email protected]> > +--- > + src/terminal.c | 3 +- > + src/testdir/samples/combining_chars.txt | 200 ++++++++++++++++++++++++ > + src/testdir/test_terminal3.vim | 15 ++ > + 3 files changed, 217 insertions(+), 1 deletion(-) > + create mode 100644 src/testdir/samples/combining_chars.txt > + > +diff --git a/src/terminal.c b/src/terminal.c > +index 78990ac..527f1b9 100644 > +--- a/src/terminal.c > ++++ b/src/terminal.c > +@@ -2080,7 +2080,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); > + } > +diff --git a/src/testdir/samples/combining_chars.txt > b/src/testdir/samples/combining_chars.txt > +new file mode 100644 > +index 0000000..d9a3c17 > +--- /dev/null > ++++ b/src/testdir/samples/combining_chars.txt > +@@ -0,0 +1,200 @@ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ > ++á́́́́ጁ > ++á́́́́ጁ > ++á́́́́ጁ > ++á́́́́ጁ > ++á́́́́ጁ > ++á́́́́ጁ > ++á́́́́ጁ > ++á́́́́ጁ > ++á́́́́ጁ > ++á́́́́ጁ > +diff --git a/src/testdir/test_terminal3.vim b/src/testdir/test_terminal3.vim > +index cb1946f..c02801e 100644 > +--- a/src/testdir/test_terminal3.vim > ++++ b/src/testdir/test_terminal3.vim > +@@ -1051,4 +1051,19 @@ func Test_terminal_max_combining_chars() > + exe buf . "bwipe!" > + 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 > +-- > +2.34.1 > + > diff --git a/meta/recipes-support/vim/files/CVE-2026-52860.patch > b/meta/recipes-support/vim/files/CVE-2026-52860.patch > new file mode 100644 > index 0000000000..2f4ffdeacf > --- /dev/null > +++ b/meta/recipes-support/vim/files/CVE-2026-52860.patch > @@ -0,0 +1,446 @@ > +From c8c63673bc4253212820626aeeb75999d9a539d2 Mon Sep 17 00:00:00 2001 > +From: Christian Brabandt <[email protected]> > +Date: Thu, 4 Jun 2026 21:06:09 +0000 > +Subject: [PATCH] patch 9.2.0597: [security]: possible code execution with > + python complete > + > +Problem: [security]: another possible code execution with python complete > + (David Carliez) > +Solution: Strip default expressions and annotations from generated > + source for pythoncomplete and python3complete. > + > +Github Security Advisory: > +https://github.com/vim/vim/security/advisories/GHSA-65p9-mwwx-7468 > + > +Signed-off-by: Christian Brabandt <[email protected]> > + > +Upstream-Status: Backport from > [https://github.com/vim/vim/commit/c8c63673bc4253212820626aeeb75999d9a539d2] > +CVE: CVE-2026-52860 > +Signed-off-by: Hitendra Prajapati <[email protected]> > +--- > + runtime/autoload/python3complete.vim | 43 +++- > + runtime/autoload/pythoncomplete.vim | 43 +++- > + src/testdir/Make_all.mak | 2 + > + src/testdir/test_plugin_python3complete.vim | 224 ++++++++++++++++++++ > + 4 files changed, 304 insertions(+), 8 deletions(-) > + create mode 100644 src/testdir/test_plugin_python3complete.vim > + > +diff --git a/runtime/autoload/python3complete.vim > b/runtime/autoload/python3complete.vim > +index aba3412..a031424 100644 > +--- a/runtime/autoload/python3complete.vim > ++++ b/runtime/autoload/python3complete.vim > +@@ -1,8 +1,8 @@ > + "python3complete.vim - Omni Completion for python > + " Maintainer: <vacancy> > + " Previous Maintainer: Aaron Griffin <[email protected]> > +-" 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) > +@@ -347,7 +370,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: > +@@ -364,8 +393,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 > +diff --git a/runtime/autoload/pythoncomplete.vim > b/runtime/autoload/pythoncomplete.vim > +index 1014776..39b1efd 100644 > +--- a/runtime/autoload/pythoncomplete.vim > ++++ b/runtime/autoload/pythoncomplete.vim > +@@ -1,8 +1,8 @@ > + "pythoncomplete.vim - Omni Completion for python > + " Maintainer: <vacancy> > + " Previous Maintainer: Aaron Griffin <[email protected]> > +-" 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) > +@@ -362,7 +385,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: > +@@ -379,8 +408,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 > +diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak > +index 0d4aeb0..87545b7 100644 > +--- a/src/testdir/Make_all.mak > ++++ b/src/testdir/Make_all.mak > +@@ -247,6 +247,7 @@ NEW_TESTS = \ > + test_plugin_helptoc \ > + test_plugin_man \ > + test_plugin_matchparen \ > ++ test_plugin_python3complete \ > + test_plugin_tar \ > + test_plugin_termdebug \ > + test_plugin_tohtml \ > +@@ -520,6 +521,7 @@ NEW_TESTS_RES = \ > + test_plugin_helptoc.res \ > + test_plugin_man.res \ > + test_plugin_matchparen.res \ > ++ test_plugin_python3complete.res \ > + test_plugin_tar.res \ > + test_plugin_termdebug.res \ > + test_plugin_tohtml.res \ > +diff --git a/src/testdir/test_plugin_python3complete.vim > b/src/testdir/test_plugin_python3complete.vim > +new file mode 100644 > +index 0000000..e2b0c66 > +--- /dev/null > ++++ b/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 > +-- > +2.34.1 > + > diff --git a/meta/recipes-support/vim/vim.inc > b/meta/recipes-support/vim/vim.inc > index 95d00a09bd..4311c36f5b 100644 > --- a/meta/recipes-support/vim/vim.inc > +++ b/meta/recipes-support/vim/vim.inc > @@ -33,6 +33,9 @@ SRC_URI = > "git://github.com/vim/vim.git;branch=master;protocol=https \ > file://CVE-2026-45130.patch \ > file://CVE-2026-46483.patch \ > file://CVE-2026-28420.patch \ > + file://CVE-2026-52858.patch \ > + file://CVE-2026-52859.patch \ > + file://CVE-2026-52860.patch \ > " > > PV .= ".1683" -- Yoann Congal Smile ECS
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#239667): https://lists.openembedded.org/g/openembedded-core/message/239667 Mute This Topic: https://lists.openembedded.org/mt/119922659/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
