Repository navigation
Expand file tree
/
Copy pathStringPrefixes.qll
More file actions
180 lines (167 loc) · 6.47 KB
/
Copy pathStringPrefixes.qll
File metadata and controls
180 lines (167 loc) · 6.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/**
* Provides classes and predicates for identifying expressions that may be appended to an interesting prefix.
*
* To use this library, extend the abstract class `InterestingPrefix` to have the library identify expressions that
* may be appended to it, then check `InterestingPrefix.getAnAppendedExpression(Expr)` to get your results.
*
* For example, to identify expressions that may follow "foo:" in some string, we could define:
*
* ```
* private class FooPrefix extends InterestingPrefix {
* int offset;
* FooPrefix() { this.getStringValue().substring("foo:") = offset };
* override int getOffset() { result = offset }
* };
*
* predicate mayFollowFoo(Expr e) { e = any(FooPrefix fp).getAnAppendedExpression() }
* ```
*
* This will identify all the `suffix` expressions in contexts such as:
*
* ```
* "foo:" + suffix1
* "barfoo:" + suffix2
* stringBuilder.append("foo:").append(suffix3);
* String.format("%sfoo:%s", notSuffix, suffix4);
* ```
*/
overlay[local?]
module;
import java
private import semmle.code.java.dataflow.TaintTracking
private import semmle.code.java.StringFormat
/**
* A string constant that contains a prefix whose possibly-appended strings are
* returned by `getAnAppendedExpression`.
*
* Extend this class to specify prefixes whose possibly-appended strings should be analyzed.
*/
abstract class InterestingPrefix extends CompileTimeConstantExpr {
/**
* Gets the offset in this constant string where the interesting prefix begins.
*/
abstract int getOffset();
/**
* Gets an expression that may follow this prefix in a derived string.
*/
Expr getAnAppendedExpression() { mayFollowInterestingPrefix(this, result) }
}
private Expr getAnInterestingPrefix(InterestingPrefix root) {
result = root
or
result.(AddExpr).getAnOperand() = getAnInterestingPrefix(root)
}
private class StringBuilderAppend extends MethodCall {
StringBuilderAppend() {
this.getMethod().getDeclaringType() instanceof StringBuildingType and
this.getMethod().hasName("append")
}
}
private class StringBuilderConstructorOrAppend extends Call {
StringBuilderConstructorOrAppend() {
this instanceof StringBuilderAppend or
this.(ClassInstanceExpr).getConstructedType() instanceof StringBuildingType
}
}
private Expr getQualifier(Expr e) { result = e.(MethodCall).getQualifier() }
/**
* An extension of `StringBuilderVar` that also accounts for strings appended in StringBuilder/Buffer's constructor
* and in `append` calls chained onto the constructor call.
*
* The original `StringBuilderVar` doesn't care about these because it is designed to model taint, and
* in taint rules terms these are not needed, as the connection between construction, appends and the
* eventual `toString` is more obvious.
*/
private class StringBuilderVarExt extends StringBuilderVar {
/**
* Returns a first assignment after this StringBuilderVar is first assigned.
*
* For example, for `StringBuilder sbv = new StringBuilder("1").append("2"); sbv.append("3").append("4");`
* this returns the append of `"3"`.
*/
private StringBuilderAppend getAFirstAppendAfterAssignment() {
result = this.getAnAppend() and not result = this.getNextAppend(_)
}
/**
* Gets the next `append` after `prev`, where `prev` is, perhaps after some more `append` or other
* chained calls, assigned to this `StringBuilderVar`.
*/
private StringBuilderAppend getNextAssignmentChainedAppend(StringBuilderConstructorOrAppend prev) {
getQualifier*(result) = this.getAnAssignedValue() and
result.getQualifier() = prev
}
/**
* Get a constructor call or `append` call that contributes a string to this string builder.
*/
StringBuilderConstructorOrAppend getAConstructorOrAppend() {
exists(this.getNextAssignmentChainedAppend(result)) or
result = this.getAnAssignedValue() or
result = this.getAnAppend()
}
/**
* Like `StringBuilderVar.getNextAppend`, except including appends and constructors directly
* assigned to this `StringBuilderVar`.
*/
private StringBuilderAppend getNextAppendIncludingAssignmentChains(
StringBuilderConstructorOrAppend prev
) {
result = this.getNextAssignmentChainedAppend(prev)
or
prev = this.getAnAssignedValue() and
result = this.getAFirstAppendAfterAssignment()
or
result = this.getNextAppend(prev)
}
/**
* Implements `StringBuilderVarExt.getNextAppendIncludingAssignmentChains+(prev)`.
*/
pragma[nomagic]
StringBuilderAppend getSubsequentAppendIncludingAssignmentChains(
StringBuilderConstructorOrAppend prev
) {
result = this.getNextAppendIncludingAssignmentChains(prev) or
result =
this.getSubsequentAppendIncludingAssignmentChains(this.getNextAppendIncludingAssignmentChains(prev))
}
}
/**
* Holds if `follows` may be concatenated after `prefix`.
*/
private predicate mayFollowInterestingPrefix(InterestingPrefix prefix, Expr follows) {
// Expressions that come after an interesting prefix in a tree of string additions:
follows =
any(AddExpr add | add.getLeftOperand() = getAnInterestingPrefix(prefix)).getRightOperand()
or
// Sanitize expressions that come after an interesting prefix in a sequence of StringBuilder operations:
exists(
StringBuilderConstructorOrAppend appendSanitizingConstant, StringBuilderAppend subsequentAppend,
StringBuilderVarExt v
|
appendSanitizingConstant = v.getAConstructorOrAppend() and
appendSanitizingConstant.getArgument(0) = getAnInterestingPrefix(prefix) and
v.getSubsequentAppendIncludingAssignmentChains(appendSanitizingConstant) = subsequentAppend and
follows = subsequentAppend.getArgument(0)
)
or
// Sanitize expressions that come after an interesting prefix in the args to a format call:
exists(
FormattingCall formatCall, FormatString formatString, int prefixOffset, int laterOffset,
int sanitizedArg
|
formatString = unique(FormatString fs | fs = formatCall.getAFormatString()) and
(
// An interesting prefix argument comes before this:
exists(int argIdx |
formatCall.getArgumentToBeFormatted(argIdx) = prefix and
prefixOffset = formatString.getAnArgUsageOffset(argIdx)
)
or
// The format string itself contains an interesting prefix that precedes subsequent arguments:
formatString = prefix.getStringValue() and
prefixOffset = prefix.getOffset()
) and
laterOffset > prefixOffset and
laterOffset = formatString.getAnArgUsageOffset(sanitizedArg) and
follows = formatCall.getArgumentToBeFormatted(sanitizedArg)
)
}