1use crate::core::types::{TrackingError, TrackingResult};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::sync::{Arc, Mutex};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ResolvedFfiFunction {
14 pub library_name: String,
16 pub function_name: String,
18 pub signature: Option<String>,
20 pub category: FfiFunctionCategory,
22 pub risk_level: FfiRiskLevel,
24 pub metadata: HashMap<String, String>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
30pub enum FfiFunctionCategory {
31 MemoryManagement,
33 StringManipulation,
35 FileIO,
37 Network,
39 Cryptographic,
41 SystemCall,
43 Graphics,
45 Database,
47 UserLibrary,
49 Unknown,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
55pub enum FfiRiskLevel {
56 VeryLow,
58 Low,
60 Medium,
62 High,
64 Critical,
66}
67
68pub struct FfiFunctionResolver {
70 function_database: Arc<Mutex<HashMap<String, ResolvedFfiFunction>>>,
72 library_mapping: Arc<Mutex<HashMap<String, String>>>,
74 stats: Arc<Mutex<ResolutionStats>>,
76 config: ResolverConfig,
78}
79
80#[derive(Debug, Clone)]
82pub struct ResolverConfig {
83 pub enable_auto_discovery: bool,
85 pub enable_risk_assessment: bool,
87 pub enable_caching: bool,
89 pub max_cache_size: usize,
91}
92
93impl Default for ResolverConfig {
94 fn default() -> Self {
95 Self {
96 enable_auto_discovery: true,
97 enable_risk_assessment: true,
98 enable_caching: true,
99 max_cache_size: 1000,
100 }
101 }
102}
103
104#[derive(Debug, Clone, Default, Serialize, Deserialize)]
106pub struct ResolutionStats {
107 pub total_attempts: usize,
109 pub successful_resolutions: usize,
111 pub failed_resolutions: usize,
113 pub cache_hits: usize,
115 pub functions_by_category: HashMap<String, usize>,
117 pub functions_by_risk: HashMap<String, usize>,
119 pub top_functions: Vec<(String, usize)>,
121}
122
123impl FfiFunctionResolver {
124 pub fn new(config: ResolverConfig) -> Self {
126 tracing::info!("๐ Initializing FFI Function Resolver");
127 tracing::info!(" โข Auto discovery: {}", config.enable_auto_discovery);
128 tracing::info!(" โข Risk assessment: {}", config.enable_risk_assessment);
129 tracing::info!(" โข Caching: {}", config.enable_caching);
130
131 let resolver = Self {
132 function_database: Arc::new(Mutex::new(HashMap::new())),
133 library_mapping: Arc::new(Mutex::new(HashMap::new())),
134 stats: Arc::new(Mutex::new(ResolutionStats::default())),
135 config,
136 };
137
138 resolver.initialize_known_functions();
140 resolver
141 }
142
143 pub fn resolve_function(
145 &self,
146 function_name: &str,
147 library_hint: Option<&str>,
148 ) -> TrackingResult<ResolvedFfiFunction> {
149 self.update_stats_attempt();
150
151 if self.config.enable_caching {
153 if let Ok(db) = self.function_database.lock() {
154 if let Some(cached) = db.get(function_name) {
155 self.update_stats_cache_hit();
156 self.update_stats_success();
157 tracing::debug!("๐ Cache hit for function: {}", function_name);
158 return Ok(cached.clone());
159 }
160 }
161 }
162
163 let resolved = if let Some(known) = self.get_known_function(function_name) {
165 known
166 } else if let Some(lib_hint) = library_hint {
167 self.resolve_with_library_hint(function_name, lib_hint)?
169 } else if self.config.enable_auto_discovery {
170 self.auto_discover_function(function_name)?
172 } else {
173 self.create_unknown_function(function_name)
175 };
176
177 if self.config.enable_caching {
179 self.cache_function(function_name, &resolved)?;
180 }
181
182 self.update_stats_success();
183 tracing::debug!(
184 "๐ Resolved function: {} -> {}::{}",
185 function_name,
186 resolved.library_name,
187 resolved.function_name
188 );
189
190 Ok(resolved)
191 }
192
193 pub fn resolve_functions_batch(
195 &self,
196 function_names: &[String],
197 ) -> Vec<TrackingResult<ResolvedFfiFunction>> {
198 function_names
199 .iter()
200 .map(|name| self.resolve_function(name, None))
201 .collect()
202 }
203
204 pub fn add_custom_function(
206 &self,
207 function_name: String,
208 resolved: ResolvedFfiFunction,
209 ) -> TrackingResult<()> {
210 if let Ok(mut db) = self.function_database.lock() {
211 db.insert(function_name.clone(), resolved.clone());
212
213 if let Ok(mut mapping) = self.library_mapping.lock() {
214 mapping.insert(function_name.clone(), resolved.library_name.clone());
215 }
216
217 tracing::info!(
218 "๐ Added custom function: {} -> {}::{}",
219 function_name,
220 resolved.library_name,
221 resolved.function_name
222 );
223 Ok(())
224 } else {
225 Err(TrackingError::LockContention(
226 "Failed to lock function database".to_string(),
227 ))
228 }
229 }
230
231 pub fn get_stats(&self) -> ResolutionStats {
233 if let Ok(stats) = self.stats.lock() {
234 stats.clone()
235 } else {
236 tracing::error!("Failed to lock resolution stats");
237 ResolutionStats::default()
238 }
239 }
240
241 pub fn clear_cache(&self) {
243 if let Ok(mut db) = self.function_database.lock() {
244 let initial_size = db.len();
245 db.retain(|_, func| self.is_builtin_function(func));
246 let cleared = initial_size - db.len();
247 tracing::info!("๐งน Cleared {} cached functions", cleared);
248 }
249 }
250
251 fn initialize_known_functions(&self) {
254 let known_functions = vec![
255 (
257 "malloc",
258 ResolvedFfiFunction {
259 library_name: "libc".to_string(),
260 function_name: "malloc".to_string(),
261 signature: Some("void* malloc(size_t size)".to_string()),
262 category: FfiFunctionCategory::MemoryManagement,
263 risk_level: FfiRiskLevel::Medium,
264 metadata: [("description".to_string(), "Allocate memory".to_string())].into(),
265 },
266 ),
267 (
268 "free",
269 ResolvedFfiFunction {
270 library_name: "libc".to_string(),
271 function_name: "free".to_string(),
272 signature: Some("void free(void* ptr)".to_string()),
273 category: FfiFunctionCategory::MemoryManagement,
274 risk_level: FfiRiskLevel::High,
275 metadata: [(
276 "description".to_string(),
277 "Free allocated memory".to_string(),
278 )]
279 .into(),
280 },
281 ),
282 (
283 "realloc",
284 ResolvedFfiFunction {
285 library_name: "libc".to_string(),
286 function_name: "realloc".to_string(),
287 signature: Some("void* realloc(void* ptr, size_t size)".to_string()),
288 category: FfiFunctionCategory::MemoryManagement,
289 risk_level: FfiRiskLevel::High,
290 metadata: [("description".to_string(), "Reallocate memory".to_string())].into(),
291 },
292 ),
293 (
294 "calloc",
295 ResolvedFfiFunction {
296 library_name: "libc".to_string(),
297 function_name: "calloc".to_string(),
298 signature: Some("void* calloc(size_t num, size_t size)".to_string()),
299 category: FfiFunctionCategory::MemoryManagement,
300 risk_level: FfiRiskLevel::Medium,
301 metadata: [(
302 "description".to_string(),
303 "Allocate and zero memory".to_string(),
304 )]
305 .into(),
306 },
307 ),
308 (
310 "strcpy",
311 ResolvedFfiFunction {
312 library_name: "libc".to_string(),
313 function_name: "strcpy".to_string(),
314 signature: Some("char* strcpy(char* dest, const char* src)".to_string()),
315 category: FfiFunctionCategory::StringManipulation,
316 risk_level: FfiRiskLevel::Critical,
317 metadata: [(
318 "description".to_string(),
319 "Copy string (unsafe)".to_string(),
320 )]
321 .into(),
322 },
323 ),
324 (
325 "strncpy",
326 ResolvedFfiFunction {
327 library_name: "libc".to_string(),
328 function_name: "strncpy".to_string(),
329 signature: Some(
330 "char* strncpy(char* dest, const char* src, size_t n)".to_string(),
331 ),
332 category: FfiFunctionCategory::StringManipulation,
333 risk_level: FfiRiskLevel::Medium,
334 metadata: [(
335 "description".to_string(),
336 "Copy string with length limit".to_string(),
337 )]
338 .into(),
339 },
340 ),
341 (
342 "sprintf",
343 ResolvedFfiFunction {
344 library_name: "libc".to_string(),
345 function_name: "sprintf".to_string(),
346 signature: Some("int sprintf(char* str, const char* format, ...)".to_string()),
347 category: FfiFunctionCategory::StringManipulation,
348 risk_level: FfiRiskLevel::Critical,
349 metadata: [(
350 "description".to_string(),
351 "Format string (unsafe)".to_string(),
352 )]
353 .into(),
354 },
355 ),
356 (
357 "snprintf",
358 ResolvedFfiFunction {
359 library_name: "libc".to_string(),
360 function_name: "snprintf".to_string(),
361 signature: Some(
362 "int snprintf(char* str, size_t size, const char* format, ...)".to_string(),
363 ),
364 category: FfiFunctionCategory::StringManipulation,
365 risk_level: FfiRiskLevel::Low,
366 metadata: [("description".to_string(), "Safe format string".to_string())]
367 .into(),
368 },
369 ),
370 (
372 "fopen",
373 ResolvedFfiFunction {
374 library_name: "libc".to_string(),
375 function_name: "fopen".to_string(),
376 signature: Some(
377 "FILE* fopen(const char* filename, const char* mode)".to_string(),
378 ),
379 category: FfiFunctionCategory::FileIO,
380 risk_level: FfiRiskLevel::Low,
381 metadata: [("description".to_string(), "Open file".to_string())].into(),
382 },
383 ),
384 (
385 "fclose",
386 ResolvedFfiFunction {
387 library_name: "libc".to_string(),
388 function_name: "fclose".to_string(),
389 signature: Some("int fclose(FILE* stream)".to_string()),
390 category: FfiFunctionCategory::FileIO,
391 risk_level: FfiRiskLevel::Low,
392 metadata: [("description".to_string(), "Close file".to_string())].into(),
393 },
394 ),
395 (
397 "fork",
398 ResolvedFfiFunction {
399 library_name: "libc".to_string(),
400 function_name: "fork".to_string(),
401 signature: Some("pid_t fork(void)".to_string()),
402 category: FfiFunctionCategory::SystemCall,
403 risk_level: FfiRiskLevel::Medium,
404 metadata: [(
405 "description".to_string(),
406 "Create child process".to_string(),
407 )]
408 .into(),
409 },
410 ),
411 ];
412
413 if let Ok(mut db) = self.function_database.lock() {
414 if let Ok(mut mapping) = self.library_mapping.lock() {
415 for (name, func) in known_functions {
416 db.insert(name.to_string(), func.clone());
417 mapping.insert(name.to_string(), func.library_name);
418 }
419 }
420 }
421
422 tracing::info!("๐ Initialized {} known FFI functions", 11);
423 }
424
425 fn get_known_function(&self, function_name: &str) -> Option<ResolvedFfiFunction> {
426 if let Ok(db) = self.function_database.lock() {
427 db.get(function_name).cloned()
428 } else {
429 None
430 }
431 }
432
433 fn resolve_with_library_hint(
434 &self,
435 function_name: &str,
436 library_hint: &str,
437 ) -> TrackingResult<ResolvedFfiFunction> {
438 let category = self.infer_category_from_name(function_name);
439 let risk_level = self.assess_risk_from_name(function_name, &category);
440
441 Ok(ResolvedFfiFunction {
442 library_name: library_hint.to_string(),
443 function_name: function_name.to_string(),
444 signature: None,
445 category,
446 risk_level,
447 metadata: HashMap::new(),
448 })
449 }
450
451 fn auto_discover_function(&self, function_name: &str) -> TrackingResult<ResolvedFfiFunction> {
452 let library_name = self.infer_library_from_name(function_name);
454 let category = self.infer_category_from_name(function_name);
455 let risk_level = self.assess_risk_from_name(function_name, &category);
456
457 Ok(ResolvedFfiFunction {
458 library_name,
459 function_name: function_name.to_string(),
460 signature: None,
461 category,
462 risk_level,
463 metadata: [("auto_discovered".to_string(), "true".to_string())].into(),
464 })
465 }
466
467 fn create_unknown_function(&self, function_name: &str) -> ResolvedFfiFunction {
468 ResolvedFfiFunction {
469 library_name: "unknown".to_string(),
470 function_name: function_name.to_string(),
471 signature: None,
472 category: FfiFunctionCategory::Unknown,
473 risk_level: FfiRiskLevel::Medium,
474 metadata: [("status".to_string(), "unresolved".to_string())].into(),
475 }
476 }
477
478 fn infer_library_from_name(&self, function_name: &str) -> String {
479 if function_name.starts_with("SSL_") || function_name.starts_with("crypto_") {
481 "libssl".to_string()
482 } else if function_name.starts_with("pthread_") {
483 "libpthread".to_string()
484 } else if function_name.starts_with("gl") || function_name.starts_with("GL") {
485 "libGL".to_string()
486 } else if function_name.starts_with("sqlite3_") {
487 "libsqlite3".to_string()
488 } else if ["malloc", "free", "printf", "scanf", "fopen", "fork"]
489 .iter()
490 .any(|&f| function_name.contains(f))
491 {
492 "libc".to_string()
493 } else {
494 "unknown".to_string()
495 }
496 }
497
498 fn infer_category_from_name(&self, function_name: &str) -> FfiFunctionCategory {
499 if ["malloc", "free", "realloc", "calloc"]
500 .iter()
501 .any(|&f| function_name.contains(f))
502 {
503 FfiFunctionCategory::MemoryManagement
504 } else if ["str", "sprintf", "printf"]
505 .iter()
506 .any(|&f| function_name.contains(f))
507 {
508 FfiFunctionCategory::StringManipulation
509 } else if ["fopen", "fread", "fwrite", "fclose"]
510 .iter()
511 .any(|&f| function_name.contains(f))
512 {
513 FfiFunctionCategory::FileIO
514 } else if ["socket", "connect", "send", "recv"]
515 .iter()
516 .any(|&f| function_name.contains(f))
517 {
518 FfiFunctionCategory::Network
519 } else if ["SSL_", "crypto_"]
520 .iter()
521 .any(|&f| function_name.starts_with(f))
522 {
523 FfiFunctionCategory::Cryptographic
524 } else if ["fork", "exec", "signal", "kill"]
525 .iter()
526 .any(|&f| function_name.contains(f))
527 {
528 FfiFunctionCategory::SystemCall
529 } else if ["gl", "GL", "Direct"]
530 .iter()
531 .any(|&f| function_name.contains(f))
532 {
533 FfiFunctionCategory::Graphics
534 } else if ["sqlite", "mysql", "postgres"]
535 .iter()
536 .any(|&f| function_name.contains(f))
537 {
538 FfiFunctionCategory::Database
539 } else {
540 FfiFunctionCategory::Unknown
541 }
542 }
543
544 fn assess_risk_from_name(
545 &self,
546 function_name: &str,
547 category: &FfiFunctionCategory,
548 ) -> FfiRiskLevel {
549 if ["strcpy", "sprintf", "gets", "scanf"].contains(&function_name) {
551 return FfiRiskLevel::Critical;
552 }
553
554 if ["free", "realloc"].contains(&function_name) {
556 return FfiRiskLevel::High;
557 }
558
559 match category {
561 FfiFunctionCategory::MemoryManagement => FfiRiskLevel::Medium,
562 FfiFunctionCategory::StringManipulation => FfiRiskLevel::High,
563 FfiFunctionCategory::SystemCall => FfiRiskLevel::Medium,
564 FfiFunctionCategory::Cryptographic => FfiRiskLevel::Low,
565 FfiFunctionCategory::FileIO => FfiRiskLevel::Low,
566 FfiFunctionCategory::Network => FfiRiskLevel::Medium,
567 FfiFunctionCategory::Graphics => FfiRiskLevel::Low,
568 FfiFunctionCategory::Database => FfiRiskLevel::Low,
569 FfiFunctionCategory::UserLibrary => FfiRiskLevel::Medium,
570 FfiFunctionCategory::Unknown => FfiRiskLevel::Medium,
571 }
572 }
573
574 fn cache_function(
575 &self,
576 function_name: &str,
577 resolved: &ResolvedFfiFunction,
578 ) -> TrackingResult<()> {
579 if let Ok(mut db) = self.function_database.lock() {
580 if db.len() >= self.config.max_cache_size {
582 let keys_to_remove: Vec<String> = db
584 .iter()
585 .filter(|(_, func)| !self.is_builtin_function(func))
586 .take(10)
587 .map(|(k, _)| k.clone())
588 .collect();
589
590 for key in keys_to_remove {
591 db.remove(&key);
592 }
593 }
594
595 db.insert(function_name.to_string(), resolved.clone());
596 Ok(())
597 } else {
598 Err(TrackingError::LockContention(
599 "Failed to lock function database".to_string(),
600 ))
601 }
602 }
603
604 fn is_builtin_function(&self, func: &ResolvedFfiFunction) -> bool {
605 !func.metadata.contains_key("auto_discovered") && func.library_name != "unknown"
606 }
607
608 fn update_stats_attempt(&self) {
609 if let Ok(mut stats) = self.stats.lock() {
610 stats.total_attempts += 1;
611 }
612 }
613
614 fn update_stats_success(&self) {
615 if let Ok(mut stats) = self.stats.lock() {
616 stats.successful_resolutions += 1;
617 }
618 }
619
620 fn update_stats_cache_hit(&self) {
621 if let Ok(mut stats) = self.stats.lock() {
622 stats.cache_hits += 1;
623 }
624 }
625}
626
627impl Default for FfiFunctionResolver {
628 fn default() -> Self {
629 Self::new(ResolverConfig::default())
630 }
631}
632
633static GLOBAL_FFI_RESOLVER: std::sync::OnceLock<Arc<FfiFunctionResolver>> =
635 std::sync::OnceLock::new();
636
637pub fn get_global_ffi_resolver() -> Arc<FfiFunctionResolver> {
639 GLOBAL_FFI_RESOLVER
640 .get_or_init(|| Arc::new(FfiFunctionResolver::new(ResolverConfig::default())))
641 .clone()
642}
643
644pub fn initialize_global_ffi_resolver(config: ResolverConfig) -> Arc<FfiFunctionResolver> {
646 let resolver = Arc::new(FfiFunctionResolver::new(config));
647 if GLOBAL_FFI_RESOLVER.set(resolver.clone()).is_err() {
648 tracing::warn!("Global FFI resolver already initialized");
649 }
650 resolver
651}
652
653#[cfg(test)]
654mod tests {
655 use super::*;
656
657 #[test]
658 fn test_known_function_resolution() {
659 let resolver = FfiFunctionResolver::new(ResolverConfig::default());
660
661 let malloc_result = resolver
662 .resolve_function("malloc", None)
663 .expect("Failed to resolve malloc");
664 assert_eq!(malloc_result.library_name, "libc");
665 assert_eq!(malloc_result.function_name, "malloc");
666 assert_eq!(
667 malloc_result.category,
668 FfiFunctionCategory::MemoryManagement
669 );
670 assert_eq!(malloc_result.risk_level, FfiRiskLevel::Medium);
671 }
672
673 #[test]
674 fn test_auto_discovery() {
675 let resolver = FfiFunctionResolver::new(ResolverConfig::default());
676
677 let ssl_result = resolver
678 .resolve_function("SSL_new", None)
679 .expect("Failed to resolve SSL_new");
680 assert_eq!(ssl_result.library_name, "libssl");
681 assert_eq!(ssl_result.category, FfiFunctionCategory::Cryptographic);
682 }
683
684 #[test]
685 fn test_library_hint() {
686 let resolver = FfiFunctionResolver::new(ResolverConfig::default());
687
688 let custom_result = resolver
689 .resolve_function("custom_func", Some("mylib"))
690 .expect("Failed to resolve custom function");
691 assert_eq!(custom_result.library_name, "mylib");
692 assert_eq!(custom_result.function_name, "custom_func");
693 }
694
695 #[test]
696 fn test_risk_assessment() {
697 let resolver = FfiFunctionResolver::new(ResolverConfig::default());
698
699 let strcpy_result = resolver
700 .resolve_function("strcpy", None)
701 .expect("Failed to resolve strcpy");
702 assert_eq!(strcpy_result.risk_level, FfiRiskLevel::Critical);
703
704 let snprintf_result = resolver
705 .resolve_function("snprintf", None)
706 .expect("Failed to resolve snprintf");
707 assert_eq!(snprintf_result.risk_level, FfiRiskLevel::Low);
708 }
709
710 #[test]
711 fn test_batch_resolution() {
712 let resolver = FfiFunctionResolver::new(ResolverConfig::default());
713
714 let functions = vec![
715 "malloc".to_string(),
716 "free".to_string(),
717 "strcpy".to_string(),
718 ];
719 let results = resolver.resolve_functions_batch(&functions);
720
721 assert_eq!(results.len(), 3);
722 assert!(results.iter().all(|r| r.is_ok()));
723 }
724
725 #[test]
726 fn test_custom_function() {
727 let resolver = FfiFunctionResolver::new(ResolverConfig::default());
728
729 let custom_func = ResolvedFfiFunction {
730 library_name: "mylib".to_string(),
731 function_name: "my_func".to_string(),
732 signature: Some("int my_func(void)".to_string()),
733 category: FfiFunctionCategory::UserLibrary,
734 risk_level: FfiRiskLevel::Low,
735 metadata: HashMap::new(),
736 };
737
738 resolver
739 .add_custom_function("my_func".to_string(), custom_func)
740 .expect("Failed to add custom function");
741
742 let resolved = resolver
743 .resolve_function("my_func", None)
744 .expect("Failed to resolve custom function");
745 assert_eq!(resolved.library_name, "mylib");
746 assert_eq!(resolved.category, FfiFunctionCategory::UserLibrary);
747 }
748}