Skip to content

Commit 01a2244

Browse files
committed
Fix some rust race conditions and comment some likely suspects
FYI The binary view saving stuff can deadlock right now, so we document that now :/
1 parent 9c9f508 commit 01a2244

File tree

11 files changed

+155
-34
lines changed

11 files changed

+155
-34
lines changed

Cargo.lock

Lines changed: 76 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/warp/src/plugin.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ fn get_warp_tag_type(view: &BinaryView) -> Ref<TagType> {
3939
// TODO: Rename to markup_function or something.
4040
pub fn on_matched_function(function: &Function, matched: &WarpFunction) {
4141
let view = function.view();
42+
// TODO: Using user symbols here is problematic
43+
// TODO: For one they queue up a bunch of main thread actions
44+
// TODO: Secondly by queueing up those main thread actions if you attempt to save the file
45+
// TODO: Before the undo actions are done completing
4246
view.define_user_symbol(&to_bn_symbol_at_address(
4347
&view,
4448
&matched.symbol,

rust/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ thiserror = "2.0"
2020

2121
[dev-dependencies]
2222
rstest = "0.24"
23-
tempfile = "3.15"
23+
tempfile = "3.15"
24+
serial_test = "3.2"

rust/src/binary_view.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1580,7 +1580,7 @@ pub trait BinaryViewExt: BinaryViewBase {
15801580
let type_container_ptr =
15811581
NonNull::new(unsafe { BNGetAnalysisUserTypeContainer(self.as_ref().handle) });
15821582
// NOTE: I have no idea how this isn't a UAF, see the note in `TypeContainer::from_raw`
1583-
unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()) }
1583+
unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()) }.clone()
15841584
}
15851585

15861586
/// Type container for auto types in the Binary View.
@@ -1878,12 +1878,26 @@ impl BinaryView {
18781878
}
18791879

18801880
/// Save the original binary file to the provided `file_path` along with any modifications.
1881+
///
1882+
/// WARNING: Currently there is a possibility to deadlock if the analysis has queued up a main thread action
1883+
/// that tries to take the [`FileMetadata`] lock of the current view, and is executed while we
1884+
/// are executing in this function.
1885+
///
1886+
/// To avoid the above issue use [`crate::main_thread::execute_on_main_thread_and_wait`] to verify there
1887+
/// are no queued up main thread actions.
18811888
pub fn save_to_path(&self, file_path: impl AsRef<Path>) -> bool {
18821889
let file = file_path.as_ref().into_bytes_with_nul();
18831890
unsafe { BNSaveToFilename(self.handle, file.as_ptr() as *mut _) }
18841891
}
18851892

18861893
/// Save the original binary file to the provided [`FileAccessor`] along with any modifications.
1894+
///
1895+
/// WARNING: Currently there is a possibility to deadlock if the analysis has queued up a main thread action
1896+
/// that tries to take the [`FileMetadata`] lock of the current view, and is executed while we
1897+
/// are executing in this function.
1898+
///
1899+
/// To avoid the above issue use [`crate::main_thread::execute_on_main_thread_and_wait`] to verify there
1900+
/// are no queued up main thread actions.
18871901
pub fn save_to_accessor(&self, file: &mut FileAccessor) -> bool {
18881902
unsafe { BNSaveToFile(self.handle, &mut file.api_object) }
18891903
}

rust/src/collaboration/remote.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ impl Remote {
192192
/// Connects to the Remote, loading metadata and optionally acquiring a token.
193193
///
194194
/// Use [Remote::connect_with_opts] if you cannot otherwise automatically connect using enterprise.
195+
///
196+
/// WARNING: This is currently **not** thread safe, if you try and connect/disconnect to a remote on
197+
/// multiple threads you will be subject to race conditions. To avoid this wrap the [`Remote`] in
198+
/// a synchronization primitive, and pass that to your threads. Or don't try and connect on multiple threads.
195199
pub fn connect(&self) -> Result<(), ()> {
196200
// TODO: implement SecretsProvider
197201
if self.is_enterprise()? && enterprise::is_server_authenticated() {
@@ -234,6 +238,10 @@ impl Remote {
234238
}
235239

236240
/// Disconnects from the remote.
241+
///
242+
/// WARNING: This is currently **not** thread safe, if you try and connect/disconnect to a remote on
243+
/// multiple threads you will be subject to race conditions. To avoid this wrap the [`Remote`] in
244+
/// a synchronization primitive, and pass that to your threads. Or don't try and connect on multiple threads.
237245
pub fn disconnect(&self) -> Result<(), ()> {
238246
let success = unsafe { BNRemoteDisconnect(self.handle.as_ptr()) };
239247
success.then_some(()).ok_or(())

rust/src/headless.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -148,26 +148,26 @@ impl Default for InitializationOptions {
148148
/// This initializes the core with the given [`InitializationOptions`].
149149
pub fn init_with_opts(options: InitializationOptions) -> Result<(), InitializationError> {
150150
// If we are the main thread that means there is no main thread, we should register a main thread handler.
151-
if options.register_main_thread_handler
152-
&& is_main_thread()
153-
&& MAIN_THREAD_HANDLE.lock().unwrap().is_none()
154-
{
155-
let (sender, receiver) = std::sync::mpsc::channel();
156-
let main_thread = HeadlessMainThreadSender::new(sender);
157-
158-
// This thread will act as our main thread.
159-
let main_thread_handle = std::thread::Builder::new()
160-
.name("HeadlessMainThread".to_string())
161-
.spawn(move || {
162-
// We must register the main thread within said thread.
163-
main_thread.register();
164-
while let Ok(action) = receiver.recv() {
165-
action.execute();
166-
}
167-
})?;
168-
169-
// Set the static MAIN_THREAD_HANDLER so that we can close the thread on shutdown.
170-
*MAIN_THREAD_HANDLE.lock().unwrap() = Some(main_thread_handle);
151+
if options.register_main_thread_handler && is_main_thread() {
152+
let mut main_thread_handle = MAIN_THREAD_HANDLE.lock().unwrap();
153+
if main_thread_handle.is_none() {
154+
let (sender, receiver) = std::sync::mpsc::channel();
155+
let main_thread = HeadlessMainThreadSender::new(sender);
156+
157+
// This thread will act as our main thread.
158+
let join_handle = std::thread::Builder::new()
159+
.name("HeadlessMainThread".to_string())
160+
.spawn(move || {
161+
// We must register the main thread within said thread.
162+
main_thread.register();
163+
while let Ok(action) = receiver.recv() {
164+
action.execute();
165+
}
166+
})?;
167+
168+
// Set the static MAIN_THREAD_HANDLER so that we can close the thread on shutdown.
169+
*main_thread_handle = Some(join_handle);
170+
}
171171
}
172172

173173
match crate::product().as_str() {

rust/src/platform.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ impl Platform {
9090
if res.is_null() {
9191
None
9292
} else {
93-
Some(Ref::new(Self { handle: res }))
93+
Some(Self::ref_from_raw(res))
9494
}
9595
}
9696
}
@@ -176,7 +176,7 @@ impl Platform {
176176
// NOTE: I have no idea how this isn't a UAF, see the note in `TypeContainer::from_raw`
177177
// TODO: We are cloning here for platforms but we dont need to do this for [BinaryViewExt::type_container]
178178
// TODO: Why does this require that we, construct a TypeContainer, duplicate the type container, then drop the original.
179-
unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()).clone() }
179+
unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()) }
180180
}
181181

182182
pub fn get_type_libraries_by_name<T: BnStrCompatible>(&self, name: T) -> Array<TypeLibrary> {

rust/src/type_container.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@ impl TypeContainer {
3636
// NOTE: but this is how the C++ and Python bindings operate so i guess its fine?
3737
// TODO: I really dont get how some of the usage of the TypeContainer doesnt free the underlying container.
3838
// TODO: So for now we always duplicate the type container
39-
// let cloned_ptr = NonNull::new(BNDuplicateTypeContainer(handle.as_ptr()));
40-
// Self {
41-
// handle: cloned_ptr.unwrap(),
42-
// }
43-
Self { handle }
39+
let cloned_ptr = NonNull::new(BNDuplicateTypeContainer(handle.as_ptr()));
40+
Self {
41+
handle: cloned_ptr.unwrap(),
42+
}
4443
}
4544

4645
/// Get an id string for the Type Container. This will be unique within a given
@@ -417,6 +416,7 @@ impl Debug for TypeContainer {
417416
.field("name", &self.name())
418417
.field("container_type", &self.container_type())
419418
.field("is_mutable", &self.is_mutable())
419+
.field("type_names", &self.type_names().unwrap().to_vec())
420420
.finish()
421421
}
422422
}

rust/tests/binary_view.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use binaryninja::headless::Session;
33
use binaryninja::symbol::{SymbolBuilder, SymbolType};
44
use rstest::*;
55
use std::path::PathBuf;
6+
use binaryninja::main_thread::execute_on_main_thread_and_wait;
67

78
#[fixture]
89
#[once]
@@ -31,6 +32,10 @@ fn test_binary_saving(_session: &Session) {
3132
// Verify that we modified the binary
3233
let modified_contents = view.read_vec(0x1560, 4);
3334
assert_eq!(modified_contents, [0xff, 0xff, 0xff, 0xff]);
35+
36+
// HACK: To prevent us from deadlocking in save_to_path we wait for all main thread actions to finish.
37+
execute_on_main_thread_and_wait(|| {});
38+
3439
// Save the modified file
3540
assert!(view.save_to_path(out_dir.join("atox.obj.new")));
3641
// Verify that the file exists and is modified.

0 commit comments

Comments
 (0)