C / C++ SAST
What xgrep detects in C and C++ — memory safety, unbounded/dangerous libc functions, injection, and crypto/TLS misuse — with taint analysis from untrusted input to dangerous sinks.
C / C++ SAST
xgrep parses .c .h and .cpp .cc .cxx .hpp into tree-sitter ASTs and
ships roughly 35 rules, 13 of them taint-based, weighted toward the failure
modes that dominate C and C++: memory safety and the unbounded libc functions
that cause classic buffer overflows.
xgrep scan --include '*.c' --include '*.h' .What xgrep detects
Memory safety — buffer-overflow, double-free, double-delete (C++
delete/delete[]), use-after-free, null-deref, unchecked-malloc-deref,
array-index-out-of-bounds, alloca-untrusted-size, integer-overflow-alloc,
writable-executable-memory (W^X).
Dangerous functions & format strings — dangerous-function (unbounded
gets/strcpy/strcat/sprintf/vsprintf), scanf-unbounded-read
(unbounded %s/%[), format-string (non-constant printf-family format,
CWE-134). See Dangerous functions below.
Injection — command-injection, sql-injection, path-traversal, ssrf,
and (C++) regex-injection. These are taint-based: untrusted input must actually
reach the sink.
Crypto & TLS — insecure-tls-version (SSLv2/SSLv3/TLS 1.0/1.1),
tls-verification-disabled (libcurl CURLOPT_SSL_VERIFYPEER/VERIFYHOST, Qt, cpr),
openssl-verify-none (OpenSSL SSL_VERIFY_NONE, reported as a low-confidence review
item — see below), insecure-randomness, and (C++) weak-cipher, weak-hash,
predictable-seed.
Filesystem — insecure-file-permissions, insecure-temp-file,
toctou-file-access (check-by-path then use-by-path race).
Deserialization & XML (C++) — boost-deserialization, xxe.
Dangerous functions
dangerous-function (CWE-676/242/120) flags the inherently unsafe libc calls that
write to a destination without regard to its size, the root of countless stack and
heap overflows:
| Flagged | Why | Safe replacement |
|---|---|---|
gets(buf) | reads unbounded stdin into buf — no size argument exists | fgets(buf, sizeof buf, stdin) |
strcpy(dst, src) | copies until src's NUL, however long | strlcpy(dst, src, sizeof dst) or snprintf |
strcat(dst, src) | appends with no bound on the result | strlcat(dst, src, sizeof dst) or snprintf |
sprintf(dst, fmt, …) | formats into dst with no size limit | snprintf(dst, sizeof dst, …) |
vsprintf(dst, fmt, …) | same, for va_list | vsnprintf |
Prefer strlcpy/strlcat (or snprintf), which always NUL-terminate within the
size. strncpy/strncat are not safe drop-ins: strncpy does not
NUL-terminate when the source is longer than n, and strncat's length argument
is the count to append, not the buffer size — both trade the overflow for a
missing-terminator or off-by-one bug if used naively.
This is a use-of-dangerous-function detection: it fires on the call because the
function is unsafe by construction, not because a specific overflow has been proven
reachable. To keep it actionable rather than noisy, xgrep excludes provably-bounded
calls — a string-literal source or format (strcpy(dst, "ok"), sprintf(dst, "hi"))
is compile-time-constant and never flagged; only the genuinely unbounded forms
(strcpy(dst, user), sprintf(dst, fmt)) are. Treat a finding as a hardening task:
switch to the bounded equivalent and always pass the destination size. Where the copy
length comes from untrusted input and a concrete overflow is provable, the taint-based
buffer-overflow rule reports it at CRITICAL instead.
TLS certificate verification
tls-verification-disabled reports the unambiguous client-side cases at HIGH —
libcurl (CURLOPT_SSL_VERIFYPEER/VERIFYHOST turned off), Qt VerifyNone, and
cpr VerifySsl{false}. An OpenSSL SSL_VERIFY_NONE is split out into
openssl-verify-none and reported at INFO / low confidence instead, because on
a TLS server it merely means "don't require a client certificate" (normal), while
only on a client is it the certificate-validation bypass the CWE describes — and
the two can't be told apart without provenance dataflow, so it is surfaced for
review rather than asserted as a vulnerability.
Taint analysis
The injection and memory rules marked above use taint mode:
untrusted input — argv/getenv, read/recv/fread, scanf, or an HTTP body —
must flow to the sink (system/popen, a SQL API, memcpy/alloca length, an
OpenSSL/curl request) before the rule fires. Matching the shape of a call is not
enough, which is what keeps command-injection, sql-injection, buffer-overflow,
and ssrf precise rather than flagging every system() or memcpy().
See also Supported languages for the full extension matrix and the CLI reference.