rapx/verify/property_checker/
cstr.rs1use rustc_middle::mir::{Local, Operand, Rvalue, StatementKind};
4use z3::{Solver, ast::{Ast, Int}};
5
6use crate::verify::{
7 contract::{ContractExpr, Property, PropertyArg},
8 report::CheckResult,
9};
10use crate::helpers::mir_scan::Checkpoint;
11use crate::verify::vm::state::{AllocId, VmState};
12
13use super::PropertyChecker;
14
15impl PropertyChecker {
16 pub(super) fn check_valid_cstr<'ctx, 'tcx>(&self, vm_state: &VmState<'ctx, 'tcx>, solver: &Solver<'ctx>,
19 checkpoint: &Checkpoint<'tcx>, property: &Property<'tcx>) -> CheckResult
20 {
21 let value = self.target_value(vm_state, checkpoint, property)
22 .or_else(|| {
23 checkpoint.destination.and_then(|d| vm_state.local_value(d).cloned())
24 });
25 let Some(value) = value else { return CheckResult::Unknown };
26
27 if let Some(alloc_id) = value.provenance_alloc_id() {
29 if vm_state.alloc(alloc_id).dead {
30 return CheckResult::Failed;
31 }
32
33 let mut root_id = alloc_id;
40 while let Some(parent_id) = vm_state.alloc(root_id).parent {
41 root_id = parent_id;
42 }
43 if vm_state.alloc(alloc_id).nul_terminated
44 || vm_state.alloc(root_id).nul_terminated
45 {
46 return CheckResult::Proved;
47 }
48
49 let alloc_size = vm_state.allocation_size(alloc_id).cloned();
50
51 let n_term = property.args().get(1).and_then(|a| match a {
58 PropertyArg::Expr(ContractExpr::Const(_)) => None,
59 a => self.resolve_arg_term(vm_state, checkpoint, a),
60 });
61 let buffer_size = n_term.or(alloc_size);
62
63 let start_offset = value.provenance.as_ref()
65 .and_then(|p| p.offset.as_u64())
66 .map(|v| v as usize)
67 .unwrap_or(0);
68
69 if let Some(r) = self.check_valid_cstr_from_known_nul(vm_state, alloc_id, start_offset) {
71 return r;
72 }
73
74 if let Some(size) = buffer_size {
76 if let Some(r) = self.check_valid_cstr_from_byte_values(vm_state, solver, alloc_id, &size) {
77 return r;
78 }
79 }
80 }
81
82 if let Some(r) = self.check_valid_cstr_from_mir_constants(vm_state, checkpoint, property) {
85 return r;
86 }
87
88 let is_strict = checkpoint.callee.as_ref().map_or(false, |callee| {
92 let name = vm_state.tcx.def_path_str(*callee);
93 crate::helpers::api_classify::is_cstr_strict_constructor(&name)
94 });
95 if is_strict {
96 CheckResult::Unknown
97 } else {
98 CheckResult::Proved
99 }
100 }
101
102 fn check_valid_cstr_from_known_nul<'ctx, 'tcx>(
108 &self,
109 vm_state: &VmState<'ctx, 'tcx>,
110 alloc_id: AllocId,
111 start_offset: usize,
112 ) -> Option<CheckResult> {
113 let known_offsets: Vec<usize> = vm_state.alloc_nul_offsets(alloc_id)
115 .into_iter()
116 .chain(vm_state.alloc_non_nul_offsets(alloc_id))
117 .collect();
118
119 if known_offsets.is_empty() {
120 return None; }
122
123 let max_known = known_offsets.iter().max().copied().unwrap_or(0);
124
125 let nul_offsets: Vec<usize> = vm_state.alloc_nul_offsets(alloc_id)
127 .into_iter()
128 .filter(|off| *off >= start_offset && *off <= max_known)
129 .collect();
130
131 if nul_offsets.is_empty() {
132 if let Some(size) = vm_state.allocation_size(alloc_id) {
134 if let Some(size_val) = size.as_u64() {
135 if max_known + 1 < size_val as usize {
136 return None;
137 }
138 }
139 }
140 return Some(CheckResult::Failed);
141 }
142
143 let min_nul = nul_offsets.iter().min().copied().unwrap_or(0);
145
146 for off in start_offset..min_nul {
148 if vm_state.is_byte_nul(alloc_id, off) {
149 return Some(CheckResult::Failed);
151 }
152 if !vm_state.is_byte_non_nul(alloc_id, off) {
153 return None;
155 }
156 }
157
158 if nul_offsets.len() > 1 && min_nul < max_known {
161 return Some(CheckResult::Failed);
162 }
163
164 Some(CheckResult::Proved)
167 }
168
169 fn check_valid_cstr_from_byte_values<'ctx, 'tcx>(
172 &self,
173 vm_state: &VmState<'ctx, 'tcx>,
174 solver: &Solver<'ctx>,
175 alloc_id: AllocId,
176 alloc_size: &Int<'ctx>,
177 ) -> Option<CheckResult> {
178 let byte_pairs = vm_state.alloc_byte_values(alloc_id);
179 if byte_pairs.is_empty() {
180 return None;
181 }
182
183 let zero = Int::from_u64(vm_state.ctx, 0);
184 let size_u64 = alloc_size.as_u64();
185
186 if size_u64.is_none() {
187 return None; }
189
190 for &(nul_off, nul_term) in &byte_pairs {
191 solver.push();
192 solver.assert(&nul_term._eq(&zero));
193
194 for &(off, term) in &byte_pairs {
195 if off < nul_off {
196 solver.assert(&term._eq(&zero).not());
197 }
198 }
199
200 let r = solver.check();
201 solver.pop(1);
202
203 if r == z3::SatResult::Sat {
204 let mut interior_safe = true;
205 for &(off, term) in &byte_pairs {
206 if off < nul_off {
207 solver.push();
208 solver.assert(&term._eq(&zero));
209 let inner = solver.check();
210 solver.pop(1);
211 if inner != z3::SatResult::Unsat {
212 interior_safe = false;
213 break;
214 }
215 }
216 }
217 if interior_safe {
218 return Some(CheckResult::Proved);
219 }
220 }
221 }
222
223 let has_nul_in_tracked = byte_pairs.iter().any(|&(_, term)| {
226 solver.push();
227 solver.assert(&term._eq(&zero));
228 let r = solver.check();
229 solver.pop(1);
230 r == z3::SatResult::Sat
231 });
232
233 if !has_nul_in_tracked {
234 let last_off = byte_pairs.last().map(|(off, _)| *off).unwrap_or(0);
235 if let Some(size) = size_u64 {
236 if last_off + 1 >= size as usize {
237 return Some(CheckResult::Failed);
238 }
239 }
240 }
241
242 None
243 }
244
245 fn check_valid_cstr_nul_store<'tcx>(
250 vm_state: &VmState<'_, 'tcx>,
251 checkpoint: &Checkpoint<'tcx>,
252 ) -> Option<CheckResult> {
253 let target_local = checkpoint.args.get(0).and_then(|op| match op {
254 Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => Some(p.local),
255 _ => None,
256 })?;
257 let body = vm_state.body;
258 let tcx = vm_state.tcx;
259
260 let parents = crate::verify::valid_cstr_util::body_parents(tcx, body);
262 let root = crate::verify::valid_cstr_util::resolve_through_casts(
263 body,
264 crate::verify::valid_cstr_util::follow_parents(&parents, target_local),
265 );
266
267 let mut buffer_locals: rustc_hash::FxHashSet<Local> = rustc_hash::FxHashSet::default();
268 let mut seen = rustc_hash::FxHashSet::default();
269 let mut work = vec![root];
270 while let Some(local) = work.pop() {
271 if !seen.insert(local) {
272 continue;
273 }
274 for data in body.basic_blocks.iter() {
275 for stmt in &data.statements {
276 let StatementKind::Assign(assign) = &stmt.kind else { continue };
277 let (target, rvalue) = &**assign;
278 if target.local != local || !target.projection.is_empty() { continue; }
279 if let Rvalue::Ref(_, _, place) = rvalue {
280 buffer_locals.insert(place.local);
281 }
282 #[cfg(rapx_rvalue_use_with_retag)]
283 if let Rvalue::Use(Operand::Copy(p) | Operand::Move(p), _) = rvalue {
284 work.push(p.local);
285 }
286 #[cfg(not(rapx_rvalue_use_with_retag))]
287 if let Rvalue::Use(Operand::Copy(p) | Operand::Move(p)) = rvalue {
288 work.push(p.local);
289 }
290 if let Rvalue::Cast(_, Operand::Copy(p) | Operand::Move(p), _) = rvalue {
291 if p.projection.is_empty() { work.push(p.local); }
292 }
293 }
294 }
295 }
296
297 let mut nul_store_count = 0u32;
298 for data in body.basic_blocks.iter() {
299 for stmt in &data.statements {
300 let StatementKind::Assign(assign) = &stmt.kind else { continue };
301 let (target, rvalue) = &**assign;
302 let target_root = crate::verify::valid_cstr_util::follow_parents(&parents, target.local);
303 if target_root != root && !buffer_locals.contains(&target_root) {
304 continue;
305 }
306 if target.projection.is_empty() {
307 continue;
308 }
309 #[cfg(rapx_rvalue_use_with_retag)]
310 let Rvalue::Use(Operand::Constant(c), _) = rvalue else { continue };
311 #[cfg(not(rapx_rvalue_use_with_retag))]
312 let Rvalue::Use(Operand::Constant(c)) = rvalue else { continue };
313 if c.const_.try_to_scalar_int()
314 .map_or(false, |s| s.to_uint(s.size()) == 0)
315 {
316 nul_store_count += 1;
317 }
318 }
319 }
320
321 if nul_store_count == 1 {
322 Some(CheckResult::Proved)
323 } else if nul_store_count > 1 {
324 Some(CheckResult::Failed)
325 } else {
326 None
327 }
328 }
329
330 fn check_valid_cstr_from_mir_constants<'ctx, 'tcx>(
334 &self,
335 vm_state: &VmState<'ctx, 'tcx>,
336 checkpoint: &Checkpoint<'tcx>,
337 _property: &Property<'tcx>,
338 ) -> Option<CheckResult> {
339 let target_local = checkpoint.args.get(0).and_then(|op| match op {
340 Operand::Copy(p) | Operand::Move(p) if p.projection.is_empty() => Some(p.local),
341 _ => None,
342 })?;
343
344 let body = vm_state.body;
345 let tcx = vm_state.tcx;
346
347 let all_bytes = crate::verify::valid_cstr_util::collect_all_const_bytes_worklist(tcx, body, target_local);
349 if !all_bytes.is_empty() {
350 let any_invalid = all_bytes.iter().any(|bytes| {
351 !(bytes.last() == Some(&0) && !bytes[..bytes.len().saturating_sub(1)].contains(&0))
352 });
353 if any_invalid {
354 return Some(CheckResult::Failed);
355 }
356 let all_valid = all_bytes.iter().all(|bytes| {
357 bytes.last() == Some(&0) && !bytes[..bytes.len().saturating_sub(1)].contains(&0)
358 });
359 if all_valid {
360 return Some(CheckResult::Proved);
361 }
362 }
363
364 if let Some(bytes) = crate::verify::valid_cstr_util::const_bytes_for_local(tcx, body, target_local) {
366 let valid = bytes.last() == Some(&0) && !bytes[..bytes.len().saturating_sub(1)].contains(&0);
367 return if valid { Some(CheckResult::Proved) } else { Some(CheckResult::Failed) };
368 }
369
370 if let Some(r) = Self::check_valid_cstr_nul_store(vm_state, checkpoint) {
373 return Some(r);
374 }
375
376 None
377 }
378}