Skip to content

Commit 219f97f

Browse files
zx2c4NateD-MSFT
andauthored
Reduce false positives compared to SDV for driver tests (#217)
* UserModeMemoryReadMultipleTimes: Remove MdlOrigin.originCanWrite The double-fetch query flags pairs of dereferences through memory origins where originCanWrite() is true. MdlOrigin had this set unconditionally, so every pair of accesses through an MDL-mapped pointer was flagged, even pure writes to an output buffer: void *Buf = MmGetSystemAddressForMdlSafe(Mdl, NormalPagePriority); ((MY_IOCTL *)Buf)->Flags = 0; // flagged ((MY_IOCTL *)Buf)->Count = 0; // flagged While usermode does retain write access to MDL-locked pages through its original VA, flagging all of these is not useful in practice, since virtually all direct I/O drivers access the buffer more than once. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> * IllegalFieldAccess2: Follow call chains from DriverEntry The query only allowed MajorFunction access directly inside a DriverEntry function. Drivers that split initialization across helper functions were falsely flagged: VOID InitDispatch(DRIVER_OBJECT *DriverObject) { DriverObject->MajorFunction[IRP_MJ_CREATE] = MyCreate; } NTSTATUS DriverEntry(DRIVER_OBJECT *DriverObject, ...) { InitDispatch(DriverObject); } Follow call chains transitively from DriverEntry so that helpers are recognized. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> * Irql: Evaluate _When_ conditions at call sites KeSetEvent is annotated _When_(Wait==1, _IRQL_requires_max_(APC_LEVEL)). When called with Wait=FALSE, the restriction does not apply and DISPATCH_LEVEL is fine. The query unconditionally applied the annotation regardless of argument values: KeAcquireInStackQueuedSpinLock(&Lock, &Handle); // raises to DISPATCH KeSetEvent(&Event, IO_NO_INCREMENT, FALSE); // flagged Evaluate _When_ conditions against compile-time argument values and skip annotations whose conditions are demonstrably false. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> * IrqlAnnotationIssue: Skip the -1 parse-failure sentinel getIrqlLevel() returns -1 when it cannot parse the annotation, such as _IRQL_saves_ or _When_ conditionals with complex expressions. The query flagged all of these as invalid annotations: _IRQL_saves_ VOID KeAcquireSpinLock(PKSPIN_LOCK Lock, PKIRQL OldIrql); // flagged Filter out the -1 sentinel since these are valid annotations beyond the analyzer's current parsing capability. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> * InvalidFunctionClassTypedef: Allow _PAGED variants DRIVER_DISPATCH_PAGED is a valid paged variant of DRIVER_DISPATCH for dispatch routines that only run at PASSIVE_LEVEL. The query flagged the mismatch between function class and typedef: _Dispatch_type_(IRP_MJ_DEVICE_CONTROL) static DRIVER_DISPATCH_PAGED MyDispatch; // flagged Recognize _PAGED suffixed typedefs as compatible with their unsuffixed base function class. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> * OpaqueMdlUse, OpaqueMdlWrite: Exclude local MDL variables Drivers sometimes construct synthetic MDLs on the stack for zero-copy operations. Direct field access is the only way to initialize these: MDL Mdl = { 0 }; Mdl.MappedSystemVa = Buffer; Mdl.ByteCount = Len; Mdl.MdlFlags = MDL_MAPPED_TO_SYSTEM_VA; Exclude accesses on locally-declared MDL struct variables. Accesses through MDL pointers are not excluded, since those typically reference system-provided MDLs. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> * UnguardedNullReturnDereference: Exclude calls to _In_opt_ parameters When a possibly-null return value is passed to a function whose parameter is annotated _In_opt_, the function explicitly handles null. The query was flagging these as unguarded dereferences: OBJ *Obj = LookupObject(...); PutObject(Obj); // flagged, but PutObject takes _In_opt_ OBJ * Exclude calls where the argument's corresponding parameter carries an _opt_ SAL annotation. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> * UnguardedNullReturnDereference: Honor _Analysis_assume_ _Analysis_assume_(Expr) tells the analyzer to treat Expr as true. MSVC compiles __assume() into an empty statement with no AST node, but the EmptyStmt remains in the control flow graph at the macro invocation site. The query was not recognizing this as a null guard: NBL *Nbl = DequeueNbl(&Queue); _Analysis_assume_(Nbl); NET_BUFFER_LIST_STATUS(Nbl) = NDIS_STATUS_FAILURE; // flagged Match the EmptyStmt at the _Analysis_assume_ location as a barrier by correlating it with the macro invocation that names the guarded variable. Also match AssumeExpr directly for compilers that emit it. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> --------- Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com> Co-authored-by: NateD-MSFT <34494373+NateD-MSFT@users.noreply.github.com>
1 parent d6ed965 commit 219f97f

10 files changed

Lines changed: 106 additions & 29 deletions

File tree

src/drivers/general/queries/InvalidFunctionClassTypedef/InvalidFunctionClassTypedef.ql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* @tags correctness
1717
* ca_ported
1818
* @scope domainspecific
19-
* @query-version v1
19+
* @query-version v2
2020
*/
2121

2222
import cpp
@@ -73,6 +73,11 @@ where
7373
not (
7474
funcClass.matches("EVT_WDF_DEVICE_%ARM_WAKE_FROM_S%") and
7575
declTypedef.matches("EVT_WDF_DEVICE_%ARM_WAKE_FROM_S%")
76+
) and
77+
not (
78+
declTypedef = funcClass + "_PAGED"
79+
or
80+
funcClass = declTypedef + "_PAGED"
7681
)
7782
select af, "The function class " + funcClass + " on the function does not match the function class " +
7883
declTypedef + " on the typedef used here"

src/drivers/general/queries/IrqlAnnotationIssue/IrqlAnnotationIssue.ql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818
* @tags correctness
1919
* ca_ported
2020
* @scope domainspecific
21-
* @query-version v1
21+
* @query-version v2
2222
*/
2323

2424
import cpp
2525
import drivers.libraries.Irql
2626

2727
from IrqlFunctionAnnotation ifa
28-
where not ifa.getIrqlLevel() instanceof IrqlValue
28+
where
29+
not ifa.getIrqlLevel() instanceof IrqlValue and
30+
ifa.getIrqlLevel() != -1
2931
select ifa, "Invalid IRQL annotation: " + ifa.getIrqlLevel()

src/drivers/general/queries/IrqlTooHigh/IrqlTooHigh.ql

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,24 @@
1818
* ca_ported
1919
* wddst
2020
* @scope domainspecific
21-
* @query-version v2
21+
* @query-version v3
2222
*/
2323

2424
import cpp
2525
import drivers.libraries.Irql
2626

27-
from FunctionCall call, IrqlRestrictsFunction irqlFunc, ControlFlowNode prior, int irqlRequirement
27+
from
28+
FunctionCall call, IrqlRestrictsFunction irqlFunc, ControlFlowNode prior, int irqlRequirement,
29+
IrqlFunctionAnnotation ifa
2830
where
2931
call.getTarget() = irqlFunc and
3032
prior = call.getAPredecessor() and
31-
(
32-
irqlFunc.(IrqlMaxAnnotatedFunction).getIrqlLevel() = irqlRequirement
33-
or
34-
irqlFunc.(IrqlRequiresAnnotatedFunction).getIrqlLevel() = irqlRequirement
35-
) and
33+
ifa = irqlFunc.getFuncIrqlAnnotation() and
34+
(ifa instanceof IrqlMaxAnnotation or ifa instanceof IrqlRequiresAnnotation) and
35+
irqlRequirement = ifa.getIrqlLevel() and
3636
irqlRequirement != -1 and
37-
irqlRequirement < min(getPotentialExitIrqlAtCfn(prior))
37+
irqlRequirement < min(getPotentialExitIrqlAtCfn(prior)) and
38+
not ifa.whenConditionIsFalseAtCallSite(call)
3839
select call,
3940
"$@: IRQL potentially too high at call to $@. Maximum IRQL for this call: " + irqlRequirement +
4041
", IRQL at preceding node: " + min(getPotentialExitIrqlAtCfn(prior)),

src/drivers/general/queries/IrqlTooLow/IrqlTooLow.ql

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,24 @@
1818
* ca_ported
1919
* wddst
2020
* @scope domainspecific
21-
* @query-version v3
21+
* @query-version v4
2222
*/
2323

2424
import cpp
2525
import drivers.libraries.Irql
2626

27-
from FunctionCall call, IrqlRestrictsFunction irqlFunc, ControlFlowNode prior, int irqlRequirement
27+
from
28+
FunctionCall call, IrqlRestrictsFunction irqlFunc, ControlFlowNode prior, int irqlRequirement,
29+
IrqlFunctionAnnotation ifa
2830
where
2931
call.getTarget() = irqlFunc and
3032
prior = call.getAPredecessor() and
31-
(
32-
irqlFunc.(IrqlMinAnnotatedFunction).getIrqlLevel() = irqlRequirement
33-
or
34-
irqlFunc.(IrqlRequiresAnnotatedFunction).getIrqlLevel() = irqlRequirement
35-
) and
33+
ifa = irqlFunc.getFuncIrqlAnnotation() and
34+
(ifa instanceof IrqlMinAnnotation or ifa instanceof IrqlRequiresAnnotation) and
35+
irqlRequirement = ifa.getIrqlLevel() and
3636
irqlRequirement != -1 and
37-
irqlRequirement > max(getPotentialExitIrqlAtCfn(prior))
37+
irqlRequirement > max(getPotentialExitIrqlAtCfn(prior)) and
38+
not ifa.whenConditionIsFalseAtCallSite(call)
3839
select call,
3940
"$@: IRQL potentially too low at call to $@. Minimum IRQL for this call: " + irqlRequirement +
4041
", IRQL at preceding node: " + max(getPotentialExitIrqlAtCfn(prior)), call.getControlFlowScope(),

src/drivers/libraries/Irql.qll

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,36 @@
208208
then result = this.getIrqlLevelString().toInt()
209209
else result = -1
210210
}
211+
212+
/**
213+
* Holds if this is a `_When_` annotation whose condition is demonstrably
214+
* false at call site `call`.
215+
*
216+
* Callers should only pass the annotation that supplied the IRQL
217+
* requirement being checked -- otherwise an unrelated `_When_` clause on
218+
* the same function (e.g. the `Wait==1` clause when we are evaluating the
219+
* `Wait==0` clause on `KeSetEvent`) would suppress legitimate findings.
220+
*/
221+
predicate whenConditionIsFalseAtCallSite(FunctionCall call) {
222+
this.getMacroName() = "_When_" and
223+
exists(string cond, string paramName, int paramIdx |
224+
cond = this.getUnexpandedArgument(0) and
225+
call.getTarget().getParameter(paramIdx).getName() = paramName and
226+
(
227+
// "Param != 0" is false when arg is 0
228+
paramName = cond.regexpCapture("(\\w+)\\s*!=\\s*0", 1) and
229+
call.getArgument(paramIdx).getValue() = "0"
230+
or
231+
// "Param == N" (N>0) is false when arg is 0
232+
paramName = cond.regexpCapture("(\\w+)\\s*==\\s*([1-9]\\d*)", 1) and
233+
call.getArgument(paramIdx).getValue() = "0"
234+
or
235+
// "Param == 0" is false when arg is nonzero
236+
paramName = cond.regexpCapture("(\\w+)\\s*==\\s*0", 1) and
237+
call.getArgument(paramIdx).getValue() != "0"
238+
)
239+
)
240+
}
211241
}
212242

213243
/** Represents an "\_IRQL\_requires\_same\_" annotation. */

src/drivers/wdm/queries/IllegalFieldAccess2/IllegalFieldAccess2.ql

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* ca_ported
1919
* wddst
2020
* @scope domainspecific
21-
* @query-version v1
21+
* @query-version v2
2222
*/
2323

2424
import cpp
@@ -87,6 +87,19 @@ class IllegalDeviceObjectFieldAccess extends FieldAccess, PotentiallyIllegalFiel
8787
}
8888
}
8989

90+
/**
91+
* Holds if `f` is `DriverEntry` itself or is transitively called from
92+
* `DriverEntry`.
93+
*/
94+
predicate calledFromDriverEntry(Function f) {
95+
f instanceof WdmDriverEntry
96+
or
97+
exists(Function caller |
98+
calledFromDriverEntry(caller) and
99+
f.getACallToThisFunction().getEnclosingFunction() = caller
100+
)
101+
}
102+
90103
/**
91104
* A potentially illegal access to a DriverObject field, namely:
92105
* - Accesses to a DriverObject's DriverStartIo, DriverUnload, MajorFunction, and DriverExtension fields outside DriverEntry
@@ -119,15 +132,14 @@ class IllegalDriverObjectFieldAccess extends FieldAccess, PotentiallyIllegalFiel
119132

120133
override predicate isIllegalAccess() {
121134
/*
122-
* Below fields are illegal iff we're not in a DriverEntry function.
123-
* Possible future improvement: do flow analysis to figure out if we're in
124-
* a call from a DriverEntry routine.
135+
* Below fields are illegal iff we're not in a DriverEntry function or a
136+
* function transitively called from DriverEntry.
125137
*/
126138

127139
this.getTarget()
128140
.getName()
129141
.matches(["DriverStartIo", "DriverUnload", "MajorFunction", "DriverExtension"]) and
130-
not this.getControlFlowScope() instanceof WdmDriverEntry
142+
not calledFromDriverEntry(this.getControlFlowScope())
131143
or
132144
not this.getTarget()
133145
.getName()

src/drivers/wdm/queries/OpaqueMdlUse/OpaqueMdlUse.ql

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* @tags correctness
1818
* wddst
1919
* @scope domainspecific
20-
* @query-version v1
20+
* @query-version v2
2121
*/
2222

2323
import cpp
@@ -42,6 +42,10 @@ class IncorrectMdlFieldAccess extends FieldAccess {
4242
exists(SafeMdlAccessMacro safeMacro |
4343
safeMacro.getAnInvocation().getAnExpandedElement() = this
4444
)
45+
) and
46+
not exists(LocalVariable lv |
47+
lv.getType().getUnspecifiedType() instanceof Mdl and
48+
this.getQualifier().(VariableAccess).getTarget() = lv
4549
)
4650
}
4751

src/drivers/wdm/queries/OpaqueMdlWrite/OpaqueMdlWrite.ql

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
* ca_ported
1919
* wddst
2020
* @scope domainspecific
21-
* @query-version v1
21+
* @query-version v2
2222
*/
2323

2424
import cpp
@@ -43,6 +43,10 @@ class IncorrectMdlWrite extends Assignment {
4343
exists(SafeMdlWriteMacro safeWriteMacro |
4444
safeWriteMacro.getAnInvocation().getAnExpandedElement() = this
4545
)
46+
) and
47+
not exists(LocalVariable lv |
48+
lv.getType().getUnspecifiedType() instanceof Mdl and
49+
access.getQualifier().(VariableAccess).getTarget() = lv
4650
)
4751
}
4852

src/microsoft/Likely Bugs/UnguardedNullReturnDereference.ql

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,14 @@ class UnguardedNullReturnDereferenceReachability extends StackVariableReachabili
7979
.getDereferencingOperation()
8080
.(FunctionCall)
8181
.getTarget()
82-
.hasGlobalName("free")
82+
.hasGlobalName("free") and
83+
not exists(FunctionCall fc, int i |
84+
fc = node.(Dereference).getDereferencingOperation() and
85+
fc.getArgument(i) = v.getAnAccess() and
86+
exists(SALMaybeNull sa |
87+
sa.getDeclaration() = fc.getTarget().getParameter(i)
88+
)
89+
)
8390
}
8491

8592
override predicate isBarrier(ControlFlowNode node, StackVariable v) {
@@ -132,6 +139,19 @@ class UnguardedNullReturnDereferenceReachability extends StackVariableReachabili
132139
"_checked_pointer_impl", "_fail_on_unexpected_null_pointer", "_fail_on_memory_op"]
133140
and c.getAnArgument().getAChild*() = v.getAnAccess()
134141
and c = node)
142+
or
143+
exists(AssumeExpr ae |
144+
ae = node and
145+
ae.getOperand().getAChild*() = v.getAnAccess()
146+
)
147+
or
148+
exists(MacroInvocation mi |
149+
mi.getMacroName() = "_Analysis_assume_" and
150+
mi.getUnexpandedArgument(0) = v.getName() and
151+
node.getLocation().getFile() = mi.getLocation().getFile() and
152+
node.getLocation().getStartLine() = mi.getLocation().getStartLine() and
153+
node instanceof EmptyStmt
154+
)
135155
}
136156
}
137157

src/microsoft/code/cpp/public/windows/kernel/MemoryOrigins.qll

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,6 @@ class MdlOrigin extends DirectMemoryOrigin, FunctionCall {
177177
}
178178

179179
override Expr getABufferSizeExpression() { none() }
180-
181-
override predicate originCanWrite() { any() }
182180
}
183181

184182
/**

0 commit comments

Comments
 (0)