Skip to content

Commit 1652187

Browse files
new restriction lint: pointer_format (#14792)
I read a blog post about kernel security, and how various features might get lost while porting to Rust. In kernel C, they have some guardrails against divulging pointers. An easy way to replicate that in Rust is a lint for pointer formatting. So that's what this lint does. --- changelog: new [`pointer_format`] lint
2 parents 57cbadd + e64dd8e commit 1652187

File tree

5 files changed

+223
-4
lines changed

5 files changed

+223
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6163,6 +6163,7 @@ Released 2018-09-13
61636163
[`pathbuf_init_then_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#pathbuf_init_then_push
61646164
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
61656165
[`permissions_set_readonly_false`]: https://rust-lang.github.io/rust-clippy/master/index.html#permissions_set_readonly_false
6166+
[`pointer_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#pointer_format
61666167
[`pointers_in_nomem_asm_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#pointers_in_nomem_asm_block
61676168
[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters
61686169
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
166166
crate::floating_point_arithmetic::SUBOPTIMAL_FLOPS_INFO,
167167
crate::format::USELESS_FORMAT_INFO,
168168
crate::format_args::FORMAT_IN_FORMAT_ARGS_INFO,
169+
crate::format_args::POINTER_FORMAT_INFO,
169170
crate::format_args::TO_STRING_IN_FORMAT_ARGS_INFO,
170171
crate::format_args::UNINLINED_FORMAT_ARGS_INFO,
171172
crate::format_args::UNNECESSARY_DEBUG_FORMATTING_INFO,

clippy_lints/src/format_args.rs

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use std::collections::hash_map::Entry;
2+
13
use arrayvec::ArrayVec;
24
use clippy_config::Conf;
3-
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
5+
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
46
use clippy_utils::macros::{
57
FormatArgsStorage, FormatParamUsage, MacroCall, find_format_arg_expr, format_arg_removal_span,
68
format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call,
@@ -22,10 +24,12 @@ use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode};
2224
use rustc_hir::{Expr, ExprKind, LangItem};
2325
use rustc_lint::{LateContext, LateLintPass, LintContext};
2426
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
25-
use rustc_middle::ty::{List, Ty, TyCtxt};
27+
use rustc_middle::ty::{self, GenericArg, List, TraitRef, Ty, TyCtxt, Upcast};
2628
use rustc_session::impl_lint_pass;
2729
use rustc_span::edition::Edition::Edition2021;
2830
use rustc_span::{Span, Symbol, sym};
31+
use rustc_trait_selection::infer::TyCtxtInferExt;
32+
use rustc_trait_selection::traits::{Obligation, ObligationCause, Selection, SelectionContext};
2933

3034
declare_clippy_lint! {
3135
/// ### What it does
@@ -194,12 +198,41 @@ declare_clippy_lint! {
194198
"use of a format specifier that has no effect"
195199
}
196200

201+
declare_clippy_lint! {
202+
/// ### What it does
203+
/// Detects [pointer format] as well as `Debug` formatting of raw pointers or function pointers
204+
/// or any types that have a derived `Debug` impl that recursively contains them.
205+
///
206+
/// ### Why restrict this?
207+
/// The addresses are only useful in very specific contexts, and certain projects may want to keep addresses of
208+
/// certain data structures or functions from prying hacker eyes as an additional line of security.
209+
///
210+
/// ### Known problems
211+
/// The lint currently only looks through derived `Debug` implementations. Checking whether a manual
212+
/// implementation prints an address is left as an exercise to the next lint implementer.
213+
///
214+
/// ### Example
215+
/// ```no_run
216+
/// let foo = &0_u32;
217+
/// fn bar() {}
218+
/// println!("{:p}", foo);
219+
/// let _ = format!("{:?}", &(bar as fn()));
220+
/// ```
221+
///
222+
/// [pointer format]: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits
223+
#[clippy::version = "1.88.0"]
224+
pub POINTER_FORMAT,
225+
restriction,
226+
"formatting a pointer"
227+
}
228+
197229
impl_lint_pass!(FormatArgs<'_> => [
198230
FORMAT_IN_FORMAT_ARGS,
199231
TO_STRING_IN_FORMAT_ARGS,
200232
UNINLINED_FORMAT_ARGS,
201233
UNNECESSARY_DEBUG_FORMATTING,
202234
UNUSED_FORMAT_SPECS,
235+
POINTER_FORMAT,
203236
]);
204237

205238
#[allow(clippy::struct_field_names)]
@@ -208,6 +241,8 @@ pub struct FormatArgs<'tcx> {
208241
msrv: Msrv,
209242
ignore_mixed: bool,
210243
ty_msrv_map: FxHashMap<Ty<'tcx>, Option<RustcVersion>>,
244+
has_derived_debug: FxHashMap<Ty<'tcx>, bool>,
245+
has_pointer_format: FxHashMap<Ty<'tcx>, bool>,
211246
}
212247

213248
impl<'tcx> FormatArgs<'tcx> {
@@ -218,6 +253,8 @@ impl<'tcx> FormatArgs<'tcx> {
218253
msrv: conf.msrv,
219254
ignore_mixed: conf.allow_mixed_uninlined_format_args,
220255
ty_msrv_map,
256+
has_derived_debug: FxHashMap::default(),
257+
has_pointer_format: FxHashMap::default(),
221258
}
222259
}
223260
}
@@ -228,14 +265,16 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> {
228265
&& is_format_macro(cx, macro_call.def_id)
229266
&& let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn)
230267
{
231-
let linter = FormatArgsExpr {
268+
let mut linter = FormatArgsExpr {
232269
cx,
233270
expr,
234271
macro_call: &macro_call,
235272
format_args,
236273
ignore_mixed: self.ignore_mixed,
237274
msrv: &self.msrv,
238275
ty_msrv_map: &self.ty_msrv_map,
276+
has_derived_debug: &mut self.has_derived_debug,
277+
has_pointer_format: &mut self.has_pointer_format,
239278
};
240279

241280
linter.check_templates();
@@ -255,10 +294,12 @@ struct FormatArgsExpr<'a, 'tcx> {
255294
ignore_mixed: bool,
256295
msrv: &'a Msrv,
257296
ty_msrv_map: &'a FxHashMap<Ty<'tcx>, Option<RustcVersion>>,
297+
has_derived_debug: &'a mut FxHashMap<Ty<'tcx>, bool>,
298+
has_pointer_format: &'a mut FxHashMap<Ty<'tcx>, bool>,
258299
}
259300

260301
impl<'tcx> FormatArgsExpr<'_, 'tcx> {
261-
fn check_templates(&self) {
302+
fn check_templates(&mut self) {
262303
for piece in &self.format_args.template {
263304
if let FormatArgsPiece::Placeholder(placeholder) = piece
264305
&& let Ok(index) = placeholder.argument.index
@@ -279,6 +320,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
279320
if placeholder.format_trait == FormatTrait::Debug {
280321
let name = self.cx.tcx.item_name(self.macro_call.def_id);
281322
self.check_unnecessary_debug_formatting(name, arg_expr);
323+
if let Some(span) = placeholder.span
324+
&& self.has_pointer_debug(self.cx.typeck_results().expr_ty(arg_expr), 0)
325+
{
326+
span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected");
327+
}
328+
}
329+
330+
if placeholder.format_trait == FormatTrait::Pointer
331+
&& let Some(span) = placeholder.span
332+
{
333+
span_lint(self.cx, POINTER_FORMAT, span, "pointer formatting detected");
282334
}
283335
}
284336
}
@@ -559,6 +611,58 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
559611

560612
false
561613
}
614+
615+
fn has_pointer_debug(&mut self, ty: Ty<'tcx>, depth: usize) -> bool {
616+
let cx = self.cx;
617+
let tcx = cx.tcx;
618+
if !tcx.recursion_limit().value_within_limit(depth) {
619+
return false;
620+
}
621+
let depth = depth + 1;
622+
let typing_env = cx.typing_env();
623+
let ty = tcx.normalize_erasing_regions(typing_env, ty);
624+
match ty.kind() {
625+
ty::RawPtr(..) | ty::FnPtr(..) | ty::FnDef(..) => true,
626+
ty::Ref(_, t, _) | ty::Slice(t) | ty::Array(t, _) => self.has_pointer_debug(*t, depth),
627+
ty::Tuple(ts) => ts.iter().any(|t| self.has_pointer_debug(t, depth)),
628+
ty::Adt(adt, args) => {
629+
match self.has_pointer_format.entry(ty) {
630+
Entry::Occupied(o) => return *o.get(),
631+
Entry::Vacant(v) => v.insert(false),
632+
};
633+
let derived_debug = if let Some(&known) = self.has_derived_debug.get(&ty) {
634+
known
635+
} else {
636+
let Some(trait_id) = tcx.get_diagnostic_item(sym::Debug) else {
637+
return false;
638+
};
639+
let (infcx, param_env) = tcx.infer_ctxt().build_with_typing_env(typing_env);
640+
let trait_ref = TraitRef::new(tcx, trait_id, [GenericArg::from(ty)]);
641+
let obligation = Obligation {
642+
cause: ObligationCause::dummy(),
643+
param_env,
644+
recursion_depth: 0,
645+
predicate: trait_ref.upcast(tcx),
646+
};
647+
let selection = SelectionContext::new(&infcx).select(&obligation);
648+
let derived = if let Ok(Some(Selection::UserDefined(data))) = selection {
649+
tcx.has_attr(data.impl_def_id, sym::automatically_derived)
650+
} else {
651+
false
652+
};
653+
self.has_derived_debug.insert(ty, derived);
654+
derived
655+
};
656+
let pointer_debug = derived_debug
657+
&& adt.all_fields().any(|f| {
658+
self.has_pointer_debug(tcx.normalize_erasing_regions(typing_env, f.ty(tcx, args)), depth)
659+
});
660+
self.has_pointer_format.insert(ty, pointer_debug);
661+
pointer_debug
662+
},
663+
_ => false,
664+
}
665+
}
562666
}
563667

564668
fn make_ty_msrv_map(tcx: TyCtxt<'_>) -> FxHashMap<Ty<'_>, Option<RustcVersion>> {

tests/ui/pointer_format.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#![warn(clippy::pointer_format)]
2+
3+
use core::fmt::Debug;
4+
use core::marker::PhantomData;
5+
6+
#[derive(Debug)]
7+
struct ContainsPointerDeep {
8+
w: WithPointer,
9+
}
10+
11+
struct ManualDebug {
12+
ptr: *const u8,
13+
}
14+
15+
#[derive(Debug)]
16+
struct WithPointer {
17+
len: usize,
18+
ptr: *const u8,
19+
}
20+
21+
impl std::fmt::Debug for ManualDebug {
22+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
23+
f.write_str("ManualDebug")
24+
}
25+
}
26+
27+
trait Foo {
28+
type Assoc: Foo + Debug;
29+
}
30+
31+
#[derive(Debug)]
32+
struct S<T: Foo + 'static>(&'static S<T::Assoc>, PhantomData<T>);
33+
34+
#[allow(unused)]
35+
fn unbounded<T: Foo + Debug + 'static>(s: &S<T>) {
36+
format!("{s:?}");
37+
}
38+
39+
fn main() {
40+
let m = &(main as fn());
41+
let g = &0;
42+
let o = &format!("{m:p}");
43+
//~^ pointer_format
44+
let _ = format!("{m:?}");
45+
//~^ pointer_format
46+
println!("{g:p}");
47+
//~^ pointer_format
48+
panic!("{o:p}");
49+
//~^ pointer_format
50+
let answer = 42;
51+
let x = &raw const answer;
52+
let arr = [0u8; 8];
53+
let with_ptr = WithPointer { len: 8, ptr: &arr as _ };
54+
let _ = format!("{x:?}");
55+
//~^ pointer_format
56+
print!("{with_ptr:?}");
57+
//~^ pointer_format
58+
let container = ContainsPointerDeep { w: with_ptr };
59+
print!("{container:?}");
60+
//~^ pointer_format
61+
62+
let no_pointer = "foo";
63+
println!("{no_pointer:?}");
64+
let manual_debug = ManualDebug { ptr: &arr as _ };
65+
println!("{manual_debug:?}");
66+
}

tests/ui/pointer_format.stderr

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
error: pointer formatting detected
2+
--> tests/ui/pointer_format.rs:42:23
3+
|
4+
LL | let o = &format!("{m:p}");
5+
| ^^^^^
6+
|
7+
= note: `-D clippy::pointer-format` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::pointer_format)]`
9+
10+
error: pointer formatting detected
11+
--> tests/ui/pointer_format.rs:44:22
12+
|
13+
LL | let _ = format!("{m:?}");
14+
| ^^^^^
15+
16+
error: pointer formatting detected
17+
--> tests/ui/pointer_format.rs:46:15
18+
|
19+
LL | println!("{g:p}");
20+
| ^^^^^
21+
22+
error: pointer formatting detected
23+
--> tests/ui/pointer_format.rs:48:13
24+
|
25+
LL | panic!("{o:p}");
26+
| ^^^^^
27+
28+
error: pointer formatting detected
29+
--> tests/ui/pointer_format.rs:54:22
30+
|
31+
LL | let _ = format!("{x:?}");
32+
| ^^^^^
33+
34+
error: pointer formatting detected
35+
--> tests/ui/pointer_format.rs:56:13
36+
|
37+
LL | print!("{with_ptr:?}");
38+
| ^^^^^^^^^^^^
39+
40+
error: pointer formatting detected
41+
--> tests/ui/pointer_format.rs:59:13
42+
|
43+
LL | print!("{container:?}");
44+
| ^^^^^^^^^^^^^
45+
46+
error: aborting due to 7 previous errors
47+

0 commit comments

Comments
 (0)