Skip to content

Commit 2427227

Browse files
authored
Merge pull request #17611 from microsoft/brodes/wcharcharconversion_false_positives_upstream5
Brodes/wcharcharconversion false positives upstream5
2 parents 204e4c5 + c496503 commit 2427227

File tree

4 files changed

+140
-6
lines changed

4 files changed

+140
-6
lines changed

cpp/ql/src/Security/CWE/CWE-704/WcharCharConversion.ql

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,85 @@
1313
*/
1414

1515
import cpp
16+
import semmle.code.cpp.controlflow.Guards
1617

1718
class WideCharPointerType extends PointerType {
1819
WideCharPointerType() { this.getBaseType() instanceof WideCharType }
1920
}
2021

22+
/**
23+
* Given type `t`, recurses through and returns all
24+
* intermediate base types, including `t`.
25+
*/
26+
Type getABaseType(Type t) {
27+
result = t
28+
or
29+
result = getABaseType(t.(DerivedType).getBaseType())
30+
or
31+
result = getABaseType(t.(TypedefType).getBaseType())
32+
}
33+
2134
/**
2235
* A type that may also be `CharPointerType`, but that are likely used as arbitrary buffers.
2336
*/
2437
class UnlikelyToBeAStringType extends Type {
2538
UnlikelyToBeAStringType() {
26-
this.(PointerType).getBaseType().(CharType).isUnsigned() or
27-
this.(PointerType).getBaseType().getName().toLowerCase().matches("%byte") or
28-
this.getName().toLowerCase().matches("%byte") or
29-
this.(PointerType).getBaseType().hasName("uint8_t")
39+
exists(Type targ | getABaseType(this) = targ |
40+
// NOTE: not using CharType isUnsigned, but rather look for any explicitly declared unsigned
41+
// char types. Assuming these are used for buffers, not strings.
42+
targ.(CharType).getName().toLowerCase().matches("unsigned%") or
43+
targ.getName().toLowerCase().matches(["uint8_t", "%byte%"])
44+
)
3045
}
3146
}
3247

48+
// Types that can be wide depending on the UNICODE macro
49+
// see https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
50+
class UnicodeMacroDependentWidthType extends Type {
51+
UnicodeMacroDependentWidthType() {
52+
exists(Type targ | getABaseType(this) = targ |
53+
targ.getName() in [
54+
"LPCTSTR",
55+
"LPTSTR",
56+
"PCTSTR",
57+
"PTSTR",
58+
"TBYTE",
59+
"TCHAR"
60+
]
61+
)
62+
}
63+
}
64+
65+
class UnicodeMacro extends Macro {
66+
UnicodeMacro() { this.getName().toLowerCase().matches("%unicode%") }
67+
}
68+
69+
class UnicodeMacroInvocation extends MacroInvocation {
70+
UnicodeMacroInvocation() { this.getMacro() instanceof UnicodeMacro }
71+
}
72+
73+
/**
74+
* Holds when a expression whose type is UnicodeMacroDependentWidthType and
75+
* is observed to be guarded by a check involving a bitwise-and operation
76+
* with a UnicodeMacroInvocation.
77+
* Such expressions are assumed to be checked dynamically, i.e.,
78+
* the flag would indicate if UNICODE typing is set correctly to allow
79+
* or disallow a widening cast.
80+
*/
81+
predicate isLikelyDynamicallyChecked(Expr e) {
82+
e.getType() instanceof UnicodeMacroDependentWidthType and
83+
exists(GuardCondition gc, BitwiseAndExpr bai, UnicodeMacroInvocation umi |
84+
bai.getAnOperand() = umi.getExpr()
85+
|
86+
// bai == 0 is false when reaching `e.getBasicBlock()`.
87+
// That is, bai != 0 when reaching `e.getBasicBlock()`.
88+
gc.ensuresEq(bai, 0, e.getBasicBlock(), false)
89+
or
90+
// bai == k and k != 0 is true when reaching `e.getBasicBlock()`.
91+
gc.ensuresEq(bai, any(int k | k != 0), e.getBasicBlock(), true)
92+
)
93+
}
94+
3395
from Expr e1, Cast e2
3496
where
3597
e2 = e1.getConversion() and
@@ -42,7 +104,11 @@ where
42104
not e1.getType() instanceof UnlikelyToBeAStringType and
43105
// Avoid castings from 'new' expressions as typically these will be safe
44106
// Example: `__Type* ret = reinterpret_cast<__Type*>(New(m_pmo) char[num * sizeof(__Type)]);`
45-
not exists(NewOrNewArrayExpr newExpr | newExpr.getAChild*() = e1)
107+
not exists(NewOrNewArrayExpr newExpr | newExpr.getAChild*() = e1) and
108+
// Avoid cases where the cast is guarded by a check to determine if
109+
// unicode encoding is enabled in such a way to disallow the dangerous cast
110+
// at runtime.
111+
not isLikelyDynamicallyChecked(e1)
46112
select e1,
47113
"Conversion from " + e1.getType().toString() + " to " + e2.getType().toString() +
48114
". Use of invalid string can lead to undefined behavior."
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The `cpp/incorrect-string-type-conversion` query now produces fewer false positives caused by failure to detect byte arrays.
5+
* The `cpp/incorrect-string-type-conversion` query now produces fewer false positives caused by failure to recognize dynamic checks prior to possible dangerous widening.

cpp/ql/test/query-tests/Security/CWE/CWE-704/WcharCharConversion.cpp

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,59 @@ void NonStringFalsePositiveTest2(unsigned char* buffer)
5353
{
5454
wchar_t *lpWchar = NULL;
5555
lpWchar = (LPWSTR)buffer; // Possible False Positive
56-
}
56+
}
57+
58+
typedef unsigned char BYTE;
59+
using FOO = BYTE*;
60+
61+
void NonStringFalsePositiveTest3(FOO buffer)
62+
{
63+
wchar_t *lpWchar = NULL;
64+
lpWchar = (LPWSTR)buffer; // GOOD
65+
}
66+
67+
#define UNICODE 0x8
68+
69+
// assume EMPTY_MACRO is tied to if UNICODE is enabled
70+
#ifdef EMPTY_MACRO
71+
typedef WCHAR* LPTSTR;
72+
#else
73+
typedef char* LPTSTR;
74+
#endif
75+
76+
void CheckedConversionFalsePositiveTest3(unsigned short flags, LPTSTR buffer)
77+
{
78+
wchar_t *lpWchar = NULL;
79+
if(flags & UNICODE)
80+
lpWchar = (LPWSTR)buffer; // GOOD
81+
else
82+
lpWchar = (LPWSTR)buffer; // BUG
83+
84+
if((flags & UNICODE) == 0x8)
85+
lpWchar = (LPWSTR)buffer; // GOOD
86+
else
87+
lpWchar = (LPWSTR)buffer; // BUG
88+
89+
if((flags & UNICODE) != 0x8)
90+
lpWchar = (LPWSTR)buffer; // BUG
91+
else
92+
lpWchar = (LPWSTR)buffer; // GOOD
93+
94+
// Bad operator precedence
95+
if(flags & UNICODE == 0x8)
96+
lpWchar = (LPWSTR)buffer; // BUG
97+
else
98+
lpWchar = (LPWSTR)buffer; // BUG
99+
100+
if((flags & UNICODE) != 0)
101+
lpWchar = (LPWSTR)buffer; // GOOD
102+
else
103+
lpWchar = (LPWSTR)buffer; // BUG
104+
105+
if((flags & UNICODE) == 0)
106+
lpWchar = (LPWSTR)buffer; // BUG
107+
else
108+
lpWchar = (LPWSTR)buffer; // GOOD
109+
110+
lpWchar = (LPWSTR)buffer; // BUG
111+
}

cpp/ql/test/query-tests/Security/CWE/CWE-704/WcharCharConversion.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,11 @@
33
| WcharCharConversion.cpp:24:22:24:27 | lpChar | Conversion from char * to wchar_t *. Use of invalid string can lead to undefined behavior. |
44
| WcharCharConversion.cpp:26:23:26:28 | lpChar | Conversion from char * to LPCWSTR. Use of invalid string can lead to undefined behavior. |
55
| WcharCharConversion.cpp:27:17:27:22 | lpChar | Conversion from char * to LPWSTR. Use of invalid string can lead to undefined behavior. |
6+
| WcharCharConversion.cpp:82:21:82:26 | buffer | Conversion from LPTSTR to LPWSTR. Use of invalid string can lead to undefined behavior. |
7+
| WcharCharConversion.cpp:87:21:87:26 | buffer | Conversion from LPTSTR to LPWSTR. Use of invalid string can lead to undefined behavior. |
8+
| WcharCharConversion.cpp:90:21:90:26 | buffer | Conversion from LPTSTR to LPWSTR. Use of invalid string can lead to undefined behavior. |
9+
| WcharCharConversion.cpp:96:21:96:26 | buffer | Conversion from LPTSTR to LPWSTR. Use of invalid string can lead to undefined behavior. |
10+
| WcharCharConversion.cpp:98:21:98:26 | buffer | Conversion from LPTSTR to LPWSTR. Use of invalid string can lead to undefined behavior. |
11+
| WcharCharConversion.cpp:103:21:103:26 | buffer | Conversion from LPTSTR to LPWSTR. Use of invalid string can lead to undefined behavior. |
12+
| WcharCharConversion.cpp:106:21:106:26 | buffer | Conversion from LPTSTR to LPWSTR. Use of invalid string can lead to undefined behavior. |
13+
| WcharCharConversion.cpp:110:20:110:25 | buffer | Conversion from LPTSTR to LPWSTR. Use of invalid string can lead to undefined behavior. |

0 commit comments

Comments
 (0)