|
| 1 | +private import cpp |
| 2 | +private import semmle.code.cpp.ir.IR |
| 3 | +private import codeql.typeflow.TypeFlow |
| 4 | + |
| 5 | +private module Input implements TypeFlowInput<Location> { |
| 6 | + private predicate hasExactSingleType(Instruction i) { |
| 7 | + // The address of a variable is always a single object |
| 8 | + i instanceof VariableAddressInstruction |
| 9 | + or |
| 10 | + // A reference always points to a always a single object |
| 11 | + i.getResultLanguageType().hasUnspecifiedType(any(ReferenceType rt), false) |
| 12 | + or |
| 13 | + // `this` is never an array |
| 14 | + i instanceof InitializeThisInstruction |
| 15 | + or |
| 16 | + // An allocation of a non-array object |
| 17 | + exists(AllocationExpr alloc | alloc = i.getUnconvertedResultExpression() | |
| 18 | + // i.e., `new int`; |
| 19 | + alloc instanceof NewExpr |
| 20 | + or |
| 21 | + // i.e., `malloc(sizeof(int))` |
| 22 | + exists(SizeofTypeOperator sizeOf | sizeOf = alloc.getSizeExpr() | |
| 23 | + not sizeOf.getTypeOperand().getUnspecifiedType() instanceof ArrayType |
| 24 | + ) |
| 25 | + ) |
| 26 | + } |
| 27 | + |
| 28 | + private predicate hasExactBufferType(Instruction i) { |
| 29 | + // Anything with an array type is a buffer |
| 30 | + i.getResultLanguageType().hasUnspecifiedType(any(ArrayType at), false) |
| 31 | + or |
| 32 | + not hasExactSingleType(i) and |
| 33 | + i.getUnconvertedResultExpression() instanceof AllocationExpr |
| 34 | + } |
| 35 | + |
| 36 | + private newtype TTypeFlowNode = |
| 37 | + TInstructionNode(Instruction i) or |
| 38 | + TFunctionNode(IRFunction func) |
| 39 | + |
| 40 | + abstract class TypeFlowNode extends TTypeFlowNode { |
| 41 | + /** Gets a textual representation of this node. */ |
| 42 | + abstract string toString(); |
| 43 | + |
| 44 | + /** |
| 45 | + * Gets the type of this node. This type may not be the most precise |
| 46 | + * possible type, but will be used as a starting point of the analysis. |
| 47 | + */ |
| 48 | + abstract Type getType(); |
| 49 | + |
| 50 | + /** Gets the location of this node. */ |
| 51 | + abstract Location getLocation(); |
| 52 | + |
| 53 | + /** Gets the underlying `Instruction` of this node, if any. */ |
| 54 | + Instruction asInstruction() { none() } |
| 55 | + |
| 56 | + /** Gets the underlying `IRFunction` of this node, if any. */ |
| 57 | + IRFunction asFunction() { none() } |
| 58 | + |
| 59 | + /** Holds if the value of this node is always null. */ |
| 60 | + abstract predicate isNullValue(); |
| 61 | + } |
| 62 | + |
| 63 | + private class InstructionNode extends TypeFlowNode, TInstructionNode { |
| 64 | + Instruction instr; |
| 65 | + |
| 66 | + InstructionNode() { this = TInstructionNode(instr) } |
| 67 | + |
| 68 | + override string toString() { result = instr.toString() } |
| 69 | + |
| 70 | + override Type getType() { |
| 71 | + if hasExactSingleType(instr) then result.isSingle() else result.isBuffer() |
| 72 | + } |
| 73 | + |
| 74 | + override Location getLocation() { result = instr.getLocation() } |
| 75 | + |
| 76 | + override Instruction asInstruction() { result = instr } |
| 77 | + |
| 78 | + override predicate isNullValue() { |
| 79 | + instr.(ConstantInstruction).getValue() = "0" and |
| 80 | + instr.getResultIRType() instanceof IRAddressType |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + /** Gets the `TypeFlowNode` corresponding to `i`. */ |
| 85 | + additional InstructionNode instructionNode(Instruction i) { result.asInstruction() = i } |
| 86 | + |
| 87 | + private class FunctionNode extends TypeFlowNode, TFunctionNode { |
| 88 | + IRFunction func; |
| 89 | + |
| 90 | + FunctionNode() { this = TFunctionNode(func) } |
| 91 | + |
| 92 | + override string toString() { result = func.toString() } |
| 93 | + |
| 94 | + Instruction getReturnValueInstruction() { |
| 95 | + result = func.getReturnInstruction().(ReturnValueInstruction).getReturnValue() |
| 96 | + } |
| 97 | + |
| 98 | + override Type getType() { result = instructionNode(this.getReturnValueInstruction()).getType() } |
| 99 | + |
| 100 | + override Location getLocation() { result = func.getLocation() } |
| 101 | + |
| 102 | + override IRFunction asFunction() { result = func } |
| 103 | + |
| 104 | + override predicate isNullValue() { |
| 105 | + instructionNode(this.getReturnValueInstruction()).isNullValue() |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * Gets an ultimiate definition of `phi`. That is, an input to `phi` that is |
| 111 | + * not itself a `PhiInstruction`. |
| 112 | + */ |
| 113 | + private Instruction getAnUltimateLocalDefinition(PhiInstruction phi) { |
| 114 | + result = phi.getAnInput*() and not result instanceof PhiInstruction |
| 115 | + } |
| 116 | + |
| 117 | + /** |
| 118 | + * Holds if this function is private (i.e., cannot be accessed outside its |
| 119 | + * compilation unit). This means we can use a closed-world assumption about |
| 120 | + * calls to this function. |
| 121 | + */ |
| 122 | + private predicate isPrivate(Function func) { |
| 123 | + func.isStatic() |
| 124 | + or |
| 125 | + func.getNamespace().getParentNamespace*().isInline() |
| 126 | + or |
| 127 | + func.(MemberFunction).isPrivate() |
| 128 | + } |
| 129 | + |
| 130 | + /** |
| 131 | + * Holds if `arg` is an argument for the parameter `p` in a private callable. |
| 132 | + */ |
| 133 | + pragma[nomagic] |
| 134 | + private predicate privateParamArg(InitializeParameterInstruction p, Instruction arg) { |
| 135 | + exists(CallInstruction call, int i, Function func | |
| 136 | + call.getArgument(pragma[only_bind_into](i)) = arg and |
| 137 | + func = call.getStaticCallTarget() and |
| 138 | + func.getParameter(pragma[only_bind_into](i)) = p.getParameter() and |
| 139 | + isPrivate(func) |
| 140 | + ) |
| 141 | + } |
| 142 | + |
| 143 | + predicate joinStep(TypeFlowNode n1, TypeFlowNode n2) { |
| 144 | + // instruction -> phi |
| 145 | + getAnUltimateLocalDefinition(n2.asInstruction()) = n1.asInstruction() |
| 146 | + or |
| 147 | + // return value -> function |
| 148 | + n2.(FunctionNode).getReturnValueInstruction() = n1.asInstruction() |
| 149 | + or |
| 150 | + // function -> call |
| 151 | + exists(Function func | func = n1.asFunction().getFunction() | |
| 152 | + not func.isVirtual() and |
| 153 | + n2.asInstruction().(CallInstruction).getStaticCallTarget() = func |
| 154 | + ) |
| 155 | + or |
| 156 | + // Argument -> parameter where the parameter's enclosing function |
| 157 | + // is "private". |
| 158 | + exists(Instruction arg, Instruction p | |
| 159 | + privateParamArg(p, arg) and |
| 160 | + n1.asInstruction() = arg and |
| 161 | + n2.asInstruction() = p |
| 162 | + ) |
| 163 | + } |
| 164 | + |
| 165 | + /** |
| 166 | + * Holds if knowing whether `i1` points to a single object or buffer implies |
| 167 | + * knowing whether `i2` points to a single object or buffer. |
| 168 | + */ |
| 169 | + private predicate instructionStep(Instruction i1, Instruction i2) { |
| 170 | + i2.(CopyInstruction).getSourceValue() = i1 |
| 171 | + or |
| 172 | + i2.(CopyValueInstruction).getSourceValue() = i1 |
| 173 | + or |
| 174 | + i2.(ConvertInstruction).getUnary() = i1 |
| 175 | + or |
| 176 | + i2.(CheckedConvertOrNullInstruction).getUnary() = i1 |
| 177 | + or |
| 178 | + i2.(InheritanceConversionInstruction).getUnary() = i1 |
| 179 | + or |
| 180 | + i2.(PointerArithmeticInstruction).getLeft() = i1 |
| 181 | + } |
| 182 | + |
| 183 | + predicate step(TypeFlowNode n1, TypeFlowNode n2) { |
| 184 | + instructionStep(n1.asInstruction(), n2.asInstruction()) |
| 185 | + } |
| 186 | + |
| 187 | + predicate isNullValue(TypeFlowNode n) { n.isNullValue() } |
| 188 | + |
| 189 | + private newtype TType = |
| 190 | + TSingle() or |
| 191 | + TBuffer() |
| 192 | + |
| 193 | + class Type extends TType { |
| 194 | + string toString() { |
| 195 | + this.isSingle() and |
| 196 | + result = "Single" |
| 197 | + or |
| 198 | + this.isBuffer() and |
| 199 | + result = "Buffer" |
| 200 | + } |
| 201 | + |
| 202 | + /** Holds if this type is the type that represents a single object. */ |
| 203 | + predicate isSingle() { this = TSingle() } |
| 204 | + |
| 205 | + /** Holds if this type is the type that represents a buffer. */ |
| 206 | + predicate isBuffer() { this = TBuffer() } |
| 207 | + |
| 208 | + /** |
| 209 | + * Gets a super type of this type, if any. |
| 210 | + * |
| 211 | + * The type relation is `Single <: Buffer`. |
| 212 | + */ |
| 213 | + Type getASupertype() { |
| 214 | + this.isSingle() and |
| 215 | + result.isBuffer() |
| 216 | + } |
| 217 | + } |
| 218 | + |
| 219 | + predicate exactTypeBase(TypeFlowNode n, Type t) { |
| 220 | + exists(Instruction instr | instr = n.asInstruction() | |
| 221 | + hasExactSingleType(instr) and t.isSingle() |
| 222 | + or |
| 223 | + hasExactBufferType(instr) and t.isBuffer() |
| 224 | + ) |
| 225 | + } |
| 226 | + |
| 227 | + pragma[nomagic] |
| 228 | + private predicate upcastCand(TypeFlowNode n, Type t1, Type t2) { |
| 229 | + exists(TypeFlowNode next | |
| 230 | + step(n, next) |
| 231 | + or |
| 232 | + joinStep(n, next) |
| 233 | + | |
| 234 | + n.getType() = t1 and |
| 235 | + next.getType() = t2 and |
| 236 | + t1 != t2 |
| 237 | + ) |
| 238 | + } |
| 239 | + |
| 240 | + private predicate upcast(TypeFlowNode n, Type t1) { |
| 241 | + exists(Type t2 | upcastCand(n, t1, t2) | |
| 242 | + // No need for transitive closure since the subtyping relation is just `Single <: Buffer` |
| 243 | + t1.getASupertype() = t2 |
| 244 | + ) |
| 245 | + } |
| 246 | + |
| 247 | + predicate typeFlowBaseCand(TypeFlowNode n, Type t) { upcast(n, t) } |
| 248 | +} |
| 249 | + |
| 250 | +private module TypeFlow = Make<Location, Input>; |
| 251 | + |
| 252 | +/** |
| 253 | + * Holds if `i` is an instruction that computes an address that points to a |
| 254 | + * single object (as opposed to pointing into a buffer). |
| 255 | + */ |
| 256 | +pragma[nomagic] |
| 257 | +predicate isPointerToSingleObject(Instruction i) { |
| 258 | + TypeFlow::bestTypeFlow(Input::instructionNode(i), any(Input::Type t | t.isSingle()), _) |
| 259 | +} |
0 commit comments