Skip to content

Commit 842aafc

Browse files
committed
C++: Fix new UnsafeDaclSecurityDescriptor FP
This query uses data flow for nullness analysis, which is always going to be a large overapproximation. The overapproximation became too big for one of the test cases after the recent change to make data flow go across assignment by reference. To make this query more conservative, it will now only report that the `pDacl` argument can be null if there isn't also evidence that it can be non-null.
1 parent 7165959 commit 842aafc

File tree

3 files changed

+86
-6
lines changed

3 files changed

+86
-6
lines changed

cpp/ql/src/Security/CWE/CWE-732/UnsafeDaclSecurityDescriptor.ql

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
import cpp
1616
import semmle.code.cpp.dataflow.DataFlow
17+
import semmle.code.cpp.dataflow.DataFlow2
1718

1819
/**
1920
* A function call to SetSecurityDescriptorDacl to set the ACL, specified by (2nd argument) bDaclPresent = TRUE
@@ -28,9 +29,9 @@ class SetSecurityDescriptorDaclFunctionCall extends FunctionCall {
2829
/**
2930
* Dataflow that detects a call to SetSecurityDescriptorDacl with a NULL DACL as the pDacl argument
3031
*/
31-
class SetSecurityDescriptorDaclFunctionConfiguration extends DataFlow::Configuration {
32-
SetSecurityDescriptorDaclFunctionConfiguration() {
33-
this = "SetSecurityDescriptorDaclFunctionConfiguration"
32+
class NullDaclConfig extends DataFlow::Configuration {
33+
NullDaclConfig() {
34+
this = "NullDaclConfig"
3435
}
3536

3637
override predicate isSource(DataFlow::Node source) {
@@ -49,6 +50,43 @@ class SetSecurityDescriptorDaclFunctionConfiguration extends DataFlow::Configura
4950
}
5051
}
5152

53+
/**
54+
* Dataflow that detects a call to SetSecurityDescriptorDacl with a pDacl
55+
* argument that's _not_ likely to be NULL.
56+
*/
57+
class NonNullDaclConfig extends DataFlow2::Configuration {
58+
NonNullDaclConfig() {
59+
this = "NonNullDaclConfig"
60+
}
61+
62+
override predicate isSource(DataFlow::Node source) {
63+
source.getType().getUnspecifiedType().(PointerType).getBaseType() =
64+
any(Type t | t.getName() = "ACL").getUnspecifiedType() and
65+
(
66+
// If the value comes from a function whose body we can't see, assume
67+
// it's not null.
68+
exists(Call call |
69+
not exists(call.getTarget().getBlock()) and
70+
source.asExpr() = call
71+
)
72+
or
73+
// If the value is assigned by reference, assume it's not null. The data
74+
// flow library cannot currently follow flow from the body of a function to
75+
// an assignment by reference, so this rule applies whether we see the
76+
// body or not.
77+
exists(Call call |
78+
call.getAnArgument() = source.asDefiningArgument()
79+
)
80+
)
81+
}
82+
83+
override predicate isSink(DataFlow::Node sink) {
84+
exists(SetSecurityDescriptorDaclFunctionCall call |
85+
sink.asExpr() = call.getArgument(2)
86+
)
87+
}
88+
}
89+
5290
from SetSecurityDescriptorDaclFunctionCall call, string message
5391
where exists
5492
(
@@ -59,9 +97,10 @@ where exists
5997
) or exists
6098
(
6199
Expr constassign, VariableAccess var,
62-
SetSecurityDescriptorDaclFunctionConfiguration config |
100+
NullDaclConfig nullDaclConfig, NonNullDaclConfig nonNullDaclConfig |
63101
message = "Setting a DACL to NULL in a SECURITY_DESCRIPTOR using variable " + var + " that is set to NULL will result in an unprotected object." |
64102
var = call.getArgument(2)
65-
and config.hasFlow(DataFlow::exprNode(constassign), DataFlow::exprNode(var))
103+
and nullDaclConfig.hasFlow(DataFlow::exprNode(constassign), DataFlow::exprNode(var))
104+
and not nonNullDaclConfig.hasFlow(_, DataFlow::exprNode(var))
66105
)
67106
select call, message

cpp/ql/test/query-tests/Security/CWE/CWE-732/UnsafeDaclSecurityDescriptor.cpp

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,44 @@ void Test()
8989
NULL, // DACL is going to be removed from security descriptor. Default/inherited access ==> should not be flagged
9090
FALSE);
9191

92-
}
92+
}
93+
94+
PACL returnUnknownAcl();
95+
96+
PACL returnNull() {
97+
return NULL;
98+
}
99+
100+
PACL returnMaybeAcl(bool b) {
101+
PACL pDacl = NULL;
102+
if (b) {
103+
SetEntriesInAcl(0, NULL, NULL, &pDacl);
104+
}
105+
return pDacl;
106+
}
107+
108+
void Test2()
109+
{
110+
PSECURITY_DESCRIPTOR pSecurityDescriptor;
111+
112+
PACL pDacl1 = returnUnknownAcl();
113+
SetSecurityDescriptorDacl(
114+
pSecurityDescriptor,
115+
TRUE, // Dacl Present
116+
pDacl1, // Give `returnUnknownAcl` the benefit of the doubt ==> should not be flagged
117+
FALSE);
118+
119+
PACL pDacl2 = returnNull();
120+
SetSecurityDescriptorDacl(
121+
pSecurityDescriptor,
122+
TRUE, // Dacl Present
123+
pDacl2, // NULL pointer to DACL == BUG
124+
FALSE);
125+
126+
PACL pDacl3 = returnMaybeAcl(true);
127+
SetSecurityDescriptorDacl(
128+
pSecurityDescriptor,
129+
TRUE, // Dacl Present
130+
pDacl3, // should not be flagged
131+
FALSE);
132+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
| UnsafeDaclSecurityDescriptor.cpp:70:9:70:33 | call to SetSecurityDescriptorDacl | Setting a DACL to NULL in a SECURITY_DESCRIPTOR will result in an unprotected object. |
22
| UnsafeDaclSecurityDescriptor.cpp:76:9:76:33 | call to SetSecurityDescriptorDacl | Setting a DACL to NULL in a SECURITY_DESCRIPTOR using variable pDacl that is set to NULL will result in an unprotected object. |
3+
| UnsafeDaclSecurityDescriptor.cpp:120:5:120:29 | call to SetSecurityDescriptorDacl | Setting a DACL to NULL in a SECURITY_DESCRIPTOR using variable pDacl2 that is set to NULL will result in an unprotected object. |

0 commit comments

Comments
 (0)