Skip to content

Commit 76caad0

Browse files
authored
Merge pull request #1119 from geoffw0/wprintf2
CPP: Better handling of %s/%c/%S/%C in Printf/FormattingFunction.qll
2 parents ed0ef36 + a6e0296 commit 76caad0

File tree

18 files changed

+123
-60
lines changed

18 files changed

+123
-60
lines changed

change-notes/1.21/analysis-cpp.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@
1717
| Memory is never freed (`cpp/memory-never-freed`) | More correct results | Support added for more Microsoft-specific allocation functions, including `LocalAlloc`, `GlobalAlloc`, `HeapAlloc` and `CoTaskMemAlloc`. |
1818
| Resource not released in destructor (`cpp/resource-not-released-in-destructor`) | Fewer false positive results | Resource allocation and deallocation functions are now determined more accurately. |
1919
| Comparison result is always the same | Fewer false positive results | The range analysis library is now more conservative about floating point values being possibly `NaN` |
20+
| Wrong type of arguments to formatting function (`cpp/wrong-type-format-argument`) | More correct results and fewer false positive results | This query now more accurately identifies wide and non-wide string/character format arguments on different platforms. Platform detection has also been made more accurate for the purposes of this query. |
2021

2122
## Changes to QL libraries

cpp/ql/src/semmle/code/cpp/File.qll

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,13 @@ class File extends Container, @file {
302302
predicate compiledAsMicrosoft() {
303303
exists(Compilation c |
304304
c.getAFileCompiled() = this and
305-
c.getAnArgument() = "--microsoft"
305+
(
306+
c.getAnArgument() = "--microsoft" or
307+
c.getAnArgument().toLowerCase().replaceAll("\\", "/").matches("%/cl.exe")
308+
)
309+
) or exists(File parent |
310+
parent.compiledAsMicrosoft() and
311+
parent.getAnIncludedFile() = this
306312
)
307313
}
308314

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

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,19 @@ class AttributeFormattingFunction extends FormattingFunction {
3232
* A standard function such as `vprintf` that has a format parameter
3333
* and a variable argument list of type `va_arg`.
3434
*/
35-
predicate primitiveVariadicFormatter(TopLevelFunction f, int formatParamIndex, boolean wide) {
35+
predicate primitiveVariadicFormatter(TopLevelFunction f, int formatParamIndex) {
3636
f.getName().regexpMatch("_?_?va?[fs]?n?w?printf(_s)?(_p)?(_l)?")
3737
and (
3838
if f.getName().matches("%\\_l")
3939
then formatParamIndex = f.getNumberOfParameters() - 3
4040
else formatParamIndex = f.getNumberOfParameters() - 2
41-
) and if f.getName().matches("%w%") then wide = true else wide = false
41+
)
4242
}
4343

4444
private
45-
predicate callsVariadicFormatter(Function f, int formatParamIndex, boolean wide) {
45+
predicate callsVariadicFormatter(Function f, int formatParamIndex) {
4646
exists(FunctionCall fc, int i |
47-
variadicFormatter(fc.getTarget(), i, wide)
47+
variadicFormatter(fc.getTarget(), i)
4848
and fc.getEnclosingFunction() = f
4949
and fc.getArgument(i) = f.getParameter(formatParamIndex).getAnAccess()
5050
)
@@ -54,11 +54,11 @@ predicate callsVariadicFormatter(Function f, int formatParamIndex, boolean wide)
5454
* Holds if `f` is a function such as `vprintf` that has a format parameter
5555
* (at `formatParamIndex`) and a variable argument list of type `va_arg`.
5656
*/
57-
predicate variadicFormatter(Function f, int formatParamIndex, boolean wide) {
58-
primitiveVariadicFormatter(f, formatParamIndex, wide)
57+
predicate variadicFormatter(Function f, int formatParamIndex) {
58+
primitiveVariadicFormatter(f, formatParamIndex)
5959
or (
6060
not f.isVarargs()
61-
and callsVariadicFormatter(f, formatParamIndex, wide)
61+
and callsVariadicFormatter(f, formatParamIndex)
6262
)
6363
}
6464

@@ -68,12 +68,10 @@ predicate variadicFormatter(Function f, int formatParamIndex, boolean wide) {
6868
*/
6969
class UserDefinedFormattingFunction extends FormattingFunction {
7070
UserDefinedFormattingFunction() {
71-
isVarargs() and callsVariadicFormatter(this, _, _)
71+
isVarargs() and callsVariadicFormatter(this, _)
7272
}
7373

74-
override int getFormatParameterIndex() { callsVariadicFormatter(this, result, _) }
75-
76-
override predicate isWideCharDefault() { callsVariadicFormatter(this, _, true) }
74+
override int getFormatParameterIndex() { callsVariadicFormatter(this, result) }
7775
}
7876

7977
/**
@@ -674,8 +672,8 @@ class FormatLiteral extends Literal {
674672
/**
675673
* Gets the char type required by the nth conversion specifier.
676674
* - in the base case this is the default for the formatting function
677-
* (e.g. `char` for `printf`, `wchar_t` for `wprintf`).
678-
* - the `%S` format character reverses wideness.
675+
* (e.g. `char` for `printf`, `char` or `wchar_t` for `wprintf`).
676+
* - the `%C` format character reverses wideness.
679677
* - the size prefixes 'l'/'w' and 'h' override the type character
680678
* to wide or single-byte characters respectively.
681679
*/
@@ -721,8 +719,8 @@ class FormatLiteral extends Literal {
721719
/**
722720
* Gets the string type required by the nth conversion specifier.
723721
* - in the base case this is the default for the formatting function
724-
* (e.g. `char` for `printf`, `wchar_t` for `wprintf`).
725-
* - the `%S` format character reverses wideness.
722+
* (e.g. `char *` for `printf`, `char *` or `wchar_t *` for `wprintf`).
723+
* - the `%S` format character reverses wideness on some platforms.
726724
* - the size prefixes 'l'/'w' and 'h' override the type character
727725
* to wide or single-byte characters respectively.
728726
*/

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

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ private Type stripTopLevelSpecifiersOnly(Type t) {
2222
*/
2323
Type getAFormatterWideType() {
2424
exists(FormattingFunction ff |
25-
result = stripTopLevelSpecifiersOnly(ff.getDefaultCharType()) and
25+
result = stripTopLevelSpecifiersOnly(ff.getFormatCharType()) and
2626
result.getSize() != 1
2727
)
2828
}
@@ -46,6 +46,14 @@ abstract class FormattingFunction extends Function {
4646
/** Gets the position at which the format parameter occurs. */
4747
abstract int getFormatParameterIndex();
4848

49+
/**
50+
* Holds if this `FormattingFunction` is in a context that supports
51+
* Microsoft rules and extensions.
52+
*/
53+
predicate isMicrosoft() {
54+
getFile().compiledAsMicrosoft()
55+
}
56+
4957
/**
5058
* Holds if the default meaning of `%s` is a `wchar_t *`, rather than
5159
* a `char *` (either way, `%S` will have the opposite meaning).
@@ -55,31 +63,44 @@ abstract class FormattingFunction extends Function {
5563
deprecated predicate isWideCharDefault() { none() }
5664

5765
/**
58-
* Gets the default character type expected for `%s` by this function. Typically
59-
* `char` or `wchar_t`.
66+
* Gets the character type used in the format string for this function.
6067
*/
61-
Type getDefaultCharType() {
62-
result =
68+
Type getFormatCharType() {
69+
result =
6370
stripTopLevelSpecifiersOnly(
6471
stripTopLevelSpecifiersOnly(
6572
getParameter(getFormatParameterIndex()).getType().getUnderlyingType()
6673
).(PointerType).getBaseType()
6774
)
6875
}
6976

77+
/**
78+
* Gets the default character type expected for `%s` by this function. Typically
79+
* `char` or `wchar_t`.
80+
*/
81+
Type getDefaultCharType() {
82+
(
83+
isMicrosoft() and
84+
result = getFormatCharType()
85+
) or (
86+
not isMicrosoft() and
87+
result instanceof PlainCharType
88+
)
89+
}
90+
7091
/**
7192
* Gets the non-default character type expected for `%S` by this function. Typically
7293
* `wchar_t` or `char`. On some snapshots there may be multiple results where we can't tell
7394
* which is correct for a particular function.
7495
*/
7596
Type getNonDefaultCharType() {
76-
(
77-
getDefaultCharType().getSize() = 1 and
78-
result = getAFormatterWideTypeOrDefault()
79-
) or (
80-
getDefaultCharType().getSize() > 1 and
81-
result instanceof PlainCharType
82-
)
97+
(
98+
getDefaultCharType().getSize() = 1 and
99+
result = getWideCharType()
100+
) or (
101+
not getDefaultCharType().getSize() = 1 and
102+
result instanceof PlainCharType
103+
)
83104
}
84105

85106
/**
@@ -89,10 +110,12 @@ abstract class FormattingFunction extends Function {
89110
*/
90111
Type getWideCharType() {
91112
(
92-
result = getDefaultCharType() or
93-
result = getNonDefaultCharType()
94-
) and
95-
result.getSize() > 1
113+
result = getFormatCharType() and
114+
result.getSize() > 1
115+
) or (
116+
not getFormatCharType().getSize() > 1 and
117+
result = getAFormatterWideTypeOrDefault() // may have more than one result
118+
)
96119
}
97120

98121
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import external.ExternalArtifact
44
predicate printfLikeFunction(Function func, int formatArg) {
55
(formatArg = func.(FormattingFunction).getFormatParameterIndex() and not func instanceof UserDefinedFormattingFunction)
66
or
7-
primitiveVariadicFormatter(func, formatArg, _)
7+
primitiveVariadicFormatter(func, formatArg)
88
or
99
exists(ExternalData data |
1010
// TODO Do this \ to / conversion in the toolchain?
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
| tests.cpp:18:15:18:22 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
22
| tests.cpp:19:15:19:22 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
3-
| tests.cpp:25:17:25:23 | Hello | This argument should be of type 'wchar_t *' but is of type 'char *' |
4-
| tests.cpp:26:17:26:24 | Hello | This argument should be of type 'wchar_t *' but is of type 'char16_t *' |
5-
| tests.cpp:30:17:30:24 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
6-
| tests.cpp:31:17:31:24 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
7-
| tests.cpp:33:36:33:42 | Hello | This argument should be of type 'char16_t *' but is of type 'char *' |
8-
| tests.cpp:35:36:35:43 | Hello | This argument should be of type 'char16_t *' but is of type 'wchar_t *' |
9-
| tests.cpp:38:36:38:43 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
10-
| tests.cpp:39:36:39:43 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
3+
| tests.cpp:26:17:26:24 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
4+
| tests.cpp:27:17:27:24 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
5+
| tests.cpp:29:17:29:23 | Hello | This argument should be of type 'wchar_t *' but is of type 'char *' |
6+
| tests.cpp:30:17:30:24 | Hello | This argument should be of type 'wchar_t *' but is of type 'char16_t *' |
7+
| tests.cpp:34:36:34:43 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
8+
| tests.cpp:35:36:35:43 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
9+
| tests.cpp:37:36:37:42 | Hello | This argument should be of type 'char16_t *' but is of type 'char *' |
10+
| tests.cpp:39:36:39:43 | Hello | This argument should be of type 'char16_t *' but is of type 'wchar_t *' |
11+
| tests.cpp:42:37:42:44 | Hello | This argument should be of type 'char *' but is of type 'char16_t *' |
12+
| tests.cpp:43:37:43:44 | Hello | This argument should be of type 'char *' but is of type 'wchar_t *' |
13+
| tests.cpp:45:37:45:43 | Hello | This argument should be of type 'char16_t *' but is of type 'char *' |
14+
| tests.cpp:47:37:47:44 | Hello | This argument should be of type 'char16_t *' but is of type 'wchar_t *' |
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
| tests.cpp:8:5:8:10 | printf | char | char16_t, wchar_t | char16_t, wchar_t |
2-
| tests.cpp:9:5:9:11 | wprintf | wchar_t | char | wchar_t |
3-
| tests.cpp:10:5:10:12 | swprintf | char16_t | char | char16_t |
1+
| tests.cpp:8:5:8:10 | printf | char | char | char16_t, wchar_t | char16_t, wchar_t |
2+
| tests.cpp:9:5:9:11 | wprintf | wchar_t | char | wchar_t | wchar_t |
3+
| tests.cpp:10:5:10:12 | swprintf | char16_t | char | char16_t | char16_t |

cpp/ql/test/query-tests/Likely Bugs/Format/WrongTypeFormatArguments/Linux_mixed_byte_wprintf/formattingFunction.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import cpp
33
from FormattingFunction f
44
select
55
f,
6+
concat(f.getFormatCharType().toString(), ", "),
67
concat(f.getDefaultCharType().toString(), ", "),
78
concat(f.getNonDefaultCharType().toString(), ", "),
89
concat(f.getWideCharType().toString(), ", ")

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

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,27 @@ void tests() {
2222
printf("%S", u"Hello"); // GOOD
2323
printf("%S", L"Hello"); // GOOD
2424

25-
wprintf(L"%s", "Hello"); // BAD: expecting wchar_t
26-
wprintf(L"%s", u"Hello"); // BAD: expecting wchar_t
27-
wprintf(L"%s", L"Hello"); // GOOD
25+
wprintf(L"%s", "Hello"); // GOOD
26+
wprintf(L"%s", u"Hello"); // BAD: expecting char
27+
wprintf(L"%s", L"Hello"); // BAD: expecting char
2828

29-
wprintf(L"%S", "Hello"); // GOOD
30-
wprintf(L"%S", u"Hello"); // BAD: expecting char
31-
wprintf(L"%S", L"Hello"); // BAD: expecting char
29+
wprintf(L"%S", "Hello"); // BAD: expecting wchar_t
30+
wprintf(L"%S", u"Hello"); // BAD: expecting wchar_t
31+
wprintf(L"%S", L"Hello"); // GOOD
3232

33-
swprintf(buffer, BUF_SIZE, u"%s", "Hello"); // BAD: expecting char16_t
34-
swprintf(buffer, BUF_SIZE, u"%s", u"Hello"); // GOOD
35-
swprintf(buffer, BUF_SIZE, u"%s", L"Hello"); // BAD: expecting char16_t
33+
swprintf(buffer, BUF_SIZE, u"%s", "Hello"); // GOOD
34+
swprintf(buffer, BUF_SIZE, u"%s", u"Hello"); // BAD: expecting char
35+
swprintf(buffer, BUF_SIZE, u"%s", L"Hello"); // BAD: expecting char
3636

37-
swprintf(buffer, BUF_SIZE, u"%S", "Hello"); // GOOD
38-
swprintf(buffer, BUF_SIZE, u"%S", u"Hello"); // BAD: expecting char
39-
swprintf(buffer, BUF_SIZE, u"%S", L"Hello"); // BAD: expecting char
37+
swprintf(buffer, BUF_SIZE, u"%S", "Hello"); // BAD: expecting char16_t
38+
swprintf(buffer, BUF_SIZE, u"%S", u"Hello"); // GOOD
39+
swprintf(buffer, BUF_SIZE, u"%S", L"Hello"); // BAD: expecting char16_t
40+
41+
swprintf(buffer, BUF_SIZE, u"%hs", "Hello"); // GOOD
42+
swprintf(buffer, BUF_SIZE, u"%hs", u"Hello"); // BAD: expecting char
43+
swprintf(buffer, BUF_SIZE, u"%hs", L"Hello"); // BAD: expecting char
44+
45+
swprintf(buffer, BUF_SIZE, u"%ls", "Hello"); // BAD: expecting char16_t
46+
swprintf(buffer, BUF_SIZE, u"%ls", u"Hello"); // GOOD
47+
swprintf(buffer, BUF_SIZE, u"%ls", L"Hello"); // BAD: expecting char16_t
4048
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
| printf.cpp:33:31:33:37 | test | This argument should be of type 'char *' but is of type 'char16_t *' |
12
| printf.cpp:45:29:45:35 | test | This argument should be of type 'char *' but is of type 'char16_t *' |
23
| printf.cpp:52:29:52:35 | test | This argument should be of type 'char16_t *' but is of type 'wchar_t *' |

0 commit comments

Comments
 (0)