rapx/check/safedrop/
safedrop.rs

1use super::{bug_records::*, corner_case::*, drop::*, graph::*};
2use crate::{
3    analysis::alias_analysis::default::{MopFnAliasMap, types::ValueKind},
4    analysis::path_analysis::{PathNode, PathTree},
5    def_id::is_drop_fn,
6    utils::source::{get_filename, get_name},
7};
8use rustc_middle::{
9    mir::{
10        Operand::{self},
11        Place, TerminatorKind,
12    },
13    ty::{self},
14};
15use rustc_span::{Span, Symbol};
16
17pub const VISIT_LIMIT: usize = 1000;
18
19impl<'tcx> SafeDropGraph<'tcx> {
20    // analyze the drop statement and update the liveness for nodes.
21    pub fn drop_check(&mut self, bb_idx: usize) {
22        let is_cleanup = self.alias_graph.cfg_block(bb_idx).is_cleanup;
23        if let Some(terminator) = self.alias_graph.terminator(bb_idx).cloned() {
24            rap_debug!("drop check bb: {}, {:?}", bb_idx, terminator);
25            match terminator.kind {
26                TerminatorKind::Drop {
27                    ref place,
28                    target: _,
29                    unwind: _,
30                    replace: _,
31                    drop: _,
32                    #[cfg(not(rapx_rustc_ge_198))]
33                        async_fut: _,
34                } => {
35                    if !self.drop_heap_item_check(place) {
36                        return;
37                    }
38                    let value_idx = self.alias_graph.projection(place.clone());
39                    self.sync_drop_record();
40                    self.add_to_drop_record(value_idx, bb_idx, is_cleanup);
41                }
42                TerminatorKind::Call {
43                    ref func, ref args, ..
44                } => {
45                    let Operand::Constant(c) = func else {
46                        return;
47                    };
48                    let ty::FnDef(id, ..) = c.ty().kind() else {
49                        return;
50                    };
51                    if !is_drop_fn(*id) {
52                        return;
53                    }
54                    if !args.is_empty() {
55                        let place = match args[0].node {
56                            Operand::Copy(place) => place,
57                            Operand::Move(place) => place,
58                            _ => {
59                                rap_error!("Constant operand exists: {:?}", args[0]);
60                                return;
61                            }
62                        };
63                        if !self.drop_heap_item_check(&place) {
64                            return;
65                        }
66                        let local = self.alias_graph.projection(place.clone());
67                        self.sync_drop_record();
68                        self.add_to_drop_record(local, bb_idx, is_cleanup);
69                    }
70                }
71                _ => {}
72            }
73        }
74    }
75
76    pub fn drop_heap_item_check(&self, place: &Place<'tcx>) -> bool {
77        let tcx = self.alias_graph.tcx();
78        let place_ty = place.ty(
79            &tcx.optimized_mir(self.alias_graph.def_id()).local_decls,
80            tcx,
81        );
82        match place_ty.ty.kind() {
83            ty::TyKind::Adt(adtdef, ..) => match self.adt_owner.get(&adtdef.did()) {
84                None => true,
85                Some(owenr_unit) => {
86                    let idx = match place_ty.variant_index {
87                        Some(vdx) => vdx.index(),
88                        None => 0,
89                    };
90                    if owenr_unit[idx].0.is_onheap() || owenr_unit[idx].1.contains(&true) {
91                        true
92                    } else {
93                        false
94                    }
95                }
96            },
97            _ => true,
98        }
99    }
100
101    /// Process pre-enumerated whole-function paths for SafeDrop via DFS on
102    /// the path tree. All paths have already been filtered by incremental
103    /// constraint-based reachability checks during enumeration, so no
104    /// per-path filtering is needed. State is saved at branch points and
105    /// restored before each sibling subtree.
106    pub fn process_function_paths(&mut self, fn_map: &MopFnAliasMap) {
107        self.process_function_paths_opt(None, fn_map)
108    }
109
110    pub fn process_function_paths_opt(
111        &mut self,
112        precomputed_paths: Option<PathTree>,
113        fn_map: &MopFnAliasMap,
114    ) {
115        let paths = precomputed_paths.unwrap_or_else(|| self.alias_graph.enumerate_paths());
116
117        let Some(root) = paths.root() else {
118            return;
119        };
120        let mut path = Vec::new();
121        let _ = self.dfs_safedrop(root, &mut path, fn_map);
122    }
123
124    fn dfs_safedrop(
125        &mut self,
126        node: &PathNode,
127        path: &mut Vec<usize>,
128        fn_map: &MopFnAliasMap,
129    ) -> Result<(), ()> {
130        path.push(node.block);
131        self.alias_bb(node.block);
132        self.alias_bbcall(node.block, fn_map);
133        self.drop_check(node.block);
134
135        let saved_values = self.alias_graph.values.clone();
136        let saved_constants = self.alias_graph.constants.clone();
137        let saved_alias_sets = self.alias_graph.alias_sets.clone();
138        let saved_drop_record = self.drop_record.clone();
139
140        if node.is_path_end {
141            self.alias_graph.increment_visit_times();
142            if self.alias_graph.visit_times() > VISIT_LIMIT {
143                path.pop();
144                return Err(());
145            }
146            if should_check(self.alias_graph.def_id()) {
147                if let Some(&last) = path.last() {
148                    let cfg_block = self.alias_graph.cfg_block(last).clone();
149                    self.dp_check(cfg_block.is_cleanup);
150                }
151            }
152        }
153
154        for child in &node.children {
155            self.alias_graph.values = saved_values.clone();
156            self.alias_graph.constants = saved_constants.clone();
157            self.alias_graph.alias_sets = saved_alias_sets.clone();
158            self.drop_record = saved_drop_record.clone();
159            self.dfs_safedrop(child, path, fn_map)?;
160        }
161
162        path.pop();
163        Ok(())
164    }
165    pub fn report_bugs(&self) {
166        rap_debug!(
167            "report bugs, id: {:?}, uaf: {:?}",
168            self.alias_graph.def_id(),
169            self.bug_records.uaf_bugs
170        );
171        let filename = get_filename(self.alias_graph.tcx(), self.alias_graph.def_id());
172        match filename {
173            Some(filename) => {
174                if filename.contains(".cargo") {
175                    return;
176                }
177            }
178            None => {}
179        }
180        if self.bug_records.is_bug_free() {
181            return;
182        }
183        let fn_name = match get_name(self.alias_graph.tcx(), self.alias_graph.def_id()) {
184            Some(name) => name,
185            None => Symbol::intern("no symbol available"),
186        };
187        let body = self
188            .alias_graph
189            .tcx()
190            .optimized_mir(self.alias_graph.def_id());
191        self.bug_records
192            .df_bugs_output(body, fn_name, self.alias_graph.span());
193        self.bug_records
194            .uaf_bugs_output(body, fn_name, self.alias_graph.span());
195        self.bug_records
196            .dp_bug_output(body, fn_name, self.alias_graph.span());
197    }
198
199    fn make_bug(
200        &self,
201        idx: usize,
202        trigger_info: LocalSpot,
203        span: Span,
204        confidence: usize,
205        bug_type: BugType,
206    ) -> TyBug {
207        TyBug {
208            drop_spot: self.drop_record[idx].drop_spot,
209            trigger_info,
210            span,
211            confidence,
212            bug_type,
213        }
214    }
215
216    fn check_drop_status(&mut self, idx: usize) -> Option<usize> {
217        self.fetch_drop_info(idx);
218        let mut fully_dropped = true;
219        if !self.drop_record[idx].is_dropped {
220            fully_dropped = false;
221            if !self.drop_record[idx].has_dropped_field {
222                return None;
223            }
224        }
225        let kind = self.alias_graph.values[idx].kind;
226        Some(Self::rate_confidence(kind, fully_dropped))
227    }
228
229    pub fn uaf_check(&mut self, value_idx: usize, bb_idx: usize, span: Span, is_fncall: bool) {
230        let local = self.alias_graph.values[value_idx].local;
231        rap_debug!(
232            "uaf_check, idx: {:?}, local: {:?}, drop_record: {:?}",
233            value_idx,
234            local,
235            self.drop_record[value_idx],
236        );
237        if !self.alias_graph.values[value_idx].may_drop {
238            return;
239        }
240        if self.alias_graph.values[value_idx].is_ptr() && !is_fncall {
241            return;
242        }
243        let Some(confidence) = self.check_drop_status(value_idx) else {
244            return;
245        };
246        if self.bug_records.uaf_bugs.contains_key(&local) {
247            return;
248        }
249        let drop_spot = self.drop_record[value_idx].drop_spot;
250        if let Some(t) = self
251            .bug_records
252            .try_merge_pair(drop_spot, bb_idx, BugType::UseAfterFree)
253        {
254            let bug = self.make_bug(
255                value_idx,
256                LocalSpot::new(bb_idx, local),
257                span.clone(),
258                confidence,
259                t,
260            );
261            rap_warn!("Find a use-after-free bug {:?}; add to records", bug);
262            self.bug_records.uaf_bugs.insert(local, bug);
263        }
264    }
265
266    pub fn rate_confidence(kind: ValueKind, fully_dropped: bool) -> usize {
267        match (kind, fully_dropped) {
268            (ValueKind::SpecialPtr, _) => 0,
269            (_, true) => 99,
270            (_, false) => 50,
271        }
272    }
273
274    pub fn df_check(
275        &mut self,
276        value_idx: usize,
277        bb_idx: usize,
278        span: Span,
279        flag_cleanup: bool,
280    ) -> bool {
281        let local = self.alias_graph.values[value_idx].local;
282        rap_debug!(
283            "df_check: value_idx = {:?}, bb_idx = {:?}, alias_sets: {:?}",
284            value_idx,
285            bb_idx,
286            self.alias_graph.alias_sets,
287        );
288        let Some(confidence) = self.check_drop_status(value_idx) else {
289            return false;
290        };
291
292        for item in &self.drop_record {
293            rap_debug!("drop_spot: {:?}", item);
294        }
295
296        let drop_spot = self.drop_record[value_idx].drop_spot;
297        let result_type = self
298            .bug_records
299            .try_merge_pair(drop_spot, bb_idx, BugType::DoubleFree);
300        let Some(t) = result_type else {
301            return true;
302        };
303
304        let bug = self.make_bug(
305            value_idx,
306            LocalSpot::new(bb_idx, local),
307            span.clone(),
308            confidence,
309            t,
310        );
311        let target_map = if flag_cleanup {
312            &mut self.bug_records.df_bugs_unwind
313        } else {
314            &mut self.bug_records.df_bugs
315        };
316        if !target_map.contains_key(&local) {
317            target_map.insert(local, bug);
318            if flag_cleanup {
319                rap_info!(
320                    "Find a double free bug {} during unwinding; add to records.",
321                    local
322                );
323            } else {
324                rap_info!("Find a double free bug {}; add to records.", local);
325            }
326        }
327        true
328    }
329
330    pub fn dp_check(&mut self, flag_cleanup: bool) {
331        rap_debug!("dangling pointer check");
332        rap_debug!("current alias sets: {:?}", self.alias_graph.alias_sets);
333        if flag_cleanup {
334            for arg_idx in 1..self.alias_graph.arg_size() + 1 {
335                self.dp_check_arg(arg_idx, flag_cleanup);
336            }
337        } else if self.alias_graph.values[0].may_drop
338            && (self.drop_record[0].is_dropped || self.drop_record[0].has_dropped_field)
339        {
340            let Some(confidence) = self.check_drop_status(0) else {
341                return;
342            };
343            if !self.bug_records.dp_bugs.contains_key(&0) {
344                let bug = self.make_bug(
345                    0,
346                    LocalSpot::from_local(0),
347                    self.alias_graph.span().clone(),
348                    confidence,
349                    BugType::DanglingPointer,
350                );
351                self.bug_records.dp_bugs.insert(0, bug);
352                rap_info!("Find a dangling pointer 0; add to record.");
353            }
354        } else {
355            for arg_idx in 0..self.alias_graph.arg_size() + 1 {
356                self.dp_check_arg(arg_idx, false);
357            }
358        }
359    }
360
361    fn dp_check_arg(&mut self, arg_idx: usize, flag_cleanup: bool) {
362        if !self.alias_graph.values[arg_idx].is_ptr() {
363            return;
364        }
365        let Some(confidence) = self.check_drop_status(arg_idx) else {
366            return;
367        };
368        let bug = self.make_bug(
369            arg_idx,
370            LocalSpot::from_local(arg_idx),
371            self.alias_graph.span().clone(),
372            confidence,
373            BugType::DanglingPointer,
374        );
375        if flag_cleanup {
376            if !self.bug_records.dp_bugs_unwind.contains_key(&arg_idx) {
377                let drop_spot = self.drop_record[arg_idx].drop_spot;
378                if self
379                    .bug_records
380                    .dp_bugs_unwind
381                    .values()
382                    .any(|e| e.drop_spot == drop_spot)
383                {
384                    return;
385                }
386                self.bug_records.dp_bugs_unwind.insert(arg_idx, bug);
387                rap_info!(
388                    "Find a dangling pointer {} during unwinding; add to record.",
389                    arg_idx
390                );
391            }
392        } else if !self.bug_records.dp_bugs.contains_key(&arg_idx) {
393            let drop_spot = self.drop_record[arg_idx].drop_spot;
394            if self
395                .bug_records
396                .dp_bugs
397                .values()
398                .any(|e| e.drop_spot == drop_spot)
399            {
400                return;
401            }
402            self.bug_records.dp_bugs.insert(arg_idx, bug);
403            rap_info!("Find a dangling pointer {}; add to record.", arg_idx);
404        }
405    }
406}