|
| 1 | +/** |
| 2 | + * @name Use of expired stack-address |
| 3 | + * @description Accessing the stack-allocated memory of a function |
| 4 | + * after it has returned can lead to memory corruption. |
| 5 | + * @kind problem |
| 6 | + * @problem.severity error |
| 7 | + * @security-severity 9.3 |
| 8 | + * @precision high |
| 9 | + * @id cpp/using-expired-stack-address |
| 10 | + * @tags reliability |
| 11 | + * security |
| 12 | + * external/cwe/cwe-825 |
| 13 | + */ |
| 14 | + |
| 15 | +import cpp |
| 16 | +// We don't actually use the global value numbering library in this query, but without it we end up |
| 17 | +// recomputing the IR. |
| 18 | +import semmle.code.cpp.valuenumbering.GlobalValueNumbering |
| 19 | +import semmle.code.cpp.ir.IR |
| 20 | + |
| 21 | +predicate instructionHasVariable(VariableAddressInstruction vai, StackVariable var, Function f) { |
| 22 | + var = vai.getASTVariable() and |
| 23 | + f = vai.getEnclosingFunction() and |
| 24 | + // Pointer-to-member types aren't properly handled in the dbscheme. |
| 25 | + not vai.getResultType() instanceof PointerToMemberType and |
| 26 | + // Rule out FPs caused by extraction errors. |
| 27 | + not any(ErrorExpr e).getEnclosingFunction() = f |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * Holds if `source` is the base address of an address computation whose |
| 32 | + * result is stored in `address`. |
| 33 | + */ |
| 34 | +predicate stackPointerFlowsToUse(Instruction address, VariableAddressInstruction source) { |
| 35 | + address = source and |
| 36 | + instructionHasVariable(source, _, _) |
| 37 | + or |
| 38 | + stackPointerFlowsToUse(address.(CopyInstruction).getSourceValue(), source) |
| 39 | + or |
| 40 | + stackPointerFlowsToUse(address.(ConvertInstruction).getUnary(), source) |
| 41 | + or |
| 42 | + stackPointerFlowsToUse(address.(CheckedConvertOrNullInstruction).getUnary(), source) |
| 43 | + or |
| 44 | + stackPointerFlowsToUse(address.(InheritanceConversionInstruction).getUnary(), source) |
| 45 | + or |
| 46 | + stackPointerFlowsToUse(address.(FieldAddressInstruction).getObjectAddress(), source) |
| 47 | + or |
| 48 | + stackPointerFlowsToUse(address.(PointerOffsetInstruction).getLeft(), source) |
| 49 | +} |
| 50 | + |
| 51 | +/** |
| 52 | + * A HashCons-like table for comparing addresses that are |
| 53 | + * computed relative to some global variable. |
| 54 | + */ |
| 55 | +newtype TGlobalAddress = |
| 56 | + TGlobalVariable(GlobalOrNamespaceVariable v) { |
| 57 | + // Pointer-to-member types aren't properly handled in the dbscheme. |
| 58 | + not v.getUnspecifiedType() instanceof PointerToMemberType |
| 59 | + } or |
| 60 | + TLoad(TGlobalAddress address) { |
| 61 | + address = globalAddress(any(LoadInstruction load).getSourceAddress()) |
| 62 | + } or |
| 63 | + TConversion(string kind, TGlobalAddress address, Type fromType, Type toType) { |
| 64 | + kind = "unchecked" and |
| 65 | + exists(ConvertInstruction convert | |
| 66 | + uncheckedConversionTypes(convert, fromType, toType) and |
| 67 | + address = globalAddress(convert.getUnary()) |
| 68 | + ) |
| 69 | + or |
| 70 | + kind = "checked" and |
| 71 | + exists(CheckedConvertOrNullInstruction convert | |
| 72 | + checkedConversionTypes(convert, fromType, toType) and |
| 73 | + address = globalAddress(convert.getUnary()) |
| 74 | + ) |
| 75 | + or |
| 76 | + kind = "inheritance" and |
| 77 | + exists(InheritanceConversionInstruction convert | |
| 78 | + inheritanceConversionTypes(convert, fromType, toType) and |
| 79 | + address = globalAddress(convert.getUnary()) |
| 80 | + ) |
| 81 | + } or |
| 82 | + TFieldAddress(TGlobalAddress address, Field f) { |
| 83 | + exists(FieldAddressInstruction fai | |
| 84 | + fai.getField() = f and |
| 85 | + address = globalAddress(fai.getObjectAddress()) |
| 86 | + ) |
| 87 | + } |
| 88 | + |
| 89 | +pragma[noinline] |
| 90 | +predicate uncheckedConversionTypes(ConvertInstruction convert, Type fromType, Type toType) { |
| 91 | + fromType = convert.getUnary().getResultType() and |
| 92 | + toType = convert.getResultType() |
| 93 | +} |
| 94 | + |
| 95 | +pragma[noinline] |
| 96 | +predicate checkedConversionTypes(CheckedConvertOrNullInstruction convert, Type fromType, Type toType) { |
| 97 | + fromType = convert.getUnary().getResultType() and |
| 98 | + toType = convert.getResultType() |
| 99 | +} |
| 100 | + |
| 101 | +pragma[noinline] |
| 102 | +predicate inheritanceConversionTypes( |
| 103 | + InheritanceConversionInstruction convert, Type fromType, Type toType |
| 104 | +) { |
| 105 | + fromType = convert.getUnary().getResultType() and |
| 106 | + toType = convert.getResultType() |
| 107 | +} |
| 108 | + |
| 109 | +/** Gets the HashCons value of an address computed by `instr`, if any. */ |
| 110 | +TGlobalAddress globalAddress(Instruction instr) { |
| 111 | + result = TGlobalVariable(instr.(VariableAddressInstruction).getASTVariable()) |
| 112 | + or |
| 113 | + not instr instanceof LoadInstruction and |
| 114 | + result = globalAddress(instr.(CopyInstruction).getSourceValue()) |
| 115 | + or |
| 116 | + exists(LoadInstruction load | instr = load | |
| 117 | + result = TLoad(globalAddress(load.getSourceAddress())) |
| 118 | + ) |
| 119 | + or |
| 120 | + exists(ConvertInstruction convert, Type fromType, Type toType | instr = convert | |
| 121 | + uncheckedConversionTypes(convert, fromType, toType) and |
| 122 | + result = TConversion("unchecked", globalAddress(convert.getUnary()), fromType, toType) |
| 123 | + ) |
| 124 | + or |
| 125 | + exists(CheckedConvertOrNullInstruction convert, Type fromType, Type toType | instr = convert | |
| 126 | + checkedConversionTypes(convert, fromType, toType) and |
| 127 | + result = TConversion("checked", globalAddress(convert.getUnary()), fromType, toType) |
| 128 | + ) |
| 129 | + or |
| 130 | + exists(InheritanceConversionInstruction convert, Type fromType, Type toType | instr = convert | |
| 131 | + inheritanceConversionTypes(convert, fromType, toType) and |
| 132 | + result = TConversion("inheritance", globalAddress(convert.getUnary()), fromType, toType) |
| 133 | + ) |
| 134 | + or |
| 135 | + exists(FieldAddressInstruction fai | instr = fai | |
| 136 | + result = TFieldAddress(globalAddress(fai.getObjectAddress()), fai.getField()) |
| 137 | + ) |
| 138 | + or |
| 139 | + result = globalAddress(instr.(PointerOffsetInstruction).getLeft()) |
| 140 | +} |
| 141 | + |
| 142 | +/** Gets a `StoreInstruction` that may be executed after executing `store`. */ |
| 143 | +pragma[inline] |
| 144 | +StoreInstruction getAStoreStrictlyAfter(StoreInstruction store) { |
| 145 | + exists(IRBlock block, int index1, int index2 | |
| 146 | + block.getInstruction(index1) = store and |
| 147 | + block.getInstruction(index2) = result and |
| 148 | + index2 > index1 |
| 149 | + ) |
| 150 | + or |
| 151 | + exists(IRBlock block1, IRBlock block2 | |
| 152 | + store.getBlock() = block1 and |
| 153 | + result.getBlock() = block2 and |
| 154 | + block1.getASuccessor+() = block2 |
| 155 | + ) |
| 156 | +} |
| 157 | + |
| 158 | +/** |
| 159 | + * Holds if `store` copies the address of `f`'s local variable `var` |
| 160 | + * into the address `globalAddress`. |
| 161 | + */ |
| 162 | +predicate stackAddressEscapes( |
| 163 | + StoreInstruction store, StackVariable var, TGlobalAddress globalAddress, Function f |
| 164 | +) { |
| 165 | + globalAddress = globalAddress(store.getDestinationAddress()) and |
| 166 | + exists(VariableAddressInstruction vai | |
| 167 | + instructionHasVariable(pragma[only_bind_into](vai), var, f) and |
| 168 | + stackPointerFlowsToUse(store.getSourceValue(), vai) |
| 169 | + ) and |
| 170 | + // Ensure there's no subsequent store that overrides the global address. |
| 171 | + not globalAddress = globalAddress(getAStoreStrictlyAfter(store).getDestinationAddress()) |
| 172 | +} |
| 173 | + |
| 174 | +predicate blockStoresToAddress( |
| 175 | + IRBlock block, int index, StoreInstruction store, TGlobalAddress globalAddress |
| 176 | +) { |
| 177 | + block.getInstruction(index) = store and |
| 178 | + globalAddress = globalAddress(store.getDestinationAddress()) |
| 179 | +} |
| 180 | + |
| 181 | +predicate blockLoadsFromAddress( |
| 182 | + IRBlock block, int index, LoadInstruction load, TGlobalAddress globalAddress |
| 183 | +) { |
| 184 | + block.getInstruction(index) = load and |
| 185 | + globalAddress = globalAddress(load.getSourceAddress()) |
| 186 | +} |
| 187 | + |
| 188 | +predicate globalAddressPointsToStack( |
| 189 | + StoreInstruction store, StackVariable var, CallInstruction call, IRBlock block, |
| 190 | + TGlobalAddress globalAddress, boolean isCallBlock, boolean isStoreBlock |
| 191 | +) { |
| 192 | + ( |
| 193 | + if blockStoresToAddress(block, _, _, globalAddress) |
| 194 | + then isStoreBlock = true |
| 195 | + else isStoreBlock = false |
| 196 | + ) and |
| 197 | + ( |
| 198 | + isCallBlock = true and |
| 199 | + exists(Function f | |
| 200 | + stackAddressEscapes(store, var, globalAddress, f) and |
| 201 | + call.getStaticCallTarget() = f and |
| 202 | + call.getBlock() = block |
| 203 | + ) |
| 204 | + or |
| 205 | + isCallBlock = false and |
| 206 | + exists(IRBlock mid | |
| 207 | + mid.immediatelyDominates(block) and |
| 208 | + // Only recurse if there is no store to `globalAddress` in `mid`. |
| 209 | + globalAddressPointsToStack(store, var, call, mid, globalAddress, _, false) |
| 210 | + ) |
| 211 | + ) |
| 212 | +} |
| 213 | + |
| 214 | +from |
| 215 | + StoreInstruction store, StackVariable var, LoadInstruction load, CallInstruction call, |
| 216 | + IRBlock block, boolean isCallBlock, TGlobalAddress address, boolean isStoreBlock |
| 217 | +where |
| 218 | + globalAddressPointsToStack(store, var, call, block, address, isCallBlock, isStoreBlock) and |
| 219 | + block.getAnInstruction() = load and |
| 220 | + globalAddress(load.getSourceAddress()) = address and |
| 221 | + ( |
| 222 | + // We know that we have a sequence: |
| 223 | + // (1) store to `address` -> (2) return from `f` -> (3) load from `address`. |
| 224 | + // But if (2) and (3) happen in the sam block we need to check the |
| 225 | + // block indices to ensure that (3) happens after (2). |
| 226 | + if isCallBlock = true |
| 227 | + then |
| 228 | + // If so, the load must happen after the call. |
| 229 | + exists(int callIndex, int loadIndex | |
| 230 | + blockLoadsFromAddress(_, loadIndex, load, _) and |
| 231 | + block.getInstruction(callIndex) = call and |
| 232 | + callIndex < loadIndex |
| 233 | + ) |
| 234 | + else any() |
| 235 | + ) and |
| 236 | + // If there is a store to the address we need to make sure that the load we found was |
| 237 | + // before that store (So that the load doesn't read an overwritten value). |
| 238 | + if isStoreBlock = true |
| 239 | + then |
| 240 | + exists(int storeIndex, int loadIndex | |
| 241 | + blockStoresToAddress(block, storeIndex, _, address) and |
| 242 | + block.getInstruction(loadIndex) = load and |
| 243 | + loadIndex < storeIndex |
| 244 | + ) |
| 245 | + else any() |
| 246 | +select load, "Stack variable $@ escapes $@ and is used after it has expired.", var, var.toString(), |
| 247 | + store, "here" |
0 commit comments