@@ -878,9 +878,80 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
878878 debug ! ( ?fn_ty_a, ?b, "coerce_from_fn_pointer" ) ;
879879 debug_assert ! ( self . shallow_resolve( b) == b) ;
880880
881+ // Check implied bounds for HRTB function pointer coercions (issue #25860)
882+ if let ty:: FnPtr ( sig_tys_b, hdr_b) = b. kind ( ) {
883+ let target_sig = sig_tys_b. with ( * hdr_b) ;
884+ self . check_hrtb_implied_bounds ( fn_ty_a, target_sig) ?;
885+ }
886+
881887 self . coerce_from_safe_fn ( fn_ty_a, b, None )
882888 }
883889
890+ /// Validates that implied bounds from nested references in the source
891+ /// function signature are satisfied when coercing to an HRTB function pointer.
892+ ///
893+ /// This prevents the soundness hole in issue #25860 where lifetime bounds
894+ /// can be circumvented through HRTB function pointer coercion.
895+ ///
896+ /// For example, a function with signature `fn<'a, 'b>(_: &'a &'b (), v: &'b T) -> &'a T`
897+ /// has an implied bound `'b: 'a` from the type `&'a &'b ()`. When coercing to
898+ /// `for<'x> fn(_, &'x T) -> &'static T`, we must ensure that the implied bound
899+ /// can be satisfied, which it cannot in this case.
900+ fn check_hrtb_implied_bounds (
901+ & self ,
902+ source_sig : ty:: PolyFnSig < ' tcx > ,
903+ target_sig : ty:: PolyFnSig < ' tcx > ,
904+ ) -> Result < ( ) , TypeError < ' tcx > > {
905+ use rustc_infer:: infer:: outlives:: implied_bounds;
906+
907+ // Only check if target has HRTB (bound variables)
908+ if target_sig. bound_vars ( ) . is_empty ( ) {
909+ return Ok ( ( ) ) ;
910+ }
911+
912+ // Extract implied bounds from the source signature's input types
913+ let source_inputs = source_sig. skip_binder ( ) . inputs ( ) ;
914+ let target_inputs = target_sig. skip_binder ( ) . inputs ( ) ;
915+
916+ // If the number of inputs differs, something unusual is happening
917+ if source_inputs. len ( ) != target_inputs. len ( ) {
918+ return Ok ( ( ) ) ; // Let normal type checking handle this
919+ }
920+
921+ let mut all_implied_bounds = Vec :: new ( ) ;
922+
923+ for input_ty in source_inputs. iter ( ) {
924+ let bounds = implied_bounds:: extract_nested_reference_bounds ( self . tcx , * input_ty) ;
925+ all_implied_bounds. extend ( bounds) ;
926+ }
927+
928+ // If there are no implied bounds, the coercion is safe
929+ if all_implied_bounds. is_empty ( ) {
930+ return Ok ( ( ) ) ;
931+ }
932+
933+ // Check if target inputs also have nested references with the same structure.
934+ // If both source and target preserve the nested reference structure, the coercion is safe.
935+ // The unsoundness only occurs when we're "collapsing" nested lifetimes.
936+ let target_has_nested_refs = target_inputs
937+ . iter ( )
938+ . any ( |ty| !implied_bounds:: extract_nested_reference_bounds ( self . tcx , * ty) . is_empty ( ) ) ;
939+
940+ if target_has_nested_refs {
941+ // Target also has nested references, so the implied bounds structure is preserved.
942+ // This is safe (e.g., fn(&'a &'b T) -> fn(for<'x, 'y> &'x &'y T)).
943+ return Ok ( ( ) ) ;
944+ }
945+
946+ // Source has implied bounds from nested refs but target doesn't preserve them.
947+ // This is the unsound case (e.g., cve-rs: fn(&'a &'b T) -> for<'x> fn(&'x T)).
948+ debug ! (
949+ "check_hrtb_implied_bounds: source has implied bounds but target doesn't preserve nested refs, rejecting coercion"
950+ ) ;
951+
952+ Err ( TypeError :: Mismatch )
953+ }
954+
884955 fn coerce_from_fn_item ( & self , a : Ty < ' tcx > , b : Ty < ' tcx > ) -> CoerceResult < ' tcx > {
885956 debug ! ( "coerce_from_fn_item(a={:?}, b={:?})" , a, b) ;
886957 debug_assert ! ( self . shallow_resolve( a) == a) ;
@@ -890,8 +961,12 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {
890961 self . at ( & self . cause , self . param_env ) . normalize ( b) ;
891962
892963 match b. kind ( ) {
893- ty:: FnPtr ( _ , b_hdr) => {
964+ ty:: FnPtr ( sig_tys_b , b_hdr) => {
894965 let mut a_sig = a. fn_sig ( self . tcx ) ;
966+
967+ // Check implied bounds for HRTB function pointer coercions (issue #25860)
968+ let target_sig = sig_tys_b. with ( * b_hdr) ;
969+ self . check_hrtb_implied_bounds ( a_sig, target_sig) ?;
895970 if let ty:: FnDef ( def_id, _) = * a. kind ( ) {
896971 // Intrinsics are not coercible to function pointers
897972 if self . tcx . intrinsic ( def_id) . is_some ( ) {
0 commit comments