Skip to content

Commit ae85d1e

Browse files
committed
[WARP] Server-side constraint matching
Reduce networked functions by constraining on the returned set of functions on the server
1 parent ca02249 commit ae85d1e

File tree

14 files changed

+129
-68
lines changed

14 files changed

+129
-68
lines changed

plugins/warp/api/python/warp.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,19 +426,25 @@ def remove_types(self, source: Source, guids: List[TypeGUID]) -> bool:
426426
core_guids[i] = guids[i].uuid
427427
return warpcore.BNWARPContainerRemoveTypes(self.handle, source.uuid, core_guids, count)
428428

429-
def fetch_functions(self, target: WarpTarget, guids: List[FunctionGUID], source_tags: Optional[List[str]] = None):
429+
def fetch_functions(self, target: WarpTarget, guids: List[FunctionGUID], source_tags: Optional[List[str]] = None, constraints: Optional[List[ConstraintGUID]] = None):
430430
count = len(guids)
431431
core_guids = (warpcore.BNWARPFunctionGUID * count)()
432432
for i in range(count):
433433
core_guids[i] = guids[i].uuid
434+
if constraints is None:
435+
constraints = []
436+
constraints_count = len(constraints)
437+
core_constraints = (warpcore.BNWARPConstraintGUID * constraints_count)()
438+
for i in range(constraints_count):
439+
core_constraints[i] = constraints[i].uuid
434440
if source_tags is None:
435441
source_tags = []
436442
source_tags_ptr = (ctypes.c_char_p * len(source_tags))()
437443
source_tags_len = len(source_tags)
438444
for i in range(len(source_tags)):
439445
source_tags_ptr[i] = source_tags[i].encode('utf-8')
440446
source_tags_array_ptr = ctypes.cast(source_tags_ptr, ctypes.POINTER(ctypes.c_char_p))
441-
warpcore.BNWARPContainerFetchFunctions(self.handle, target.handle, source_tags_array_ptr, source_tags_len, core_guids, count)
447+
warpcore.BNWARPContainerFetchFunctions(self.handle, target.handle, source_tags_array_ptr, source_tags_len, core_guids, count, core_constraints, constraints_count)
442448

443449
def get_sources_with_function_guid(self, target: WarpTarget, guid: FunctionGUID) -> List[Source]:
444450
count = ctypes.c_size_t()

plugins/warp/api/warp.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ bool Container::RemoveTypes(const Source &source, const std::vector<TypeGUID> &g
353353
return result;
354354
}
355355

356-
void Container::FetchFunctions(const Target &target, const std::vector<FunctionGUID> &guids, const std::vector<SourceTag> &tags) const
356+
void Container::FetchFunctions(const Target &target, const std::vector<FunctionGUID> &guids, const std::vector<SourceTag> &tags, const std::vector<ConstraintGUID> &constraints) const
357357
{
358358
size_t count = guids.size();
359359
BNWARPFunctionGUID *apiGuids = new BNWARPFunctionGUID[count];
@@ -363,9 +363,14 @@ void Container::FetchFunctions(const Target &target, const std::vector<FunctionG
363363
const char** rawTags = new const char*[tagCount];
364364
for (size_t i = 0; i < tagCount; i++)
365365
rawTags[i] = tags[i].c_str();
366-
BNWARPContainerFetchFunctions(m_object, target.m_object, rawTags, tagCount, apiGuids, count);
366+
size_t constraintCount = constraints.size();
367+
BNWARPConstraintGUID *apiConstraints = new BNWARPConstraintGUID[constraintCount];
368+
for (size_t i = 0; i < constraintCount; i++)
369+
apiConstraints[i] = *constraints[i].Raw();
370+
BNWARPContainerFetchFunctions(m_object, target.m_object, rawTags, tagCount, apiGuids, count, apiConstraints, constraintCount);
367371
delete[] apiGuids;
368372
delete[] rawTags;
373+
delete[] apiConstraints;
369374
}
370375

371376
std::vector<Source> Container::GetSourcesWithFunctionGUID(const Target& target, const FunctionGUID &guid) const

plugins/warp/api/warp.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ namespace Warp {
409409

410410
bool RemoveTypes(const Source &source, const std::vector<TypeGUID> &guids) const;
411411

412-
void FetchFunctions(const Target &target, const std::vector<FunctionGUID> &guids, const std::vector<SourceTag> &tags = {}) const;
412+
void FetchFunctions(const Target &target, const std::vector<FunctionGUID> &guids, const std::vector<SourceTag> &tags = {}, const std::vector<ConstraintGUID> &constraints = {}) const;
413413

414414
std::vector<Source> GetSourcesWithFunctionGUID(const Target &target, const FunctionGUID &guid) const;
415415

plugins/warp/api/warpcore.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ extern "C"
128128
WARP_FFI_API bool BNWARPContainerRemoveFunctions(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPSource* source, BNWARPFunction** functions, size_t count);
129129
WARP_FFI_API bool BNWARPContainerRemoveTypes(BNWARPContainer* container, const BNWARPSource* source, BNWARPTypeGUID* types, size_t count);
130130

131-
WARP_FFI_API void BNWARPContainerFetchFunctions(BNWARPContainer* container, BNWARPTarget* target, const char** sourceTags, size_t sourceTagCount, const BNWARPTypeGUID* guids, size_t count);
131+
WARP_FFI_API void BNWARPContainerFetchFunctions(BNWARPContainer* container, BNWARPTarget* target, const char** sourceTags, size_t sourceTagCount, const BNWARPFunctionGUID* guids, size_t count, const BNWARPConstraintGUID* constraints, size_t constraintCount);
132132

133133
WARP_FFI_API BNWARPSource* BNWARPContainerGetSourcesWithFunctionGUID(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPFunctionGUID* guid, size_t* count);
134134
WARP_FFI_API BNWARPSource* BNWARPContainerGetSourcesWithTypeGUID(BNWARPContainer* container, const BNWARPTypeGUID* guid, size_t* count);

plugins/warp/src/container.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use thiserror::Error;
99
use uuid::Uuid;
1010
use warp::r#type::guid::TypeGUID;
1111
use warp::r#type::{ComputedType, Type};
12+
use warp::signature::constraint::ConstraintGUID;
1213
use warp::signature::function::{Function, FunctionGUID};
1314
use warp::symbol::Symbol;
1415
use warp::target::Target;
@@ -295,11 +296,15 @@ pub trait Container: Send + Sync + Display + Debug {
295296
/// Typically, a container that resides only in memory has nothing to fetch, so the default implementation
296297
/// will do nothing. This function is blocking, so assume it will take a few seconds for a container
297298
/// that intends to fetch over the network.
299+
///
300+
/// To constrain on the fetched functions, pass a list of [`ConstraintGUID`]s that will be
301+
/// used to filter the fetched functions which do not contain at least one of the constraints.
298302
fn fetch_functions(
299303
&self,
300304
_target: &Target,
301305
_tags: &[SourceTag],
302306
_functions: &[FunctionGUID],
307+
_constraints: &[ConstraintGUID],
303308
) -> ContainerResult<()> {
304309
Ok(())
305310
}

plugins/warp/src/container/network.rs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use warp::r#type::chunk::TypeChunk;
1414
use warp::r#type::guid::TypeGUID;
1515
use warp::r#type::{ComputedType, Type};
1616
use warp::signature::chunk::SignatureChunk;
17+
use warp::signature::constraint::ConstraintGUID;
1718
use warp::signature::function::{Function, FunctionGUID};
1819
use warp::target::Target;
1920
use warp::{WarpFile, WarpFileHeader};
@@ -45,7 +46,7 @@ pub struct NetworkContainer {
4546
/// NOTE: This is a [`DashMap`] purely for the sake of interior mutability as we do not wish to hold
4647
/// a write lock on the entire container while performing network operations.
4748
known_function_sources: DashMap<FunctionGUID, Vec<SourceId>>,
48-
/// Populated when user adds function, this is used for writing back to the server.
49+
/// Populated when the user adds a function, this is used for writing back to the server.
4950
added_chunks: HashMap<SourceId, Vec<Chunk<'static>>>,
5051
/// Populated when connecting to the server, this is used to determine which sources are writable.
5152
///
@@ -165,18 +166,25 @@ impl NetworkContainer {
165166
/// Every request we store the returned objects on disk, this means that users will first
166167
/// query against the disk objects, then the server. This also means we need to cache functions f
167168
/// or which we have not received any functions for, as otherwise we would keep trying to query it.
168-
pub fn pull_functions(&self, target: &Target, source: &SourceId, functions: &[FunctionGUID]) {
169+
pub fn pull_functions(
170+
&self,
171+
target: &Target,
172+
source: &SourceId,
173+
functions: &[FunctionGUID],
174+
constraints: &[ConstraintGUID],
175+
) {
169176
let target_id = self.get_target_id(target);
170-
let file = match self
171-
.client
172-
.query_functions(target_id, Some(*source), functions)
173-
{
174-
Ok(file) => file,
175-
Err(e) => {
176-
tracing::error!("Failed to query functions: {}", e);
177-
return;
178-
}
179-
};
177+
let file =
178+
match self
179+
.client
180+
.query_functions(target_id, Some(*source), functions, constraints)
181+
{
182+
Ok(file) => file,
183+
Err(e) => {
184+
tracing::error!("Failed to query functions: {}", e);
185+
return;
186+
}
187+
};
180188

181189
tracing::debug!("Got {} chunks from server", file.chunks.len());
182190
for chunk in &file.chunks {
@@ -396,16 +404,18 @@ impl Container for NetworkContainer {
396404
target: &Target,
397405
tags: &[SourceTag],
398406
functions: &[FunctionGUID],
407+
constraints: &[ConstraintGUID],
399408
) -> ContainerResult<()> {
400409
// NOTE: Blocking request to get the mapped function sources.
401410
let mapped_unseen_functions =
402411
self.get_unseen_functions_source(Some(&target), tags, functions);
403412

413+
// TODO: It would be nice to have a way to not have to pull through each source individually.
404414
// Actually get the function data for the unseen guids, we really only want to do this once per
405415
// session, anymore, and this is annoying!
406416
for (source, unseen_guids) in mapped_unseen_functions {
407417
// NOTE: Blocking request to get the function data in the container cache.
408-
self.pull_functions(&target, &source, &unseen_guids);
418+
self.pull_functions(&target, &source, &unseen_guids, constraints);
409419
}
410420

411421
Ok(())

plugins/warp/src/container/network/client.rs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ use base64::Engine;
77
use binaryninja::download::DownloadProvider;
88
use serde::Deserialize;
99
use serde_json::json;
10-
use std::collections::HashMap;
10+
use std::collections::{HashMap, HashSet};
1111
use std::str::FromStr;
1212
use uuid::Uuid;
1313
use warp::chunk::ChunkKind;
1414
use warp::r#type::guid::TypeGUID;
1515
use warp::r#type::{ComputedType, Type};
16+
use warp::signature::constraint::ConstraintGUID;
1617
use warp::signature::function::{Function, FunctionGUID};
1718
use warp::target::Target;
1819
use warp::WarpFile;
@@ -30,7 +31,8 @@ pub struct NetworkClient {
3031
impl NetworkClient {
3132
pub fn new(server_url: String, server_token: Option<String>) -> Self {
3233
// TODO: This might want to be kept for the request header?
33-
let mut headers: Vec<(String, String)> = vec![];
34+
let mut headers: Vec<(String, String)> =
35+
vec![("Content-Encoding".to_string(), "gzip".to_string())];
3436
if let Some(token) = &server_token {
3537
headers.push(("authorization".to_string(), format!("Bearer {}", token)));
3638
}
@@ -214,13 +216,14 @@ impl NetworkClient {
214216
source: Option<SourceId>,
215217
source_tags: &[SourceTag],
216218
guids: &[FunctionGUID],
219+
constraints: &[ConstraintGUID],
217220
) -> serde_json::Value {
218-
let guids_str: Vec<String> = guids.iter().map(|g| g.to_string()).collect();
221+
let guids_str: HashSet<String> = guids.iter().map(|g| g.to_string()).collect();
219222
// TODO: The limit here needs to be somewhat flexible. But 1000 will do for now.
220223
let mut body = json!({
221224
"format": "flatbuffer",
222225
"guids": guids_str,
223-
"limit": 1000
226+
"limit": 10000,
224227
});
225228
if let Some(target_id) = target {
226229
body["target_id"] = json!(target_id);
@@ -231,6 +234,11 @@ impl NetworkClient {
231234
if !source_tags.is_empty() {
232235
body["source_tags"] = json!(source_tags);
233236
}
237+
if !constraints.is_empty() {
238+
let constraint_guids_str: HashSet<String> =
239+
constraints.iter().map(|g| g.to_string()).collect();
240+
body["constraints"] = json!(constraint_guids_str);
241+
}
234242
body
235243
}
236244

@@ -244,13 +252,13 @@ impl NetworkClient {
244252
target: Option<NetworkTargetId>,
245253
source: Option<SourceId>,
246254
guids: &[FunctionGUID],
255+
constraints: &[ConstraintGUID],
247256
) -> Result<WarpFile<'static>, String> {
248257
let query_functions_url = format!("{}/api/v1/functions/query", self.server_url);
249258
// TODO: Allow for source tags? We really only need this in query_functions_source as that
250259
// TODO: is what prevents a undesired source from being "known" to the container.
251-
let payload = Self::query_functions_body(target, source, &[], guids);
260+
let payload = Self::query_functions_body(target, source, &[], guids, constraints);
252261
let mut inst = self.provider.create_instance().unwrap();
253-
254262
let resp = inst.post_json(&query_functions_url, self.headers.clone(), &payload)?;
255263
if !resp.is_success() {
256264
return Err(format!(
@@ -275,7 +283,10 @@ impl NetworkClient {
275283
) -> Result<HashMap<SourceId, Vec<FunctionGUID>>, String> {
276284
let query_functions_source_url =
277285
format!("{}/api/v1/functions/query/source", self.server_url);
278-
let payload = Self::query_functions_body(target, None, tags, guids);
286+
// NOTE: We do not filter by constraint guids here since this pass is only responsible for
287+
// returning the source ids, not the actual function data, see [`NetworkClient::query_functions`]
288+
// for the place where the constraints are applied, and _do_ matter.
289+
let payload = Self::query_functions_body(target, None, tags, guids, &[]);
279290
let mut inst = self.provider.create_instance().unwrap();
280291

281292
let resp = inst.post_json(&query_functions_source_url, self.headers.clone(), &payload)?;

plugins/warp/src/plugin/ffi/container.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use crate::container::{
55
};
66
use crate::convert::{from_bn_type, to_bn_type};
77
use crate::plugin::ffi::{
8-
BNWARPContainer, BNWARPFunction, BNWARPFunctionGUID, BNWARPSource, BNWARPTarget, BNWARPTypeGUID,
8+
BNWARPConstraintGUID, BNWARPContainer, BNWARPFunction, BNWARPFunctionGUID, BNWARPSource,
9+
BNWARPTarget, BNWARPTypeGUID,
910
};
1011
use binaryninja::architecture::CoreArchitecture;
1112
use binaryninja::binary_view::BinaryView;
@@ -218,6 +219,8 @@ pub unsafe extern "C" fn BNWARPContainerFetchFunctions(
218219
source_tags_count: usize,
219220
guids: *const BNWARPFunctionGUID,
220221
count: usize,
222+
constraints: *const BNWARPConstraintGUID,
223+
constraints_count: usize,
221224
) {
222225
let arc_container = ManuallyDrop::new(Arc::from_raw(container));
223226
let Ok(container) = arc_container.read() else {
@@ -234,8 +237,9 @@ pub unsafe extern "C" fn BNWARPContainerFetchFunctions(
234237
.collect();
235238

236239
let guids = unsafe { std::slice::from_raw_parts(guids, count) };
240+
let constraints = unsafe { std::slice::from_raw_parts(constraints, constraints_count) };
237241

238-
if let Err(e) = container.fetch_functions(&target, &source_tags, guids) {
242+
if let Err(e) = container.fetch_functions(&target, &source_tags, guids, constraints) {
239243
tracing::error!("Failed to fetch functions: {}", e);
240244
}
241245
}

plugins/warp/src/plugin/settings.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub struct PluginSettings {
4040
impl PluginSettings {
4141
pub const ALLOWED_SOURCE_TAGS_DEFAULT: [&'static str; 2] = ["official", "trusted"];
4242
pub const ALLOWED_SOURCE_TAGS_SETTING: &'static str = "warp.fetcher.allowedSourceTags";
43-
pub const FETCH_BATCH_SIZE_DEFAULT: usize = 100;
43+
pub const FETCH_BATCH_SIZE_DEFAULT: usize = 10000;
4444
pub const FETCH_BATCH_SIZE_SETTING: &'static str = "warp.fetcher.fetchBatchSize";
4545
pub const LOAD_BUNDLED_FILES_DEFAULT: bool = true;
4646
pub const LOAD_BUNDLED_FILES_SETTING: &'static str = "warp.container.loadBundledFiles";
@@ -81,8 +81,8 @@ impl PluginSettings {
8181
let fetch_size_props = json!({
8282
"title" : "Fetch Batch Limit",
8383
"type" : "number",
84-
"minValue" : 1,
85-
"maxValue" : 1000,
84+
"minValue" : 100,
85+
"maxValue" : 20000,
8686
"default" : Self::FETCH_BATCH_SIZE_DEFAULT,
8787
"description" : "The maximum number of functions to fetch in a single batch. This is used to limit the amount of functions to fetch at once, lowering this value will make the fetch process more comprehensive at the cost of more network requests.",
8888
"ignore" : [],

plugins/warp/src/plugin/workflow.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use std::cmp::Ordering;
2424
use std::collections::HashMap;
2525
use std::time::Instant;
2626
use warp::r#type::class::function::{Location, RegisterLocation, StackLocation};
27+
use warp::signature::constraint::ConstraintGUID;
2728
use warp::signature::function::{Function, FunctionGUID};
2829
use warp::target::Target;
2930

@@ -171,7 +172,7 @@ pub fn run_matcher(view: &BinaryView) {
171172
.maximum_possible_functions
172173
.is_some_and(|max| max < matched_functions.len() as u64)
173174
{
174-
tracing::warn!(
175+
tracing::debug!(
175176
"Skipping {}, too many possible functions: {}",
176177
guid,
177178
matched_functions.len()
@@ -270,6 +271,20 @@ pub fn run_fetcher(view: &BinaryView) {
270271
let mut query_opts = QueryOptions::new_with_view(view);
271272
let plugin_settings = PluginSettings::from_settings(&view_settings, &mut query_opts);
272273

274+
let is_ignored_func = |f: &BNFunction| !f.function_tags(None, Some(IGNORE_TAG_NAME)).is_empty();
275+
276+
let constraints: Vec<ConstraintGUID> = view
277+
.functions()
278+
.iter()
279+
// Skip functions that have the ignored tag! Otherwise, we will store their constraints.
280+
.filter(|f| !is_ignored_func(f))
281+
.filter_map(|f| {
282+
let function = try_cached_function_match(&f)?;
283+
Some(function.constraints.into_iter().map(|c| c.guid))
284+
})
285+
.flatten()
286+
.collect();
287+
273288
let Some(function_set) = FunctionSet::from_view(view) else {
274289
background_task.finish();
275290
return;
@@ -285,8 +300,12 @@ pub fn run_fetcher(view: &BinaryView) {
285300
if background_task.is_cancelled() {
286301
break;
287302
}
288-
let _ =
289-
container.fetch_functions(target, &plugin_settings.allowed_source_tags, batch);
303+
let _ = container.fetch_functions(
304+
target,
305+
&plugin_settings.allowed_source_tags,
306+
batch,
307+
&constraints,
308+
);
290309
}
291310
}
292311
});

0 commit comments

Comments
 (0)