|
| 1 | +import cpp |
| 2 | +import semmle.code.cpp.security.Security |
| 3 | +private import semmle.code.cpp.ir.dataflow.DataFlow |
| 4 | +private import semmle.code.cpp.ir.IR |
| 5 | + |
| 6 | +/** |
| 7 | + * A predictable expression is one where an external user can predict |
| 8 | + * the value. For example, a literal in the source code is considered |
| 9 | + * predictable. |
| 10 | + */ |
| 11 | +// TODO: Change to use Instruction instead of Expr. Naive attempt breaks |
| 12 | +// TaintedAllocationSize qltest. |
| 13 | +private predicate predictable(Expr expr) { |
| 14 | + expr instanceof Literal |
| 15 | + or |
| 16 | + exists(BinaryOperation binop | binop = expr | |
| 17 | + predictable(binop.getLeftOperand()) and predictable(binop.getRightOperand()) |
| 18 | + ) |
| 19 | + or |
| 20 | + exists(UnaryOperation unop | unop = expr | predictable(unop.getOperand())) |
| 21 | +} |
| 22 | + |
| 23 | +// TODO: remove when `predictable` has an `Instruction` parameter instead of `Expr`. |
| 24 | +private predicate predictableInstruction(Instruction instr) { |
| 25 | + exists(DataFlow::Node node | |
| 26 | + node.asInstruction() = instr and |
| 27 | + predictable(node.asExpr()) |
| 28 | + ) |
| 29 | +} |
| 30 | + |
| 31 | +private class DefaultTaintTrackingCfg extends DataFlow::Configuration { |
| 32 | + DefaultTaintTrackingCfg() { this = "DefaultTaintTrackingCfg" } |
| 33 | + |
| 34 | + override predicate isSource(DataFlow::Node source) { isUserInput(source.asExpr(), _) } |
| 35 | + |
| 36 | + override predicate isSink(DataFlow::Node sink) { any() } |
| 37 | + |
| 38 | + override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) { |
| 39 | + instructionTaintStep(n1.asInstruction(), n2.asInstruction()) |
| 40 | + } |
| 41 | + |
| 42 | + override predicate isBarrier(DataFlow::Node node) { |
| 43 | + exists(Variable checkedVar | |
| 44 | + accessesVariable(node.asInstruction(), checkedVar) and |
| 45 | + hasUpperBoundsCheck(checkedVar) |
| 46 | + ) |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +private predicate accessesVariable(CopyInstruction copy, Variable var) { |
| 51 | + exists(VariableAddressInstruction va | |
| 52 | + va.getVariable().getAST() = var |
| 53 | + | |
| 54 | + copy.(StoreInstruction).getDestinationAddress() = va |
| 55 | + or |
| 56 | + copy.(LoadInstruction).getSourceAddress() = va |
| 57 | + ) |
| 58 | +} |
| 59 | + |
| 60 | +/** |
| 61 | + * A variable that has any kind of upper-bound check anywhere in the program |
| 62 | + */ |
| 63 | +// TODO: This coarse overapproximation, ported from the old taint tracking |
| 64 | +// library, could be replaced with an actual semantic check that a particular |
| 65 | +// variable _access_ is guarded by an upper-bound check. We probably don't want |
| 66 | +// to do this right away since it could expose a lot of FPs that were |
| 67 | +// previously suppressed by this predicate by coincidence. |
| 68 | +private predicate hasUpperBoundsCheck(Variable var) { |
| 69 | + exists(RelationalOperation oper, VariableAccess access | |
| 70 | + oper.getLeftOperand() = access and |
| 71 | + access.getTarget() = var and |
| 72 | + // Comparing to 0 is not an upper bound check |
| 73 | + not oper.getRightOperand().getValue() = "0" |
| 74 | + ) |
| 75 | +} |
| 76 | + |
| 77 | +private predicate instructionTaintStep(Instruction i1, Instruction i2) { |
| 78 | + // Expressions computed from tainted data are also tainted |
| 79 | + i2 = any(CallInstruction call | |
| 80 | + isPureFunction(call.getStaticCallTarget().getName()) and |
| 81 | + call.getAnArgument() = i1 and |
| 82 | + forall(Instruction arg | arg = call.getAnArgument() | arg = i1 or predictableInstruction(arg)) and |
| 83 | + // flow through `strlen` tends to cause dubious results, if the length is |
| 84 | + // bounded. |
| 85 | + not call.getStaticCallTarget().getName() = "strlen" |
| 86 | + ) |
| 87 | + or |
| 88 | + // Flow through pointer dereference |
| 89 | + i2.(LoadInstruction).getSourceAddress() = i1 |
| 90 | + or |
| 91 | + i2.(UnaryInstruction).getUnary() = i1 |
| 92 | + or |
| 93 | + exists(BinaryInstruction bin | |
| 94 | + bin = i2 and |
| 95 | + predictableInstruction(i2.getAnOperand().getDef()) and |
| 96 | + i1 = i2.getAnOperand().getDef() |
| 97 | + ) |
| 98 | + // TODO: Check that we have flow from `a` to `a[i]`. It may work for constant |
| 99 | + // `i` because there is flow through `predictable` `BinaryInstruction` and |
| 100 | + // through `LoadInstruction`. |
| 101 | + // |
| 102 | + // TODO: Flow from argument to return of known functions: Port missing parts |
| 103 | + // of `returnArgument` to the `interfaces.Taint` and `interfaces.DataFlow` |
| 104 | + // libraries. |
| 105 | + // |
| 106 | + // TODO: Flow from input argument to output argument of known functions: Port |
| 107 | + // missing parts of `copyValueBetweenArguments` to the `interfaces.Taint` and |
| 108 | + // `interfaces.DataFlow` libraries and implement call side-effect nodes. This |
| 109 | + // will help with the test for `ExecTainted.ql`. The test for |
| 110 | + // `TaintedPath.ql` is more tricky because the output arg is a pointer |
| 111 | + // addition expression. |
| 112 | +} |
| 113 | + |
| 114 | +predicate tainted(Expr source, Element tainted) { |
| 115 | + exists(DefaultTaintTrackingCfg cfg, DataFlow::Node sink | |
| 116 | + cfg.hasFlow(DataFlow::exprNode(source), sink) |
| 117 | + | |
| 118 | + // TODO: is it more appropriate to use asConvertedExpr here and avoid |
| 119 | + // `getConversion*`? Or will that cause us to miss some cases where there's |
| 120 | + // flow to a conversion (like a `ReferenceDereferenceExpr`) and we want to |
| 121 | + // pretend there was flow to the converted `Expr` for the sake of |
| 122 | + // compatibility. |
| 123 | + sink.asExpr().getConversion*() = tainted |
| 124 | + or |
| 125 | + // For compatibility, send flow from arguments to parameters, even for |
| 126 | + // functions with no body. |
| 127 | + exists(FunctionCall call, int i | |
| 128 | + sink.asExpr() = call.getArgument(i) and |
| 129 | + tainted = resolveCall(call).getParameter(i) |
| 130 | + ) |
| 131 | + or |
| 132 | + // For compatibility, send flow into a `Variable` if there is flow to any |
| 133 | + // Load or Store of that variable. |
| 134 | + exists(CopyInstruction copy | |
| 135 | + copy.getSourceValue() = sink.asInstruction() and |
| 136 | + accessesVariable(copy, tainted) and |
| 137 | + not hasUpperBoundsCheck(tainted) |
| 138 | + ) |
| 139 | + or |
| 140 | + // For compatibility, send flow into a `NotExpr` even if it's part of a |
| 141 | + // short-circuiting condition and thus might get skipped. |
| 142 | + tainted.(NotExpr).getOperand() = sink.asExpr() |
| 143 | + ) |
| 144 | +} |
| 145 | + |
| 146 | +predicate taintedIncludingGlobalVars(Expr source, Element tainted, string globalVar) { |
| 147 | + tainted(source, tainted) and |
| 148 | + // TODO: Find a way to emulate how `security.TaintTracking` reports the last |
| 149 | + // global variable that taint has passed through. Also make sure we emulate |
| 150 | + // its behavior for interprocedural flow through globals. |
| 151 | + globalVar = "" |
| 152 | +} |
| 153 | + |
| 154 | +GlobalOrNamespaceVariable globalVarFromId(string id) { |
| 155 | + // TODO: Implement this when `taintedIncludingGlobalVars` has support for |
| 156 | + // global variables. |
| 157 | + none() |
| 158 | +} |
| 159 | + |
| 160 | +Function resolveCall(Call call) { |
| 161 | + // TODO: improve virtual dispatch. This will help in the test for |
| 162 | + // `UncontrolledProcessOperation.ql`. |
| 163 | + result = call.getTarget() |
| 164 | +} |
0 commit comments