/**
 * Memory Safety Scanner
 *
 * Detects memory corruption vulnerabilities in C/C++/Rust unsafe code.
 *
 * Vulnerability types:
 * - Buffer overflow (CWE-120, CWE-787)
 * - Use-after-free (CWE-416)
 * - Double free (CWE-415)
 * - Null dereference (CWE-476)
 * - Uninitialized memory (CWE-908)
 * - Format string vulnerabilities (CWE-134)
 *
 * Tools:
 * - cppcheck for C/C++
 * - cargo-geiger for Rust unsafe code audit
 * - Pattern matching for dangerous function calls
 */
import { exec } from "child_process";
import { promisify } from "util";
import * as fs from "fs/promises";
import * as path from "path";
import { glob } from "glob";
const execAsync = promisify(exec);
const DANGEROUS_C_PATTERNS = [
    {
        pattern: /\bstrcpy\s*\(/g,
        severity: "high",
        cwe: "CWE-120",
        ruleId: "memory:unsafe-strcpy",
        message: "Use of strcpy() is unsafe. Use strncpy() or strlcpy() instead.",
        replacement: "strncpy() or strlcpy()",
    },
    {
        pattern: /\bsprintf\s*\(/g,
        severity: "high",
        cwe: "CWE-134",
        ruleId: "memory:unsafe-sprintf",
        message: "Use of sprintf() can cause buffer overflow. Use snprintf() instead.",
        replacement: "snprintf()",
    },
    {
        pattern: /\bgets\s*\(/g,
        severity: "critical",
        cwe: "CWE-242",
        ruleId: "memory:banned-gets",
        message: "gets() is banned - it cannot limit input size. Use fgets() instead.",
        replacement: "fgets()",
    },
    {
        pattern: /\bstrcat\s*\(/g,
        severity: "high",
        cwe: "CWE-120",
        ruleId: "memory:unsafe-strcat",
        message: "Use of strcat() is unsafe. Use strncat() or strlcat() instead.",
        replacement: "strncat() or strlcat()",
    },
    {
        pattern: /\bscanf\s*\(\s*"[^"]*%s/g,
        severity: "high",
        cwe: "CWE-120",
        ruleId: "memory:unsafe-scanf",
        message: "scanf() with %s can overflow buffer. Use width specifier like %255s.",
    },
    {
        pattern: /\bvsprintf\s*\(/g,
        severity: "high",
        cwe: "CWE-134",
        ruleId: "memory:unsafe-vsprintf",
        message: "Use of vsprintf() can cause buffer overflow. Use vsnprintf() instead.",
        replacement: "vsnprintf()",
    },
    {
        pattern: /\brealpath\s*\([^,]+,\s*NULL\s*\)/g,
        severity: "medium",
        cwe: "CWE-120",
        ruleId: "memory:realpath-null",
        message: "realpath() with NULL resolved_path relies on malloc which may fail.",
    },
    {
        pattern: /\bstrtok\s*\(/g,
        severity: "medium",
        cwe: "CWE-362",
        ruleId: "memory:unsafe-strtok",
        message: "strtok() uses static buffer and is not thread-safe. Use strtok_r().",
        replacement: "strtok_r()",
    },
    {
        pattern: /\balloca\s*\(/g,
        severity: "high",
        cwe: "CWE-770",
        ruleId: "memory:unsafe-alloca",
        message: "alloca() can cause stack overflow with large sizes. Use heap allocation.",
    },
    {
        pattern: /\bfree\s*\(\s*(\w+)\s*\)\s*;[^}]*\bfree\s*\(\s*\1\s*\)/g,
        severity: "critical",
        cwe: "CWE-415",
        ruleId: "memory:potential-double-free",
        message: "Potential double-free detected. Ensure pointer is set to NULL after free.",
    },
];
// Rust unsafe pattern detection
const RUST_UNSAFE_PATTERNS = [
    {
        pattern: /unsafe\s*\{[^}]*\*\s*\w+\.as_ptr\(\)/g,
        severity: "medium",
        cwe: "CWE-119",
        ruleId: "memory:rust-raw-pointer-deref",
        message: "Raw pointer dereference in unsafe block. Verify pointer validity.",
    },
    {
        pattern: /unsafe\s*\{[^}]*std::mem::transmute/g,
        severity: "high",
        cwe: "CWE-843",
        ruleId: "memory:rust-transmute",
        message: "std::mem::transmute can cause undefined behavior. Consider safer alternatives.",
    },
    {
        pattern: /unsafe\s*\{[^}]*std::ptr::read\s*\(/g,
        severity: "medium",
        cwe: "CWE-908",
        ruleId: "memory:rust-ptr-read",
        message: "std::ptr::read may read uninitialized memory. Ensure pointer is valid and initialized.",
    },
    {
        pattern: /#\[no_mangle\][\s\S]*?pub\s+extern/g,
        severity: "info",
        cwe: "CWE-119",
        ruleId: "memory:rust-ffi-boundary",
        message: "FFI boundary detected. Ensure proper validation of foreign inputs.",
    },
];
/**
 * Check if cppcheck is available
 */
export async function checkCppcheckAvailable() {
    try {
        await execAsync("cppcheck --version");
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Check if cargo-geiger is available
 */
export async function checkCargoGeigerAvailable() {
    try {
        await execAsync("cargo geiger --version");
        return true;
    }
    catch {
        return false;
    }
}
/**
 * Detect languages that may have memory safety issues
 */
export async function detectUnsafeLanguages(projectPath) {
    const detection = {
        cpp: false,
        rust: false,
        nodeNative: false,
    };
    const absolutePath = path.resolve(projectPath);
    // Check for C/C++
    const cppPatterns = ["**/*.c", "**/*.cpp", "**/*.cc", "**/*.cxx", "**/*.h", "**/*.hpp"];
    for (const pattern of cppPatterns) {
        const files = await glob(pattern, {
            cwd: absolutePath,
            ignore: ["**/node_modules/**", "**/vendor/**", "**/build/**", "**/dist/**"],
            nodir: true,
        });
        if (files.length > 0) {
            detection.cpp = true;
            break;
        }
    }
    // Check for Rust
    const cargoFiles = await glob("**/Cargo.toml", {
        cwd: absolutePath,
        ignore: ["**/node_modules/**", "**/target/**"],
    });
    detection.rust = cargoFiles.length > 0;
    // Check for Node.js native addons
    const bindingFiles = await glob("**/binding.gyp", {
        cwd: absolutePath,
        ignore: ["**/node_modules/**"],
    });
    detection.nodeNative = bindingFiles.length > 0;
    return detection;
}
/**
 * Run cppcheck on C/C++ files
 */
async function runCppcheck(projectPath, timeout = 120000) {
    const findings = [];
    const absolutePath = path.resolve(projectPath);
    try {
        // Find C/C++ source files
        const cppFiles = await glob("**/*.{c,cpp,cc,cxx}", {
            cwd: absolutePath,
            ignore: ["**/node_modules/**", "**/vendor/**", "**/build/**", "**/dist/**", "**/test/**"],
            nodir: true,
        });
        if (cppFiles.length === 0) {
            return findings;
        }
        // Run cppcheck with XML output for structured parsing
        const { stdout, stderr } = await execAsync(`cppcheck --enable=all --xml --xml-version=2 ${cppFiles.slice(0, 100).map(f => `"${f}"`).join(" ")} 2>&1`, {
            cwd: absolutePath,
            timeout,
            maxBuffer: 10 * 1024 * 1024,
        });
        const output = stdout || stderr;
        // Parse XML output for errors
        const errorRegex = /<error[^>]*id="([^"]*)"[^>]*severity="([^"]*)"[^>]*msg="([^"]*)"[^>]*>/g;
        const locationRegex = /<location[^>]*file="([^"]*)"[^>]*line="(\d+)"[^>]*column="(\d+)"[^>]*/;
        let match;
        while ((match = errorRegex.exec(output)) !== null) {
            const [, id, severity, msg] = match;
            // Find the location tag after this error
            const errorEndIndex = output.indexOf("</error>", match.index);
            const errorBlock = output.slice(match.index, errorEndIndex);
            const locationMatch = locationRegex.exec(errorBlock);
            const mapCppcheckSeverity = (sev) => {
                switch (sev) {
                    case "error": return "high";
                    case "warning": return "medium";
                    case "style": return "low";
                    case "performance": return "low";
                    case "portability": return "info";
                    case "information": return "info";
                    default: return "medium";
                }
            };
            // Map cppcheck IDs to CWEs
            const cweMapping = {
                bufferAccessOutOfBounds: "CWE-120",
                nullPointer: "CWE-476",
                nullPointerRedundantCheck: "CWE-476",
                uninitvar: "CWE-908",
                memleak: "CWE-401",
                resourceLeak: "CWE-404",
                arrayIndexOutOfBounds: "CWE-787",
                negativeIndex: "CWE-786",
                doubleFree: "CWE-415",
                deallocuse: "CWE-416",
                invalidFree: "CWE-590",
            };
            findings.push({
                scanner: "memory-safety",
                ruleId: `cppcheck:${id}`,
                file: locationMatch ? locationMatch[1] : "unknown",
                line: locationMatch ? parseInt(locationMatch[2], 10) : 0,
                column: locationMatch ? parseInt(locationMatch[3], 10) : 0,
                message: msg,
                severity: mapCppcheckSeverity(severity),
                confidence: 90,
                category: "memory-safety",
                cweIds: cweMapping[id] ? [cweMapping[id]] : [],
                evidence: `cppcheck severity: ${severity}`,
                metadata: { tool: "cppcheck", checkId: id },
            });
        }
    }
    catch (error) {
        // cppcheck failed or timed out
        if (error instanceof Error && error.message.includes("TIMEOUT")) {
            findings.push({
                scanner: "memory-safety",
                ruleId: "memory:cppcheck-timeout",
                file: projectPath,
                line: 0,
                column: 0,
                message: "cppcheck analysis timed out. Consider analyzing fewer files.",
                severity: "info",
                confidence: 100,
                category: "memory-safety",
                cweIds: [],
                evidence: "Timeout during analysis",
                metadata: { tool: "cppcheck" },
            });
        }
    }
    return findings;
}
/**
 * Run cargo-geiger for Rust unsafe code detection
 */
async function runCargoGeiger(projectPath, timeout = 120000) {
    const findings = [];
    const absolutePath = path.resolve(projectPath);
    try {
        // Find Cargo.toml files
        const cargoFiles = await glob("**/Cargo.toml", {
            cwd: absolutePath,
            ignore: ["**/node_modules/**", "**/target/**"],
        });
        for (const cargoFile of cargoFiles) {
            const cargoDir = path.dirname(path.join(absolutePath, cargoFile));
            try {
                const { stdout } = await execAsync("cargo geiger --output-format Json 2>/dev/null || cargo geiger 2>&1", {
                    cwd: cargoDir,
                    timeout,
                    maxBuffer: 10 * 1024 * 1024,
                });
                // Parse JSON output if available
                try {
                    const data = JSON.parse(stdout);
                    if (data.packages) {
                        for (const pkg of data.packages) {
                            if (pkg.unsafety && pkg.unsafety.used > 0) {
                                findings.push({
                                    scanner: "memory-safety",
                                    ruleId: "rust:unsafe-usage",
                                    file: cargoFile,
                                    line: 0,
                                    column: 0,
                                    message: `Package ${pkg.name} uses ${pkg.unsafety.used} unsafe blocks. Review for memory safety.`,
                                    severity: "medium",
                                    confidence: 100,
                                    category: "memory-safety",
                                    cweIds: ["CWE-119"],
                                    evidence: `unsafe blocks: ${pkg.unsafety.used}, unsafe functions: ${pkg.unsafety.functions || 0}`,
                                    metadata: { tool: "cargo-geiger", package: pkg.name },
                                });
                            }
                        }
                    }
                }
                catch {
                    // Not JSON, parse text output
                    const unsafeMatch = stdout.match(/(\d+)\s+unsafe\s+blocks/);
                    if (unsafeMatch && parseInt(unsafeMatch[1], 10) > 0) {
                        findings.push({
                            scanner: "memory-safety",
                            ruleId: "rust:unsafe-usage",
                            file: cargoFile,
                            line: 0,
                            column: 0,
                            message: `Rust project contains ${unsafeMatch[1]} unsafe blocks. Review for memory safety.`,
                            severity: "medium",
                            confidence: 100,
                            category: "memory-safety",
                            cweIds: ["CWE-119"],
                            evidence: `cargo geiger detected ${unsafeMatch[1]} unsafe blocks`,
                            metadata: { tool: "cargo-geiger" },
                        });
                    }
                }
            }
            catch {
                // cargo-geiger failed for this crate
            }
        }
    }
    catch {
        // No Rust projects or cargo-geiger unavailable
    }
    return findings;
}
/**
 * Pattern-based analysis for dangerous function calls
 */
async function runPatternAnalysis(projectPath, patterns, filePatterns) {
    const findings = [];
    const absolutePath = path.resolve(projectPath);
    try {
        const files = await glob(filePatterns.join(","), {
            cwd: absolutePath,
            ignore: ["**/node_modules/**", "**/vendor/**", "**/build/**", "**/dist/**"],
            nodir: true,
        });
        for (const file of files.slice(0, 200)) { // Limit to 200 files
            try {
                const content = await fs.readFile(path.join(absolutePath, file), "utf-8");
                const lines = content.split("\n");
                for (const pattern of patterns) {
                    // Reset regex lastIndex
                    pattern.pattern.lastIndex = 0;
                    let match;
                    while ((match = pattern.pattern.exec(content)) !== null) {
                        // Find line number
                        const upToMatch = content.slice(0, match.index);
                        const lineNumber = upToMatch.split("\n").length;
                        const columnNumber = match.index - upToMatch.lastIndexOf("\n");
                        findings.push({
                            scanner: "memory-safety",
                            ruleId: pattern.ruleId,
                            file,
                            line: lineNumber,
                            column: columnNumber,
                            message: pattern.message,
                            severity: pattern.severity,
                            confidence: 95,
                            category: "memory-safety",
                            cweIds: [pattern.cwe],
                            evidence: `Found: ${match[0].trim()}`,
                            metadata: {
                                suggestion: pattern.replacement,
                            },
                        });
                    }
                }
            }
            catch {
                // Could not read file
            }
        }
    }
    catch {
        // Glob failed
    }
    return findings;
}
/**
 * Analyze Node.js native addon source files
 */
async function analyzeNodeNativeSource(projectPath) {
    const findings = [];
    const absolutePath = path.resolve(projectPath);
    // Find binding.gyp files and analyze associated source
    const bindingFiles = await glob("**/binding.gyp", {
        cwd: absolutePath,
        ignore: ["**/node_modules/**"],
    });
    for (const bindingFile of bindingFiles) {
        const bindingDir = path.dirname(path.join(absolutePath, bindingFile));
        // Look for source files in the same directory
        const sourceFiles = await glob("**/*.{c,cpp,cc}", {
            cwd: bindingDir,
            nodir: true,
        });
        for (const sourceFile of sourceFiles) {
            try {
                const content = await fs.readFile(path.join(bindingDir, sourceFile), "utf-8");
                const relativePath = path.relative(absolutePath, path.join(bindingDir, sourceFile));
                // Check for N-API buffer handling issues
                if (content.includes("napi_get_buffer_info") && !content.includes("napi_create_external_buffer")) {
                    if (!content.includes("length") || content.match(/napi_get_buffer_info[^;]+;[^}]*\[[^\]]*\]/)) {
                        findings.push({
                            scanner: "memory-safety",
                            ruleId: "memory:napi-buffer-bounds",
                            file: relativePath,
                            line: 0,
                            column: 0,
                            message: "N-API buffer access may not check bounds. Verify buffer length before access.",
                            severity: "medium",
                            confidence: 70,
                            category: "memory-safety",
                            cweIds: ["CWE-120"],
                            evidence: "napi_get_buffer_info without apparent bounds checking",
                            metadata: { context: "Node.js native addon" },
                        });
                    }
                }
                // Check for raw pointer casts from JS values
                if (content.match(/reinterpret_cast.*napi_value/) || content.match(/\(void\s*\*\).*napi/)) {
                    findings.push({
                        scanner: "memory-safety",
                        ruleId: "memory:napi-unsafe-cast",
                        file: relativePath,
                        line: 0,
                        column: 0,
                        message: "Unsafe pointer cast from N-API value. Validate type before casting.",
                        severity: "high",
                        confidence: 80,
                        category: "memory-safety",
                        cweIds: ["CWE-843"],
                        evidence: "reinterpret_cast or C-style cast on napi values",
                        metadata: { context: "Node.js native addon" },
                    });
                }
            }
            catch {
                // Could not read source file
            }
        }
    }
    return findings;
}
/**
 * Main entry point: Run memory safety analysis on a project
 */
export async function runMemorySafetyAnalysis(projectPath, options) {
    const startTime = Date.now();
    const findings = [];
    const timeout = options?.timeout || 120000;
    try {
        // 1. Detect languages with potential memory safety issues
        const languages = await detectUnsafeLanguages(projectPath);
        if (!languages.cpp && !languages.rust && !languages.nodeNative) {
            return {
                scanner: "memory-safety",
                findings: [],
                duration: Date.now() - startTime,
                success: true,
                metadata: {
                    message: "No C/C++/Rust code detected",
                    languages,
                },
            };
        }
        // 2. Check tool availability
        const hasCppcheck = await checkCppcheckAvailable();
        const hasCargoGeiger = await checkCargoGeigerAvailable();
        // 3. Run C/C++ analysis
        if (languages.cpp) {
            // Run cppcheck if available
            if (hasCppcheck) {
                const cppcheckFindings = await runCppcheck(projectPath, timeout);
                findings.push(...cppcheckFindings);
            }
            // Always run pattern analysis as baseline
            const patternFindings = await runPatternAnalysis(projectPath, DANGEROUS_C_PATTERNS, ["**/*.c", "**/*.cpp", "**/*.cc", "**/*.cxx", "**/*.h", "**/*.hpp"]);
            findings.push(...patternFindings);
        }
        // 4. Run Rust analysis
        if (languages.rust) {
            // Run cargo-geiger if available
            if (hasCargoGeiger) {
                const geigerFindings = await runCargoGeiger(projectPath, timeout);
                findings.push(...geigerFindings);
            }
            // Run pattern analysis for unsafe code
            const rustPatternFindings = await runPatternAnalysis(projectPath, RUST_UNSAFE_PATTERNS, ["**/*.rs"]);
            findings.push(...rustPatternFindings);
        }
        // 5. Analyze Node.js native addons
        if (languages.nodeNative) {
            const nodeFindings = await analyzeNodeNativeSource(projectPath);
            findings.push(...nodeFindings);
        }
        // Deduplicate findings by file+line+ruleId
        const seen = new Set();
        const dedupedFindings = findings.filter(f => {
            const key = `${f.file}:${f.line}:${f.ruleId}`;
            if (seen.has(key))
                return false;
            seen.add(key);
            return true;
        });
        return {
            scanner: "memory-safety",
            findings: dedupedFindings,
            duration: Date.now() - startTime,
            success: true,
            metadata: {
                languages,
                toolsUsed: {
                    cppcheck: hasCppcheck && languages.cpp,
                    cargoGeiger: hasCargoGeiger && languages.rust,
                    patternAnalysis: true,
                },
            },
        };
    }
    catch (error) {
        return {
            scanner: "memory-safety",
            findings,
            duration: Date.now() - startTime,
            success: false,
            error: error instanceof Error ? error.message : "Unknown error",
        };
    }
}
//# sourceMappingURL=memory-safety.js.map