Skip to content

Commit 31ba692

Browse files
authored
Add safe snprintf rule (#275)
The snprintf safe rule uses the new `libcc2rs::format_c` function. The rule is: ```rs 1. let s = libcc2rs::format_c(&a2.to_rust_string(), va); 2. let b = s.as_bytes(); 3. if a1 > 0 { 4. let n = min(b.len(), a1 - 1); 5. a0.with_slice_mut(n + 1, ... copy n+1 + NUL byte in a0) 6. } 7. b.len() ``` Initially, I planned to use `format!` instead of `libcc2rs::format_c` at line 1 because that's definitely more idiomatic. However we need to support runtime format strings, for example: ```c char fmt[] = "%d", snprintf(buf, n, fmt, 123); ``` The `format!` macro in rust does not support runtime format strings. It only works with compile time format strings. For this reason I created `libcc2rs::format_c` which internally uses the [sprintf crate](https://docs.rs/sprintf/latest/sprintf/index.html). The role of `format_c` is to validate the format string, convert each `libcc2rs::VaArg` to `sprintf::Printf` then do the actual formatting.
1 parent 9cfdb15 commit 31ba692

7 files changed

Lines changed: 790 additions & 7 deletions

File tree

‎libcc2rs/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ libcc2rs-macros = { path = "../libcc2rs-macros", version = "0.1.0" }
88
libc = "0.2"
99
nix = { version = "0.30", features = ["socket", "net", "fs", "poll", "time", "user", "dir", "term", "event", "hostname"] }
1010
jiff = "0.2"
11+
sprintf = "0.4"

‎libcc2rs/src/format.rs‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) 2022-present INESC-ID.
2+
// Distributed under the MIT license that can be found in the LICENSE file.
3+
4+
use sprintf::parser::{ConversionType, FormatElement, parse_format_string};
5+
use sprintf::{Printf, vsprintfp};
6+
7+
use crate::va_args::{VaArg, VaArgGet};
8+
9+
pub fn format_c(fmt: &str, va: &[VaArg]) -> String {
10+
let elements = match parse_format_string(fmt) {
11+
Ok(elements) => elements,
12+
Err(e) => panic!("format_c: cannot parse {fmt:?}: {e:?}"),
13+
};
14+
let mut args: Vec<Box<dyn Printf>> = Vec::new();
15+
let mut pos = 0;
16+
for element in &elements {
17+
if let FormatElement::Format(spec) = element {
18+
if spec.conversion_type == ConversionType::PercentSign {
19+
continue;
20+
}
21+
let arg = &va[pos];
22+
args.push(match spec.conversion_type {
23+
ConversionType::DecInt => match arg {
24+
VaArg::Int(v) => Box::new(*v),
25+
VaArg::UInt(v) => Box::new(*v),
26+
VaArg::Long(v) => Box::new(*v),
27+
VaArg::ULong(v) => Box::new(*v),
28+
_ => panic!("format_c: integer conversion expects an integer argument"),
29+
},
30+
ConversionType::OctInt
31+
| ConversionType::HexIntLower
32+
| ConversionType::HexIntUpper => match arg {
33+
VaArg::Int(v) => Box::new(*v as u32),
34+
VaArg::UInt(v) => Box::new(*v),
35+
VaArg::Long(v) => Box::new(*v as u64),
36+
VaArg::ULong(v) => Box::new(*v),
37+
VaArg::Ptr(p) => Box::new(p.to_int()),
38+
VaArg::RawPtr(v) => Box::new(*v as usize),
39+
VaArg::Double(_) => {
40+
panic!("format_c: integer conversion expects an integer argument")
41+
}
42+
},
43+
ConversionType::Char => Box::new(i32::get(arg) as u8 as char),
44+
ConversionType::String => match arg {
45+
VaArg::Ptr(v) => Box::new(v.reinterpret_cast::<u8>().to_rust_string()),
46+
_ => panic!("format_c: %s expects a string argument"),
47+
},
48+
ConversionType::DecFloatLower
49+
| ConversionType::DecFloatUpper
50+
| ConversionType::SciFloatLower
51+
| ConversionType::SciFloatUpper
52+
| ConversionType::CompactFloatLower
53+
| ConversionType::CompactFloatUpper => Box::new(f64::get(arg)),
54+
ConversionType::PercentSign => panic!("format_c: %% consumes no argument"),
55+
});
56+
pos += 1;
57+
}
58+
}
59+
let refs: Vec<&dyn Printf> = args.iter().map(|arg| arg.as_ref()).collect();
60+
match vsprintfp(&elements, &refs) {
61+
Ok(s) => s,
62+
Err(e) => panic!("format_c: cannot format {fmt:?}: {e:?}"),
63+
}
64+
}

‎libcc2rs/src/lib.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,7 @@ pub use compat::*;
3737
mod va_args;
3838
pub use va_args::*;
3939

40+
mod format;
41+
pub use format::*;
42+
4043
pub use libcc2rs_macros::{goto, goto_block, switch};

‎rules/stdio/tgt_refcount.rs‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,16 @@ fn f19(a0: Ptr<::std::fs::File>, a1: i64, a2: i32) -> i32 {
214214
}
215215

216216
fn f21(a0: Ptr<u8>, a1: usize, a2: Ptr<u8>, va: &[VaArg]) -> i32 {
217-
panic!(
218-
"snprintf is not supported in the refcount model (buf_is_null={}, size={}, fmt={:?}, varargs={})",
219-
a0.is_null(),
220-
a1,
221-
a2.to_rust_string(),
222-
va.len()
223-
)
217+
let __s = libcc2rs::format_c(&a2.to_rust_string(), va);
218+
let __b = __s.as_bytes();
219+
if a1 > 0 {
220+
let __n = ::std::cmp::min(__b.len(), a1 - 1);
221+
a0.with_slice_mut(__n + 1, |__dst| {
222+
__dst[..__n].copy_from_slice(&__b[..__n]);
223+
__dst[__n] = 0;
224+
});
225+
}
226+
__b.len() as i32
224227
}
225228

226229
fn f22(a0: Ptr<u8>, a1: Ptr<u8>) -> i32 {

0 commit comments

Comments
 (0)