Skip to content

Commit 55c72ce

Browse files
committed
Improve StringBuilder append chain tracking
Previously this didn't catch the case of constructors chaining directly into appends, like `StringBuilder sb = new StringBuilder("1").append("2")`
1 parent 5b25694 commit 55c72ce

File tree

3 files changed

+183
-77
lines changed

3 files changed

+183
-77
lines changed

java/ql/src/semmle/code/java/security/RequestForgery.qll

Lines changed: 81 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -248,20 +248,85 @@ private Expr getAHostnameSanitizingPrefix() {
248248
result.(AddExpr).getAnOperand() = getAHostnameSanitizingPrefix()
249249
}
250250

251-
private MethodAccess getNextAppend(MethodAccess append) {
252-
result = any(StringBuilderVar sbv).getNextAppend(append)
251+
private class StringBuilderAppend extends MethodAccess {
252+
StringBuilderAppend() {
253+
this.getMethod().getDeclaringType() instanceof StringBuildingType and
254+
this.getMethod().hasName("append")
255+
}
253256
}
254257

255-
private Expr getQualifier(MethodAccess e) { result = e.getQualifier() }
258+
private class StringBuilderConstructorOrAppend extends Call {
259+
StringBuilderConstructorOrAppend() {
260+
this instanceof StringBuilderAppend or
261+
this.(ClassInstanceExpr).getConstructedType() instanceof StringBuildingType
262+
}
263+
}
264+
265+
private Expr getQualifier(Expr e) { result = e.(MethodAccess).getQualifier() }
266+
267+
/**
268+
* An extension of `StringBuilderVar` that also accounts for strings appended in StringBuilder/Buffer's constructor
269+
* and in `append` calls chained onto the constructor call.
270+
*
271+
* The original `StringBuilderVar` doesn't care about these because it is designed to model taint, and
272+
* in taint rules terms these are not needed, as the connection between construction, appends and the
273+
* eventual `toString` is more obvious.
274+
*/
275+
private class StringBuilderVarExt extends StringBuilderVar {
276+
/**
277+
* Returns a first assignment after this StringBuilderVar is first assigned.
278+
*
279+
* For example, for `StringBuilder sbv = new StringBuilder("1").append("2"); sbv.append("3").append("4");`
280+
* this returns the append of `"3"`.
281+
*/
282+
private StringBuilderAppend getAFirstAppendAfterAssignment() {
283+
//
284+
result = this.getAnAppend() and not result = this.getNextAppend(_)
285+
}
286+
287+
/**
288+
* Gets the next `append` after `prev`, where `prev` is, perhaps after some more `append` or other
289+
* chained calls, assigned to this `StringBuilderVar`.
290+
*/
291+
private StringBuilderAppend getNextAssignmentChainedAppend(StringBuilderConstructorOrAppend prev) {
292+
getQualifier*(result) = this.getAnAssignedValue() and
293+
result.getQualifier() = prev
294+
}
295+
296+
/**
297+
* Get a constructor call or `append` call that contributes a string to this string builder.
298+
*/
299+
StringBuilderConstructorOrAppend getAConstructorOrAppend() {
300+
exists(this.getNextAssignmentChainedAppend(result)) or
301+
result = this.getAnAssignedValue() or
302+
result = this.getAnAppend()
303+
}
256304

257-
private MethodAccess getAChainedAppend(Expr e) {
258-
(
259-
result.getQualifier() = e
305+
/**
306+
* Like `StringBuilderVar.getNextAppend`, except including appends and constructors directly
307+
* assigned to this `StringBuilderVar`.
308+
*/
309+
private StringBuilderAppend getNextAppendIncludingAssignmentChains(
310+
StringBuilderConstructorOrAppend prev
311+
) {
312+
result = getNextAssignmentChainedAppend(prev)
313+
or
314+
prev = this.getAnAssignedValue() and
315+
result = this.getAFirstAppendAfterAssignment()
260316
or
261-
result.getQualifier() = getAChainedAppend(e)
262-
) and
263-
result.getCallee().getDeclaringType() instanceof StringBuildingType and
264-
result.getCallee().getName() = "append"
317+
result = this.getNextAppend(prev)
318+
}
319+
320+
/**
321+
* Implements `StringBuilderVarExt.getNextAppendIncludingAssignmentChains+(prev)`.
322+
*/
323+
StringBuilderAppend getSubsequentAppendIncludingAssignmentChains(
324+
StringBuilderConstructorOrAppend prev
325+
) {
326+
result = this.getNextAppendIncludingAssignmentChains(prev) or
327+
result =
328+
this.getSubsequentAppendIncludingAssignmentChains(this.getNextAppendIncludingAssignmentChains(prev))
329+
}
265330
}
266331

267332
/**
@@ -275,25 +340,14 @@ private class HostnameSanitizedExpr extends Expr {
275340
this =
276341
any(AddExpr add | add.getLeftOperand() = getAHostnameSanitizingPrefix()).getRightOperand()
277342
or
278-
// Sanitize all appends to a StringBuilder that is initialized with a sanitizing prefix:
279-
// (note imprecision: if the same StringBuilder/StringBuffer has more than one constructor call,
280-
// this sanitizes all of its append calls, not just those that may follow the constructor).
281-
exists(StringBuilderVar sbv, ConstructorCall constructor, Expr initializer |
282-
initializer = sbv.getAnAssignedValue() and
283-
constructor = getQualifier*(initializer) and
284-
constructor.getArgument(0) = getAHostnameSanitizingPrefix() and
285-
(
286-
this = sbv.getAnAppend().getArgument(0)
287-
or
288-
this = getAChainedAppend(constructor).getArgument(0)
289-
)
290-
)
291-
or
292343
// Sanitize expressions that come after a sanitizing prefix in a sequence of StringBuilder operations:
293-
exists(MethodAccess appendSanitizingConstant, MethodAccess subsequentAppend |
294-
appendSanitizingConstant = any(StringBuilderVar v).getAnAppend() and
344+
exists(
345+
StringBuilderConstructorOrAppend appendSanitizingConstant,
346+
StringBuilderAppend subsequentAppend, StringBuilderVarExt v
347+
|
348+
appendSanitizingConstant = v.getAConstructorOrAppend() and
295349
appendSanitizingConstant.getArgument(0) = getAHostnameSanitizingPrefix() and
296-
getNextAppend*(appendSanitizingConstant) = subsequentAppend and
350+
v.getSubsequentAppendIncludingAssignmentChains(appendSanitizingConstant) = subsequentAppend and
297351
this = subsequentAppend.getArgument(0)
298352
)
299353
or

0 commit comments

Comments
 (0)