2019-11-15 21:38:20 +01:00
#!/usr/bin/env python3
2015-08-18 16:14:53 +02:00
#
# Cert: Some extra CERT checkers
#
# Cppcheck itself handles many CERT rules. Cppcheck warns when there is undefined behaviour.
2018-03-16 08:12:39 +01:00
# CERT Homepage: https://www.cert.org/secure-coding/
2015-08-18 16:14:53 +02:00
#
# Example usage of this addon (scan a sourcefile main.cpp)
# cppcheck --dump main.cpp
# python cert.py main.cpp.dump
2019-07-26 15:40:25 +02:00
import argparse
2019-12-30 17:30:17 +01:00
import cppcheckdata
2015-08-18 16:14:53 +02:00
import sys
2015-08-28 18:07:12 +02:00
import re
2022-02-24 16:08:59 +01:00
import subprocess
2015-08-21 10:55:19 +02:00
2017-10-15 16:18:29 +02:00
VERIFY = ( ' -verify ' in sys . argv )
VERIFY_EXPECTED = [ ]
VERIFY_ACTUAL = [ ]
2015-12-14 09:37:26 +01:00
2017-08-15 20:34:54 +02:00
def reportError ( token , severity , msg , id ) :
2017-10-15 16:18:29 +02:00
if VERIFY :
2019-04-09 20:36:24 +02:00
VERIFY_ACTUAL . append ( str ( token . linenr ) + ' :cert- ' + id )
2017-10-15 16:18:29 +02:00
else :
2019-04-14 08:54:53 +02:00
cppcheckdata . reportError ( token , severity , msg , ' cert ' , id )
2015-12-14 09:37:26 +01:00
2017-10-14 22:24:24 +02:00
def simpleMatch ( token , pattern ) :
2020-11-11 14:24:55 +01:00
return cppcheckdata . simpleMatch ( token , pattern )
2015-08-21 10:55:19 +02:00
2018-01-23 17:18:47 +01:00
def isUnpackedStruct ( token ) :
if token . valueType is None :
return False
if token . valueType . typeScope is None :
2018-08-05 20:36:21 +02:00
return False
2018-01-23 17:18:47 +01:00
if token . valueType . typeScope . type != " Struct " :
return False
2018-04-28 23:06:54 +02:00
startToken = token . valueType . typeScope . bodyStart
2018-01-23 17:18:47 +01:00
linenr = int ( startToken . linenr )
for line in open ( startToken . file ) :
linenr - = 1
if linenr == 0 :
return True
if linenr < 3 and re . match ( r ' #pragma \ s+pack \ s* \ ( ' , line ) :
return False
return True
2015-08-28 18:07:12 +02:00
2015-12-14 09:37:26 +01:00
2015-08-28 18:07:12 +02:00
def isLocalUnpackedStruct ( arg ) :
2015-08-19 10:21:25 +02:00
if arg and arg . str == ' & ' and not arg . astOperand2 :
arg = arg . astOperand1
2018-01-23 17:18:47 +01:00
return arg and arg . variable and ( arg . variable . isLocal or arg . variable . isArgument ) and isUnpackedStruct ( arg )
2015-08-21 10:55:19 +02:00
2015-12-14 09:37:26 +01:00
2015-08-18 16:14:53 +02:00
def isBitwiseOp ( token ) :
2017-06-04 22:51:48 +02:00
return token and ( token . str in { ' & ' , ' | ' , ' ^ ' } )
2015-08-18 16:14:53 +02:00
2015-08-21 10:55:19 +02:00
2015-08-18 16:14:53 +02:00
def isComparisonOp ( token ) :
2017-06-04 22:51:48 +02:00
return token and ( token . str in { ' == ' , ' != ' , ' > ' , ' >= ' , ' < ' , ' <= ' } )
2015-08-18 16:14:53 +02:00
2017-10-14 22:24:24 +02:00
def isCast ( expr ) :
if not expr or expr . str != ' ( ' or not expr . astOperand1 or expr . astOperand2 :
return False
if simpleMatch ( expr , ' ( ) ' ) :
return False
return True
2015-08-19 10:21:25 +02:00
2018-08-24 15:05:50 +02:00
def isStandardFunction ( token ) :
if token . function :
return False
prev = token . previous
if prev :
if prev . str == ' . ' :
return False
if prev . str == ' :: ' :
prevprev = prev . previous
if prevprev and not prevprev . str == ' std ' :
return False
return True
2019-06-23 13:59:24 +02:00
# Is this a function call
def isFunctionCall ( token , function_names , number_of_arguments = None ) :
if not token . isName :
return False
if token . str not in function_names :
return False
if ( token . next is None ) or token . next . str != ' ( ' or token . next != token . astParent :
return False
if number_of_arguments is None :
return True
return len ( cppcheckdata . getArguments ( token ) ) == number_of_arguments
2018-04-03 13:35:19 +02:00
# EXP05-C
# do not attempt to cast away const
def exp05 ( data ) :
2018-04-03 15:12:01 +02:00
# TODO Reuse code in misra rule 11.8
2018-04-03 13:35:19 +02:00
for token in data . tokenlist :
if isCast ( token ) :
# C-style cast
if not token . valueType :
continue
if not token . astOperand1 . valueType :
2018-04-03 15:12:01 +02:00
continue
if token . valueType . pointer == 0 :
continue
if token . astOperand1 . valueType . pointer == 0 :
continue
2018-04-03 13:35:19 +02:00
const1 = token . valueType . constness
const2 = token . astOperand1 . valueType . constness
if ( const1 % 2 ) < ( const2 % 2 ) :
2019-04-09 20:36:24 +02:00
reportError ( token , ' style ' , " Attempt to cast away const " , ' EXP05-C ' )
2018-04-03 13:35:19 +02:00
elif token . str == ' ( ' and token . astOperand1 and token . astOperand2 and token . astOperand1 . function :
function = token . astOperand1 . function
2019-06-23 14:05:24 +02:00
arguments = cppcheckdata . getArguments ( token . previous )
2019-11-23 17:41:47 +01:00
if not arguments :
continue
2018-04-03 13:35:19 +02:00
for argnr , argvar in function . argument . items ( ) :
if argnr < 1 or argnr > len ( arguments ) :
continue
2018-04-03 15:12:01 +02:00
if not argvar . isPointer :
continue
2018-04-12 20:23:50 +02:00
if ( argvar . constness % 2 ) == 1 : # data is const
continue
2018-04-03 13:35:19 +02:00
argtok = arguments [ argnr - 1 ]
if not argtok . valueType :
continue
2018-04-03 15:12:01 +02:00
if argtok . valueType . pointer == 0 :
continue
2018-04-03 13:35:19 +02:00
const2 = arguments [ argnr - 1 ] . valueType . constness
2018-04-12 20:23:50 +02:00
if ( const2 % 2 ) == 1 :
2019-04-09 20:36:24 +02:00
reportError ( token , ' style ' , " Attempt to cast away const " , ' EXP05-C ' )
2018-04-03 13:35:19 +02:00
2015-08-19 10:21:25 +02:00
# EXP42-C
# do not compare padding data
def exp42 ( data ) :
for token in data . tokenlist :
2018-01-23 17:18:47 +01:00
if token . str != ' ( ' or not token . astOperand1 or token . astOperand1 . str != ' memcmp ' :
2015-08-19 10:21:25 +02:00
continue
arg1 = None
arg2 = None
if token . astOperand2 and token . astOperand2 . str == ' , ' :
if token . astOperand2 . astOperand1 and token . astOperand2 . astOperand1 . str == ' , ' :
arg1 = token . astOperand2 . astOperand1 . astOperand1
arg2 = token . astOperand2 . astOperand1 . astOperand2
2018-01-23 17:18:47 +01:00
if isLocalUnpackedStruct ( arg1 ) or isLocalUnpackedStruct ( arg2 ) :
2015-12-14 09:37:26 +01:00
reportError (
2017-08-15 20:34:54 +02:00
token , ' style ' , " Comparison of struct padding data " +
2019-04-09 20:36:24 +02:00
" (fix either by packing the struct using ' #pragma pack ' or by rewriting the comparison) " , ' EXP42-C ' )
2017-06-04 22:51:48 +02:00
2019-06-26 18:49:47 +02:00
# EXP15-C
# Do not place a semicolon on the same line as an if, for or while statement
def exp15 ( data ) :
for scope in data . scopes :
if scope . type in ( ' If ' , ' For ' , ' While ' ) :
2019-11-05 21:05:43 +01:00
token = scope . bodyStart . next
2019-06-26 18:49:47 +02:00
if token . str == ' ; ' and token . linenr == scope . bodyStart . linenr :
reportError ( token , ' style ' , ' Do not place a semicolon on the same line as an IF, FOR or WHILE ' , ' EXP15-C ' )
2015-08-19 10:21:25 +02:00
2015-08-18 16:14:53 +02:00
# EXP46-C
# Do not use a bitwise operator with a Boolean-like operand
# int x = (a == b) & c;
def exp46 ( data ) :
for token in data . tokenlist :
if isBitwiseOp ( token ) and ( isComparisonOp ( token . astOperand1 ) or isComparisonOp ( token . astOperand2 ) ) :
2015-12-14 09:37:26 +01:00
reportError (
2019-04-09 20:36:24 +02:00
token , ' style ' , ' Bitwise operator is used with a Boolean-like operand ' , ' EXP46-c ' )
2015-08-18 16:14:53 +02:00
2017-10-14 22:24:24 +02:00
# INT31-C
# Ensure that integer conversions do not result in lost or misinterpreted data
def int31 ( data , platform ) :
if not platform :
return
for token in data . tokenlist :
2022-01-20 21:09:39 +01:00
to_value_type = None
from_values = None
action = ' '
if isCast ( token ) :
to_value_type = token . valueType
from_values = token . astOperand1 . values
action = ' casting '
elif token . str == ' = ' and token . astOperand1 and token . astOperand2 :
to_value_type = token . astOperand1 . valueType
from_values = token . astOperand2 . values
action = ' assign '
else :
2017-10-14 22:24:24 +02:00
continue
2022-01-20 21:09:39 +01:00
if to_value_type is None or not from_values :
2017-10-14 22:24:24 +02:00
continue
bits = None
2022-02-05 14:02:01 +01:00
if token . valueType . pointer > 0 :
bits = platform . pointer_bit
elif to_value_type . type == ' char ' :
2017-10-14 22:24:24 +02:00
bits = platform . char_bit
2022-01-20 21:09:39 +01:00
elif to_value_type . type == ' short ' :
2017-10-14 22:24:24 +02:00
bits = platform . short_bit
2022-01-20 21:09:39 +01:00
elif to_value_type . type == ' int ' :
2017-10-14 22:24:24 +02:00
bits = platform . int_bit
2022-01-20 21:09:39 +01:00
elif to_value_type . type == ' long ' :
2017-10-14 22:24:24 +02:00
bits = platform . long_bit
2022-01-20 21:09:39 +01:00
elif to_value_type . type == ' long long ' :
2017-10-16 13:35:07 +02:00
bits = platform . long_long_bit
2017-10-14 22:24:24 +02:00
else :
continue
2022-01-20 21:09:39 +01:00
if to_value_type . sign == ' unsigned ' :
2017-10-16 13:35:07 +02:00
found = False
2022-01-20 21:09:39 +01:00
for value in from_values :
2018-10-28 20:55:15 +01:00
if value . intvalue and value . intvalue < 0 :
2017-10-16 13:35:07 +02:00
found = True
reportError (
token ,
' style ' ,
2022-01-20 21:09:39 +01:00
' Ensure that integer conversions do not result in lost or misinterpreted data ( ' + action + ' ' + str ( value . intvalue ) + ' to unsigned ' + token . valueType . type + ' ) ' ,
2019-04-09 20:36:24 +02:00
' INT31-c ' )
2017-10-16 13:35:07 +02:00
break
if found :
continue
2017-10-14 22:24:24 +02:00
if bits > = 64 :
continue
2017-10-16 13:35:07 +02:00
minval = 0
maxval = 1
if token . valueType . sign == ' signed ' :
minval = - ( 1 << ( bits - 1 ) )
maxval = ( ( 1 << ( bits - 1 ) ) - 1 )
else :
minval = 0
maxval = ( ( 1 << bits ) - 1 )
2022-01-20 21:09:39 +01:00
for value in from_values :
2018-10-29 01:39:52 +01:00
if value . intvalue and ( value . intvalue < minval or value . intvalue > maxval ) :
2017-10-16 13:35:07 +02:00
destType = ' '
if token . valueType . sign :
destType = token . valueType . sign + ' ' + token . valueType . type
else :
destType = token . valueType . type
2017-10-14 22:24:24 +02:00
reportError (
token ,
' style ' ,
2022-01-20 21:09:39 +01:00
' Ensure that integer conversions do not result in lost or misinterpreted data ( ' + action + ' ' + str ( value . intvalue ) + ' to ' + destType + ' ) ' ,
2019-04-09 20:36:24 +02:00
' INT31-c ' )
2019-11-05 21:05:43 +01:00
break
2019-11-21 06:46:46 +01:00
# ENV33-C
# Do not call system()
def env33 ( data ) :
for token in data . tokenlist :
if isFunctionCall ( token , ( ' system ' , ) , 1 ) :
2019-11-24 10:31:08 +01:00
# Invalid syntax
if not token . next . astOperand2 :
continue
# ENV33-C-EX1: It is permissible to call system() with a null
# pointer argument to determine the presence of a command processor
# for the system.
argValue = token . next . astOperand2 . getValue ( 0 )
if argValue and argValue . intvalue == 0 and argValue . isKnown ( ) :
continue
2019-11-21 06:46:46 +01:00
reportError ( token , ' style ' , ' Do not call system() ' , ' ENV33-C ' )
2019-07-04 12:19:52 +02:00
# MSC24-C
# Do not use deprecated or obsolescent functions
def msc24 ( data ) :
for token in data . tokenlist :
if isFunctionCall ( token , ( ' asctime ' , ) , 1 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use asctime() better use asctime_s() ' , ' MSC24-C ' )
2019-07-04 12:19:52 +02:00
elif isFunctionCall ( token , ( ' atof ' , ) , 1 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use atof() better use strtod() ' , ' MSC24-C ' )
2019-07-04 12:19:52 +02:00
elif isFunctionCall ( token , ( ' atoi ' , ) , 1 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use atoi() better use strtol() ' , ' MSC24-C ' )
2019-07-04 12:19:52 +02:00
elif isFunctionCall ( token , ( ' atol ' , ) , 1 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use atol() better use strtol() ' , ' MSC24-C ' )
2019-07-04 12:19:52 +02:00
elif isFunctionCall ( token , ( ' atoll ' , ) , 1 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use atoll() better use strtoll() ' , ' MSC24-C ' )
2019-07-04 12:19:52 +02:00
elif isFunctionCall ( token , ( ' ctime ' , ) , 1 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use ctime() better use ctime_s() ' , ' MSC24-C ' )
2019-07-04 12:19:52 +02:00
elif isFunctionCall ( token , ( ' fopen ' , ) , 2 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use fopen() better use fopen_s() ' , ' MSC24-C ' )
2019-07-04 12:19:52 +02:00
elif isFunctionCall ( token , ( ' freopen ' , ) , 3 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use freopen() better use freopen_s() ' , ' MSC24-C ' )
2019-07-04 12:19:52 +02:00
elif isFunctionCall ( token , ( ' rewind ' , ) , 1 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use rewind() better use fseek() ' , ' MSC24-C ' )
2019-07-04 12:19:52 +02:00
elif isFunctionCall ( token , ( ' setbuf ' , ) , 2 ) :
2019-08-08 21:05:28 +02:00
reportError ( token , ' style ' , ' Do not use setbuf() better use setvbuf() ' , ' MSC24-C ' )
2017-10-14 22:24:24 +02:00
2018-08-24 15:05:50 +02:00
# MSC30-C
# Do not use the rand() function for generating pseudorandom numbers
def msc30 ( data ) :
for token in data . tokenlist :
if simpleMatch ( token , " rand ( ) " ) and isStandardFunction ( token ) :
2019-04-09 20:36:24 +02:00
reportError ( token , ' style ' , ' Do not use the rand() function for generating pseudorandom numbers ' , ' MSC30-c ' )
2018-08-24 15:05:50 +02:00
2019-06-24 18:41:43 +02:00
# STR03-C
# Do not inadvertently truncate a string
def str03 ( data ) :
for token in data . tokenlist :
if not isFunctionCall ( token , ' strncpy ' ) :
continue
arguments = cppcheckdata . getArguments ( token )
if len ( arguments ) != 3 :
continue
if arguments [ 2 ] . str == ' ( ' and arguments [ 2 ] . astOperand1 . str == ' sizeof ' :
reportError ( token , ' style ' , ' Do not inadvertently truncate a string ' , ' STR03-C ' )
2019-06-17 20:42:23 +02:00
# STR05-C
# Use pointers to const when referring to string literals
def str05 ( data ) :
for token in data . tokenlist :
if token . isString :
parent = token . astParent
if parent is None :
continue
parentOp1 = parent . astOperand1
if parent . isAssignmentOp and parentOp1 . valueType :
if ( parentOp1 . valueType . type in ( ' char ' , ' wchar_t ' ) ) and parentOp1 . valueType . pointer and not parentOp1 . valueType . constness :
reportError ( parentOp1 , ' style ' , ' Use pointers to const when referring to string literals ' , ' STR05-C ' )
2019-06-23 13:59:24 +02:00
# STR07-C
# Use the bounds-checking interfaces for string manipulation
def str07 ( data ) :
2021-01-31 14:27:11 +01:00
if data . standards . c == ' c89 ' or data . standards . c == ' c99 ' :
2020-04-07 07:29:49 +02:00
return
2019-06-23 13:59:24 +02:00
for token in data . tokenlist :
if not isFunctionCall ( token , ( ' strcpy ' , ' strcat ' ) ) :
continue
args = cppcheckdata . getArguments ( token )
if len ( args ) != 2 :
continue
if args [ 1 ] . isString :
continue
2021-01-31 14:27:11 +01:00
reportError ( token , ' style ' , ' Use the bounds-checking interfaces %s _s() ' % token . str , ' STR07-C ' )
2019-06-17 20:42:23 +02:00
2019-07-02 11:44:24 +02:00
# STR11-C
# Do not specify the bound of a character array initialized with a string literal
def str11 ( data ) :
for token in data . tokenlist :
if not token . isString :
continue
strlen = token . strlen
parent = token . astParent
if parent is None :
continue
parentOp1 = parent . astOperand1
if parentOp1 is None or parentOp1 . str != ' [ ' :
continue
if not parent . isAssignmentOp :
continue
2019-11-05 21:05:43 +01:00
2019-07-02 11:44:24 +02:00
varToken = parentOp1 . astOperand1
if varToken is None or not varToken . isName :
continue
if varToken . variable is None :
continue
if varToken != varToken . variable . nameToken :
continue
valueToken = parentOp1 . astOperand2
if valueToken is None :
continue
2019-11-05 21:05:43 +01:00
2019-07-02 11:44:24 +02:00
if valueToken . isNumber and int ( valueToken . str ) == strlen :
reportError ( valueToken , ' style ' , ' Do not specify the bound of a character array initialized with a string literal ' , ' STR11-C ' )
2019-07-24 10:39:31 +02:00
# API01-C
# Avoid laying out strings in memory directly before sensitive data
def api01 ( data ) :
for scope in data . scopes :
if scope . type != ' Struct ' :
continue
token = scope . bodyStart
2022-03-13 16:42:07 +01:00
string_found = False
2019-07-24 10:39:31 +02:00
# loop through the complete struct
while token != scope . bodyEnd :
if token . isName and token . variable :
2022-03-13 16:42:07 +01:00
is_string = False
2019-07-24 10:39:31 +02:00
if token . variable . isArray :
2022-03-13 16:42:07 +01:00
type_token = token . variable . typeStartToken
while type_token and type_token . isName :
if type_token . str in ( ' char ' , ' wchar_t ' ) and not type_token . isExpandedMacro :
is_string = True
type_token = type_token . next
if is_string :
string_found = True
elif string_found and not token . variable . isConst :
2019-07-24 10:39:31 +02:00
reportError ( token , ' style ' , ' Avoid laying out strings in memory directly before sensitive data ' , ' API01-C ' )
# reset flags to report other positions in the same struct
2022-03-13 16:42:07 +01:00
string_found = False
2019-07-24 10:39:31 +02:00
token = token . next
2017-10-15 16:18:29 +02:00
2021-07-06 22:13:04 +02:00
def get_args_parser ( ) :
2019-07-26 15:40:25 +02:00
parser = cppcheckdata . ArgumentParser ( )
parser . add_argument ( " -verify " , help = argparse . SUPPRESS , action = " store_true " )
2021-07-06 22:13:04 +02:00
return parser
2017-10-15 16:18:29 +02:00
2019-07-26 15:40:25 +02:00
if __name__ == ' __main__ ' :
2021-07-06 22:13:04 +02:00
parser = get_args_parser ( )
args = parser . parse_args ( )
2019-07-26 15:40:25 +02:00
2022-02-24 16:08:59 +01:00
path_premium_addon = cppcheckdata . get_path_premium_addon ( )
2019-07-26 15:40:25 +02:00
if args . verify :
VERIFY = True
2019-11-15 20:14:30 +01:00
if not args . dumpfile :
if not args . quiet :
print ( " no input files. " )
sys . exit ( 0 )
2019-07-26 15:40:25 +02:00
for dumpfile in args . dumpfile :
if not args . quiet :
print ( ' Checking %s ... ' % dumpfile )
2022-02-24 16:08:59 +01:00
if path_premium_addon :
premium_command = [ path_premium_addon , ' --cert ' , dumpfile ]
if args . cli :
premium_command . append ( ' --cli ' )
for line in subprocess . check_output ( premium_command ) . decode ( ' ascii ' ) . split ( ' \n ' ) :
if line . find ( ' cert- ' ) > 0 :
print ( line . strip ( ) )
2019-12-27 08:50:56 +01:00
data = cppcheckdata . CppcheckData ( dumpfile )
2019-07-26 15:40:25 +02:00
if VERIFY :
VERIFY_ACTUAL = [ ]
VERIFY_EXPECTED = [ ]
for tok in data . rawTokens :
if tok . str . startswith ( ' // ' ) and ' TODO ' not in tok . str :
for word in tok . str [ 2 : ] . split ( ' ' ) :
if re . match ( r ' cert-[A-Z][A-Z][A-Z][0-9][0-9].* ' , word ) :
VERIFY_EXPECTED . append ( str ( tok . linenr ) + ' : ' + word )
2019-12-27 08:50:56 +01:00
for cfg in data . iterconfigurations ( ) :
if not args . quiet :
2019-07-26 15:40:25 +02:00
print ( ' Checking %s , config %s ... ' % ( dumpfile , cfg . name ) )
exp05 ( cfg )
exp42 ( cfg )
exp46 ( cfg )
exp15 ( cfg )
int31 ( cfg , data . platform )
str03 ( cfg )
str05 ( cfg )
str07 ( cfg )
str11 ( cfg )
2019-11-21 06:46:46 +01:00
env33 ( cfg )
2019-07-26 15:40:25 +02:00
msc24 ( cfg )
msc30 ( cfg )
api01 ( cfg )
if VERIFY :
2022-03-13 16:42:07 +01:00
fail = False
2019-07-26 15:40:25 +02:00
for expected in VERIFY_EXPECTED :
if expected not in VERIFY_ACTUAL :
print ( ' Expected but not seen: ' + expected )
2022-03-13 16:42:07 +01:00
fail = True
2019-07-26 15:40:25 +02:00
for actual in VERIFY_ACTUAL :
if actual not in VERIFY_EXPECTED :
print ( ' Not expected: ' + actual )
2022-03-13 16:42:07 +01:00
fail = True
if fail :
sys . exit ( 1 )
2020-08-29 07:44:13 +02:00
sys . exit ( cppcheckdata . EXIT_CODE )