Skip to content

Commit e0a9e2d

Browse files
authored
Merge pull request #4754 from geoffw0/modelchanges3
C++: Expose more information in FormattingFunction and make subclasses private.
2 parents 6e6cd05 + d20619d commit e0a9e2d

File tree

12 files changed

+160
-63
lines changed

12 files changed

+160
-63
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
lgtm,codescanning
2+
* `FormattingFunction.getOutputParameterIndex` now has a parameter identifying whether the output at that index is a buffer or a stream.
3+
* `FormattingFunction` now has a predicate `isOutputGlobal` indicating when the output is to a global stream.
4+
* The `primitiveVariadicFormatter` and `variadicFormatter` predicates have more parameters exposing information about the function.

cpp/ql/src/semmle/code/cpp/commons/Printf.qll

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -34,66 +34,87 @@ class AttributeFormattingFunction extends FormattingFunction {
3434

3535
/**
3636
* A standard function such as `vprintf` that has a format parameter
37-
* and a variable argument list of type `va_arg`.
37+
* and a variable argument list of type `va_arg`. `formatParamIndex` indicates
38+
* the format parameter and `type` indicates the type of `vprintf`:
39+
* - `""` is a `vprintf` variant, `outputParamIndex` is `-1`.
40+
* - `"f"` is a `vfprintf` variant, `outputParamIndex` indicates the output stream parameter.
41+
* - `"s"` is a `vsprintf` variant, `outputParamIndex` indicates the output buffer parameter.
42+
* - `"?"` if the type cannot be deteremined. `outputParamIndex` is `-1`.
3843
*/
39-
predicate primitiveVariadicFormatter(TopLevelFunction f, int formatParamIndex) {
40-
f.getName().regexpMatch("_?_?va?[fs]?n?w?printf(_s)?(_p)?(_l)?") and
44+
predicate primitiveVariadicFormatter(
45+
TopLevelFunction f, string type, int formatParamIndex, int outputParamIndex
46+
) {
47+
type = f.getName().regexpCapture("_?_?va?([fs]?)n?w?printf(_s)?(_p)?(_l)?", 1) and
4148
(
4249
if f.getName().matches("%\\_l")
4350
then formatParamIndex = f.getNumberOfParameters() - 3
4451
else formatParamIndex = f.getNumberOfParameters() - 2
45-
)
52+
) and
53+
if type = "" then outputParamIndex = -1 else outputParamIndex = 0 // Conveniently, these buffer parameters are all at index 0.
4654
}
4755

48-
/**
49-
* A standard function such as `vsprintf` that has an output parameter
50-
* and a variable argument list of type `va_arg`.
51-
*/
52-
private predicate primitiveVariadicFormatterOutput(TopLevelFunction f, int outputParamIndex) {
53-
// note: this might look like the regular expression in `primitiveVariadicFormatter`, but
54-
// there is one important difference: the [fs] part is not optional, as these classify
55-
// the `printf` variants that write to a buffer.
56-
// Conveniently, these buffer parameters are all at index 0.
57-
f.getName().regexpMatch("_?_?va?[fs]n?w?printf(_s)?(_p)?(_l)?") and outputParamIndex = 0
58-
}
59-
60-
private predicate callsVariadicFormatter(Function f, int formatParamIndex) {
61-
exists(FunctionCall fc, int i |
62-
variadicFormatter(fc.getTarget(), i) and
56+
private predicate callsVariadicFormatter(
57+
Function f, string type, int formatParamIndex, int outputParamIndex
58+
) {
59+
// calls a variadic formatter with `formatParamIndex`, `outputParamIndex` linked
60+
exists(FunctionCall fc, int format, int output |
61+
variadicFormatter(fc.getTarget(), type, format, output) and
6362
fc.getEnclosingFunction() = f and
64-
fc.getArgument(i) = f.getParameter(formatParamIndex).getAnAccess()
63+
fc.getArgument(format) = f.getParameter(formatParamIndex).getAnAccess() and
64+
fc.getArgument(output) = f.getParameter(outputParamIndex).getAnAccess()
6565
)
66-
}
67-
68-
private predicate callsVariadicFormatterOutput(Function f, int outputParamIndex) {
69-
exists(FunctionCall fc, int i |
66+
or
67+
// calls a variadic formatter with only `formatParamIndex` linked
68+
exists(FunctionCall fc, string calledType, int format, int output |
69+
variadicFormatter(fc.getTarget(), calledType, format, output) and
7070
fc.getEnclosingFunction() = f and
71-
variadicFormatterOutput(fc.getTarget(), i) and
72-
fc.getArgument(i) = f.getParameter(outputParamIndex).getAnAccess()
71+
fc.getArgument(format) = f.getParameter(formatParamIndex).getAnAccess() and
72+
not fc.getArgument(output) = f.getParameter(_).getAnAccess() and
73+
(
74+
calledType = "" and
75+
type = ""
76+
or
77+
calledType != "" and
78+
type = "?" // we probably should have an `outputParamIndex` link but have lost it.
79+
) and
80+
outputParamIndex = -1
7381
)
7482
}
7583

7684
/**
77-
* Holds if `f` is a function such as `vprintf` that takes variable argument list
78-
* of type `va_arg` and writes formatted output to a buffer given as a parameter at
79-
* index `outputParamIndex`, if any.
85+
* Holds if `f` is a function such as `vprintf` that has a format parameter
86+
* and a variable argument list of type `va_arg`. `formatParamIndex` indicates
87+
* the format parameter and `type` indicates the type of `vprintf`:
88+
* - `""` is a `vprintf` variant, `outputParamIndex` is `-1`.
89+
* - `"f"` is a `vfprintf` variant, `outputParamIndex` indicates the output stream parameter.
90+
* - `"s"` is a `vsprintf` variant, `outputParamIndex` indicates the output buffer parameter.
91+
* - `"?"` if the type cannot be deteremined. `outputParamIndex` is `-1`.
8092
*/
81-
private predicate variadicFormatterOutput(Function f, int outputParamIndex) {
82-
primitiveVariadicFormatterOutput(f, outputParamIndex)
93+
predicate variadicFormatter(Function f, string type, int formatParamIndex, int outputParamIndex) {
94+
primitiveVariadicFormatter(f, type, formatParamIndex, outputParamIndex)
8395
or
8496
not f.isVarargs() and
85-
callsVariadicFormatterOutput(f, outputParamIndex)
97+
callsVariadicFormatter(f, type, formatParamIndex, outputParamIndex)
98+
}
99+
100+
/**
101+
* A standard function such as `vprintf` that has a format parameter
102+
* and a variable argument list of type `va_arg`.
103+
*
104+
* DEPRECATED: Use the four argument version instead.
105+
*/
106+
deprecated predicate primitiveVariadicFormatter(TopLevelFunction f, int formatParamIndex) {
107+
primitiveVariadicFormatter(f, _, formatParamIndex, _)
86108
}
87109

88110
/**
89111
* Holds if `f` is a function such as `vprintf` that has a format parameter
90112
* (at `formatParamIndex`) and a variable argument list of type `va_arg`.
113+
*
114+
* DEPRECATED: Use the four argument version instead.
91115
*/
92-
predicate variadicFormatter(Function f, int formatParamIndex) {
93-
primitiveVariadicFormatter(f, formatParamIndex)
94-
or
95-
not f.isVarargs() and
96-
callsVariadicFormatter(f, formatParamIndex)
116+
deprecated predicate variadicFormatter(Function f, int formatParamIndex) {
117+
variadicFormatter(f, _, formatParamIndex, _)
97118
}
98119

99120
/**
@@ -103,11 +124,17 @@ predicate variadicFormatter(Function f, int formatParamIndex) {
103124
class UserDefinedFormattingFunction extends FormattingFunction {
104125
override string getAPrimaryQlClass() { result = "UserDefinedFormattingFunction" }
105126

106-
UserDefinedFormattingFunction() { isVarargs() and callsVariadicFormatter(this, _) }
127+
UserDefinedFormattingFunction() { isVarargs() and callsVariadicFormatter(this, _, _, _) }
128+
129+
override int getFormatParameterIndex() { callsVariadicFormatter(this, _, result, _) }
107130

108-
override int getFormatParameterIndex() { callsVariadicFormatter(this, result) }
131+
override int getOutputParameterIndex(boolean isStream) {
132+
callsVariadicFormatter(this, "f", _, result) and isStream = true
133+
or
134+
callsVariadicFormatter(this, "s", _, result) and isStream = false
135+
}
109136

110-
override int getOutputParameterIndex() { callsVariadicFormatterOutput(this, result) }
137+
override predicate isOutputGlobal() { callsVariadicFormatter(this, "", _, _) }
111138
}
112139

113140
/**

cpp/ql/src/semmle/code/cpp/models/implementations/Printf.qll

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import semmle.code.cpp.models.interfaces.Alias
1111
/**
1212
* The standard functions `printf`, `wprintf` and their glib variants.
1313
*/
14-
class Printf extends FormattingFunction, AliasFunction {
14+
private class Printf extends FormattingFunction, AliasFunction {
1515
Printf() {
1616
this instanceof TopLevelFunction and
1717
(
@@ -31,6 +31,8 @@ class Printf extends FormattingFunction, AliasFunction {
3131
hasGlobalName("wprintf_s")
3232
}
3333

34+
override predicate isOutputGlobal() { any() }
35+
3436
override predicate parameterNeverEscapes(int n) { n = 0 }
3537

3638
override predicate parameterEscapesOnlyViaReturn(int n) { none() }
@@ -41,7 +43,7 @@ class Printf extends FormattingFunction, AliasFunction {
4143
/**
4244
* The standard functions `fprintf`, `fwprintf` and their glib variants.
4345
*/
44-
class Fprintf extends FormattingFunction {
46+
private class Fprintf extends FormattingFunction {
4547
Fprintf() {
4648
this instanceof TopLevelFunction and
4749
(
@@ -56,7 +58,7 @@ class Fprintf extends FormattingFunction {
5658

5759
deprecated override predicate isWideCharDefault() { hasGlobalOrStdName("fwprintf") }
5860

59-
override int getOutputParameterIndex() { result = 0 }
61+
override int getOutputParameterIndex(boolean isStream) { result = 0 and isStream = true }
6062
}
6163

6264
/**
@@ -109,7 +111,9 @@ private class Sprintf extends FormattingFunction {
109111
result = 1
110112
}
111113

112-
override int getOutputParameterIndex() { not hasGlobalName("g_strdup_printf") and result = 0 }
114+
override int getOutputParameterIndex(boolean isStream) {
115+
not hasGlobalName("g_strdup_printf") and result = 0 and isStream = false
116+
}
113117

114118
override int getFirstFormatArgumentIndex() {
115119
if hasGlobalName("__builtin___sprintf_chk")
@@ -164,7 +168,7 @@ private class SnprintfImpl extends Snprintf {
164168
.getSize() > 1
165169
}
166170

167-
override int getOutputParameterIndex() { result = 0 }
171+
override int getOutputParameterIndex(boolean isStream) { result = 0 and isStream = false }
168172

169173
override int getFirstFormatArgumentIndex() {
170174
exists(string name |
@@ -224,20 +228,22 @@ private class StringCchPrintf extends FormattingFunction {
224228
.getSize() > 1
225229
}
226230

227-
override int getOutputParameterIndex() { result = 0 }
231+
override int getOutputParameterIndex(boolean isStream) { result = 0 and isStream = false }
228232

229233
override int getSizeParameterIndex() { result = 1 }
230234
}
231235

232236
/**
233237
* The standard function `syslog`.
234238
*/
235-
class Syslog extends FormattingFunction {
239+
private class Syslog extends FormattingFunction {
236240
Syslog() {
237241
this instanceof TopLevelFunction and
238242
hasGlobalName("syslog") and
239243
not exists(getDefinition().getFile().getRelativePath())
240244
}
241245

242246
override int getFormatParameterIndex() { result = 1 }
247+
248+
override predicate isOutputGlobal() { any() }
243249
}

cpp/ql/src/semmle/code/cpp/models/interfaces/FormattingFunction.qll

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,26 @@ abstract class FormattingFunction extends ArrayFunction, TaintFunction {
108108
result = getAFormatterWideTypeOrDefault() // may have more than one result
109109
}
110110

111+
/**
112+
* Gets the position at which the output parameter, if any, occurs. If
113+
* `isStream` is `true`, the output parameter is a stream (that is, this
114+
* function behaves like `fprintf`). If `isStream` is `false`, the output
115+
* parameter is a buffer (that is, this function behaves like `sprintf`).
116+
*/
117+
int getOutputParameterIndex(boolean isStream) { none() }
118+
111119
/**
112120
* Gets the position at which the output parameter, if any, occurs.
121+
*
122+
* DEPRECATED: use `getOutputParameterIndex(boolean isStream)` instead.
123+
*/
124+
deprecated int getOutputParameterIndex() { result = getOutputParameterIndex(_) }
125+
126+
/**
127+
* Holds if this function outputs to a global stream such as standard output,
128+
* standard error or a system log. For example `printf`.
113129
*/
114-
int getOutputParameterIndex() { none() }
130+
predicate isOutputGlobal() { none() }
115131

116132
/**
117133
* Gets the position of the first format argument, corresponding with
@@ -141,18 +157,18 @@ abstract class FormattingFunction extends ArrayFunction, TaintFunction {
141157
}
142158

143159
override predicate hasArrayWithVariableSize(int bufParam, int countParam) {
144-
bufParam = getOutputParameterIndex() and
160+
bufParam = getOutputParameterIndex(false) and
145161
countParam = getSizeParameterIndex()
146162
}
147163

148164
override predicate hasArrayWithUnknownSize(int bufParam) {
149-
bufParam = getOutputParameterIndex() and
165+
bufParam = getOutputParameterIndex(false) and
150166
not exists(getSizeParameterIndex())
151167
}
152168

153169
override predicate hasArrayInput(int bufParam) { bufParam = getFormatParameterIndex() }
154170

155-
override predicate hasArrayOutput(int bufParam) { bufParam = getOutputParameterIndex() }
171+
override predicate hasArrayOutput(int bufParam) { bufParam = getOutputParameterIndex(false) }
156172

157173
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
158174
exists(int arg |
@@ -161,7 +177,7 @@ abstract class FormattingFunction extends ArrayFunction, TaintFunction {
161177
arg >= getFirstFormatArgumentIndex()
162178
) and
163179
input.isParameterDeref(arg) and
164-
output.isParameterDeref(getOutputParameterIndex())
180+
output.isParameterDeref(getOutputParameterIndex(_))
165181
)
166182
}
167183
}

cpp/ql/src/semmle/code/cpp/security/BufferWrite.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ class SprintfBW extends BufferWriteCall {
229229
result = this.(FormattingFunctionCall).getFormatArgument(_)
230230
}
231231

232-
override Expr getDest() { result = getArgument(f.getOutputParameterIndex()) }
232+
override Expr getDest() { result = getArgument(f.getOutputParameterIndex(false)) }
233233

234234
override int getMaxData() {
235235
exists(FormatLiteral fl |

cpp/ql/src/semmle/code/cpp/security/FileWrite.qll

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
*/
44

55
import cpp
6-
import semmle.code.cpp.models.implementations.Printf
76

87
/**
98
* A function call that writes to a file.
@@ -144,8 +143,8 @@ private predicate fileWrite(Call write, Expr source, Expr dest) {
144143
)
145144
or
146145
// fprintf
147-
s >= f.(Fprintf).getFormatParameterIndex() and
148-
d = f.(Fprintf).getOutputParameterIndex()
146+
s >= f.(FormattingFunction).getFormatParameterIndex() and
147+
d = f.(FormattingFunction).getOutputParameterIndex(true)
149148
)
150149
or
151150
// file stream using '<<', 'put' or 'write'

cpp/ql/src/semmle/code/cpp/security/OutputWrite.qll

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,9 @@ private predicate outputWrite(Expr write, Expr source) {
5959
exists(Function f, int arg |
6060
f = write.(Call).getTarget() and source = write.(Call).getArgument(arg)
6161
|
62-
// printf
63-
arg >= f.(Printf).getFormatParameterIndex()
64-
or
65-
// syslog
66-
arg >= f.(Syslog).getFormatParameterIndex()
62+
// printf / syslog
63+
f.(FormattingFunction).isOutputGlobal() and
64+
arg >= f.(FormattingFunction).getFormatParameterIndex()
6765
or
6866
// puts, putchar
6967
(

cpp/ql/src/semmle/code/cpp/security/PrintfLike.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ predicate printfLikeFunction(Function func, int formatArg) {
1717
formatArg = func.(FormattingFunction).getFormatParameterIndex() and
1818
not func instanceof UserDefinedFormattingFunction
1919
or
20-
primitiveVariadicFormatter(func, formatArg)
20+
primitiveVariadicFormatter(func, _, formatArg, _)
2121
or
2222
exists(ExternalData data |
2323
// TODO Do this \ to / conversion in the toolchain?

cpp/ql/src/semmle/code/cpp/security/TaintTrackingImpl.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ private predicate copyValueBetweenArguments(Function f, int sourceArg, int destA
478478
or
479479
exists(FormattingFunction ff | ff = f |
480480
sourceArg in [ff.getFormatParameterIndex() .. maxArgIndex(f)] and
481-
destArg = ff.getOutputParameterIndex()
481+
destArg = ff.getOutputParameterIndex(false)
482482
)
483483
}
484484

cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_signed_chars/WrongTypeFormatArguments.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@
5353
| printf1.h:231:25:231:25 | i | This argument should be of type 'char *' but is of type 'int' |
5454
| printf1.h:234:25:234:25 | i | This argument should be of type 'char *' but is of type 'int' |
5555
| printf1.h:235:22:235:22 | s | This argument should be of type 'int' but is of type 'char *' |
56+
| printf1.h:276:32:276:32 | s | This argument should be of type 'int' but is of type 'char *' |
57+
| printf1.h:278:17:278:17 | s | This argument should be of type 'int' but is of type 'char *' |
5658
| real_world.h:61:21:61:22 | & ... | This argument should be of type 'int *' but is of type 'short *' |
5759
| real_world.h:62:22:62:23 | & ... | This argument should be of type 'short *' but is of type 'int *' |
5860
| real_world.h:63:22:63:24 | & ... | This argument should be of type 'short *' but is of type 'unsigned int *' |

0 commit comments

Comments
 (0)