Headline
CVE-2023-45805: Merge pull request from GHSA-j44v-mmf2-xvm9 · pdm-project/pdm@6853e26
pdm is a Python package and dependency manager supporting the latest PEP standards. It’s possible to craft a malicious pdm.lock
file that could allow e.g. an insider or a malicious open source project to appear to depend on a trusted PyPI project, but actually install another project. A project foo
can be targeted by creating the project foo-2
and uploading the file foo-2-2.tar.gz
to pypi.org. PyPI will see this as project foo-2
version 2
, while PDM will see this as project foo
version 2-2
. The version must only be parseable as a version
and the filename must be a prefix of the project name, but it’s not verified to match the version being installed. Version 2-2
is also not a valid normalized version per PEP 440. Matching the project name exactly (not just prefix) would fix the issue. When installing dependencies with PDM, what’s actually installed could differ from what’s listed in pyproject.toml
(including arbitrary code execution on install). It could also be used for downgrade attacks by only changing the version. This issue has been addressed in commit 6853e2642df
which is included in release version 2.9.4
. Users are advised to upgrade. There are no known workarounds for this vulnerability.
Expand Up
@@ -10,7 +10,7 @@
from typing import TYPE_CHECKING, Generator, TypeVar, cast
from pdm import termui
from pdm.exceptions import CandidateInfoNotFound, CandidateNotFound, PackageWarning
from pdm.exceptions import CandidateInfoNotFound, CandidateNotFound, PackageWarning, PdmException
from pdm.models.candidates import Candidate, make_candidate
from pdm.models.requirements import (
Requirement,
Expand Down Expand Up
@@ -486,6 +486,7 @@ def all_candidates(self) -> dict[str, Candidate]:
def _read_lockfile(self, lockfile: Mapping[str, Any]) -> None:
root = self.environment.project.root
static_urls = self.environment.project.lockfile.static_urls
with cd(root):
for package in lockfile.get("package", []):
version = package.get(“version”)
Expand All
@@ -500,6 +501,10 @@ def _read_lockfile(self, lockfile: Mapping[str, Any]) -> None:
req.url = path_to_url(posixpath.join(root, req.path)) # type: ignore[attr-defined]
can = make_candidate(req, name=package_name, version=version)
can.hashes = package.get("files", [])
if not static_urls and any(“url” in f for f in can.hashes):
raise PdmException(
“Static URLs are not allowed in lockfile unless enabled by `pdm lock --static-urls`.”
)
can_id = self._identify_candidate(can)
self.packages[can_id] = can
candidate_info: CandidateInfo = (
Expand Down
Related news
### Summary It's possible to craft a malicious `pdm.lock` file that could allow e.g. an insider or a malicious open source project to appear to depend on a trusted PyPI project, but actually install another project. ### Details Project `foo` can be targeted by creating the project `foo-2` and uploading the file `foo-2-2.tar.gz` to pypi.org. PyPI will see this as project `foo-2` version `2`, while PDM will see this as project `foo` version `2-2`. The version must only be [parseable as a version](https://github.com/frostming/unearth/blob/eca170d9370ac5032f2e497ee9b1b63823d3fe0f/src/unearth/evaluator.py#L215-L229) (and the filename must be a prefix of the project name), but it's [not verified to match the version being installed](https://github.com/pdm-project/pdm/blob/45d1dfa47d4900c14a31b9bb761e4c46eb5c9442/src/pdm/models/candidates.py#L98-L99). (Version `2-2` is also not a valid [normalized version per PEP 440](https://peps.python.org/pep-0440/#post-release-spelling).) Matching the p...