Skip to content

Commit a2889f3

Browse files
committed
Add Rust format string provider API
1 parent 33ed90f commit a2889f3

3 files changed

Lines changed: 317 additions & 0 deletions

File tree

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//! APIs for resolving argument types described by format strings.
2+
3+
use binaryninjacore_sys::*;
4+
use std::ffi::{c_char, c_void};
5+
use std::fmt::Debug;
6+
use std::ptr::NonNull;
7+
8+
use crate::confidence::Conf;
9+
use crate::platform::Platform;
10+
use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref};
11+
use crate::string::{raw_to_string, BnString, IntoCStr};
12+
use crate::types::Type;
13+
14+
/// A custom provider that validates format strings and resolves their argument types.
15+
///
16+
/// Providers are registered for the lifetime of the process and may be invoked from multiple
17+
/// analysis threads.
18+
pub trait CustomFormatStringResolutionProvider: Send + Sync + 'static {
19+
/// Resolve the argument types described by `format` for `platform`.
20+
///
21+
/// Return `None` when the string is not valid for this provider. A valid string with no
22+
/// arguments is represented by `Some(Vec::new())`. Each returned type retains its individual
23+
/// confidence value.
24+
fn is_valid(&self, format: &str, platform: Option<&Platform>) -> Option<Vec<Conf<Ref<Type>>>>;
25+
}
26+
27+
/// A registered format string resolution provider.
28+
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
29+
#[repr(transparent)]
30+
pub struct FormatStringResolutionProvider {
31+
handle: NonNull<BNFormatStringResolutionProvider>,
32+
}
33+
34+
impl FormatStringResolutionProvider {
35+
pub(crate) unsafe fn from_raw(handle: NonNull<BNFormatStringResolutionProvider>) -> Self {
36+
Self { handle }
37+
}
38+
39+
/// Register a custom format string resolution provider.
40+
///
41+
/// The provider is retained for the lifetime of the process.
42+
pub fn register<P>(name: &str, provider: P) -> Self
43+
where
44+
P: CustomFormatStringResolutionProvider,
45+
{
46+
let name = name.to_cstr();
47+
// The core registry has no unregister operation, so the callback context must remain valid
48+
// for the lifetime of the process.
49+
let provider = Box::leak(Box::new(provider));
50+
let mut callbacks = BNFormatStringResolutionProviderCallbacks {
51+
context: provider as *mut P as *mut c_void,
52+
isValid: Some(cb_is_valid::<P>),
53+
freeTypeList: Some(cb_free_type_list),
54+
};
55+
let result =
56+
unsafe { BNRegisterFormatStringResolutionProvider(name.as_ptr(), &mut callbacks) };
57+
let handle =
58+
NonNull::new(result).expect("failed to register format string resolution provider");
59+
unsafe { Self::from_raw(handle) }
60+
}
61+
62+
/// Retrieve all registered format string resolution providers.
63+
pub fn all() -> Array<Self> {
64+
let mut count = 0;
65+
let result = unsafe { BNGetFormatStringResolutionProviderList(&mut count) };
66+
assert!(!result.is_null());
67+
unsafe { Array::new(result, count, ()) }
68+
}
69+
70+
/// Retrieve a registered format string resolution provider by name.
71+
pub fn by_name(name: &str) -> Option<Self> {
72+
let name = name.to_cstr();
73+
let result = unsafe { BNGetFormatStringResolutionProviderByName(name.as_ptr()) };
74+
NonNull::new(result).map(|handle| unsafe { Self::from_raw(handle) })
75+
}
76+
77+
/// Return the provider's registration name.
78+
pub fn name(&self) -> String {
79+
let result = unsafe { BNGetFormatStringResolutionProviderName(self.handle.as_ptr()) };
80+
assert!(!result.is_null());
81+
unsafe { BnString::into_string(result) }
82+
}
83+
84+
/// Resolve the argument types described by `format` for `platform`.
85+
///
86+
/// Return `None` when the string is not valid for this provider. A valid string with no
87+
/// arguments is represented by `Some(Vec::new())`.
88+
pub fn is_valid(
89+
&self,
90+
format: &str,
91+
platform: Option<&Platform>,
92+
) -> Option<Vec<Conf<Ref<Type>>>> {
93+
let format = format.to_cstr();
94+
let mut types = std::ptr::null_mut();
95+
let mut count = 0;
96+
let valid = unsafe {
97+
BNFormatStringResolutionProviderIsValid(
98+
self.handle.as_ptr(),
99+
format.as_ptr(),
100+
platform.map_or(std::ptr::null_mut(), |platform| platform.handle),
101+
&mut types,
102+
&mut count,
103+
)
104+
};
105+
106+
if !valid {
107+
if !types.is_null() {
108+
unsafe { BNFreeTypeWithConfidenceList(types, count) };
109+
}
110+
return None;
111+
}
112+
113+
if count == 0 {
114+
if !types.is_null() {
115+
unsafe { BNFreeTypeWithConfidenceList(types, count) };
116+
}
117+
return Some(Vec::new());
118+
}
119+
120+
if types.is_null() {
121+
return None;
122+
}
123+
124+
let result = unsafe { std::slice::from_raw_parts(types, count) }
125+
.iter()
126+
.map(Conf::<Ref<Type>>::from_raw)
127+
.collect();
128+
unsafe { BNFreeTypeWithConfidenceList(types, count) };
129+
Some(result)
130+
}
131+
}
132+
133+
impl Debug for FormatStringResolutionProvider {
134+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135+
f.debug_struct("FormatStringResolutionProvider")
136+
.field("name", &self.name())
137+
.finish()
138+
}
139+
}
140+
141+
unsafe impl Send for FormatStringResolutionProvider {}
142+
unsafe impl Sync for FormatStringResolutionProvider {}
143+
144+
impl CoreArrayProvider for FormatStringResolutionProvider {
145+
type Raw = *mut BNFormatStringResolutionProvider;
146+
type Context = ();
147+
type Wrapped<'a> = Self;
148+
}
149+
150+
unsafe impl CoreArrayProviderInner for FormatStringResolutionProvider {
151+
unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) {
152+
BNFreeFormatStringResolutionProviderList(raw);
153+
}
154+
155+
unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> {
156+
let handle =
157+
NonNull::new(*raw).expect("format string resolution provider list contained null");
158+
Self::from_raw(handle)
159+
}
160+
}
161+
162+
unsafe extern "C" fn cb_is_valid<P>(
163+
ctxt: *mut c_void,
164+
format: *const c_char,
165+
platform: *mut BNPlatform,
166+
types: *mut *mut BNTypeWithConfidence,
167+
count: *mut usize,
168+
) -> bool
169+
where
170+
P: CustomFormatStringResolutionProvider,
171+
{
172+
ffi_wrap!("CustomFormatStringResolutionProvider::is_valid", unsafe {
173+
if types.is_null() || count.is_null() {
174+
return false;
175+
}
176+
*types = std::ptr::null_mut();
177+
*count = 0;
178+
179+
let Some(format) = raw_to_string(format) else {
180+
return false;
181+
};
182+
let provider = &*(ctxt as *const P);
183+
let platform = NonNull::new(platform).map(|handle| Platform::from_raw(handle.as_ptr()));
184+
let Some(result) = provider.is_valid(&format, platform.as_ref()) else {
185+
return false;
186+
};
187+
188+
let raw_types: Box<[BNTypeWithConfidence]> = result
189+
.into_iter()
190+
.map(Conf::<Ref<Type>>::into_raw)
191+
.collect();
192+
*count = raw_types.len();
193+
if raw_types.is_empty() {
194+
true
195+
} else {
196+
*types = Box::leak(raw_types).as_mut_ptr();
197+
true
198+
}
199+
})
200+
}
201+
202+
unsafe extern "C" fn cb_free_type_list(
203+
_ctxt: *mut c_void,
204+
types: *mut BNTypeWithConfidence,
205+
count: usize,
206+
) {
207+
ffi_wrap!(
208+
"CustomFormatStringResolutionProvider::free_type_list",
209+
unsafe {
210+
if types.is_null() {
211+
return;
212+
}
213+
let types = Box::from_raw(std::ptr::slice_from_raw_parts_mut(types, count));
214+
for ty in types {
215+
Conf::<Ref<Type>>::free_raw(ty);
216+
}
217+
}
218+
)
219+
}

rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub mod external_library;
5353
pub mod file_accessor;
5454
pub mod file_metadata;
5555
pub mod flowgraph;
56+
pub mod format_string_resolution_provider;
5657
pub mod function;
5758
pub mod function_recognizer;
5859
pub mod headless;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use binaryninja::confidence::Conf;
2+
use binaryninja::format_string_resolution_provider::{
3+
CustomFormatStringResolutionProvider, FormatStringResolutionProvider,
4+
};
5+
use binaryninja::headless::Session;
6+
use binaryninja::platform::Platform;
7+
use binaryninja::rc::Ref;
8+
use binaryninja::types::Type;
9+
use serial_test::serial;
10+
11+
struct TestProvider;
12+
13+
impl CustomFormatStringResolutionProvider for TestProvider {
14+
fn is_valid(&self, format: &str, platform: Option<&Platform>) -> Option<Vec<Conf<Ref<Type>>>> {
15+
match format {
16+
"platform" => {
17+
let platform = platform?;
18+
Some(vec![
19+
Conf::new(Type::int(platform.address_size(), true), 173),
20+
Conf::new(Type::float(8), 91),
21+
])
22+
}
23+
"empty" => Some(Vec::new()),
24+
_ => None,
25+
}
26+
}
27+
}
28+
29+
#[test]
30+
#[serial]
31+
fn register_list_and_lookup_provider() {
32+
let _session = Session::new().expect("Failed to initialize session");
33+
let provider = FormatStringResolutionProvider::register(
34+
"RustFormatStringResolutionProvider.List",
35+
TestProvider,
36+
);
37+
assert_eq!(provider.name(), "RustFormatStringResolutionProvider.List");
38+
39+
let provider =
40+
FormatStringResolutionProvider::by_name("RustFormatStringResolutionProvider.List")
41+
.expect("registered provider is available by name");
42+
assert_eq!(provider.name(), "RustFormatStringResolutionProvider.List");
43+
assert!(FormatStringResolutionProvider::all()
44+
.iter()
45+
.any(|candidate| candidate.name() == "RustFormatStringResolutionProvider.List"));
46+
}
47+
48+
#[test]
49+
#[serial]
50+
fn custom_provider_round_trip_preserves_platform_types_and_confidence() {
51+
let _session = Session::new().expect("Failed to initialize session");
52+
let provider = FormatStringResolutionProvider::register(
53+
"RustFormatStringResolutionProvider.RoundTrip",
54+
TestProvider,
55+
);
56+
let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists");
57+
58+
let types = provider
59+
.is_valid("platform", Some(&platform))
60+
.expect("format is valid");
61+
assert_eq!(types.len(), 2);
62+
assert_eq!(types[0].contents.width(), platform.address_size() as u64);
63+
assert_eq!(types[0].confidence, 173);
64+
assert_eq!(types[1].contents.width(), 8);
65+
assert_eq!(types[1].confidence, 91);
66+
67+
assert!(provider
68+
.is_valid("empty", Some(&platform))
69+
.expect("empty format is valid")
70+
.is_empty());
71+
assert!(provider.is_valid("invalid", Some(&platform)).is_none());
72+
assert!(provider.is_valid("platform", None).is_none());
73+
}
74+
75+
#[test]
76+
#[serial]
77+
fn native_c_style_provider_is_loaded_in_headless_sessions() {
78+
let _session = Session::new().expect("Failed to initialize session");
79+
let provider = FormatStringResolutionProvider::by_name("CStyleFormatString")
80+
.expect("native C-style provider is loaded without explicit registration");
81+
let windows = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists");
82+
let linux = Platform::by_name("linux-x86_64").expect("linux-x86_64 exists");
83+
84+
let windows_types = provider
85+
.is_valid("%ld", Some(&windows))
86+
.expect("%ld is a valid Windows format string");
87+
assert_eq!(windows_types.len(), 1);
88+
assert_eq!(windows_types[0].contents.width(), 4);
89+
assert_eq!(windows_types[0].confidence, u8::MAX);
90+
91+
let linux_types = provider
92+
.is_valid("%ld", Some(&linux))
93+
.expect("%ld is a valid Linux format string");
94+
assert_eq!(linux_types.len(), 1);
95+
assert_eq!(linux_types[0].contents.width(), 8);
96+
assert_eq!(linux_types[0].confidence, u8::MAX);
97+
}

0 commit comments

Comments
 (0)