mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
contributor-scan 2.12. c/injection and c/memory carried zero sanitizers, so no C flow could ever be discharged: every `argv -> strcpy` entailed however carefully it was bounded. Which sinks can be discharged this way is read from the sink fact's own CWE rather than declared: CWE-121 is a stack buffer overflow, so gets/sprintf/strcat/strcpy qualify. It is the only overflow CWE in the fact tables and it is C-only, so no Python or JavaScript sink is touched and the pair corpus is unaffected -- gates byte-identical. Not a sanitizer list, because `strlen` appears in the guarded and the unguarded case alike; the discharge is a guard, not a call. **The first implementation required the bound to DOMINATE the sink, and it found nothing.** libpng guards `strcpy(outname+len, ".png")` with `(len = strlen(inname)) > 250`, but that check sits on an else-if chain whose sibling branch skips it entirely, and the protection reaches the sink through an `error` flag: `++error` in the guarded branch, `if (!error)` around the copy. Joern's dominator set for that sink has 89 calls and correctly excludes the bound; `controlledBy` returns none either. Verified against the CPG before the rule was changed rather than after it failed. So the bound is searched in the method, and the consequence is corroborated rather than silence. That is the honest rung: a bound on this variable means the graph has NOT shown the copy is unguarded, and it has not shown it is guarded either -- off-by-one being how bounds fail. The judge is asked. Proving the flag-mediated case needs path-sensitive reasoning this arbiter does not do. libpng entailed 3 -> 1 The survivor is also a false positive: `strcpy(output + len, ".icc")` in iccfrompng.c, where `output = malloc(len + 5)` makes it fit exactly. That is a different discharge shape -- allocated to size, not bounds-checked -- and it is recorded as 2.13 rather than chased. Both shapes were found on one repository, and extending a rule until that repository is clean is the overfitting Req 8 exists to prevent; 2.13 requires a second C checkout with a genuine CWE-121 so the change can be measured for what it silences as well as what it clears. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
23 lines
3.0 KiB
JSON
23 lines
3.0 KiB
JSON
{
|
|
"caveat": "One repository, and both discharge shapes were found on it. The rule is measured here only; whether a bound-in-the-method demotes real overflows elsewhere is unknown until a checkout with a genuine CWE-121 is scanned.",
|
|
"delta": {
|
|
"entailed_findings": "3 -> 1",
|
|
"taint_query_seconds": "125.0 -> 127.1 (unchanged within noise)"
|
|
},
|
|
"generated": "2026-09-09",
|
|
"how": {
|
|
"which_sinks": "Read from the sink fact's own CWE, not declared here. CWE-121 is a stack buffer overflow, so gets/sprintf/strcat/strcpy are dischargeable by a bound. It is the only overflow CWE in the fact tables and it is C-only, so no Python or JavaScript sink is affected and the pair corpus is untouched -- gates byte-identical.",
|
|
"why_corroborated_and_not_silence": "A bound somewhere in the function means the graph CANNOT ENTAIL -- it has not shown the copy is unguarded. It has not shown it is guarded either, and a bound is no proof of safety in any case, off-by-one being the classic way they fail. So the judge is asked. Proving the flag-mediated case needs path-sensitive reasoning this arbiter does not do.",
|
|
"why_not_a_sanitizer_list": "`strlen` appears in the guarded and the unguarded case alike. The discharge is a GUARD, not a call, so a sanitizer entry would silence both.",
|
|
"why_the_method_and_not_the_dominators": "The first implementation required the bound to dominate the sink, which is what the access-control arbiter does. It found nothing. libpng guards `strcpy(outname+len, \".png\")` with `(len = strlen(inname)) > 250`, but that check sits on an else-if chain whose sibling branch skips it, and the protection reaches the sink through an `error` flag -- `++error` in the guarded branch, `if (!error)` around the copy. Joern's dominator set for that sink has 89 calls and correctly excludes the bound. Verified directly against the CPG before changing the rule; `controlledBy` returns 0 conditions there as well."
|
|
},
|
|
"reading": "c/injection and c/memory carried zero sanitizers, so no C flow could ever be discharged and every argv -> strcpy entailed however carefully it was bounded. A bound now lowers the rung to corroborated: libpng's entailed findings fall from 3 to 1, and the survivor is a different false-positive shape recorded as 2.13 rather than chased.",
|
|
"repo": "libpng at cf53e54ccf631d8de68a30ebb3b8eeed1d6f1a08",
|
|
"spec": "contributor-scan 2.12 (Req 8.1, 8.2)",
|
|
"the_remaining_finding_is_also_a_false_positive": {
|
|
"site": "contrib/examples/iccfrompng.c:124 -- strcpy(output + len, \".icc\")",
|
|
"why_it_is_safe": "`output = malloc(len + 5)`, then `memcpy(output, filename, len)` and a 5-byte copy at offset len. It fits exactly.",
|
|
"why_the_rule_does_not_reach_it": "There is no relational comparison here at all. The destination is ALLOCATED to size rather than bounds-checked, which is a different discharge shape. Recorded as 2.13 and deliberately not chased: extending the rule on this repository to clear this site is the overfitting Req 8 exists to prevent."
|
|
}
|
|
}
|