nabla_cli/enterprise/secure/
static_analysis.rs

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    // Analyze unsafe functions from imports and symbols
88    result.unsafe_functions = analyze_unsafe_functions(analysis);
89
90    // Analyze memory-related security issues
91    result.memory_issues = analyze_memory_issues(analysis);
92
93    // Analyze dangerous system calls
94    result.system_calls = analyze_system_calls(analysis);
95
96    // Analyze hardening features
97    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    // Define dangerous C functions and their safer alternatives
109    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    // Check imports for unsafe functions
273    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    // Check detected symbols for unsafe functions
292    for symbol in &analysis.detected_symbols {
293        if let Some((severity, description, alternatives)) = unsafe_functions.get(symbol.as_str()) {
294            // Avoid duplicates from imports
295            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    // Check for potential memory management issues based on function usage patterns
320    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    // Check for memory allocation without corresponding deallocation
328    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    // Check for potential integer overflow in size calculations
347    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    // Check for unbounded memory operations
372    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    // Define dangerous system calls and their risk levels
406    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    // Check imports and symbols for dangerous system calls
586    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    // Check for stack canaries (GCC stack protection)
615    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    // Check for position independent executable (PIE) indicators
641    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    // Check for RELRO (Read-Only Relocations)
660    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    // Check for NX/DEP (Data Execution Prevention)
681    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    // Check for format string protections
702    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    // Check for static linking (can be a hardening concern)
725    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}