Skip to content

Commit 6e91d13

Browse files
Limit FFI calls by default to explicitly supported ones (rust-lang#2428)
C-FFI is fairly unstable right now and the lack of error handling causes a poor experience when things go wrong. In this PR, we limit its usage to only support CBMC built-in functions that are explicitly included in Kani and alloc functions from kani_lib.c. User's should still be enable to go back to previous behavior by enabling unstable C-FFI support Co-authored-by: Zyad Hassan <[email protected]>
1 parent 8e6267d commit 6e91d13

File tree

16 files changed

+311
-27
lines changed

16 files changed

+311
-27
lines changed

cprover_bindings/src/goto_program/builtin.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub enum BuiltinFn {
1717
Copysignf,
1818
Cos,
1919
Cosf,
20+
Error,
21+
ErrorNoLocation,
2022
Exp,
2123
Exp2,
2224
Exp2f,
@@ -60,6 +62,7 @@ pub enum BuiltinFn {
6062
Sqrtf,
6163
Trunc,
6264
Truncf,
65+
Unlink,
6366
}
6467

6568
impl ToString for BuiltinFn {
@@ -76,6 +79,8 @@ impl ToString for BuiltinFn {
7679
Copysignf => "copysignf",
7780
Cos => "cos",
7881
Cosf => "cosf",
82+
Error => "__error",
83+
ErrorNoLocation => "__errno_location",
7984
Exp => "exp",
8085
Exp2 => "exp2",
8186
Exp2f => "exp2f",
@@ -119,6 +124,7 @@ impl ToString for BuiltinFn {
119124
Sqrtf => "sqrtf",
120125
Trunc => "trunc",
121126
Truncf => "truncf",
127+
Unlink => "unlink",
122128
}
123129
.to_string()
124130
}
@@ -139,6 +145,8 @@ impl BuiltinFn {
139145
Copysignf => vec![Type::float(), Type::float()],
140146
Cos => vec![Type::double()],
141147
Cosf => vec![Type::float()],
148+
Error => vec![],
149+
ErrorNoLocation => vec![],
142150
Exp => vec![Type::double()],
143151
Exp2 => vec![Type::double()],
144152
Exp2f => vec![Type::float()],
@@ -179,6 +187,7 @@ impl BuiltinFn {
179187
Sqrtf => vec![Type::float()],
180188
Trunc => vec![Type::double()],
181189
Truncf => vec![Type::float()],
190+
Unlink => vec![Type::c_char().to_pointer()],
182191
}
183192
}
184193

@@ -195,6 +204,8 @@ impl BuiltinFn {
195204
Copysignf => Type::float(),
196205
Cos => Type::double(),
197206
Cosf => Type::float(),
207+
Error => Type::c_int().to_pointer(),
208+
ErrorNoLocation => Type::c_int().to_pointer(),
198209
Exp => Type::double(),
199210
Exp2 => Type::double(),
200211
Exp2f => Type::float(),
@@ -238,6 +249,7 @@ impl BuiltinFn {
238249
Sqrtf => Type::float(),
239250
Trunc => Type::double(),
240251
Truncf => Type::float(),
252+
Unlink => Type::c_int(),
241253
}
242254
}
243255

@@ -254,6 +266,8 @@ impl BuiltinFn {
254266
Copysignf,
255267
Cos,
256268
Cosf,
269+
Error,
270+
ErrorNoLocation,
257271
Exp,
258272
Exp2,
259273
Exp2f,
@@ -297,6 +311,7 @@ impl BuiltinFn {
297311
Sqrtf,
298312
Trunc,
299313
Truncf,
314+
Unlink,
300315
]
301316
}
302317
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//! This module implements foreign function handling.
4+
//!
5+
//! Kani currently only support CBMC built-in functions that are declared in the `cprover_bindings`
6+
//! crate, and allocation functions defined in `kani_lib.c`.
7+
//!
8+
//! All other functions will be replaced by an unimplemented check, due to current issues with
9+
//! linking and usability unless unstable C-FFI support is enabled.
10+
use std::collections::HashSet;
11+
12+
use crate::codegen_cprover_gotoc::codegen::PropertyClass;
13+
use crate::codegen_cprover_gotoc::GotocCtx;
14+
use crate::kani_middle;
15+
use cbmc::goto_program::{Expr, Location, Stmt, Symbol, Type};
16+
use cbmc::{InternString, InternedString};
17+
use kani_queries::UserInput;
18+
use lazy_static::lazy_static;
19+
use rustc_middle::ty::Instance;
20+
use rustc_target::abi::call::Conv;
21+
use tracing::{debug, trace};
22+
23+
lazy_static! {
24+
/// The list of Rust allocation functions that are declared in the `core::alloc` module
25+
/// but defined by each backend.
26+
/// For our `goto-program` backend, these functions are defined inside `kani_lib.c`.
27+
/// For now, we blindly trust that the definitions in `kani_lib.c` are kept in sync with the
28+
/// declarations from the standard library, provided here:
29+
/// <https://stdrs.dev/nightly/x86_64-unknown-linux-gnu/alloc/alloc/index.html>
30+
static ref RUST_ALLOC_FNS: HashSet<InternedString> = {
31+
HashSet::from([
32+
"__rust_alloc".into(),
33+
"__rust_alloc_zeroed".into(),
34+
"__rust_dealloc".into(),
35+
"__rust_realloc".into(),
36+
])
37+
};
38+
}
39+
40+
impl<'tcx> GotocCtx<'tcx> {
41+
/// Generate the symbol and symbol table entry for foreign items.
42+
///
43+
/// CBMC built-in functions that are supported by Kani are always added to the symbol table, and
44+
/// this function will return them.
45+
///
46+
/// For other foreign items, we declare a shim and add to the list of foreign shims to be
47+
/// handled later.
48+
pub fn codegen_foreign_fn(&mut self, instance: Instance<'tcx>) -> &Symbol {
49+
debug!(?instance, "codegen_foreign_function");
50+
let fn_name = self.symbol_name(instance).intern();
51+
if self.symbol_table.contains(fn_name) {
52+
// Symbol has been added (either a built-in CBMC function or a Rust allocation function).
53+
self.symbol_table.lookup(fn_name).unwrap()
54+
} else if RUST_ALLOC_FNS.contains(&fn_name)
55+
|| (self.is_cffi_enabled() && kani_middle::fn_abi(self.tcx, instance).conv == Conv::C)
56+
{
57+
// Add a Rust alloc lib function as is declared by core.
58+
// When C-FFI feature is enabled, we just trust the rust declaration.
59+
// TODO: Add proper casting and clashing definitions check.
60+
// https://github.com/model-checking/kani/issues/1350
61+
// https://github.com/model-checking/kani/issues/2426
62+
self.ensure(fn_name, |gcx, _| {
63+
let typ = gcx.codegen_ffi_type(instance);
64+
Symbol::function(
65+
fn_name,
66+
typ,
67+
None,
68+
gcx.readable_instance_name(instance),
69+
Location::none(),
70+
)
71+
.with_is_extern(true)
72+
})
73+
} else {
74+
let shim_name = format!("{fn_name}_ffi_shim");
75+
trace!(?shim_name, "codegen_foreign_function");
76+
self.ensure(&shim_name, |gcx, _| {
77+
// Generate a shim with an unsupported C-FFI error message.
78+
let typ = gcx.codegen_ffi_type(instance);
79+
Symbol::function(
80+
&shim_name,
81+
typ,
82+
Some(gcx.codegen_ffi_shim(shim_name.as_str().into(), instance)),
83+
gcx.readable_instance_name(instance),
84+
Location::none(),
85+
)
86+
})
87+
}
88+
}
89+
90+
/// Checks whether C-FFI has been enabled or not.
91+
/// When enabled, we blindly encode the function type as is.
92+
fn is_cffi_enabled(&self) -> bool {
93+
self.queries.get_unstable_features().contains(&"c-ffi".to_string())
94+
}
95+
96+
/// Generate code for a foreign function shim.
97+
fn codegen_ffi_shim(&mut self, shim_name: InternedString, instance: Instance<'tcx>) -> Stmt {
98+
debug!(?shim_name, ?instance, sym=?self.symbol_table.lookup(shim_name), "generate_foreign_shim");
99+
100+
let loc = self.codegen_span(&self.tcx.def_span(instance.def_id()));
101+
let unsupported_check = self.codegen_ffi_unsupported(instance, loc);
102+
Stmt::block(vec![unsupported_check], loc)
103+
}
104+
105+
/// Generate type for the given foreign instance.
106+
fn codegen_ffi_type(&mut self, instance: Instance<'tcx>) -> Type {
107+
let fn_name = self.symbol_name(instance);
108+
let fn_abi = kani_middle::fn_abi(self.tcx, instance);
109+
let loc = self.codegen_span(&self.tcx.def_span(instance.def_id()));
110+
let params = fn_abi
111+
.args
112+
.iter()
113+
.enumerate()
114+
.filter_map(|(idx, arg)| {
115+
(!arg.is_ignore()).then(|| {
116+
let arg_name = format!("{fn_name}::param_{idx}");
117+
let base_name = format!("param_{idx}");
118+
let arg_type = self.codegen_ty(arg.layout.ty);
119+
let sym = Symbol::variable(&arg_name, &base_name, arg_type.clone(), loc)
120+
.with_is_parameter(true);
121+
self.symbol_table.insert(sym);
122+
arg_type.as_parameter(Some(arg_name.into()), Some(base_name.into()))
123+
})
124+
})
125+
.collect();
126+
let ret_type = self.codegen_ty(fn_abi.ret.layout.ty);
127+
128+
if fn_abi.c_variadic {
129+
Type::variadic_code(params, ret_type)
130+
} else {
131+
Type::code(params, ret_type)
132+
}
133+
}
134+
135+
/// Kani does not currently support FFI functions except for built-in CBMC functions.
136+
///
137+
/// This will behave like `codegen_unimplemented_stmt` but print a message that includes
138+
/// the name of the function not supported and the calling convention.
139+
pub fn codegen_ffi_unsupported(&mut self, instance: Instance<'tcx>, loc: Location) -> Stmt {
140+
let fn_name = &self.symbol_name(instance);
141+
debug!(?fn_name, ?loc, "codegen_ffi_unsupported");
142+
143+
// Save this occurrence so we can emit a warning in the compilation report.
144+
let entry = self.unsupported_constructs.entry("foreign function".into()).or_default();
145+
entry.push(loc);
146+
147+
let call_conv = kani_middle::fn_abi(self.tcx, instance).conv;
148+
let msg = format!("call to foreign \"{call_conv:?}\" function `{fn_name}`");
149+
let url = if call_conv == Conv::C {
150+
"https://github.com/model-checking/kani/issues/2423"
151+
} else {
152+
"https://github.com/model-checking/kani/issues/new/choose"
153+
};
154+
self.codegen_assert_assume(
155+
Expr::bool_false(),
156+
PropertyClass::UnsupportedConstruct,
157+
&GotocCtx::unsupported_msg(&msg, Some(url)),
158+
loc,
159+
)
160+
}
161+
}

kani-compiler/src/codegen_cprover_gotoc/codegen/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
mod assert;
88
mod block;
9+
mod foreign_function;
910
mod function;
1011
mod intrinsic;
1112
mod operand;

kani-compiler/src/codegen_cprover_gotoc/codegen/operand.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -743,19 +743,18 @@ impl<'tcx> GotocCtx<'tcx> {
743743
/// sometimes subtly differs from the type that codegen_function_sig returns.
744744
/// This is tracked in <https://github.com/model-checking/kani/issues/1350>.
745745
fn codegen_func_symbol(&mut self, instance: Instance<'tcx>) -> (&Symbol, Type) {
746-
let func = self.symbol_name(instance);
747746
let funct = self.codegen_function_sig(self.fn_sig_of_instance(instance));
748-
// make sure the functions imported from other modules (or externs) are in the symbol table
749-
let sym = self.ensure(&func, |ctx, _| {
750-
Symbol::function(
751-
&func,
752-
funct.clone(),
753-
None,
754-
ctx.readable_instance_name(instance),
755-
Location::none(),
756-
)
757-
.with_is_extern(true)
758-
});
747+
let sym = if self.tcx.is_foreign_item(instance.def_id()) {
748+
// Get the symbol that represents a foreign instance.
749+
self.codegen_foreign_fn(instance)
750+
} else {
751+
// All non-foreign functions should've been declared beforehand.
752+
trace!(func=?instance, "codegen_func_symbol");
753+
let func = self.symbol_name(instance);
754+
self.symbol_table
755+
.lookup(func)
756+
.expect("Function `{func}` should've been declared before usage")
757+
};
759758
(sym, funct)
760759
}
761760

kani-compiler/src/kani_middle/attributes.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,13 @@ use kani_metadata::{CbmcSolver, HarnessAttributes, Stub};
88
use rustc_ast::{AttrKind, Attribute, LitKind, MetaItem, MetaItemKind, NestedMetaItem};
99
use rustc_errors::ErrorGuaranteed;
1010
use rustc_hir::{def::DefKind, def_id::DefId};
11-
use rustc_middle::ty::{self, Instance, TyCtxt};
11+
use rustc_middle::ty::{Instance, TyCtxt};
1212
use rustc_span::Span;
1313
use std::str::FromStr;
1414
use strum_macros::{AsRefStr, EnumString};
1515

16-
use rustc_middle::ty::layout::FnAbiOf;
1716
use tracing::{debug, trace};
1817

19-
use crate::kani_middle::CompilerHelpers;
20-
2118
use super::resolve;
2219

2320
#[derive(Debug, Clone, Copy, AsRefStr, EnumString, PartialEq, Eq, PartialOrd, Ord)]
@@ -227,8 +224,7 @@ fn check_proof_attribute(tcx: TyCtxt, def_id: DefId, proof_attributes: &Vec<&Att
227224
tcx.sess.span_err(span, "the `proof` attribute cannot be applied to generic functions");
228225
} else {
229226
let instance = Instance::mono(tcx, def_id);
230-
let helper = CompilerHelpers { tcx };
231-
if !helper.fn_abi_of_instance(instance, ty::List::empty()).args.is_empty() {
227+
if !super::fn_abi(tcx, instance).args.is_empty() {
232228
tcx.sess.span_err(span, "functions used as harnesses cannot have any arguments");
233229
}
234230
}

kani-compiler/src/kani_middle/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ use rustc_hir::{def::DefKind, def_id::LOCAL_CRATE};
1010
use rustc_middle::mir::mono::MonoItem;
1111
use rustc_middle::span_bug;
1212
use rustc_middle::ty::layout::{
13-
FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers,
14-
TyAndLayout,
13+
FnAbiError, FnAbiOf, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError,
14+
LayoutOfHelpers, TyAndLayout,
1515
};
16-
use rustc_middle::ty::{self, Ty, TyCtxt};
16+
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
1717
use rustc_span::source_map::respan;
1818
use rustc_span::Span;
1919
use rustc_target::abi::call::FnAbi;
@@ -72,6 +72,12 @@ pub fn check_reachable_items(tcx: TyCtxt, queries: &QueryDb, items: &[MonoItem])
7272
tcx.sess.abort_if_errors();
7373
}
7474

75+
/// Get the FnAbi of a given instance with no extra variadic arguments.
76+
pub fn fn_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> &'tcx FnAbi<'tcx, Ty<'tcx>> {
77+
let helper = CompilerHelpers { tcx };
78+
helper.fn_abi_of_instance(instance, ty::List::empty())
79+
}
80+
7581
struct CompilerHelpers<'tcx> {
7682
tcx: TyCtxt<'tcx>,
7783
}

kani-driver/src/cbmc_property_renderer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,7 @@ pub fn format_result(
410410
result_str.push_str(
411411
"** WARNING: A Rust construct that is not currently supported \
412412
by Kani was found to be reachable. Check the results for \
413-
more details.",
413+
more details.\n",
414414
);
415415
}
416416
if has_unwinding_assertion_failures(properties) {

tests/cargo-kani/libc/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright Kani Contributors
2+
# SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
[package]
5+
name = "libc_checks"
6+
version = "0.1.0"
7+
edition = "2021"
8+
description = "Test integration with libc"
9+
10+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11+
12+
[dependencies]
13+
libc = "0.2"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
warning: Found the following unsupported constructs:\
2+
- foreign function
3+
Failed Checks: call to foreign "C" function `pthread_key_create` is not currently supported by Kani. Please post your example at https://github.com/model-checking/kani/issues/2423

tests/cargo-kani/libc/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
4+
pub mod pthread_key_create;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
//! Check that Kani doesn't crash if it invokes calls to pthread_key_create.
5+
//!
6+
//! The declaration of `pthread_key_create` in the libc crate differs in type from the C one.
7+
//! The rust libc crate uses `Option<unsafe extern "C" fn(*mut u8)>` to represent the optional
8+
//! destructor, while the C one takes a function pointer.
9+
//!
10+
//! See <https://github.com/model-checking/kani/issues/1781> for more details.
11+
//!
12+
//! Until we add full support to C-FFI, functions that are not explicitly declared in the Kani
13+
//! compiler setup will be codegen as unsupported.
14+
//!
15+
//! This test ensures that a harness only fails during verification if the call is reachable.
16+
use libc;
17+
18+
#[kani::proof]
19+
pub fn check_create() {
20+
let mut key = 0;
21+
let _res = unsafe { libc::pthread_key_create(&mut key, None) };
22+
}

0 commit comments

Comments
 (0)