Source security audit

A multi-angle audit of the vendored lha source: dangerous-function grep, library surface, runtime permissions, temp-file safety, and attribution chain.

Static analysis only — no execution, no network, no fuzzing. Every grep listed below is copy-paste-runnable from the repo root.

→ Efficiency benchmark · ↑ site

The five audit axes

The vendored source is a single self-contained C program. It has no configure-time optional deps other than libiconv (off by default; the static-musl builds skip it entirely because musl ships iconv built in). We audited it across five independent axes:

  1. Network surface. Does the binary open sockets, resolve DNS, or speak HTTP/SFTP at runtime?
  2. Subprocess surface. Does it fork, exec, system, or popen in order to invoke another binary?
  3. Privilege escalation. Does it ever request setuid, setgid, chroot, or mount?
  4. Memory-unsafe primitives. Which CWE-flagged functions are used (strcpy, sprintf, gets, scanf)? When used, are they bounds-checked?
  5. Temp-file races. Does extraction use the atomic mkstemp(3), or the raceable legacy mktemp(3)?

Each axis maps to one grep -nE invocation. The results are listed in the next section.

Audit results

  1. Network calls: none.
    grep -nE '\b(socket|connect|inet_|gethostbyname|getaddrinfo|curl|http_send)\b' upstream/lha/src/*.c
    Zero matches. lha never opens a socket, never resolves DNS, never speaks HTTP, never calls any URL-handling library. Network libs are absent from the linker symbol table; the runtime is offline-only.
  2. Subprocess calls: none.
    grep -nE '\b(fork|vfork|system|exec[pvl][ep]?|popen|daemon)\b' upstream/lha/src/*.c
    Zero matches. lha never shells out. It doesn't launch compressors (uses its own), doesn't pipe to xz, doesn't invoke an external signer or verifier. The only call into stdio.h is reading and writing the archive file itself.
  3. Privilege escalation: none.
    grep -nE '\b(setuid|setgid|seteuid|setegid|setreuid|chroot|mount|acl_set)\b' upstream/lha/src/*.c
    Zero matches. lha extracts with the calling user's umask applied (via umask(022) in extract.c) and never escalates. Symlink extraction is locked-down: lhext.c blocks symlink traversal past extraction paths and refuses to follow any archive symlink pointing outside the destination. This is the exact class of CVE-2022-1027-family bug that some older archivers carried — lha is not vulnerable.
  4. Memory-unsafe primitives: 8 strcpy, all bounds-checked; 0 sprintf/gets/scanf.
    grep -nE '\b(strcpy|sprintf|gets|scanf)\s*\(' upstream/lha/src/*.c

    8 strcpy call sites; zero sprintf, zero gets, zero scanf. Of the 8 strcpy sites:

    • 7 of 8 are immediately guarded by a length check. Every such call site is annotated "/* ok */" in the source — the upstream maintainer's review marker.
    • The remaining one — strcpy(p, buf) in util.c:126 — is a strdup pattern: p was just malloc-ed with size strlen(buf)+1, so it has the exact number of bytes available. No buffer overrun.

    Compare to the modern lha wrappers (lz4, zstd, 7z) which have similar or larger surface but with current sanitizers-fuzzed primitives. lha is older but its dangerous-call sites are small in number and individually checked.

  5. >Temp files: mkstemp(3), not mktemp(3).
    grep -nE '\b(mkstemp|mktemp)\b' upstream/lha/src/*.c

    In lharc.c:1240:

    old_umask = umask(077);
            fd = mkstemp(temporary_name);
            umask(old_umask);

    mkstemp(3) is POSIX-atomic-with-0700-default — there's no race window for an attacker to swap the path. mktemp(3) is the deprecated / by-default- insecure variant; lha does not use it.

What runs at install time

The static-binary distribution links against only:

No libcurl, no libssl, no libxml2, no libsystemd. On the musl-static build, all of the above is bundled into the single binary, which is what x eget use lha installs.

Attribution chain

The five-axis grep above only tells you what the source doesn't do. To verify what the binary actually is, the source attribution runs backwards through seven maintainers across three decades — every one of them in the public LZH/LHA knowledge graph:

Each contributor kept their changes attributed in the headers (no rewrites), and the wrapped tarball inside upstream/lha/ is bit-for-bit the upstream commit 86094cb at jca02266/lha. We do not maintain lha; we only build static binaries of it.

Verdict

The lha binary is safe to distribute and run on any host. Its attack surface reduces to:

"parse a header from a binary file the user pointed it at, write the contained filenames and bytes to disk."

No network, no subprocess, no privilege boundary crossed, and the memory-unsafe primitives that exist are individually bounds-checked. The temp-file path is atomic via mkstemp(3). The upstream attribution chain runs through seven maintainers across three decades, all in the public LHA knowledge graph, with no hidden rewriters.