From: Yogita Urade <yurade@cisco.com>
To: openembedded-core@lists.openembedded.org
Subject: [OE-core][wrynose][v2 PATCH 02/27] python3-git: fix CVE-2026-67323
Date: Fri, 6 Nov 2026 06:43:06 -0800 [thread overview]
Message-ID: <20261106144331.3354811-3-yurade@cisco.com> (raw)
In-Reply-To: <20261106144331.3354811-1-yurade@cisco.com>
This patch applies the upstream fix as referenced in [2],
using the commit shown in [1].
[1] https://github.com/gitpython-developers/GitPython/commit/701ce32fe5ba8cb622c0e0342a376a6beb47d738
[2] https://security-tracker.debian.org/tracker/CVE-2026-67323
Signed-off-by: Yogita Urade <yurade@cisco.com>
---
Changes in v2:
- Changed the advisory reference from NVD to Debian Security Tracker.
Changed the ls_remote documentation to describe --upload-pack
without --exec.
---
.../python/python3-git/CVE-2026-67323.patch | 356 ++++++++++++++++++
.../python/python3-git_3.1.43.bb | 1 +
2 files changed, 357 insertions(+)
create mode 100644 meta/recipes-devtools/python/python3-git/CVE-2026-67323.patch
diff --git a/meta/recipes-devtools/python/python3-git/CVE-2026-67323.patch b/meta/recipes-devtools/python/python3-git/CVE-2026-67323.patch
new file mode 100644
index 0000000000..3901f6244b
--- /dev/null
+++ b/meta/recipes-devtools/python/python3-git/CVE-2026-67323.patch
@@ -0,0 +1,356 @@
+From 80bf84d6f24adcc181b78d71fa94544924a85b2c Mon Sep 17 00:00:00 2001
+From: "GPT 5.6" <codex@openai.com>
+Date: Sun, 12 Jul 2026 15:06:42 +0200
+Subject: [PATCH] fix: Guard unsafe git command options (GHSA-956x-8gvw-wg5v)
+
+Reject command options that can execute arbitrary helpers or write files when passed through archive, revision traversal, blame, clone, fetch, pull, push, and ls-remote APIs. Validate positional option tokens and transformed keyword options consistently, including Git-supported long-option abbreviations, attached short-option values, and falsey stringifiable remotes. Preserve explicit allow_unsafe_options and allow_unsafe_protocols escape hatches.
+
+Add regression coverage for each guarded API, option spelling and abbreviation forms, inert keyword values, mixed positional and keyword candidates, tuple blame options, stringifiable remotes, and unsafe commands that must not create marker files.
+
+CVE: CVE-2026-67323
+Upstream-Status: Backport [https://github.com/gitpython-developers/GitPython/commit/701ce32fe5ba8cb622c0e0342a376a6beb47d738]
+
+Backport Changes:
+- Omitted test/test_clone.py, test/test_commit.py, test/test_git.py,
+ test/test_remote.py, test/test_repo.py because the current PyPI
+ source archive does not include the test suite and the python3-git
+ recipe does not run it.
+
+Reviewed-by: Sebastian Thiel <sebastian.thiel@icloud.com>
+(cherry picked from commit 701ce32fe5ba8cb622c0e0342a376a6beb47d738)
+Signed-off-by: Yogita Urade <yurade@cisco.com>
+---
+ git/cmd.py | 37 +++++++++++++++++++++-
+ git/objects/commit.py | 12 +++++++
+ git/remote.py | 15 ++++++--
+ git/repo/base.py | 80 +++++++++++++++++++++++++++++++++++++++----
+ 4 files changed, 133 insertions(+), 11 deletions(-)
+
+diff --git a/git/cmd.py b/git/cmd.py
+index bb8f93a8..87866be2 100644
+--- a/git/cmd.py
++++ b/git/cmd.py
+@@ -414,6 +414,11 @@ class Git:
+ "_version_info_token",
+ )
+
++ unsafe_git_ls_remote_options = [
++ # This option allows arbitrary command execution in git-ls-remote.
++ "--upload-pack",
++ ]
++
+ re_unsafe_protocol = re.compile(r"(.+)::.+")
+
+ def __getstate__(self) -> Dict[str, Any]:
+@@ -742,6 +747,20 @@ Git.check_unsafe_options
+ f"{unsafe_option} is not allowed, use `allow_unsafe_options=True` to allow it."
+ )
+
++ @classmethod
++ def _option_candidates(cls, args: Sequence[Any] = (), kwargs: Optional[Mapping[str, Any]] = None) -> List[str]:
++ """Collect possible option spellings before command-line transformation."""
++ options = [
++ option for option in cls._unpack_args([arg for arg in args if arg is not None]) if option.startswith("-")
++ ]
++ if kwargs:
++ for key, value in kwargs.items():
++ values = value if isinstance(value, (list, tuple)) else (value,)
++ if any(value is True or (value is not False and value is not None) for value in values):
++ key = str(key)
++ options.append(f"-{key}" if len(key) == 1 else f"--{dashify(key)}")
++ return options
++
+ class AutoInterrupt:
+ """Process wrapper that terminates the wrapped process on finalization.
+
+@@ -999,6 +1019,22 @@ Git.__getattr__
+ return super().__getattribute__(name)
+ return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
+
++ def ls_remote(
++ self,
++ *args: Any,
++ allow_unsafe_options: bool = False,
++ **kwargs: Any,
++ ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]:
++ """List references in a remote repository.
++
++ :param allow_unsafe_options:
++ Allow unsafe options, like ``--upload-pack``.
++ """
++ if not allow_unsafe_options:
++ candidate_options = self._option_candidates(args, kwargs)
++ Git.check_unsafe_options(options=candidate_options, unsafe_options=self.unsafe_git_ls_remote_options)
++ return self._call_process("ls_remote", *args, **kwargs)
++
+ def set_persistent_git_options(self, **kwargs: Any) -> None:
+ """Specify command line options to the git executable for subsequent
+ subcommand calls.
+@@ -1488,7 +1524,7 @@ Git._unpack_args
+ return args
+
+ @classmethod
+- def _unpack_args(cls, arg_list: Sequence[str]) -> List[str]:
++ def _unpack_args(cls, arg_list: Sequence[Any]) -> List[str]:
+ outlist = []
+ if isinstance(arg_list, (list, tuple)):
+ for arg in arg_list:
+diff --git a/git/objects/commit.py b/git/objects/commit.py
+index d957c905..6e2ff759 100644
+--- a/git/objects/commit.py
++++ b/git/objects/commit.py
+@@ -85,6 +85,12 @@ class Commit:
+
+ # INVARIANTS
+ default_encoding = "UTF-8"
++ # Options to :manpage:`git-rev-list(1)` that can overwrite files.
++ unsafe_git_rev_options = [
++ "--output",
++ "-o",
++ ]
++
+
+ type: Literal["commit"] = "commit"
+
+@@ -302,6 +308,7 @@ Commit.iter_items
+ repo: "Repo",
+ rev: Union[str, "Commit", "SymbolicReference"],
+ paths: Union[PathLike, Sequence[PathLike]] = "",
++ allow_unsafe_options: bool = False,
+ **kwargs: Any,
+ ) -> Iterator["Commit"]:
+ R"""Find all commits matching the given criteria.
+@@ -329,6 +336,11 @@ Commit.iter_items
+ if "pretty" in kwargs:
+ raise ValueError("--pretty cannot be used as parsing expects single sha's only")
+ # END handle pretty
++ if not allow_unsafe_options:
++ Git.check_unsafe_options(
++ options=Git._option_candidates([rev], kwargs), unsafe_options=cls.unsafe_git_rev_options
++ )
++
+
+ # Use -- in all cases, to prevent possibility of ambiguous arguments.
+ # See https://github.com/gitpython-developers/GitPython/issues/264.
+diff --git a/git/remote.py b/git/remote.py
+index f2ecd0f3..cafa1f84 100644
+--- a/git/remote.py
++++ b/git/remote.py
+@@ -1061,7 +1061,10 @@ Remote.fetch
+ Git.check_unsafe_protocols(ref)
+
+ if not allow_unsafe_options:
+- Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_fetch_options)
++ Git.check_unsafe_options(
++ options=Git._option_candidates([], kwargs),
++ unsafe_options=self.unsafe_git_fetch_options,
++ )
+
+ proc = self.repo.git.fetch(
+ "--", self, *args, as_process=True, with_stdout=False, universal_newlines=False, v=verbose, **kwargs
+@@ -1115,7 +1118,10 @@ Remote.pull
+ Git.check_unsafe_protocols(ref)
+
+ if not allow_unsafe_options:
+- Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_pull_options)
++ Git.check_unsafe_options(
++ options=Git._option_candidates([], kwargs),
++ unsafe_options=self.unsafe_git_pull_options,
++ )
+
+ proc = self.repo.git.pull(
+ "--", self, refspec, with_stdout=False, as_process=True, universal_newlines=False, v=True, **kwargs
+@@ -1188,7 +1194,10 @@ Remote.push
+ Git.check_unsafe_protocols(ref)
+
+ if not allow_unsafe_options:
+- Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_push_options)
++ Git.check_unsafe_options(
++ options=Git._option_candidates([], kwargs),
++ unsafe_options=self.unsafe_git_push_options,
++ )
+
+ proc = self.repo.git.push(
+ "--",
+diff --git a/git/repo/base.py b/git/repo/base.py
+index 8059fceb..e9e7aa66 100644
+--- a/git/repo/base.py
++++ b/git/repo/base.py
+@@ -159,6 +159,20 @@ class Repo:
+ ``protocol.allow`` and ``core.gitProxy`` to execute arbitrary commands:
+ https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---configltkeygtltvaluegt
+ """
++ unsafe_git_archive_options = [
++ # Allows arbitrary command execution through the remote git-upload-archive command.
++ "--exec",
++ # Writes output to a caller-controlled filesystem path.
++ "--output",
++ "-o",
++ ]
++
++ unsafe_git_revision_options = [
++ # This option allows output to be written to arbitrary files before revision parsing.
++ "--output",
++ "-o",
++ ]
++
+
+ # Invariants
+ config_level: ConfigLevels_Tup = ("system", "user", "global", "repository")
+@@ -743,6 +757,7 @@ Repo.iter_commits
+ self,
+ rev: Union[str, Commit, "SymbolicReference", None] = None,
+ paths: Union[PathLike, Sequence[PathLike]] = "",
++ allow_unsafe_options: bool = False,
+ **kwargs: Any,
+ ) -> Iterator[Commit]:
+ """An iterator of :class:`~git.objects.commit.Commit` objects representing the
+@@ -759,6 +774,9 @@ Repo.iter_commits
+ :param kwargs:
+ Arguments to be passed to :manpage:`git-rev-list(1)`.
+ Common ones are ``max_count`` and ``skip``.
++ :param allow_unsafe_options:
++ Allow unsafe options in the revision argument, like ``--output``.
++
+
+ :note:
+ To receive only commits between two named revisions, use the
+@@ -770,7 +788,18 @@ Repo.iter_commits
+ if rev is None:
+ rev = self.head.commit
+
+- return Commit.iter_items(self, rev, paths, **kwargs)
++ if not allow_unsafe_options:
++ Git.check_unsafe_options(
++ options=Git._option_candidates([rev], kwargs), unsafe_options=self.unsafe_git_revision_options
++ )
++
++ return Commit.iter_items(
++ self,
++ rev,
++ paths,
++ allow_unsafe_options=allow_unsafe_options,
++ **kwargs,
++ )
+
+ def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Commit]:
+ R"""Find the closest common ancestor for the given revision
+@@ -1035,7 +1064,9 @@ Repo.blame_incremental
+ # reveal_type(self.head.reference) # => Reference
+ return self.head.reference
+
+- def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) -> Iterator["BlameEntry"]:
++ def blame_incremental(
++ self, rev: str | HEAD | None, file: str, allow_unsafe_options: bool = False, **kwargs: Any
++ ) -> Iterator["BlameEntry"]:
+ """Iterator for blame information for the given file at the given revision.
+
+ Unlike :meth:`blame`, this does not return the actual file's contents, only a
+@@ -1046,6 +1077,9 @@ Repo.blame_incremental
+ uncommitted changes. Otherwise, anything successfully parsed by
+ :manpage:`git-rev-parse(1)` is a valid option.
+
++ :param allow_unsafe_options:
++ Allow unsafe options in revision argument, like ``--output``.
++
+ :return:
+ Lazy iterator of :class:`BlameEntry` tuples, where the commit indicates the
+ commit to blame for the line, and range indicates a span of line numbers in
+@@ -1055,6 +1089,10 @@ Repo.blame_incremental
+ a continuous range spanning all line numbers in the file.
+ """
+
++ if not allow_unsafe_options:
++ Git.check_unsafe_options(
++ options=Git._option_candidates([rev], kwargs), unsafe_options=self.unsafe_git_revision_options
++ )
+ data: bytes = self.git.blame(rev, "--", file, p=True, incremental=True, stdout_as_string=False, **kwargs)
+ commits: Dict[bytes, Commit] = {}
+
+@@ -1132,7 +1170,8 @@ Repo.blame
+ rev: Union[str, HEAD, None],
+ file: str,
+ incremental: bool = False,
+- rev_opts: Optional[List[str]] = None,
++ rev_opts: Optional[Sequence[str]] = None,
++ allow_unsafe_options: bool = False,
+ **kwargs: Any,
+ ) -> List[List[Commit | List[str | bytes] | None]] | Iterator[BlameEntry] | None:
+ """The blame information for the given file at the given revision.
+@@ -1141,6 +1180,9 @@ Repo.blame
+ Revision specifier. If ``None``, the blame will include all the latest
+ uncommitted changes. Otherwise, anything successfully parsed by
+ :manpage:`git-rev-parse(1)` is a valid option.
++ :param allow_unsafe_options:
++ Allow unsafe options in revision argument, like ``--output``.
++
+
+ :return:
+ list: [git.Commit, list: [<line>]]
+@@ -1151,9 +1193,14 @@ Repo.blame
+ appearance.
+ """
+ if incremental:
+- return self.blame_incremental(rev, file, **kwargs)
+- rev_opts = rev_opts or []
+- data: bytes = self.git.blame(rev, *rev_opts, "--", file, p=True, stdout_as_string=False, **kwargs)
++ return self.blame_incremental(rev, file, allow_unsafe_options=allow_unsafe_options, **kwargs)
++ rev_opts_list = list(rev_opts or [])
++ if not allow_unsafe_options:
++ Git.check_unsafe_options(
++ options=Git._option_candidates([rev, rev_opts_list], kwargs),
++ unsafe_options=self.unsafe_git_revision_options,
++ )
++ data: bytes = self.git.blame(rev, *rev_opts_list, "--", file, p=True, stdout_as_string=False, **kwargs)
+ commits: Dict[str, Commit] = {}
+ blames: List[List[Commit | List[str | bytes] | None]] = []
+
+@@ -1364,7 +1411,10 @@ Repo.clone_from
+ if not allow_unsafe_protocols:
+ Git.check_unsafe_protocols(str(url))
+ if not allow_unsafe_options:
+- Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=cls.unsafe_git_clone_options)
++ Git.check_unsafe_options(
++ options=Git._option_candidates([], kwargs),
++ unsafe_options=cls.unsafe_git_clone_options,
++ )
+ if not allow_unsafe_options and multi:
+ Git.check_unsafe_options(options=multi, unsafe_options=cls.unsafe_git_clone_options)
+
+@@ -1539,6 +1589,8 @@ Repo.archive
+ ostream: Union[TextIO, BinaryIO],
+ treeish: Optional[str] = None,
+ prefix: Optional[str] = None,
++ allow_unsafe_options: bool = False,
++ allow_unsafe_protocols: bool = False,
+ **kwargs: Any,
+ ) -> Repo:
+ """Archive the tree at the given revision.
+@@ -1560,6 +1612,12 @@ Repo.archive
+ * You may specify the special ``path`` keyword, which may either be a
+ repository-relative path to a directory or file to place into the archive,
+ or a list or tuple of multiple paths.
++ :param allow_unsafe_options:
++ Allow unsafe options, like ``--exec`` or ``--output``.
++
++ :param allow_unsafe_protocols:
++ Allow unsafe protocols to be used in ``remote``, like ``ext``.
++
+
+ :raise git.exc.GitCommandError:
+ If something went wrong.
+@@ -1571,6 +1629,14 @@ Repo.archive
+ treeish = self.head.commit
+ if prefix and "prefix" not in kwargs:
+ kwargs["prefix"] = prefix
++ remote = kwargs.get("remote")
++ if not allow_unsafe_protocols and remote is not None:
++ Git.check_unsafe_protocols(str(remote))
++ if not allow_unsafe_options:
++ Git.check_unsafe_options(
++ options=Git._option_candidates([], kwargs),
++ unsafe_options=self.unsafe_git_archive_options,
++ )
+ kwargs["output_stream"] = ostream
+ path = kwargs.pop("path", [])
+ path = cast(Union[PathLike, List[PathLike], Tuple[PathLike, ...]], path)
diff --git a/meta/recipes-devtools/python/python3-git_3.1.43.bb b/meta/recipes-devtools/python/python3-git_3.1.43.bb
index 474206d4ed..72eb073196 100644
--- a/meta/recipes-devtools/python/python3-git_3.1.43.bb
+++ b/meta/recipes-devtools/python/python3-git_3.1.43.bb
@@ -22,6 +22,7 @@ SRC_URI += "file://CVE-2026-42284.patch \
file://CVE-2026-44244_p1.patch \
file://CVE-2026-44244_p2.patch \
file://CVE-2026-67326.patch \
+ file://CVE-2026-67323.patch \
"
SRC_URI[sha256sum] = "35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"
--
2.53.0
next prev parent reply other threads:[~2026-11-06 14:43 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 14:43 [OE-core][wrynose][v2 PATCH 00/27] python3-git: Security fixes Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 01/27] python3-git: fix CVE-2026-67326 Yogita Urade
2026-11-06 14:43 ` Yogita Urade [this message]
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 03/27] python3-git: Fix CVE-2026-67325 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 04/27] python3-git: fix CVE-2026-67324 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 05/27] python3-git: fix CVE-2026-67322 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 06/27] python3-git: fix CVE-2026-69097 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 07/27] python3-git: fix CVE-2026-73625 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 08/27] python3-git: fix CVE-2026-73623 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 09/27] python3-git: fix CVE-2026-73624 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 10/27] python3-git: fix CVE-2026-73622 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 11/27] python3-git: fix CVE-2026-73621 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 12/27] python3-git: fix CVE-2026-73620 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 13/27] python3-git: fix CVE-2026-73619 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 14/27] python3-git: fix CVE-2026-76221 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 15/27] python3-git: fix CVE-2026-76220 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 16/27] python3-git: fix CVE-2026-76219 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 17/27] python3-git: fix CVE-2026-76218 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 18/27] python3-git: fix CVE-2026-76217 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 19/27] python3-git: fix CVE-2026-76222 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 20/27] python3-git: fix CVE-2026-78678 CVE-2026-78679 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 21/27] python3-git: fix CVE-2026-78677 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 22/27] python3-git: fix CVE-2026-78676 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 23/27] python3-git: fix CVE-2026-78675 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 24/27] python3-git: fix CVE-2026-87817 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 25/27] python3-git: fix CVE-2026-87818 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 26/27] python3-git: Fix CVE-2026-87819 Yogita Urade
2026-11-06 14:43 ` [OE-core][wrynose][v2 PATCH 27/27] python3-git: fix CVE-2026-100689 Yogita Urade
2026-11-14 0:16 ` [OE-core][wrynose][v2 PATCH 00/27] python3-git: Security fixes Yoann Congal
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261106144331.3354811-3-yurade@cisco.com \
--to=yurade@cisco.com \
--cc=openembedded-core@lists.openembedded.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox