|
14 | 14 |
|
15 | 15 | import csharp |
16 | 16 | import semmle.code.csharp.controlflow.Guards |
| 17 | +import semmle.code.csharp.commons.ComparisonTest |
17 | 18 |
|
18 | | -from RelationalOperation cmp, Variable array, Variable index, ElementAccess ea, VariableAccess indexAccess |
19 | | -// Look for `index <= array.Length` or `array.Length >= index` |
20 | | -where (cmp instanceof GEExpr or cmp instanceof LEExpr) |
21 | | - and cmp.getGreaterOperand() = any(PropertyAccess pa | pa.getQualifier() = array.getAnAccess() and pa.getTarget().hasName("Length")) |
22 | | - and cmp.getLesserOperand() = index.getAnAccess() |
| 19 | +/** A comparison of an index variable with the length of an array. */ |
| 20 | +class IndexGuard extends Expr { |
| 21 | + ComparisonTest ct; |
| 22 | + VariableAccess indexAccess; |
| 23 | + Variable array; |
| 24 | + |
| 25 | + IndexGuard() { |
| 26 | + ct.getExpr() = this and |
| 27 | + ct.getFirstArgument() = indexAccess and |
| 28 | + ct.getSecondArgument() = any(PropertyAccess lengthAccess | |
| 29 | + lengthAccess.getQualifier() = array.getAnAccess() and |
| 30 | + lengthAccess.getTarget().hasName("Length") |
| 31 | + ) |
| 32 | + } |
| 33 | + |
| 34 | + /** This comparison applies to array `arr` and index `ind`. */ |
| 35 | + predicate controls(Variable arr, Variable ind) { |
| 36 | + arr = array and |
| 37 | + ind.getAnAccess() = indexAccess |
| 38 | + } |
| 39 | + |
| 40 | + /** This comparison guards `expr`. */ |
| 41 | + predicate guards(GuardedExpr expr, boolean condition) { |
| 42 | + expr.isGuardedBy(this, indexAccess, condition) |
| 43 | + } |
| 44 | + |
| 45 | + /** This comparison is an incorrect `<=` or equivalent. */ |
| 46 | + predicate isIncorrect() { |
| 47 | + ct.getComparisonKind().isLessThanEquals() |
| 48 | + } |
| 49 | +} |
| 50 | + |
| 51 | +from IndexGuard incorrectGuard, Variable array, Variable index, ElementAccess ea, GuardedExpr indexAccess |
| 52 | +where |
| 53 | + // Look for `index <= array.Length` or `array.Length >= index` |
| 54 | + incorrectGuard.controls(array, index) and |
| 55 | + incorrectGuard.isIncorrect() and |
23 | 56 | // Look for `array[index]` |
24 | | - and ea.getQualifier() = array.getAnAccess() |
25 | | - and ea.getIndex(0) = indexAccess |
26 | | - and indexAccess = index.getAnAccess() |
| 57 | + ea.getQualifier() = array.getAnAccess() and |
| 58 | + ea.getIndex(0) = indexAccess and |
| 59 | + indexAccess = index.getAnAccess() and |
27 | 60 | // Where the index access is guarded by the comparison |
28 | | - and indexAccess.(GuardedExpr).isGuardedBy(cmp, index.getAnAccess(), true) |
29 | | -select cmp, "Off-by-one index comparison against length leads to possible out of bounds $@.", ea, ea.toString() |
| 61 | + incorrectGuard.guards(indexAccess, true) and |
| 62 | + // And there are no other guards |
| 63 | + not exists(IndexGuard validGuard | |
| 64 | + not validGuard.isIncorrect() and |
| 65 | + validGuard.controls(array, index) and |
| 66 | + validGuard.guards(indexAccess, _) |
| 67 | + ) |
| 68 | +select incorrectGuard, "Off-by-one index comparison against length leads to possible out of bounds $@.", ea, ea.toString() |
0 commit comments