|
| 1 | +/** |
| 2 | + * @id c/cert/variable-length-array-size-not-in-valid-range |
| 3 | + * @name ARR32-C: Ensure size arguments for variable length arrays are in a valid range |
| 4 | + * @description A variable-length array size that is zero, negative, overflowed, wrapped around, or |
| 5 | + * excessively large may lead to undefined behaviour. |
| 6 | + * @kind problem |
| 7 | + * @precision high |
| 8 | + * @problem.severity error |
| 9 | + * @tags external/cert/id/arr32-c |
| 10 | + * correctness |
| 11 | + * security |
| 12 | + * external/cert/obligation/rule |
| 13 | + */ |
| 14 | + |
| 15 | +import cpp |
| 16 | +import codingstandards.c.cert |
| 17 | +import codingstandards.cpp.Overflow |
| 18 | + |
| 19 | +/** |
| 20 | + * Gets the maximum size (in bytes) a variable-length array |
| 21 | + * should be to not be deemed excessively large persuant to this rule. |
| 22 | + * This value has been arbitrarily chosen to be 2^16 - 1 bytes. |
| 23 | + */ |
| 24 | +private int maximumTotalVlaSize() { result = 65535 } |
| 25 | + |
| 26 | +/** |
| 27 | + * Gets the base type of a pointer or array type. In the case of an array of |
| 28 | + * arrays, the inner base type is returned. |
| 29 | + * |
| 30 | + * Copied from IncorrectPointerScalingCommon.qll. |
| 31 | + */ |
| 32 | +private Type baseType(Type t) { |
| 33 | + ( |
| 34 | + exists(PointerType dt | |
| 35 | + dt = t.getUnspecifiedType() and |
| 36 | + result = dt.getBaseType().getUnspecifiedType() |
| 37 | + ) |
| 38 | + or |
| 39 | + exists(ArrayType at | |
| 40 | + at = t.getUnspecifiedType() and |
| 41 | + not at.getBaseType().getUnspecifiedType() instanceof ArrayType and |
| 42 | + result = at.getBaseType().getUnspecifiedType() |
| 43 | + ) |
| 44 | + or |
| 45 | + exists(ArrayType at, ArrayType at2 | |
| 46 | + at = t.getUnspecifiedType() and |
| 47 | + at2 = at.getBaseType().getUnspecifiedType() and |
| 48 | + result = baseType(at2) |
| 49 | + ) |
| 50 | + ) and |
| 51 | + // Make sure that the type has a size and that it isn't ambiguous. |
| 52 | + strictcount(result.getSize()) = 1 |
| 53 | +} |
| 54 | + |
| 55 | +/** |
| 56 | + * The `SimpleRangeAnalysis` analysis over-zealously expands upper bounds of |
| 57 | + * `SubExpr`s to account for potential wrapping even when no wrapping can occur. |
| 58 | + * |
| 59 | + * This class represents a `SubExpr` that is safe from wrapping. |
| 60 | + */ |
| 61 | +class SafeSubExprWithErroneouslyWrappedUpperBound extends SubExpr { |
| 62 | + SafeSubExprWithErroneouslyWrappedUpperBound() { |
| 63 | + lowerBound(this.getLeftOperand().getFullyConverted()) - |
| 64 | + upperBound(this.getRightOperand().getFullyConverted()) >= 0 and |
| 65 | + upperBound(this.getFullyConverted()) = exprMaxVal(this.getFullyConverted()) |
| 66 | + } |
| 67 | + |
| 68 | + /** |
| 69 | + * Gets the lower bound of the difference. |
| 70 | + */ |
| 71 | + float getlowerBoundOfDifference() { |
| 72 | + result = |
| 73 | + lowerBound(this.getLeftOperand().getFullyConverted()) - |
| 74 | + upperBound(this.getRightOperand().getFullyConverted()) |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +/** |
| 79 | + * Holds if `e` is an expression that is not in a valid range due to it |
| 80 | + * being partially or fully derived from an overflowing arithmetic operation. |
| 81 | + */ |
| 82 | +predicate isExprTaintedByOverflowingExpr(Expr e) { |
| 83 | + exists(InterestingOverflowingOperation bop | |
| 84 | + // `bop` is not pre-checked to prevent overflow/wrapping |
| 85 | + not bop.hasValidPreCheck() and |
| 86 | + // and the destination is tainted by `bop` |
| 87 | + TaintTracking::localExprTaint(bop, e.getAChild*()) and |
| 88 | + // and there does not exist a post-wrapping-check before `e` |
| 89 | + not exists(GuardCondition gc | |
| 90 | + gc = bop.getAValidPostCheck() and |
| 91 | + gc.controls(e.getBasicBlock(), _) |
| 92 | + ) |
| 93 | + ) |
| 94 | +} |
| 95 | + |
| 96 | +predicate getVlaSizeExprBounds(Expr e, float lower, float upper) { |
| 97 | + lower = lowerBound(e) and |
| 98 | + upper = |
| 99 | + // upper is the smallest of either a `SubExpr` which flows to `e` and does |
| 100 | + // not wrap, or the upper bound of `e` derived from the range-analysis library |
| 101 | + min(float f | |
| 102 | + f = |
| 103 | + any(SafeSubExprWithErroneouslyWrappedUpperBound sub | |
| 104 | + DataFlow::localExprFlow(sub, e) |
| 105 | + | |
| 106 | + sub.getlowerBoundOfDifference() |
| 107 | + ) or |
| 108 | + f = upperBound(e) |
| 109 | + ) |
| 110 | +} |
| 111 | + |
| 112 | +/** |
| 113 | + * Holds if `e` is not bounded to a valid range, (0 .. maximumTotalVlaSize()], for |
| 114 | + * a element count of an individual variable-length array dimension. |
| 115 | + */ |
| 116 | +predicate isVlaSizeExprOutOfRange(VlaDeclStmt vla, Expr e) { |
| 117 | + vla.getVlaDimensionStmt(_).getDimensionExpr() = e and |
| 118 | + exists(float lower, float upper | |
| 119 | + getVlaSizeExprBounds(e.getFullyConverted(), lower, upper) and |
| 120 | + ( |
| 121 | + lower <= 0 |
| 122 | + or |
| 123 | + upper > maximumTotalVlaSize() / baseType(vla.getVariable().getType()).getSize() |
| 124 | + ) |
| 125 | + ) |
| 126 | +} |
| 127 | + |
| 128 | +/** |
| 129 | + * Returns the upper bound of `e.getFullyConverted()`. |
| 130 | + */ |
| 131 | +float getVlaSizeExprUpperBound(Expr e) { getVlaSizeExprBounds(e.getFullyConverted(), _, result) } |
| 132 | + |
| 133 | +/** |
| 134 | + * Returns the upper bound of `vla`'s dimension expression at `index`. |
| 135 | + * |
| 136 | + * If `index` does not exist, then the result is `1`. |
| 137 | + */ |
| 138 | +bindingset[index] |
| 139 | +private float getVlaSizeExprUpperBoundAtIndexOrOne(VlaDeclStmt vla, float index) { |
| 140 | + if vla.getNumberOfVlaDimensionStmts() > index |
| 141 | + then result = getVlaSizeExprUpperBound(vla.getVlaDimensionStmt(index).getDimensionExpr()) |
| 142 | + else result = 1 |
| 143 | +} |
| 144 | + |
| 145 | +predicate vlaupper = getVlaSizeExprUpperBoundAtIndexOrOne/2; |
| 146 | + |
| 147 | +/** |
| 148 | + * Gets the upper bound of the total size of `vla`. |
| 149 | + */ |
| 150 | +float getTotalVlaSizeUpperBound(VlaDeclStmt vla) { |
| 151 | + result = |
| 152 | + vlaupper(vla, 0) * vlaupper(vla, 1) * vlaupper(vla, 2) * vlaupper(vla, 3) * vlaupper(vla, 4) * |
| 153 | + vlaupper(vla, 5) * vlaupper(vla, 6) * vlaupper(vla, 7) * vlaupper(vla, 8) * vlaupper(vla, 9) |
| 154 | +} |
| 155 | + |
| 156 | +from VlaDeclStmt vla, string message |
| 157 | +where |
| 158 | + not isExcluded(vla, InvalidMemory2Package::variableLengthArraySizeNotInValidRangeQuery()) and |
| 159 | + ( |
| 160 | + if isExprTaintedByOverflowingExpr(vla.getVlaDimensionStmt(_).getDimensionExpr()) |
| 161 | + then message = "Variable-length array size derives from an overflowing or wrapping expression." |
| 162 | + else ( |
| 163 | + if isVlaSizeExprOutOfRange(vla, vla.getVlaDimensionStmt(_).getDimensionExpr()) |
| 164 | + then message = "Variable-length array dimension size may be in an invalid range." |
| 165 | + else ( |
| 166 | + getTotalVlaSizeUpperBound(vla) > maximumTotalVlaSize() and |
| 167 | + message = "Variable-length array total size may be excessively large." |
| 168 | + ) |
| 169 | + ) |
| 170 | + ) |
| 171 | +select vla, message |
0 commit comments