Skip to content

Commit c4216b7

Browse files
Merge pull request #102 from david-a-wheeler/improvements2
Strip leading ./ from SARIF artifact URIs for uriBaseId portability
2 parents 1acb3df + 3859d11 commit c4216b7

5 files changed

Lines changed: 121 additions & 5 deletions

File tree

ChangeLog

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,50 @@
5858
* Removed duplicate word in help text.
5959
* Fixed missing closing </li> tags in HTML output.
6060
* Fixed typos in documentation.
61+
* Fix false positives for method and member calls. Functions like
62+
read(), system(), or StrCat() are no longer flagged when preceded
63+
by "." or "->", e.g. stream.read(), obj.system(), ptr->read().
64+
Global calls and explicit global-scope calls (::system()) are still
65+
flagged. Fixes https://github.com/david-a-wheeler/flawfinder/issues/87
66+
https://github.com/david-a-wheeler/flawfinder/issues/83
67+
https://github.com/david-a-wheeler/flawfinder/issues/82
68+
https://github.com/david-a-wheeler/flawfinder/issues/76
69+
https://github.com/david-a-wheeler/flawfinder/issues/59
70+
* Warn and skip files with encoding errors instead of aborting the
71+
entire scan. A count of skipped files is reported at the end and
72+
flawfinder exits with code 15 if any were skipped. The new
73+
--error-on-encoding flag restores the old abort-on-first-error
74+
behavior for strict pipelines.
75+
Fixes https://github.com/david-a-wheeler/flawfinder/issues/80
76+
https://github.com/david-a-wheeler/flawfinder/issues/68
77+
* Fix SARIF output to produce valid URIs and helpUri values.
78+
File paths are now percent-encoded so spaces and special characters
79+
don't produce malformed URIs. Leading "./" prefixes are stripped so
80+
paths work correctly with the uriBaseId/SRCROOT placeholder (paths
81+
beginning with "-" retain "./" to avoid misinterpretation as CLI
82+
options). The helpUri field now reliably resolves to a CWE URL or
83+
falls back to a flawfinder rule URL; previously it could produce
84+
invalid values like ")".
85+
Fixes https://github.com/david-a-wheeler/flawfinder/issues/78
86+
https://github.com/david-a-wheeler/flawfinder/issues/67
87+
https://github.com/david-a-wheeler/flawfinder/issues/66
88+
* Fix swprintf/vswprintf format-string detection. These functions take
89+
(buf, n, format, ...) so the format string is at position 3, not 2.
90+
Previously flawfinder inspected the size argument n instead, causing
91+
false positives on constant formats and missing non-constant ones.
92+
Fixes https://github.com/david-a-wheeler/flawfinder/issues/43
93+
* Fix false positive: function names inside __attribute__((...)) are
94+
compiler metadata, not callable expressions,
95+
and are no longer flagged.
96+
E.g., __attribute__((format(printf, 1, 2))) no longer triggers a
97+
format-string warning.
98+
Fixes https://github.com/david-a-wheeler/flawfinder/issues/27
99+
* Add --exclude PATTERN option (repeatable) to skip files or
100+
directories whose full path matches a glob pattern, e.g.
101+
--exclude '*/third_party/*'. Useful for ignoring generated code,
102+
vendored libraries, or test directories.
103+
Fixes https://github.com/david-a-wheeler/flawfinder/issues/89
104+
https://github.com/david-a-wheeler/flawfinder/issues/65
61105

62106
2021-08-29 David A. Wheeler
63107
* Version 2.0.19

flawfinder.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,12 @@ def _to_sarif_rank(level):
362362

363363
@staticmethod
364364
def _to_uri_path(path):
365-
return url_quote(path.replace("\\", "/"), safe='/')
365+
p = path.replace("\\", "/")
366+
while p.startswith("./"):
367+
p = p[2:]
368+
if p.startswith("-"):
369+
p = "./" + p # prevent misinterpretation as a CLI option
370+
return url_quote(p, safe='/')
366371

367372
@staticmethod
368373
def _append_period(text):
@@ -647,7 +652,8 @@ def __init__(self, data):
647652
self.end = None
648653
self.parameters = None
649654
self.lookahead = None # set only when extract_lookahead is true
650-
_allowed_keys = {'check_for_null', 'extract_lookahead', 'format_position', 'input'}
655+
_allowed_keys = {'check_for_null', 'extract_lookahead',
656+
'format_position', 'input', 'source_position'}
651657
for key in other:
652658
if key in _allowed_keys:
653659
setattr(self, key, other[key])
@@ -1354,12 +1360,19 @@ def found_system(hit):
13541360
"Use fgets() instead", "buffer", "", {'input': 1}, "FF1014"),
13551361

13561362
# The "sprintf" hook will raise "format" issues instead if appropriate:
1357-
"sprintf|vsprintf|swprintf|vswprintf|_stprintf|_vstprintf":
1363+
"sprintf|vsprintf|_stprintf|_vstprintf":
13581364
(c_sprintf, 4,
13591365
"Does not check for buffer overflows (CWE-120)",
13601366
"Use sprintf_s, snprintf, or vsnprintf",
13611367
"buffer", "", {}, "FF1015"),
13621368

1369+
# swprintf/vswprintf take (buf, n, format, ...) so format is at position 3.
1370+
"swprintf|vswprintf":
1371+
(c_sprintf, 4,
1372+
"Does not check for buffer overflows (CWE-120)",
1373+
"Use sprintf_s, snprintf, or vsnprintf",
1374+
"buffer", "", {'source_position': 3}, "FF1015"),
1375+
13631376
"printf|vprintf|vwprintf|vfwprintf|_vtprintf|wprintf":
13641377
(c_printf, 4,
13651378
"If format strings can be influenced by an attacker, they can be exploited (CWE-134)",
@@ -1881,6 +1894,33 @@ def _preceded_by_member_or_namespace(text, startpos):
18811894
return False
18821895

18831896

1897+
def _skip_attribute_args(text, pos):
1898+
"""Advance past the ((...)) argument of __attribute__, tracking paren depth.
1899+
1900+
Returns the position after the closing paren, or pos unchanged if no
1901+
opening paren is found (malformed or no argument).
1902+
"""
1903+
n = len(text)
1904+
while pos < n and text[pos] in ' \t\n\r':
1905+
pos += 1
1906+
if pos >= n or text[pos] != '(':
1907+
return pos
1908+
# Limitation: paren counting ignores string literals, so an unbalanced
1909+
# '(' inside a string argument (e.g. deprecated("old open( call")) will
1910+
# cause the scanner to skip the rest of the file. This is accepted as a
1911+
# known edge case; attribute strings with unbalanced parens are very rare.
1912+
depth = 0
1913+
while pos < n:
1914+
if text[pos] == '(':
1915+
depth += 1
1916+
elif text[pos] == ')':
1917+
depth -= 1
1918+
if depth == 0:
1919+
return pos + 1
1920+
pos += 1
1921+
return pos
1922+
1923+
18841924
def process_directive():
18851925
"Given a directive, process it."
18861926
global ignoreline, num_ignored_hits
@@ -2102,7 +2142,9 @@ def process_c_file(f, patch_infos):
21022142
i = endpos
21032143
word = text[startpos:endpos]
21042144
# print "Word is:", text[startpos:endpos]
2105-
if (word in c_ruleset) and \
2145+
if word == "__attribute__":
2146+
i = _skip_attribute_args(text, endpos)
2147+
elif (word in c_ruleset) and \
21062148
not _preceded_by_member_or_namespace(text, startpos) and \
21072149
c_valid_match(text, endpos):
21082150
if ((patch_infos is None)

test/correct-results-014.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
test-attribute.c:18:5: [4] (format) printf:
2+
If format strings can be influenced by an attacker, they can be exploited
3+
(CWE-134). Use a constant for the format specification.

test/makefile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ test_013: $(FLAWFINDER) test-member-calls.cpp
9797
test-member-calls.cpp > test-results-013.txt
9898
@diff -u correct-results-013.txt test-results-013.txt
9999

100+
test_014: $(FLAWFINDER) test-attribute.c
101+
@echo 'test_014 (__attribute__((format(printf,...))) not flagged; real calls still flagged)'
102+
# printf inside __attribute__((...)) is a specifier keyword, not a call;
103+
# must produce no hit. Real printf calls outside attributes must still fire.
104+
@$(PYTHON) $(FLAWFINDER) --omittime -m0 -DC --quiet \
105+
test-attribute.c > test-results-014.txt
106+
@diff -u correct-results-014.txt test-results-014.txt
107+
100108
test_012: $(FLAWFINDER) test-modern-cpp.cpp
101109
@echo 'test_012 (modern C++ lexing: binary/hex literals, hex floats, digit separators)'
102110
# Modern C++ syntax must produce zero hits (no false positives from the lexer).
@@ -109,7 +117,7 @@ test_012: $(FLAWFINDER) test-modern-cpp.cpp
109117
# If everything works as expected, it just prints test numbers.
110118
# Set PYTHON as needed, including to ""
111119
test: test_001 test_002 test_003 test_004 test_005 test_006 test_007 test_008 \
112-
test_009 test_010 test_011 test_012 test_013
120+
test_009 test_010 test_011 test_012 test_013 test_014
113121
@echo 'All tests pass!'
114122

115123
check: test

test/test-attribute.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// test-attribute.c: __attribute__((format(printf,...))) must not produce
2+
// false positives; real printf calls outside attributes must still be flagged.
3+
4+
// --- These should produce NO hits ---
5+
6+
// GCC format attribute: 'printf' here is a specifier keyword, not a call.
7+
#define ZT_FORMAT_PRINTF(a, b) __attribute__((format(printf, a, b)))
8+
9+
// Attribute directly on a declaration.
10+
void my_log(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
11+
12+
// Nested parens inside attribute argument.
13+
void my_warn(const char *fmt, ...) __attribute__((format(printf, 1, 2), noinline));
14+
15+
// --- These SHOULD still produce hits ---
16+
17+
void real_call(const char *name) {
18+
printf(name); // non-constant format: real hit
19+
}

0 commit comments

Comments
 (0)