From 473d7c75a4e8a99a14bbbca459c493acb717fae2 Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 22 Apr 2025 17:36:27 +0200 Subject: [PATCH] Check dist name to handle bogus redirect (#12917) When an index performs a bogus redirect or otherwise returns a different distribution name than expected, uv currently hangs. In the example case, requesting the simple index page for any package returns the page for anyio. This mean querying the sniffio version map returns only anyio entries, and the version maps resolves to an anyio version. When the resolver makes a query for sniffio and waits for it to resolve, the main thread finds an anyio and resolves only that in the wait map, causing the hang. We fix this by checking the name of the returned distribution against the name of the requested distribution. For good measure, we add the same check in `Request::Dist` and `Request::Installed`. For performance and complexity reasons, we don't perform this check in the version map itself, but only after a candidate distribution has been selected. --------- Co-authored-by: Zanie Blue --- crates/uv-resolver/src/error.rs | 6 +++++ crates/uv-resolver/src/resolver/mod.rs | 29 ++++++++++++++++++++- crates/uv/tests/it/pip_install.rs | 35 +++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/crates/uv-resolver/src/error.rs b/crates/uv-resolver/src/error.rs index 48fe21307..9d7ce0b4b 100644 --- a/crates/uv-resolver/src/error.rs +++ b/crates/uv-resolver/src/error.rs @@ -124,6 +124,12 @@ pub enum ResolveError { #[source] name_error: InvalidNameError, }, + #[error("The index returned metadata for the wrong package: expected {request} for {expected}, got {request} for {actual}")] + MismatchedPackageName { + request: &'static str, + expected: PackageName, + actual: PackageName, + }, } impl From> for ResolveError { diff --git a/crates/uv-resolver/src/resolver/mod.rs b/crates/uv-resolver/src/resolver/mod.rs index e482b0d27..7ae2636bc 100644 --- a/crates/uv-resolver/src/resolver/mod.rs +++ b/crates/uv-resolver/src/resolver/mod.rs @@ -25,7 +25,7 @@ use uv_distribution::DistributionDatabase; use uv_distribution_types::{ BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata, IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations, - IndexMetadata, IndexUrl, InstalledDist, PythonRequirementKind, RemoteSource, Requirement, + IndexMetadata, IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement, ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef, }; use uv_git::GitResolver; @@ -2261,12 +2261,32 @@ impl ResolverState { let metadata = provider.get_installed_metadata(&dist).boxed_local().await?; + if let MetadataResponse::Found(metadata) = &metadata { + if &metadata.metadata.name != dist.name() { + return Err(ResolveError::MismatchedPackageName { + request: "installed metadata", + expected: dist.name().clone(), + actual: metadata.metadata.name.clone(), + }); + } + } + Ok(Some(Response::Installed { dist, metadata })) } @@ -2369,6 +2389,13 @@ impl ResolverState { diff --git a/crates/uv/tests/it/pip_install.rs b/crates/uv/tests/it/pip_install.rs index 2682385a7..ef96ab3b7 100644 --- a/crates/uv/tests/it/pip_install.rs +++ b/crates/uv/tests/it/pip_install.rs @@ -17,7 +17,6 @@ use wiremock::{ #[cfg(feature = "git")] use crate::common::{self, decode_token}; - use crate::common::{ build_vendor_links_url, download_to_disk, get_bin, uv_snapshot, venv_bin_path, venv_to_interpreter, TestContext, @@ -11108,3 +11107,37 @@ fn pep_751_multiple_sources() -> Result<()> { Ok(()) } + +/// Test that uv doesn't hang if an index returns a distribution for the wrong package. +#[tokio::test] +async fn bogus_redirect() -> Result<()> { + let context = TestContext::new("3.12"); + + let redirect_server = MockServer::start().await; + + // Configure a bogus redirect where for all packages, anyio is returned. + Mock::given(method("GET")) + .respond_with( + ResponseTemplate::new(302).insert_header("Location", "https://pypi.org/simple/anyio/"), + ) + .mount(&redirect_server) + .await; + + uv_snapshot!( + context + .pip_install() + .arg("--default-index") + .arg(redirect_server.uri()) + .arg("sniffio"), + @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: The index returned metadata for the wrong package: expected distribution for sniffio, got distribution for anyio + " + ); + + Ok(()) +}