Skip to main content

rapx/check/safedrop/
checks.rs

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