1#![allow(dead_code)]
2use crate::binary::BinaryAnalysis;
3use crate::enterprise::types::{CodeLocation, SeverityLevel};
4use chrono::Utc;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use uuid::Uuid;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct StaticAnalysisResult {
11 pub analysis_id: Uuid,
12 pub file_path: String,
13 pub unsafe_functions: Vec<UnsafeFunctionFinding>,
14 pub memory_issues: Vec<MemoryIssueFinding>,
15 pub system_calls: Vec<SystemCallFinding>,
16 pub hardening_issues: Vec<HardeningFinding>,
17 pub analysis_duration_ms: u64,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct UnsafeFunctionFinding {
22 pub function_name: String,
23 pub location: CodeLocation,
24 pub risk_level: SeverityLevel,
25 pub description: String,
26 pub alternatives: Vec<String>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct MemoryIssueFinding {
31 pub issue_type: MemoryIssueType,
32 pub location: CodeLocation,
33 pub severity: SeverityLevel,
34 pub description: String,
35 pub vulnerable_code: Option<String>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub enum MemoryIssueType {
40 BufferOverflow,
41 UseAfterFree,
42 DoubleFree,
43 MemoryLeak,
44 IntegerOverflow,
45 UnboundedRead,
46 UnboundedWrite,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct SystemCallFinding {
51 pub syscall_name: String,
52 pub location: CodeLocation,
53 pub danger_level: SeverityLevel,
54 pub reason: String,
55 pub mitigation: Option<String>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct HardeningFinding {
60 pub hardening_feature: String,
61 pub status: HardeningStatus,
62 pub recommendation: String,
63 pub impact: SeverityLevel,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub enum HardeningStatus {
68 Missing,
69 Weak,
70 Misconfigured,
71 Present,
72}
73
74pub fn analyze_static_security(analysis: &BinaryAnalysis) -> StaticAnalysisResult {
75 let start_time = Utc::now();
76
77 let mut result = StaticAnalysisResult {
78 analysis_id: Uuid::new_v4(),
79 file_path: analysis.file_name.clone(),
80 unsafe_functions: Vec::new(),
81 memory_issues: Vec::new(),
82 system_calls: Vec::new(),
83 hardening_issues: Vec::new(),
84 analysis_duration_ms: 0,
85 };
86
87 result.unsafe_functions = analyze_unsafe_functions(analysis);
89
90 result.memory_issues = analyze_memory_issues(analysis);
92
93 result.system_calls = analyze_system_calls(analysis);
95
96 result.hardening_issues = analyze_hardening_features(analysis);
98
99 let end_time = Utc::now();
100 result.analysis_duration_ms = (end_time - start_time).num_milliseconds() as u64;
101
102 result
103}
104
105fn analyze_unsafe_functions(analysis: &BinaryAnalysis) -> Vec<UnsafeFunctionFinding> {
106 let mut findings = Vec::new();
107
108 let unsafe_functions: HashMap<&str, (SeverityLevel, &str, Vec<&str>)> = HashMap::from([
110 (
111 "strcpy",
112 (
113 SeverityLevel::High,
114 "Buffer overflow risk - no bounds checking",
115 vec!["strncpy", "strlcpy", "strcpy_s"],
116 ),
117 ),
118 (
119 "strcat",
120 (
121 SeverityLevel::High,
122 "Buffer overflow risk - no bounds checking",
123 vec!["strncat", "strlcat", "strcat_s"],
124 ),
125 ),
126 (
127 "sprintf",
128 (
129 SeverityLevel::High,
130 "Buffer overflow risk - no size limit",
131 vec!["snprintf", "sprintf_s"],
132 ),
133 ),
134 (
135 "vsprintf",
136 (
137 SeverityLevel::High,
138 "Buffer overflow risk - no size limit",
139 vec!["vsnprintf", "vsprintf_s"],
140 ),
141 ),
142 (
143 "gets",
144 (
145 SeverityLevel::Critical,
146 "Always vulnerable to buffer overflow",
147 vec!["fgets", "getline"],
148 ),
149 ),
150 (
151 "scanf",
152 (
153 SeverityLevel::High,
154 "Format string and buffer overflow risks",
155 vec!["fgets with parsing", "scanf_s"],
156 ),
157 ),
158 (
159 "sscanf",
160 (
161 SeverityLevel::Medium,
162 "Format string risks",
163 vec!["sscanf_s", "manual parsing"],
164 ),
165 ),
166 (
167 "memcpy",
168 (
169 SeverityLevel::Medium,
170 "No overlap checking",
171 vec!["memmove", "memcpy_s"],
172 ),
173 ),
174 (
175 "strncpy",
176 (
177 SeverityLevel::Medium,
178 "May not null-terminate",
179 vec!["strlcpy", "strncpy_s"],
180 ),
181 ),
182 (
183 "strncat",
184 (
185 SeverityLevel::Medium,
186 "Complex length calculation",
187 vec!["strlcat", "strncat_s"],
188 ),
189 ),
190 (
191 "realpath",
192 (
193 SeverityLevel::Medium,
194 "Path traversal if misused",
195 vec!["realpath with buffer size check"],
196 ),
197 ),
198 (
199 "mktemp",
200 (
201 SeverityLevel::High,
202 "Race condition vulnerability",
203 vec!["mkstemp", "mkdtemp"],
204 ),
205 ),
206 (
207 "tmpnam",
208 (
209 SeverityLevel::High,
210 "Race condition vulnerability",
211 vec!["tmpfile", "mkstemp"],
212 ),
213 ),
214 (
215 "alloca",
216 (
217 SeverityLevel::Medium,
218 "Stack overflow risk",
219 vec!["malloc with proper cleanup"],
220 ),
221 ),
222 (
223 "setuid",
224 (
225 SeverityLevel::High,
226 "Privilege escalation risk",
227 vec!["proper privilege dropping sequence"],
228 ),
229 ),
230 (
231 "setgid",
232 (
233 SeverityLevel::High,
234 "Privilege escalation risk",
235 vec!["proper privilege dropping sequence"],
236 ),
237 ),
238 (
239 "system",
240 (
241 SeverityLevel::Critical,
242 "Command injection vulnerability",
243 vec!["execve", "posix_spawn"],
244 ),
245 ),
246 (
247 "popen",
248 (
249 SeverityLevel::High,
250 "Command injection risk",
251 vec!["fork + exec pattern"],
252 ),
253 ),
254 (
255 "eval",
256 (
257 SeverityLevel::Critical,
258 "Code injection vulnerability",
259 vec!["safe parsing alternatives"],
260 ),
261 ),
262 (
263 "exec",
264 (
265 SeverityLevel::High,
266 "Command injection if input not sanitized",
267 vec!["execve with argument validation"],
268 ),
269 ),
270 ]);
271
272 for import in &analysis.imports {
274 if let Some((severity, description, alternatives)) = unsafe_functions.get(import.as_str()) {
275 findings.push(UnsafeFunctionFinding {
276 function_name: import.clone(),
277 location: CodeLocation {
278 file_path: analysis.file_name.clone(),
279 line_number: None,
280 column_number: None,
281 function_name: Some(import.clone()),
282 binary_offset: None,
283 },
284 risk_level: severity.clone(),
285 description: description.to_string(),
286 alternatives: alternatives.iter().map(|s| s.to_string()).collect(),
287 });
288 }
289 }
290
291 for symbol in &analysis.detected_symbols {
293 if let Some((severity, description, alternatives)) = unsafe_functions.get(symbol.as_str()) {
294 if !findings.iter().any(|f| f.function_name == *symbol) {
296 findings.push(UnsafeFunctionFinding {
297 function_name: symbol.clone(),
298 location: CodeLocation {
299 file_path: analysis.file_name.clone(),
300 line_number: None,
301 column_number: None,
302 function_name: Some(symbol.clone()),
303 binary_offset: None,
304 },
305 risk_level: severity.clone(),
306 description: description.to_string(),
307 alternatives: alternatives.iter().map(|s| s.to_string()).collect(),
308 });
309 }
310 }
311 }
312
313 findings
314}
315
316fn analyze_memory_issues(analysis: &BinaryAnalysis) -> Vec<MemoryIssueFinding> {
317 let mut findings = Vec::new();
318
319 let has_malloc = analysis.imports.iter().any(|s| s == "malloc")
321 || analysis.detected_symbols.iter().any(|s| s == "malloc");
322 let has_free = analysis.imports.iter().any(|s| s == "free")
323 || analysis.detected_symbols.iter().any(|s| s == "free");
324 let has_calloc = analysis.imports.iter().any(|s| s == "calloc")
325 || analysis.detected_symbols.iter().any(|s| s == "calloc");
326
327 if (has_malloc || has_calloc) && !has_free {
329 findings.push(MemoryIssueFinding {
330 issue_type: MemoryIssueType::MemoryLeak,
331 location: CodeLocation {
332 file_path: analysis.file_name.clone(),
333 line_number: None,
334 column_number: None,
335 function_name: Some("malloc".to_string()),
336 binary_offset: None,
337 },
338 severity: SeverityLevel::Medium,
339 description:
340 "Memory allocation detected without corresponding free() - potential memory leak"
341 .to_string(),
342 vulnerable_code: None,
343 });
344 }
345
346 let size_related_functions = ["malloc", "calloc", "realloc", "memcpy", "memset"];
348 for func in &size_related_functions {
349 if analysis.imports.contains(&func.to_string())
350 || analysis.detected_symbols.contains(&func.to_string())
351 {
352 findings.push(MemoryIssueFinding {
353 issue_type: MemoryIssueType::IntegerOverflow,
354 location: CodeLocation {
355 file_path: analysis.file_name.clone(),
356 line_number: None,
357 column_number: None,
358 function_name: Some(func.to_string()),
359 binary_offset: None,
360 },
361 severity: SeverityLevel::Medium,
362 description: format!(
363 "Function {} may be vulnerable to integer overflow in size calculations",
364 func
365 ),
366 vulnerable_code: None,
367 });
368 }
369 }
370
371 let unbounded_functions = [
373 ("strcpy", MemoryIssueType::UnboundedWrite),
374 ("strcat", MemoryIssueType::UnboundedWrite),
375 ("sprintf", MemoryIssueType::UnboundedWrite),
376 ("gets", MemoryIssueType::UnboundedRead),
377 ];
378
379 for (func, issue_type) in &unbounded_functions {
380 if analysis.imports.contains(&func.to_string())
381 || analysis.detected_symbols.contains(&func.to_string())
382 {
383 findings.push(MemoryIssueFinding {
384 issue_type: issue_type.clone(),
385 location: CodeLocation {
386 file_path: analysis.file_name.clone(),
387 line_number: None,
388 column_number: None,
389 function_name: Some(func.to_string()),
390 binary_offset: None,
391 },
392 severity: SeverityLevel::High,
393 description: format!("Function {} performs unbounded memory operations", func),
394 vulnerable_code: Some(format!("{}(...)", func)),
395 });
396 }
397 }
398
399 findings
400}
401
402fn analyze_system_calls(analysis: &BinaryAnalysis) -> Vec<SystemCallFinding> {
403 let mut findings = Vec::new();
404
405 let dangerous_syscalls: HashMap<&str, (SeverityLevel, &str, Option<&str>)> = HashMap::from([
407 (
408 "system",
409 (
410 SeverityLevel::Critical,
411 "Command injection vulnerability",
412 Some("Use execve() with proper argument validation"),
413 ),
414 ),
415 (
416 "exec",
417 (
418 SeverityLevel::High,
419 "Potential command injection",
420 Some("Validate all arguments and use execve()"),
421 ),
422 ),
423 (
424 "execl",
425 (
426 SeverityLevel::High,
427 "Potential command injection",
428 Some("Validate all arguments"),
429 ),
430 ),
431 (
432 "execlp",
433 (
434 SeverityLevel::High,
435 "Potential command injection",
436 Some("Validate all arguments and avoid PATH dependency"),
437 ),
438 ),
439 (
440 "execle",
441 (
442 SeverityLevel::High,
443 "Potential command injection",
444 Some("Validate all arguments"),
445 ),
446 ),
447 (
448 "execv",
449 (
450 SeverityLevel::High,
451 "Potential command injection",
452 Some("Validate all arguments"),
453 ),
454 ),
455 (
456 "execvp",
457 (
458 SeverityLevel::High,
459 "Potential command injection",
460 Some("Validate all arguments and avoid PATH dependency"),
461 ),
462 ),
463 (
464 "execve",
465 (
466 SeverityLevel::Medium,
467 "Safe if arguments are validated",
468 Some("Ensure all arguments are properly validated"),
469 ),
470 ),
471 (
472 "popen",
473 (
474 SeverityLevel::High,
475 "Command injection risk",
476 Some("Use fork() + execve() pattern instead"),
477 ),
478 ),
479 (
480 "fork",
481 (
482 SeverityLevel::Medium,
483 "Resource exhaustion risk",
484 Some("Implement proper process limiting"),
485 ),
486 ),
487 (
488 "setuid",
489 (
490 SeverityLevel::High,
491 "Privilege escalation risk",
492 Some("Use proper privilege dropping sequence"),
493 ),
494 ),
495 (
496 "setgid",
497 (
498 SeverityLevel::High,
499 "Privilege escalation risk",
500 Some("Use proper privilege dropping sequence"),
501 ),
502 ),
503 (
504 "seteuid",
505 (
506 SeverityLevel::High,
507 "Privilege escalation risk",
508 Some("Use proper privilege dropping sequence"),
509 ),
510 ),
511 (
512 "setegid",
513 (
514 SeverityLevel::High,
515 "Privilege escalation risk",
516 Some("Use proper privilege dropping sequence"),
517 ),
518 ),
519 (
520 "chroot",
521 (
522 SeverityLevel::Medium,
523 "Incomplete sandboxing",
524 Some("Combine with proper privilege dropping"),
525 ),
526 ),
527 (
528 "ptrace",
529 (
530 SeverityLevel::Medium,
531 "Debugging/injection risk",
532 Some("Restrict usage and validate target processes"),
533 ),
534 ),
535 (
536 "mmap",
537 (
538 SeverityLevel::Low,
539 "Memory mapping risks",
540 Some("Use appropriate flags and validate addresses"),
541 ),
542 ),
543 (
544 "mprotect",
545 (
546 SeverityLevel::Medium,
547 "Memory protection bypass",
548 Some("Avoid making pages writable and executable"),
549 ),
550 ),
551 (
552 "dlopen",
553 (
554 SeverityLevel::Medium,
555 "Dynamic loading risk",
556 Some("Validate library paths and use RTLD_NOW"),
557 ),
558 ),
559 (
560 "dlsym",
561 (
562 SeverityLevel::Medium,
563 "Symbol resolution risk",
564 Some("Validate symbol names"),
565 ),
566 ),
567 (
568 "signal",
569 (
570 SeverityLevel::Low,
571 "Signal handling race conditions",
572 Some("Use sigaction() instead"),
573 ),
574 ),
575 (
576 "alarm",
577 (
578 SeverityLevel::Low,
579 "Signal race conditions",
580 Some("Use timer_create() for better control"),
581 ),
582 ),
583 ]);
584
585 for item in analysis
587 .imports
588 .iter()
589 .chain(analysis.detected_symbols.iter())
590 {
591 if let Some((severity, reason, mitigation)) = dangerous_syscalls.get(item.as_str()) {
592 findings.push(SystemCallFinding {
593 syscall_name: item.clone(),
594 location: CodeLocation {
595 file_path: analysis.file_name.clone(),
596 line_number: None,
597 column_number: None,
598 function_name: Some(item.clone()),
599 binary_offset: None,
600 },
601 danger_level: severity.clone(),
602 reason: reason.to_string(),
603 mitigation: mitigation.map(|s| s.to_string()),
604 });
605 }
606 }
607
608 findings
609}
610
611fn analyze_hardening_features(analysis: &BinaryAnalysis) -> Vec<HardeningFinding> {
612 let mut findings = Vec::new();
613
614 let has_stack_protection = analysis
616 .detected_symbols
617 .iter()
618 .any(|s| s.contains("__stack_chk_fail") || s.contains("__stack_chk_guard"))
619 || analysis
620 .imports
621 .iter()
622 .any(|s| s.contains("__stack_chk_fail") || s.contains("__stack_chk_guard"));
623
624 findings.push(HardeningFinding {
625 hardening_feature: "Stack Canaries".to_string(),
626 status: if has_stack_protection {
627 HardeningStatus::Present
628 } else {
629 HardeningStatus::Missing
630 },
631 recommendation: if has_stack_protection {
632 "Stack canaries are present - good security practice".to_string()
633 } else {
634 "Enable stack protection (-fstack-protector-strong or -fstack-protector-all)"
635 .to_string()
636 },
637 impact: SeverityLevel::High,
638 });
639
640 let metadata_str = analysis.metadata.to_string().to_lowercase();
642 let has_pie = metadata_str.contains("pie") || metadata_str.contains("position independent");
643
644 findings.push(HardeningFinding {
645 hardening_feature: "Position Independent Executable (PIE)".to_string(),
646 status: if has_pie {
647 HardeningStatus::Present
648 } else {
649 HardeningStatus::Missing
650 },
651 recommendation: if has_pie {
652 "PIE is enabled - provides ASLR benefits".to_string()
653 } else {
654 "Enable PIE compilation (-fPIE -pie) for ASLR protection".to_string()
655 },
656 impact: SeverityLevel::Medium,
657 });
658
659 let has_relro = analysis.detected_symbols.iter().any(|s| {
661 s.contains("__libc_start_main")
662 && analysis.linked_libraries.iter().any(|l| l.contains("libc"))
663 });
664
665 findings.push(HardeningFinding {
666 hardening_feature: "RELRO (Read-Only Relocations)".to_string(),
667 status: if has_relro {
668 HardeningStatus::Present
669 } else {
670 HardeningStatus::Missing
671 },
672 recommendation: if has_relro {
673 "RELRO appears to be enabled - protects GOT from overwrites".to_string()
674 } else {
675 "Enable full RELRO (-Wl,-z,relro,-z,now) for GOT protection".to_string()
676 },
677 impact: SeverityLevel::Medium,
678 });
679
680 let is_executable_stack = analysis
682 .detected_symbols
683 .iter()
684 .any(|s| s.contains("execstack"));
685
686 findings.push(HardeningFinding {
687 hardening_feature: "NX/DEP (Data Execution Prevention)".to_string(),
688 status: if is_executable_stack {
689 HardeningStatus::Weak
690 } else {
691 HardeningStatus::Present
692 },
693 recommendation: if is_executable_stack {
694 "Executable stack detected - disable with -Wl,-z,noexecstack".to_string()
695 } else {
696 "NX/DEP appears to be enabled - stack is non-executable".to_string()
697 },
698 impact: SeverityLevel::High,
699 });
700
701 let has_fortify = analysis.detected_symbols.iter().any(|s| {
703 s.contains("__printf_chk")
704 || s.contains("__sprintf_chk")
705 || s.contains("__snprintf_chk")
706 || s.contains("__vprintf_chk")
707 });
708
709 findings.push(HardeningFinding {
710 hardening_feature: "FORTIFY_SOURCE".to_string(),
711 status: if has_fortify {
712 HardeningStatus::Present
713 } else {
714 HardeningStatus::Missing
715 },
716 recommendation: if has_fortify {
717 "FORTIFY_SOURCE is enabled - provides runtime buffer overflow detection".to_string()
718 } else {
719 "Enable FORTIFY_SOURCE (-D_FORTIFY_SOURCE=2) for buffer overflow protection".to_string()
720 },
721 impact: SeverityLevel::Medium,
722 });
723
724 findings.push(HardeningFinding {
726 hardening_feature: "Dynamic Linking".to_string(),
727 status: if analysis.static_linked {
728 HardeningStatus::Missing
729 } else {
730 HardeningStatus::Present
731 },
732 recommendation: if analysis.static_linked {
733 "Binary is statically linked - consider dynamic linking for security updates"
734 .to_string()
735 } else {
736 "Binary uses dynamic linking - enables library security updates".to_string()
737 },
738 impact: SeverityLevel::Low,
739 });
740
741 findings
742}
743
744#[cfg(test)]
745mod tests {
746 use super::*;
747 use chrono::Utc;
748
749 fn create_test_analysis() -> BinaryAnalysis {
750 BinaryAnalysis {
751 id: Uuid::new_v4(),
752 file_name: "test.bin".to_string(),
753 format: "elf".to_string(),
754 architecture: "x86_64".to_string(),
755 languages: vec!["C".to_string()],
756 detected_symbols: vec!["malloc".to_string(), "strcpy".to_string()],
757 embedded_strings: vec![],
758 suspected_secrets: vec![],
759 imports: vec!["system".to_string(), "gets".to_string()],
760 exports: vec![],
761 hash_sha256: "test".to_string(),
762 hash_blake3: None,
763 size_bytes: 1024,
764 linked_libraries: vec!["libc.so.6".to_string()],
765 static_linked: false,
766 version_info: None,
767 license_info: None,
768 metadata: serde_json::json!({}),
769 created_at: Utc::now(),
770 sbom: None,
771 binary_data: None,
772 entry_point: None,
773 code_sections: Vec::new(),
774 }
775 }
776
777 #[test]
778 fn test_analyze_unsafe_functions() {
779 let analysis = create_test_analysis();
780 let findings = analyze_unsafe_functions(&analysis);
781
782 assert!(!findings.is_empty());
783 assert!(findings.iter().any(|f| f.function_name == "strcpy"));
784 assert!(findings.iter().any(|f| f.function_name == "gets"));
785 assert!(findings.iter().any(|f| f.function_name == "system"));
786 }
787
788 #[test]
789 fn test_analyze_static_security() {
790 let analysis = create_test_analysis();
791 let result = analyze_static_security(&analysis);
792
793 assert_eq!(result.file_path, "test.bin");
794 assert!(!result.unsafe_functions.is_empty());
795 assert!(!result.system_calls.is_empty());
796 assert!(!result.hardening_issues.is_empty());
797 }
798}