Skip to content

Commit 04c5a74

Browse files
authored
Mitigate issue with dangling pointers and memcmp (rust-lang#1526)
1 parent ec3026d commit 04c5a74

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

kani-compiler/src/codegen_cprover_gotoc/overrides/hooks.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,72 @@ impl<'tcx> GotocHook<'tcx> for SliceFromRawPart {
298298
}
299299
}
300300

301+
/// This hook intercepts calls to `memcmp` and skips CBMC's pointer checks if the number of bytes to be compared is zero.
302+
/// See issue <https://github.com/model-checking/kani/issues/1489>
303+
///
304+
/// This compiles `memcmp(first, second, count)` to:
305+
/// ```c
306+
/// count_var = count;
307+
/// first_var = first;
308+
/// second_var = second;
309+
/// count_var == 0 && first_var != NULL && second_var != NULL ? 0 : memcmp(first_var, second_var, count_var)
310+
/// ```
311+
pub struct MemCmp;
312+
313+
impl<'tcx> GotocHook<'tcx> for MemCmp {
314+
fn hook_applies(&self, tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> bool {
315+
let name = with_no_trimmed_paths!(tcx.def_path_str(instance.def_id()));
316+
name == "core::slice::cmp::memcmp" || name == "std::slice::cmp::memcmp"
317+
}
318+
319+
fn handle(
320+
&self,
321+
tcx: &mut GotocCtx<'tcx>,
322+
instance: Instance<'tcx>,
323+
mut fargs: Vec<Expr>,
324+
assign_to: Place<'tcx>,
325+
target: Option<BasicBlock>,
326+
span: Option<Span>,
327+
) -> Stmt {
328+
let loc = tcx.codegen_span_option(span);
329+
let target = target.unwrap();
330+
let first = fargs.remove(0);
331+
let second = fargs.remove(0);
332+
let count = fargs.remove(0);
333+
let (count_var, count_decl) = tcx.decl_temp_variable(count.typ().clone(), Some(count), loc);
334+
let (first_var, first_decl) = tcx.decl_temp_variable(first.typ().clone(), Some(first), loc);
335+
let (second_var, second_decl) =
336+
tcx.decl_temp_variable(second.typ().clone(), Some(second), loc);
337+
let is_count_zero = count_var.clone().is_zero();
338+
// We have to ensure that the pointers are valid even if we're comparing zero bytes.
339+
// According to Rust's current definition (see https://github.com/model-checking/kani/issues/1489),
340+
// this means they have to be non-null and aligned.
341+
// But alignment is automatically satisfied because `memcmp` takes `*const u8` pointers.
342+
let is_first_ok = first_var.clone().is_nonnull();
343+
let is_second_ok = second_var.clone().is_nonnull();
344+
let should_skip_pointer_checks = is_count_zero.and(is_first_ok).and(is_second_ok);
345+
let place_expr =
346+
unwrap_or_return_codegen_unimplemented_stmt!(tcx, tcx.codegen_place(&assign_to))
347+
.goto_expr;
348+
let rhs = should_skip_pointer_checks.ternary(
349+
Expr::int_constant(0, place_expr.typ().clone()), // zero bytes are always equal (as long as pointers are nonnull and aligned)
350+
tcx.codegen_func_expr(instance, span.as_ref())
351+
.call(vec![first_var, second_var, count_var]),
352+
);
353+
let code = place_expr.assign(rhs, loc).with_location(loc);
354+
Stmt::block(
355+
vec![
356+
count_decl,
357+
first_decl,
358+
second_decl,
359+
code,
360+
Stmt::goto(tcx.current_fn().find_label(&target), loc),
361+
],
362+
loc,
363+
)
364+
}
365+
}
366+
301367
pub fn fn_hooks<'tcx>() -> GotocHooks<'tcx> {
302368
GotocHooks {
303369
hooks: vec![
@@ -308,6 +374,7 @@ pub fn fn_hooks<'tcx>() -> GotocHooks<'tcx> {
308374
Rc::new(Nondet),
309375
Rc::new(RustAlloc),
310376
Rc::new(SliceFromRawPart),
377+
Rc::new(MemCmp),
311378
],
312379
}
313380
}

tests/kani/MemCmpCpyZero/issue1489.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
// Regression test for https://github.com/model-checking/kani/issues/1489
5+
// Tests that memcmp and memcpy can be called with dangling pointers if the count is zero.
6+
7+
#[kani::proof]
8+
fn main() {
9+
// Vec::new() creates a dangling pointer
10+
assert_eq!(Vec::<u8>::new(), Vec::<u8>::new());
11+
}

0 commit comments

Comments
 (0)