From 8ac47c19a22f2f55bdcff753d3c8341ddc0be42a Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Tue, 29 Sep 2026 18:27:16 -0700 Subject: [PATCH] =?UTF-8?q?Fix=20xz=20download=20+=20discovery=20URLs=20?= =?UTF-8?q?=E2=80=94=20tukaani.org=20non-authoritative=20post-CVE-2024-309?= =?UTF-8?q?4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After the March 2024 xz-utils supply-chain backdoor (CVE-2024-3094), the xz project moved release hosting and authoritative release metadata from Lasse Collin's tukaani.org infrastructure to the community-maintained github.com/tukaani-project/xz repository. Tarball paths under `tukaani.org/xz/` now 404 (the listing page at `https://tukaani.org/xz/` remains up but its anchor hrefs point directly at GitHub releases), which broke the Auto-Update Versions workflow when it tried to fetch xz-5.8.4: Downloading http://tukaani.org/xz/xz-5.8.4.tar.gz... HTTP Error 404: Not Found relenv.common.RelenvException: Error fetching url ... Confirmed via curl: http://tukaani.org/xz/xz-5.8.4.tar.gz -> 301 -> https https://tukaani.org/xz/xz-5.8.4.tar.gz -> 404 https://github.com/tukaani-project/xz/releases/ download/v5.8.4/xz-5.8.4.tar.gz -> 302 -> 200 This commit makes two related changes: 1. Switch every hardcoded xz download URL to the canonical GitHub-releases template `https://github.com/tukaani-project/xz/releases/download/ v{version}/xz-{version}.tar.gz`. 2. Switch xz version *discovery* off tukaani.org too. Even though `https://tukaani.org/xz/` still returns 200 today, we shouldn't rely on that host as authoritative post-CVE. `detect_xz_versions()` now queries the tukaani-project Releases API (`https://api.github.com/repos/tukaani-project/xz/releases`), which is what tukaani.org itself now links to; drafts and prereleases (e.g. `v5.7.2beta`) are filtered. The CPE identifier `cpe:2.3:a:tukaani:xz:...` in build/windows.py is a vulnerability-DB name, not a URL, and is left alone. Files touched: - relenv/python-versions.json (5.8.2, 5.8.3 URL templates) - relenv/pyversions.py (download URL for Auto-Update Versions, stored template for new releases, detect_xz_versions -> GitHub API) - relenv/build/linux.py (fallback URL when JSON lookup fails) - relenv/build/darwin.py (fallback URL when JSON lookup fails) - tests/test_pyversions_runtime.py (mock updated to GitHub API JSON, asserts endpoint + prerelease/draft filtering) Impact: `relenv versions --check-deps` now reports `XZ 5.8.3 -> 5.8.4 (update available)` correctly, and `relenv versions --update-deps` can actually fetch the tarball. No version pin is bumped by this commit — that is the downstream job of the Auto-Update Versions workflow once this fix lands. --- relenv/build/darwin.py | 2 +- relenv/build/linux.py | 2 +- relenv/python-versions.json | 4 ++-- relenv/pyversions.py | 40 ++++++++++++++++++++++++-------- tests/test_pyversions_runtime.py | 29 ++++++++++++++++------- 5 files changed, 54 insertions(+), 23 deletions(-) diff --git a/relenv/build/darwin.py b/relenv/build/darwin.py index ab6c8a4b..c35fb80f 100644 --- a/relenv/build/darwin.py +++ b/relenv/build/darwin.py @@ -197,7 +197,7 @@ def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> N xz_checksum = xz_info["sha256"] else: xz_version = "5.8.1" - xz_url = "http://tukaani.org/xz/xz-{version}.tar.gz" + xz_url = "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz" xz_checksum = "ed4d5589c4cfe84e1697bd02a9954b76af336931" build.add( diff --git a/relenv/build/linux.py b/relenv/build/linux.py index 35f2206f..62b10c9d 100644 --- a/relenv/build/linux.py +++ b/relenv/build/linux.py @@ -689,7 +689,7 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: else: # Fallback to hardcoded values xz_version = "5.8.1" - xz_url = "http://tukaani.org/xz/xz-{version}.tar.gz" + xz_url = "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz" xz_checksum = "ed4d5589c4cfe84e1697bd02a9954b76af336931" build.add( diff --git a/relenv/python-versions.json b/relenv/python-versions.json index 1af410f4..d84959b1 100644 --- a/relenv/python-versions.json +++ b/relenv/python-versions.json @@ -367,7 +367,7 @@ }, "xz": { "5.8.2": { - "url": "http://tukaani.org/xz/xz-{version}.tar.gz", + "url": "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz", "sha256": "ce09c50a5962786b83e5da389c90dd2c15ecd0980a258dd01f70f9e7ce58a8f1", "platforms": [ "linux", @@ -376,7 +376,7 @@ ] }, "5.8.3": { - "url": "http://tukaani.org/xz/xz-{version}.tar.gz", + "url": "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz", "sha256": "3d3a1b973af218114f4f889bbaa2f4c037deaae0c8e815eec381c3d546b974a0", "platforms": [ "linux", diff --git a/relenv/pyversions.py b/relenv/pyversions.py index c9f210ab..a9844cc0 100644 --- a/relenv/pyversions.py +++ b/relenv/pyversions.py @@ -331,16 +331,36 @@ def detect_sqlite_versions() -> list[tuple[str, str]]: def detect_xz_versions() -> list[str]: """ - Detect available XZ versions from tukaani.org. + Detect available XZ versions from the tukaani-project GitHub releases API. + + Sourcing xz version metadata from tukaani.org is discouraged after + CVE-2024-3094 (the March 2024 xz-utils supply-chain backdoor). Both + release tarball hosting and the authoritative release listing moved + to https://github.com/tukaani-project/xz, so we consult that project's + Releases API and skip drafts / prereleases. """ - url = "https://tukaani.org/xz/" + url = "https://api.github.com/repos/tukaani-project/xz/releases" content = fetch_url_content(url) - # Find xz-X.Y.Z.tar.gz - pattern = r"xz-(\d+\.\d+\.\d+)\.tar\.gz" - matches = re.findall(pattern, content) - # Deduplicate and sort - versions = sorted(set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True) - return versions + try: + releases = json.loads(content) + except json.JSONDecodeError: + return [] + versions: list[str] = [] + for release in releases: + if not isinstance(release, dict): + continue + if release.get("draft") or release.get("prerelease"): + continue + tag = release.get("tag_name", "") + if not isinstance(tag, str): + continue + # Tags are like "v5.8.4"; accept plain "5.8.4" too for robustness. + if tag.startswith("v"): + tag = tag[1:] + if not re.fullmatch(r"\d+\.\d+\.\d+", tag): + continue + versions.append(tag) + return sorted(set(versions), key=lambda v: [int(x) for x in v.split(".")], reverse=True) def detect_libffi_versions() -> list[str]: @@ -691,13 +711,13 @@ def update_dependency_versions(path: pathlib.Path, deps_to_update: list[str] | N if "xz" not in dependencies: dependencies["xz"] = {} if latest not in dependencies["xz"]: - url = f"http://tukaani.org/xz/xz-{latest}.tar.gz" + url = f"https://github.com/tukaani-project/xz/releases/download/v{latest}/xz-{latest}.tar.gz" print(f"Downloading {url}...") download_path = download_url(url, cwd) checksum = sha256_digest(download_path) print(f"SHA-256: {checksum}") dependencies["xz"][latest] = { - "url": "http://tukaani.org/xz/xz-{version}.tar.gz", + "url": "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz", "sha256": checksum, "platforms": ["linux", "darwin", "win32"], } diff --git a/tests/test_pyversions_runtime.py b/tests/test_pyversions_runtime.py index 52e924e7..50eebae8 100644 --- a/tests/test_pyversions_runtime.py +++ b/tests/test_pyversions_runtime.py @@ -170,24 +170,35 @@ def fake_fetch(url: str) -> str: def test_detect_xz_versions(monkeypatch: pytest.MonkeyPatch) -> None: - """Test XZ version detection from tukaani.org.""" - mock_html = """ - - xz-5.8.1.tar.gz - xz-5.8.0.tar.gz - xz-5.6.3.tar.gz - - """ + """Test XZ version detection from the tukaani-project GitHub releases API.""" + import json + + mock_releases = json.dumps( + [ + {"tag_name": "v5.8.1", "draft": False, "prerelease": False}, + {"tag_name": "v5.7.2beta", "draft": False, "prerelease": True}, + {"tag_name": "v5.8.0", "draft": False, "prerelease": False}, + {"tag_name": "v5.6.3", "draft": False, "prerelease": False}, + {"tag_name": "v5.9.0-draft", "draft": True, "prerelease": False}, + ] + ) + + captured_url: dict[str, str] = {} def fake_fetch(url: str) -> str: - return mock_html + captured_url["url"] = url + return mock_releases monkeypatch.setattr(pyversions, "fetch_url_content", fake_fetch) versions = pyversions.detect_xz_versions() + assert captured_url["url"] == "https://api.github.com/repos/tukaani-project/xz/releases" assert isinstance(versions, list) assert "5.8.1" in versions assert "5.8.0" in versions assert "5.6.3" in versions + # Draft and prerelease tags must be skipped + assert "5.7.2beta" not in versions + assert not any(v.startswith("5.9") for v in versions) # Verify sorting (latest first) assert versions[0] == "5.8.1"