Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e79ff5b
[AI] Initial stab at optimizing suppressions and IRQL.
NateD-MSFT Apr 23, 2026
bcd6ac6
Merge branch 'development' into user/nated-msft/optimization-work
NateD-MSFT Apr 24, 2026
59ce447
[AI] Revise IRQL queries and libraries to reduce false positives.
NateD-MSFT Apr 24, 2026
97de0ec
Compress repetitive code and fix misplaced comments in IRQL queries
Copilot Apr 24, 2026
d837689
Merge pull request #219 from microsoft/copilot/review-code-comments-c…
NateD-MSFT Apr 24, 2026
64b4a6b
Optimize MultiplePagedCode.ql to avoid timeouts on large codebases
NateD-MSFT Apr 28, 2026
90fbd1f
Bump @query-version on queries changed since development branch
NateD-MSFT Apr 28, 2026
f44073f
Refine IrqlFloatStateMismatch suppression to be CFG-independent
NateD-MSFT Apr 28, 2026
b57ad9b
Catch IrqlFloatStateMismatch through unannotated helper wrappers
NateD-MSFT Apr 28, 2026
2515238
Add adversarial tests + document known false negatives in qhelp
NateD-MSFT Apr 28, 2026
3f4ff1a
Fix three false negatives surfaced by the prior review
NateD-MSFT Apr 28, 2026
b2493b3
Fix MultiplePagedCode cross-file conflation FP
NateD-MSFT Apr 29, 2026
2c3d0e8
Pretty-print SARIF baselines compressed by #220
Copilot Apr 29, 2026
e1549f2
Merge branch 'user/nated-msft/optimization-work' into copilot/pretty-…
NateD-MSFT Apr 29, 2026
a76f855
Merge pull request #221 from microsoft/copilot/pretty-print-sarifs
NateD-MSFT Apr 29, 2026
4cacaf0
Tidy up: indentation, ASCII-only comments, dedupe IFSM where, refresh…
NateD-MSFT Apr 29, 2026
39dd725
IFSM: add AST-loop OR-branch to irqlChangesBetween for intra-function…
NateD-MSFT Apr 29, 2026
ad1379a
Irql.qll: add loop-body AST fallback to getPotentialExitIrqlAtCfn
NateD-MSFT Apr 30, 2026
65e6328
IrqlFloatStateMismatch: pragma[inline_late] on irqlChangesBetween
NateD-MSFT May 1, 2026
9a8eba1
[AI] More IRQL correctness tweaks
NateD-MSFT May 6, 2026
7848c04
[AI] Reduce verbosity of AI comments + other cleanup.
NateD-MSFT May 7, 2026
92c933e
Refactor a Supression predicate into a class
NateD-MSFT May 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,13 @@ Correct example

## References
* [ C28111 ](https://learn.microsoft.com/en-us/windows-hardware/drivers/devtest/28111-floating-point-irql-mismatch)

## Semmle-specific notes
**Wrapper / common-caller pattern.** The query searches for IRQL-changing calls between save and restore in either their shared enclosing function, or — when one or both endpoints sit inside a thin one-level helper (e.g. `save_fp_helper` forwarding to `KeSaveFloatingPointState`) — in the common caller of those helpers.

**Known false negatives:**

* **IRQL changes deep inside helper bodies.** If a helper raises/lowers IRQL between its entry and the save/restore primitive it forwards to, that change isn't visible from the common caller. Annotate the helper with `_IRQL_raises_` / `_IRQL_saves_global_` to make its IRQL behavior visible without body inspection.
* **Indirect calls.** IRQL changes via function pointer or dispatch-table dispatch are not recognized; the predicate inspects only the static call target.
* **Loops where restore is textually before save.** The AST-loop branch of `irqlChangesBetween` correctly recognizes such patterns, but the upstream IRQL cascade does not always bind at `KeSaveFloatingPointState`'s argument expression inside loop bodies, so the `irqlSource != irqlSink` filter rejects them before this predicate fires. Recovering this case needs work in `Irql.qll`.
* **Wrapper chains longer than one level.** Only one level of helper wrapping is modelled. Multi-level wrappers need the annotation hint above, or a direct call site.
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,51 @@
</a>
</li>
</references>
<semmleNotes>
<!-- === AI-generated === -->
<p>
<b>Wrapper / common-caller pattern.</b> The query searches for
IRQL-changing calls between save and restore in either their
shared enclosing function, or &#8212; when one or both endpoints
sit inside a thin one-level helper (e.g.
<code>save_fp_helper</code> forwarding to
<code>KeSaveFloatingPointState</code>) &#8212; in the common
caller of those helpers.
</p>
<p>
<b>Known false negatives:</b>
</p>
<ul>
<li>
<b>IRQL changes deep inside helper bodies.</b> If a helper
raises/lowers IRQL between its entry and the save/restore
primitive it forwards to, that change isn't visible from
the common caller. Annotate the helper with
<code>_IRQL_raises_</code> /
<code>_IRQL_saves_global_</code> to make its IRQL behavior
visible without body inspection.
</li>
<li>
<b>Indirect calls.</b> IRQL changes via function pointer or
dispatch-table dispatch are not recognized; the predicate
inspects only the static call target.
</li>
<li>
<b>Loops where restore is textually before save.</b> The
AST-loop branch of <code>irqlChangesBetween</code>
correctly recognizes such patterns, but the upstream IRQL
cascade does not always bind at
<code>KeSaveFloatingPointState</code>'s argument expression
inside loop bodies, so the
<code>irqlSource != irqlSink</code> filter rejects them
before this predicate fires. Recovering this case needs
work in <code>Irql.qll</code>.
</li>
<li>
<b>Wrapper chains longer than one level.</b> Only one level
of helper wrapping is modelled. Multi-level wrappers need
the annotation hint above, or a direct call site.
</li>
</ul>
</semmleNotes>
</qhelp>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* @tags correctness
* ca_ported
* @scope domainspecific
* @query-version v1
* @query-version v6
*/

import cpp
Expand All @@ -42,12 +42,139 @@ module FloatStateFlowConfig implements DataFlow::ConfigSig {

module FloatStateFlow = DataFlow::Global<FloatStateFlowConfig>;

from DataFlow::Node source, DataFlow::Node sink, int irqlSink, int irqlSource
/**
* --- AI-generated ---
*
* Gets a source line in `f` that anchors `fc` from `f`'s perspective:
*
* - If `fc`'s enclosing function is `f`, the anchor is `fc`'s own
* start line.
* - Otherwise, if `f` contains a call site whose static target is
* `fc`'s enclosing function, the anchor is that call site's start
* line. (One-level wrapper case.)
*
* This lets `irqlChangesBetween` reason about the relative source
* position of the save and the restore in any function that either
* directly contains the call or calls the helper that does.
*/
private int anchorLineForCall(Function f, FunctionCall fc) {
f = fc.getEnclosingFunction() and
result = fc.getLocation().getStartLine()
or
exists(FunctionCall site |
site.getEnclosingFunction() = f and
site.getTarget() = fc.getEnclosingFunction() and
f != fc.getEnclosingFunction() and
result = site.getLocation().getStartLine()
)
}

/**
* --- AI-generated ---
*
* Holds if some IRQL-changing call could run between `saveCall` and
* `restoreCall` at runtime. Used as a sanity filter on top of the
* dataflow result: dataflow already paired save -> restore, but
* without this filter we'd flag pairs whose hypothetical entry IRQLs
* differ even though no actual IRQL transition runs between them.
*
* Two additive disjuncts:
*
* 1. **Source-position branch.** A `FunctionCall` `mid` syntactically
* inside some `f` has `isIrqlChangingCall(mid)` and lies on a
* source line bracketed by `anchorLineForCall(f, save/restoreCall)`.
* Two cross-function reach mechanisms with different depth bounds
* cooperate: `anchorLineForCall` walks at most one call-graph edge
* (we need a concrete line in `f` to anchor each endpoint), but
* `isIrqlChangingCall` is transitively recursive through
* `isIrqlChangingFunction`, so `mid`'s target can chain through any
* number of wrappers before reaching a primitive (`KeRaiseIrql`,
* `_IRQL_raises_`, etc.).
*
* 2. **AST-loop branch.** All three calls share an enclosing loop body
* in the same function. The back-edge makes any IRQL-changing call
* in the loop a real transition between save and restore on a
* subsequent iteration, including when restore is textually above
* save (which makes branch 1's range empty).
*
* We use source-line bracketing rather than CFG reachability because
* the extracted cpp CFG can drop forward edges across `if (call(...))`
* and similar boundaries, silently losing TPs. The AST relation in
* branch 2 is densely populated and avoids that gap.
*
* Caveat: `getPotentialExitIrqlAtCfn` doesn't always bind at save-call
* argument expressions inside loop bodies in current extracted DBs, so
* branch 2 can fire correctly while the upstream `irqlSource != irqlSink`
* still filters it out. Recovering those needs work in `Irql.qll`.
*
* Performance: `pragma[inline_late]` + `bindingset[saveCall, restoreCall]`
* specialize this per call site after dataflow has bound the endpoints,
* turning a codebase-wide enumeration of (save, restore) pairs into a
* per-pair check. Both annotations are planner hints; semantics unchanged.
*
* --- Human comments ---
*
* Branch (1) wound up like this for perf reasons as well; a transitive
* check across all of the helper's internals gets expensive and in practice
* if there are helper functions involved they're pretty shallow.
*/
bindingset[saveCall, restoreCall]
pragma[inline_late]
predicate irqlChangesBetween(FunctionCall saveCall, FunctionCall restoreCall) {
// Branch 1: source-line bracketing in a function `f` that anchors
// both calls (directly enclosing or one-level wrapper / common caller).
exists(Function f, int saveLine, int restoreLine, FunctionCall mid |
saveLine = anchorLineForCall(f, saveCall) and
restoreLine = anchorLineForCall(f, restoreCall) and
mid.getEnclosingFunction() = f and
isIrqlChangingCall(mid) and
mid.getLocation().getStartLine() >= saveLine and
mid.getLocation().getStartLine() <= restoreLine and
mid != saveCall and
mid != restoreCall
)
or
// Branch 2: all three calls live inside the body of the same loop in
// their shared enclosing function. The loop back-edge makes any
// IRQL-changing call in the body a real transition between save and
// restore on a subsequent iteration, even when source-line position
// would put restore before save.
exists(Function f, Loop l, FunctionCall mid |
f = saveCall.getEnclosingFunction() and
f = restoreCall.getEnclosingFunction() and
f = mid.getEnclosingFunction() and
isIrqlChangingCall(mid) and
mid != saveCall and
mid != restoreCall and
l.getStmt().getAChild*() = saveCall.getEnclosingStmt() and
l.getStmt().getAChild*() = restoreCall.getEnclosingStmt() and
l.getStmt().getAChild*() = mid.getEnclosingStmt()
)
}

from
DataFlow::Node source, DataFlow::Node sink, int irqlSink, int irqlSource,
FunctionCall saveCall, FunctionCall restoreCall
where
FloatStateFlow::flow(source, sink) and
// FloatStateFlow's source/sink predicates already restrict the flow's
// endpoints to be the first arguments of KeSaveFloatingPointState /
// KeRestoreFloatingPointState. The two arg(0) equalities below
// uniquely bind `saveCall` and `restoreCall` (each Expr has exactly
// one parent FunctionCall), without re-stating the function-name
// constraints.
source.asIndirectExpr() = saveCall.getArgument(0) and
sink.asIndirectExpr() = restoreCall.getArgument(0) and
irqlSource = getPotentialExitIrqlAtCfn(source.asIndirectExpr()) and
irqlSink = getPotentialExitIrqlAtCfn(sink.asIndirectExpr()) and
irqlSink != irqlSource
irqlSink != irqlSource and
// Only flag if there is an actual IRQL-changing call between the save
// and the restore (in source order), in either the directly enclosing
// function or a one-level wrapper / common-caller. If no IRQL-changing
// call exists between them, the IRQL is invariant within a single
// invocation and the mismatch is a may-analysis artifact from
// different hypothetical entry IRQLs.
irqlChangesBetween(saveCall, restoreCall)
select sink.asIndirectExpr(),
"The irql level where the floating-point state was saved (" + irqlSource +
") does not match the irql level for the restore operation (" + irqlSink + ")."
Loading
Loading