diff --git a/c/misra/src/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.ql b/c/misra/src/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.ql new file mode 100644 index 0000000000..70ec91e3c1 --- /dev/null +++ b/c/misra/src/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.ql @@ -0,0 +1,28 @@ +/** + * @id c/misra/ctype-function-arg-not-unsigned-char-or-eof + * @name RULE-21-13: function arguments shall be represented as unsigned char + * @description Passing arguments to functions outside the range of unsigned char or EOF + * causes undefined behavior. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-21-13 + * external/misra/obligation/mandatory + */ + +import cpp +import codingstandards.c.misra +import codingstandards.cpp.CharFunctions +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis + +from UseOfToOrIsChar ctypeCall +where + not isExcluded(ctypeCall, + StandardLibraryFunctionTypesPackage::ctypeFunctionArgNotUnsignedCharOrEofQuery()) and + not exists(Expr ctypeCallArgument | ctypeCallArgument = ctypeCall.getConvertedArgument() | + /* The argument's value should be in the EOF + `unsigned char` range. */ + -1 <= lowerBound(ctypeCallArgument) and upperBound(ctypeCallArgument) <= 255 + ) +select ctypeCall, + "The function " + ctypeCall + " accepts an argument " + ctypeCall.getConvertedArgument() + + " that is not an unsigned char nor an EOF." diff --git a/c/misra/src/rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.ql b/c/misra/src/rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.ql new file mode 100644 index 0000000000..2c585d8f10 --- /dev/null +++ b/c/misra/src/rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.ql @@ -0,0 +1,46 @@ +/** + * @id c/misra/memcpy-memmove-memcmp-arg-not-pointers-to-compatible-types + * @name RULE-21-15: The pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers + * @description Passing pointers to incompatible types as arguments to memcpy, memmove and memcmp + * indicates programmers' confusion. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-21-15 + * external/misra/obligation/required + */ + +import cpp +import codingstandards.c.misra +import codingstandards.c.Pointers + +class MemCmpMoveCpy extends Function { + // Couldn't extend BuiltInFunction because it misses `memcmp` + MemCmpMoveCpy() { + this.getName().regexpMatch("mem(cmp|cpy|move)") and + this.getADeclaration().getAFile().(HeaderFile).getBaseName() = "string.h" + } +} + +from FunctionCall fc +where + not isExcluded(fc, + StandardLibraryFunctionTypesPackage::memcpyMemmoveMemcmpArgNotPointersToCompatibleTypesQuery()) and + exists(MemCmpMoveCpy memfun, Type dstType, Type srcType | fc.getTarget() = memfun | + dstType = fc.getArgument(0).getUnspecifiedType() and + srcType = fc.getArgument(1).getUnspecifiedType() and + ( + /* Case 1: dst and src are pointer types */ + dstType instanceof PointerType and + srcType instanceof PointerType + or + /* Case 2: dst and src are array types */ + dstType instanceof ArrayType and + srcType instanceof ArrayType + ) and + not dstType = srcType + ) +select fc, + "The dest type " + fc.getArgument(0).getUnspecifiedType() + " and src type " + + fc.getArgument(1).getUnspecifiedType() + " of function " + fc.getTarget() + + " are not compatible." diff --git a/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected new file mode 100644 index 0000000000..0a8d568dec --- /dev/null +++ b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected @@ -0,0 +1,2 @@ +| test.c:14:7:14:13 | call to isalnum | The function call to isalnum accepts an argument c3 that is not an unsigned char nor an EOF. | +| test.c:20:7:20:13 | call to isalnum | The function call to isalnum accepts an argument c4 that is not an unsigned char nor an EOF. | \ No newline at end of file diff --git a/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected.clang b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected.clang new file mode 100644 index 0000000000..6af28a74db --- /dev/null +++ b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected.clang @@ -0,0 +1,2 @@ +| test.c:14:7:14:17 | isalnum(c) | The function isalnum(c) accepts an argument (...) that is not an unsigned char nor an EOF. | +| test.c:20:7:20:17 | isalnum(c) | The function isalnum(c) accepts an argument (...) that is not an unsigned char nor an EOF. | \ No newline at end of file diff --git a/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected.gcc b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected.gcc new file mode 100644 index 0000000000..6af28a74db --- /dev/null +++ b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected.gcc @@ -0,0 +1,2 @@ +| test.c:14:7:14:17 | isalnum(c) | The function isalnum(c) accepts an argument (...) that is not an unsigned char nor an EOF. | +| test.c:20:7:20:17 | isalnum(c) | The function isalnum(c) accepts an argument (...) that is not an unsigned char nor an EOF. | \ No newline at end of file diff --git a/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected.qcc b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected.qcc new file mode 100644 index 0000000000..6af28a74db --- /dev/null +++ b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.expected.qcc @@ -0,0 +1,2 @@ +| test.c:14:7:14:17 | isalnum(c) | The function isalnum(c) accepts an argument (...) that is not an unsigned char nor an EOF. | +| test.c:20:7:20:17 | isalnum(c) | The function isalnum(c) accepts an argument (...) that is not an unsigned char nor an EOF. | \ No newline at end of file diff --git a/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.qlref b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.qlref new file mode 100644 index 0000000000..be454538c7 --- /dev/null +++ b/c/misra/test/rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.qlref @@ -0,0 +1 @@ +rules/RULE-21-13/CtypeFunctionArgNotUnsignedCharOrEof.ql \ No newline at end of file diff --git a/c/misra/test/rules/RULE-21-13/test.c b/c/misra/test/rules/RULE-21-13/test.c new file mode 100644 index 0000000000..727ac8ad65 --- /dev/null +++ b/c/misra/test/rules/RULE-21-13/test.c @@ -0,0 +1,39 @@ +#include +#include + +void sample() { + unsigned char c1 = 'c'; + int r1 = isalnum( + c1); // COMPLIANT: ASCII 99 is within unsigned char range of [0, 255] + int r2 = isalnum(EOF); // COMPLIANT: EOF (-1) + + int x3 = 256; + int x4 = x3; + int c3 = x4; + int r3 = + isalnum(c3); // NON_COMPLIANT: is outside unsigned char range of [0, 255] + + unsigned char x5 = EOF; + unsigned char x6 = x5; + int c4 = x6 + 10000; + int r4 = + isalnum(c4); // NON_COMPLIANT: is outside unsigned char range of [0, 255] + + int c5 = getchar(); + int r5 = isalnum( + c5); // COMPLIANT: source functions like getchar are modelled + + unsigned char x7; + int c6; + if (x7 == 1) { + c6 = EOF; + } else { + c6 = 'c'; + } + int r6 = + isalnum(c6); // COMPLIANT: either control branch make this call compliant + + int r7 = isalnum(EOF); // COMPLIANT: EOF (-1) +} + +int main() { return 0; } \ No newline at end of file diff --git a/c/misra/test/rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.expected b/c/misra/test/rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.expected new file mode 100644 index 0000000000..67bb52d079 --- /dev/null +++ b/c/misra/test/rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.expected @@ -0,0 +1,2 @@ +| test.c:6:3:6:8 | call to memcpy | The dest type int * and src type char * of function memcpy are not compatible. | +| test.c:18:3:18:9 | call to memmove | The dest type char[9] and src type int[2] of function memmove are not compatible. | \ No newline at end of file diff --git a/c/misra/test/rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.qlref b/c/misra/test/rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.qlref new file mode 100644 index 0000000000..8acf3deee7 --- /dev/null +++ b/c/misra/test/rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.qlref @@ -0,0 +1 @@ +rules/RULE-21-15/MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes.ql \ No newline at end of file diff --git a/c/misra/test/rules/RULE-21-15/test.c b/c/misra/test/rules/RULE-21-15/test.c new file mode 100644 index 0000000000..4a3f233c11 --- /dev/null +++ b/c/misra/test/rules/RULE-21-15/test.c @@ -0,0 +1,25 @@ +#include + +void sample() { + int from1 = 1000000; + char to1; + memcpy(&from1, &to1, 1); // NON_COMPLIANT, the types are not compatible + + int from2 = 1000000; + int to2; + memcpy(&from2, &to2, 2); // COMPLIANT + + char from3[] = "string"; + char to3[7]; + memmove(from3, to3, 7); // COMPLIANT + + char from4[] = "sstringg"; + int to4[2]; + memmove(from4, to4, 8); // NON_COMPLIANT, despite being equal in byte counts + + char from5[] = "STRING"; + char to5[] = "string"; + memcmp(from5, to5, 2); // COMPLIANT +} + +int main() { return 0; } \ No newline at end of file diff --git a/cpp/common/src/codingstandards/cpp/CharFunctions.qll b/cpp/common/src/codingstandards/cpp/CharFunctions.qll index 7f69c353e5..bb47f77101 100644 --- a/cpp/common/src/codingstandards/cpp/CharFunctions.qll +++ b/cpp/common/src/codingstandards/cpp/CharFunctions.qll @@ -18,7 +18,6 @@ private string getCToOrIsName() { * the structure of the macro, or */ abstract class UseOfToOrIsChar extends Element { - /** */ abstract Expr getConvertedArgument(); } diff --git a/cpp/common/src/codingstandards/cpp/SimpleRangeAnalysisCustomizations.qll b/cpp/common/src/codingstandards/cpp/SimpleRangeAnalysisCustomizations.qll index c06b6584a6..469fe9a738 100644 --- a/cpp/common/src/codingstandards/cpp/SimpleRangeAnalysisCustomizations.qll +++ b/cpp/common/src/codingstandards/cpp/SimpleRangeAnalysisCustomizations.qll @@ -151,6 +151,28 @@ private class CastEnumToIntegerSimpleRange extends SimpleRangeAnalysisExpr, Cast override predicate dependsOnChild(Expr child) { child = getExpr() } } +/** + * functions that read a character from the STDIN, + * or return EOF if it fails to do so. + * Their return type is `int` by their signatures, but + * they actually return either an unsigned char or an EOF. + */ +private class CtypeGetcharFunctionsRange extends SimpleRangeAnalysisExpr, FunctionCall { + CtypeGetcharFunctionsRange() { + this.getTarget().getFile().(HeaderFile).getBaseName() = "stdio.h" and + this.getTarget().getName().regexpMatch("(fgetc|getc|getchar|)") + } + + /* It can return an EOF, which is -1 on most implementations. */ + override float getLowerBounds() { result = -1 } + + /* Otherwise, it can return any unsigned char. */ + override float getUpperBounds() { result = 255 } + + /* No, its call does not depend on any of its child. */ + override predicate dependsOnChild(Expr expr) { none() } +} + /** * Gets the value of the expression `e`, if it is a constant. * diff --git a/cpp/common/src/codingstandards/cpp/exclusions/c/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/c/RuleMetadata.qll index dc8bdcbae2..977123ae5d 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/c/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/c/RuleMetadata.qll @@ -49,6 +49,7 @@ import Preprocessor6 import SideEffects1 import SideEffects2 import SignalHandlers +import StandardLibraryFunctionTypes import Strings1 import Strings2 import Strings3 @@ -103,6 +104,7 @@ newtype TCQuery = TSideEffects1PackageQuery(SideEffects1Query q) or TSideEffects2PackageQuery(SideEffects2Query q) or TSignalHandlersPackageQuery(SignalHandlersQuery q) or + TStandardLibraryFunctionTypesPackageQuery(StandardLibraryFunctionTypesQuery q) or TStrings1PackageQuery(Strings1Query q) or TStrings2PackageQuery(Strings2Query q) or TStrings3PackageQuery(Strings3Query q) or @@ -157,6 +159,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isSideEffects1QueryMetadata(query, queryId, ruleId, category) or isSideEffects2QueryMetadata(query, queryId, ruleId, category) or isSignalHandlersQueryMetadata(query, queryId, ruleId, category) or + isStandardLibraryFunctionTypesQueryMetadata(query, queryId, ruleId, category) or isStrings1QueryMetadata(query, queryId, ruleId, category) or isStrings2QueryMetadata(query, queryId, ruleId, category) or isStrings3QueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/common/src/codingstandards/cpp/exclusions/c/StandardLibraryFunctionTypes.qll b/cpp/common/src/codingstandards/cpp/exclusions/c/StandardLibraryFunctionTypes.qll new file mode 100644 index 0000000000..0d86bd9014 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/c/StandardLibraryFunctionTypes.qll @@ -0,0 +1,46 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype StandardLibraryFunctionTypesQuery = + TCtypeFunctionArgNotUnsignedCharOrEofQuery() or + TMemcpyMemmoveMemcmpArgNotPointersToCompatibleTypesQuery() + +predicate isStandardLibraryFunctionTypesQueryMetadata( + Query query, string queryId, string ruleId, string category +) { + query = + // `Query` instance for the `ctypeFunctionArgNotUnsignedCharOrEof` query + StandardLibraryFunctionTypesPackage::ctypeFunctionArgNotUnsignedCharOrEofQuery() and + queryId = + // `@id` for the `ctypeFunctionArgNotUnsignedCharOrEof` query + "c/misra/ctype-function-arg-not-unsigned-char-or-eof" and + ruleId = "RULE-21-13" and + category = "mandatory" + or + query = + // `Query` instance for the `memcpyMemmoveMemcmpArgNotPointersToCompatibleTypes` query + StandardLibraryFunctionTypesPackage::memcpyMemmoveMemcmpArgNotPointersToCompatibleTypesQuery() and + queryId = + // `@id` for the `memcpyMemmoveMemcmpArgNotPointersToCompatibleTypes` query + "c/misra/memcpy-memmove-memcmp-arg-not-pointers-to-compatible-types" and + ruleId = "RULE-21-15" and + category = "required" +} + +module StandardLibraryFunctionTypesPackage { + Query ctypeFunctionArgNotUnsignedCharOrEofQuery() { + //autogenerate `Query` type + result = + // `Query` type for `ctypeFunctionArgNotUnsignedCharOrEof` query + TQueryC(TStandardLibraryFunctionTypesPackageQuery(TCtypeFunctionArgNotUnsignedCharOrEofQuery())) + } + + Query memcpyMemmoveMemcmpArgNotPointersToCompatibleTypesQuery() { + //autogenerate `Query` type + result = + // `Query` type for `memcpyMemmoveMemcmpArgNotPointersToCompatibleTypes` query + TQueryC(TStandardLibraryFunctionTypesPackageQuery(TMemcpyMemmoveMemcmpArgNotPointersToCompatibleTypesQuery())) + } +} diff --git a/rule_packages/c/StandardLibraryFunctionTypes.json b/rule_packages/c/StandardLibraryFunctionTypes.json new file mode 100644 index 0000000000..274eadbced --- /dev/null +++ b/rule_packages/c/StandardLibraryFunctionTypes.json @@ -0,0 +1,38 @@ +{ + "MISRA-C-2012": { + "RULE-21-13": { + "properties": { + "obligation": "mandatory" + }, + "queries": [ + { + "description": "Passing arguments to functions outside the range of unsigned char or EOF causes undefined behavior.", + "kind": "problem", + "name": " function arguments shall be represented as unsigned char", + "precision": "very-high", + "severity": "error", + "short_name": "CtypeFunctionArgNotUnsignedCharOrEof", + "tags": [] + } + ], + "title": "Any value passed to a function in shall be representable as an unsigned char or be the value EOF" + }, + "RULE-21-15": { + "properties": { + "obligation": "required" + }, + "queries": [ + { + "description": "Passing pointers to incompatible types as arguments to memcpy, memmove and memcmp indicates programmers' confusion.", + "kind": "problem", + "name": "The pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers", + "precision": "very-high", + "severity": "error", + "short_name": "MemcpyMemmoveMemcmpArgNotPointersToCompatibleTypes", + "tags": [] + } + ], + "title": "The pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible types" + } + } +} \ No newline at end of file diff --git a/rules.csv b/rules.csv index ab73c95d96..59d4bc3fda 100644 --- a/rules.csv +++ b/rules.csv @@ -755,9 +755,9 @@ c,MISRA-C-2012,RULE-21-9,Yes,Required,,,The Standard Library functions bsearch a c,MISRA-C-2012,RULE-21-10,Yes,Required,,,The Standard Library time and date functions shall not be used,,Banned,Easy, c,MISRA-C-2012,RULE-21-11,Yes,Required,,,The standard header file shall not be used,,Banned,Easy, c,MISRA-C-2012,RULE-21-12,Yes,Advisory,,,The exception handling features of should not be used,,Banned,Easy, -c,MISRA-C-2012,RULE-21-13,Yes,Mandatory,,,Any value passed to a function in shall be representable as an unsigned char or be the value EOF,,Types,Medium, +c,MISRA-C-2012,RULE-21-13,Yes,Mandatory,,,Any value passed to a function in shall be representable as an unsigned char or be the value EOF,,StandardLibraryFunctionTypes,Medium, c,MISRA-C-2012,RULE-21-14,Yes,Required,,,The Standard Library function memcmp shall not be used to compare null terminated strings,,EssentialTypes,Hard, -c,MISRA-C-2012,RULE-21-15,Yes,Required,,,"The pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible types",,Types,Medium, +c,MISRA-C-2012,RULE-21-15,Yes,Required,,,"The pointer arguments to the Standard Library functions memcpy, memmove and memcmp shall be pointers to qualified or unqualified versions of compatible types",,StandardLibraryFunctionTypes,Medium, c,MISRA-C-2012,RULE-21-16,Yes,Required,,,"The pointer arguments to the Standard Library function memcmp shall point to either a pointer type, an essentially signed type, an essentially unsigned type, an essentially Boolean type or an essentially enum type",,EssentialTypes,Medium, c,MISRA-C-2012,RULE-21-17,Yes,Mandatory,,,Use of the string handling functions from shall not result in accesses beyond the bounds of the objects referenced by their pointer parameters,,Memory2,Hard, c,MISRA-C-2012,RULE-21-18,Yes,Mandatory,,,The size_t argument passed to any function in shall have an appropriate value,,OutOfBounds,Hard,