Skip to main content

rapx/check/safedrop/
checks.rs

1use rustc_span::Span;
2use crate::analysis::alias::default::graph::AliasGraph;
3use crate::analysis::alias::default::types::ValueKind;
4use super::bug_records::*;
5use super::drop::*;
6
7// ── public entry points ──
8
9/// Extends `drop_record` to match `graph.values.len()`.
10/// For each new index, if the value has a father, copies from the father's
11/// drop_record; otherwise creates a false_record.
12pub fn sync_drop_record(graph: &AliasGraph, drop_record: &mut Vec<DropRecord>) {
13    let target_len = graph.values.len();
14    while drop_record.len() < target_len {
15        let new_idx = drop_record.len();
16        let father = if new_idx < graph.values.len() {
17            graph.values[new_idx].father.clone()
18        } else {
19            None
20        };
21        drop_record.push(if let Some(ref fi) = father {
22            DropRecord::from(new_idx, &drop_record[fi.father_value_id])
23        } else {
24            DropRecord::false_record(new_idx)
25        });
26    }
27}
28
29pub fn clear_drop_info(graph: &AliasGraph, drop_record: &mut Vec<DropRecord>, value_idx: usize) {
30    rap_debug!("clear_drop: value_idx = {}", value_idx);
31    drop_record[value_idx].clear();
32    clear_field_drop(graph, drop_record, value_idx);
33    clear_father_drop(graph, drop_record, value_idx);
34}
35
36pub fn uaf_check(
37    graph: &AliasGraph,
38    drop_record: &mut Vec<DropRecord>,
39    bug_records: &mut BugRecords,
40    value_idx: usize,
41    bb_idx: usize,
42    span: Span,
43    is_fncall: bool,
44) {
45    let local = graph.values[value_idx].local;
46    rap_debug!(
47        "uaf_check, idx: {:?}, local: {:?}, drop_record: {:?}",
48        value_idx,
49        local,
50        drop_record[value_idx],
51    );
52    if !graph.value_may_drop(value_idx) {
53        return;
54    }
55    if graph.value_is_ptr(value_idx) && !is_fncall {
56        return;
57    }
58    let Some(confidence) = check_drop_status(graph, drop_record, value_idx) else {
59        return;
60    };
61    if bug_records.uaf_bugs.contains_key(&local) {
62        return;
63    }
64    let drop_spot = drop_record[value_idx].drop_spot;
65    if let Some(t) = bug_records.try_merge_pair(drop_spot, bb_idx, BugType::UseAfterFree) {
66        let bug = make_bug(
67            &drop_record[value_idx],
68            LocalSpot::new(bb_idx, local),
69            span.clone(),
70            confidence,
71            t,
72        );
73        rap_warn!("Find a use-after-free bug {:?}; add to records", bug);
74        bug_records.uaf_bugs.insert(local, bug);
75    }
76}
77
78// ── internal helpers ──
79
80pub fn check_drop_status(
81    graph: &AliasGraph,
82    drop_record: &mut Vec<DropRecord>,
83    idx: usize,
84) -> Option<usize> {
85    fetch_drop_info(graph, drop_record, idx);
86    let mut fully_dropped = true;
87    if !drop_record[idx].is_dropped {
88        fully_dropped = false;
89        if !drop_record[idx].has_dropped_field {
90            return None;
91        }
92    }
93    let kind = graph.value_to_slot_idx(idx)
94        .map(|si| graph.pts_graph.slot_kind(si))
95        .unwrap_or(ValueKind::Adt);
96    Some(rate_confidence(kind, fully_dropped))
97}
98
99fn rate_confidence(kind: ValueKind, fully_dropped: bool) -> usize {
100    match (kind, fully_dropped) {
101        (ValueKind::SpecialPtr, _) => 0,
102        (_, true) => 99,
103        (_, false) => 50,
104    }
105}
106
107pub fn make_bug(
108    drop_record: &DropRecord,
109    trigger_info: LocalSpot,
110    span: Span,
111    confidence: usize,
112    bug_type: BugType,
113) -> TyBug {
114    TyBug {
115        drop_spot: drop_record.drop_spot,
116        trigger_info,
117        span,
118        confidence,
119        bug_type,
120    }
121}
122
123// ── drop propagation ──
124
125pub fn push_drop_info(
126    graph: &AliasGraph,
127    drop_record: &mut Vec<DropRecord>,
128    value_idx: usize,
129    drop_spot: LocalSpot,
130) {
131    push_drop_bottom_up(graph, drop_record, value_idx, drop_spot);
132    push_drop_top_down(graph, drop_record, value_idx, drop_spot);
133    push_drop_through_move(graph, drop_record, value_idx, drop_spot);
134}
135
136fn push_drop_through_move(
137    graph: &AliasGraph,
138    drop_record: &mut Vec<DropRecord>,
139    value_idx: usize,
140    drop_spot: LocalSpot,
141) {
142    if let Some(&src) = graph.move_sources.get(&value_idx) {
143        if !drop_record[src].is_dropped {
144            drop_record[src] = DropRecord::new(src, true, drop_spot);
145        }
146    }
147    for (&dest, &src) in graph.move_sources.iter() {
148        if src == value_idx && !drop_record[dest].is_dropped {
149            drop_record[dest] = DropRecord::new(dest, true, drop_spot);
150        }
151    }
152}
153
154fn push_drop_bottom_up(
155    graph: &AliasGraph,
156    drop_record: &mut Vec<DropRecord>,
157    value_idx: usize,
158    drop_spot: LocalSpot,
159) {
160    rap_debug!("push_drop_bottom_up: value_idx = {}", value_idx);
161    let mut father = graph.values[value_idx].father.clone();
162    let mut prop_chain = vec![value_idx];
163    while let Some(father_info) = father {
164        let father_idx = father_info.father_value_id;
165        drop_record[father_idx].has_dropped_field = true;
166        if !drop_record[father_idx].is_dropped {
167            prop_chain.push(father_idx);
168            drop_record[father_idx].prop_chain = prop_chain.clone();
169            drop_record[father_idx].drop_spot = drop_spot;
170        }
171        rap_debug!("{:?}", drop_record[father_idx]);
172        father = graph.values[father_idx].father.clone();
173    }
174}
175
176fn push_drop_top_down(
177    graph: &AliasGraph,
178    drop_record: &mut Vec<DropRecord>,
179    value_idx: usize,
180    drop_spot: LocalSpot,
181) {
182    rap_debug!("push_drop_top_down: value_idx = {}", value_idx);
183    let mut prop_chain = vec![value_idx];
184    for (_field_id, field_value_id) in graph.values[value_idx].fields.clone() {
185        if graph.value_to_slot_idx(field_value_id)
186            .map_or(false, |si| graph.pts_graph.slot_kind(si) == ValueKind::Ref) {
187            continue;
188        }
189        drop_record[field_value_id] = DropRecord::new(field_value_id, true, drop_spot);
190        prop_chain.push(field_value_id);
191        drop_record[field_value_id].prop_chain = prop_chain.clone();
192        rap_debug!("{:?}", drop_record[field_value_id]);
193        push_drop_top_down(graph, drop_record, field_value_id, drop_spot);
194    }
195}
196
197// ── drop fetching ──
198
199fn fetch_drop_info(
200    graph: &AliasGraph,
201    drop_record: &mut Vec<DropRecord>,
202    value_idx: usize,
203) {
204    fetch_drop_from_bottom(graph, drop_record, value_idx);
205    fetch_drop_from_top(graph, drop_record, value_idx);
206    fetch_drop_from_alias(graph, drop_record, value_idx);
207    fetch_drop_from_pointee(graph, drop_record, value_idx);
208}
209
210fn fetch_drop_from_pointee(
211    graph: &AliasGraph,
212    drop_record: &mut Vec<DropRecord>,
213    value_idx: usize,
214) {
215    if !graph.value_is_ptr(value_idx) {
216        return;
217    }
218    if drop_record[value_idx].is_dropped || drop_record[value_idx].has_dropped_field {
219        return;
220    }
221    let Some(slot_idx) = graph.value_to_slot_idx(value_idx) else {
222        return;
223    };
224    let pointees: Vec<usize> = graph.pts_graph.direct_pointees(slot_idx)
225        .filter_map(|loc| match loc {
226            crate::analysis::points_to::slot::AbstractLoc::Slot(s) => {
227                if s.fields.is_empty() && s.local < graph.values.len() {
228                    Some(s.local)
229                } else {
230                    for (v, _) in graph.values.iter().enumerate() {
231                        if let Some(v_slot) = graph.value_to_slot_idx(v) {
232                            if graph.pts_graph.get_slot(v_slot)
233                                .is_some_and(|vs| vs.local == s.local && vs.fields == s.fields)
234                            {
235                                return Some(v);
236                            }
237                        }
238                    }
239                    None
240                }
241            }
242            _ => None,
243        })
244        .collect();
245    for &pointee_vidx in &pointees {
246        if pointee_vidx == value_idx {
247            continue;
248        }
249        check_drop_status(graph, drop_record, pointee_vidx);
250        if drop_record[pointee_vidx].is_dropped || drop_record[pointee_vidx].has_dropped_field {
251            drop_record[value_idx] = drop_record[pointee_vidx].clone();
252            drop_record[value_idx].value_index = value_idx;
253            drop_record[value_idx].prop_chain.push(value_idx);
254            break;
255        }
256    }
257}
258
259fn fetch_drop_from_bottom(
260    graph: &AliasGraph,
261    drop_record: &mut Vec<DropRecord>,
262    value_idx: usize,
263) {
264    rap_debug!("fetch_drop_from_bottom: value_idx = {}", value_idx);
265    for (_field_id, field_value_id) in graph.values[value_idx].fields.clone() {
266        rap_debug!("{:?}", drop_record[field_value_id]);
267        fetch_drop_from_alias(graph, drop_record, field_value_id);
268        if drop_record[field_value_id].is_dropped {
269            push_drop_bottom_up(
270                graph,
271                drop_record,
272                field_value_id,
273                drop_record[field_value_id].drop_spot,
274            );
275            rap_debug!("{:?}", drop_record[value_idx]);
276            break;
277        }
278        fetch_drop_from_bottom(graph, drop_record, field_value_id);
279    }
280    if drop_record[value_idx].is_dropped || drop_record[value_idx].has_dropped_field {
281        return;
282    }
283    fetch_drop_from_pts_fields(graph, drop_record, value_idx);
284}
285
286fn fetch_drop_from_pts_fields(
287    graph: &AliasGraph,
288    drop_record: &mut Vec<DropRecord>,
289    value_idx: usize,
290) {
291    let Some(slot_idx) = graph.value_to_slot_idx(value_idx) else {
292        return;
293    };
294    let base_slot = match graph.pts_graph.get_slot(slot_idx) {
295        Some(s) => s.clone(),
296        None => return,
297    };
298    for i in 0..graph.pts_graph.slot_count() {
299        let Some(child_slot) = graph.pts_graph.get_slot(i) else {
300            continue;
301        };
302        if child_slot.local != base_slot.local || child_slot.fields.len() != base_slot.fields.len() + 1 {
303            continue;
304        }
305        if !child_slot.fields.starts_with(&base_slot.fields) {
306            continue;
307        }
308        let mut found = false;
309        for (v, dr) in drop_record.iter().enumerate() {
310            if !dr.is_dropped {
311                continue;
312            }
313            if let Some(drop_slot) = graph.value_to_slot_idx(v) {
314                if graph.pts_graph.may_alias(i, drop_slot) {
315                    drop_record[value_idx].has_dropped_field = true;
316                    drop_record[value_idx].drop_spot = drop_record[v].drop_spot.clone();
317                    found = true;
318                    break;
319                }
320            }
321        }
322        if found {
323            break;
324        }
325    }
326}
327
328fn fetch_drop_from_top(
329    graph: &AliasGraph,
330    drop_record: &mut Vec<DropRecord>,
331    value_idx: usize,
332) {
333    rap_debug!("fetch_drop_from_top: value_idx = {}", value_idx);
334    let mut father = graph.values[value_idx].father.clone();
335    while let Some(father_info) = father {
336        let father_idx = father_info.father_value_id;
337        fetch_drop_from_alias(graph, drop_record, father_idx);
338        if drop_record[father_idx].is_dropped {
339            push_drop_top_down(
340                graph,
341                drop_record,
342                father_idx,
343                drop_record[father_idx].drop_spot,
344            );
345            rap_debug!("{:?}", drop_record[value_idx]);
346            break;
347        }
348        father = graph.values[father_idx].father.clone();
349    }
350}
351
352fn fetch_drop_from_alias(
353    graph: &AliasGraph,
354    drop_record: &mut Vec<DropRecord>,
355    value_idx: usize,
356) {
357    rap_debug!("fetch_drop_from_alias: value_idx = {}", value_idx);
358    if let Some(aliases) = get_alias_set(graph, value_idx) {
359        for idx in aliases {
360            if drop_record[idx].is_dropped {
361                drop_record[value_idx] = drop_record[idx].clone();
362                drop_record[value_idx].value_index = value_idx;
363                drop_record[value_idx].prop_chain.push(value_idx);
364            }
365        }
366    }
367}
368
369// ── drop clearing ──
370
371fn clear_father_drop(
372    graph: &AliasGraph,
373    drop_record: &mut Vec<DropRecord>,
374    value_idx: usize,
375) {
376    rap_debug!("clear_drop_father: value_idx = {}", value_idx);
377    let mut father = graph.values[value_idx].father.clone();
378    while let Some(father_info) = father {
379        let father_idx = father_info.father_value_id;
380        if !drop_record[father_idx].is_dropped {
381            drop_record[father_idx].clear();
382        }
383        father = graph.values[father_idx].father.clone();
384    }
385}
386
387fn clear_field_drop(
388    graph: &AliasGraph,
389    drop_record: &mut Vec<DropRecord>,
390    value_idx: usize,
391) {
392    rap_debug!("clear_field_drop: value_idx = {}", value_idx);
393    for (_field_id, field_value_id) in graph.values[value_idx].fields.clone() {
394        drop_record[field_value_id].clear();
395        clear_field_drop(graph, drop_record, field_value_id);
396    }
397}
398
399// ── misc ──
400
401fn get_alias_set(graph: &AliasGraph, e: usize) -> Option<Vec<usize>> {
402    graph.get_alias_set(e)
403}