Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import cpp
import codingstandards.c.cert
import codingstandards.cpp.SideEffect
import semmle.code.cpp.dataflow.TaintTracking
import semmle.code.cpp.dataflow.new.TaintTracking
import semmle.code.cpp.valuenumbering.GlobalValueNumbering

/** Holds if the function's return value is derived from the `AliasParamter` p. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
| test.c:45:17:45:30 | ... + ... | Buffer may access up to offset 101*1 which is greater than the fixed size 100 of the $@. | test.c:45:17:45:22 | buffer | buffer |
| test.c:55:5:55:13 | ... - ... | Buffer access may be to a negative index in the buffer. | test.c:55:5:55:9 | ptr16 | buffer |
| test.c:57:5:57:14 | ... + ... | Buffer accesses offset 22 which is greater than the fixed size 20 of the $@. | test.c:57:5:57:9 | ptr16 | buffer |
| test.c:58:5:58:14 | ... - ... | Buffer access may be to a negative index in the buffer. | test.c:55:5:55:9 | ptr16 | buffer |
| test.c:58:5:58:14 | ... - ... | Buffer access may be to a negative index in the buffer. | test.c:56:5:56:9 | ptr16 | buffer |
| test.c:58:5:58:14 | ... - ... | Buffer access may be to a negative index in the buffer. | test.c:57:5:57:9 | ptr16 | buffer |
| test.c:58:5:58:14 | ... - ... | Buffer access may be to a negative index in the buffer. | test.c:58:5:58:9 | ptr16 | buffer |
| test.c:63:3:63:9 | access to array | Buffer access may be to a negative index in the buffer. | test.c:63:3:63:5 | arr | buffer |
| test.c:65:3:65:9 | access to array | Buffer accesses offset 44 which is greater than the fixed size 40 of the $@. | test.c:65:3:65:5 | arr | buffer |
| test.c:66:3:66:10 | access to array | Buffer access may be to a negative index in the buffer. | test.c:63:3:63:5 | arr | buffer |
| test.c:66:3:66:10 | access to array | Buffer access may be to a negative index in the buffer. | test.c:64:3:64:5 | arr | buffer |
| test.c:66:3:66:10 | access to array | Buffer access may be to a negative index in the buffer. | test.c:65:3:65:5 | arr | buffer |
| test.c:66:3:66:10 | access to array | Buffer access may be to a negative index in the buffer. | test.c:66:3:66:5 | arr | buffer |
Original file line number Diff line number Diff line change
@@ -1,25 +1 @@
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:28,31-39)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:28,59-67)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:31,33-41)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:31,57-65)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:35,33-41)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:35,59-67)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:44,5-13)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:44,25-33)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:44,53-61)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:47,31-39)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:47,57-65)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:56,31-39)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:56,55-63)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:63,31-39)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:63,57-65)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:75,31-39)
WARNING: module 'DataFlow' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:75,55-63)
WARNING: module 'TaintTracking' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:28,5-18)
WARNING: module 'TaintTracking' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:31,7-20)
WARNING: module 'TaintTracking' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:35,7-20)
WARNING: module 'TaintTracking' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:47,5-18)
WARNING: module 'TaintTracking' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:56,5-18)
WARNING: module 'TaintTracking' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:63,5-18)
WARNING: module 'TaintTracking' has been deprecated and may be removed in future (DependenceOnOrderOfFunctionArgumentsForSideEffects.ql:75,5-18)
| test.c:20:3:20:4 | call to f1 | Depending on the order of evaluation for the arguments $@ and $@ for side effects on shared state is unspecified and can result in unexpected behavior. | test.c:20:6:20:7 | call to f2 | call to f2 | test.c:20:12:20:13 | call to f3 | call to f3 |
88 changes: 36 additions & 52 deletions c/common/src/codingstandards/c/OutOfBounds.qll
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import codingstandards.cpp.Allocations
import codingstandards.cpp.Overflow
import codingstandards.cpp.PossiblyUnsafeStringOperation
import codingstandards.cpp.SimpleRangeAnalysisCustomizations
private import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.ir.IR
import semmle.code.cpp.valuenumbering.GlobalValueNumbering

module OOB {
Expand Down Expand Up @@ -319,15 +319,6 @@ module OOB {
none()
}

/**
* Holds if `i` is the index of a parameter of this function that expects an element count rather than buffer size argument.
* This predicate should be overriden by extending classes to specify length parameters, if necessary.
*/
predicate getALengthParameterIndex(int i) {
// by default, size parameters do not exclude the size of a null terminator
none()
}

/**
* Holds if the read or write parameter at index `i` is allowed to be null.
* This predicate should be overriden by extending classes to specify permissibly null parameters, if necessary.
Expand Down Expand Up @@ -379,9 +370,9 @@ module OOB {
class StrncatLibraryFunction extends StringConcatenationFunctionLibraryFunction {
StrncatLibraryFunction() { this.getName() = getNameOrInternalName(["strncat", "wcsncat"]) }

override predicate getALengthParameterIndex(int i) {
// `strncat` and `wcsncat` exclude the size of a null terminator
i = 2
override predicate getANullTerminatedParameterIndex(int i) {
// The destination must be null-terminated.
i = 0
}
}

Expand Down Expand Up @@ -645,8 +636,7 @@ module OOB {
}

/**
* A class for reasoning about the offset of a variable from the original value flowing to it
* as a result of arithmetic or pointer arithmetic expressions.
* Gets the offset of `expr` from `base` due to arithmetic or pointer arithmetic.
*/
bindingset[expr]
private int getArithmeticOffsetValue(Expr expr, Expr base) {
Expand Down Expand Up @@ -906,6 +896,24 @@ module OOB {
override predicate isNotNullTerminated() { none() }
}

/** Gets a dataflow node at which to track the buffer or size used by `use`. */
private DataFlow::Node getBufferOrSizeUseNode(Expr use) {
exists(Expr base |
exists(getArithmeticOffsetValue(use, base)) and
(
result = DataFlow::exprNode(base)
or
// Returned-pointer flow can bypass the AST base call and reach the arithmetic
// instruction or its converted value instead.
exists(PointerOffsetInstruction arithmetic |
arithmetic.getAst() = [use, use.(AddressOfExpr).getOperand()] and
DataFlow::localFlow(DataFlow::instructionNode(arithmetic), result) and
(result.asInstruction() = arithmetic or result.asExpr() = use)
)
)
)
}

private module PointerToObjectSourceOrSizeToBufferAccessFunctionConfig implements
DataFlow::ConfigSig
{
Expand All @@ -926,7 +934,7 @@ module OOB {
) and
(
sink.asExpr() = arg or
exists(getArithmeticOffsetValue(arg, sink.asExpr()))
sink = getBufferOrSizeUseNode(arg)
)
)
}
Expand Down Expand Up @@ -956,11 +964,8 @@ module OOB {
DataFlow::Global<PointerToObjectSourceOrSizeToBufferAccessFunctionConfig>;

private predicate hasFlowFromBufferOrSizeExprToUse(Expr source, Expr use) {
exists(Expr useOrChild |
exists(getArithmeticOffsetValue(use, useOrChild)) and
PointerToObjectSourceOrSizeToBufferAccessFunctionFlow::flow(DataFlow::exprNode(source),
DataFlow::exprNode(useOrChild))
)
PointerToObjectSourceOrSizeToBufferAccessFunctionFlow::flow(DataFlow::exprNode(source),
getBufferOrSizeUseNode(use))
}

private predicate bufferUseComputableBufferSize(
Expand Down Expand Up @@ -1017,26 +1022,6 @@ module OOB {
hasFlowFromBufferOrSizeExprToUse(allocSize, bufferSizeArg)
}

/**
* Holds if `arg` refers to the number of characters excluding a null terminator
*/
bindingset[fc, arg]
private predicate isArgNumCharacters(BufferAccessLibraryFunctionCall fc, Expr arg) {
exists(int i |
arg = fc.getArgument(i) and
fc.getTarget().(BufferAccessLibraryFunction).getALengthParameterIndex(i)
)
}

/**
* Returns '1' if `arg` refers to the number of characters excluding a null terminator,
* otherwise '0' if `arg` refers to the number of characters including a null terminator.
*/
bindingset[fc, arg]
private int argNumCharactersOffset(BufferAccess fc, Expr arg) {
if isArgNumCharacters(fc, arg) then result = 1 else result = 0
}

/**
* Holds if the call `fc` may result in an invalid buffer access due a read buffer being bigger
* than the write buffer. This heuristic is useful for cases such as strcpy(dst, src).
Expand All @@ -1059,6 +1044,13 @@ module OOB {
writeBufferSizeBase - writeSizeMult * getArithmeticOffsetValue(writeBuffer, _) and
// the read buffer size is larger than the write buffer size
readBufferSize > writeBufferSize and
// bounded concatenation appends at most `n` elements and a null terminator
not exists(Expr readSizeArg, int readSizeArgValue |
fc.getTarget() instanceof StrncatLibraryFunction and
readSizeArg = fc.getReadSizeArg(readSizeMult) and
sizeExprComputableSize(readSizeArg, _, readSizeArgValue) and
writeSizeMult.(float) * (readSizeArgValue + 1).(float) <= writeBufferSize
) and
(
// if a size arg exists and it is computable, then it must be <= to the write buffer size
exists(fc.getWriteSizeArg(writeSizeMult))
Expand All @@ -1068,9 +1060,7 @@ module OOB {
not exists(Expr writeSizeArg, int writeSizeArgValue |
writeSizeArg = fc.getWriteSizeArg(writeSizeMult) and
sizeExprComputableSize(writeSizeArg, _, writeSizeArgValue) and
writeSizeMult.(float) *
(writeSizeArgValue + argNumCharactersOffset(fc, writeSizeArg)).(float) <=
writeBufferSize
writeSizeMult.(float) * writeSizeArgValue.(float) <= writeBufferSize
)
)
)
Expand Down Expand Up @@ -1102,14 +1092,8 @@ module OOB {
// Handle cases such as *(ptr - 1)
(
if isSizeArgPointerSubExprRightOperand(sizeArg)
then
computedSizeAccessed =
sizeMult.(float) *
(-sizeArgValue + argNumCharactersOffset(bufferAccess, sizeArg)).(float)
else
computedSizeAccessed =
sizeMult.(float) *
(sizeArgValue + argNumCharactersOffset(bufferAccess, sizeArg)).(float)
then computedSizeAccessed = sizeMult.(float) * (-sizeArgValue).(float)
else computedSizeAccessed = sizeMult.(float) * sizeArgValue.(float)
) and
computedBufferSize < computedSizeAccessed
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
problems
| test.c:11:7:11:12 | * ... | test.c:18:16:18:21 | call to getenv | test.c:11:7:11:12 | * ... | The object returned by the function getenv should not be modified. |
| test.c:11:8:11:12 | c_str | test.c:18:16:18:21 | call to getenv | test.c:11:7:11:12 | * ... | The object returned by the function getenv should not be modified. |
| test.c:67:5:67:9 | conv4 | test.c:64:11:64:20 | call to localeconv | test.c:67:5:67:9 | conv4 | The object returned by the function localeconv should not be modified. |
| test.c:76:5:76:8 | conv | test.c:72:25:72:34 | call to localeconv | test.c:76:5:76:8 | conv | The object returned by the function localeconv should not be modified. |
Expand Down
14 changes: 13 additions & 1 deletion c/misra/src/rules/RULE-14-3/ControllingExprInvariant.ql
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ import cpp
import codingstandards.c.misra
import codingstandards.c.misra.EssentialTypes

/** Holds if `expr` has an evaluated operand that prevents the constant-expression exception. */
private predicate hasEvaluatedNonConstantOperand(Expr expr) {
not expr.isUnevaluated() and
(
expr instanceof VariableAccess or
expr instanceof FunctionCall or
expr instanceof CommaExpr or
hasEvaluatedNonConstantOperand(expr.getAChild())
)
}

from Expr expr, string message
where
not isExcluded(expr, Statements5Package::controllingExprInvariantQuery()) and
Expand All @@ -40,7 +51,8 @@ where
conditionAlwaysFalse(expr) and
not (
getEssentialTypeCategory(getEssentialType(expr)) instanceof EssentiallyBooleanType and
expr.getValue() = "0"
expr.getValue() = "0" and
not hasEvaluatedNonConstantOperand(expr)
)
or
conditionAlwaysTrue(expr) and
Expand Down
10 changes: 7 additions & 3 deletions c/misra/test/rules/RULE-14-3/ControllingExprInvariant.expected
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
| test.c:16:9:16:13 | ... > ... | Controlling expression in if statement has an invariant value. |
| test.c:20:20:20:24 | ... < ... | Controlling expression in loop statement has an invariant value. |
| test.c:27:10:27:14 | ... < ... | Controlling expression in loop statement has an invariant value. |
| test.c:37:3:37:6 | 1 | Controlling expression in conditional statement has an invariant value. |
| test.c:38:3:38:3 | 1 | Controlling expression in conditional statement has an invariant value. |
| test.c:45:10:45:26 | ... && ... | Controlling expression in loop statement has an invariant value. |
| test.c:35:12:35:12 | 0 | Controlling expression in loop statement has an invariant value. |
| test.c:41:3:41:6 | 1 | Controlling expression in conditional statement has an invariant value. |
| test.c:42:3:42:3 | 1 | Controlling expression in conditional statement has an invariant value. |
| test.c:49:10:49:26 | ... && ... | Controlling expression in loop statement has an invariant value. |
| test.c:51:10:51:21 | ... && ... | Controlling expression in loop statement has an invariant value. |
| test.c:66:10:66:33 | ... && ... | Controlling expression in loop statement has an invariant value. |
| test.c:68:11:68:21 | ... , ... | Controlling expression in loop statement has an invariant value. |
27 changes: 25 additions & 2 deletions c/misra/test/rules/RULE-14-3/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ void f1(int p1) {

void f2() {
while (20 > 10) { // NON_COMPLIANT
if (1 > 2) {
} // NON_COMPLIANT
if (1 > 2) { // NON_COMPLIANT
}
}

for (int i = 10; i < 5; i++) { // NON_COMPLIANT
Expand All @@ -31,6 +31,10 @@ void f3() {
void f4() {
do {
} while (0u == 1u); // COMPLIANT - by exception 2
do {
} while (0); // NON_COMPLIANT - a bare literal `0` is not essentially Boolean
Comment thread
mbaluda marked this conversation as resolved.
do {
} while (false); // COMPLIANT - by exception 2
}

void f5(bool b1) {
Expand All @@ -44,4 +48,23 @@ void f6(int p1) {
}
while (1 == 0 && p1 > 12) { // NON_COMPLIANT
}
while (0 && p1 > 12) { // NON_COMPLIANT
Comment thread
mbaluda marked this conversation as resolved.
}
}

bool get_flag(void);

void f7(int p1) {
do {
} while (sizeof(p1) == 0u); // COMPLIANT
do {
} while (_Alignof(p1) == 0u); // COMPLIANT
do {
} while (sizeof(get_flag()) == 0u); // COMPLIANT
do {
} while (sizeof((p1++, get_flag())) == 0u); // COMPLIANT
while (get_flag() && (0u == 1u)) { // NON_COMPLIANT
}
while ((0, 0u == 1u)) { // NON_COMPLIANT
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,5 @@
| test.c:94:5:94:11 | call to strncmp | The size of the $@ passed to strncmp is 5 bytes, but the $@ is 6 bytes. | test.c:94:23:94:29 | ca5_bad | read buffer | test.c:94:32:94:32 | 6 | size argument |
| test.c:101:5:101:11 | call to strxfrm | The size of the $@ passed to strxfrm is 64 bytes, but the $@ is 65 bytes. | test.c:101:13:101:15 | buf | write buffer | test.c:101:25:101:39 | ... + ... | size argument |
| test.c:103:5:103:11 | call to strxfrm | The $@ passed to strxfrm might not be null-terminated. | test.c:103:22:103:25 | buf2 | argument | test.c:103:22:103:25 | buf2 | |
| test.c:114:5:114:11 | call to strncat | The size of the $@ passed to strncat is 1 bytes, but the $@ is 2 bytes. | test.c:114:26:114:31 | source | read buffer | test.c:114:34:114:34 | 2 | size argument |
| test.c:124:5:124:11 | call to strncat | The size of the $@ passed to strncat is 3 bytes, but the size of the $@ is only 2 bytes. | test.c:124:26:124:31 | source | read buffer | test.c:124:13:124:23 | destination | write buffer |
22 changes: 21 additions & 1 deletion c/misra/test/rules/RULE-21-18/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,24 @@ void test(void) {
strxfrm(buf + 1, buf2,
sizeof(buf) - 1); // NON_COMPLIANT - not null-terminated
}
}
{
char destination[2] = {0};
char source[1] = {'x'};
strncat(destination, source, 1); // COMPLIANT
}
{
char destination[10] = {0};
char source[1] = {'x'};
strncat(destination, source, 2); // NON_COMPLIANT
}
{
char destination[2] = {0};
char source[3] = {'x', 'y', 'z'};
strncat(destination, source, 1); // COMPLIANT
}
{
char destination[2] = {0};
char source[3] = {'x', 'y', 'z'};
strncat(destination, source, 2); // NON_COMPLIANT - null-terminator past end
}
}
14 changes: 14 additions & 0 deletions change_notes/2026-08-28-cpp-all-upgrade-result-changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
- `ENV30-C`, `RULE-21-19`, `RULE-25-5-2`: removed duplicate alerts for the same modification of a
pointer returned by an environment or locale function.
- `RULE-14-3`: loop controlling expressions with an invariant false value are now reported when
they use the integer literal `0`, including within compound expressions.
Unevaluated operands of `sizeof` and `_Alignof` do not prevent the constant-expression exception,
while evaluated function calls and comma operators do.
- `ARR30-C`: negative out-of-bounds accesses may now produce a result for each reaching buffer
expression.
- `ARR38-C`, `RULE-21-17`, `RULE-21-18`, `RULE-8-7-1`: corrected the modeling of `strncat` and
`wcsncat`. Their destination must be null-terminated, while their source does not need a null
terminator within the specified character limit. Avoided comparing the full source buffer with
the destination when the bounded append and its null terminator fit.
Removed the unused `getALengthParameterIndex` customization point and its terminator-adjustment
helpers from the shared bounds libraries.
Loading
Loading