11use arrayvec:: ArrayVec ;
22use clippy_config:: Conf ;
3- use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
3+ use clippy_utils:: diagnostics:: { span_lint , span_lint_and_sugg, span_lint_and_then} ;
44use clippy_utils:: macros:: {
55 FormatArgsStorage , FormatParamUsage , MacroCall , find_format_arg_expr, format_arg_removal_span,
66 format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call,
@@ -16,16 +16,18 @@ use rustc_ast::{
1616 FormatPlaceholder , FormatTrait ,
1717} ;
1818use rustc_attr_data_structures:: RustcVersion ;
19- use rustc_data_structures:: fx:: FxHashMap ;
19+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
2020use rustc_errors:: Applicability ;
2121use rustc_errors:: SuggestionStyle :: { CompletelyHidden , ShowCode } ;
2222use rustc_hir:: { Expr , ExprKind , LangItem } ;
2323use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
2424use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment } ;
25- use rustc_middle:: ty:: { List , Ty , TyCtxt } ;
25+ use rustc_middle:: ty:: { self , GenericArg , List , TraitRef , Ty , TyCtxt , Upcast } ;
2626use rustc_session:: impl_lint_pass;
2727use rustc_span:: edition:: Edition :: Edition2021 ;
2828use rustc_span:: { Span , Symbol , sym} ;
29+ use rustc_trait_selection:: infer:: TyCtxtInferExt ;
30+ use rustc_trait_selection:: traits:: { Obligation , ObligationCause , Selection , SelectionContext } ;
2931
3032declare_clippy_lint ! {
3133 /// ### What it does
@@ -194,12 +196,34 @@ declare_clippy_lint! {
194196 "use of a format specifier that has no effect"
195197}
196198
199+ declare_clippy_lint ! {
200+ /// ### What it does
201+ /// Detects [pointer format].
202+ ///
203+ /// ### Why restrict this?
204+ /// In kernel context, this might be vulnerable to misuse for exfiltrating
205+ /// stack or kernel function addresses.
206+ ///
207+ /// ### Example
208+ /// ```no_run
209+ /// let foo = &0_u32;
210+ /// println!("{:p}", foo);
211+ /// ```
212+ ///
213+ /// [pointer format]: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits
214+ #[ clippy:: version = "1.88.0" ]
215+ pub POINTER_FORMAT ,
216+ restriction,
217+ "use of a pointer format specifier"
218+ }
219+
197220impl_lint_pass ! ( FormatArgs <' _> => [
198221 FORMAT_IN_FORMAT_ARGS ,
199222 TO_STRING_IN_FORMAT_ARGS ,
200223 UNINLINED_FORMAT_ARGS ,
201224 UNNECESSARY_DEBUG_FORMATTING ,
202225 UNUSED_FORMAT_SPECS ,
226+ POINTER_FORMAT ,
203227] ) ;
204228
205229#[ allow( clippy:: struct_field_names) ]
@@ -208,6 +232,7 @@ pub struct FormatArgs<'tcx> {
208232 msrv : Msrv ,
209233 ignore_mixed : bool ,
210234 ty_msrv_map : FxHashMap < Ty < ' tcx > , Option < RustcVersion > > ,
235+ has_derived_debug : FxHashMap < Ty < ' tcx > , bool > ,
211236}
212237
213238impl < ' tcx > FormatArgs < ' tcx > {
@@ -218,6 +243,7 @@ impl<'tcx> FormatArgs<'tcx> {
218243 msrv : conf. msrv ,
219244 ignore_mixed : conf. allow_mixed_uninlined_format_args ,
220245 ty_msrv_map,
246+ has_derived_debug : FxHashMap :: default ( ) ,
221247 }
222248 }
223249}
@@ -228,14 +254,15 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> {
228254 && is_format_macro ( cx, macro_call. def_id )
229255 && let Some ( format_args) = self . format_args . get ( cx, expr, macro_call. expn )
230256 {
231- let linter = FormatArgsExpr {
257+ let mut linter = FormatArgsExpr {
232258 cx,
233259 expr,
234260 macro_call : & macro_call,
235261 format_args,
236262 ignore_mixed : self . ignore_mixed ,
237263 msrv : & self . msrv ,
238264 ty_msrv_map : & self . ty_msrv_map ,
265+ has_derived_debug : & mut self . has_derived_debug ,
239266 } ;
240267
241268 linter. check_templates ( ) ;
@@ -255,10 +282,11 @@ struct FormatArgsExpr<'a, 'tcx> {
255282 ignore_mixed : bool ,
256283 msrv : & ' a Msrv ,
257284 ty_msrv_map : & ' a FxHashMap < Ty < ' tcx > , Option < RustcVersion > > ,
285+ has_derived_debug : & ' a mut FxHashMap < Ty < ' tcx > , bool > ,
258286}
259287
260288impl < ' tcx > FormatArgsExpr < ' _ , ' tcx > {
261- fn check_templates ( & self ) {
289+ fn check_templates ( & mut self ) {
262290 for piece in & self . format_args . template {
263291 if let FormatArgsPiece :: Placeholder ( placeholder) = piece
264292 && let Ok ( index) = placeholder. argument . index
@@ -279,6 +307,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
279307 if placeholder. format_trait == FormatTrait :: Debug {
280308 let name = self . cx . tcx . item_name ( self . macro_call . def_id ) ;
281309 self . check_unnecessary_debug_formatting ( name, arg_expr) ;
310+ if let Some ( span) = placeholder. span
311+ && self . has_pointer_debug ( self . cx . typeck_results ( ) . expr_ty ( arg_expr) )
312+ {
313+ span_lint ( self . cx , POINTER_FORMAT , span, "pointer formatting detected" ) ;
314+ }
315+ }
316+
317+ if placeholder. format_trait == FormatTrait :: Pointer
318+ && let Some ( span) = placeholder. span
319+ {
320+ span_lint ( self . cx , POINTER_FORMAT , span, "pointer formatting detected" ) ;
282321 }
283322 }
284323 }
@@ -559,6 +598,59 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
559598
560599 false
561600 }
601+
602+ fn has_pointer_debug ( & mut self , ty : Ty < ' tcx > ) -> bool {
603+ let mut visited = FxHashSet :: default ( ) ;
604+ let mut open: Vec < Ty < ' _ > > = Vec :: new ( ) ;
605+ open. push ( ty) ;
606+ let mut contains = Vec :: new ( ) ;
607+ while let Some ( ty) = open. pop ( ) {
608+ match ty. kind ( ) {
609+ ty:: RawPtr ( _, _) => return true ,
610+ ty:: Ref ( _, t, _) | ty:: Slice ( t) | ty:: Array ( t, _) => open. push ( * t) ,
611+ ty:: Tuple ( ts) => open. extend_from_slice ( ts) ,
612+ ty:: Adt ( adt, args) => {
613+ // avoid infinite recursion
614+ if !visited. insert ( adt. did ( ) ) {
615+ continue ;
616+ }
617+ let cx = self . cx ;
618+ let tcx = cx. tcx ;
619+ let has_derived_debug = if let Some ( known) = self . has_derived_debug . get ( & ty) {
620+ * known
621+ } else {
622+ let Some ( trait_id) = tcx. get_diagnostic_item ( sym:: Debug ) else {
623+ continue ;
624+ } ;
625+ let ( infcx, param_env) = tcx. infer_ctxt ( ) . build_with_typing_env ( cx. typing_env ( ) ) ;
626+ let ty = tcx. erase_regions ( ty) ;
627+ let trait_ref = TraitRef :: new ( tcx, trait_id, [ GenericArg :: from ( ty) ] ) ;
628+ let obligation = Obligation {
629+ cause : ObligationCause :: dummy ( ) ,
630+ param_env,
631+ recursion_depth : 0 ,
632+ predicate : trait_ref. upcast ( tcx) ,
633+ } ;
634+ let selection = SelectionContext :: new ( & infcx) . select ( & obligation) ;
635+ let Ok ( Some ( Selection :: UserDefined ( data) ) ) = selection else {
636+ continue ;
637+ } ;
638+ let has_derived_debug = tcx. has_attr ( data. impl_def_id , sym:: automatically_derived) ;
639+ self . has_derived_debug . insert ( ty, has_derived_debug) ;
640+ has_derived_debug
641+ } ;
642+ // we currently only look into derived impls because those will
643+ // debug-format the types fields which is easy enough to pull off
644+ if has_derived_debug {
645+ contains. push ( ty) ;
646+ open. extend ( adt. all_fields ( ) . map ( |f| f. ty ( tcx, args) ) ) ;
647+ }
648+ } ,
649+ _ => ( ) ,
650+ }
651+ }
652+ false
653+ }
562654}
563655
564656fn make_ty_msrv_map ( tcx : TyCtxt < ' _ > ) -> FxHashMap < Ty < ' _ > , Option < RustcVersion > > {
0 commit comments