Skip to content

Commit 3b8361a

Browse files
author
Nikita Kraiouchkine
committed
Expand ARR30-C coverage and test + add comments
1 parent 0f6e747 commit 3b8361a

File tree

3 files changed

+108
-13
lines changed

3 files changed

+108
-13
lines changed

c/cert/test/rules/ARR30-C/DoNotFormOutOfBoundsPointersOrArraySubscripts.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@
33
| test.c:21:5:21:15 | ... + ... | Buffer access may be to a negative index in the buffer. | test.c:21:5:21:7 | arr | buffer |
44
| test.c:41:17:41:30 | ... + ... | Buffer access may be to a negative index in the buffer. | test.c:41:17:41:22 | buffer | buffer |
55
| test.c:45:17:45:30 | ... + ... | Buffer accesses 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 |
6+
| 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 |
7+
| 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 |
8+
| 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 |
9+
| 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 |
10+
| 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 |
11+
| 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 |

c/cert/test/rules/ARR30-C/test.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ enum { ARRAY_SIZE = 100 };
44

55
static int arr[ARRAY_SIZE];
66

7-
void test_fixed_wrong() {
7+
void test_fixed_wrong(void) {
88
arr + 101; // NON_COMPLIANT
99
}
1010

11-
void test_fixed_right() {
11+
void test_fixed_right(void) {
1212
arr + 2; // COMPLIANT
1313
}
1414

@@ -48,4 +48,20 @@ void test_local_buffer_invalid_check(int index) {
4848
if (index >= 0 && index < ARRAY_SIZE) {
4949
char *ptr = buffer + index; // COMPLIANT
5050
}
51+
}
52+
53+
void test_dereference_pointer_arithmetic_const(void) {
54+
short ptr16[10];
55+
*(ptr16 - 1); // NON_COMPLIANT - offset is negative
56+
*(ptr16 + 5); // COMPLIANT
57+
*(ptr16 + 11); // NON_COMPLIANT - offset is too large
58+
*(ptr16 - 11); // NON_COMPLIANT - offset is negative
59+
}
60+
61+
void test_array_expr_const(void) {
62+
int arr[10];
63+
arr[-1]; // NON_COMPLIANT - offset is negative
64+
arr[5]; // COMPLIANT
65+
arr[11]; // NON_COMPLIANT - offset is too large
66+
arr[-11]; // NON_COMPLIANT - offset is negative
5167
}

c/common/src/codingstandards/c/OutOfBounds.qll

Lines changed: 84 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import semmle.code.cpp.dataflow.DataFlow
1515
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
1616

1717
module OOB {
18+
/**
19+
* Holds if `result` is either `name` or a string matching a pattern such as
20+
* `__builtin_*name*_chk` or similar. This predicate exists to model internal functions
21+
* such as `__builtin___memcpy_chk` under a common `memcpy` name in the table.
22+
*/
1823
bindingset[name, result]
1924
string getNameOrInternalName(string name) {
2025
result = name or
@@ -288,6 +293,10 @@ module OOB {
288293
p.getType().getUnspecifiedType().(DerivedType).getBaseType().getSize().maximum(1) = result
289294
}
290295

296+
/**
297+
* Holds if `i` is the index of a parameter of this function that requires arguments to be null-terminated.
298+
* This predicate should be overriden by extending classes to specify null-terminated parameters, if necessary.
299+
*/
291300
predicate getANullTerminatedParameterIndex(int i) {
292301
// by default, require null-terminated parameters for src but
293302
// only if the type of src is a plain char pointer or wchar_t.
@@ -301,19 +310,28 @@ module OOB {
301310
)
302311
}
303312

313+
/**
314+
* Holds if `i` is the index of a parameter of this function that is a size multiplier.
315+
* This predicate should be overriden by extending classes to specify size multiplier parameters, if necessary.
316+
*/
304317
predicate getASizeMultParameterIndex(int i) {
305318
// by default, there is no size multiplier parameter
306319
// exceptions: fread, fwrite, bsearch, qsort
307320
none()
308321
}
309322

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

315332
/**
316333
* Holds if the read or write parameter at index `i` is allowed to be null.
334+
* This predicate should be overriden by extending classes to specify permissibly null parameters, if necessary.
317335
*/
318336
predicate getAPermissiblyNullParameterIndex(int i) {
319337
// by default, pointer parameters are not allowed to be null
@@ -569,6 +587,8 @@ module OOB {
569587
}
570588

571589
int getSizeMultArgValue() {
590+
// Note: This predicate currently expects the size multiplier argument to be a constant.
591+
// This implementation could be improved with range-analysis or data-flow to determine the argument value.
572592
exists(int i |
573593
this.getTarget().(BufferAccessLibraryFunction).getASizeMultParameterIndex(i) and
574594
result = this.getArgument(i).getValue().toInt()
@@ -596,6 +616,12 @@ module OOB {
596616
* Because range-analysis can over-widen bounds, take the minimum of range analysis and data-flow sources.
597617
*
598618
* If there is no source value that flows to `e`, this predicate does not hold.
619+
*
620+
* This predicate, if `e` is the size argument to malloc, would return `20` for the following example:
621+
* ```
622+
* size_t sz = condition ? 10 : 20;
623+
* malloc(sz);
624+
* ```
599625
*/
600626
private int getMaxStatedValue(Expr e) {
601627
result = upperBound(e).minimum(max(getSourceConstantExpr(e).getValue().toInt()))
@@ -606,6 +632,12 @@ module OOB {
606632
* Because range-analysis can over-widen bounds, take the minimum of range analysis and data-flow sources.
607633
*
608634
* If there is no source value that flows to `e`, this predicate does not hold.
635+
*
636+
* This predicate, if `e` is the size argument to malloc, would return `10` for the following example:
637+
* ```
638+
* size_t sz = condition ? 10 : 20;
639+
* malloc(sz);
640+
* ```
609641
*/
610642
private int getMinStatedValue(Expr e) {
611643
result = upperBound(e).minimum(min(getSourceConstantExpr(e).getValue().toInt()))
@@ -809,6 +841,8 @@ module OOB {
809841
override predicate isNotNullTerminated() {
810842
// StringLiteral::getOriginalLength uses Expr::getValue, which implicitly truncates string literal
811843
// values to the length fitting the buffer they are assigned to, thus breaking the 'obvious' check.
844+
// Note: `CharArrayInitializedWithStringLiteral` falsely reports the string literal length in certain cases
845+
// (e.g. when the string literal contains escape characters or on certain compilers), resulting in false-negatives
812846
exists(CharArrayInitializedWithStringLiteral init |
813847
init = this.(VariableAccess).getTarget().getInitializer().getExpr() and
814848
init.getStringLiteralLength() + 1 > init.getContainerLength()
@@ -869,7 +903,8 @@ module OOB {
869903
override predicate isNotNullTerminated() { none() }
870904
}
871905

872-
private class PointerToObjectSourceOrSizeToBufferAccessFunctionConfig extends DataFlow::Configuration {
906+
private class PointerToObjectSourceOrSizeToBufferAccessFunctionConfig extends DataFlow::Configuration
907+
{
873908
PointerToObjectSourceOrSizeToBufferAccessFunctionConfig() {
874909
this = "PointerToObjectSourceOrSizeToBufferAccessFunctionConfig"
875910
}
@@ -922,9 +957,11 @@ module OOB {
922957
)
923958
}
924959

925-
private predicate bufferUseComputableBufferSize(Expr bufferUse, Expr source, int size) {
960+
private predicate bufferUseComputableBufferSize(
961+
Expr bufferUse, PointerToObjectSource source, int size
962+
) {
926963
// flow from a PointerToObjectSource for which we can compute the exact size
927-
size = source.(PointerToObjectSource).getFixedSize() and
964+
size = source.getFixedSize() and
928965
hasFlowFromBufferOrSizeExprToUse(source, bufferUse)
929966
}
930967

@@ -933,12 +970,21 @@ module OOB {
933970
hasFlowFromBufferOrSizeExprToUse(source.(DynamicAllocationSource), bufferUse)
934971
}
935972

973+
/**
974+
* Relates `sizeExpr`, a buffer access size expresion, to `source`, which is either `sizeExpr`
975+
* if `sizeExpr` has a stated value, or a `DynamicAllocationSource::getSizeExprSource` for which
976+
* we can compute the exact size and that has flow to `sizeExpr`.
977+
*/
936978
private predicate sizeExprComputableSize(Expr sizeExpr, Expr source, int size) {
937-
// computable direct value
979+
// computable direct value, e.g. array_base[10], where "10" is sizeExpr and source.
938980
size = getMinStatedValue(sizeExpr) and
939981
source = sizeExpr
940982
or
941-
// computable source value that flows to the size expression
983+
// computable source value that flows to the size expression, e.g. in cases such as the following:
984+
// size_t sz = 10;
985+
// malloc(sz);
986+
// ... sz passed interprocedurally to another function ...
987+
// use(p, sz + 1);
942988
size = source.(DynamicAllocationSource).getFixedSize() + getArithmeticOffsetValue(sizeExpr, _) and
943989
hasFlowFromBufferOrSizeExprToUse(source.(DynamicAllocationSource).getSizeExprSource(_, _),
944990
sizeExpr)
@@ -1025,6 +1071,13 @@ module OOB {
10251071
)
10261072
}
10271073

1074+
/**
1075+
* Holds if `sizeArg` is the right operand of a `PointerSubExpr`
1076+
*/
1077+
predicate isSizeArgPointerSubExprRightOperand(Expr sizeArg) {
1078+
exists(PointerSubExpr subExpr | sizeArg = subExpr.getRightOperand())
1079+
}
1080+
10281081
/**
10291082
* Holds if the BufferAccess `bufferAccess` results in a buffer overflow due to a size argument
10301083
* or buffer access offset being greater in size than the buffer size being accessed or written to.
@@ -1040,8 +1093,18 @@ module OOB {
10401093
(bufferArg instanceof StaticBufferAccessSource implies bufferSource = bufferArg) and
10411094
sizeExprComputableSize(sizeArg, _, sizeArgValue) and
10421095
computedBufferSize = bufferArgSize - sizeMult.(float) * getArithmeticOffsetValue(bufferArg, _) and
1043-
computedSizeAccessed =
1044-
sizeMult.(float) * (sizeArgValue + argNumCharactersOffset(bufferAccess, sizeArg)).(float) and
1096+
// Handle cases such as *(ptr - 1)
1097+
(
1098+
if isSizeArgPointerSubExprRightOperand(sizeArg)
1099+
then
1100+
computedSizeAccessed =
1101+
sizeMult.(float) *
1102+
(-sizeArgValue + argNumCharactersOffset(bufferAccess, sizeArg)).(float)
1103+
else
1104+
computedSizeAccessed =
1105+
sizeMult.(float) *
1106+
(sizeArgValue + argNumCharactersOffset(bufferAccess, sizeArg)).(float)
1107+
) and
10451108
computedBufferSize < computedSizeAccessed
10461109
)
10471110
}
@@ -1166,10 +1229,20 @@ module OOB {
11661229
bufferUseComputableBufferSize(bufferArg, bufferSource, _) or
11671230
bufferUseNonComputableSize(bufferArg, bufferSource)
11681231
) and
1169-
// Not a size expression for which we can compute a specific size
1170-
not sizeExprComputableSize(sizeArg, _, _) and
1171-
// If the lower bound is less than zero, taking into account any offsets
1172-
lowerBound(sizeArg) + getArithmeticOffsetValue(bufferArg, _) < 0
1232+
(
1233+
// Not a size expression for which we can compute a specific size
1234+
// and with a lower bound that is less than zero, taking into account offsets
1235+
not sizeExprComputableSize(sizeArg, _, _) and
1236+
lowerBound(sizeArg) + getArithmeticOffsetValue(bufferArg, _) < 0
1237+
or
1238+
// A size expression for which we can compute a specific size and that size is less than zero
1239+
sizeExprComputableSize(sizeArg, _, _) and
1240+
(
1241+
if isSizeArgPointerSubExprRightOperand(sizeArg)
1242+
then -getMinStatedValue(sizeArg) + getArithmeticOffsetValue(bufferArg, _) < 0
1243+
else getMinStatedValue(sizeArg) + getArithmeticOffsetValue(bufferArg, _) < 0
1244+
)
1245+
)
11731246
)
11741247
}
11751248

0 commit comments

Comments
 (0)