@@ -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+
18841924def 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 )
0 commit comments