📄 04-vulnerabilities.md
⬇ Download Raw

Security Vulnerability Audit Report — ScorpioX Clang Codebase

Audit Date: 2026-04-28 Auditor: Security Audit Agent (automated) Codebase: scorpiox/clang — 148 C source files, 74 headers, ~141,971 lines of code (excluding vendor) Scope: C-specific security vulnerabilities: buffer overflows, memory safety, format strings, integer overflows, use-after-free, command injection, race conditions

Executive Summary

The ScorpioX codebase is a large, multi-component C project comprising a terminal multiplexer, email server (SMTP/IMAP), HTTP server, AI provider clients, a KVM-based VM hypervisor, file transfer utilities, and more. The project demonstrates good security awareness in many areas — using snprintf over sprintf in 2,172 out of 2,192 format calls, performing bounds-checked header parsing in the HTTP server, and sanitizing filenames in network receive paths. However, critical and high-severity vulnerabilities exist, primarily in shell command injection (232 system()/popen() calls, many with insufficiently sanitized input), missing null checks after allocation (166 instances), and 20 unbounded sprintf calls that can cause heap/stack buffer overflows. The email server and HTTP server — both network-facing — have the highest exposure.


Vulnerability Summary

#SeverityCVSSLocationCategoryDescription
V-01CRITICAL9.8sxmail_maildir.c:704Command Injectionsystem("rm -rf ''") — single-quote in folder name = arbitrary shell command execution
V-02CRITICAL9.8scorpiox-vm.c:488-493Command Injectionsystem(cmd) with URL/path from user config passed through curl/wget — single-quote injection
V-03CRITICAL9.1sx.c:1318,1575,2109,2262,2458Command InjectionMultiple system() calls building commands from session data, config values, file paths
V-04CRITICAL9.1scorpiox-agent.c (12 calls)Command Injectionsystem() and popen() with agent-provided paths, find -exec with unsanitized directory names
V-05HIGH8.1libsxnet/sx_http_wasm.c:388-460Buffer Overflowsprintf chain into static char json[32768] — escape expansion (\uXXXX) can exceed needed estimate
V-06HIGH8.1libsxnet/sx_api.c:367-404Buffer Overflowsprintf chain into heap buffer — size estimate uses +16 per field but sprintf format overhead can exceed this
V-07HIGH7.8scorpiox-wsl.c:1278-1279Buffer Overflowstrcat(command_buf, argv[i]) in loop into char command_buf[4096] — no bounds checking
V-08HIGH7.8scorpiox-traffic.c:926-927Buffer Overflowstrcat(cmdline, argv[i]) in loop into char cmdline[4096] — no bounds checking
V-09HIGH7.5166 locations (see §5)Null Derefmalloc/calloc/realloc return values used without null checks — crash on OOM
V-10HIGH7.5libsxnet/sx_mcp.c:701Null Deref + Overflowrealloc(buf, cap) in loop with cap = 2 — no null check, potential integer overflow on cap
V-11HIGH7.5libsxnet/sx_http.c:44Integer Overflowsize nmemb multiplication in curl write callback — can overflow size_t on 32-bit platforms
V-12MEDIUM6.5libsxnet/sx_http_wasm.c:39,303,388,639Thread SafetyMultiple static char buffers (up to 65536 bytes) — data races if called from multiple threads
V-13MEDIUM6.5sx_dll.c:288Fixed Bufferchar enqueue_buf[8192] — shared across agent messages, no overflow check on concatenation
V-14MEDIUM6.1sx_term.c:1840Buffer Overflowsprintf(osc, ...) with b64 string — safe (allocated as b64_size + 20) but relies on exact math
V-15MEDIUM5.9scorpiox-server.c:1233Input ValidationContent-Length parsed via strtoll without upper-bound validation — can cause huge allocations
V-16MEDIUM5.520 sprintf callsBuffer OverflowUnbounded sprintf calls (see §2 for full list) — heap or stack corruption risk
V-17MEDIUM5.3sxmail_smtp.c:365Missing Null Checkrealloc for SMTP DATA accumulation — null return causes use-after-free of old buffer
V-18MEDIUM5.3scorpiox-unshare.c:140Buffer Overflowstrcpy(buf + dlen + 1, prog) — no bounds check on prog length vs remaining buffer space
V-19LOW4.0sx_term.c:515-518Signal SafetySIGWINCH handler sets g_term.resized = true — safe (signal-flag pattern), but signal() used instead of sigaction()
V-20LOW3.773 compiler warningsIgnored Returnssystem(), read(), write(), chdir(), symlink() return values ignored — silent failures
V-21LOW3.3scorpiox-unshare.c:124-141TOCTOUaccess() check then execve() — time-of-check-time-of-use race on file existence
V-22LOW3.028 strcpy callsBounded CopyMost strcpy calls copy string literals or bounded values — low risk but should use snprintf
V-23INFO2.0scorpiox-whatsapp.c:156Input Parsingsscanf for version parsing — bounded by format but lacks input length validation

1. Buffer Overflow Analysis

1.1 Unbounded sprintf Calls (20 instances)

The codebase contains 20 sprintf calls without bounds checking. While the project predominantly uses snprintf (2,172 calls), the remaining sprintf calls represent real overflow risks:

Critical sprintf chains:
FileLinePatternRisk
sx_http_wasm.c394-451p += sprintf(p, ...) chain building JSON into static char[32768]The needed calculation uses *2 for escaping but \uXXXX expands 1→6 chars (6x). If body contains many control characters, actual expansion exceeds estimate. The if (needed > sizeof(json)) check uses the underestimate.
sx_api.c367-404offset += sprintf(buf + offset, ...) chainBuffer total calculation uses +16 per field for format overhead, but sprintf("%zu:", len) for large len values can produce 20+ digit strings. Multiple fields compound the undercount.
sx_slashcmd.c1118sprintf(p, "- %s: ", name)No remaining-space tracking in loop building description string
sx_exec_wasm_builtins.c1778-1781sprintf(new_json, "[%s]", entry) / sprintf(new_json + len - 1, ",%s]", entry)entry could be arbitrarily large
scorpiox-mcp.c1192sprintf(out, "Error: %s", estr)If estr exceeds out buffer
Recommendation: Replace all sprintf calls with snprintf and check return values.

1.2 Unbounded strcpy and strcat (37 instances)

High-risk strcat in loops:
// scorpiox-wsl.c:1278-1279 — argv concatenation into fixed buffer

char command_buf[4096] = {0};

for (int i = arg_start + 1; i < argc; i++) {

if (command_buf[0]) strcat(command_buf, " ");

strcat(command_buf, argv[i]); // NO BOUNDS CHECK

}

// scorpiox-traffic.c:926-927 — same pattern

char cmdline[4096] = {0};

for (int i = 0; i < argc; i++) {

if (i > 0) strcat(cmdline, " ");

strcat(cmdline, argv[i]); // NO BOUNDS CHECK

}

Long argument lists will overflow these 4096-byte stack buffers, corrupting the return address.

High-risk strcpy:
// scorpiox-unshare.c:140

strcpy(buf + dlen + 1, prog); // prog length unchecked

// scorpiox-agent.c:1452

strcpy(out + len - 4, ".dll"); // assumes len >= 4

1.3 Stack Buffer Analysis

The codebase uses 2,311 stack-allocated char buffers. Notable large allocations:

Stack exhaustion risk on deeply nested calls with 8KB+ buffers per frame.


2. Format String Analysis

2.1 Direct Format String Vulnerabilities

No classic format string vulnerabilities were found (i.e., printf(user_input) without format string). All printf/fprintf calls use string literals as format specifiers. This is well-implemented.

2.2 Indirect Format Risks

The snprintf calls that build command strings for system() can indirectly introduce format-related issues if the resulting command string is interpreted by the shell. This is primarily a command injection risk (see §6).


3. Integer Overflow Analysis

3.1 Multiplication Before Allocation

// sx_http.c:44 — curl write callback

size_t real_size = size * nmemb; // Can overflow on 32-bit

While curl guarantees size == 1, the contract allows arbitrary values. On 32-bit platforms, size * nmemb can wrap to a small value, causing subsequent memcpy to write beyond the buffer.

// sx_http.c:48 — doubling strategy

size_t newcap = buf->cap * 2; // Can overflow to 0

If cap is near SIZE_MAX/2, doubling wraps to 0 or a small value. The follow-up realloc(buf->data, newcap) with newcap=0 frees the buffer, and subsequent memcpy writes to freed memory.

3.2 Integer Truncation

// sxtmux_backend_sxmux.c:62

int len = (int)strlen(keys); // Truncates on strings > 2GB (theoretical)

// sxmux_session.c:28

int len = (int)strlen(tmp);

19 instances of casting strlen() (returns size_t) to int. On 64-bit systems this truncates for strings > 2GB. While unlikely in practice, this violates C safety invariants.

3.3 realloc Doubling Overflow

// sx_mcp.c:701

while (len + need >= cap) { cap *= 2; buf = realloc(buf, cap); }

Same pattern in sx_provider_gemini_wasm.c:56, sx_provider_openai.c:323.


4. Memory Safety Analysis

4.1 Missing Null Checks After Allocation (166 instances)

166 malloc/calloc/realloc calls lack null-pointer checks. These will crash on out-of-memory conditions. Key high-risk locations:
FileLineCallImpact
sx_http.c156malloc(8192) — HTTP response bufferCrash during active HTTP request
sx_frp.c493malloc(st->read_buf_size) — FRP read bufferCrash in network relay
sx_provider.c151calloc(1, sizeof(SxProvider))Crash on provider init
sx_api.c25, 61calloc(SX_INITIAL_HISTORY, ...)Crash on API init
sx_bgtask.c210malloc(SX_BGTASK_OUTPUT_MAX) — background task outputCrash during task creation
sx_mcp.c701realloc(buf, cap) — MCP tool listNull deref + use-after-free

4.2 Potential Use-After-Free

The realloc pattern without null check is the primary use-after-free risk:

// When realloc fails, it returns NULL but does NOT free the old pointer.

// However, assigning the result to the same variable:

buf = realloc(buf, cap); // If this returns NULL, old buf is leaked

// and buf is now NULL — subsequent use = crash

This pattern appears in:

4.3 Memory Leaks

Return paths that skip free():


5. Command Injection Analysis

5.1 Overview

The codebase contains 232 system()/popen()/exec*() calls. This is the most significant attack surface.

5.2 Critical: Shell Metacharacter Injection via Single Quotes

Multiple locations use single-quote wrapping to "protect" user input in shell commands:

// sxmail_maildir.c:704 — CRITICAL: email folder name from IMAP client

snprintf(cmd, sizeof(cmd), "rm -rf '%s'", path);

system(cmd); // If path contains: foo'; rm -rf /; echo '

// scorpiox-vm.c:488 — CRITICAL: URL from config

snprintf(cmd, sizeof(cmd), "curl -fSL --progress-bar -o '%s' '%s'", dest, url);

system(cmd); // If url contains single quotes

// scorpiox-unshare.c:427

snprintf(cmd, sizeof(cmd), "tar xf '%s' -C '%s'", tar_path, rootfs_dir);

// scorpiox-server.c:688

snprintf(cmd, sizeof(cmd), "git -C '%s' rev-parse HEAD 2>&1", repo_dir);

Single-quote wrapping is not sufficient — an attacker providing a value like '; malicious_command; echo ' breaks out of the quotes.

5.3 Critical: Argument Concatenation

// scorpiox-agent.c:1313 — find -exec with agent directory

snprintf(cmd, sizeof(cmd),

"find '%s' -name '*.csproj' -exec grep -li 'scorpiox\\.agent\\.dotnet' {} \\;",

agent_dir);

FILE *fp = popen(cmd, "r");

5.4 High-Risk system() Calls by Component

ComponentCountUser Input PathRisk
scorpiox-wsl.c12Distro names, bind paths, WSL argsHigh — user-provided distro names flow to system()
scorpiox-unshare.c15Rootfs paths, tar paths, package listsHigh — paths from config
scorpiox-agent.c8Agent directory, hook scriptsCritical — agent-provided paths
sxmail_maildir.c2Folder name from IMAP DELETECritical — network input
scorpiox-vm.c4Image URLs, file pathsHigh — URLs from user
sx.c8Session IDs, file paths, voice commandsMedium — local user input
scorpiox-server.c2Git repo paths (CGI)High — web-accessible
Recommendation: Replace all system()/popen() calls with fork()+execve() with explicit argv arrays, or implement proper shell-escaping functions.

6. Race Condition Analysis

6.1 Threading Model

The codebase uses POSIX threads in two primary areas:

Both use pthread_mutex_lock/unlock consistently. No obvious data races in the mutex-protected sections.

6.2 Thread-Unsafe Static Buffers

sx_http_wasm.c uses multiple static buffers:
static char sx_xhr_response_buf[65536];  // Line 39

static char json[4096]; // Line 303

static char json[32768]; // Line 388

static char body_copy[65536]; // Line 639

These are in WASM context (single-threaded), so this is low-risk in practice but violates safe coding principles.

6.3 Signal Handler Safety

// sx_term.c:515

static void term_sigwinch(int sig) {

(void)sig;

g_term.resized = true; // Safe: volatile-style flag pattern

}

The handler only sets a boolean flag — this is the correct async-signal-safe pattern. However, signal() is used instead of sigaction(), which has undefined behavior for re-entrant signals on some platforms.

6.4 TOCTOU (Time-of-Check-Time-of-Use)

// scorpiox-unshare.c:124,141

return access(prog, X_OK) == 0;

// ... later ...

execve(buf, ...); // File could have changed between access() and exec()

30+ instances of stat()/access() followed by open()/exec(). Low risk in most contexts (local programs, not setuid) but technically exploitable.

6.5 PID File Races

// sxmux_session.c:1558

if (fscanf(pf, "%d", &existing_pid) == 1 && existing_pid > 0) {

// Check if pid is alive, then decide to reuse

// Race: pid could die and be recycled between check and use

}


7. Compiler Warning Analysis

Compilation with -Wall -Wextra -Wformat-security -Wstack-protector produced 95 unique warnings:

Warning TypeCountSecurity Impact
-Wunused-result73Medium: Ignored system(), read(), write(), chdir(), symlink(), fread() return values can mask failures silently
-Wunused-function13Low: Dead code
-Wunused-variable3Info: Minor
-Wunused-const-variable2Info: Minor
-Wswitch1Low: Unhandled enum case in sx.c:927 (SX_CMDSRC_BUILTIN_CMD)
-Wtype-limits1Low: Comparison always true/false
Notable: The project already suppresses -Wformat-truncation and -Wno-stringop-truncation in CMakeLists.txt — these may mask legitimate truncation bugs. Key ignored return values:

8. Positive Security Findings

The codebase demonstrates several good security practices:

  • snprintf dominance: 2,172 of 2,192 format calls use snprintf with sizeof() bounds (99.1%)
  • HTTP header parsing: scorpiox-server.c properly bounds header extraction with sizeof() checks
  • Filename sanitization: scorpiox-beam.c:755 uses sx_basename() to strip path components from received filenames
  • SMTP size limits: sxmail_smtp.c enforces SXMAIL_SMTP_MAX_MSG_SIZE on accumulated data
  • Network buffer management: sx_http.c write_cb uses doubling-growth realloc with null checks on realloc (partial — the check exists but the overflow on doubling does not)
  • Content-Type bounds: HTTP server checks ct_len >= sizeof(req->content_type) before copy
  • No format string vulns: All printf-family calls use literal format strings

  • 9. CVSS Risk Scoring Summary

    Risk LevelCountFindings
    CRITICAL (9.0+)4V-01, V-02, V-03, V-04 — Shell command injection
    HIGH (7.0-8.9)7V-05 through V-11 — Buffer overflows, null derefs, integer overflows
    MEDIUM (4.0-6.9)8V-12 through V-19 — Thread safety, input validation, bounded copies
    LOW (1.0-3.9)4V-20 through V-23 — Ignored returns, TOCTOU, safe strcpy
    Total23

    10. Remediation Recommendations

    Priority 1 — Critical (Immediate)

  • Eliminate system()/popen() with unsanitized input: Replace all system(snprintf(..., "'%s'", user_input)) patterns with fork()+execve() using explicit argv[] arrays. This eliminates shell interpretation entirely.
  • Implement shell-escape function: If system() must be used, create sx_shell_escape(const char input, char output, size_t output_size) that escapes all shell metacharacters (', ", ` `, $, \, !, (, ), {, }, |, &, ;, <, >, \n).
  • Audit sxmail_maildir.c:704: This is network-reachable via IMAP DELETE — an attacker with mailbox access can execute arbitrary commands by creating a folder with shell metacharacters in the name.
  • Priority 2 — High (This Sprint)

  • Replace all 20 sprintf calls with snprintf: Especially the chain patterns in sx_http_wasm.c, sx_api.c, and sx_exec_wasm_builtins.c.
  • Add null checks after all 166 allocation calls: Use a wrapper macro:
  •    #define SX_ALLOC(ptr, size) do { \
    

    (ptr) = malloc(size); \

    if (!(ptr)) { SX_ERROR("OOM at %s:%d", __FILE__, __LINE__); abort(); } \

    } while(0)

  • Fix realloc patterns: Never assign realloc() result to the same pointer:
  •    char *tmp = realloc(buf, cap);
    

    if (!tmp) { free(buf); return -1; }

    buf = tmp;

  • Fix strcat loops in scorpiox-wsl.c and scorpiox-traffic.c: Use snprintf with remaining-space tracking.
  • Priority 3 — Medium (Next Sprint)

  • Replace signal() with sigaction() for portable, reliable signal handling.
  • Add integer overflow checks before multiplication: if (nmemb && size > SIZE_MAX / nmemb) return 0;
  • Validate Content-Length against a maximum (e.g., 100MB) in HTTP server before allocating.
  • Replace remaining strcpy calls with snprintf(dst, sizeof(dst), "%s", src).
  • Priority 4 — Low (Backlog)

  • Fix all 73 ignored-return-value warnings: Check system(), read(), write() returns.
  • Replace access()+exec() TOCTOU patterns with fexecve() or openat().
  • Remove -Wno-format-truncation from CMakeLists.txt and fix any resulting warnings.
  • Add -Wformat-security to default build flags permanently.
  • Consider AddressSanitizer (-fsanitize=address) in CI/debug builds for runtime detection.

  • Appendix A: Unsafe Function Usage Counts

    FunctionOccurrencesSafe Alternative
    sprintf20snprintf
    strcpy28snprintf / strlcpy
    strcat9 (functional)snprintf / strlcat
    sscanf15Field-width limits
    fscanf15fgets + sscanf
    fgets122(safe when used correctly ✓)
    system~120fork+execve
    popen~50fork+execve+pipe
    signal4sigaction
    access15faccessat / open
    atoi3strtol with validation

    Appendix B: Files with Highest Risk Concentration

    FileLinesRisk Factors
    scorpiox-unshare.c~240015 system() calls, TOCTOU, ignored returns, strcpy
    scorpiox-wsl.c~130012 system() calls, strcat overflow, strcpy
    scorpiox-vm.c~3149system() with URLs, popen(), ioctl() without full error checking
    scorpiox-agent.c~22008 system()/popen() with agent paths, find -exec injection
    sxmail_maildir.c~900system("rm -rf") with IMAP folder name — network reachable
    scorpiox-server.c~2200HTTP request parsing, CGI environment, popen()
    sx_dll.c~1100Threading, fixed enqueue_buf, sprintf
    sx_http_wasm.c~1200Static buffers, sprintf chains, thread-unsafe globals
    sx_api.c~420sprintf chain, realloc without null check
    sxmail_smtp.c~800Network input parsing, realloc` without null check

    End of Security Audit Report