diff --git a/.github/workflows/powershell-pr-check.yml b/.github/workflows/powershell-pr-check.yml
new file mode 100644
index 000000000000..f3fa0a38a671
--- /dev/null
+++ b/.github/workflows/powershell-pr-check.yml
@@ -0,0 +1,28 @@
+name: PowerShell PR Check
+
+on:
+ pull_request:
+ branches:
+ - main
+ workflow_dispatch:
+
+jobs:
+ powershell-pr-check:
+ name: powershell-pr-check
+ runs-on: ubuntu-latest
+ if: github.repository == 'microsoft/codeql'
+ permissions:
+ contents: read
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ token: ${{ github.token }}
+ - name: Setup CodeQL
+ uses: ./.github/actions/fetch-codeql
+ with:
+ channel: release
+ - name: Compile PowerShell Queries
+ run: |
+ codeql query compile --check-only --keep-going powershell/ql/src
diff --git a/.github/workflows/sync-main-tags.yml b/.github/workflows/sync-main-tags.yml
new file mode 100644
index 000000000000..f27a112ed9b6
--- /dev/null
+++ b/.github/workflows/sync-main-tags.yml
@@ -0,0 +1,27 @@
+name: Sync Main Tags
+
+on:
+ pull_request:
+ types:
+ - closed
+ branches:
+ - main
+
+jobs:
+ sync-main-tags:
+ name: Sync Main Tags
+ runs-on: ubuntu-latest
+ if: github.repository == 'microsoft/codeql' && github.event.pull_request.merged == true && github.event.pull_request.head.ref == 'auto/sync-main-pr'
+ permissions:
+ contents: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Push Tags
+ run: |
+ git fetch upstream --tags --force
+ git push --force origin --tags
+ env:
+ GH_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
diff --git a/.github/workflows/sync-main.yml b/.github/workflows/sync-main.yml
new file mode 100644
index 000000000000..fa4d2cdeecd3
--- /dev/null
+++ b/.github/workflows/sync-main.yml
@@ -0,0 +1,88 @@
+name: Sync Main
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - .github/workflows/sync-main.yml
+ schedule:
+ - cron: '55 * * * *'
+
+jobs:
+ sync-main:
+ name: Sync-main
+ runs-on: ubuntu-latest
+ if: github.repository == 'microsoft/codeql'
+ permissions:
+ contents: write
+ pull-requests: write
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - name: Git config
+ shell: bash
+ run: |
+ git config user.name "dilanbhalla"
+ git config user.email "dilanbhalla@microsoft.com"
+ - name: Git checkout auto/sync-main-pr
+ shell: bash
+ run: |
+ git fetch origin
+ if git ls-remote --exit-code --heads origin auto/sync-main-pr > /dev/null; then
+ echo "Branch exists remotely. Checking it out."
+ git checkout -B auto/sync-main-pr origin/auto/sync-main-pr
+ else
+ echo "Branch does not exist remotely. Creating from main."
+ git checkout -B auto/sync-main-pr origin/main
+ git push -u origin auto/sync-main-pr
+ fi
+ - name: Sync origin/main
+ shell: bash
+ run: |
+ echo "::group::Sync with main branch"
+ git pull origin auto/sync-main-pr; exitCode=$?; if [ $exitCode -ne 0 ]; then exitCode=0; fi
+ git pull origin main --no-rebase
+ git push --force origin auto/sync-main-pr
+ echo "::endgroup::"
+ - name: Sync upstream/codeql-cli/latest
+ shell: bash
+ run: |
+ echo "::group::Set up remote"
+ git remote add upstream https://github.com/github/codeql.git
+ git fetch upstream --tags --force
+ echo "::endgroup::"
+ echo "::group::Merge codeql-cli/latest"
+ set -x
+ git merge codeql-cli/latest
+ set +x
+ echo "::endgroup::"
+ - name: Push sync branch
+ run: |
+ git push origin auto/sync-main-pr
+ env:
+ GH_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
+ - name: Create PR if it doesn't exist
+ shell: bash
+ run: |
+ pr_number=$(gh pr list --repo microsoft/codeql --head auto/sync-main-pr --base main --json number --jq '.[0].number')
+ if [ -n "$pr_number" ]; then
+ echo "PR from auto/sync-main-pr to main already exists (PR #$pr_number). Exiting gracefully."
+ else
+ if git fetch origin main auto/sync-main-pr && [ -n "$(git rev-list origin/main..origin/auto/sync-main-pr)" ]; then
+ echo "PR does not exist. Creating one..."
+ gh pr create --repo microsoft/codeql --fill -B main -H auto/sync-main-pr \
+ --label 'autogenerated' \
+ --title 'Sync Main (autogenerated)' \
+ --body "This PR syncs the latest changes from \`codeql-cli/latest\` into \`main\`." \
+ --reviewer 'MathiasVP'
+ --reviewer 'ropwareJB'
+ else
+ echo "No changes to sync from auto/sync-main-pr to main. Exiting gracefully."
+ fi
+ fi
+ env:
+ GH_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
diff --git a/README.md b/README.md
index 99433b8ca49f..b0f6c52bbdcd 100644
--- a/README.md
+++ b/README.md
@@ -29,3 +29,5 @@ You can install the [CodeQL for Visual Studio Code](https://marketplace.visualst
### Tasks
The `.vscode/tasks.json` file defines custom tasks specific to working in this repository. To invoke one of these tasks, select the `Terminal | Run Task...` menu option, and then select the desired task from the dropdown. You can also invoke the `Tasks: Run Task` command from the command palette.
+
+
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000000..e138ec5d6a77
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,41 @@
+
+
+## Security
+
+Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
+
+If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
+
+## Reporting Security Issues
+
+**Please do not report security vulnerabilities through public GitHub issues.**
+
+Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
+
+If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
+
+You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
+
+Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
+
+ * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
+ * Full paths of source file(s) related to the manifestation of the issue
+ * The location of the affected source code (tag/branch/commit or direct URL)
+ * Any special configuration required to reproduce the issue
+ * Step-by-step instructions to reproduce the issue
+ * Proof-of-concept or exploit code (if possible)
+ * Impact of the issue, including how an attacker might exploit the issue
+
+This information will help us triage your report more quickly.
+
+If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
+
+## Preferred Languages
+
+We prefer all communications to be in English.
+
+## Policy
+
+Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
+
+
diff --git a/cpp/ql/lib/change-notes/2023-10-12-additional-call-targets.md b/cpp/ql/lib/change-notes/2023-10-12-additional-call-targets.md
new file mode 100644
index 000000000000..f87fba1f1720
--- /dev/null
+++ b/cpp/ql/lib/change-notes/2023-10-12-additional-call-targets.md
@@ -0,0 +1,4 @@
+---
+category: feature
+---
+* Added a new class `AdditionalCallTarget` for specifying additional call targets.
diff --git a/cpp/ql/lib/experimental/cryptography/utils/OpenSSL/CryptoFunction.qll b/cpp/ql/lib/experimental/cryptography/utils/OpenSSL/CryptoFunction.qll
index 2c46a7c06744..a265241a36bd 100644
--- a/cpp/ql/lib/experimental/cryptography/utils/OpenSSL/CryptoFunction.qll
+++ b/cpp/ql/lib/experimental/cryptography/utils/OpenSSL/CryptoFunction.qll
@@ -115,6 +115,10 @@ private string normalizeFunctionName(Function f, string algType) {
(result.matches("RSA") implies not f.getName().toUpperCase().matches("%UNIVERSAL%")) and
//rsaz functions deemed to be too low level, and can be ignored
not f.getLocation().getFile().getBaseName().matches("rsaz_exp.c") and
+ // SHA false positives
+ (result.matches("SHA") implies not f.getName().toUpperCase().matches("%SHAKE%")) and
+ // CAST false positives
+ (result.matches("CAST") implies not f.getName().toUpperCase().matches(["%UPCAST%", "%DOWNCAST%"])) and
// General False positives
// Functions that 'get' do not set an algorithm, and therefore are considered ignorable
not f.getName().toLowerCase().matches("%get%")
diff --git a/cpp/ql/lib/semmle/code/cpp/models/implementations/Iterator.qll b/cpp/ql/lib/semmle/code/cpp/models/implementations/Iterator.qll
index 3a93188e9ca6..d6abbf771114 100644
--- a/cpp/ql/lib/semmle/code/cpp/models/implementations/Iterator.qll
+++ b/cpp/ql/lib/semmle/code/cpp/models/implementations/Iterator.qll
@@ -26,6 +26,14 @@ private class IteratorTraits extends Class {
}
Type getIteratorType() { result = this.getTemplateArgument(0) }
+
+ Type getValueType() {
+ exists(TypedefType t |
+ this.getAMember() = t and
+ t.getName() = "value_type" and
+ result = t.getUnderlyingType()
+ )
+ }
}
/**
@@ -34,16 +42,13 @@ private class IteratorTraits extends Class {
*/
private class IteratorByTraits extends Iterator {
IteratorTraits trait;
+ IteratorByTraits() {
+ trait.getIteratorType() = this and
+ not trait.getValueType() = this
+ }
- IteratorByTraits() { trait.getIteratorType() = this }
+ override Type getValueType() { result = trait.getValueType() }
- override Type getValueType() {
- exists(TypedefType t |
- trait.getAMember() = t and
- t.getName() = "value_type" and
- result = t.getUnderlyingType()
- )
- }
}
/**
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/Adding365DaysPerYear.qhelp b/cpp/ql/src/Likely Bugs/Leap Year/Adding365DaysPerYear.qhelp
index 186ec8079944..f0d303a05787 100644
--- a/cpp/ql/src/Likely Bugs/Leap Year/Adding365DaysPerYear.qhelp
+++ b/cpp/ql/src/Likely Bugs/Leap Year/Adding365DaysPerYear.qhelp
@@ -11,7 +11,7 @@ It is not safe to assume that a year is 365 days long.
Determine whether the time span in question contains a leap day, then perform the calculation using the correct number
-of days. Alternatively, use an established library routine that already contains correct leap year logic.
+of days. Alternatively, use an established library routine that already contains correct leap year logic.
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/Adding365DaysPerYear.ql b/cpp/ql/src/Likely Bugs/Leap Year/Adding365DaysPerYear.ql
index 71aa97c0ae56..341d014dae7d 100644
--- a/cpp/ql/src/Likely Bugs/Leap Year/Adding365DaysPerYear.ql
+++ b/cpp/ql/src/Likely Bugs/Leap Year/Adding365DaysPerYear.ql
@@ -4,8 +4,8 @@
* value of 365, it may be a sign that leap years are not taken
* into account.
* @kind problem
- * @problem.severity warning
- * @id cpp/leap-year/adding-365-days-per-year
+ * @problem.severity error
+ * @id cpp/microsoft/public/leap-year/adding-365-days-per-year
* @precision medium
* @tags leap-year
* correctness
@@ -13,11 +13,13 @@
import cpp
import LeapYear
+import semmle.code.cpp.dataflow.new.DataFlow
from Expr source, Expr sink
where
PossibleYearArithmeticOperationCheckFlow::flow(DataFlow::exprNode(source),
DataFlow::exprNode(sink))
select sink,
- "An arithmetic operation $@ that uses a constant value of 365 ends up modifying this date/time, without considering leap year scenarios.",
- source, source.toString()
+ "$@: This arithmetic operation $@ uses a constant value of 365 ends up modifying the date/time located at $@, without considering leap year scenarios.",
+ sink.getEnclosingFunction(), sink.getEnclosingFunction().toString(), source, source.toString(),
+ sink, sink.toString()
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck.ql b/cpp/ql/src/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck.ql
new file mode 100644
index 000000000000..7a2cb9b04df4
--- /dev/null
+++ b/cpp/ql/src/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck.ql
@@ -0,0 +1,17 @@
+/**
+ * @name Leap Year Invalid Check (AntiPattern 5)
+ * @description An expression is used to check a year is presumably a leap year, but the conditions used are insufficient.
+ * @kind problem
+ * @problem.severity warning
+ * @id cpp/microsoft/public/leap-year/invalid-leap-year-check
+ * @precision medium
+ * @tags leap-year
+ * correctness
+ */
+
+import cpp
+import LeapYear
+
+from Mod4CheckedExpr exprMod4
+where not exists(ExprCheckLeapYear lyCheck | lyCheck.getAChild*() = exprMod4)
+select exprMod4, "Possible Insufficient Leap Year check (AntiPattern 5)"
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll b/cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll
index 3cff86412e49..d3b53bf8ff9b 100644
--- a/cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll
+++ b/cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll
@@ -3,7 +3,7 @@
*/
import cpp
-import semmle.code.cpp.ir.dataflow.TaintTracking
+import semmle.code.cpp.dataflow.new.TaintTracking
import semmle.code.cpp.commons.DateTime
/**
@@ -41,6 +41,271 @@ class CheckForLeapYearOperation extends Expr {
}
}
+bindingset[modVal]
+Expr moduloCheckEQ_0(EQExpr eq, int modVal) {
+ exists(RemExpr rem | rem = eq.getLeftOperand() |
+ result = rem.getLeftOperand() and
+ rem.getRightOperand().getValue().toInt() = modVal
+ ) and
+ eq.getRightOperand().getValue().toInt() = 0
+}
+
+bindingset[modVal]
+Expr moduloCheckNEQ_0(NEExpr neq, int modVal) {
+ exists(RemExpr rem | rem = neq.getLeftOperand() |
+ result = rem.getLeftOperand() and
+ rem.getRightOperand().getValue().toInt() = modVal
+ ) and
+ neq.getRightOperand().getValue().toInt() = 0
+}
+
+/**
+ * Returns if the two expressions resolve to the same value, albeit it is a fuzzy attempt.
+ * SSA is not fit for purpose here as calls break SSA equivalence.
+ */
+predicate exprEq_propertyPermissive(Expr e1, Expr e2) {
+ not e1 = e2 and
+ (
+ DataFlow::localFlow(DataFlow::exprNode(e1), DataFlow::exprNode(e2))
+ or
+ if e1 instanceof ThisExpr and e2 instanceof ThisExpr
+ then any()
+ else
+ /* If it's a direct Access, check that the target is the same. */
+ if e1 instanceof Access
+ then e1.(Access).getTarget() = e2.(Access).getTarget()
+ else
+ /* If it's a Call, compare qualifiers and only permit no-argument Calls. */
+ if e1 instanceof Call
+ then
+ e1.(Call).getTarget() = e2.(Call).getTarget() and
+ e1.(Call).getNumberOfArguments() = 0 and
+ e2.(Call).getNumberOfArguments() = 0 and
+ if e1.(Call).hasQualifier()
+ then exprEq_propertyPermissive(e1.(Call).getQualifier(), e2.(Call).getQualifier())
+ else any()
+ else
+ /* If it's a binaryOperation, compare op and recruse */
+ if e1 instanceof BinaryOperation
+ then
+ e1.(BinaryOperation).getOperator() = e2.(BinaryOperation).getOperator() and
+ exprEq_propertyPermissive(e1.(BinaryOperation).getLeftOperand(),
+ e2.(BinaryOperation).getLeftOperand()) and
+ exprEq_propertyPermissive(e1.(BinaryOperation).getRightOperand(),
+ e2.(BinaryOperation).getRightOperand())
+ else
+ // Otherwise fail (and permit the raising of a finding)
+ if e1 instanceof Literal
+ then e1.(Literal).getValue() = e2.(Literal).getValue()
+ else none()
+ )
+}
+
+/**
+ * An expression that is the subject of a mod-4 check.
+ * ie `expr % 4 == 0`
+ */
+class Mod4CheckedExpr extends Expr {
+ Mod4CheckedExpr() { exists(Expr e | e = moduloCheckEQ_0(this, 4)) }
+}
+
+/**
+ * Year Div of 100 not equal to 0:
+ * - `year % 100 != 0`
+ * - `!(year % 100 == 0)`
+ */
+abstract class ExprCheckCenturyComponentDiv100 extends Expr {
+ abstract Expr getYearExpr();
+}
+
+/**
+ * The normal form of the expression `year % 100 != 0`.
+ */
+final class ExprCheckCenturyComponentDiv100Normative extends ExprCheckCenturyComponentDiv100 {
+ ExprCheckCenturyComponentDiv100Normative() { exists(moduloCheckNEQ_0(this, 100)) }
+
+ override Expr getYearExpr() { result = moduloCheckNEQ_0(this, 100) }
+}
+
+/**
+ * The inverted form of the expression `year % 100 != 0`, ie `!(year % 100 == 0)`
+ */
+final class ExprCheckCenturyComponentDiv100Inverted extends ExprCheckCenturyComponentDiv100, NotExpr
+{
+ ExprCheckCenturyComponentDiv100Inverted() { exists(moduloCheckEQ_0(this.getOperand(), 100)) }
+
+ override Expr getYearExpr() { result = moduloCheckEQ_0(this.getOperand(), 100) }
+}
+
+/**
+ * A check that an expression is divisible by 400 or not
+ * - `(year % 400 == 0)`
+ * - `!(year % 400 != 0)`
+ */
+abstract class ExprCheckCenturyComponentDiv400 extends Expr {
+ abstract Expr getYearExpr();
+}
+
+/**
+ * The normative form of expression is divisible by 400:
+ * ie `year % 400 == 0`
+ */
+final class ExprCheckCenturyComponentDiv400Normative extends ExprCheckCenturyComponentDiv400 {
+ ExprCheckCenturyComponentDiv400Normative() { exists(moduloCheckEQ_0(this, 400)) }
+
+ override Expr getYearExpr() {
+ exists(Expr e |
+ e = moduloCheckEQ_0(this, 400) and
+ (
+ if e instanceof ConvertedYearByOffset
+ then result = e.(ConvertedYearByOffset).getYearOperand()
+ else result = e
+ )
+ )
+ }
+}
+
+/**
+ * An arithmetic operation that seemingly converts an operand between time formats.
+ */
+class ConvertedYearByOffset extends BinaryArithmeticOperation {
+ ConvertedYearByOffset() {
+ this.getAnOperand().getValue().toInt() instanceof TimeFormatConversionOffset
+ }
+
+ Expr getYearOperand() {
+ this.getLeftOperand().getValue().toInt() instanceof TimeFormatConversionOffset and
+ result = this.getRightOperand()
+ or
+ this.getRightOperand().getValue().toInt() instanceof TimeFormatConversionOffset and
+ result = this.getLeftOperand()
+ }
+}
+
+/**
+ * A flow configuration to track DataFlow from a `CovertedYearByOffset` to some `StructTmLeapYearFieldAccess`.
+ */
+module LocalConvertedYearByOffsetToLeapYearCheckFlowConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node n) { not n.asExpr() instanceof ConvertedYearByOffset }
+
+ predicate isSink(DataFlow::Node n) { n.asExpr() instanceof StructTmLeapYearFieldAccess }
+}
+
+module LocalConvertedYearByOffsetToLeapYearCheckFlow =
+ DataFlow::Global;
+
+/**
+ * The set of ints (or strings) which represent a value that is typically used to convert between time data types.
+ */
+final class TimeFormatConversionOffset extends int {
+ TimeFormatConversionOffset() {
+ this =
+ [
+ 1900, // tm_year represents years since 1900
+ 1970, // converting from/to Unix epoch
+ 2000, // some systems may use 2000 for 2-digit year conversions
+ ]
+ }
+}
+
+/**
+ * The inverted form of expression is divisible by 400:
+ * ie `!(year % 400 != 0)`
+ */
+final class ExprCheckCenturyComponentDiv400Inverted extends ExprCheckCenturyComponentDiv400, NotExpr
+{
+ ExprCheckCenturyComponentDiv400Inverted() { exists(moduloCheckNEQ_0(this.getOperand(), 400)) }
+
+ override Expr getYearExpr() { result = moduloCheckNEQ_0(this.getOperand(), 400) }
+}
+
+/**
+ * The Century component of a Leap-Year guard
+ */
+class ExprCheckCenturyComponent extends LogicalOrExpr {
+ ExprCheckCenturyComponent() {
+ exists(ExprCheckCenturyComponentDiv400 exprDiv400, ExprCheckCenturyComponentDiv100 exprDiv100 |
+ this.getAnOperand() = exprDiv100 and
+ this.getAnOperand() = exprDiv400 and
+ exprEq_propertyPermissive(exprDiv100.getYearExpr(), exprDiv400.getYearExpr())
+ )
+ }
+
+ Expr getYearExpr() {
+ exists(ExprCheckCenturyComponentDiv400 exprDiv400 |
+ this.getAnOperand() = exprDiv400 and
+ result = exprDiv400.getYearExpr()
+ )
+ }
+}
+
+/**
+ * A **Valid** Leap year check expression.
+ */
+abstract class ExprCheckLeapYear extends Expr { }
+
+/**
+ * A valid Leap-Year guard expression of the following form:
+ * `dt.Year % 4 == 0 && (dt.Year % 100 != 0 || dt.Year % 400 == 0)`
+ */
+final class ExprCheckLeapYearFormA extends ExprCheckLeapYear, LogicalAndExpr {
+ ExprCheckLeapYearFormA() {
+ exists(Expr e, ExprCheckCenturyComponent centuryCheck |
+ e = moduloCheckEQ_0(this.getLeftOperand(), 4) and
+ centuryCheck = this.getAnOperand().getAChild*() and
+ exprEq_propertyPermissive(e, centuryCheck.getYearExpr())
+ )
+ }
+}
+
+/**
+ * A valid Leap-Year guard expression of the following forms:
+ * `year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)`
+ * `(year + 1900) % 400 == 0 || (year % 100 != 0 && year % 4 == 0)`
+ */
+final class ExprCheckLeapYearFormB extends ExprCheckLeapYear, LogicalOrExpr {
+ ExprCheckLeapYearFormB() {
+ exists(VariableAccess va1, VariableAccess va2, VariableAccess va3 |
+ va1 = moduloCheckEQ_0(this.getAnOperand(), 400) and
+ va2 = moduloCheckNEQ_0(this.getAnOperand().(LogicalAndExpr).getAnOperand(), 100) and
+ va3 = moduloCheckEQ_0(this.getAnOperand().(LogicalAndExpr).getAnOperand(), 4) and
+ // The 400-leap year check may be offset by [1900,1970,2000].
+ exists(Expr va1_subExpr | va1_subExpr = va1.getAChild*() |
+ exprEq_propertyPermissive(va1_subExpr, va2) and
+ exprEq_propertyPermissive(va2, va3)
+ )
+ )
+ }
+}
+
+Expr leapYearOpEnclosingElement(CheckForLeapYearOperation op) { result = op.getEnclosingElement() }
+
+/**
+ * A value that resolves as a constant integer that represents some normalization or conversion between date types.
+ */
+pragma[inline]
+private predicate isNormalizationPrimitiveValue(Expr e) {
+ e.getValue().toInt() = [1900, 2000, 1980, 80]
+}
+
+/**
+ * A normalization operation is an expression that is merely attempting to convert between two different datetime schemes,
+ * and does not apply any additional mutation to the represented value.
+ */
+pragma[inline]
+predicate isNormalizationOperation(Expr e) {
+ isNormalizationPrimitiveValue([e, e.(Operation).getAChild()])
+ or
+ // Special case for transforming marshaled 2-digit year date:
+ // theTime.wYear += 100*value;
+ e.(Operation).getAChild().(MulExpr).getValue().toInt() = 100
+}
+
+/**
+ * Get the field accesses used in a `ExprCheckLeapYear` expression.
+ */
+LeapYearFieldAccess leapYearCheckFieldAccess(ExprCheckLeapYear a) { result = a.getAChild*() }
+
/**
* A `YearFieldAccess` that would represent an access to a year field on a struct and is used for arguing about leap year calculations.
*/
@@ -73,48 +338,7 @@ abstract class LeapYearFieldAccess extends YearFieldAccess {
this.isModified() and
exists(Operation op |
op.getAnOperand() = this and
- (
- op instanceof AssignArithmeticOperation and
- not (
- op.getAChild().getValue().toInt() = 1900
- or
- op.getAChild().getValue().toInt() = 2000
- or
- op.getAChild().getValue().toInt() = 1980
- or
- op.getAChild().getValue().toInt() = 80
- or
- // Special case for transforming marshaled 2-digit year date:
- // theTime.wYear += 100*value;
- exists(MulExpr mulBy100 | mulBy100 = op.getAChild() |
- mulBy100.getAChild().getValue().toInt() = 100
- )
- )
- or
- exists(BinaryArithmeticOperation bao |
- bao = op.getAnOperand() and
- // we're specifically interested in calculations that update the existing
- // value (like `x = x + 1`), so look for a child `YearFieldAccess`.
- bao.getAChild*() instanceof YearFieldAccess and
- not (
- bao.getAChild().getValue().toInt() = 1900
- or
- bao.getAChild().getValue().toInt() = 2000
- or
- bao.getAChild().getValue().toInt() = 1980
- or
- bao.getAChild().getValue().toInt() = 80
- or
- // Special case for transforming marshaled 2-digit year date:
- // theTime.wYear += 100*value;
- exists(MulExpr mulBy100 | mulBy100 = op.getAChild() |
- mulBy100.getAChild().getValue().toInt() = 100
- )
- )
- )
- or
- op instanceof CrementOperation
- )
+ not isNormalizationOperation(op)
)
}
@@ -155,9 +379,7 @@ abstract class LeapYearFieldAccess extends YearFieldAccess {
// but these centurial years are leap years if they are exactly divisible by 400
//
// https://aa.usno.navy.mil/faq/docs/calendars.php
- this.isUsedInMod4Operation() and
- this.additionalModulusCheckForLeapYear(400) and
- this.additionalModulusCheckForLeapYear(100)
+ this = leapYearCheckFieldAccess(_)
}
}
@@ -175,19 +397,9 @@ class StructTmLeapYearFieldAccess extends LeapYearFieldAccess {
StructTmLeapYearFieldAccess() { this.getTarget().getName() = "tm_year" }
override predicate isUsedInCorrectLeapYearCheck() {
- this.isUsedInMod4Operation() and
- this.additionalModulusCheckForLeapYear(400) and
- this.additionalModulusCheckForLeapYear(100) and
- // tm_year represents years since 1900
- (
- this.additionalAdditionOrSubstractionCheckForLeapYear(1900)
- or
- // some systems may use 2000 for 2-digit year conversions
- this.additionalAdditionOrSubstractionCheckForLeapYear(2000)
- or
- // converting from/to Unix epoch
- this.additionalAdditionOrSubstractionCheckForLeapYear(1970)
- )
+ this = leapYearCheckFieldAccess(_) and
+ /* There is some data flow from some conversion arithmetic to this expression. */
+ LocalConvertedYearByOffsetToLeapYearCheckFlow::flow(_, DataFlow::exprNode(this))
}
}
@@ -206,10 +418,10 @@ class ChecksForLeapYearFunctionCall extends FunctionCall {
}
/**
- * Data flow configuration for finding a variable access that would flow into
+ * A `DataFlow` configuraiton for finding a variable access that would flow into
* a function call that includes an operation to check for leap year.
*/
-private module LeapYearCheckConfig implements DataFlow::ConfigSig {
+private module LeapYearCheckFlowConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { source.asExpr() instanceof VariableAccess }
predicate isSink(DataFlow::Node sink) {
@@ -217,11 +429,10 @@ private module LeapYearCheckConfig implements DataFlow::ConfigSig {
}
}
-module LeapYearCheckFlow = DataFlow::Global;
+module LeapYearCheckFlow = DataFlow::Global;
/**
- * Data flow configuration for finding an operation with hardcoded 365 that will flow into
- * a `FILEINFO` field.
+ * A `DataFlow` configuration for finding an operation with hardcoded 365 that will flow into a `_FILETIME` field.
*/
private module FiletimeYearArithmeticOperationCheckConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
@@ -246,46 +457,72 @@ module FiletimeYearArithmeticOperationCheckFlow =
DataFlow::Global;
/**
- * Taint configuration for finding an operation with hardcoded 365 that will flow into any known date/time field.
+ * A `DataFlow` configuration for finding an operation with hardcoded 365 that will flow into any known date/time field.
*/
private module PossibleYearArithmeticOperationCheckConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) {
- exists(Operation op | op = source.asExpr() |
- op.getAChild*().getValue().toInt() = 365 and
- (
- not op.getParent() instanceof Expr or
- op.getParent() instanceof Assignment
- )
- )
- }
-
- predicate isBarrierIn(DataFlow::Node node) { isSource(node) }
-
- predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
- // flow from anything on the RHS of an assignment to a time/date structure to that
- // assignment.
- exists(StructLikeClass dds, FieldAccess fa, Assignment aexpr, Expr e |
- e = node1.asExpr() and
- fa = node2.asExpr()
- |
- (dds instanceof PackedTimeType or dds instanceof UnpackedTimeType) and
- fa.getQualifier().getUnderlyingType() = dds and
- aexpr.getLValue() = fa and
- aexpr.getRValue().getAChild*() = e
- )
+ // NOTE: addressing current issue with new IR dataflow, where
+ // constant folding occurs before dataflow nodes are associated
+ // with the constituent literals.
+ source.asExpr().getAChild*().getValue().toInt() = 365 and
+ not exists(DataFlow::Node parent | parent.asExpr().getAChild+() = source.asExpr())
}
predicate isSink(DataFlow::Node sink) {
exists(StructLikeClass dds, FieldAccess fa, AssignExpr aexpr |
- aexpr.getRValue() = sink.asExpr()
- |
(dds instanceof PackedTimeType or dds instanceof UnpackedTimeType) and
fa.getQualifier().getUnderlyingType() = dds and
fa.isModified() and
- aexpr.getLValue() = fa
+ aexpr.getLValue() = fa and
+ sink.asExpr() = aexpr.getRValue()
)
}
}
module PossibleYearArithmeticOperationCheckFlow =
TaintTracking::Global;
+
+/**
+ * A `YearFieldAccess` that is modifying the year by any arithmetic operation.
+ *
+ * NOTE:
+ * To change this class to work for general purpose date transformations that do not check the return value,
+ * make the following changes:
+ * - change `extends LeapYearFieldAccess` to `extends FieldAccess`.
+ * - change `this.isModifiedByArithmeticOperation()` to `this.isModified()`.
+ * Expect a lower precision for a general purpose version.
+ */
+class DateStructModifiedFieldAccess extends LeapYearFieldAccess {
+ DateStructModifiedFieldAccess() {
+ exists(Field f, StructLikeClass struct |
+ f.getAnAccess() = this and
+ struct.getAField() = f and
+ struct.getUnderlyingType() instanceof UnpackedTimeType and
+ this.isModifiedByArithmeticOperation()
+ )
+ }
+}
+
+/**
+ * This is a list of APIs that will get the system time, and therefore guarantee that the value is valid.
+ */
+class SafeTimeGatheringFunction extends Function {
+ SafeTimeGatheringFunction() {
+ this.getQualifiedName() = ["GetFileTime", "GetSystemTime", "NtQuerySystemTime"]
+ }
+}
+
+/**
+ * This list of APIs should check for the return value to detect problems during the conversion.
+ */
+class TimeConversionFunction extends Function {
+ TimeConversionFunction() {
+ this.getQualifiedName() =
+ [
+ "FileTimeToSystemTime", "SystemTimeToFileTime", "SystemTimeToTzSpecificLocalTime",
+ "SystemTimeToTzSpecificLocalTimeEx", "TzSpecificLocalTimeToSystemTime",
+ "TzSpecificLocalTimeToSystemTimeEx", "RtlLocalTimeToSystemTime",
+ "RtlTimeToSecondsSince1970", "_mkgmtime"
+ ]
+ }
+}
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/LeapYearConditionalLogic.qhelp b/cpp/ql/src/Likely Bugs/Leap Year/LeapYearConditionalLogic.qhelp
new file mode 100644
index 000000000000..4c94585477ae
--- /dev/null
+++ b/cpp/ql/src/Likely Bugs/Leap Year/LeapYearConditionalLogic.qhelp
@@ -0,0 +1,26 @@
+
+
+
+
+ This anti-pattern occurs when a developer uses conditional logic to execute a different path of code for a leap year than for a common year, without fully testing both code paths.
+ Though using a framework or library's leap year function is better than manually calculating the leap year (as described in anti-pattern 5), it can still be a source of errors if the result is used to execute a different code path. The bug is especially easy to be masked if the year is derived from the current time of the system clock. See Prevention Measures for techniques to avoid this bug.
+
+
+
+ - Avoid using conditional logic that creates a separate branch in your code for leap year.
+ - Ensure your code is testable, and test how it will behave when presented with leap year dates of February 29th and December 31st as inputs.
+
+
+
+Note in the following examples, that year, month, and day might instead be .wYear, .wMonth, and .wDay fields of a SYSTEMTIME structure, or might be .tm_year, .tm_mon, and .tm_mday fields of a struct tm.
+
+
+
+
+ NASA / Goddard Space Flight Center - Calendars
+ Wikipedia - Leap year bug
+ Microsoft Azure blog - Is your code ready for the leap year?
+
+
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/LeapYearConditionalLogic.ql b/cpp/ql/src/Likely Bugs/Leap Year/LeapYearConditionalLogic.ql
new file mode 100644
index 000000000000..43c8690c591a
--- /dev/null
+++ b/cpp/ql/src/Likely Bugs/Leap Year/LeapYearConditionalLogic.ql
@@ -0,0 +1,28 @@
+/**
+ * @name Leap Year Conditional Logic (AntiPattern 7)
+ * @description Conditional logic is present for leap years and common years, potentially leading to untested code pathways.
+ * @kind problem
+ * @problem.severity warning
+ * @id cpp/microsoft/public/leap-year/conditional-logic-branches
+ * @precision medium
+ * @tags leap-year
+ * correctness
+ */
+
+import cpp
+import LeapYear
+import semmle.code.cpp.dataflow.new.DataFlow
+
+class IfStmtLeapYearCheck extends IfStmt {
+ IfStmtLeapYearCheck() {
+ this.hasElse() and
+ exists(ExprCheckLeapYear lyCheck, DataFlow::Node source, DataFlow::Node sink |
+ source.asExpr() = lyCheck and
+ sink.asExpr() = this.getCondition() and
+ DataFlow::localFlow(source, sink)
+ )
+ }
+}
+
+from IfStmtLeapYearCheck lyCheckIf
+select lyCheckIf, "Leap Year conditional statement may have untested code paths"
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.qhelp b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.qhelp
index 8fbe7933201b..b708e127767c 100644
--- a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.qhelp
+++ b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.qhelp
@@ -15,10 +15,10 @@
In this example, we are adding 1 year to the current date. This may work most of the time, but on any given February 29th, the resulting value will be invalid.
-
+
To fix this bug, check the result for leap year.
-
+
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
index 03570b3611cd..18ad003eb71f 100644
--- a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
+++ b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
@@ -1,9 +1,9 @@
/**
- * @name Year field changed using an arithmetic operation without checking for leap year
+ * @name Year field changed using an arithmetic operation without checking for leap year (AntiPattern 1)
* @description A field that represents a year is being modified by an arithmetic operation, but no proper check for leap years can be detected afterwards.
* @kind problem
* @problem.severity warning
- * @id cpp/leap-year/unchecked-after-arithmetic-year-modification
+ * @id cpp/microsoft/public/leap-year/unchecked-after-arithmetic-year-modification
* @precision medium
* @tags leap-year
* correctness
@@ -12,13 +12,16 @@
import cpp
import LeapYear
-from Variable var, LeapYearFieldAccess yfa
-where
- exists(VariableAccess va |
+/**
+ * Holds if there is no known leap-year verification for the given `YearWriteOp`.
+ * Binds the `var` argument to the qualifier of the `ywo` argument.
+ */
+bindingset[ywo]
+predicate isYearModifedWithoutExplicitLeapYearCheck(Variable var, YearWriteOp ywo) {
+ exists(VariableAccess va, YearFieldAccess yfa |
+ yfa = ywo.getYearAccess() and
yfa.getQualifier() = va and
var.getAnAccess() = va and
- // The year is modified with an arithmetic operation. Avoid values that are likely false positives
- yfa.isModifiedByArithmeticOperationNotForNormalization() and
// Avoid false positives
not (
// If there is a local check for leap year after the modification
@@ -41,8 +44,10 @@ where
LeapYearCheckFlow::flow(DataFlow::exprNode(yfacheck), DataFlow::exprNode(fc.getAnArgument()))
)
or
- // If there is a successor or predecessor that sets the month = 1
- exists(MonthFieldAccess mfa, AssignExpr ae |
+ // If there is a successor or predecessor that sets the month or day to a fixed value
+ exists(FieldAccess mfa, AssignExpr ae, int val |
+ mfa instanceof MonthFieldAccess or mfa instanceof DayFieldAccess
+ |
mfa.getQualifier() = var.getAnAccess() and
mfa.isModified() and
(
@@ -50,10 +55,87 @@ where
yfa.getBasicBlock() = mfa.getBasicBlock().getASuccessor+()
) and
ae = mfa.getEnclosingElement() and
- ae.getAnOperand().getValue().toInt() = 1
+ ae.getAnOperand().getValue().toInt() = val
)
)
)
-select yfa,
- "Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found.",
- yfa.getTarget(), yfa.getTarget().toString(), var, var.toString()
+}
+
+/**
+ * The set of all write operations to the Year field of a date struct.
+ */
+abstract class YearWriteOp extends Operation {
+ /** Extracts the access to the Year field */
+ abstract YearFieldAccess getYearAccess();
+
+ /** Get the expression which represents the new value. */
+ abstract Expr getMutationExpr();
+}
+
+/**
+ * A unary operation (Crement) performed on a Year field.
+ */
+class YearWriteOpUnary extends YearWriteOp, UnaryOperation {
+ YearWriteOpUnary() { this.getOperand() instanceof YearFieldAccess }
+
+ override YearFieldAccess getYearAccess() { result = this.getOperand() }
+
+ override Expr getMutationExpr() { result = this }
+}
+
+/**
+ * An assignment operation or mutation on the Year field of a date object.
+ */
+class YearWriteOpAssignment extends YearWriteOp, Assignment {
+ YearWriteOpAssignment() { this.getLValue() instanceof YearFieldAccess }
+
+ override YearFieldAccess getYearAccess() { result = this.getLValue() }
+
+ override Expr getMutationExpr() {
+ // Note: may need to use DF analysis to pull out the original value,
+ // if there is excessive false positives.
+ if this.getOperator() = "="
+ then
+ exists(DataFlow::Node source, DataFlow::Node sink |
+ sink.asExpr() = this.getRValue() and
+ OperationToYearAssignmentFlow::flow(source, sink) and
+ result = source.asExpr()
+ )
+ else result = this
+ }
+}
+
+/**
+ * A DataFlow configuration for identifying flows from some non trivial access or literal
+ * to the Year field of a date object.
+ */
+module OperationToYearAssignmentConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node n) {
+ not n.asExpr() instanceof Access and
+ not n.asExpr() instanceof Literal
+ }
+
+ predicate isSink(DataFlow::Node n) {
+ exists(Assignment a |
+ a.getLValue() instanceof YearFieldAccess and
+ a.getRValue() = n.asExpr()
+ )
+ }
+}
+
+module OperationToYearAssignmentFlow = DataFlow::Global;
+
+from Variable var, YearWriteOp ywo, Expr mutationExpr
+where
+ mutationExpr = ywo.getMutationExpr() and
+ isYearModifedWithoutExplicitLeapYearCheck(var, ywo) and
+ not isNormalizationOperation(mutationExpr) and
+ not ywo instanceof AddressOfExpr and
+ not exists(Call c, TimeConversionFunction f | f.getACallToThisFunction() = c |
+ c.getAnArgument().getAChild*() = var.getAnAccess() and
+ ywo.getASuccessor*() = c
+ )
+select ywo,
+ "$@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found.",
+ ywo.getEnclosingFunction(), ywo.getEnclosingFunction().toString(),
+ ywo.getYearAccess().getTarget(), ywo.getYearAccess().getTarget().toString(), var, var.toString()
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedReturnValueForTimeFunctions.qhelp b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedReturnValueForTimeFunctions.qhelp
index 6be0e091caf3..f3c4822632fb 100644
--- a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedReturnValueForTimeFunctions.qhelp
+++ b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedReturnValueForTimeFunctions.qhelp
@@ -27,10 +27,10 @@
In this example, we are adding 1 year to the current date. This may work most of the time, but on any given February 29th, the resulting value will be invalid.
-
+
To fix this bug, you must verify the return value for SystemTimeToFileTime
and handle any potential error accordingly.
-
+
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedReturnValueForTimeFunctions.ql b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedReturnValueForTimeFunctions.ql
index af02a2814a20..b223080fb6b3 100644
--- a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedReturnValueForTimeFunctions.ql
+++ b/cpp/ql/src/Likely Bugs/Leap Year/UncheckedReturnValueForTimeFunctions.ql
@@ -1,11 +1,11 @@
/**
- * @name Unchecked return value for time conversion function
+ * @name Unchecked return value for time conversion function (AntiPattern 6)
* @description When the return value of a fallible time conversion function is
* not checked for failure, its output parameters may contain
* invalid dates.
* @kind problem
* @problem.severity warning
- * @id cpp/leap-year/unchecked-return-value-for-time-conversion-function
+ * @id cpp/microsoft/public/leap-year/unchecked-return-value-for-time-conversion-function
* @precision medium
* @tags leap-year
* correctness
@@ -14,51 +14,6 @@
import cpp
import LeapYear
-/**
- * A `YearFieldAccess` that is modifying the year by any arithmetic operation.
- *
- * NOTE:
- * To change this class to work for general purpose date transformations that do not check the return value,
- * make the following changes:
- * - change `extends LeapYearFieldAccess` to `extends FieldAccess`.
- * - change `this.isModifiedByArithmeticOperation()` to `this.isModified()`.
- * Expect a lower precision for a general purpose version.
- */
-class DateStructModifiedFieldAccess extends LeapYearFieldAccess {
- DateStructModifiedFieldAccess() {
- exists(Field f, StructLikeClass struct |
- f.getAnAccess() = this and
- struct.getAField() = f and
- struct.getUnderlyingType() instanceof UnpackedTimeType and
- this.isModifiedByArithmeticOperation()
- )
- }
-}
-
-/**
- * This is a list of APIs that will get the system time, and therefore guarantee that the value is valid.
- */
-class SafeTimeGatheringFunction extends Function {
- SafeTimeGatheringFunction() {
- this.getQualifiedName() = ["GetFileTime", "GetSystemTime", "NtQuerySystemTime"]
- }
-}
-
-/**
- * This list of APIs should check for the return value to detect problems during the conversion.
- */
-class TimeConversionFunction extends Function {
- TimeConversionFunction() {
- this.getQualifiedName() =
- [
- "FileTimeToSystemTime", "SystemTimeToFileTime", "SystemTimeToTzSpecificLocalTime",
- "SystemTimeToTzSpecificLocalTimeEx", "TzSpecificLocalTimeToSystemTime",
- "TzSpecificLocalTimeToSystemTimeEx", "RtlLocalTimeToSystemTime",
- "RtlTimeToSecondsSince1970", "_mkgmtime"
- ]
- }
-}
-
from FunctionCall fcall, TimeConversionFunction trf, Variable var
where
fcall = trf.getACallToThisFunction() and
@@ -104,5 +59,6 @@ where
)
)
select fcall,
- "Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe.",
- trf, trf.getQualifiedName().toString(), var, var.getName()
+ "$@: Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe.",
+ fcall.getEnclosingFunction(), fcall.getEnclosingFunction().toString(), trf,
+ trf.getQualifiedName().toString(), var, var.getName()
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear.qhelp b/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear.qhelp
index d2c0375f0afc..03a8ff6216bb 100644
--- a/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear.qhelp
+++ b/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear.qhelp
@@ -16,15 +16,15 @@
In this example, we are allocating 365 integers, one for each day of the year. This code will fail on a leap year, when there are 366 days.
-
+
When using arrays, allocate the correct number of elements to match the year.
-
+
NASA / Goddard Space Flight Center - Calendars
- Wikipedia - Leap year bug
+ Wikipedia - Leap year bug
Microsoft Azure blog - Is your code ready for the leap year?
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear.ql b/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear.ql
index b27db937b577..72aa653c4dff 100644
--- a/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear.ql
+++ b/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear.ql
@@ -1,41 +1,62 @@
/**
- * @name Unsafe array for days of the year
+ * @name Unsafe array for days of the year (AntiPattern 4)
* @description An array of 365 items typically indicates one entry per day of the year, but without considering leap years, which would be 366 days.
* An access on a leap year could result in buffer overflow bugs.
* @kind problem
* @problem.severity warning
- * @id cpp/leap-year/unsafe-array-for-days-of-the-year
+ * @id cpp/microsoft/public/leap-year/unsafe-array-for-days-of-the-year
* @precision low
- * @tags security
- * leap-year
+ * @tags leap-year
+ * correctness
*/
import cpp
-class LeapYearUnsafeDaysOfTheYearArrayType extends ArrayType {
- LeapYearUnsafeDaysOfTheYearArrayType() { this.getArraySize() = 365 }
-}
+/* Note: We used to have a `LeapYearUnsafeDaysOfTheYearArrayType` class which was the
+ set of ArrayType that had a fixed length of 365. However, to eliminate false positives,
+ we use `isElementAnArrayOfFixedSize` that *also* finds arrays of 366 items, where the programmer
+ has also catered for leap years.
+ So, instead of `instanceof` checks, for simplicity, we simply pass in 365/366 as integers as needed.
+*/
-from Element element, string allocType
-where
+bindingset[size]
+predicate isElementAnArrayOfFixedSize(
+ Element element, Type t, Declaration f, string allocType, int size
+) {
exists(NewArrayExpr nae |
element = nae and
- nae.getAllocatedType() instanceof LeapYearUnsafeDaysOfTheYearArrayType and
- allocType = "an array allocation"
+ nae.getAllocatedType().(ArrayType).getArraySize() = size and
+ allocType = "an array allocation" and
+ f = nae.getEnclosingFunction() and
+ t = nae.getAllocatedType().(ArrayType).getBaseType()
)
or
exists(Variable var |
var = element and
- var.getType() instanceof LeapYearUnsafeDaysOfTheYearArrayType and
- allocType = "an array allocation"
+ var.getType().(ArrayType).getArraySize() = size and
+ allocType = "an array allocation" and
+ f = var and
+ t = var.getType().(ArrayType).getBaseType()
)
or
exists(ConstructorCall cc |
element = cc and
cc.getTarget().hasName("vector") and
- cc.getArgument(0).getValue().toInt() = 365 and
- allocType = "a std::vector allocation"
+ cc.getArgument(0).getValue().toInt() = size and
+ allocType = "a std::vector allocation" and
+ f = cc.getEnclosingFunction() and
+ t = cc.getTarget().getDeclaringType()
+ )
+}
+
+from Element element, string allocType, Declaration f, Type t
+where
+ isElementAnArrayOfFixedSize(element, t, f, allocType, 365) and
+ not exists(Element element2, Declaration f2 |
+ isElementAnArrayOfFixedSize(element2, t, f2, _, 366) and
+ if f instanceof Function then f = f2 else f.getParentScope() = f2.getParentScope()
)
select element,
- "There is " + allocType +
- " with a hard-coded set of 365 elements, which may indicate the number of days in a year without considering leap year scenarios."
+ "$@: There is " + allocType +
+ " with a hard-coded set of 365 elements, which may indicate the number of days in a year without considering leap year scenarios.",
+ f, f.toString()
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/examples/LeapYearConditionalLogicBad.c b/cpp/ql/src/Likely Bugs/Leap Year/examples/LeapYearConditionalLogicBad.c
new file mode 100644
index 000000000000..7751b9eb34b8
--- /dev/null
+++ b/cpp/ql/src/Likely Bugs/Leap Year/examples/LeapYearConditionalLogicBad.c
@@ -0,0 +1,21 @@
+// Checking for leap year
+bool isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+if (isLeapYear)
+{
+ // untested path
+}
+else
+{
+ // tested path
+}
+
+
+// Checking specifically for the leap day
+if (month == 2 && day == 29) // (or 1 with a tm_mon value)
+{
+ // untested path
+}
+else
+{
+ // tested path
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModificationBad.c b/cpp/ql/src/Likely Bugs/Leap Year/examples/UncheckedLeapYearAfterYearModificationBad.c
similarity index 100%
rename from cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModificationBad.c
rename to cpp/ql/src/Likely Bugs/Leap Year/examples/UncheckedLeapYearAfterYearModificationBad.c
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModificationGood.c b/cpp/ql/src/Likely Bugs/Leap Year/examples/UncheckedLeapYearAfterYearModificationGood.c
similarity index 100%
rename from cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModificationGood.c
rename to cpp/ql/src/Likely Bugs/Leap Year/examples/UncheckedLeapYearAfterYearModificationGood.c
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYearBad.c b/cpp/ql/src/Likely Bugs/Leap Year/examples/UnsafeArrayForDaysOfYearBad.c
similarity index 100%
rename from cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYearBad.c
rename to cpp/ql/src/Likely Bugs/Leap Year/examples/UnsafeArrayForDaysOfYearBad.c
diff --git a/cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYearGood.c b/cpp/ql/src/Likely Bugs/Leap Year/examples/UnsafeArrayForDaysOfYearGood.c
similarity index 100%
rename from cpp/ql/src/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYearGood.c
rename to cpp/ql/src/Likely Bugs/Leap Year/examples/UnsafeArrayForDaysOfYearGood.c
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuard.qhelp b/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuard.qhelp
new file mode 100644
index 000000000000..1f27b051e8f8
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuard.qhelp
@@ -0,0 +1,20 @@
+
+
+
+ Checking for overflow of an addition by comparing against one of the arguments of the addition fails if the size of all the argument types are smaller than 4 bytes. This is because the result of the addition is promoted to a 4 byte int.
+
+
+
+Check the overflow by comparing the addition against a value that is at least 4 bytes.
+
+
+
+ In this example, the result of the comparison will result in an integer overflow.
+
+
+ To fix the bug, check the overflow by comparing the addition against a value that is at least 4 bytes.
+
+
+
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuard.ql b/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuard.ql
new file mode 100644
index 000000000000..8d220bdd62eb
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuard.ql
@@ -0,0 +1,31 @@
+/**
+ * @name Bad overflow check
+ * @description Checking for overflow of an addition by comparing against one
+ * of the arguments of the addition fails if the size of all the
+ * argument types are smaller than 4 bytes. This is because the
+ * result of the addition is promoted to a 4 byte int.
+ * @kind problem
+ * @problem.severity error
+ * @tags security
+ * external/cwe/cwe-190
+ * external/cwe/cwe-191
+ * @id cpp/microsoft/public/badoverflowguard
+ */
+
+import cpp
+
+/*
+ * Example:
+ *
+ * uint16 v, uint16 b
+ * if ((v + b < v) <-- bad check for overflow
+ */
+
+from AddExpr a, Variable v, RelationalOperation cmp
+where
+ a.getAnOperand() = v.getAnAccess() and
+ forall(Expr op | op = a.getAnOperand() | op.getType().getSize() < 4) and
+ cmp.getAnOperand() = a and
+ cmp.getAnOperand() = v.getAnAccess() and
+ not a.getExplicitlyConverted().getType().getSize() < 4
+select cmp, "Bad overflow check"
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuardBadCode.c b/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuardBadCode.c
new file mode 100644
index 000000000000..b7dc59a33785
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuardBadCode.c
@@ -0,0 +1,9 @@
+unsigned short CheckForInt16OverflowBadCode(unsigned short v, unsigned short b)
+{
+ if (v + b < v) // BUG: "v + b" will be promoted to 32 bits
+ {
+ // ... do something
+ }
+
+ return v + b;
+}
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuardGoodCode.c b/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuardGoodCode.c
new file mode 100644
index 000000000000..f5cc5c2ed4f6
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/Conversion/BadOverflowGuardGoodCode.c
@@ -0,0 +1,9 @@
+unsigned short CheckForInt16OverflowCorrectCode(unsigned short v, unsigned short b)
+{
+ if (v + b > 0x00FFFF)
+ {
+ // ... do something
+ }
+
+ return v + b;
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.qhelp b/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.qhelp
new file mode 100644
index 000000000000..e7a85e353ed5
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.qhelp
@@ -0,0 +1,29 @@
+
+
+
+ RtlCompareMemory
routine compares two blocks of memory and returns the number of bytes that match, not a boolean value indicating a full comparison like RtlEqualMemory
does.
+ This query detects the return value of RtlCompareMemory
being handled as a boolean.
+
+
+
+ Any findings from this rule may indicate that the return value of a call to RtlCompareMemory
is being incorrectly interpreted as a boolean.
+ Review the logic of the call, and if necessary, replace the function call with RtlEqualMemory
.
+
+
+
+The following example is a typical one where an identity comparison is intended, but the wrong API is being used.
+
+
+In this example, the fix is to replace the call to RtlCompareMemory
with RtlEqualMemory
.
+
+
+
+
+
+ Books online RtlCompareMemory function (wdm.h)
+ Books online RtlEqualMemory macro (wdm.h)
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.ql b/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.ql
new file mode 100644
index 000000000000..1470a0905465
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.ql
@@ -0,0 +1,69 @@
+/**
+ * @id cpp/microsoft/public/drivers/incorrect-usage-of-rtlcomparememory
+ * @name Incorrect usage of RtlCompareMemory
+ * @description `RtlCompareMemory` routine compares two blocks of memory and returns the number of bytes that match, not a boolean value indicating a full comparison like `RtlEqualMemory` does.
+ * This query detects the return value of `RtlCompareMemory` being handled as a boolean.
+ * @security.severity Important
+ * @kind problem
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ * kernel
+ */
+
+import cpp
+
+predicate isLiteralABooleanMacro(Literal l) {
+ exists(MacroInvocation mi | mi.getExpr() = l |
+ mi.getMacroName() in ["true", "false", "TRUE", "FALSE"]
+ )
+}
+
+from FunctionCall fc, Function f, Expr e, string msg
+where
+ f.getQualifiedName() = "RtlCompareMemory" and
+ f.getACallToThisFunction() = fc and
+ (
+ exists(UnaryLogicalOperation ulo | e = ulo |
+ ulo.getAnOperand() = fc and
+ msg = "as an operand in an unary logical operation"
+ )
+ or
+ exists(BinaryLogicalOperation blo | e = blo |
+ blo.getAnOperand() = fc and
+ msg = "as an operand in a binary logical operation"
+ )
+ or
+ exists(Conversion conv | e = conv |
+ (
+ conv.getType().hasName("bool") or
+ conv.getType().hasName("BOOLEAN") or
+ conv.getType().hasName("_Bool")
+ ) and
+ conv.getUnconverted() = fc and
+ msg = "as a boolean"
+ )
+ or
+ exists(IfStmt s | e = s.getControllingExpr() |
+ s.getControllingExpr() = fc and
+ msg = "as the controlling expression in an If statement"
+ )
+ or
+ exists(EqualityOperation bao, Expr e2 | e = bao |
+ bao.hasOperands(fc, e2) and
+ isLiteralABooleanMacro(e2) and
+ msg =
+ "as an operand in an equality operation where the other operand is a boolean value (high precision result)"
+ )
+ or
+ exists(EqualityOperation bao, Expr e2 | e = bao |
+ bao.hasOperands(fc, e2) and
+ (e2.(Literal).getValue().toInt() = 1 or e2.(Literal).getValue().toInt() = 0) and
+ not isLiteralABooleanMacro(e2) and
+ msg =
+ "as an operand in an equality operation where the other operand is likely a boolean value (lower precision result, needs to be reviewed)"
+ )
+ )
+select e,
+ "This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`.",
+ fc, "call to `RtlCompareMemory`", e, msg
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemoryBad.c b/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemoryBad.c
new file mode 100644
index 000000000000..34dd300663ca
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemoryBad.c
@@ -0,0 +1,5 @@
+//bug, the code assumes RtlCompareMemory is comparing for identical values & return false if not identical
+if (!RtlCompareMemory(pBuffer, ptr, 16))
+{
+ return FALSE;
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemoryGood.c b/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemoryGood.c
new file mode 100644
index 000000000000..a8a5945a9e3c
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemoryGood.c
@@ -0,0 +1,5 @@
+//fixed
+if (!RtlEqualMemory(pBuffer, ptr, 16))
+{
+ return FALSE;
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.qhelp b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.qhelp
new file mode 100644
index 000000000000..5cca10d929ed
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.qhelp
@@ -0,0 +1,22 @@
+
+
+
+ If the argument for a sizeof
call is a binary operation or a sizeof
call, it is typically a sign that there is a confusion on the usage of the sizeof usage.
+
+
+
+ Any findings from this rule may indicate that the sizeof
is being used incorrectly.
+ Review the logic of the call.
+
+
+
+ The following example shows a case where sizeof
a binary operation by mistake.
+
+
+ In this example, the fix is to multiply the result of sizeof
by the number of elements.
+
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.ql b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.ql
new file mode 100644
index 000000000000..4a20a36d4f2f
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.ql
@@ -0,0 +1,62 @@
+/**
+ * @id cpp/microsoft/public/sizeof/sizeof-or-operation-as-argument
+ * @name Usage of an expression that is a binary operation, or sizeof call passed as an argument to a sizeof call
+ * @description When the `expr` passed to `sizeof` is a binary operation, or a sizeof call, this is typically a sign that there is a confusion on the usage of sizeof.
+ * @kind problem
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ */
+
+import cpp
+import SizeOfTypeUtils
+
+/**
+ * Windows SDK corecrt_math.h defines a macro _CLASS_ARG that
+ * intentionally misuses sizeof to determine the size of a floating point type.
+ * Explicitly ignoring any hit in this macro.
+ */
+predicate isPartOfCrtFloatingPointMacroExpansion(Expr e) {
+ exists(MacroInvocation mi |
+ mi.getMacroName() = "_CLASS_ARG" and
+ mi.getMacro().getFile().getBaseName() = "corecrt_math.h" and
+ mi.getAnExpandedElement() = e
+ )
+}
+
+/**
+ * Determines if the sizeOfExpr is ignorable.
+ */
+predicate ignorableSizeof(SizeofExprOperator sizeofExpr) {
+ // a common pattern found is to sizeof a binary operation to check a type
+ // to then perfomr an onperaiton for a 32 or 64 bit type.
+ // these cases often look like sizeof(x) >=4
+ // more generally we see binary operations frequently used in different type
+ // checks, where the sizeof is part of some comparison operation of a switch statement guard.
+ // sizeof as an argument is also similarly used, but seemingly less frequently.
+ exists(ComparisonOperation comp | comp.getAnOperand() = sizeofExpr)
+ or
+ exists(ConditionalStmt s | s.getControllingExpr() = sizeofExpr)
+ or
+ // another common practice is to use bit-wise operations in sizeof to allow the compiler to
+ // 'pack' the size appropriate but get the size of the result out of a sizeof operation.
+ sizeofExpr.getExprOperand() instanceof BinaryBitwiseOperation
+}
+
+from SizeofExprOperator sizeofExpr, string message, Expr op
+where
+ exists(string tmpMsg |
+ (
+ op instanceof BinaryOperation and tmpMsg = "binary operator"
+ or
+ op instanceof SizeofOperator and tmpMsg = "sizeof"
+ ) and
+ if sizeofExpr.isInMacroExpansion()
+ then message = tmpMsg + "(in a macro expansion)"
+ else message = tmpMsg
+ ) and
+ op = sizeofExpr.getExprOperand() and
+ not isPartOfCrtFloatingPointMacroExpansion(op) and
+ not ignorableSizeof(sizeofExpr)
+select sizeofExpr, "$@: $@ of $@ inside sizeof.", sizeofExpr, message,
+ sizeofExpr.getEnclosingFunction(), "Usage", op, message
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperationBad.c b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperationBad.c
new file mode 100644
index 000000000000..52b296b94016
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperationBad.c
@@ -0,0 +1,5 @@
+#define SIZEOF_CHAR sizeof(char)
+
+char* buffer;
+// bug - the code is really going to allocate sizeof(size_t) instead o fthe intended sizeof(char) * 10
+buffer = (char*) malloc(sizeof(SIZEOF_CHAR * 10));
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperationGood.c b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperationGood.c
new file mode 100644
index 000000000000..c61e019b41ef
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperationGood.c
@@ -0,0 +1,4 @@
+#define SIZEOF_CHAR sizeof(char)
+
+char* buffer;
+buffer = (char*) malloc(SIZEOF_CHAR * 10);
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.qhelp b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.qhelp
new file mode 100644
index 000000000000..ed473d234e4b
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.qhelp
@@ -0,0 +1,26 @@
+
+
+
+ If the argument for a sizeof
call is a macro that expands to a constant integer type, it is a likely indication that the macro operation may be misused or that the argument was selected by mistake (i.e. typo).
+ This query detects if the argument for sizeof
is a macro that expands to a constant integer value.
+ NOTE: This rule will ignore multicharacter literal values that are exactly 4 bytes long as it matches the length of int
and may be expected.
+
+
+
+ Any findings from this rule may indicate that the sizeof
is being used incorrectly.
+ Review the logic of the call.
+
+
+
+The following example shows a case where sizeof
a constant was used instead of the sizeof
of a structure by mistake as the names are similar.
+
+
+In this example, the fix is to replace the argument for sizeof
with the structure name.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.ql b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.ql
new file mode 100644
index 000000000000..709a33865924
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.ql
@@ -0,0 +1,54 @@
+/**
+ * @id cpp/microsoft/public/sizeof/const-int-argument
+ * @name Passing a constant integer macro to sizeof
+ * @description The expression passed to sizeof is a macro that expands to an integer constant. A data type was likely intended instead.
+ * @kind problem
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ */
+
+import cpp
+import SizeOfTypeUtils
+
+predicate isExprAConstInteger(Expr e, MacroInvocation mi) {
+ exists(Type type |
+ type = e.getExplicitlyConverted().getType() and
+ isTypeDangerousForSizeof(type) and
+ // Special case for wide-char literals when the compiler doesn't recognize wchar_t (i.e. L'\\', L'\0')
+ // Accounting for parenthesis "()" around the value
+ not exists(Macro m | m = mi.getMacro() |
+ m.getBody().toString().regexpMatch("^[\\s(]*L'.+'[\\s)]*$")
+ ) and
+ // Special case for token pasting operator
+ not exists(Macro m | m = mi.getMacro() | m.getBody().toString().regexpMatch("^.*\\s*##\\s*.*$")) and
+ // Special case for multichar literal integers that are exactly 4 character long (i.e. 'val1')
+ not exists(Macro m | m = mi.getMacro() |
+ e.getType().toString() = "int" and
+ m.getBody().toString().regexpMatch("^'.{4}'$")
+ ) and
+ e.isConstant()
+ )
+}
+
+int countMacros(Expr e) { result = count(MacroInvocation mi | mi.getExpr() = e | mi) }
+
+predicate isSizeOfExprOperandMacroInvocationAConstInteger(
+ SizeofExprOperator sizeofExpr, MacroInvocation mi
+) {
+ exists(Expr e |
+ e = mi.getExpr() and
+ e = sizeofExpr.getExprOperand() and
+ isExprAConstInteger(e, mi) and
+ // Special case for FPs that involve an inner macro that resolves to 0 such as _T('\0')
+ not exists(int macroCount | macroCount = countMacros(e) |
+ macroCount > 1 and e.(Literal).getValue().toInt() = 0
+ )
+ )
+}
+
+from SizeofExprOperator sizeofExpr, MacroInvocation mi
+where isSizeOfExprOperandMacroInvocationAConstInteger(sizeofExpr, mi)
+select sizeofExpr,
+ "$@: sizeof of integer macro $@ will always return the size of the underlying integer type.",
+ sizeofExpr, sizeofExpr.getEnclosingFunction().getName(), mi.getMacro(), mi.getMacro().getName()
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacroBad.c b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacroBad.c
new file mode 100644
index 000000000000..63d73f4d349c
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacroBad.c
@@ -0,0 +1,12 @@
+#define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d
+
+typedef struct {
+ int a;
+ bool b;
+} SOMESTRUCT_THAT_MATTERS;
+
+//bug, the code is using SOMESTRUCT_ERRNO_THAT_MATTERS by mistake instead of SOMESTRUCT_THAT_MATTERS
+if (somedata.length >= sizeof(SOMESTRUCT_ERRNO_THAT_MATTERS))
+{
+ /// Do something
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacroGood.c b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacroGood.c
new file mode 100644
index 000000000000..bfadb4d59892
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacroGood.c
@@ -0,0 +1,11 @@
+#define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d
+
+typedef struct {
+ int a;
+ bool b;
+} SOMESTRUCT_THAT_MATTERS;
+
+if (somedata.length >= sizeof(SOMESTRUCT_THAT_MATTERS))
+{
+ /// Do something
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfTypeUtils.qll b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfTypeUtils.qll
new file mode 100644
index 000000000000..87e5b1fa0f4b
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfTypeUtils.qll
@@ -0,0 +1,45 @@
+import cpp
+
+/**
+ * Holds if `type` is a `Type` that typically should not be used for `sizeof` in macros or function return values.
+ */
+predicate isTypeDangerousForSizeof(Type type) {
+ (
+ type instanceof IntegralOrEnumType and
+ // ignore string literals
+ not type instanceof WideCharType and
+ not type instanceof CharType
+ )
+}
+
+/**
+ * Holds if `type` is a `Type` that typically should not be used for `sizeof` in macros or function return values.
+ * This predicate extends the types detected in exchange of precision.
+ * For higher precision, please use `isTypeDangerousForSizeof`
+ */
+predicate isTypeDangerousForSizeofLowPrecision(Type type) {
+ (
+ // UINT8/BYTE are typedefs to char, so we treat them separately.
+ // WCHAR is sometimes a typedef to UINT16, so we treat it separately too.
+ type.getName() = "UINT8"
+ or
+ type.getName() = "BYTE"
+ or
+ not type.getName() = "WCHAR" and
+ exists(Type ut |
+ ut = type.getUnderlyingType() and
+ ut instanceof IntegralOrEnumType and
+ not ut instanceof WideCharType and
+ not ut instanceof CharType
+ )
+ )
+}
+
+/**
+ * Holds if the `Function` return type is dangerous as input for `sizeof`.
+ */
+class FunctionWithTypeDangerousForSizeofLowPrecision extends Function {
+ FunctionWithTypeDangerousForSizeofLowPrecision() {
+ exists(Type type | type = this.getType() | isTypeDangerousForSizeofLowPrecision(type))
+ }
+}
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/BannedEncryption.qhelp b/cpp/ql/src/Microsoft/Security/Cryptography/BannedEncryption.qhelp
new file mode 100644
index 000000000000..57ea002bd6a7
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/BannedEncryption.qhelp
@@ -0,0 +1,46 @@
+
+
+
+
+
+ Finds explicit uses of symmetric encryption algorithms that are weak, obsolete, or otherwise unapproved.
+
+
+ Encryption algorithms such as DES, (uses keys of 56 bits only), RC2 (uses keys of 128 bits only), and TripleDES (provides at most 112 bits of security) are considered to be weak.
+
+
+ These cryptographic algorithms do not provide as much security assurance as more modern counterparts.
+
+
+
+
+
+ For Microsoft internal security standards:
+
+
+ For WinCrypt, switch to ALG_SID_AES, ALG_SID_AES_128, ALG_SID_AES_192, or ALG_SID_AES_256.
+
+
+ For BCrypt, switch to AES or any algorithm other than RC2, RC4, DES, DESX, 3DES, 3DES_112. AES_GMAC and AES_CMAC require crypto board review.
+
+
+
+
+Violations:
+
+
+
+
+Solutions:
+
+
+
+
+
+
+Microsoft Docs: Microsoft SDL Cryptographic Recommendations.
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/BannedEncryption.ql b/cpp/ql/src/Microsoft/Security/Cryptography/BannedEncryption.ql
new file mode 100644
index 000000000000..0be6cf70086f
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/BannedEncryption.ql
@@ -0,0 +1,58 @@
+/**
+ * @name Weak cryptography
+ * @description Finds explicit uses of symmetric encryption algorithms that are weak, obsolete, or otherwise unapproved.
+ * @kind problem
+ * @id cpp/microsoft/public/weak-crypto/banned-encryption-algorithms
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ * external/cwe/cwe-327
+ */
+
+import cpp
+import CryptoFilters
+import CryptoDataflowCapi
+import CryptoDataflowCng
+import experimental.cryptography.Concepts
+
+predicate isCapiOrCNGBannedAlg(Expr e, string msg) {
+ exists(FunctionCall fc |
+ CapiCryptCreateEncryptionBanned::flow(DataFlow::exprNode(e),
+ DataFlow::exprNode(fc.getArgument(1)))
+ or
+ BCryptOpenAlgorithmProviderBannedEncryption::flow(DataFlow::exprNode(e),
+ DataFlow::exprNode(fc.getArgument(1)))
+ ) and
+ msg =
+ "Call to a cryptographic function with a banned symmetric encryption algorithm: " +
+ e.getValueText()
+}
+
+predicate isGeneralBannedAlg(SymmetricEncryptionAlgorithm alg, Expr confSink, string msg) {
+ // Handle unknown cases in a separate query
+ not alg.getEncryptionName() = unknownAlgorithm() and
+ exists(string resMsg |
+ (
+ not alg.getEncryptionName().matches("AES%") and
+ resMsg = "Use of banned symmetric encryption algorithm: " + alg.getEncryptionName() + "."
+ ) and
+ (
+ if alg.hasConfigurationSink() and alg.configurationSink() != alg
+ then (
+ confSink = alg.configurationSink() and msg = resMsg + " Algorithm used at sink: $@."
+ ) else (
+ confSink = alg and msg = resMsg
+ )
+ )
+ )
+}
+
+from Expr sink, Expr confSink, string msg
+where
+ (
+ isCapiOrCNGBannedAlg(sink, msg) and confSink = sink
+ or
+ isGeneralBannedAlg(sink, confSink, msg)
+ ) and
+ not isSrcSinkFiltered(sink, confSink)
+select sink, msg, confSink, confSink.toString()
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCAPI.qhelp b/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCAPI.qhelp
new file mode 100644
index 000000000000..e6e00a06cdb9
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCAPI.qhelp
@@ -0,0 +1,29 @@
+
+
+
+
+ Violation - Use of one of the following unsafe encryption modes that is not approved: ECB, OFB, CFB, CTR, CCM, or GCM.
+ These modes are vulnerable to attacks and may cause exposure of sensitive information. For example, using ECB
to encrypt a plaintext block always produces a same cipher text, so it can easily tell if two encrypted messages are identical. Using approved modes can avoid these unnecessary risks.
+
+
+
+ - Use only approved modes CBC, CTS and XTS.
+
+
+
+Violation:
+
+
+
+Solution:
+
+
+
+
+
+Microsoft Docs: Microsoft SDL Cryptographic Recommendations.
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCAPI.ql b/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCAPI.ql
new file mode 100644
index 000000000000..16d83e54abc6
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCAPI.ql
@@ -0,0 +1,40 @@
+/**
+ * @name Weak cryptography
+ * @description Finds explicit uses of block cipher chaining mode algorithms that are not approved. (CAPI)
+ * @kind problem
+ * @id cpp/microsoft/public/weak-crypto/capi/banned-modes
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ * external/cwe/cwe-327
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.new.DataFlow
+import CryptoDataflowCapi
+
+module CapiSetBlockCipherConfiguration implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ source.asExpr().isConstant() and
+ // KP_MODE
+ // CRYPT_MODE_CBC 1 - Cipher block chaining - Microsoft-Only: Only mode allowed by Crypto Board from this list (CBC-MAC)
+ // CRYPT_MODE_ECB 2 - Electronic code book - Generally not recommended for usage in cryptographic protocols at all
+ // CRYPT_MODE_OFB 3 - Output feedback mode - Microsoft-Only: Banned, usage requires Crypto Board review
+ // CRYPT_MODE_CFB 4 - Cipher feedback mode - Microsoft-Only: Banned, usage requires Crypto Board review
+ // CRYPT_MODE_CTS 5 - Ciphertext stealing mode - Microsoft-Only: CTS is approved by Crypto Board, but should probably use CNG and not CAPI
+ source.asExpr().getValue().toInt() != 1
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(CapiCryptCryptSetKeyParamtoKPMODE call | sink.asIndirectExpr() = call.getArgument(2))
+ }
+}
+
+module CapiSetBlockCipherTrace = DataFlow::Global;
+
+from CapiCryptCryptSetKeyParamtoKPMODE call, DataFlow::Node src, DataFlow::Node sink
+where
+ sink.asIndirectExpr() = call.getArgument(2) and
+ CapiSetBlockCipherTrace::flow(src, sink)
+select call,
+ "Call to 'CryptSetKeyParam' function with argument dwParam = KP_MODE is setting up a banned block cipher mode."
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCNG.qhelp b/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCNG.qhelp
new file mode 100644
index 000000000000..4713ef9ff13c
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCNG.qhelp
@@ -0,0 +1,31 @@
+
+
+
+
+ Violation - Use of one of the following unsafe encryption modes that is not approved: ECB, OFB, CFB, CTR, CCM, or GCM.
+ These modes are vulnerable to attacks and may cause exposure of sensitive information. For example, using ECB
to encrypt a plaintext block always produces a same cipher text, so it can easily tell if two encrypted messages are identical. Using approved modes can avoid these unnecessary risks.
+
+
+
+ - Use only approved modes CBC, CTS and XTS.
+
+
+
+Violation:
+
+
+
+Solution:
+
+
+
+
+
+
+Microsoft Docs: Microsoft SDL Cryptographic Recommendations.
+
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCNG.ql b/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCNG.ql
new file mode 100644
index 000000000000..d7184114b0a7
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/BannedModesCNG.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Weak cryptography
+ * @description Finds explicit uses of block cipher chaining mode algorithms that are not approved. (CNG)
+ * @kind problem
+ * @id cpp/microsoft/public/weak-crypto/cng/banned-modes
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ * external/cwe/cwe-327
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.new.DataFlow
+import CryptoDataflowCng
+
+from CngBCryptSetPropertyParamtoKChainingMode call, DataFlow::Node src, DataFlow::Node sink
+where
+ sink.asIndirectArgument() = call.getArgument(2) and
+ CngBCryptSetPropertyChainingBannedModeIndirectParameter::flow(src, sink)
+ or
+ sink.asExpr() = call.getArgument(2) and CngBCryptSetPropertyChainingBannedMode::flow(src, sink)
+select call,
+ "Call to 'BCryptSetProperty' function with argument pszProperty = \"ChainingMode\" is setting up a banned block cipher mode."
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/CryptoDataflowCapi.qll b/cpp/ql/src/Microsoft/Security/Cryptography/CryptoDataflowCapi.qll
new file mode 100644
index 000000000000..52c835ea9b89
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/CryptoDataflowCapi.qll
@@ -0,0 +1,97 @@
+/**
+ * Provides classes and predicates for identifying expressions that are use Crypto API (CAPI).
+ */
+
+import cpp
+private import semmle.code.cpp.dataflow.new.DataFlow
+
+/**
+ * Dataflow that detects a call to CryptSetKeyParam dwParam = KP_MODE (CAPI)
+ */
+module CapiCryptCryptSetKeyParamtoKPMODEConfiguration implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ source.asExpr().getValue().toInt() = 4 // KP_MODE
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(FunctionCall call |
+ // CryptSetKeyParam 2nd argument specifies the key parameter to set
+ sink.asExpr() = call.getArgument(1) and
+ call.getTarget().hasGlobalName("CryptSetKeyParam")
+ )
+ }
+}
+
+module CapiCryptCryptSetKeyParamtoKPMODE =
+ DataFlow::Global;
+
+/**
+ * A function call to CryptSetKeyParam with dwParam = KP_MODE (CAPI)
+ */
+class CapiCryptCryptSetKeyParamtoKPMODE extends FunctionCall {
+ CapiCryptCryptSetKeyParamtoKPMODE() {
+ exists(Expr var |
+ CapiCryptCryptSetKeyParamtoKPMODE::flow(DataFlow::exprNode(var),
+ DataFlow::exprNode(this.getArgument(1)))
+ )
+ }
+}
+
+// CAPI-specific DataFlow configuration
+module CapiCryptCreateHashBannedConfiguration implements DataFlow::ConfigSig {
+ // This mechnism will verify for approved set of values to call, rejecting anythign that is not in the list.
+ // NOTE: This mechanism is not guaranteed to work with CSPs that do not use the same algorithms defined in Wincrypt.h
+ //
+ predicate isSource(DataFlow::Node source) {
+ // Verify if source matched the mask for CAPI ALG_CLASS_HASH == 32768
+ source.asExpr().getValue().toInt().bitShiftRight(13) = 4 and
+ // The following hash algorithms are safe to use, anything else is considered banned
+ not (
+ source.asExpr().getValue().toInt().bitXor(32768) = 12 or // ALG_SID_SHA_256
+ source.asExpr().getValue().toInt().bitXor(32768) = 13 or // ALG_SID_SHA_384
+ source.asExpr().getValue().toInt().bitXor(32768) = 14 // ALG_SID_SHA_512
+ )
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(FunctionCall call |
+ // CryptCreateHash 2nd argument specifies the hash algorithm to be used.
+ sink.asExpr() = call.getArgument(1) and
+ call.getTarget().hasGlobalName("CryptCreateHash")
+ )
+ }
+}
+
+module CapiCryptCreateHashBanned = DataFlow::Global;
+
+// CAPI-specific DataFlow configuration
+module CapiCryptCreateEncryptionBannedConfiguration implements DataFlow::ConfigSig {
+ // This mechanism will verify for approved set of values to call, rejecting anything that is not in the list.
+ // NOTE: This mechanism is not guaranteed to work with CSPs that do not use the same algorithms defined in Wincrypt.h
+ //
+ predicate isSource(DataFlow::Node source) {
+ // Verify if source matched the mask for CAPI ALG_CLASS_DATA_ENCRYPT == 24576
+ source.asExpr().getValue().toInt().bitShiftRight(13) = 3 and
+ // The following algorithms are safe to use, anything else is considered banned
+ not (
+ source.asExpr().getValue().toInt().bitXor(26112) = 14 or // ALG_SID_AES_128
+ source.asExpr().getValue().toInt().bitXor(26112) = 15 or // ALG_SID_AES_192
+ source.asExpr().getValue().toInt().bitXor(26112) = 16 or // ALG_SID_AES_256
+ source.asExpr().getValue().toInt().bitXor(26112) = 17 // ALG_SID_AES
+ )
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(FunctionCall call |
+ // CryptGenKey or CryptDeriveKey 2nd argument specifies the hash algorithm to be used.
+ sink.asExpr() = call.getArgument(1) and
+ (
+ call.getTarget().hasGlobalName("CryptGenKey") or
+ call.getTarget().hasGlobalName("CryptDeriveKey")
+ )
+ )
+ }
+}
+
+module CapiCryptCreateEncryptionBanned =
+ DataFlow::Global;
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/CryptoDataflowCng.qll b/cpp/ql/src/Microsoft/Security/Cryptography/CryptoDataflowCng.qll
new file mode 100644
index 000000000000..a54650692075
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/CryptoDataflowCng.qll
@@ -0,0 +1,137 @@
+/**
+ * Provides classes and predicates for identifying expressions that are use crypto API Next Generation (CNG).
+ */
+
+import cpp
+private import semmle.code.cpp.dataflow.new.DataFlow
+
+/**
+ * Dataflow that detects a call to BCryptSetProperty pszProperty = ChainingMode (CNG)
+ */
+module CngBCryptSetPropertyParamtoKChainingModeConfiguration implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ source.asExpr().getValue().toString().matches("ChainingMode")
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(FunctionCall call |
+ // BCryptSetProperty 2nd argument specifies the key parameter to set
+ sink.asExpr() = call.getArgument(1) and
+ call.getTarget().hasGlobalName("BCryptSetProperty")
+ )
+ }
+}
+
+module CngBCryptSetPropertyParamtoKChainingMode =
+ DataFlow::Global;
+
+/**
+ * A function call to BCryptSetProperty pszProperty = ChainingMode (CNG)
+ */
+class CngBCryptSetPropertyParamtoKChainingMode extends FunctionCall {
+ CngBCryptSetPropertyParamtoKChainingMode() {
+ exists(Expr var |
+ CngBCryptSetPropertyParamtoKChainingMode::flow(DataFlow::exprNode(var),
+ DataFlow::exprNode(this.getArgument(1)))
+ )
+ }
+}
+
+predicate isChaniningModeCbc(DataFlow::Node source) {
+ // Verify if algorithm is in the approved list.
+ exists(string s | s = source.asExpr().getValue().toString() |
+ s.regexpMatch("ChainingMode[A-Za-z0-9/]+") and
+ // Property Strings
+ // BCRYPT_CHAIN_MODE_NA L"ChainingModeN/A" - The algorithm does not support chaining
+ // BCRYPT_CHAIN_MODE_CBC L"ChainingModeCBC" - Microsoft-Only: Only mode allowed by Crypto Board from this list (CBC-MAC)
+ // BCRYPT_CHAIN_MODE_ECB L"ChainingModeECB" - Generally not recommended for usage in cryptographic protocols at all
+ // BCRYPT_CHAIN_MODE_CFB L"ChainingModeCFB" - Microsoft-Only: Banned, usage requires Crypto Board review
+ // BCRYPT_CHAIN_MODE_CCM L"ChainingModeCCM" - Microsoft-Only: Banned, usage requires Crypto Board review
+ // BCRYPT_CHAIN_MODE_GCM L"ChainingModeGCM" - Microsoft-Only: Only for TLS, other usage requires Crypto Board review
+ not s.matches("ChainingModeCBC")
+ )
+}
+
+module CngBCryptSetPropertyChainingBannedModeConfiguration implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) { isChaniningModeCbc(source) }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(CngBCryptSetPropertyParamtoKChainingMode call |
+ // BCryptOpenAlgorithmProvider 3rd argument sets the chaining mode value
+ sink.asExpr() = call.getArgument(2)
+ )
+ }
+}
+
+module CngBCryptSetPropertyChainingBannedMode =
+ DataFlow::Global;
+
+module CngBCryptSetPropertyChainingBannedModeIndirectParameterConfiguration implements
+ DataFlow::ConfigSig
+{
+ predicate isSource(DataFlow::Node source) { isChaniningModeCbc(source) }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(CngBCryptSetPropertyParamtoKChainingMode call |
+ // CryptSetKeyParam 3rd argument specifies the mode (KP_MODE)
+ sink.asIndirectExpr() = call.getArgument(2)
+ )
+ }
+}
+
+module CngBCryptSetPropertyChainingBannedModeIndirectParameter =
+ DataFlow::Global;
+
+// CNG-specific DataFlow configuration
+module BCryptOpenAlgorithmProviderBannedHashConfiguration implements DataFlow::ConfigSig {
+ // NOTE: Unlike the CAPI scenario, CNG will use this method to load and initialize
+ // a cryptographic provider for any type of algorithm,not only hash.
+ // Therefore, we have to take a banned-list instead of approved list approach.
+ //
+ predicate isSource(DataFlow::Node source) {
+ // Verify if algorithm is marked as banned.
+ source.asExpr().getValue().toString().matches("MD_")
+ or
+ source.asExpr().getValue().toString().matches("SHA1")
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(FunctionCall call |
+ // BCryptOpenAlgorithmProvider 2nd argument specifies the algorithm to be used
+ sink.asExpr() = call.getArgument(1) and
+ call.getTarget().hasGlobalName("BCryptOpenAlgorithmProvider")
+ )
+ }
+}
+
+module BCryptOpenAlgorithmProviderBannedHash =
+ DataFlow::Global;
+
+// CNG-specific DataFlow configuration
+module BCryptOpenAlgorithmProviderBannedEncryptionConfiguration implements DataFlow::ConfigSig {
+ // NOTE: Unlike the CAPI scenario, CNG will use this method to load and initialize
+ // a cryptographic provider for any type of algorithm,not only encryption.
+ // Therefore, we have to take a banned-list instead of approved list approach.
+ //
+ predicate isSource(DataFlow::Node source) {
+ // Verify if algorithm is marked as banned.
+ source.asExpr().getValue().toString().matches("RC_") or
+ source.asExpr().getValue().toString().matches("DES") or
+ source.asExpr().getValue().toString().matches("DESX") or
+ source.asExpr().getValue().toString().matches("3DES") or
+ source.asExpr().getValue().toString().matches("3DES_112") or
+ source.asExpr().getValue().toString().matches("AES_GMAC") or // Microsoft Only: Requires Cryptoboard review
+ source.asExpr().getValue().toString().matches("AES_CMAC") // Microsoft Only: Requires Cryptoboard review
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(FunctionCall call |
+ // BCryptOpenAlgorithmProvider 2nd argument specifies the algorithm to be used
+ sink.asExpr() = call.getArgument(1) and
+ call.getTarget().hasGlobalName("BCryptOpenAlgorithmProvider")
+ )
+ }
+}
+
+module BCryptOpenAlgorithmProviderBannedEncryption =
+ DataFlow::Global;
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/CryptoFilters.qll b/cpp/ql/src/Microsoft/Security/Cryptography/CryptoFilters.qll
new file mode 100644
index 000000000000..65f9dde5f911
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/CryptoFilters.qll
@@ -0,0 +1,45 @@
+import cpp
+
+/**
+ * Determines if an element should be filtered (ignored)
+ * from any result set.
+ *
+ * The current strategy is to determine if the element
+ * resides in a path that appears to be a library (in particular openssl).
+ *
+ * It is therefore important that the element being examined represents
+ * a use or configuration of cryptography in the user code.
+ * E.g., if a global variable were defined in an OpenSSL library
+ * representing a bad/vuln algorithm, and this global were assessed
+ * it would appear to be ignorable, as it exists in a a filtered library.
+ * The use of that global must be examined with this filter.
+ *
+ * ASSUMPTION/CAVEAT: note if an openssl library wraps a dangerous crypo use
+ * this filter approach will ignore the wrapper call, unless it is also flagged
+ * as dangerous. e.g., SomeWraper(){ ... ...}
+ * The wrapper if defined in openssl would result in ignoring
+ * the use of MD5 internally, since it's use is entirely in openssl.
+ *
+ * TODO: these caveats need to be reassessed in the future.
+ */
+predicate isUseFiltered(Element e) {
+ e.getFile().getAbsolutePath().toLowerCase().matches("%openssl%")
+}
+
+/**
+ * Filtered only if both src and sink are considered filtered.
+ *
+ * This approach is meant to partially address some of the implications of
+ * `isUseFiltered`. Specifically, if an algorithm is specified by a user
+ * and some how passes to a user inside openssl, then this filter
+ * would not ignore that the user was specifying the use of something dangerous.
+ *
+ * e.g., if a wrapper in openssl existed of the form SomeWrapper(string alg, ...){ ... ...}
+ * and the user did something like SomeWrapper("MD5", ...), this would not be ignored.
+ *
+ * The source in the above example would the algorithm, and the sink is the configuration sink
+ * of the algorithm.
+ */
+predicate isSrcSinkFiltered(Element src, Element sink) {
+ isUseFiltered(src) and isUseFiltered(sink)
+}
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/HardcodedIVCNG.qhelp b/cpp/ql/src/Microsoft/Security/Cryptography/HardcodedIVCNG.qhelp
new file mode 100644
index 000000000000..bd0b71f227e4
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/HardcodedIVCNG.qhelp
@@ -0,0 +1,23 @@
+
+
+
+ An initialization vector (IV) is an input to a cryptographic primitive being used to provide the initial state. The IV is typically required to be random or pseudorandom (randomized scheme), but sometimes an IV only needs to be unpredictable or unique (stateful scheme).
+ Randomization is crucial for some encryption schemes to achieve semantic security, a property whereby repeated usage of the scheme under the same key does not allow an attacker to infer relationships between (potentially similar) segments of the encrypted message.
+
+
+
+ All symmetric block ciphers must also be used with an appropriate initialization vector (IV) according to the mode of operation being used.
+ If using a randomized scheme such as CBC, it is recommended to use cryptographically secure pseudorandom number generator such as BCryptGenRandom
.
+
+
+
+
+ BCryptEncrypt function (bcrypt.h)
+ BCryptGenRandom function (bcrypt.h)
+ Initialization vector (Wikipedia)
+
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/HardcodedIVCNG.ql b/cpp/ql/src/Microsoft/Security/Cryptography/HardcodedIVCNG.ql
new file mode 100644
index 000000000000..86b98d807723
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/HardcodedIVCNG.ql
@@ -0,0 +1,58 @@
+/**
+ * @name Weak cryptography
+ * @description Finds usage of a static (hardcoded) IV. (CNG)
+ * @kind problem
+ * @id cpp/microsoft/public/weak-crypto/cng/hardcoded-iv
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ * external/cwe/cwe-327
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.new.DataFlow
+
+/**
+ * Gets const element of `ArrayAggregateLiteral`.
+ */
+Expr getConstElement(ArrayAggregateLiteral lit) {
+ exists(int n |
+ result = lit.getElementExpr(n, _) and
+ result.isConstant()
+ )
+}
+
+/**
+ * Gets the last element in an `ArrayAggregateLiteral`.
+ */
+Expr getLastElement(ArrayAggregateLiteral lit) {
+ exists(int n |
+ result = lit.getElementExpr(n, _) and
+ not exists(lit.getElementExpr(n + 1, _))
+ )
+}
+
+module CngBCryptEncryptHardcodedIVConfiguration implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ exists(AggregateLiteral lit |
+ getLastElement(lit) = source.asDefinition() and
+ exists(getConstElement(lit))
+ )
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(FunctionCall call |
+ // BCryptEncrypt 5h argument specifies the IV
+ sink.asIndirectExpr() = call.getArgument(4) and
+ call.getTarget().hasGlobalName("BCryptEncrypt")
+ )
+ }
+}
+
+module Flow = DataFlow::Global;
+
+from DataFlow::Node sl, DataFlow::Node fc, AggregateLiteral lit
+where
+ Flow::flow(sl, fc) and
+ getLastElement(lit) = sl.asDefinition()
+select lit, "Calling BCryptEncrypt with a hard-coded IV on function "
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFBannedHashAlgorithm.qhelp b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFBannedHashAlgorithm.qhelp
new file mode 100644
index 000000000000..b61202a4dbc3
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFBannedHashAlgorithm.qhelp
@@ -0,0 +1,14 @@
+
+
+
+
+Use of KDF algorithm BCryptDeriveKeyPBKDF2 uses insecure hash from BCryptOpenAlgorithmProvider.
+
+
+
+Use SHA 256, 384, or 512.
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFBannedHashAlgorithm.ql b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFBannedHashAlgorithm.ql
new file mode 100644
index 000000000000..27f15531df56
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFBannedHashAlgorithm.ql
@@ -0,0 +1,85 @@
+/**
+ * @name KDF may only use SHA256/384/512 in generating a key.
+ * @description KDF may only use SHA256/384/512 in generating a key.
+ * @kind problem
+ * @id cpp/microsoft/public/kdf-insecure-hash
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.new.DataFlow
+
+module BannedHashAlgorithmConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ // Verify if algorithm is marked as banned.
+ not source.asExpr().getValue().toString().matches("SHA256") and
+ not source.asExpr().getValue().toString().matches("SHA384") and
+ not source.asExpr().getValue().toString().matches("SHA512")
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(FunctionCall call |
+ // Argument 1 (0-based) specified the algorithm ID.
+ // NTSTATUS BCryptOpenAlgorithmProvider(
+ // [out] BCRYPT_ALG_HANDLE *phAlgorithm,
+ // [in] LPCWSTR pszAlgId,
+ // [in] LPCWSTR pszImplementation,
+ // [in] ULONG dwFlags
+ // );
+ sink.asExpr() = call.getArgument(1) and
+ call.getTarget().hasGlobalName("BCryptOpenAlgorithmProvider")
+ )
+ }
+}
+
+module BannedHashAlgorithmTrace = DataFlow::Global;
+
+module BCRYPT_ALG_HANDLE_Config implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ exists(FunctionCall call |
+ // Argument 0 (0-based) specified the algorithm handle
+ // NTSTATUS BCryptOpenAlgorithmProvider(
+ // [out] BCRYPT_ALG_HANDLE *phAlgorithm,
+ // [in] LPCWSTR pszAlgId,
+ // [in] LPCWSTR pszImplementation,
+ // [in] ULONG dwFlags
+ // );
+ source.asDefiningArgument() = call.getArgument(0) and
+ call.getTarget().hasGlobalName("BCryptOpenAlgorithmProvider")
+ )
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ // Algorithm handle is the 0th (0-based) argument of the call
+ // NTSTATUS BCryptDeriveKeyPBKDF2(
+ // [in] BCRYPT_ALG_HANDLE hPrf,
+ // [in, optional] PUCHAR pbPassword,
+ // [in] ULONG cbPassword,
+ // [in, optional] PUCHAR pbSalt,
+ // [in] ULONG cbSalt,
+ // [in] ULONGLONG cIterations,
+ // [out] PUCHAR pbDerivedKey,
+ // [in] ULONG cbDerivedKey,
+ // [in] ULONG dwFlags
+ // );
+ exists(Call c | c.getTarget().getName() = "BCryptDeriveKeyPBKDF2" |
+ c.getArgument(0) = sink.asExpr()
+ )
+ }
+}
+
+module BCRYPT_ALG_HANDLE_Trace = DataFlow::Global;
+
+from DataFlow::Node src1, DataFlow::Node src2, DataFlow::Node sink1, DataFlow::Node sink2
+where
+ BannedHashAlgorithmTrace::flow(src1, sink1) and
+ exists(Call c |
+ c.getAnArgument() = sink1.asExpr() and src2.asDefiningArgument() = c.getAnArgument()
+ |
+ BCRYPT_ALG_HANDLE_Trace::flow(src2, sink2)
+ )
+select sink2.asExpr(),
+ "BCRYPT_ALG_HANDLE is passed to this to KDF derived from insecure hashing function $@. Must use SHA256 or higher.",
+ src1.asExpr(), src1.asExpr().getValue()
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFLowIterationCount.qhelp b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFLowIterationCount.qhelp
new file mode 100644
index 000000000000..84722ccec5cd
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFLowIterationCount.qhelp
@@ -0,0 +1,14 @@
+
+
+
+
+Use of KDF algorithm BCryptDeriveKeyPBKDF2 uses low iteration count (less than 100k).
+
+
+
+Use a minimum of 100,000 for iteration count.
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFLowIterationCount.ql b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFLowIterationCount.ql
new file mode 100644
index 000000000000..53f7ab79a74d
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFLowIterationCount.ql
@@ -0,0 +1,51 @@
+/**
+ * @name Use iteration count at least 100k to prevent brute force attacks
+ * @description When deriving cryptographic keys from user-provided inputs such as password, use sufficient iteration count (at least 100k).
+ * This query traces constants of <100k to the iteration count parameter of CNG's BCryptDeriveKeyPBKDF2.
+ * This query traces constants of less than the min length to the target parameter.
+ * NOTE: if the constant is modified, or if a non-constant gets to the iteration count, this query will not flag these cases.
+ * The rationale currently is that this query is meant to validate common uses of key derivation.
+ * Non-common uses (modifying the iteration count somehow or getting the count from outside sources) are assumed to be intentional.
+ * @kind problem
+ * @id cpp/microsoft/public/kdf-low-iteration-count
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.new.DataFlow
+
+module IterationCountDataFlowConfig implements DataFlow::ConfigSig {
+ /**
+ * Defines the source for iteration count when it's coming from a fixed value
+ * Any expression that has an assigned value < 100000 could be a source.
+ */
+ predicate isSource(DataFlow::Node src) { src.asExpr().getValue().toInt() < 100000 }
+
+ predicate isSink(DataFlow::Node sink) {
+ // iterations count is the 5th (0-based) argument of the call
+ // NTSTATUS BCryptDeriveKeyPBKDF2(
+ // [in] BCRYPT_ALG_HANDLE hPrf,
+ // [in, optional] PUCHAR pbPassword,
+ // [in] ULONG cbPassword,
+ // [in, optional] PUCHAR pbSalt,
+ // [in] ULONG cbSalt,
+ // [in] ULONGLONG cIterations,
+ // [out] PUCHAR pbDerivedKey,
+ // [in] ULONG cbDerivedKey,
+ // [in] ULONG dwFlags
+ // );
+ exists(Call c | c.getTarget().getName() = "BCryptDeriveKeyPBKDF2" |
+ c.getArgument(5) = sink.asExpr()
+ )
+ }
+}
+
+module IterationCountDataFlow = DataFlow::Global;
+
+from DataFlow::Node src, DataFlow::Node sink
+where IterationCountDataFlow::flow(src, sink)
+select sink.asExpr(),
+ "Iteration count $@ is passed to this to KDF. Use at least 100000 iterations when deriving cryptographic key from password.",
+ src, src.asExpr().getValue()
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallKeyLength.qhelp b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallKeyLength.qhelp
new file mode 100644
index 000000000000..6927cd16583c
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallKeyLength.qhelp
@@ -0,0 +1,14 @@
+
+
+
+
+Use of KDF algorithm BCryptDeriveKeyPBKDF2 uses small key size (less than 16 bytes).
+
+
+
+Use a minimum of 16 bytes for key size.
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallKeyLength.ql b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallKeyLength.ql
new file mode 100644
index 000000000000..b70e68fba371
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallKeyLength.ql
@@ -0,0 +1,46 @@
+/**
+ * @name Small KDF derived key length.
+ * @description KDF derived keys should be a minimum of 128 bits (16 bytes).
+ * This query traces constants of less than the min length to the target parameter.
+ * NOTE: if the constant is modified, or if a non-constant gets to the target, this query will not flag these cases.
+ * The rationale currently is that this query is meant to validate common uses of key derivation.
+ * Non-common uses (modifying the values somehow or getting the count from outside sources) are assumed to be intentional.
+ * @kind problem
+ * @id cpp/microsoft/public/kdf-small-key-size
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.new.DataFlow
+
+module KeyLenConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node src) { src.asExpr().getValue().toInt() < 16 }
+
+ predicate isSink(DataFlow::Node sink) {
+ // Key length size is the 7th (0-based) argument of the call
+ // NTSTATUS BCryptDeriveKeyPBKDF2(
+ // [in] BCRYPT_ALG_HANDLE hPrf,
+ // [in, optional] PUCHAR pbPassword,
+ // [in] ULONG cbPassword,
+ // [in, optional] PUCHAR pbSalt,
+ // [in] ULONG cbSalt,
+ // [in] ULONGLONG cIterations,
+ // [out] PUCHAR pbDerivedKey,
+ // [in] ULONG cbDerivedKey,
+ // [in] ULONG dwFlags
+ // );
+ exists(Call c | c.getTarget().getName() = "BCryptDeriveKeyPBKDF2" |
+ c.getArgument(7) = sink.asExpr()
+ )
+ }
+}
+
+module KeyLenTrace = DataFlow::Global;
+
+from DataFlow::Node src, DataFlow::Node sink
+where KeyLenTrace::flow(src, sink)
+select sink.asExpr(),
+ "Key size $@ is passed to this to KDF. Use at least 16 bytes for key length when deriving cryptographic key from password.",
+ src.asExpr(), src.asExpr().getValue()
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallSaltSize.qhelp b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallSaltSize.qhelp
new file mode 100644
index 000000000000..bf664be8d63c
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallSaltSize.qhelp
@@ -0,0 +1,15 @@
+
+
+
+
+Use of KDF algorithm BCryptDeriveKeyPBKDF2 uses small salt size (less than 16 bytes).
+
+
+
+Use a minimum of 16 bytes for salt size.
+
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallSaltSize.ql b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallSaltSize.ql
new file mode 100644
index 000000000000..8f42679c584a
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/WeakKDFSmallSaltSize.ql
@@ -0,0 +1,46 @@
+/**
+ * @name Small KDF salt length.
+ * @description KDF salts should be a minimum of 128 bits (16 bytes).
+ * This query traces constants of less than the min length to the target parameter.
+ * NOTE: if the constant is modified, or if a non-constant gets to the target, this query will not flag these cases.
+ * The rationale currently is that this query is meant to validate common uses of key derivation.
+ * Non-common uses (modifying the values somehow or getting the count from outside sources) are assumed to be intentional.
+ * @kind problem
+ * @id cpp/microsoft/public/kdf-small-salt-size
+ * @problem.severity error
+ * @precision high
+ * @tags security
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.new.DataFlow
+
+module SaltLenConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node src) { src.asExpr().getValue().toInt() < 16 }
+
+ predicate isSink(DataFlow::Node sink) {
+ // Key length size is the 7th (0-based) argument of the call
+ // NTSTATUS BCryptDeriveKeyPBKDF2(
+ // [in] BCRYPT_ALG_HANDLE hPrf,
+ // [in, optional] PUCHAR pbPassword,
+ // [in] ULONG cbPassword,
+ // [in, optional] PUCHAR pbSalt,
+ // [in] ULONG cbSalt,
+ // [in] ULONGLONG cIterations,
+ // [out] PUCHAR pbDerivedKey,
+ // [in] ULONG cbDerivedKey,
+ // [in] ULONG dwFlags
+ // );
+ exists(Call c | c.getTarget().getName() = "BCryptDeriveKeyPBKDF2" |
+ c.getArgument(4) = sink.asExpr()
+ )
+ }
+}
+
+module SaltLenTrace = DataFlow::Global;
+
+from DataFlow::Node src, DataFlow::Node sink
+where SaltLenTrace::flow(src, sink)
+select sink.asExpr(),
+ "Salt size $@ is passed to this to KDF. Use at least 16 bytes for salt size when deriving cryptographic key from password.",
+ src, src.asExpr().getValue()
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCAPI/BannedModesCAPI1.cpp b/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCAPI/BannedModesCAPI1.cpp
new file mode 100644
index 000000000000..6296e68499bf
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCAPI/BannedModesCAPI1.cpp
@@ -0,0 +1,11 @@
+#include
+#include
+#include
+
+int main(){
+ DWORD ivLen;
+ HCRYPTKEY hKey;
+
+ //BAD
+ CryptGetKeyParam(hKey, CRYPT_MODE_ECB, NULL, &ivLen, 0);
+}
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCAPI/BannedModesCAPI2.cpp b/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCAPI/BannedModesCAPI2.cpp
new file mode 100644
index 000000000000..d804432a1237
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCAPI/BannedModesCAPI2.cpp
@@ -0,0 +1,11 @@
+#include
+#include
+#include
+
+int main(){
+ DWORD ivLen;
+ HCRYPTKEY hKey;
+
+ //OKAY
+ CryptGetKeyParam(hKey, CRYPT_MODE_CBC, NULL, &ivLen, 0);
+}
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCNG/BannedModesCNG1.cpp b/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCNG/BannedModesCNG1.cpp
new file mode 100644
index 000000000000..45b2e3607b55
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCNG/BannedModesCNG1.cpp
@@ -0,0 +1,14 @@
+#include
+#include
+#include
+
+int main(){
+ BCRYPT_ALG_HANDLE aes;
+
+ //BAD
+ status = BCryptSetProperty(aes,
+ BCRYPT_CHAINING_MODE,
+ (PBYTE)BCRYPT_CHAIN_MODE_ECB,
+ sizeof(BCRYPT_CHAIN_MODE_ECB),
+ 0);
+}
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCNG/BannedModesCNG2.cpp b/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCNG/BannedModesCNG2.cpp
new file mode 100644
index 000000000000..5bf92ca87a47
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/examples/BannedModesCNG/BannedModesCNG2.cpp
@@ -0,0 +1,14 @@
+#include
+#include
+#include
+
+int main(){
+ BCRYPT_ALG_HANDLE aes;
+
+ //OKAY
+ status = BCryptSetProperty(aes,
+ BCRYPT_CHAINING_MODE,
+ (PBYTE)BCRYPT_CHAIN_MODE_CBC,
+ sizeof(BCRYPT_CHAIN_MODE_CBC),
+ 0);
+}
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption1.cpp b/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption1.cpp
new file mode 100644
index 000000000000..bdaaa7056ec5
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption1.cpp
@@ -0,0 +1,14 @@
+#include
+#include
+#include
+
+int main(){
+ HCRYPTPROV hCryptProv;
+ HCRYPTKEY hKey;
+
+ //BAD
+ if(CryptGenKey( hCryptProv, CALG_DES_128, KEYLENGTH | CRYPT_EXPORTABLE, &hKey))
+ {
+ printf("A session key has been created.\n");
+ }
+}
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption2.cpp b/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption2.cpp
new file mode 100644
index 000000000000..7e20995e8c95
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption2.cpp
@@ -0,0 +1,14 @@
+#include
+#include
+#include
+
+int main(){
+ HCRYPTPROV hCryptProv;
+ HCRYPTKEY hKey;
+
+ //OKAY
+ if(CryptGenKey( hCryptProv, CALG_AES_128, KEYLENGTH | CRYPT_EXPORTABLE, &hKey))
+ {
+ printf("A session key has been created.\n");
+ }
+}
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption3.cpp b/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption3.cpp
new file mode 100644
index 000000000000..e0ee60d830f9
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption3.cpp
@@ -0,0 +1,12 @@
+#include
+#include
+#include
+
+int main(){
+ BCRYPT_ALG_HANDLE hAlg;
+ NTSTATUS status;
+ //BAD
+ status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_DES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
+}
+
+
diff --git a/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption4.cpp b/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption4.cpp
new file mode 100644
index 000000000000..57d8e0bb9675
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Cryptography/examples/WeakEncryption/WeakEncryption4.cpp
@@ -0,0 +1,12 @@
+#include
+#include
+#include
+
+int main(){
+ BCRYPT_ALG_HANDLE hAlg;
+ NTSTATUS status;
+ //OKAY
+ status = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_AES_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
+}
+
+
diff --git a/cpp/ql/src/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.c b/cpp/ql/src/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.c
new file mode 100644
index 000000000000..dead60efd5fd
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.c
@@ -0,0 +1,15 @@
+typedef enum {
+ exampleSomeValue,
+ exampleSomeOtherValue,
+ exampleValueMax
+} EXAMPLE_VALUES;
+
+/*...*/
+
+int variable = someStructure->example;
+if (variable >= exampleValueMax)
+{
+ /* ... Some action ... */
+}
+// ...
+Status = someArray[variable](/*...*/);
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.qhelp b/cpp/ql/src/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.qhelp
new file mode 100644
index 000000000000..3f3f6b8c4ab6
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.qhelp
@@ -0,0 +1,24 @@
+
+
+
+ This rule finds code where an enumerated type (enum
) is used to check for an upper boundary, but not the lower boundary, and the value is used as an index to access an array.
+ By default an enum variable is signed, and therefore it is important to ensure that it cannot take on a negative value. When the enum is subsequently used to index an array, or worse still an array of function pointers, then a negative enum value would lead to potentially arbitrary memory being read, used and/or executed.
+
+
+In the majority of cases the fix is simply to add the required lower bounds check to ensure that the enum has a positive value.
+
+
+
+The following example a value is passed and gets cast to an enumerated type and only partially bounds checked.
+
+In this example, the result of the out-of-bounds may allow for arbitrary code execution.
+To fix the problem in this example, you need to add an additional check to the guarding if statement to verify that the index is a positive value.
+
+
+
+
+
+
+
diff --git a/cpp/ql/src/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.ql b/cpp/ql/src/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.ql
new file mode 100644
index 000000000000..963538355c0b
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.ql
@@ -0,0 +1,122 @@
+/**
+ * @name EnumIndex
+ * @description Code using enumerated types as indexes into arrays will often check for
+ * an upper bound to ensure the index is not out of range.
+ * By default an enum variable is signed, and therefore it is important to ensure
+ * that it cannot take on a negative value. When the enum is subsequently used
+ * to index an array, or worse still an array of function pointers, then a negative
+ * enum value would lead to potentially arbitrary memory being read, used and/or executed.
+ * @kind problem
+ * @problem.severity error
+ * @precision high
+ * @id cpp/microsoft/public/enum-index
+ * @tags security
+ * external/cwe/cwe-125
+ * external/microsoft/c33010
+ */
+
+import cpp
+import semmle.code.cpp.controlflow.Guards
+private import semmle.code.cpp.rangeanalysis.RangeAnalysisUtils
+import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
+
+/**
+ * Holds if `ec` is the upper bound of an enum
+ */
+predicate isUpperBoundEnumValue(EnumConstant ec) {
+ not exists(EnumConstant ec2, Enum enum | enum = ec2.getEnclosingElement() |
+ enum = ec.getEnclosingElement() and
+ ec2.getValue().toInt() > ec.getValue().toInt()
+ )
+}
+
+/**
+ * Holds if 'eca' is an access to the upper bound of an enum
+ */
+predicate isUpperBoundEnumAccess(EnumConstantAccess eca) {
+ exists(EnumConstant ec |
+ varbind(eca, ec) and
+ isUpperBoundEnumValue(ec)
+ )
+}
+
+/**
+ * Holds if the expression `e` is accessing the enum constant `ec`
+ */
+predicate isExpressionAccessingUpperboundEnum(Expr e, EnumConstantAccess ec) {
+ isExpressionAccessingUpperboundEnum(e.getAChild(), ec)
+ or
+ ec = e and
+ isUpperBoundEnumAccess(ec)
+}
+
+/**
+ * Holds if `e` is a child of an If statement
+ */
+predicate isPartOfAnIfStatement(Expr e) {
+ isPartOfAnIfStatement(e.getAChild())
+ or
+ exists(IfStmt ifs | ifs.getAChild() = e)
+}
+
+/**
+ * Holds if the variable access `offsetExpr` upper bound is guarded by an If statement GuardCondition
+ * that is using the upper bound of an enum to check the upper bound of `offsetExpr`
+ */
+predicate hasUpperBoundDefinedByEnum(VariableAccess offsetExpr) {
+ exists(BasicBlock controlled, StackVariable offsetVar, SsaDefinition def |
+ controlled.contains(offsetExpr) and
+ linearBoundControlsEnum(controlled, def, offsetVar, Lesser()) and
+ offsetExpr = def.getAUse(offsetVar)
+ )
+}
+
+pragma[noinline]
+predicate linearBoundControlsEnum(
+ BasicBlock controlled, SsaDefinition def, StackVariable offsetVar, RelationDirection direction
+) {
+ exists(GuardCondition guard |
+ exists(boolean branch |
+ guard.controls(controlled, branch) and
+ cmpWithLinearBound(guard, def.getAUse(offsetVar), direction, branch)
+ ) and
+ exists(EnumConstantAccess enumca | isExpressionAccessingUpperboundEnum(guard, enumca)) and
+ isPartOfAnIfStatement(guard)
+ )
+}
+
+/**
+ * Holds if the variable access `offsetExpr` lower bound is guarded
+ */
+predicate hasLowerBound(VariableAccess offsetExpr) {
+ exists(BasicBlock controlled, StackVariable offsetVar, SsaDefinition def |
+ controlled.contains(offsetExpr) and
+ linearBoundControls(controlled, def, offsetVar, Greater()) and
+ offsetExpr = def.getAUse(offsetVar)
+ )
+}
+
+pragma[noinline]
+predicate linearBoundControls(
+ BasicBlock controlled, SsaDefinition def, StackVariable offsetVar, RelationDirection direction
+) {
+ exists(GuardCondition guard, boolean branch |
+ guard.controls(controlled, branch) and
+ cmpWithLinearBound(guard, def.getAUse(offsetVar), direction, branch) and
+ isPartOfAnIfStatement(guard)
+ )
+}
+
+from VariableAccess offset, ArrayExpr array
+where
+ offset = array.getArrayOffset() and
+ hasUpperBoundDefinedByEnum(offset) and
+ not hasLowerBound(offset) and
+ exists(IntegralType t |
+ t = offset.getUnderlyingType() and
+ not t.isUnsigned()
+ ) and
+ lowerBound(offset.getFullyConverted()) < 0
+select offset,
+ "When accessing array " + array.getArrayBase() + " with index " + offset +
+ ", the upper bound of an enum is used to check the upper bound of the array, but the lower bound is not checked."
diff --git a/cpp/ql/src/Microsoft/Security/Protocols/HardCodedSecurityProtocol.qhelp b/cpp/ql/src/Microsoft/Security/Protocols/HardCodedSecurityProtocol.qhelp
new file mode 100644
index 000000000000..e34d26933823
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Protocols/HardCodedSecurityProtocol.qhelp
@@ -0,0 +1,29 @@
+
+
+
+
+Hard-coding security protocols rather than specifying the system default is risky because the protocol may become deprecated in future.
+The grbitEnabledProtocols
member of the SCHANNEL_CRED
struct contains a bit string that represents the protocols supported by connections made with credentials acquired by using this structure. If this member is zero, Schannel selects the protocol. Applications should set grbitEnabledProtocols
to zero and use the protocol versions enabled on the system by default.
+
+
+
+ - Set the grbitEnabledProtocols
member of the SCHANNEL_CRED
struct to 0
.
+
+
+
+Violation:
+
+
+
+Solution:
+
+
+
+
+
+Microsoft Docs: SCHANNEL_CRED structure.
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Protocols/HardCodedSecurityProtocol.ql b/cpp/ql/src/Microsoft/Security/Protocols/HardCodedSecurityProtocol.ql
new file mode 100644
index 000000000000..64c1be93c24e
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Protocols/HardCodedSecurityProtocol.ql
@@ -0,0 +1,19 @@
+/**
+ * @name Hard-coded use of a security protocol
+ * @description Hard-coding the security protocol used rather than specifying the system default is
+ * risky because the protocol may become deprecated in future.
+ * @kind problem
+ * @problem.severity warning
+ * @id cpp/microsoft/public/hardcoded-security-protocol
+ */
+
+import cpp
+import HardCodedSecurityProtocol
+
+from ProtocolConstant constantValue, DataFlow::Node grbitEnabledProtocolsAssignment
+where
+ GrbitEnabledConstantTace::flow(DataFlow::exprNode(constantValue), grbitEnabledProtocolsAssignment) and
+ constantValue.isHardcodedProtocol()
+select constantValue,
+ "Hard-coded use of security protocol " + getConstantName(constantValue) + " set here $@.",
+ grbitEnabledProtocolsAssignment, grbitEnabledProtocolsAssignment.toString()
diff --git a/cpp/ql/src/Microsoft/Security/Protocols/HardCodedSecurityProtocol.qll b/cpp/ql/src/Microsoft/Security/Protocols/HardCodedSecurityProtocol.qll
new file mode 100644
index 000000000000..1cc71668ccbe
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Protocols/HardCodedSecurityProtocol.qll
@@ -0,0 +1,140 @@
+import cpp
+import semmle.code.cpp.dataflow.new.TaintTracking
+
+/**
+ * A constant representing one or more security protocols for the `grbitEnabledProtocols` field.
+ */
+class ProtocolConstant extends Expr {
+ ProtocolConstant() {
+ this.isConstant() and
+ GrbitEnabledConstantTace::flow(DataFlow::exprNode(this), _) and
+ (
+ this instanceof Literal
+ or
+ this = any(ConstantMacroInvocation mi).getExpr()
+ or
+ // This is a workaround for folded constants, which currently have no
+ // dataflow node representation. Attach to the outermost dataflow node
+ // where a literal exists as a child that has no dataflow node representation.
+ exists(Literal l |
+ this.getAChild*() = l and
+ not exists(DataFlow::Node n | n.asExpr() = l)
+ )
+ )
+ }
+
+ /** Gets the bitmask represented by this constant. */
+ int getBitmask() { result = this.getValue().toInt() }
+
+ /** Holds if this constant only represents TLS1.3 protocols. */
+ predicate isTLS1_3Only() {
+ // Flags for TLS1.3 are 0x00001000 and 0x00002000
+ // 12288 = 0x00001000 | 0x00002000
+ this.getBitmask().bitAnd(12288.bitNot()) = 0 and
+ not this.isSystemDefault()
+ }
+
+ /** Holds if this constant only represents TLS1.2 protocols. */
+ predicate isTLS1_2Only() {
+ // Flags for TLS1.2 are 0x00000400 and 0x00000800
+ // 3072 = 0x00000400 | 0x00000800
+ this.getBitmask().bitAnd(3072.bitNot()) = 0 and
+ not this.isSystemDefault()
+ }
+
+ /** Holds if this constant only represents TLS1.1 protocols. */
+ predicate isTLS1_1Only() {
+ // Flags for TLS1.1 are 0x00000100 and 0x00000200
+ // 768 = 0x00000100 | 0x00000200
+ this.getBitmask().bitAnd(768.bitNot()) = 0 and
+ not this.isSystemDefault()
+ }
+
+ /** Holds if this constant only represents TLS1.0 protocols. */
+ predicate isTLS1_0Only() {
+ // Flags for TLS1.0 are 0x00000040 and 0x00000080
+ // 192 = 0x00000040 | 0x00000080
+ this.getBitmask().bitAnd(192.bitNot()) = 0 and
+ not this.isSystemDefault()
+ }
+
+ /** Holds if this constant only represents TLS1.1 protocols. */
+ predicate isSSL3Only() {
+ // Flags for SSL3 are 0x00000010 and 0x00000020
+ // 48 = 0x00000010 | 0x00000020
+ this.getBitmask().bitAnd(48.bitNot()) = 0 and
+ not this.isSystemDefault()
+ }
+
+ /** Holds if this constant only represents SSL2 protocols. */
+ predicate isSSL2Only() {
+ // Flags for TLS1.0 are 0x00000004 and 0x00000008
+ // 12 = 0x00000004 | 0x00000008
+ this.getBitmask().bitAnd(12.bitNot()) = 0 and
+ not this.isSystemDefault()
+ }
+
+ /** Holds if this constant only represents PCT1 protocols. */
+ predicate isPCT1Only() {
+ // Flags for PCT are 0x00000001 and 0x00000002
+ // 3 = 0x00000001 | 0x00000002
+ this.getBitmask().bitAnd(3.bitNot()) = 0 and
+ not this.isSystemDefault()
+ }
+
+ /** Holds if this constant only represents any combination of TLS-related protocols. */
+ predicate isHardcodedProtocol() {
+ // 16383 = SP_PROT_TLS1_3 | SP_PROT_TLS1_2 | SP_PROT_TLS1_1 | SP_PROT_TLS1_3
+ // | SP_PROT_TLS1 | SP_PROT_SSL3 | SP_PROT_SSL2 | SP_PROT_PCT1
+ this.getBitmask().bitAnd(16383.bitNot()) = 0 and
+ not this.isSystemDefault()
+ }
+
+ /** Holds if this constant represents the system default protocol. */
+ predicate isSystemDefault() { this.getBitmask() = 0 }
+}
+
+/**
+ * A data flow configuration that tracks from constant values to assignments to the
+ * `grbitEnabledProtocols` field on the SCHANNEL_CRED structure.
+ */
+module GrbitEnabledConstantConfiguration implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) { source.asExpr().isConstant() }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(Field grbitEnabledProtocols |
+ grbitEnabledProtocols.hasName("grbitEnabledProtocols") and
+ sink.asExpr() = grbitEnabledProtocols.getAnAssignedValue()
+ )
+ }
+
+ predicate isBarrier(DataFlow::Node node) {
+ // Do not flow through other macro invocations if they would, themselves, be represented
+ node.asExpr() = any(ConstantMacroInvocation mi).getExpr().getAChild+()
+ or
+ // Do not flow through complements, as they change the meaning
+ node.asExpr() instanceof ComplementExpr
+ }
+}
+
+module GrbitEnabledConstantTace = TaintTracking::Global;
+
+/**
+ * A macro that represents a constant value.
+ */
+class ConstantMacroInvocation extends MacroInvocation {
+ ConstantMacroInvocation() {
+ exists(this.getExpr().getValue()) and
+ not this.getMacro().getHead().matches("%(%)%")
+ }
+}
+
+/**
+ * Gets the name of the constant `val`, if it is a constant.
+ */
+string getConstantName(Expr val) {
+ exists(val.getValue()) and
+ if exists(ConstantMacroInvocation mi | mi.getExpr() = val)
+ then result = any(ConstantMacroInvocation mi | mi.getExpr() = val).getMacroName()
+ else result = val.toString()
+}
diff --git a/cpp/ql/src/Microsoft/Security/Protocols/UseOfDeprecatedSecurityProtocol.qhelp b/cpp/ql/src/Microsoft/Security/Protocols/UseOfDeprecatedSecurityProtocol.qhelp
new file mode 100644
index 000000000000..0876a4e50439
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Protocols/UseOfDeprecatedSecurityProtocol.qhelp
@@ -0,0 +1,29 @@
+
+
+
+
+Older protocol versions of TLS are less secure than TLS 1.2 and TLS 1.3 and are more likely to have new vulnerabilities. Avoid older protocol versions to minimize risk.
+The grbitEnabledProtocols
member of the SCHANNEL_CRED
struct contains a bit string that represents the protocols supported by connections made with credentials acquired by using this structure. If this member is zero, Schannel selects the protocol. Applications should set grbitEnabledProtocols
to zero and use the protocol versions enabled on the system by default.
+
+
+
+ - Set the grbitEnabledProtocols
member of the SCHANNEL_CRED
struct to 0
.
+
+
+
+Violation:
+
+
+
+Solution:
+
+
+
+
+
+Microsoft Docs: SCHANNEL_CRED structure.
+
+
+
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Protocols/UseOfDeprecatedSecurityProtocol.ql b/cpp/ql/src/Microsoft/Security/Protocols/UseOfDeprecatedSecurityProtocol.ql
new file mode 100644
index 000000000000..f9d957e15e26
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Protocols/UseOfDeprecatedSecurityProtocol.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Hard-coded use of a deprecated security protocol
+ * @description Using a deprecated security protocol rather than the system default is risky.
+ * @kind problem
+ * @problem.severity error
+ * @id cpp/microsoft/public/use-of-deprecated-security-protocol
+ */
+
+import cpp
+import HardCodedSecurityProtocol
+
+from ProtocolConstant constantValue, DataFlow::Node grbitEnabledProtocolsAssignment
+where
+ GrbitEnabledConstantTace::flow(DataFlow::exprNode(constantValue), grbitEnabledProtocolsAssignment) and
+ // If the system default hasn't been specified, and TLS2 has not been specified, then this is a deprecated security protocol
+ not constantValue.isSystemDefault() and
+ not constantValue.isTLS1_2Only() and
+ not constantValue.isTLS1_3Only()
+select constantValue,
+ "Hard-coded use of deprecated security protocol " + getConstantName(constantValue) +
+ " set here $@.", constantValue, getConstantName(constantValue)
diff --git a/cpp/ql/src/Microsoft/Security/Protocols/examples/HardCodedSecurityProtocol/HardCodedSecurityProtocol1.cpp b/cpp/ql/src/Microsoft/Security/Protocols/examples/HardCodedSecurityProtocol/HardCodedSecurityProtocol1.cpp
new file mode 100644
index 000000000000..3ad2eca405bb
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Protocols/examples/HardCodedSecurityProtocol/HardCodedSecurityProtocol1.cpp
@@ -0,0 +1,18 @@
+#include
+#include
+#include
+#include
+#include
+
+void HardCodedSecurityProtocolGood()
+{
+
+ SCHANNEL_CRED credData;
+ ZeroMemory(&credData, sizeof(credData));
+
+ // BAD: hardcoded protocols
+ credData.grbitEnabledProtocols = SP_PROT_TLS1_2;
+ credData.grbitEnabledProtocols = SP_PROT_TLS1_3;
+
+ return;
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Protocols/examples/HardCodedSecurityProtocol/HardCodedSecurityProtocol2.cpp b/cpp/ql/src/Microsoft/Security/Protocols/examples/HardCodedSecurityProtocol/HardCodedSecurityProtocol2.cpp
new file mode 100644
index 000000000000..7f5318248ef1
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Protocols/examples/HardCodedSecurityProtocol/HardCodedSecurityProtocol2.cpp
@@ -0,0 +1,17 @@
+#include
+#include
+#include
+#include
+#include
+
+void HardCodedSecurityProtocolGood()
+{
+
+ SCHANNEL_CRED credData;
+ ZeroMemory(&credData, sizeof(credData));
+
+ // GOOD: system default protocol
+ credData.grbitEnabledProtocols = 0;
+
+ return;
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Protocols/examples/UseOfDeprecatedSecurityProtocol/UseOfDeprecatedSecurityProtocol1.cpp b/cpp/ql/src/Microsoft/Security/Protocols/examples/UseOfDeprecatedSecurityProtocol/UseOfDeprecatedSecurityProtocol1.cpp
new file mode 100644
index 000000000000..d9b0ddc4af74
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Protocols/examples/UseOfDeprecatedSecurityProtocol/UseOfDeprecatedSecurityProtocol1.cpp
@@ -0,0 +1,23 @@
+#include
+#include
+#include
+#include
+#include
+
+void UseOfDeprecatedSecurityProtocolGood()
+{
+
+ SCHANNEL_CRED credData;
+ ZeroMemory(&credData, sizeof(credData));
+
+ // BAD: Deprecated protocols
+ credData.grbitEnabledProtocols = SP_PROT_PCT1_SERVER;
+ credData.grbitEnabledProtocols = SP_PROT_SSL2_SERVER;
+ credData.grbitEnabledProtocols = SP_PROT_SSL3_SERVER;
+ credData.grbitEnabledProtocols = SP_PROT_TLS1_1;
+ credData.grbitEnabledProtocols = SP_PROT_TLS1_1_SERVER;
+ credData.grbitEnabledProtocols = SP_PROT_TLS1_1_CLIENT;
+ credData.grbitEnabledProtocols = SP_PROT_SSL3TLS1;
+
+ return;
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Microsoft/Security/Protocols/examples/UseOfDeprecatedSecurityProtocol/UseOfDeprecatedSecurityProtocol2.cpp b/cpp/ql/src/Microsoft/Security/Protocols/examples/UseOfDeprecatedSecurityProtocol/UseOfDeprecatedSecurityProtocol2.cpp
new file mode 100644
index 000000000000..7f5318248ef1
--- /dev/null
+++ b/cpp/ql/src/Microsoft/Security/Protocols/examples/UseOfDeprecatedSecurityProtocol/UseOfDeprecatedSecurityProtocol2.cpp
@@ -0,0 +1,17 @@
+#include
+#include
+#include
+#include
+#include
+
+void HardCodedSecurityProtocolGood()
+{
+
+ SCHANNEL_CRED credData;
+ ZeroMemory(&credData, sizeof(credData));
+
+ // GOOD: system default protocol
+ credData.grbitEnabledProtocols = 0;
+
+ return;
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Security/CWE/CWE-129/ImproperArrayIndexValidation.ql b/cpp/ql/src/Security/CWE/CWE-129/ImproperArrayIndexValidation.ql
index b5dc4d893b21..3fd2be384a98 100644
--- a/cpp/ql/src/Security/CWE/CWE-129/ImproperArrayIndexValidation.ql
+++ b/cpp/ql/src/Security/CWE/CWE-129/ImproperArrayIndexValidation.ql
@@ -51,7 +51,10 @@ predicate offsetIsAlwaysInBounds(ArrayExpr arrayExpr, VariableAccess offsetExpr)
}
module ImproperArrayIndexValidationConfig implements DataFlow::ConfigSig {
- predicate isSource(DataFlow::Node source) { isFlowSource(source, _) }
+ predicate isSource(DataFlow::Node source) {
+ isFlowSource(source, _) and
+ not source.getLocation().getFile().getRelativePath().regexpMatch("(.*/)?tests?/.*")
+ }
predicate isBarrier(DataFlow::Node node) {
node = DataFlow::BarrierGuard::getABarrierNode()
@@ -71,7 +74,8 @@ module ImproperArrayIndexValidationConfig implements DataFlow::ConfigSig {
module ImproperArrayIndexValidation = TaintTracking::Global;
from
- ImproperArrayIndexValidation::PathNode source, ImproperArrayIndexValidation::PathNode sink,
+ ImproperArrayIndexValidation::PathNode source,
+ ImproperArrayIndexValidation::PathNode sink,
string sourceType
where
ImproperArrayIndexValidation::flowPath(source, sink) and
diff --git a/cpp/ql/src/experimental/cryptography/example_alerts/WeakEncryption.ql b/cpp/ql/src/experimental/cryptography/example_alerts/WeakEncryption.ql
index d8d5c4e4a566..a29a620675d4 100644
--- a/cpp/ql/src/experimental/cryptography/example_alerts/WeakEncryption.ql
+++ b/cpp/ql/src/experimental/cryptography/example_alerts/WeakEncryption.ql
@@ -2,7 +2,7 @@
* @name Weak cryptography
* @description Finds explicit uses of symmetric encryption algorithms that are weak, unknown, or otherwise unaccepted.
* @kind problem
- * @id cpp/weak-crypto/banned-encryption-algorithms
+ * @id cpp/experimental/weak-crypto/banned-encryption-algorithms
* @problem.severity error
* @precision high
* @tags external/cwe/cwe-327
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/Adding365DaysPerYear/Adding365daysPerYear.expected b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/Adding365DaysPerYear/Adding365daysPerYear.expected
index d9d9c4d3d338..094d84495ce4 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/Adding365DaysPerYear/Adding365daysPerYear.expected
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/Adding365DaysPerYear/Adding365daysPerYear.expected
@@ -1,5 +1,7 @@
-| test.cpp:173:29:173:51 | ... & ... | An arithmetic operation $@ that uses a constant value of 365 ends up modifying this date/time, without considering leap year scenarios. | test.cpp:170:2:170:47 | ... += ... | ... += ... |
-| test.cpp:174:30:174:45 | ... >> ... | An arithmetic operation $@ that uses a constant value of 365 ends up modifying this date/time, without considering leap year scenarios. | test.cpp:170:2:170:47 | ... += ... | ... += ... |
-| test.cpp:193:15:193:24 | ... / ... | An arithmetic operation $@ that uses a constant value of 365 ends up modifying this date/time, without considering leap year scenarios. | test.cpp:193:15:193:24 | ... / ... | ... / ... |
-| test.cpp:217:29:217:51 | ... & ... | An arithmetic operation $@ that uses a constant value of 365 ends up modifying this date/time, without considering leap year scenarios. | test.cpp:214:2:214:47 | ... += ... | ... += ... |
-| test.cpp:218:30:218:45 | ... >> ... | An arithmetic operation $@ that uses a constant value of 365 ends up modifying this date/time, without considering leap year scenarios. | test.cpp:214:2:214:47 | ... += ... | ... += ... |
+| test.cpp:175:29:175:51 | ... & ... | $@: This arithmetic operation $@ uses a constant value of 365 ends up modifying the date/time located at $@, without considering leap year scenarios. | test.cpp:159:6:159:17 | antipattern2 | antipattern2 | test.cpp:172:2:172:47 | ... += ... | ... += ... | test.cpp:175:29:175:51 | ... & ... | ... & ... |
+| test.cpp:176:30:176:45 | ... >> ... | $@: This arithmetic operation $@ uses a constant value of 365 ends up modifying the date/time located at $@, without considering leap year scenarios. | test.cpp:159:6:159:17 | antipattern2 | antipattern2 | test.cpp:172:2:172:47 | ... += ... | ... += ... | test.cpp:176:30:176:45 | ... >> ... | ... >> ... |
+| test.cpp:195:15:195:24 | ... / ... | $@: This arithmetic operation $@ uses a constant value of 365 ends up modifying the date/time located at $@, without considering leap year scenarios. | test.cpp:185:8:185:13 | mkTime | mkTime | test.cpp:195:15:195:24 | ... / ... | ... / ... | test.cpp:195:15:195:24 | ... / ... | ... / ... |
+| test.cpp:219:29:219:51 | ... & ... | $@: This arithmetic operation $@ uses a constant value of 365 ends up modifying the date/time located at $@, without considering leap year scenarios. | test.cpp:203:6:203:19 | checkedExample | checkedExample | test.cpp:216:2:216:47 | ... += ... | ... += ... | test.cpp:219:29:219:51 | ... & ... | ... & ... |
+| test.cpp:220:30:220:45 | ... >> ... | $@: This arithmetic operation $@ uses a constant value of 365 ends up modifying the date/time located at $@, without considering leap year scenarios. | test.cpp:203:6:203:19 | checkedExample | checkedExample | test.cpp:216:2:216:47 | ... += ... | ... += ... | test.cpp:220:30:220:45 | ... >> ... | ... >> ... |
+| test.cpp:247:29:247:51 | ... & ... | $@: This arithmetic operation $@ uses a constant value of 365 ends up modifying the date/time located at $@, without considering leap year scenarios. | test.cpp:230:6:230:18 | antipattern2A | antipattern2A | test.cpp:240:20:240:23 | 365 | 365 | test.cpp:247:29:247:51 | ... & ... | ... & ... |
+| test.cpp:248:30:248:45 | ... >> ... | $@: This arithmetic operation $@ uses a constant value of 365 ends up modifying the date/time located at $@, without considering leap year scenarios. | test.cpp:230:6:230:18 | antipattern2A | antipattern2A | test.cpp:240:20:240:23 | 365 | 365 | test.cpp:248:30:248:45 | ... >> ... | ... >> ... |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/Adding365DaysPerYear/test.cpp b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/Adding365DaysPerYear/test.cpp
index a14667c75ca5..ccc9bf8446e0 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/Adding365DaysPerYear/test.cpp
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/Adding365DaysPerYear/test.cpp
@@ -153,7 +153,9 @@ GetFileTime(
LPFILETIME lpLastWriteTime
);
-
+/**
+ * AntiPattern2 - datetime.AddDays(±365)
+*/
void antipattern2()
{
// get the current time as a FILETIME
@@ -223,3 +225,28 @@ void checkedExample()
// handle error...
}
}
+
+
+void antipattern2A()
+{
+ // get the current time as a FILETIME
+ SYSTEMTIME st; FILETIME ft;
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ft);
+
+ // convert to a quadword (64-bit integer) to do arithmetic
+ ULONGLONG qwLongTime;
+ qwLongTime = (((ULONGLONG)ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
+ int days_in_year = 365;
+
+ // add a year by calculating the ticks in 365 days
+ // (which may be incorrect when crossing a leap day)
+ qwLongTime += days_in_year * 24 * 60 * 60 * 10000000LLU;
+
+ // copy back to a FILETIME
+ ft.dwLowDateTime = (DWORD)(qwLongTime & 0xFFFFFFFF); // BAD
+ ft.dwHighDateTime = (DWORD)(qwLongTime >> 32); // BAD
+
+ // convert back to SYSTEMTIME for display or other usage
+ FileTimeToSystemTime(&ft, &st);
+}
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck/AntiPattern5InvalidLeapYearCheck.expected b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck/AntiPattern5InvalidLeapYearCheck.expected
new file mode 100644
index 000000000000..8e375a5ea5ce
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck/AntiPattern5InvalidLeapYearCheck.expected
@@ -0,0 +1,3 @@
+| test.cpp:183:23:183:35 | ... == ... | Possible Insufficient Leap Year check (AntiPattern 5) |
+| test.cpp:190:24:190:40 | ... == ... | Possible Insufficient Leap Year check (AntiPattern 5) |
+| test.cpp:245:6:245:18 | ... == ... | Possible Insufficient Leap Year check (AntiPattern 5) |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck/AntiPattern5InvalidLeapYearCheck.qlref b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck/AntiPattern5InvalidLeapYearCheck.qlref
new file mode 100644
index 000000000000..70e3f8ba1029
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck/AntiPattern5InvalidLeapYearCheck.qlref
@@ -0,0 +1 @@
+Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck.ql
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck/test.cpp b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck/test.cpp
new file mode 100644
index 000000000000..c0cd102321c1
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/AntiPattern5InvalidLeapYearCheck/test.cpp
@@ -0,0 +1,255 @@
+typedef unsigned short WORD;
+typedef unsigned long DWORD, HANDLE;
+typedef int BOOL, BOOLEAN, errno_t;
+typedef char CHAR;
+typedef short SHORT;
+typedef long LONG;
+typedef unsigned short WCHAR; // wc, 16-bit UNICODE character
+typedef long __time64_t, time_t;
+#define NULL 0
+
+typedef long long LONGLONG;
+typedef unsigned long long ULONGLONG;
+
+
+typedef struct _SYSTEMTIME {
+ WORD wYear;
+ WORD wMonth;
+ WORD wDayOfWeek;
+ WORD wDay;
+ WORD wHour;
+ WORD wMinute;
+ WORD wSecond;
+ WORD wMilliseconds;
+} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
+
+typedef struct _FILETIME {
+ DWORD dwLowDateTime;
+ DWORD dwHighDateTime;
+} FILETIME, *PFILETIME, *LPFILETIME;
+
+typedef struct _TIME_ZONE_INFORMATION {
+ LONG Bias;
+ WCHAR StandardName[32];
+ SYSTEMTIME StandardDate;
+ LONG StandardBias;
+ WCHAR DaylightName[32];
+ SYSTEMTIME DaylightDate;
+ LONG DaylightBias;
+} TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION, *LPTIME_ZONE_INFORMATION;
+
+typedef struct _TIME_DYNAMIC_ZONE_INFORMATION {
+ LONG Bias;
+ WCHAR StandardName[32];
+ SYSTEMTIME StandardDate;
+ LONG StandardBias;
+ WCHAR DaylightName[32];
+ SYSTEMTIME DaylightDate;
+ LONG DaylightBias;
+ WCHAR TimeZoneKeyName[128];
+ BOOLEAN DynamicDaylightTimeDisabled;
+} DYNAMIC_TIME_ZONE_INFORMATION, *PDYNAMIC_TIME_ZONE_INFORMATION;
+
+struct tm
+{
+ int tm_sec; // seconds after the minute - [0, 60] including leap second
+ int tm_min; // minutes after the hour - [0, 59]
+ int tm_hour; // hours since midnight - [0, 23]
+ int tm_mday; // day of the month - [1, 31]
+ int tm_mon; // months since January - [0, 11]
+ int tm_year; // years since 1900
+ int tm_wday; // days since Sunday - [0, 6]
+ int tm_yday; // days since January 1 - [0, 365]
+ int tm_isdst; // daylight savings time flag
+};
+
+BOOL
+SystemTimeToFileTime(
+ const SYSTEMTIME* lpSystemTime,
+ LPFILETIME lpFileTime
+);
+
+BOOL
+FileTimeToSystemTime(
+ const FILETIME* lpFileTime,
+ LPSYSTEMTIME lpSystemTime
+);
+
+BOOL
+SystemTimeToTzSpecificLocalTime(
+ const TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpUniversalTime,
+ LPSYSTEMTIME lpLocalTime
+);
+
+BOOL
+SystemTimeToTzSpecificLocalTimeEx(
+ const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpUniversalTime,
+ LPSYSTEMTIME lpLocalTime
+);
+
+BOOL
+TzSpecificLocalTimeToSystemTime(
+ const TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpLocalTime,
+ LPSYSTEMTIME lpUniversalTime
+);
+
+BOOL
+TzSpecificLocalTimeToSystemTimeEx(
+ const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpLocalTime,
+ LPSYSTEMTIME lpUniversalTime
+);
+
+void GetSystemTime(
+ LPSYSTEMTIME lpSystemTime
+);
+
+void GetSystemTimeAsFileTime(
+ LPFILETIME lpSystemTimeAsFileTime
+);
+
+__time64_t _mkgmtime64(
+ struct tm* _Tm
+);
+
+__time64_t _mkgmtime(
+ struct tm* const _Tm
+)
+{
+ return _mkgmtime64(_Tm);
+}
+
+__time64_t mktime(
+ struct tm* const _Tm
+)
+{
+ return _mkgmtime64(_Tm);
+}
+
+__time64_t _time64(
+ __time64_t* _Time
+);
+
+__time64_t time(
+ time_t* const _Time
+)
+{
+ return _time64(_Time);
+}
+
+int gmtime_s(
+ struct tm* _Tm,
+ __time64_t const* _Time
+);
+
+BOOL
+GetFileTime(
+ HANDLE hFile,
+ LPFILETIME lpCreationTime,
+ LPFILETIME lpLastAccessTime,
+ LPFILETIME lpLastWriteTime
+);
+
+time_t mktime(struct tm *timeptr);
+struct tm *gmtime(const time_t *timer);
+
+time_t mkTime(int days)
+{
+ struct tm tm;
+ time_t t;
+
+ tm.tm_sec = 0;
+ tm.tm_min = 0;
+ tm.tm_hour = 0;
+ tm.tm_mday = 0;
+ tm.tm_mon = 0;
+ tm.tm_year = days / 365; // BAD
+ // ...
+
+ t = mktime(&tm); // convert tm -> time_t
+
+ return t;
+}
+
+/**
+ * Positive AntiPattern 5 - year % 4 == 0
+*/
+void antipattern5()
+{
+ int year = 1;
+ bool isLeapYear = year % 4 == 0;
+
+ // get the current time as a FILETIME
+ SYSTEMTIME st; FILETIME ft;
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ft);
+
+ bool isLeapYear2 = st.wYear % 4 == 0;
+}
+
+/**
+ * Negative AntiPattern 5 - year % 4 == 0
+*/
+void antipattern5_negative()
+{
+ SYSTEMTIME st; FILETIME ft;
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ft);
+ bool isLeapYear = st.wYear % 4 == 0 && (st.wYear % 100 != 0 || st.wYear % 400 == 0);
+
+ int year = 1;
+ bool isLeapYear2 = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
+}
+
+/**
+* Negative - Valid Leap year check (logically equivalent) (#1035)
+*/
+bool ap5_negative_inverted_form(int year){
+ return year % 400 == 0 || (year % 100 != 0 && year % 4 == 0);
+}
+
+/**
+* Negative - Valid Leap Year check (#1035)
+* Century subexpression component is inverted `!(year % 100 == 0)`
+*/
+bool ap5_negative_inverted_century_100(int year){
+ return !((year % 4 == 0) && (!(year % 100 == 0) || (year % 400 == 0)));
+}
+
+class SomeResultClass{
+ public:
+ int GetYear() {
+ return 2000;
+ }
+};
+
+/**
+ * Negative - Valid Leap Year Check (#1038)
+ * Valid leap year check, but the expression is the result of a Call and thus breaks SSA.
+*/
+bool ap5_fp_expr_call(SomeResultClass result){
+ if (result.GetYear() % 4 == 0 && (result.GetYear() % 100 != 0 || result.GetYear() % 400 == 0)){
+ return true;
+ }
+ return false;
+}
+
+/**
+* Positive - Invalid Leap Year check
+* Components are split up and distributed across multiple if statements.
+*/
+bool tp_leap_year_multiple_if_statements(int year){
+ if (year % 4 == 0) {
+ if (year % 100 == 0) {
+ if (year % 400 == 0) {
+ return true;
+ }
+ }else{
+ return true;
+ }
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/LeapYearConditionalLogic/LeapYearConditionalLogic.cpp b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/LeapYearConditionalLogic/LeapYearConditionalLogic.cpp
new file mode 100644
index 000000000000..3f9b61c68505
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/LeapYearConditionalLogic/LeapYearConditionalLogic.cpp
@@ -0,0 +1,198 @@
+
+typedef unsigned short WORD;
+typedef unsigned long DWORD, HANDLE;
+typedef int BOOL, BOOLEAN, errno_t;
+typedef char CHAR;
+typedef short SHORT;
+typedef long LONG;
+typedef unsigned short WCHAR; // wc, 16-bit UNICODE character
+typedef long __time64_t, time_t;
+#define NULL 0
+
+typedef long long LONGLONG;
+typedef unsigned long long ULONGLONG;
+
+
+typedef struct _SYSTEMTIME {
+ WORD wYear;
+ WORD wMonth;
+ WORD wDayOfWeek;
+ WORD wDay;
+ WORD wHour;
+ WORD wMinute;
+ WORD wSecond;
+ WORD wMilliseconds;
+} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
+
+typedef struct _FILETIME {
+ DWORD dwLowDateTime;
+ DWORD dwHighDateTime;
+} FILETIME, *PFILETIME, *LPFILETIME;
+
+typedef struct _TIME_ZONE_INFORMATION {
+ LONG Bias;
+ WCHAR StandardName[32];
+ SYSTEMTIME StandardDate;
+ LONG StandardBias;
+ WCHAR DaylightName[32];
+ SYSTEMTIME DaylightDate;
+ LONG DaylightBias;
+} TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION, *LPTIME_ZONE_INFORMATION;
+
+typedef struct _TIME_DYNAMIC_ZONE_INFORMATION {
+ LONG Bias;
+ WCHAR StandardName[32];
+ SYSTEMTIME StandardDate;
+ LONG StandardBias;
+ WCHAR DaylightName[32];
+ SYSTEMTIME DaylightDate;
+ LONG DaylightBias;
+ WCHAR TimeZoneKeyName[128];
+ BOOLEAN DynamicDaylightTimeDisabled;
+} DYNAMIC_TIME_ZONE_INFORMATION, *PDYNAMIC_TIME_ZONE_INFORMATION;
+
+struct tm
+{
+ int tm_sec; // seconds after the minute - [0, 60] including leap second
+ int tm_min; // minutes after the hour - [0, 59]
+ int tm_hour; // hours since midnight - [0, 23]
+ int tm_mday; // day of the month - [1, 31]
+ int tm_mon; // months since January - [0, 11]
+ int tm_year; // years since 1900
+ int tm_wday; // days since Sunday - [0, 6]
+ int tm_yday; // days since January 1 - [0, 365]
+ int tm_isdst; // daylight savings time flag
+};
+
+BOOL
+SystemTimeToFileTime(
+ const SYSTEMTIME* lpSystemTime,
+ LPFILETIME lpFileTime
+);
+
+BOOL
+FileTimeToSystemTime(
+ const FILETIME* lpFileTime,
+ LPSYSTEMTIME lpSystemTime
+);
+
+BOOL
+SystemTimeToTzSpecificLocalTime(
+ const TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpUniversalTime,
+ LPSYSTEMTIME lpLocalTime
+);
+
+BOOL
+SystemTimeToTzSpecificLocalTimeEx(
+ const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpUniversalTime,
+ LPSYSTEMTIME lpLocalTime
+);
+
+BOOL
+TzSpecificLocalTimeToSystemTime(
+ const TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpLocalTime,
+ LPSYSTEMTIME lpUniversalTime
+);
+
+BOOL
+TzSpecificLocalTimeToSystemTimeEx(
+ const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation,
+ const SYSTEMTIME* lpLocalTime,
+ LPSYSTEMTIME lpUniversalTime
+);
+
+void GetSystemTime(
+ LPSYSTEMTIME lpSystemTime
+);
+
+void GetSystemTimeAsFileTime(
+ LPFILETIME lpSystemTimeAsFileTime
+);
+
+__time64_t _mkgmtime64(
+ struct tm* _Tm
+);
+
+__time64_t _mkgmtime(
+ struct tm* const _Tm
+)
+{
+ return _mkgmtime64(_Tm);
+}
+
+__time64_t mktime(
+ struct tm* const _Tm
+)
+{
+ return _mkgmtime64(_Tm);
+}
+
+__time64_t _time64(
+ __time64_t* _Time
+);
+
+__time64_t time(
+ time_t* const _Time
+)
+{
+ return _time64(_Time);
+}
+
+int gmtime_s(
+ struct tm* _Tm,
+ __time64_t const* _Time
+);
+
+BOOL
+GetFileTime(
+ HANDLE hFile,
+ LPFILETIME lpCreationTime,
+ LPFILETIME lpLastAccessTime,
+ LPFILETIME lpLastWriteTime
+);
+
+void print(const char* s);
+
+/**
+ * AntiPattern7 - isLeapYear Conditional
+*/
+void antipattern7()
+{
+ // get the current time as a FILETIME
+ SYSTEMTIME st; FILETIME ft;
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ft);
+
+ bool isLeapYear = st.wYear % 4 == 0 && (st.wYear % 100 != 0 || st.wYear % 400 == 0);
+ if(isLeapYear){
+ // do something to cater for a leap year....
+ print("It was a leap year");
+ }else{
+ // do another (different) thing
+ print("It was **not** a leap year");
+ }
+}
+
+time_t mktime(struct tm *timeptr);
+struct tm *gmtime(const time_t *timer);
+
+time_t mkTime(int days)
+{
+ struct tm tm;
+ time_t t;
+
+ tm.tm_sec = 0;
+ tm.tm_min = 0;
+ tm.tm_hour = 0;
+ tm.tm_mday = 0;
+ tm.tm_mon = 0;
+ tm.tm_year = days / 365; // BAD
+ // ...
+
+ t = mktime(&tm); // convert tm -> time_t
+
+ return t;
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/LeapYearConditionalLogic/LeapYearConditionalLogic.expected b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/LeapYearConditionalLogic/LeapYearConditionalLogic.expected
new file mode 100644
index 000000000000..be5f31d55769
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/LeapYearConditionalLogic/LeapYearConditionalLogic.expected
@@ -0,0 +1 @@
+| LeapYearConditionalLogic.cpp:170:5:176:5 | if (...) ... | Leap Year conditional statement may have untested code paths |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/LeapYearConditionalLogic/LeapYearConditionalLogic.qlref b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/LeapYearConditionalLogic/LeapYearConditionalLogic.qlref
new file mode 100644
index 000000000000..750ff8deb60a
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/LeapYearConditionalLogic/LeapYearConditionalLogic.qlref
@@ -0,0 +1 @@
+Likely Bugs/Leap Year/LeapYearConditionalLogic.ql
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedLeapYearAfterYearModification.expected b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedLeapYearAfterYearModification.expected
index a9c1bc66c50f..555002b40867 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedLeapYearAfterYearModification.expected
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedLeapYearAfterYearModification.expected
@@ -1,15 +1,8 @@
-| test.cpp:314:5:314:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:309:13:309:14 | st | st |
-| test.cpp:327:5:327:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:322:13:322:14 | st | st |
-| test.cpp:338:6:338:10 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:333:62:333:63 | st | st |
-| test.cpp:484:5:484:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:480:13:480:14 | st | st |
-| test.cpp:497:5:497:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:492:13:492:14 | st | st |
-| test.cpp:509:5:509:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:505:13:505:14 | st | st |
-| test.cpp:606:11:606:17 | tm_year | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:56:6:56:12 | tm_year | tm_year | test.cpp:602:12:602:19 | timeinfo | timeinfo |
-| test.cpp:634:11:634:17 | tm_year | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:56:6:56:12 | tm_year | tm_year | test.cpp:628:12:628:19 | timeinfo | timeinfo |
-| test.cpp:636:11:636:17 | tm_year | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:56:6:56:12 | tm_year | tm_year | test.cpp:628:12:628:19 | timeinfo | timeinfo |
-| test.cpp:640:5:640:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:629:13:629:14 | st | st |
-| test.cpp:642:5:642:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:629:13:629:14 | st | st |
-| test.cpp:718:5:718:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:712:13:712:14 | st | st |
-| test.cpp:731:5:731:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:725:13:725:14 | st | st |
-| test.cpp:732:5:732:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:725:13:725:14 | st | st |
-| test.cpp:733:5:733:9 | wYear | Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:725:13:725:14 | st | st |
+| test.cpp:617:2:617:11 | ... ++ | $@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:611:6:611:32 | AntiPattern_1_year_addition | AntiPattern_1_year_addition | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:613:13:613:14 | st | st |
+| test.cpp:634:2:634:25 | ... += ... | $@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:627:6:627:32 | AntiPattern_simple_addition | AntiPattern_simple_addition | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:629:13:629:14 | st | st |
+| test.cpp:763:2:763:19 | ... ++ | $@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:756:6:756:40 | AntiPattern_year_addition_struct_tm | AntiPattern_year_addition_struct_tm | test.cpp:56:6:56:12 | tm_year | tm_year | test.cpp:759:12:759:19 | timeinfo | timeinfo |
+| test.cpp:800:2:800:40 | ... = ... | $@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:791:6:791:23 | FalseNegativeTests | FalseNegativeTests | test.cpp:56:6:56:12 | tm_year | tm_year | test.cpp:793:12:793:19 | timeinfo | timeinfo |
+| test.cpp:803:2:803:43 | ... = ... | $@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:791:6:791:23 | FalseNegativeTests | FalseNegativeTests | test.cpp:56:6:56:12 | tm_year | tm_year | test.cpp:793:12:793:19 | timeinfo | timeinfo |
+| test.cpp:808:2:808:24 | ... = ... | $@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:791:6:791:23 | FalseNegativeTests | FalseNegativeTests | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:794:13:794:14 | st | st |
+| test.cpp:811:2:811:33 | ... = ... | $@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:791:6:791:23 | FalseNegativeTests | FalseNegativeTests | test.cpp:12:7:12:11 | wYear | wYear | test.cpp:794:13:794:14 | st | st |
+| test.cpp:850:3:850:36 | ... = ... | $@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found. | test.cpp:818:6:818:23 | tp_intermediaryVar | tp_intermediaryVar | test.cpp:56:6:56:12 | tm_year | tm_year | test.cpp:70:18:70:19 | tm | tm |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedReturnValueForTimeFunctions.expected b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedReturnValueForTimeFunctions.expected
index fb79592b7f2d..ae8a55449daf 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedReturnValueForTimeFunctions.expected
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedReturnValueForTimeFunctions.expected
@@ -1,5 +1,5 @@
-| test.cpp:317:2:317:21 | call to SystemTimeToFileTime | Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:63:1:63:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:309:13:309:14 | st | st |
-| test.cpp:330:2:330:21 | call to SystemTimeToFileTime | Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:63:1:63:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:322:13:322:14 | st | st |
-| test.cpp:341:2:341:21 | call to SystemTimeToFileTime | Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:63:1:63:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:333:62:333:63 | st | st |
-| test.cpp:720:2:720:21 | call to SystemTimeToFileTime | Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:63:1:63:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:712:13:712:14 | st | st |
-| test.cpp:735:2:735:21 | call to SystemTimeToFileTime | Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:63:1:63:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:725:13:725:14 | st | st |
+| test.cpp:395:2:395:21 | call to SystemTimeToFileTime | $@: Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:385:6:385:48 | AntiPattern_unchecked_filetime_conversion2a | AntiPattern_unchecked_filetime_conversion2a | test.cpp:75:1:75:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:387:13:387:14 | st | st |
+| test.cpp:413:2:413:21 | call to SystemTimeToFileTime | $@: Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:403:6:403:48 | AntiPattern_unchecked_filetime_conversion2b | AntiPattern_unchecked_filetime_conversion2b | test.cpp:75:1:75:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:405:13:405:14 | st | st |
+| test.cpp:429:2:429:21 | call to SystemTimeToFileTime | $@: Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:421:6:421:48 | AntiPattern_unchecked_filetime_conversion2b | AntiPattern_unchecked_filetime_conversion2b | test.cpp:75:1:75:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:421:62:421:63 | st | st |
+| test.cpp:948:3:948:22 | call to SystemTimeToFileTime | $@: Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:938:7:938:15 | modified3 | modified3 | test.cpp:75:1:75:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:940:14:940:15 | st | st |
+| test.cpp:965:3:965:22 | call to SystemTimeToFileTime | $@: Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe. | test.cpp:955:7:955:15 | modified4 | modified4 | test.cpp:75:1:75:20 | SystemTimeToFileTime | SystemTimeToFileTime | test.cpp:957:14:957:15 | st | st |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp
index 3db9b61edd2b..beb2c4061496 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp
@@ -59,6 +59,18 @@ struct tm
int tm_isdst; // daylight savings time flag
};
+struct timespec
+{
+ time_t tv_sec;
+ long tv_nsec;
+};
+
+/* Timestamps of log entries. */
+struct logtime {
+ struct tm tm;
+ long usec;
+};
+
BOOL
SystemTimeToFileTime(
const SYSTEMTIME* lpSystemTime,
@@ -102,6 +114,9 @@ TzSpecificLocalTimeToSystemTimeEx(
void GetSystemTime(
LPSYSTEMTIME lpSystemTime
);
+void GetLocalTime(
+ LPSYSTEMTIME lpSystemTime
+);
void GetSystemTimeAsFileTime(
LPFILETIME lpSystemTimeAsFileTime
@@ -149,6 +164,12 @@ GetFileTime(
LPFILETIME lpLastWriteTime
);
+struct tm *localtime_r( const time_t *timer, struct tm *buf );
+
+/**
+ * Negative Case
+ * FileTimeToSystemTime is called and the return value is checked
+*/
void Correct_FileTimeToSystemTime(const FILETIME* lpFileTime)
{
SYSTEMTIME systemTime;
@@ -162,6 +183,10 @@ void Correct_FileTimeToSystemTime(const FILETIME* lpFileTime)
/// Normal usage
}
+/**
+ * Positive (Out of Scope) Bug Case
+ * FileTimeToSystemTime is called but no check is conducted to verify the result of the operation
+*/
void AntiPattern_FileTimeToSystemTime(const FILETIME* lpFileTime)
{
SYSTEMTIME systemTime;
@@ -170,6 +195,10 @@ void AntiPattern_FileTimeToSystemTime(const FILETIME* lpFileTime)
FileTimeToSystemTime(lpFileTime, &systemTime);
}
+/**
+ * Negative Case
+ * SystemTimeToTzSpecificLocalTime is called and the return value is verified
+*/
void Correct_SystemTimeToTzSpecificLocalTime(const TIME_ZONE_INFORMATION *lpTimeZoneInformation, const SYSTEMTIME *lpUniversalTime)
{
SYSTEMTIME localTime;
@@ -183,6 +212,10 @@ void Correct_SystemTimeToTzSpecificLocalTime(const TIME_ZONE_INFORMATION *lpTime
/// Normal usage
}
+/**
+ * Positive (Out of Scope) Case
+ * AntiPattern_SystemTimeToTzSpecificLocalTime is called but the return value is not validated
+*/
void AntiPattern_SystemTimeToTzSpecificLocalTime(const TIME_ZONE_INFORMATION *lpTimeZoneInformation, const SYSTEMTIME *lpUniversalTime)
{
SYSTEMTIME localTime;
@@ -191,6 +224,10 @@ void AntiPattern_SystemTimeToTzSpecificLocalTime(const TIME_ZONE_INFORMATION *lp
SystemTimeToTzSpecificLocalTime(lpTimeZoneInformation, lpUniversalTime, &localTime);
}
+/**
+ * Negative Case
+ * SystemTimeToTzSpecificLocalTimeEx is called and the return value is validated
+*/
void Correct_SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION *lpTimeZoneInformation, const SYSTEMTIME *lpUniversalTime)
{
SYSTEMTIME localTime;
@@ -204,6 +241,10 @@ void Correct_SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATI
/// Normal usage
}
+/**
+ * Positive Case
+ * SystemTimeToTzSpecificLocalTimeEx is called but the return value is not validated
+*/
void AntiPattern_SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION *lpTimeZoneInformation, const SYSTEMTIME *lpUniversalTime)
{
SYSTEMTIME localTime;
@@ -212,6 +253,10 @@ void AntiPattern_SystemTimeToTzSpecificLocalTimeEx(const DYNAMIC_TIME_ZONE_INFOR
SystemTimeToTzSpecificLocalTimeEx(lpTimeZoneInformation, lpUniversalTime, &localTime);
}
+/**
+ * Negative Case
+ * Correct use of TzSpecificLocalTimeToSystemTime, function is called and the return value is validated.
+*/
void Correct_TzSpecificLocalTimeToSystemTime(const TIME_ZONE_INFORMATION *lpTimeZoneInformation, const SYSTEMTIME *lpLocalTime)
{
SYSTEMTIME universalTime;
@@ -225,6 +270,10 @@ void Correct_TzSpecificLocalTimeToSystemTime(const TIME_ZONE_INFORMATION *lpTime
/// Normal usage
}
+/**
+ * Positive (Out of Scope) Case
+ * TzSpecificLocalTimeToSystemTime is called however the return value is not validated
+*/
void AntiPattern_TzSpecificLocalTimeToSystemTime(const TIME_ZONE_INFORMATION *lpTimeZoneInformation, const SYSTEMTIME *lpLocalTime)
{
SYSTEMTIME universalTime;
@@ -233,6 +282,10 @@ void AntiPattern_TzSpecificLocalTimeToSystemTime(const TIME_ZONE_INFORMATION *lp
TzSpecificLocalTimeToSystemTime(lpTimeZoneInformation, lpLocalTime, &universalTime);
}
+/**
+ * Negative Case
+ * TzSpecificLocalTimeToSystemTimeEx is called and the return value is correctly validated
+*/
void Correct_TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION *lpTimeZoneInformation, const SYSTEMTIME *lpLocalTime)
{
SYSTEMTIME universalTime;
@@ -246,6 +299,10 @@ void Correct_TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATI
/// Normal usage
}
+/**
+ * Positive (Out of Scope) Case
+ * TzSpecificLocalTimeToSystemTimeEx is called however the return value is not validated
+*/
void AntiPattern_TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFORMATION *lpTimeZoneInformation, const SYSTEMTIME *lpLocalTime)
{
SYSTEMTIME universalTime;
@@ -258,6 +315,10 @@ void AntiPattern_TzSpecificLocalTimeToSystemTimeEx(const DYNAMIC_TIME_ZONE_INFOR
SYSTEMTIME Cases
*************************************************/
+/**
+ * Negative Case
+ * SystemTimeToFileTime is called and the return value is validated in a guard
+*/
void Correct_filetime_conversion_check(SYSTEMTIME& st)
{
FILETIME ft;
@@ -273,6 +334,10 @@ void Correct_filetime_conversion_check(SYSTEMTIME& st)
//////////////////////////////////////////////
+/**
+ * Positive (Out of Scope) Case
+ * SystemTimeToFileTime is called but the return value is not validated in a guard
+*/
void AntiPattern_unchecked_filetime_conversion(SYSTEMTIME& st)
{
FILETIME ft;
@@ -281,6 +346,10 @@ void AntiPattern_unchecked_filetime_conversion(SYSTEMTIME& st)
SystemTimeToFileTime(&st, &ft);
}
+/**
+ * Positive (Out of Scope) Case
+ * SystemTimeToFileTime is called but the return value is not validated in a guard
+*/
void AntiPattern_unchecked_filetime_conversion2(SYSTEMTIME* st)
{
FILETIME ft;
@@ -292,6 +361,10 @@ void AntiPattern_unchecked_filetime_conversion2(SYSTEMTIME* st)
}
}
+/**
+ * Positive (Out of Scope)
+ * SYSTEMTIME.wDay is incremented by one (and no guard exists)
+*/
void AntiPattern_unchecked_filetime_conversion2()
{
SYSTEMTIME st;
@@ -304,6 +377,11 @@ void AntiPattern_unchecked_filetime_conversion2()
SystemTimeToFileTime(&st, &ft);
}
+/**
+ * Positive Cases
+ * - Anti-pattern 1: [year ±n, month, day]
+ * - Generic (Out of Scope) - UncheckedReturnValueForTimeFunctions
+*/
void AntiPattern_unchecked_filetime_conversion2a()
{
SYSTEMTIME st;
@@ -317,6 +395,11 @@ void AntiPattern_unchecked_filetime_conversion2a()
SystemTimeToFileTime(&st, &ft);
}
+/**
+ * Positive Cases
+ * - Anti-pattern 1: [year ±n, month, day]
+ * - Generic (Out of Scope) - UncheckedReturnValueForTimeFunctions
+*/
void AntiPattern_unchecked_filetime_conversion2b()
{
SYSTEMTIME st;
@@ -330,6 +413,11 @@ void AntiPattern_unchecked_filetime_conversion2b()
SystemTimeToFileTime(&st, &ft);
}
+/**
+ * Positive Cases
+ * - Anti-pattern 1: [year ±n, month, day]
+ * - Generic (Out of Scope) - UncheckedReturnValueForTimeFunctions
+*/
void AntiPattern_unchecked_filetime_conversion2b(SYSTEMTIME* st)
{
FILETIME ft;
@@ -341,6 +429,11 @@ void AntiPattern_unchecked_filetime_conversion2b(SYSTEMTIME* st)
SystemTimeToFileTime(st, &ft);
}
+/**
+ * Positive Cases
+ * - Anti-pattern 3: datetime.AddDays(±28)
+ * - Generic (Out of Scope) - UncheckedReturnValueForTimeFunctions
+*/
void AntiPattern_unchecked_filetime_conversion3()
{
SYSTEMTIME st;
@@ -349,11 +442,12 @@ void AntiPattern_unchecked_filetime_conversion3()
if (st.wMonth < 12)
{
+ // Anti-pattern 3: datetime.AddDays(±28)
st.wMonth++;
}
else
{
- // Check for leap year, but...
+ // No check for leap year is required here, as the month is statically set to January.
st.wMonth = 1;
st.wYear++;
}
@@ -363,6 +457,11 @@ void AntiPattern_unchecked_filetime_conversion3()
}
//////////////////////////////////////////////
+
+/**
+ * Negative Case - Anti-pattern 1: [year ±n, month, day]
+ * Year is incremented and if we are on Feb the 29th, set to the 28th if the new year is a common year.
+*/
void CorrectPattern_check1()
{
SYSTEMTIME st;
@@ -370,7 +469,7 @@ void CorrectPattern_check1()
st.wYear++;
- // Guard
+ // Guard against February the 29th
if (st.wMonth == 2 && st.wDay == 29)
{
// move back a day when landing on Feb 29 in an non-leap year
@@ -385,6 +484,10 @@ void CorrectPattern_check1()
AntiPattern_unchecked_filetime_conversion(st);
}
+/**
+ * Negative Case - Anti-pattern 1: [year ±n, month, day]
+ * Years is incremented by some integer and then the leap year case is correctly guarded and handled.
+*/
void CorrectPattern_check2(int yearsToAdd)
{
SYSTEMTIME st;
@@ -400,11 +503,18 @@ void CorrectPattern_check2(int yearsToAdd)
AntiPattern_unchecked_filetime_conversion(st);
}
+/**
+ * Could give rise to AntiPattern 7: IsLeapYear (Conditional Logic)
+*/
bool isLeapYear(SYSTEMTIME& st)
{
return st.wYear % 4 == 0 && (st.wYear % 100 != 0 || st.wYear % 400 == 0);
}
+/**
+ * Negative Case - Anti-pattern 1: [year ±n, month, day]
+ * Years is incremented by some integer and then the leap year case is correctly guarded and handled.
+*/
void CorrectPattern_check3()
{
SYSTEMTIME st;
@@ -413,6 +523,9 @@ void CorrectPattern_check3()
st.wYear++;
// Guard
+ /** Negative Case - Anti-pattern 7: IsLeapYear
+ * Body of conditional statement is safe recommended code
+ */
if (st.wMonth == 2 && st.wDay == 29 && isLeapYear(st))
{
// move back a day when landing on Feb 29 in an non-leap year
@@ -423,6 +536,9 @@ void CorrectPattern_check3()
AntiPattern_unchecked_filetime_conversion(st);
}
+/**
+ * Could give rise to AntiPattern 7: IsLeapYear (Conditional Logic)
+*/
bool isLeapYear2(int year)
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
@@ -433,6 +549,10 @@ bool fixDate(int day, int month, int year)
return (month == 2 && day == 29 && isLeapYear2(year));
}
+/**
+ * Negative Case - Anti-pattern 1: [year ±n, month, day]
+ * Years is incremented by some integer and then the leap year case is correctly guarded and handled.
+*/
void CorrectPattern_check4()
{
SYSTEMTIME st;
@@ -442,18 +562,23 @@ void CorrectPattern_check4()
st.wYear++;
// Guard
+ /** Negative Case - Anti-pattern 7: IsLeapYear
+ * Body of conditional statement is safe recommended code
+ */
if (fixDate(st.wDay, st.wMonth, st.wYear))
{
// move back a day when landing on Feb 29 in an non-leap year
- st.wDay = 28; // GOOD [FALSE POSITIVE]
+ st.wDay = 28; // GOOD [FALSE POSITIVE] Anti-pattern 7
}
// Safe to use
AntiPattern_unchecked_filetime_conversion(st);
}
-
-
+/**
+ * Negative Case - Generic
+ * No manipulation is conducted on struct populated from GetSystemTime.
+*/
void CorrectPattern_NotManipulated_DateFromAPI_0()
{
SYSTEMTIME st;
@@ -464,6 +589,10 @@ void CorrectPattern_NotManipulated_DateFromAPI_0()
SystemTimeToFileTime(&st, &ft);
}
+/**
+ * Negative Case - Generic
+ * No manipulation is conducted on struct populated from GetFileTime.
+*/
void CorrectPattern_NotManipulated_DateFromAPI_1(HANDLE hWatchdog)
{
SYSTEMTIME st;
@@ -475,18 +604,26 @@ void CorrectPattern_NotManipulated_DateFromAPI_1(HANDLE hWatchdog)
/////////////////////////////////////////////////////////////////
+/**
+ * Positive Case - Anti-pattern 1: [year ±n, month, day]
+ * Years is incremented by some integer but a leap year is not handled.
+*/
void AntiPattern_1_year_addition()
{
SYSTEMTIME st;
GetSystemTime(&st);
- // BUG - UncheckedLeapYearAfterYearModification
- st.wYear++;
+ // BUG - UncheckedLeapYearAfterYearModification
+ st.wYear++; // BUg V2
// Usage of potentially invalid date
Correct_filetime_conversion_check(st);
}
+/**
+ * Positive Case - Anti-pattern 1: [year ±n, month, day]
+ * Years is incremented by some integer but a leap year is not handled.
+*/
void AntiPattern_simple_addition(int yearAddition)
{
SYSTEMTIME st;
@@ -494,12 +631,16 @@ void AntiPattern_simple_addition(int yearAddition)
GetSystemTime(&st);
// BUG - UncheckedLeapYearAfterYearModification
- st.wYear += yearAddition;
+ st.wYear += yearAddition; // Bug V2
// Usage of potentially invalid date
Correct_filetime_conversion_check(st);
}
+/**
+ * Positive Case - Anti-pattern 1: [year ±n, month, day]
+ * Years is incremented by some integer but a leap year is not handled *correctly*.
+*/
void AntiPattern_IncorrectGuard(int yearsToAdd)
{
SYSTEMTIME st;
@@ -511,7 +652,7 @@ void AntiPattern_IncorrectGuard(int yearsToAdd)
// Incorrect Guard
if (st.wMonth == 2 && st.wDay == 29)
{
- // Part of a different anti-pattern.
+ // Part of a different anti-pattern (AntiPattern 5).
// Make sure the guard includes the proper check
bool isLeapYear = st.wYear % 4 == 0;
if (!isLeapYear)
@@ -539,6 +680,10 @@ void CorrectUsageOf_mkgmtime(struct tm& timeinfo)
/// _mkgmtime succeeded
}
+/**
+ * Positive Case - General (Out of Scope)
+ * Must Check for return value of _mkgmtime
+*/
void AntiPattern_uncheckedUsageOf_mkgmtime(struct tm& timeinfo)
{
// (out-of-scope) GeneralBug: Must check return value for _mkgmtime
@@ -550,6 +695,10 @@ void AntiPattern_uncheckedUsageOf_mkgmtime(struct tm& timeinfo)
//////////////////////////////////////////////////////////
+/**
+ * Negative Case - Anti-pattern 1: [year ±n, month, day]
+ * Years is incremented by some integer and leap year is not handled correctly.
+*/
void Correct_year_addition_struct_tm()
{
time_t rawtime;
@@ -575,6 +724,10 @@ void Correct_year_addition_struct_tm()
AntiPattern_uncheckedUsageOf_mkgmtime(timeinfo);
}
+/**
+ * Negative Case - Anti-pattern 1: [year ±n, month, day]
+ * Years is incremented by some integer and leap year is not handled correctly.
+*/
void Correct_LinuxPattern()
{
time_t rawtime;
@@ -596,6 +749,10 @@ void Correct_LinuxPattern()
//////////////////////////////////////////
+/**
+ * Negative Case - Anti-pattern 1: [year ±n, month, day]
+ * Years is incremented by some integer and leap year is not handled correctly.
+*/
void AntiPattern_year_addition_struct_tm()
{
time_t rawtime;
@@ -603,7 +760,7 @@ void AntiPattern_year_addition_struct_tm()
time(&rawtime);
gmtime_s(&timeinfo, &rawtime);
// BUG - UncheckedLeapYearAfterYearModification
- timeinfo.tm_year++;
+ timeinfo.tm_year++; // Bug V2
// Usage of potentially invalid date
CorrectUsageOf_mkgmtime(timeinfo);
@@ -611,6 +768,10 @@ void AntiPattern_year_addition_struct_tm()
/////////////////////////////////////////////////////////
+/**
+ * Negative Case - Anti-pattern 1: [year ±n, month, day]
+ * False positive: Years is initialized to or incremented by some integer (but never used).
+*/
void FalsePositiveTests(int x)
{
struct tm timeinfo;
@@ -623,6 +784,10 @@ void FalsePositiveTests(int x)
st.wYear = 1900 + x;
}
+/**
+ * Positive Case - Anti-pattern 1: [year ±n, month, day]
+ * False positive: Years is initialized to or incremented by some integer (but never used).
+*/
void FalseNegativeTests(int x)
{
struct tm timeinfo;
@@ -631,106 +796,211 @@ void FalseNegativeTests(int x)
timeinfo.tm_year = x;
// BUG - UncheckedLeapYearAfterYearModification
- timeinfo.tm_year = x + timeinfo.tm_year;
+ // Positive Case - Anti-pattern 1: [year ±n, month, day]
+ timeinfo.tm_year = x + timeinfo.tm_year; // Bug V2
// BUG - UncheckedLeapYearAfterYearModification
- timeinfo.tm_year = 1970 + timeinfo.tm_year;
+ // Positive Case - Anti-pattern 1: [year ±n, month, day]
+ timeinfo.tm_year = 1970 + timeinfo.tm_year; // Bug V2
st.wYear = x;
// BUG - UncheckedLeapYearAfterYearModification
- st.wYear = x + st.wYear;
+ // Positive Case - Anti-pattern 1: [year ±n, month, day]
+ st.wYear = x + st.wYear; // Bug V2
// BUG - UncheckedLeapYearAfterYearModification
- st.wYear = (1986 + st.wYear) - 1;
+ // Positive Case - Anti-pattern 1: [year ±n, month, day]
+ st.wYear = (1986 + st.wYear) - 1; // Bug V2
+}
+
+/**
+ * Positive AntiPattern 1
+ * Year field is modified but via an intermediary variable.
+*/
+bool tp_intermediaryVar(struct timespec now, struct logtime ×tamp_remote)
+{
+ struct tm tm_parsed;
+ bool timestamp_found = false;
+
+ struct tm tm_now;
+ time_t t_now;
+ int year;
+
+ timestamp_found = true;
+
+ /*
+ * As the timestamp does not contain the year
+ * number, daylight saving time information, nor
+ * a time zone, attempt to infer it. Due to
+ * clock skews, the timestamp may even be part
+ * of the next year. Use the last year for which
+ * the timestamp is at most one week in the
+ * future.
+ *
+ * This loop can only run for at most three
+ * iterations before terminating.
+ */
+ t_now = now.tv_sec;
+ localtime_r(&t_now, &tm_now);
+
+ timestamp_remote.tm = tm_parsed;
+ timestamp_remote.tm.tm_isdst = -1;
+ timestamp_remote.usec = now.tv_nsec * 0.001;
+ for (year = tm_now.tm_year + 1;; --year)
+ {
+ // assert(year >= tm_now.tm_year - 1);
+ timestamp_remote.tm.tm_year = year;
+ if (mktime(×tamp_remote.tm) < t_now + 7 * 24 * 60 * 60)
+ break;
+ }
}
-// False positive
-inline void
-IncrementMonth(LPSYSTEMTIME pst)
-{
- if (pst->wMonth < 12)
+
+ // False positive
+ inline void
+ IncrementMonth(LPSYSTEMTIME pst)
{
- pst->wMonth++;
+ if (pst->wMonth < 12)
+ {
+ pst->wMonth++;
+ }
+ else
+ {
+ pst->wMonth = 1;
+ pst->wYear++;
+ }
}
- else
+
+ /////////////////////////////////////////////////////////
+
+ void mkDateTest(int year)
{
- pst->wMonth = 1;
- pst->wYear++;
+ struct tm t;
+
+ t.tm_sec = 0;
+ t.tm_min = 0;
+ t.tm_hour = 0;
+ t.tm_mday = 1; // day of the month - [1, 31]
+ t.tm_mon = 0; // months since January - [0, 11]
+ if (year >= 1900)
+ {
+ // 4-digit year
+ t.tm_year = year - 1900; // GOOD
+ }
+ else if ((year >= 0) && (year < 100))
+ {
+ // 2-digit year assumed in the range 2000 - 2099
+ t.tm_year = year + 100; // GOOD [FALSE POSITIVE]
+ }
+ else
+ {
+ // fail
+ }
+ // ...
}
-}
-/////////////////////////////////////////////////////////
+ /**
+ * Negative Case - Anti-pattern 1a: [a.year, b.month, b.day]
+ * False positive: No modification of SYSTEMTIME struct.
+ */
+ void unmodified1()
+ {
+ SYSTEMTIME st;
+ FILETIME ft;
+ WORD w;
-void mkDateTest(int year)
-{
- struct tm t;
+ GetSystemTime(&st);
- t.tm_sec = 0;
- t.tm_min = 0;
- t.tm_hour = 0;
- t.tm_mday = 1; // day of the month - [1, 31]
- t.tm_mon = 0; // months since January - [0, 11]
- if (year >= 1900)
- {
- // 4-digit year
- t.tm_year = year - 1900; // GOOD
- } else if ((year >= 0) && (year < 100)) {
- // 2-digit year assumed in the range 2000 - 2099
- t.tm_year = year + 100; // GOOD [FALSE POSITIVE]
- } else {
- // fail
+ w = st.wYear;
+
+ SystemTimeToFileTime(&st, &ft); // GOOD - no modification
}
- // ...
-}
-void unmodified1()
-{
- SYSTEMTIME st;
- FILETIME ft;
- WORD w;
+ /**
+ * Negative Case - Anti-pattern 1a: [a.year, b.month, b.day]
+ * False positive: No modification of SYSTEMTIME struct.
+ */
+ void unmodified2()
+ {
+ SYSTEMTIME st;
+ FILETIME ft;
+ WORD *w_ptr;
- GetSystemTime(&st);
+ GetSystemTime(&st);
- w = st.wYear;
+ w_ptr = &(st.wYear);
- SystemTimeToFileTime(&st, &ft); // GOOD - no modification
-}
+ SystemTimeToFileTime(&st, &ft); // GOOD - no modification
+ }
-void unmodified2()
-{
- SYSTEMTIME st;
- FILETIME ft;
- WORD *w_ptr;
+ /**
+ * Positive Case - Anti-pattern 1: [year ±n, month, day]
+ * Modification of SYSTEMTIME struct adding to year but no leap year guard is conducted.
+ */
+ void modified3()
+ {
+ SYSTEMTIME st;
+ FILETIME ft;
+ WORD *w_ptr;
- GetSystemTime(&st);
+ GetSystemTime(&st);
- w_ptr = &(st.wYear);
+ st.wYear = st.wYear + 1; // BAD
- SystemTimeToFileTime(&st, &ft); // GOOD - no modification
-}
+ SystemTimeToFileTime(&st, &ft);
+ }
-void modified3()
-{
- SYSTEMTIME st;
- FILETIME ft;
- WORD *w_ptr;
+ /**
+ * Positive Case - Anti-pattern 1: [year ±n, month, day]
+ * Modification of SYSTEMTIME struct adding to year but no leap year guard is conducted.
+ */
+ void modified4()
+ {
+ SYSTEMTIME st;
+ FILETIME ft;
+ WORD *w_ptr;
- GetSystemTime(&st);
+ GetSystemTime(&st);
- st.wYear = st.wYear + 1; // BAD
+ st.wYear++; // BAD Positive Case - Anti-pattern 1: [year ±n, month, day]
- SystemTimeToFileTime(&st, &ft);
-}
+ SystemTimeToFileTime(&st, &ft);
+ }
-void modified4()
-{
- SYSTEMTIME st;
- FILETIME ft;
- WORD *w_ptr;
+ /**
+ * Negative Case - Anti-pattern 1: [year ±n, month, day]
+ * Modification of SYSTEMTIME struct adding to year but no leap year guard is conducted.
+ */
+ void modified5()
+ {
+ SYSTEMTIME st;
+ FILETIME ft;
+ WORD *w_ptr;
- GetSystemTime(&st);
+ GetSystemTime(&st);
- st.wYear++; // BAD
- st.wYear++; // BAD
- st.wYear++; // BAD
+ st.wYear++; // Negative Case - Anti-pattern 1: [year ±n, month, day], guard condition below.
- SystemTimeToFileTime(&st, &ft);
-}
+ if (SystemTimeToFileTime(&st, &ft))
+ {
+ ///...
+ }
+ }
+
+ struct tm ltime(void)
+ {
+ SYSTEMTIME st;
+ struct tm tm;
+ bool isLeapYear;
+
+ GetLocalTime(&st);
+ tm.tm_sec=st.wSecond;
+ tm.tm_min=st.wMinute;
+ tm.tm_hour=st.wHour;
+ tm.tm_mday=st.wDay;
+ tm.tm_mon=st.wMonth-1;
+ tm.tm_year=(st.wYear>=1900?st.wYear-1900:0);
+
+ // Check for leap year, and adjust the date accordingly
+ isLeapYear = tm.tm_year % 4 == 0 && (tm.tm_year % 100 != 0 || tm.tm_year % 400 == 0);
+ tm.tm_mday = tm.tm_mon == 2 && tm.tm_mday == 29 && !isLeapYear ? 28 : tm.tm_mday;
+ return tm;
+ }
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/GlobalFp.cpp b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/GlobalFp.cpp
new file mode 100644
index 000000000000..abed05719fa6
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/GlobalFp.cpp
@@ -0,0 +1,2 @@
+int NormalYear[365];
+int LeapYear[366];
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/UnsafeArrayForDaysOfYear.expected b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/UnsafeArrayForDaysOfYear.expected
index 37dd8b1ae7d0..59a981aa3a8f 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/UnsafeArrayForDaysOfYear.expected
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/UnsafeArrayForDaysOfYear.expected
@@ -1,3 +1,4 @@
-| test.cpp:17:6:17:10 | items | There is an array allocation with a hard-coded set of 365 elements, which may indicate the number of days in a year without considering leap year scenarios. |
-| test.cpp:25:15:25:26 | new[] | There is an array allocation with a hard-coded set of 365 elements, which may indicate the number of days in a year without considering leap year scenarios. |
-| test.cpp:52:20:52:23 | call to vector | There is a std::vector allocation with a hard-coded set of 365 elements, which may indicate the number of days in a year without considering leap year scenarios. |
+| test.cpp:20:6:20:10 | items | $@: There is an array allocation with a hard-coded set of 365 elements, which may indicate the number of days in a year without considering leap year scenarios. | test.cpp:20:6:20:10 | items | items |
+| test.cpp:31:15:31:26 | new[] | $@: There is an array allocation with a hard-coded set of 365 elements, which may indicate the number of days in a year without considering leap year scenarios. | test.cpp:28:6:28:21 | ArrayOfDays_Bug2 | ArrayOfDays_Bug2 |
+| test.cpp:68:20:68:23 | call to vector | $@: There is a std::vector allocation with a hard-coded set of 365 elements, which may indicate the number of days in a year without considering leap year scenarios. | test.cpp:65:6:65:21 | VectorOfDays_Bug | VectorOfDays_Bug |
+| test.cpp:115:7:115:15 | items_bad | $@: There is an array allocation with a hard-coded set of 365 elements, which may indicate the number of days in a year without considering leap year scenarios. | test.cpp:115:7:115:15 | items_bad | items_bad |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/test.cpp b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/test.cpp
index 7f6f2cfd3fe7..32a0f59ac6f8 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/test.cpp
+++ b/cpp/ql/test/query-tests/Likely Bugs/Leap Year/UnsafeArrayForDaysOfYear/test.cpp
@@ -11,6 +11,9 @@ class vector {
const T& operator[](int idx) const { return _x; }
};
+/**
+ * AntiPattern 4 - Static allocation of 365 array items
+*/
void ArrayOfDays_Bug(int dayOfYear, int x)
{
// BUG
@@ -19,6 +22,9 @@ void ArrayOfDays_Bug(int dayOfYear, int x)
items[dayOfYear - 1] = x;
}
+/**
+ * AntiPattern 4 - Static allocation of 365 array items
+*/
void ArrayOfDays_Bug2(int dayOfYear, int x)
{
// BUG
@@ -28,7 +34,10 @@ void ArrayOfDays_Bug2(int dayOfYear, int x)
delete items;
}
-
+/**
+ * True Negative
+ * Correct conditional allocation of array length
+*/
void ArrayOfDays_Correct(unsigned long year, int dayOfYear, int x)
{
bool isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
@@ -39,6 +48,10 @@ void ArrayOfDays_Correct(unsigned long year, int dayOfYear, int x)
delete[] items;
}
+/**
+ * True Negative
+ * Allocation of 366 items (Irregardless of common or leap year)
+*/
void ArrayOfDays_FalsePositive(int dayOfYear, int x)
{
int items[366];
@@ -46,6 +59,9 @@ void ArrayOfDays_FalsePositive(int dayOfYear, int x)
items[dayOfYear - 1] = x;
}
+/**
+ * AntiPattern 4 - Static allocation of 365 array items
+*/
void VectorOfDays_Bug(int dayOfYear, int x)
{
// BUG
@@ -54,6 +70,10 @@ void VectorOfDays_Bug(int dayOfYear, int x)
items[dayOfYear - 1] = x;
}
+/**
+ * True Negative
+ * Conditional quantity allocation on the basis of common or leap year
+*/
void VectorOfDays_Correct(unsigned long year, int dayOfYear, int x)
{
bool isLeapYear = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
@@ -62,9 +82,66 @@ void VectorOfDays_Correct(unsigned long year, int dayOfYear, int x)
items[dayOfYear - 1] = x;
}
+/**
+ * True Negative
+ * Allocation of 366 items (Irregardless of common or leap year)
+*/
void VectorOfDays_FalsePositive(int dayOfYear, int x)
{
vector items(366);
items[dayOfYear - 1] = x;
}
+
+/**
+ * AntiPattern 4 - Static allocation of 365 array items
+*/
+void HandleBothCases(int dayOfYear, int x)
+{
+ vector items(365);
+ vector items_leap(366);
+
+ items[dayOfYear - 1] = x; // BUG
+}
+
+/**
+ * AntiPattern 4 - Static allocation of 365 array items
+*/
+void HandleBothCases2(int dayOfYear, int x)
+{
+ int items[365];
+ int items_leap[366];
+
+ char items_bad[365]; // BUG
+
+ items[dayOfYear - 1] = x; // BUG
+}
+
+const short LeapYearDayToMonth[366] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // January
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // February
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // March
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // April
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // May
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // June
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // July
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // August
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // September
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // October
+ 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, // November
+ 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11}; // December
+
+/* Negative - #947 Sibling definition above*/
+const short NormalYearDayToMonth[365] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // January
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // February
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // March
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // April
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // May
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, // June
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, // July
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // August
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // September
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // October
+ 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10, // November
+ 11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11}; // December
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.expected b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.expected
new file mode 100644
index 000000000000..812f7dffd433
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.expected
@@ -0,0 +1,15 @@
+| test.c:29:6:29:46 | ... && ... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.c:29:15:29:30 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.c:29:6:29:46 | ... && ... | as an operand in a binary logical operation |
+| test.c:34:6:34:38 | ! ... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.c:34:7:34:22 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.c:34:6:34:38 | ! ... | as an operand in an unary logical operation |
+| test.c:39:6:39:21 | call to RtlCompareMemory | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.c:39:6:39:21 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.c:39:6:39:21 | call to RtlCompareMemory | as the controlling expression in an If statement |
+| test.c:49:6:49:42 | ... == ... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.c:49:11:49:26 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.c:49:6:49:42 | ... == ... | as an operand in an equality operation where the other operand is likely a boolean value (lower precision result, needs to be reviewed) |
+| test.c:75:6:75:37 | (bool)... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.c:75:6:75:21 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.c:75:6:75:37 | (bool)... | as a boolean |
+| test.c:77:6:77:46 | ... == ... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.c:77:15:77:30 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.c:77:6:77:46 | ... == ... | as an operand in an equality operation where the other operand is a boolean value (high precision result) |
+| test.c:84:6:84:37 | (BOOLEAN)... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.c:84:6:84:21 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.c:84:6:84:37 | (BOOLEAN)... | as a boolean |
+| test.c:86:6:86:45 | ... == ... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.c:86:14:86:29 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.c:86:6:86:45 | ... == ... | as an operand in an equality operation where the other operand is a boolean value (high precision result) |
+| test.c:91:9:91:52 | ... && ... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.c:91:21:91:36 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.c:91:9:91:52 | ... && ... | as an operand in a binary logical operation |
+| test.cpp:18:6:18:46 | ... && ... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.cpp:18:15:18:30 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.cpp:18:6:18:46 | ... && ... | as an operand in a binary logical operation |
+| test.cpp:18:15:18:46 | (bool)... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.cpp:18:15:18:30 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.cpp:18:15:18:46 | (bool)... | as a boolean |
+| test.cpp:23:6:23:38 | ! ... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.cpp:23:7:23:22 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.cpp:23:6:23:38 | ! ... | as an operand in an unary logical operation |
+| test.cpp:23:7:23:38 | (bool)... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.cpp:23:7:23:22 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.cpp:23:7:23:38 | (bool)... | as a boolean |
+| test.cpp:28:9:28:52 | ... && ... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.cpp:28:21:28:36 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.cpp:28:9:28:52 | ... && ... | as an operand in a binary logical operation |
+| test.cpp:28:21:28:52 | (bool)... | This $@ is being handled $@ instead of the number of matching bytes. Please review the usage of this function and consider replacing it with `RtlEqualMemory`. | test.cpp:28:21:28:36 | call to RtlCompareMemory | call to `RtlCompareMemory` | test.cpp:28:21:28:52 | (bool)... | as a boolean |
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.qlref b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.qlref
new file mode 100644
index 000000000000..629e248bce7e
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.qlref
@@ -0,0 +1 @@
+Microsoft/Likely Bugs/Drivers/IncorrectUsageOfRtlCompareMemory.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/test.c b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/test.c
new file mode 100644
index 000000000000..cf3b006d0030
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/test.c
@@ -0,0 +1,92 @@
+// semmle-extractor-options: --microsoft
+typedef unsigned __int64 size_t;
+
+size_t RtlCompareMemory(
+ const void* Source1,
+ const void* Source2,
+ size_t Length
+)
+{
+ return Length;
+}
+
+
+#define bool _Bool
+#define false 0
+#define true 1
+
+typedef unsigned char UCHAR;
+typedef UCHAR BOOLEAN; // winnt
+#define FALSE 0
+#define TRUE 1
+
+int Test(const void* ptr)
+{
+ size_t t = RtlCompareMemory("test", ptr, 5); //OK
+ bool x;
+ BOOLEAN y;
+
+ if (t > 0 && RtlCompareMemory("test", ptr, 5)) //bug
+ {
+ t++;
+ }
+
+ if (!RtlCompareMemory("test", ptr, 4)) //bug
+ {
+ t--;
+ }
+
+ if (RtlCompareMemory("test", ptr, 4)) //bug
+ {
+ t--;
+ }
+
+ if (6 == RtlCompareMemory("test", ptr, 4)) //OK
+ {
+ t++;
+ }
+
+ if (0 == RtlCompareMemory("test", ptr, 4)) // potentially a bug (lower precision)
+ {
+ t++;
+ }
+
+ if (6 == RtlCompareMemory("test", ptr, 4) + 1) //OK
+ {
+ t++;
+ }
+
+ if (0 == RtlCompareMemory("test", ptr, 4) + 1) // OK
+ {
+ t++;
+ }
+
+ switch (RtlCompareMemory("test", ptr, 4))
+ {
+ case 1:
+ t--;
+ break;
+ default:
+ t++;
+ }
+
+ /// _Bool
+
+ x = RtlCompareMemory("test", ptr, 4); // bug
+
+ if (false == RtlCompareMemory("test", ptr, 4)) // bug
+ {
+ t++;
+ }
+
+ // BOOLEAN
+
+ y = RtlCompareMemory("test", ptr, 4); // bug
+
+ if (TRUE == RtlCompareMemory("test", ptr, 4)) // bug
+ {
+ t++;
+ }
+
+ return (t == 5) && RtlCompareMemory("test", ptr, 5); //bug
+}
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/test.cpp b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/test.cpp
new file mode 100644
index 000000000000..f876133c67aa
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/Drivers/test.cpp
@@ -0,0 +1,29 @@
+// semmle-extractor-options: --microsoft
+typedef unsigned __int64 size_t;
+
+size_t RtlCompareMemory(
+ const void* Source1,
+ const void* Source2,
+ size_t Length
+)
+{
+ return Length;
+}
+
+
+bool Test(const void* ptr)
+{
+ size_t t = RtlCompareMemory("test", ptr, 5); //OK
+
+ if (t > 0 && RtlCompareMemory("test", ptr, 5)) //bug
+ {
+ t++;
+ }
+
+ if (!RtlCompareMemory("test", ptr, 4)) //bug
+ {
+ t--;
+ }
+
+ return (t == 5) && RtlCompareMemory("test", ptr, 5); //bug
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.expected b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.expected
new file mode 100644
index 000000000000..2099532f8f4a
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.expected
@@ -0,0 +1,48 @@
+| test2.c:86:6:86:29 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:86:6:86:29 | sizeof() | binary operator | test2.c:64:6:64:11 | Test01 | Usage | test2.c:86:13:86:28 | ... / ... | binary operator |
+| test2.c:86:6:86:29 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:86:6:86:29 | sizeof() | binary operator | test.c:64:6:64:11 | Test01 | Usage | test2.c:86:13:86:28 | ... / ... | binary operator |
+| test2.c:93:6:93:30 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:93:6:93:30 | sizeof() | binary operator | test2.c:64:6:64:11 | Test01 | Usage | test2.c:93:13:93:29 | ... * ... | binary operator |
+| test2.c:93:6:93:30 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:93:6:93:30 | sizeof() | binary operator | test.c:64:6:64:11 | Test01 | Usage | test2.c:93:13:93:29 | ... * ... | binary operator |
+| test2.c:95:6:95:35 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:95:6:95:35 | sizeof() | binary operator | test2.c:64:6:64:11 | Test01 | Usage | test2.c:95:13:95:34 | ... * ... | binary operator |
+| test2.c:95:6:95:35 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:95:6:95:35 | sizeof() | binary operator | test.c:64:6:64:11 | Test01 | Usage | test2.c:95:13:95:34 | ... * ... | binary operator |
+| test2.c:98:6:98:31 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:98:6:98:31 | sizeof() | sizeof | test2.c:64:6:64:11 | Test01 | Usage | test2.c:98:13:98:30 | sizeof(int) | sizeof |
+| test2.c:98:6:98:31 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:98:6:98:31 | sizeof() | sizeof | test.c:64:6:64:11 | Test01 | Usage | test2.c:98:13:98:30 | sizeof(int) | sizeof |
+| test2.c:116:6:116:24 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:116:6:116:24 | sizeof() | sizeof | test2.c:64:6:64:11 | Test01 | Usage | test2.c:116:13:116:23 | sizeof(int) | sizeof |
+| test2.c:116:6:116:24 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:116:6:116:24 | sizeof() | sizeof | test.c:64:6:64:11 | Test01 | Usage | test2.c:116:13:116:23 | sizeof(int) | sizeof |
+| test2.c:117:6:117:18 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:117:6:117:18 | sizeof() | binary operator | test2.c:64:6:64:11 | Test01 | Usage | test2.c:117:13:117:17 | ... + ... | binary operator |
+| test2.c:117:6:117:18 | sizeof() | $@: $@ of $@ inside sizeof. | test2.c:117:6:117:18 | sizeof() | binary operator | test.c:64:6:64:11 | Test01 | Usage | test2.c:117:13:117:17 | ... + ... | binary operator |
+| test2.cpp:89:6:89:29 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:89:6:89:29 | sizeof() | binary operator | test2.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:89:13:89:28 | ... / ... | binary operator |
+| test2.cpp:89:6:89:29 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:89:6:89:29 | sizeof() | binary operator | test.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:89:13:89:28 | ... / ... | binary operator |
+| test2.cpp:96:6:96:30 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:96:6:96:30 | sizeof() | binary operator | test2.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:96:13:96:29 | ... * ... | binary operator |
+| test2.cpp:96:6:96:30 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:96:6:96:30 | sizeof() | binary operator | test.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:96:13:96:29 | ... * ... | binary operator |
+| test2.cpp:98:6:98:35 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:98:6:98:35 | sizeof() | binary operator | test2.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:98:13:98:34 | ... * ... | binary operator |
+| test2.cpp:98:6:98:35 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:98:6:98:35 | sizeof() | binary operator | test.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:98:13:98:34 | ... * ... | binary operator |
+| test2.cpp:101:6:101:31 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:101:6:101:31 | sizeof() | sizeof | test2.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:101:13:101:30 | sizeof(int) | sizeof |
+| test2.cpp:101:6:101:31 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:101:6:101:31 | sizeof() | sizeof | test.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:101:13:101:30 | sizeof(int) | sizeof |
+| test2.cpp:120:6:120:24 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:120:6:120:24 | sizeof() | sizeof | test2.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:120:13:120:23 | sizeof(int) | sizeof |
+| test2.cpp:120:6:120:24 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:120:6:120:24 | sizeof() | sizeof | test.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:120:13:120:23 | sizeof(int) | sizeof |
+| test2.cpp:121:6:121:18 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:121:6:121:18 | sizeof() | binary operator | test2.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:121:13:121:17 | ... + ... | binary operator |
+| test2.cpp:121:6:121:18 | sizeof() | $@: $@ of $@ inside sizeof. | test2.cpp:121:6:121:18 | sizeof() | binary operator | test.cpp:66:6:66:11 | Test01 | Usage | test2.cpp:121:13:121:17 | ... + ... | binary operator |
+| test.c:86:6:86:29 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:86:6:86:29 | sizeof() | binary operator | test2.c:64:6:64:11 | Test01 | Usage | test.c:86:13:86:28 | ... / ... | binary operator |
+| test.c:86:6:86:29 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:86:6:86:29 | sizeof() | binary operator | test.c:64:6:64:11 | Test01 | Usage | test.c:86:13:86:28 | ... / ... | binary operator |
+| test.c:93:6:93:30 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:93:6:93:30 | sizeof() | binary operator | test2.c:64:6:64:11 | Test01 | Usage | test.c:93:13:93:29 | ... * ... | binary operator |
+| test.c:93:6:93:30 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:93:6:93:30 | sizeof() | binary operator | test.c:64:6:64:11 | Test01 | Usage | test.c:93:13:93:29 | ... * ... | binary operator |
+| test.c:95:6:95:35 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:95:6:95:35 | sizeof() | binary operator | test2.c:64:6:64:11 | Test01 | Usage | test.c:95:13:95:34 | ... * ... | binary operator |
+| test.c:95:6:95:35 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:95:6:95:35 | sizeof() | binary operator | test.c:64:6:64:11 | Test01 | Usage | test.c:95:13:95:34 | ... * ... | binary operator |
+| test.c:98:6:98:31 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:98:6:98:31 | sizeof() | sizeof | test2.c:64:6:64:11 | Test01 | Usage | test.c:98:13:98:30 | sizeof(int) | sizeof |
+| test.c:98:6:98:31 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:98:6:98:31 | sizeof() | sizeof | test.c:64:6:64:11 | Test01 | Usage | test.c:98:13:98:30 | sizeof(int) | sizeof |
+| test.c:116:6:116:24 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:116:6:116:24 | sizeof() | sizeof | test2.c:64:6:64:11 | Test01 | Usage | test.c:116:13:116:23 | sizeof(int) | sizeof |
+| test.c:116:6:116:24 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:116:6:116:24 | sizeof() | sizeof | test.c:64:6:64:11 | Test01 | Usage | test.c:116:13:116:23 | sizeof(int) | sizeof |
+| test.c:117:6:117:18 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:117:6:117:18 | sizeof() | binary operator | test2.c:64:6:64:11 | Test01 | Usage | test.c:117:13:117:17 | ... + ... | binary operator |
+| test.c:117:6:117:18 | sizeof() | $@: $@ of $@ inside sizeof. | test.c:117:6:117:18 | sizeof() | binary operator | test.c:64:6:64:11 | Test01 | Usage | test.c:117:13:117:17 | ... + ... | binary operator |
+| test.cpp:89:6:89:29 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:89:6:89:29 | sizeof() | binary operator | test2.cpp:66:6:66:11 | Test01 | Usage | test.cpp:89:13:89:28 | ... / ... | binary operator |
+| test.cpp:89:6:89:29 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:89:6:89:29 | sizeof() | binary operator | test.cpp:66:6:66:11 | Test01 | Usage | test.cpp:89:13:89:28 | ... / ... | binary operator |
+| test.cpp:96:6:96:30 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:96:6:96:30 | sizeof() | binary operator | test2.cpp:66:6:66:11 | Test01 | Usage | test.cpp:96:13:96:29 | ... * ... | binary operator |
+| test.cpp:96:6:96:30 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:96:6:96:30 | sizeof() | binary operator | test.cpp:66:6:66:11 | Test01 | Usage | test.cpp:96:13:96:29 | ... * ... | binary operator |
+| test.cpp:98:6:98:35 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:98:6:98:35 | sizeof() | binary operator | test2.cpp:66:6:66:11 | Test01 | Usage | test.cpp:98:13:98:34 | ... * ... | binary operator |
+| test.cpp:98:6:98:35 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:98:6:98:35 | sizeof() | binary operator | test.cpp:66:6:66:11 | Test01 | Usage | test.cpp:98:13:98:34 | ... * ... | binary operator |
+| test.cpp:101:6:101:31 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:101:6:101:31 | sizeof() | sizeof | test2.cpp:66:6:66:11 | Test01 | Usage | test.cpp:101:13:101:30 | sizeof(int) | sizeof |
+| test.cpp:101:6:101:31 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:101:6:101:31 | sizeof() | sizeof | test.cpp:66:6:66:11 | Test01 | Usage | test.cpp:101:13:101:30 | sizeof(int) | sizeof |
+| test.cpp:120:6:120:24 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:120:6:120:24 | sizeof() | sizeof | test2.cpp:66:6:66:11 | Test01 | Usage | test.cpp:120:13:120:23 | sizeof(int) | sizeof |
+| test.cpp:120:6:120:24 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:120:6:120:24 | sizeof() | sizeof | test.cpp:66:6:66:11 | Test01 | Usage | test.cpp:120:13:120:23 | sizeof(int) | sizeof |
+| test.cpp:121:6:121:18 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:121:6:121:18 | sizeof() | binary operator | test2.cpp:66:6:66:11 | Test01 | Usage | test.cpp:121:13:121:17 | ... + ... | binary operator |
+| test.cpp:121:6:121:18 | sizeof() | $@: $@ of $@ inside sizeof. | test.cpp:121:6:121:18 | sizeof() | binary operator | test.cpp:66:6:66:11 | Test01 | Usage | test.cpp:121:13:121:17 | ... + ... | binary operator |
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.qlref b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.qlref
new file mode 100644
index 000000000000..662f83b06cc0
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.qlref
@@ -0,0 +1 @@
+Microsoft/Likely Bugs/SizeOfMisuse/ArgumentIsSizeofOrOperation.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.expected b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.expected
new file mode 100644
index 000000000000..bf751cf1e9b6
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.expected
@@ -0,0 +1,24 @@
+| test2.c:72:6:72:42 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.c:72:6:72:42 | sizeof() | Test01 | test2.c:46:1:46:48 | #define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d | SOMESTRUCT_ERRNO_THAT_MATTERS |
+| test2.c:80:10:80:32 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.c:80:10:80:32 | sizeof() | Test01 | test2.c:2:1:2:26 | #define BAD_MACRO_CONST 5l | BAD_MACRO_CONST |
+| test2.c:81:6:81:29 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.c:81:6:81:29 | sizeof() | Test01 | test2.c:3:1:3:35 | #define BAD_MACRO_CONST2 0x80005001 | BAD_MACRO_CONST2 |
+| test2.c:89:7:89:35 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.c:89:7:89:35 | sizeof() | Test01 | test2.c:1:1:1:19 | #define PAGESIZE 64 | PAGESIZE |
+| test2.c:98:6:98:31 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.c:98:6:98:31 | sizeof() | Test01 | test2.c:17:1:17:40 | #define SOME_SIZEOF_MACRO2 (sizeof(int)) | SOME_SIZEOF_MACRO2 |
+| test2.c:112:6:112:37 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.c:112:6:112:37 | sizeof() | Test01 | test2.c:31:1:31:45 | #define ACE_CONDITION_SIGNATURE2 'xt' | ACE_CONDITION_SIGNATURE2 |
+| test2.cpp:75:6:75:42 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.cpp:75:6:75:42 | sizeof() | Test01 | test2.cpp:48:1:48:48 | #define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d | SOMESTRUCT_ERRNO_THAT_MATTERS |
+| test2.cpp:83:10:83:32 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.cpp:83:10:83:32 | sizeof() | Test01 | test2.cpp:2:1:2:26 | #define BAD_MACRO_CONST 5l | BAD_MACRO_CONST |
+| test2.cpp:84:6:84:29 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.cpp:84:6:84:29 | sizeof() | Test01 | test2.cpp:3:1:3:35 | #define BAD_MACRO_CONST2 0x80005001 | BAD_MACRO_CONST2 |
+| test2.cpp:92:7:92:35 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.cpp:92:7:92:35 | sizeof() | Test01 | test2.cpp:1:1:1:19 | #define PAGESIZE 64 | PAGESIZE |
+| test2.cpp:101:6:101:31 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.cpp:101:6:101:31 | sizeof() | Test01 | test2.cpp:17:1:17:40 | #define SOME_SIZEOF_MACRO2 (sizeof(int)) | SOME_SIZEOF_MACRO2 |
+| test2.cpp:116:6:116:37 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test2.cpp:116:6:116:37 | sizeof() | Test01 | test2.cpp:32:1:32:45 | #define ACE_CONDITION_SIGNATURE2 'xt' | ACE_CONDITION_SIGNATURE2 |
+| test.c:72:6:72:42 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.c:72:6:72:42 | sizeof() | Test01 | test.c:46:1:46:48 | #define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d | SOMESTRUCT_ERRNO_THAT_MATTERS |
+| test.c:80:10:80:32 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.c:80:10:80:32 | sizeof() | Test01 | test.c:2:1:2:26 | #define BAD_MACRO_CONST 5l | BAD_MACRO_CONST |
+| test.c:81:6:81:29 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.c:81:6:81:29 | sizeof() | Test01 | test.c:3:1:3:35 | #define BAD_MACRO_CONST2 0x80005001 | BAD_MACRO_CONST2 |
+| test.c:89:7:89:35 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.c:89:7:89:35 | sizeof() | Test01 | test.c:1:1:1:19 | #define PAGESIZE 64 | PAGESIZE |
+| test.c:98:6:98:31 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.c:98:6:98:31 | sizeof() | Test01 | test.c:17:1:17:40 | #define SOME_SIZEOF_MACRO2 (sizeof(int)) | SOME_SIZEOF_MACRO2 |
+| test.c:112:6:112:37 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.c:112:6:112:37 | sizeof() | Test01 | test.c:31:1:31:45 | #define ACE_CONDITION_SIGNATURE2 'xt' | ACE_CONDITION_SIGNATURE2 |
+| test.cpp:75:6:75:42 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.cpp:75:6:75:42 | sizeof() | Test01 | test.cpp:48:1:48:48 | #define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d | SOMESTRUCT_ERRNO_THAT_MATTERS |
+| test.cpp:83:10:83:32 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.cpp:83:10:83:32 | sizeof() | Test01 | test.cpp:2:1:2:26 | #define BAD_MACRO_CONST 5l | BAD_MACRO_CONST |
+| test.cpp:84:6:84:29 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.cpp:84:6:84:29 | sizeof() | Test01 | test.cpp:3:1:3:35 | #define BAD_MACRO_CONST2 0x80005001 | BAD_MACRO_CONST2 |
+| test.cpp:92:7:92:35 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.cpp:92:7:92:35 | sizeof() | Test01 | test.cpp:1:1:1:19 | #define PAGESIZE 64 | PAGESIZE |
+| test.cpp:101:6:101:31 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.cpp:101:6:101:31 | sizeof() | Test01 | test.cpp:17:1:17:40 | #define SOME_SIZEOF_MACRO2 (sizeof(int)) | SOME_SIZEOF_MACRO2 |
+| test.cpp:116:6:116:37 | sizeof() | $@: sizeof of integer macro $@ will always return the size of the underlying integer type. | test.cpp:116:6:116:37 | sizeof() | Test01 | test.cpp:32:1:32:45 | #define ACE_CONDITION_SIGNATURE2 'xt' | ACE_CONDITION_SIGNATURE2 |
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.qlref b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.qlref
new file mode 100644
index 000000000000..3804507965c1
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.qlref
@@ -0,0 +1 @@
+Microsoft/Likely Bugs/SizeOfMisuse/SizeOfConstIntMacro.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test.c b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test.c
new file mode 100644
index 000000000000..b11d0b552053
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test.c
@@ -0,0 +1,118 @@
+#define PAGESIZE 64
+#define BAD_MACRO_CONST 5l
+#define BAD_MACRO_CONST2 0x80005001
+#define BAD_MACRO_OP1(VAR) strlen(#VAR)
+#define BAD_MACRO_OP2(VAR) sizeof(VAR)/sizeof(int)
+
+long strlen(const char* x) { return 5; }
+
+#define ALIGN_UP_BY(length, sizeofType) length * sizeofType
+#define ALIGN_UP(length, type) \
+ ALIGN_UP_BY(length, sizeof(type))
+
+#define SOME_SIZEOF_MACRO (sizeof(int) * 3)
+#define SOME_SIZEOF_MACRO_CAST ((char)(sizeof(int) * 3))
+
+
+#define SOME_SIZEOF_MACRO2 (sizeof(int))
+
+typedef unsigned short WCHAR; // wc, 16-bit UNICODE character
+
+#define UNICODE_NULL1 ((WCHAR)0)
+
+#define ASCII_NULL ((char)0)
+
+#define UNICODE_STRING_SIG L"xtra"
+#define ASCII_STRING_SIG "xtra"
+
+#define UNICODE_SIG L'x'
+
+#define ACE_CONDITION_SIGNATURE1 'xtra'
+#define ACE_CONDITION_SIGNATURE2 'xt'
+
+#define ACE_CONDITION_SIGNATURE3(VAL) #VAL
+
+#define NULL (void *)0
+
+#define EFI_FILEPATH_SEPARATOR_UNICODE L'\\'
+
+const char* Test()
+{
+ return "foobar";
+}
+
+#define FUNCTION_MACRO_OP1 Test()
+
+#define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d
+
+
+char _RTL_CONSTANT_STRING_type_check(const void* s) {
+ return ((char*)(s))[0];
+}
+
+#define RTL_CONSTANT_STRING(s) \
+{ \
+ sizeof( s ) - sizeof( (s)[0] ); \
+ sizeof( s ) / sizeof(_RTL_CONSTANT_STRING_type_check(s)); \
+}
+
+typedef struct {
+ int a;
+ char b;
+} SOMESTRUCT_THAT_MATTERS;
+
+void Test01() {
+
+ RTL_CONSTANT_STRING("hello");
+
+ sizeof(NULL);
+ sizeof(EFI_FILEPATH_SEPARATOR_UNICODE);
+
+ int y = sizeof(SOMESTRUCT_THAT_MATTERS);
+ y = sizeof(SOMESTRUCT_ERRNO_THAT_MATTERS); // BUG: SizeOfConstIntMacro
+
+ const unsigned short* p = UNICODE_STRING_SIG;
+ const char* p2 = ASCII_STRING_SIG;
+ char p3 = 'xtra';
+ unsigned short p4 = L'xtra';
+
+ int a[10];
+ int x = sizeof(BAD_MACRO_CONST); //BUG: SizeOfConstIntMacro
+ x = sizeof(BAD_MACRO_CONST2); //BUG: SizeOfConstIntMacro
+
+ x = sizeof(FUNCTION_MACRO_OP1); // GOOD
+
+ x = sizeof(BAD_MACRO_OP1(a)); //BUG: ArgumentIsFunctionCall (Low Prec)
+ x = sizeof(BAD_MACRO_OP2(a)); //BUG: ArgumentIsSizeofOrOperation
+
+ x = 0;
+ x += ALIGN_UP(sizeof(a), PAGESIZE); //BUG: SizeOfConstIntMacro
+ x += ALIGN_UP_BY(sizeof(a), PAGESIZE); // GOOD
+
+ x = SOME_SIZEOF_MACRO * 3; // GOOD
+ x = sizeof(SOME_SIZEOF_MACRO) * 3; //BUG: ArgumentIsSizeofOrOperation
+
+ x = sizeof(SOME_SIZEOF_MACRO_CAST) * 3; //BUG: ArgumentIsSizeofOrOperation
+
+ x = SOME_SIZEOF_MACRO2; // GOOD
+ x = sizeof(SOME_SIZEOF_MACRO2); //BUG: SizeOfConstIntMacro, ArgumentIsSizeofOrOperation
+
+ x = sizeof(a) / sizeof(int); // GOOD
+
+ x = sizeof(UNICODE_NULL1);
+
+ x = sizeof(ASCII_NULL);
+
+ x = sizeof(UNICODE_STRING_SIG);
+ x = sizeof(ASCII_STRING_SIG);
+
+ x = sizeof(UNICODE_SIG);
+
+ x = sizeof(ACE_CONDITION_SIGNATURE1); // GOOD (special case)
+ x = sizeof(ACE_CONDITION_SIGNATURE2); // BUG: SizeOfConstIntMacro
+
+ x = sizeof(ACE_CONDITION_SIGNATURE3(xtra));
+
+ x = sizeof(sizeof(int)); // BUG: ArgumentIsSizeofOrOperation
+ x = sizeof(3 + 2); // BUg: ArgumentIsSizeofOrOperation
+}
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test.cpp b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test.cpp
new file mode 100644
index 000000000000..d9622d3c0d94
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test.cpp
@@ -0,0 +1,136 @@
+#define PAGESIZE 64
+#define BAD_MACRO_CONST 5l
+#define BAD_MACRO_CONST2 0x80005001
+#define BAD_MACRO_OP1(VAR) strlen(#VAR)
+#define BAD_MACRO_OP2(VAR) sizeof(VAR)/sizeof(int)
+
+long strlen(const char* x) { return 5; }
+
+#define ALIGN_UP_BY(length, sizeofType) length * sizeofType
+#define ALIGN_UP(length, type) \
+ ALIGN_UP_BY(length, sizeof(type))
+
+#define SOME_SIZEOF_MACRO (sizeof(int) * 3)
+#define SOME_SIZEOF_MACRO_CAST ((char)(sizeof(int) * 3))
+
+
+#define SOME_SIZEOF_MACRO2 (sizeof(int))
+
+typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
+
+#define UNICODE_NULL1 ((WCHAR)0)
+#define UNICODE_NULL2 ((wchar_t)0)
+#define ASCII_NULL ((char)0)
+
+#define UNICODE_STRING_SIG L"xtra"
+#define ASCII_STRING_SIG "xtra"
+
+#define ASCII_SIG 'x'
+#define UNICODE_SIG L'x'
+
+#define ACE_CONDITION_SIGNATURE1 'xtra'
+#define ACE_CONDITION_SIGNATURE2 'xt'
+
+#define ACE_CONDITION_SIGNATURE3(VAL) #VAL
+
+#define NULL (void *)0
+
+#define EFI_FILEPATH_SEPARATOR_UNICODE L'\\'
+#define EFI_FILEPATH_SEPARATOR_ASCII '\\'
+
+const char* Test()
+{
+ return "foobar";
+}
+
+#define FUNCTION_MACRO_OP1 Test()
+
+#define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d
+
+
+char _RTL_CONSTANT_STRING_type_check(const void* s) {
+ return ((char*)(s))[0];
+}
+
+#define RTL_CONSTANT_STRING(s) \
+{ \
+ sizeof( s ) - sizeof( (s)[0] ); \
+ sizeof( s ) / sizeof(_RTL_CONSTANT_STRING_type_check(s)); \
+}
+
+typedef struct {
+ int a;
+ bool b;
+} SOMESTRUCT_THAT_MATTERS;
+
+void Test01() {
+
+ RTL_CONSTANT_STRING("hello");
+
+ sizeof(NULL);
+ sizeof(EFI_FILEPATH_SEPARATOR_UNICODE);
+ sizeof(EFI_FILEPATH_SEPARATOR_ASCII);
+
+ int y = sizeof(SOMESTRUCT_THAT_MATTERS);
+ y = sizeof(SOMESTRUCT_ERRNO_THAT_MATTERS); // BUG: SizeOfConstIntMacro
+
+ const wchar_t* p = UNICODE_STRING_SIG;
+ const char* p2 = ASCII_STRING_SIG;
+ char p3 = 'xtra';
+ wchar_t p4 = L'xtra';
+
+ int a[10];
+ int x = sizeof(BAD_MACRO_CONST); //BUG: SizeOfConstIntMacro
+ x = sizeof(BAD_MACRO_CONST2); //BUG: SizeOfConstIntMacro
+
+ x = sizeof(FUNCTION_MACRO_OP1); // GOOD
+
+ x = sizeof(BAD_MACRO_OP1(a)); //BUG: ArgumentIsFunctionCall (Low Prec)
+ x = sizeof(BAD_MACRO_OP2(a)); //BUG: ArgumentIsSizeofOrOperation
+
+ x = 0;
+ x += ALIGN_UP(sizeof(a), PAGESIZE); //BUG: SizeOfConstIntMacro
+ x += ALIGN_UP_BY(sizeof(a), PAGESIZE); // GOOD
+
+ x = SOME_SIZEOF_MACRO * 3; // GOOD
+ x = sizeof(SOME_SIZEOF_MACRO) * 3; //BUG: ArgumentIsSizeofOrOperation
+
+ x = sizeof(SOME_SIZEOF_MACRO_CAST) * 3; //BUG: ArgumentIsSizeofOrOperation
+
+ x = SOME_SIZEOF_MACRO2; // GOOD
+ x = sizeof(SOME_SIZEOF_MACRO2); //BUG: SizeOfConstIntMacro, ArgumentIsSizeofOrOperation
+
+ x = sizeof(a) / sizeof(int); // GOOD
+
+ x = sizeof(UNICODE_NULL1);
+ x = sizeof(UNICODE_NULL2);
+ x = sizeof(ASCII_NULL);
+
+ x = sizeof(UNICODE_STRING_SIG);
+ x = sizeof(ASCII_STRING_SIG);
+
+ x = sizeof(ASCII_SIG);
+ x = sizeof(UNICODE_SIG);
+
+ x = sizeof(ACE_CONDITION_SIGNATURE1); // GOOD (special case)
+ x = sizeof(ACE_CONDITION_SIGNATURE2); // BUG: SizeOfConstIntMacro
+
+ x = sizeof(ACE_CONDITION_SIGNATURE3(xtra));
+
+ x = sizeof(sizeof(int)); // BUG: ArgumentIsSizeofOrOperation
+ x = sizeof(3 + 2); // BUg: ArgumentIsSizeofOrOperation
+}
+
+#define WNULL (L'\0')
+#define WNULL_SIZE (sizeof(WNULL))
+
+#define RKF_PATH_UTIL_STREAM_MARKER ( L':' )
+
+#define _T(x) L ## x
+
+void test02_FalsePositives()
+{
+ int x = WNULL_SIZE;
+ x = sizeof(RKF_PATH_UTIL_STREAM_MARKER);
+ sizeof(_T('\0'));
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test2.c b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test2.c
new file mode 100644
index 000000000000..b11d0b552053
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test2.c
@@ -0,0 +1,118 @@
+#define PAGESIZE 64
+#define BAD_MACRO_CONST 5l
+#define BAD_MACRO_CONST2 0x80005001
+#define BAD_MACRO_OP1(VAR) strlen(#VAR)
+#define BAD_MACRO_OP2(VAR) sizeof(VAR)/sizeof(int)
+
+long strlen(const char* x) { return 5; }
+
+#define ALIGN_UP_BY(length, sizeofType) length * sizeofType
+#define ALIGN_UP(length, type) \
+ ALIGN_UP_BY(length, sizeof(type))
+
+#define SOME_SIZEOF_MACRO (sizeof(int) * 3)
+#define SOME_SIZEOF_MACRO_CAST ((char)(sizeof(int) * 3))
+
+
+#define SOME_SIZEOF_MACRO2 (sizeof(int))
+
+typedef unsigned short WCHAR; // wc, 16-bit UNICODE character
+
+#define UNICODE_NULL1 ((WCHAR)0)
+
+#define ASCII_NULL ((char)0)
+
+#define UNICODE_STRING_SIG L"xtra"
+#define ASCII_STRING_SIG "xtra"
+
+#define UNICODE_SIG L'x'
+
+#define ACE_CONDITION_SIGNATURE1 'xtra'
+#define ACE_CONDITION_SIGNATURE2 'xt'
+
+#define ACE_CONDITION_SIGNATURE3(VAL) #VAL
+
+#define NULL (void *)0
+
+#define EFI_FILEPATH_SEPARATOR_UNICODE L'\\'
+
+const char* Test()
+{
+ return "foobar";
+}
+
+#define FUNCTION_MACRO_OP1 Test()
+
+#define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d
+
+
+char _RTL_CONSTANT_STRING_type_check(const void* s) {
+ return ((char*)(s))[0];
+}
+
+#define RTL_CONSTANT_STRING(s) \
+{ \
+ sizeof( s ) - sizeof( (s)[0] ); \
+ sizeof( s ) / sizeof(_RTL_CONSTANT_STRING_type_check(s)); \
+}
+
+typedef struct {
+ int a;
+ char b;
+} SOMESTRUCT_THAT_MATTERS;
+
+void Test01() {
+
+ RTL_CONSTANT_STRING("hello");
+
+ sizeof(NULL);
+ sizeof(EFI_FILEPATH_SEPARATOR_UNICODE);
+
+ int y = sizeof(SOMESTRUCT_THAT_MATTERS);
+ y = sizeof(SOMESTRUCT_ERRNO_THAT_MATTERS); // BUG: SizeOfConstIntMacro
+
+ const unsigned short* p = UNICODE_STRING_SIG;
+ const char* p2 = ASCII_STRING_SIG;
+ char p3 = 'xtra';
+ unsigned short p4 = L'xtra';
+
+ int a[10];
+ int x = sizeof(BAD_MACRO_CONST); //BUG: SizeOfConstIntMacro
+ x = sizeof(BAD_MACRO_CONST2); //BUG: SizeOfConstIntMacro
+
+ x = sizeof(FUNCTION_MACRO_OP1); // GOOD
+
+ x = sizeof(BAD_MACRO_OP1(a)); //BUG: ArgumentIsFunctionCall (Low Prec)
+ x = sizeof(BAD_MACRO_OP2(a)); //BUG: ArgumentIsSizeofOrOperation
+
+ x = 0;
+ x += ALIGN_UP(sizeof(a), PAGESIZE); //BUG: SizeOfConstIntMacro
+ x += ALIGN_UP_BY(sizeof(a), PAGESIZE); // GOOD
+
+ x = SOME_SIZEOF_MACRO * 3; // GOOD
+ x = sizeof(SOME_SIZEOF_MACRO) * 3; //BUG: ArgumentIsSizeofOrOperation
+
+ x = sizeof(SOME_SIZEOF_MACRO_CAST) * 3; //BUG: ArgumentIsSizeofOrOperation
+
+ x = SOME_SIZEOF_MACRO2; // GOOD
+ x = sizeof(SOME_SIZEOF_MACRO2); //BUG: SizeOfConstIntMacro, ArgumentIsSizeofOrOperation
+
+ x = sizeof(a) / sizeof(int); // GOOD
+
+ x = sizeof(UNICODE_NULL1);
+
+ x = sizeof(ASCII_NULL);
+
+ x = sizeof(UNICODE_STRING_SIG);
+ x = sizeof(ASCII_STRING_SIG);
+
+ x = sizeof(UNICODE_SIG);
+
+ x = sizeof(ACE_CONDITION_SIGNATURE1); // GOOD (special case)
+ x = sizeof(ACE_CONDITION_SIGNATURE2); // BUG: SizeOfConstIntMacro
+
+ x = sizeof(ACE_CONDITION_SIGNATURE3(xtra));
+
+ x = sizeof(sizeof(int)); // BUG: ArgumentIsSizeofOrOperation
+ x = sizeof(3 + 2); // BUg: ArgumentIsSizeofOrOperation
+}
diff --git a/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test2.cpp b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test2.cpp
new file mode 100644
index 000000000000..d9622d3c0d94
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Likely Bugs/SizeOfMisuse/test2.cpp
@@ -0,0 +1,136 @@
+#define PAGESIZE 64
+#define BAD_MACRO_CONST 5l
+#define BAD_MACRO_CONST2 0x80005001
+#define BAD_MACRO_OP1(VAR) strlen(#VAR)
+#define BAD_MACRO_OP2(VAR) sizeof(VAR)/sizeof(int)
+
+long strlen(const char* x) { return 5; }
+
+#define ALIGN_UP_BY(length, sizeofType) length * sizeofType
+#define ALIGN_UP(length, type) \
+ ALIGN_UP_BY(length, sizeof(type))
+
+#define SOME_SIZEOF_MACRO (sizeof(int) * 3)
+#define SOME_SIZEOF_MACRO_CAST ((char)(sizeof(int) * 3))
+
+
+#define SOME_SIZEOF_MACRO2 (sizeof(int))
+
+typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
+
+#define UNICODE_NULL1 ((WCHAR)0)
+#define UNICODE_NULL2 ((wchar_t)0)
+#define ASCII_NULL ((char)0)
+
+#define UNICODE_STRING_SIG L"xtra"
+#define ASCII_STRING_SIG "xtra"
+
+#define ASCII_SIG 'x'
+#define UNICODE_SIG L'x'
+
+#define ACE_CONDITION_SIGNATURE1 'xtra'
+#define ACE_CONDITION_SIGNATURE2 'xt'
+
+#define ACE_CONDITION_SIGNATURE3(VAL) #VAL
+
+#define NULL (void *)0
+
+#define EFI_FILEPATH_SEPARATOR_UNICODE L'\\'
+#define EFI_FILEPATH_SEPARATOR_ASCII '\\'
+
+const char* Test()
+{
+ return "foobar";
+}
+
+#define FUNCTION_MACRO_OP1 Test()
+
+#define SOMESTRUCT_ERRNO_THAT_MATTERS 0x8000000d
+
+
+char _RTL_CONSTANT_STRING_type_check(const void* s) {
+ return ((char*)(s))[0];
+}
+
+#define RTL_CONSTANT_STRING(s) \
+{ \
+ sizeof( s ) - sizeof( (s)[0] ); \
+ sizeof( s ) / sizeof(_RTL_CONSTANT_STRING_type_check(s)); \
+}
+
+typedef struct {
+ int a;
+ bool b;
+} SOMESTRUCT_THAT_MATTERS;
+
+void Test01() {
+
+ RTL_CONSTANT_STRING("hello");
+
+ sizeof(NULL);
+ sizeof(EFI_FILEPATH_SEPARATOR_UNICODE);
+ sizeof(EFI_FILEPATH_SEPARATOR_ASCII);
+
+ int y = sizeof(SOMESTRUCT_THAT_MATTERS);
+ y = sizeof(SOMESTRUCT_ERRNO_THAT_MATTERS); // BUG: SizeOfConstIntMacro
+
+ const wchar_t* p = UNICODE_STRING_SIG;
+ const char* p2 = ASCII_STRING_SIG;
+ char p3 = 'xtra';
+ wchar_t p4 = L'xtra';
+
+ int a[10];
+ int x = sizeof(BAD_MACRO_CONST); //BUG: SizeOfConstIntMacro
+ x = sizeof(BAD_MACRO_CONST2); //BUG: SizeOfConstIntMacro
+
+ x = sizeof(FUNCTION_MACRO_OP1); // GOOD
+
+ x = sizeof(BAD_MACRO_OP1(a)); //BUG: ArgumentIsFunctionCall (Low Prec)
+ x = sizeof(BAD_MACRO_OP2(a)); //BUG: ArgumentIsSizeofOrOperation
+
+ x = 0;
+ x += ALIGN_UP(sizeof(a), PAGESIZE); //BUG: SizeOfConstIntMacro
+ x += ALIGN_UP_BY(sizeof(a), PAGESIZE); // GOOD
+
+ x = SOME_SIZEOF_MACRO * 3; // GOOD
+ x = sizeof(SOME_SIZEOF_MACRO) * 3; //BUG: ArgumentIsSizeofOrOperation
+
+ x = sizeof(SOME_SIZEOF_MACRO_CAST) * 3; //BUG: ArgumentIsSizeofOrOperation
+
+ x = SOME_SIZEOF_MACRO2; // GOOD
+ x = sizeof(SOME_SIZEOF_MACRO2); //BUG: SizeOfConstIntMacro, ArgumentIsSizeofOrOperation
+
+ x = sizeof(a) / sizeof(int); // GOOD
+
+ x = sizeof(UNICODE_NULL1);
+ x = sizeof(UNICODE_NULL2);
+ x = sizeof(ASCII_NULL);
+
+ x = sizeof(UNICODE_STRING_SIG);
+ x = sizeof(ASCII_STRING_SIG);
+
+ x = sizeof(ASCII_SIG);
+ x = sizeof(UNICODE_SIG);
+
+ x = sizeof(ACE_CONDITION_SIGNATURE1); // GOOD (special case)
+ x = sizeof(ACE_CONDITION_SIGNATURE2); // BUG: SizeOfConstIntMacro
+
+ x = sizeof(ACE_CONDITION_SIGNATURE3(xtra));
+
+ x = sizeof(sizeof(int)); // BUG: ArgumentIsSizeofOrOperation
+ x = sizeof(3 + 2); // BUg: ArgumentIsSizeofOrOperation
+}
+
+#define WNULL (L'\0')
+#define WNULL_SIZE (sizeof(WNULL))
+
+#define RKF_PATH_UTIL_STREAM_MARKER ( L':' )
+
+#define _T(x) L ## x
+
+void test02_FalsePositives()
+{
+ int x = WNULL_SIZE;
+ x = sizeof(RKF_PATH_UTIL_STREAM_MARKER);
+ sizeof(_T('\0'));
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/CapiAndCng/Test.cpp b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/CapiAndCng/Test.cpp
new file mode 100644
index 000000000000..01c4a8b7e800
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/CapiAndCng/Test.cpp
@@ -0,0 +1,205 @@
+#define CONST const
+
+typedef unsigned long DWORD;
+typedef int BOOL;
+typedef unsigned char BYTE;
+typedef unsigned long ULONG_PTR;
+typedef unsigned long *PULONG_PTR;
+typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
+typedef void *PVOID;
+typedef CONST WCHAR *LPCWSTR, *PCWSTR;
+typedef PVOID BCRYPT_ALG_HANDLE;
+typedef long LONG;
+typedef unsigned long ULONG;
+typedef ULONG *PULONG;
+typedef LONG NTSTATUS;
+typedef ULONG_PTR HCRYPTHASH;
+typedef ULONG_PTR HCRYPTPROV;
+typedef ULONG_PTR HCRYPTKEY;
+typedef ULONG_PTR HCRYPTHASH;
+typedef unsigned int ALG_ID;
+
+// algorithm identifier definitions
+#define ALG_CLASS_DATA_ENCRYPT (3 << 13)
+#define ALG_TYPE_ANY (0)
+#define ALG_TYPE_BLOCK (3 << 9)
+#define ALG_TYPE_STREAM (4 << 9)
+#define ALG_TYPE_THIRDPARTY (8 << 9)
+#define ALG_SID_THIRDPARTY_ANY (0)
+
+#define ALG_SID_DES 1
+#define ALG_SID_3DES 3
+#define ALG_SID_DESX 4
+#define ALG_SID_IDEA 5
+#define ALG_SID_CAST 6
+#define ALG_SID_SAFERSK64 7
+#define ALG_SID_SAFERSK128 8
+#define ALG_SID_3DES_112 9
+#define ALG_SID_CYLINK_MEK 12
+#define ALG_SID_RC5 13
+#define ALG_SID_AES_128 14
+#define ALG_SID_AES_192 15
+#define ALG_SID_AES_256 16
+#define ALG_SID_AES 17
+// Fortezza sub-ids
+#define ALG_SID_SKIPJACK 10
+#define ALG_SID_TEK 11
+// RC2 sub-ids
+#define ALG_SID_RC2 2
+// Stream cipher sub-ids
+#define ALG_SID_RC4 1
+#define ALG_SID_SEAL 2
+
+// CAPI encryption algorithm definitions
+#define CALG_DES (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_DES)
+#define CALG_3DES_112 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_3DES_112)
+#define CALG_3DES (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_3DES)
+#define CALG_DESX (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_DESX)
+#define CALG_RC2 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_RC2)
+#define CALG_RC4 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_STREAM|ALG_SID_RC4)
+#define CALG_SEAL (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_STREAM|ALG_SID_SEAL)
+#define CALG_SKIPJACK (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_SKIPJACK)
+#define CALG_TEK (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_TEK)
+#define CALG_CYLINK_MEK (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_CYLINK_MEK) // Deprecated. Do not use
+#define CALG_RC5 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_RC5)
+#define CALG_AES_128 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_128)
+#define CALG_AES_192 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_192)
+#define CALG_AES_256 (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_256)
+#define CALG_AES (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES)
+#define CALG_THIRDPARTY_CIPHER (ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_THIRDPARTY | ALG_SID_THIRDPARTY_ANY)
+
+
+#define BCRYPT_RC2_ALGORITHM L"RC2"
+#define BCRYPT_RC4_ALGORITHM L"RC4"
+#define BCRYPT_AES_ALGORITHM L"AES"
+#define BCRYPT_DES_ALGORITHM L"DES"
+#define BCRYPT_DESX_ALGORITHM L"DESX"
+#define BCRYPT_3DES_ALGORITHM L"3DES"
+#define BCRYPT_3DES_112_ALGORITHM L"3DES_112"
+#define BCRYPT_AES_GMAC_ALGORITHM L"AES-GMAC"
+#define BCRYPT_AES_CMAC_ALGORITHM L"AES-CMAC"
+#define BCRYPT_XTS_AES_ALGORITHM L"XTS-AES"
+
+BOOL
+CryptGenKey(
+ HCRYPTPROV hProv,
+ ALG_ID Algid,
+ DWORD dwFlags,
+ HCRYPTKEY *phKey)
+{
+ return 0;
+}
+
+BOOL
+CryptDeriveKey(
+ HCRYPTPROV hProv,
+ ALG_ID Algid,
+ HCRYPTHASH hBaseData,
+ DWORD dwFlags,
+ HCRYPTKEY *phKey)
+{
+ return 0;
+}
+
+void
+DummyFunction(
+ LPCWSTR pszAlgId,
+ ALG_ID Algid)
+{}
+
+NTSTATUS
+BCryptOpenAlgorithmProvider(
+ BCRYPT_ALG_HANDLE *phAlgorithm,
+ LPCWSTR pszAlgId,
+ LPCWSTR pszImplementation,
+ ULONG dwFlags)
+{
+ return 0;
+}
+
+// Macro testing
+#define MACRO_INVOCATION_CRYPTGENKEY CryptGenKey(0, CALG_RC4, 0, 0);
+#define MACRO_INVOCATION_CRYPTDERIVEKEY CryptDeriveKey(0, CALG_CYLINK_MEK, 0, 0, 0);
+#define MACRO_INVOCATION_CNG BCryptOpenAlgorithmProvider(0, BCRYPT_3DES_112_ALGORITHM, 0, 0);
+
+int main()
+{
+ ////////////////////////////
+ // CAPI Test section
+ // Should fire an event
+ CryptGenKey(0, CALG_DES, 0, 0);
+ CryptGenKey(0, CALG_3DES_112, 0, 0);
+ CryptGenKey(0, CALG_3DES, 0, 0);
+ CryptGenKey(0, CALG_DESX, 0, 0);
+ CryptGenKey(0, CALG_RC2, 0, 0);
+ CryptGenKey(0, CALG_RC4, 0, 0);
+ CryptGenKey(0, CALG_SEAL, 0, 0);
+ CryptGenKey(0, CALG_SKIPJACK, 0, 0);
+ CryptGenKey(0, CALG_TEK, 0, 0);
+ CryptGenKey(0, CALG_CYLINK_MEK, 0, 0);
+ CryptGenKey(0, CALG_RC5, 0, 0);
+ CryptGenKey(0, CALG_THIRDPARTY_CIPHER, 0, 0);
+ CryptGenKey(0, ALG_CLASS_DATA_ENCRYPT, 0, 0);
+ CryptGenKey(0, ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK, 0, 0);
+ CryptGenKey(0, ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM, 0, 0);
+ // Verifying that all stream ciphers are flagged
+ CryptGenKey(0, ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_AES, 0, 0);
+ // Verifying that invocations through a macro are flagged
+ MACRO_INVOCATION_CRYPTGENKEY
+ // Numeric representation
+ CryptGenKey(0, 0x6609, 0, 0);
+
+ CryptDeriveKey(0, CALG_DES, 0, 0, 0);
+ CryptDeriveKey(0, CALG_3DES_112, 0, 0, 0);
+ CryptDeriveKey(0, CALG_3DES, 0, 0, 0);
+ CryptDeriveKey(0, CALG_DESX, 0, 0, 0);
+ CryptDeriveKey(0, CALG_RC2, 0, 0, 0);
+ CryptDeriveKey(0, CALG_RC4, 0, 0, 0);
+ CryptDeriveKey(0, CALG_SEAL, 0, 0, 0);
+ CryptDeriveKey(0, CALG_SKIPJACK, 0, 0, 0);
+ CryptDeriveKey(0, CALG_TEK, 0, 0, 0);
+ CryptDeriveKey(0, CALG_CYLINK_MEK, 0, 0, 0);
+ CryptDeriveKey(0, CALG_RC5, 0, 0, 0);
+ CryptDeriveKey(0, CALG_THIRDPARTY_CIPHER, 0, 0, 0);
+ CryptDeriveKey(0, ALG_CLASS_DATA_ENCRYPT, 0, 0, 0);
+ CryptDeriveKey(0, ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK, 0, 0, 0);
+ CryptDeriveKey(0, ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM, 0, 0, 0);
+ // Verifying that all stream ciphers are flagged
+ CryptDeriveKey(0, ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_AES, 0, 0, 0);
+ // Verifying that invocations through a macro are flagged
+ MACRO_INVOCATION_CRYPTDERIVEKEY
+ // Numeric representation
+ CryptDeriveKey(0, 0x6609, 0, 0, 0);
+
+ // Should not fire an event
+ CryptGenKey(0, CALG_AES_128, 0, 0);
+ CryptGenKey(0, CALG_AES_192, 0, 0);
+ CryptGenKey(0, CALG_AES_256, 0, 0);
+ CryptGenKey(0, CALG_AES, 0, 0);
+ CryptDeriveKey(0, CALG_AES_128, 0, 0, 0);
+ CryptDeriveKey(0, CALG_AES_192, 0, 0, 0);
+ CryptDeriveKey(0, CALG_AES_256, 0, 0, 0);
+ CryptDeriveKey(0, CALG_AES, 0, 0, 0);
+ if (CALG_RC5 > 0)
+ {
+ DummyFunction(0, CALG_SKIPJACK);
+ }
+
+ /////////////////////////////
+ // CNG Test section
+ // Should fire an event
+ BCryptOpenAlgorithmProvider(0, BCRYPT_RC2_ALGORITHM, 0, 0);
+ BCryptOpenAlgorithmProvider(0, BCRYPT_RC4_ALGORITHM, 0, 0);
+ BCryptOpenAlgorithmProvider(0, BCRYPT_DES_ALGORITHM, 0, 0);
+ BCryptOpenAlgorithmProvider(0, BCRYPT_DESX_ALGORITHM, 0, 0);
+ BCryptOpenAlgorithmProvider(0, BCRYPT_3DES_ALGORITHM, 0, 0);
+ BCryptOpenAlgorithmProvider(0, BCRYPT_3DES_112_ALGORITHM, 0, 0);
+ BCryptOpenAlgorithmProvider(0, BCRYPT_AES_GMAC_ALGORITHM, 0, 0);
+ BCryptOpenAlgorithmProvider(0, BCRYPT_AES_CMAC_ALGORITHM, 0, 0);
+ BCryptOpenAlgorithmProvider(0, L"3DES", 0, 0);
+ MACRO_INVOCATION_CNG
+
+ // Should not fire an event
+ BCryptOpenAlgorithmProvider(0, BCRYPT_AES_ALGORITHM, 0, 0);
+ BCryptOpenAlgorithmProvider(0, BCRYPT_XTS_AES_ALGORITHM, 0, 0);
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/CapiAndCng/WeakEncryption.expected b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/CapiAndCng/WeakEncryption.expected
new file mode 100644
index 000000000000..bb9b8c65c83c
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/CapiAndCng/WeakEncryption.expected
@@ -0,0 +1,46 @@
+| Test.cpp:130:17:130:24 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_DES | Test.cpp:130:17:130:24 | ... \| ... | ... \| ... |
+| Test.cpp:131:17:131:29 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_3DES_112 | Test.cpp:131:17:131:29 | ... \| ... | ... \| ... |
+| Test.cpp:132:17:132:25 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_3DES | Test.cpp:132:17:132:25 | ... \| ... | ... \| ... |
+| Test.cpp:133:17:133:25 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_DESX | Test.cpp:133:17:133:25 | ... \| ... | ... \| ... |
+| Test.cpp:134:17:134:24 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_RC2 | Test.cpp:134:17:134:24 | ... \| ... | ... \| ... |
+| Test.cpp:135:17:135:24 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_RC4 | Test.cpp:135:17:135:24 | ... \| ... | ... \| ... |
+| Test.cpp:136:17:136:25 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_SEAL | Test.cpp:136:17:136:25 | ... \| ... | ... \| ... |
+| Test.cpp:137:17:137:29 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_SKIPJACK | Test.cpp:137:17:137:29 | ... \| ... | ... \| ... |
+| Test.cpp:138:17:138:24 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_TEK | Test.cpp:138:17:138:24 | ... \| ... | ... \| ... |
+| Test.cpp:139:17:139:31 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_CYLINK_MEK | Test.cpp:139:17:139:31 | ... \| ... | ... \| ... |
+| Test.cpp:140:17:140:24 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_RC5 | Test.cpp:140:17:140:24 | ... \| ... | ... \| ... |
+| Test.cpp:141:17:141:38 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_THIRDPARTY_CIPHER | Test.cpp:141:17:141:38 | ... \| ... | ... \| ... |
+| Test.cpp:142:17:142:38 | ... << ... | Call to a cryptographic function with a banned symmetric encryption algorithm: ALG_CLASS_DATA_ENCRYPT | Test.cpp:142:17:142:38 | ... << ... | ... << ... |
+| Test.cpp:143:17:143:55 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: 26112 | Test.cpp:143:17:143:55 | ... \| ... | ... \| ... |
+| Test.cpp:144:17:144:56 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: 26624 | Test.cpp:144:17:144:56 | ... \| ... | ... \| ... |
+| Test.cpp:146:17:146:70 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: 26641 | Test.cpp:146:17:146:70 | ... \| ... | ... \| ... |
+| Test.cpp:148:2:148:29 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: 26625 | Test.cpp:148:2:148:29 | ... \| ... | ... \| ... |
+| Test.cpp:150:17:150:22 | 26121 | Call to a cryptographic function with a banned symmetric encryption algorithm: 0x6609 | Test.cpp:150:17:150:22 | 26121 | 26121 |
+| Test.cpp:152:20:152:27 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_DES | Test.cpp:152:20:152:27 | ... \| ... | ... \| ... |
+| Test.cpp:153:20:153:32 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_3DES_112 | Test.cpp:153:20:153:32 | ... \| ... | ... \| ... |
+| Test.cpp:154:20:154:28 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_3DES | Test.cpp:154:20:154:28 | ... \| ... | ... \| ... |
+| Test.cpp:155:20:155:28 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_DESX | Test.cpp:155:20:155:28 | ... \| ... | ... \| ... |
+| Test.cpp:156:20:156:27 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_RC2 | Test.cpp:156:20:156:27 | ... \| ... | ... \| ... |
+| Test.cpp:157:20:157:27 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_RC4 | Test.cpp:157:20:157:27 | ... \| ... | ... \| ... |
+| Test.cpp:158:20:158:28 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_SEAL | Test.cpp:158:20:158:28 | ... \| ... | ... \| ... |
+| Test.cpp:159:20:159:32 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_SKIPJACK | Test.cpp:159:20:159:32 | ... \| ... | ... \| ... |
+| Test.cpp:160:20:160:27 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_TEK | Test.cpp:160:20:160:27 | ... \| ... | ... \| ... |
+| Test.cpp:161:20:161:34 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_CYLINK_MEK | Test.cpp:161:20:161:34 | ... \| ... | ... \| ... |
+| Test.cpp:162:20:162:27 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_RC5 | Test.cpp:162:20:162:27 | ... \| ... | ... \| ... |
+| Test.cpp:163:20:163:41 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: CALG_THIRDPARTY_CIPHER | Test.cpp:163:20:163:41 | ... \| ... | ... \| ... |
+| Test.cpp:164:20:164:41 | ... << ... | Call to a cryptographic function with a banned symmetric encryption algorithm: ALG_CLASS_DATA_ENCRYPT | Test.cpp:164:20:164:41 | ... << ... | ... << ... |
+| Test.cpp:165:20:165:58 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: 26112 | Test.cpp:165:20:165:58 | ... \| ... | ... \| ... |
+| Test.cpp:166:20:166:59 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: 26624 | Test.cpp:166:20:166:59 | ... \| ... | ... \| ... |
+| Test.cpp:168:20:168:73 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: 26641 | Test.cpp:168:20:168:73 | ... \| ... | ... \| ... |
+| Test.cpp:170:2:170:32 | ... \| ... | Call to a cryptographic function with a banned symmetric encryption algorithm: 26124 | Test.cpp:170:2:170:32 | ... \| ... | ... \| ... |
+| Test.cpp:172:20:172:25 | 26121 | Call to a cryptographic function with a banned symmetric encryption algorithm: 0x6609 | Test.cpp:172:20:172:25 | 26121 | 26121 |
+| Test.cpp:191:33:191:52 | RC2 | Call to a cryptographic function with a banned symmetric encryption algorithm: BCRYPT_RC2_ALGORITHM | Test.cpp:191:33:191:52 | RC2 | RC2 |
+| Test.cpp:192:33:192:52 | RC4 | Call to a cryptographic function with a banned symmetric encryption algorithm: BCRYPT_RC4_ALGORITHM | Test.cpp:192:33:192:52 | RC4 | RC4 |
+| Test.cpp:193:33:193:52 | DES | Call to a cryptographic function with a banned symmetric encryption algorithm: BCRYPT_DES_ALGORITHM | Test.cpp:193:33:193:52 | DES | DES |
+| Test.cpp:194:33:194:53 | DESX | Call to a cryptographic function with a banned symmetric encryption algorithm: BCRYPT_DESX_ALGORITHM | Test.cpp:194:33:194:53 | DESX | DESX |
+| Test.cpp:195:33:195:53 | 3DES | Call to a cryptographic function with a banned symmetric encryption algorithm: BCRYPT_3DES_ALGORITHM | Test.cpp:195:33:195:53 | 3DES | 3DES |
+| Test.cpp:196:33:196:57 | 3DES_112 | Call to a cryptographic function with a banned symmetric encryption algorithm: BCRYPT_3DES_112_ALGORITHM | Test.cpp:196:33:196:57 | 3DES_112 | 3DES_112 |
+| Test.cpp:197:33:197:57 | AES-GMAC | Call to a cryptographic function with a banned symmetric encryption algorithm: BCRYPT_AES_GMAC_ALGORITHM | Test.cpp:197:33:197:57 | AES-GMAC | AES-GMAC |
+| Test.cpp:198:33:198:57 | AES-CMAC | Call to a cryptographic function with a banned symmetric encryption algorithm: BCRYPT_AES_CMAC_ALGORITHM | Test.cpp:198:33:198:57 | AES-CMAC | AES-CMAC |
+| Test.cpp:199:33:199:39 | 3DES | Call to a cryptographic function with a banned symmetric encryption algorithm: L"3DES" | Test.cpp:199:33:199:39 | 3DES | 3DES |
+| Test.cpp:200:2:200:21 | 3DES_112 | Call to a cryptographic function with a banned symmetric encryption algorithm: 3DES_112 | Test.cpp:200:2:200:21 | 3DES_112 | 3DES_112 |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/CapiAndCng/WeakEncryption.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/CapiAndCng/WeakEncryption.qlref
new file mode 100644
index 000000000000..cfdff69c3171
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/CapiAndCng/WeakEncryption.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Cryptography/BannedEncryption.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/Test.cpp b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/Test.cpp
new file mode 100644
index 000000000000..f4c58d6b9ea9
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/Test.cpp
@@ -0,0 +1,191 @@
+#include "./openssl/other.h"
+
+// Other RC4
+void RC4()
+{
+ int myRc4 = 0;
+}
+
+void* malloc(int size);
+
+#define MACRO_RC4 RC4(0, 0, 0, 0);
+#define NULL 0
+
+void func_calls()
+{
+ // Should not be flagged
+ AES_encrypt(0, 0, 0);
+ AES_cbc_encrypt(0, 0, 0, 0, 0, 0);
+
+ // OK Encryption but bad mode
+ AES_ecb_encrypt(0, 0, 0, 0);
+ AES_cfb128_encrypt(0, 0, 0, 0, 0, 0, 0);
+ AES_cfb1_encrypt(0, 0, 0, 0, 0, 0, 0);
+ AES_cfb8_encrypt(0, 0, 0, 0, 0, 0, 0);
+ AES_ofb128_encrypt(0, 0, 0, 0, 0, 0);
+ AES_ige_encrypt(0, 0, 0, 0, 0, 0);
+ AES_bi_ige_encrypt(0, 0, 0, 0, 0, 0, 0);
+
+ // Everything else should be flagged as bad encryption
+ MACRO_RC4
+ BF_encrypt(0,0);
+ BF_ecb_encrypt(0,0,0,0);
+ BF_cbc_encrypt(0, 0, 0, 0, 0, 0);
+ BF_cfb64_encrypt(0, 0, 0, 0, 0, 0,0);
+ BF_ofb64_encrypt(0, 0, 0, 0, 0, 0);
+ Camellia_encrypt(0,0,0);
+ Camellia_ecb_encrypt(0, 0, 0, 0);
+ Camellia_cbc_encrypt(0, 0, 0, 0, 0, 0);
+ Camellia_cfb128_encrypt(0, 0, 0, 0, 0, 0, 0);
+ Camellia_cfb1_encrypt(0, 0, 0, 0, 0, 0, 0);
+ Camellia_cfb8_encrypt(0, 0, 0, 0, 0, 0,0);
+ Camellia_ofb128_encrypt(0, 0, 0, 0, 0, 0);
+ Camellia_ctr128_encrypt(0, 0, 0, 0, 0, 0,0);
+ DES_ecb3_encrypt(0,0,0,0,0,0);
+ DES_cbc_encrypt(0, 0, 0, 0, 0, 0);
+ DES_ncbc_encrypt(0, 0, 0, 0, 0, 0);
+ DES_xcbc_encrypt(0, 0, 0, 0, 0, 0,0,0);
+ DES_cfb_encrypt(0, 0, 0, 0, 0, 0,0);
+ DES_ecb_encrypt(0, 0, 0, 0);
+ DES_encrypt1(0, 0, 0);
+ DES_encrypt2(0, 0, 0);
+ DES_encrypt3(0, 0, 0,0);
+ DES_ede3_cbc_encrypt(0, 0, 0, 0, 0, 0, 0,0);
+ DES_ofb64_encrypt(0,0,0,0,0,0);
+ IDEA_ecb_encrypt(0,0,0);
+ IDEA_cbc_encrypt(0,0,0,0,0,0);
+ IDEA_cfb64_encrypt(0, 0, 0, 0, 0, 0,0);
+ IDEA_ofb64_encrypt(0, 0, 0, 0, 0, 0);
+ IDEA_encrypt(0, 0);
+ RC2_ecb_encrypt(0, 0, 0, 0);
+ RC2_encrypt(0, 0);
+ RC2_cbc_encrypt(0, 0, 0, 0, 0, 0);
+ RC2_cfb64_encrypt(0, 0, 0, 0, 0, 0,0);
+ RC2_ofb64_encrypt(0, 0, 0, 0, 0, 0);
+ RC5_32_ecb_encrypt(0, 0, 0, 0);
+ RC5_32_encrypt(0,0);
+ RC5_32_cbc_encrypt(0, 0, 0, 0, 0, 0);
+ RC5_32_cfb64_encrypt(0, 0, 0, 0, 0, 0, 0);
+ RC5_32_ofb64_encrypt(0, 0, 0, 0, 0, 0);
+ RC4_set_key(0,0,0);
+ RC4(0, 0, 0, 0);
+}
+
+void non_func_calls(int argc, char **argv)
+{
+ // GOOD cases: should not be flagged
+ {
+ EVP_CIPHER *cipher = NULL;
+ ASN1_OBJECT *obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+
+ cipher = EVP_CIPHER_fetch(NULL, "aes-256-xts", NULL);
+ cipher = EVP_get_cipherbyname("aes-128-cbc");
+ cipher = EVP_get_cipherbynid(423); //NID 423 is aes-192-cbc
+ obj->nid = 913; // NID 913 is aes-128-xts
+ cipher = EVP_get_cipherbyobj(obj);
+ obj = (ASN1_OBJECT*)malloc(sizeof(ASN1_OBJECT));
+ obj->sn = "aes-128-cbc-hmac-sha1";
+ cipher = EVP_get_cipherbyobj(obj);
+
+ // Indirect flow through transformative functions (i.e., converting the alg format)
+ int nid = OBJ_obj2nid(obj);
+ cipher = EVP_get_cipherbynid(nid);
+ ASN1_OBJECT *obj_cpy = OBJ_dup(obj);
+ cipher = EVP_get_cipherbyobj(obj_cpy);
+ char* name = "THIS STRING WILL BE OVERWRITTEN";
+ OBJ_obj2txt(name, 0, obj, 0);
+ cipher = EVP_get_cipherbyname(name);
+ nid = OBJ_obj2nid(obj_cpy);
+ name = OBJ_nid2sn(nid);
+ ASN1_OBJECT *obj2 = OBJ_txt2obj(name, 0);
+ cipher = EVP_get_cipherbyobj(obj2);
+ }
+
+ // Bad Cases: UNKNOWN algorithms
+ {
+ EVP_CIPHER *cipher = NULL;
+ ASN1_OBJECT *obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+
+ cipher = EVP_CIPHER_fetch(NULL, "FOOBAR", NULL);
+ cipher = EVP_get_cipherbyname("TEST");
+ cipher = EVP_get_cipherbynid(2000);
+ obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+ obj->nid = 1999;
+ cipher = EVP_get_cipherbyobj(obj);
+ obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+ obj->sn = "Test2";
+ cipher = EVP_get_cipherbyobj(obj);
+ obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+ obj->sn = argv[0]; // Ignoring the possible overflow
+ cipher = EVP_get_cipherbyobj(obj);
+
+ cipher = EVP_CIPHER_fetch(NULL, "NULL", NULL);
+ cipher = EVP_CIPHER_fetch(NULL, "othermailbox", NULL);
+
+ cipher = EVP_get_cipherbynid(0);
+
+ // Indirect flow through transformative functions (i.e., converting the alg format)
+ // Testing flow with unknown inputs should be sufficient with known bad inputs,
+ // so only testing with known bad inputs for UNKNOWN for now.
+ ASN1_OBJECT *obj_cpy = NULL;
+ ASN1_OBJECT *obj2 = NULL;
+ obj->nid = 1998;
+ int nid = OBJ_obj2nid(obj);
+ cipher = EVP_get_cipherbynid(nid);
+ obj->nid = 1997;
+ obj_cpy = OBJ_dup(obj);
+ cipher = EVP_get_cipherbyobj(obj_cpy);
+ obj->sn = "NOT AN ALG";
+ char* name = "THIS STRING WILL BE OVERWRITTEN";
+ OBJ_obj2txt(name, 0, obj, 0);
+ cipher = EVP_get_cipherbyname(name);
+ obj->nid = 1996;
+ obj_cpy = OBJ_dup(obj);
+ nid = OBJ_obj2nid(obj_cpy);
+ name = OBJ_nid2sn(nid);
+ obj2 = OBJ_txt2obj(name, 0);
+ cipher = EVP_get_cipherbyobj(obj2);
+
+ // Nonsense cases (known algorithms to incorrect sinks)
+ cipher = EVP_get_cipherbynid(19); // NID 19 is RSA
+ cipher = EVP_get_cipherbyname("secp160k1"); // An elliptic curve
+ }
+
+ // Bad Cases: Banned algorithms
+ {
+ EVP_CIPHER *cipher = NULL;
+ ASN1_OBJECT *obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+
+ // banned symmetric ciphers
+ cipher = EVP_CIPHER_fetch(NULL, "des-ede3", NULL);
+ cipher = EVP_get_cipherbyname("des-ede3-cbc");
+ cipher = EVP_get_cipherbynid(31); // NID 31 is des-cbc
+ obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+ obj->nid = 30; // NID 30 is des-cfb
+ cipher = EVP_get_cipherbyobj(obj);
+ obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+ obj->sn = "camellia256";
+ cipher = EVP_get_cipherbyobj(obj);
+ cipher = EVP_CIPHER_fetch(NULL, "rc4", NULL);
+ cipher = EVP_get_cipherbyname("rc4-40");
+ cipher = EVP_get_cipherbynid(5); // NID 5 is rc4
+ obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+ obj->sn = "desx-cbc";
+ cipher = EVP_get_cipherbyobj(obj);
+ cipher = EVP_CIPHER_fetch(NULL, "bf-cbc", NULL);
+ cipher = EVP_get_cipherbyname("rc2-64-cbc");
+ cipher = EVP_get_cipherbynid(1019); // NID 1019 is chacha20
+ obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+ obj->nid = 813; // NID 813 is gost89
+ cipher = EVP_get_cipherbyobj(obj);
+ obj = (ASN1_OBJECT *)malloc(sizeof(ASN1_OBJECT));
+ obj->sn = "sm4-cbc";
+ cipher = EVP_get_cipherbyobj(obj);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ func_calls();
+ non_func_calls(argc, argv);
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/WeakEncryption.expected b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/WeakEncryption.expected
new file mode 100644
index 000000000000..a9165f715dae
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/WeakEncryption.expected
@@ -0,0 +1,57 @@
+| Test.cpp:30:2:30:10 | call to RC4 | Use of banned symmetric encryption algorithm: RC4. | Test.cpp:30:2:30:10 | call to RC4 | call to RC4 |
+| Test.cpp:31:2:31:11 | call to BF_encrypt | Use of banned symmetric encryption algorithm: BF. | Test.cpp:31:2:31:11 | call to BF_encrypt | call to BF_encrypt |
+| Test.cpp:32:2:32:15 | call to BF_ecb_encrypt | Use of banned symmetric encryption algorithm: BF. | Test.cpp:32:2:32:15 | call to BF_ecb_encrypt | call to BF_ecb_encrypt |
+| Test.cpp:33:2:33:15 | call to BF_cbc_encrypt | Use of banned symmetric encryption algorithm: BF. | Test.cpp:33:2:33:15 | call to BF_cbc_encrypt | call to BF_cbc_encrypt |
+| Test.cpp:34:2:34:17 | call to BF_cfb64_encrypt | Use of banned symmetric encryption algorithm: BF. | Test.cpp:34:2:34:17 | call to BF_cfb64_encrypt | call to BF_cfb64_encrypt |
+| Test.cpp:35:2:35:17 | call to BF_ofb64_encrypt | Use of banned symmetric encryption algorithm: BF. | Test.cpp:35:2:35:17 | call to BF_ofb64_encrypt | call to BF_ofb64_encrypt |
+| Test.cpp:36:2:36:17 | call to Camellia_encrypt | Use of banned symmetric encryption algorithm: CAMELLIA. | Test.cpp:36:2:36:17 | call to Camellia_encrypt | call to Camellia_encrypt |
+| Test.cpp:37:2:37:21 | call to Camellia_ecb_encrypt | Use of banned symmetric encryption algorithm: CAMELLIA. | Test.cpp:37:2:37:21 | call to Camellia_ecb_encrypt | call to Camellia_ecb_encrypt |
+| Test.cpp:38:2:38:21 | call to Camellia_cbc_encrypt | Use of banned symmetric encryption algorithm: CAMELLIA. | Test.cpp:38:2:38:21 | call to Camellia_cbc_encrypt | call to Camellia_cbc_encrypt |
+| Test.cpp:39:2:39:24 | call to Camellia_cfb128_encrypt | Use of banned symmetric encryption algorithm: CAMELLIA. | Test.cpp:39:2:39:24 | call to Camellia_cfb128_encrypt | call to Camellia_cfb128_encrypt |
+| Test.cpp:40:2:40:22 | call to Camellia_cfb1_encrypt | Use of banned symmetric encryption algorithm: CAMELLIA. | Test.cpp:40:2:40:22 | call to Camellia_cfb1_encrypt | call to Camellia_cfb1_encrypt |
+| Test.cpp:41:2:41:22 | call to Camellia_cfb8_encrypt | Use of banned symmetric encryption algorithm: CAMELLIA. | Test.cpp:41:2:41:22 | call to Camellia_cfb8_encrypt | call to Camellia_cfb8_encrypt |
+| Test.cpp:42:2:42:24 | call to Camellia_ofb128_encrypt | Use of banned symmetric encryption algorithm: CAMELLIA. | Test.cpp:42:2:42:24 | call to Camellia_ofb128_encrypt | call to Camellia_ofb128_encrypt |
+| Test.cpp:43:2:43:24 | call to Camellia_ctr128_encrypt | Use of banned symmetric encryption algorithm: CAMELLIA. | Test.cpp:43:2:43:24 | call to Camellia_ctr128_encrypt | call to Camellia_ctr128_encrypt |
+| Test.cpp:44:2:44:17 | call to DES_ecb3_encrypt | Use of banned symmetric encryption algorithm: DES. | Test.cpp:44:2:44:17 | call to DES_ecb3_encrypt | call to DES_ecb3_encrypt |
+| Test.cpp:45:2:45:16 | call to DES_cbc_encrypt | Use of banned symmetric encryption algorithm: DES. | Test.cpp:45:2:45:16 | call to DES_cbc_encrypt | call to DES_cbc_encrypt |
+| Test.cpp:46:2:46:17 | call to DES_ncbc_encrypt | Use of banned symmetric encryption algorithm: DES. | Test.cpp:46:2:46:17 | call to DES_ncbc_encrypt | call to DES_ncbc_encrypt |
+| Test.cpp:47:2:47:17 | call to DES_xcbc_encrypt | Use of banned symmetric encryption algorithm: DESX. | Test.cpp:47:2:47:17 | call to DES_xcbc_encrypt | call to DES_xcbc_encrypt |
+| Test.cpp:48:2:48:16 | call to DES_cfb_encrypt | Use of banned symmetric encryption algorithm: DES. | Test.cpp:48:2:48:16 | call to DES_cfb_encrypt | call to DES_cfb_encrypt |
+| Test.cpp:49:2:49:16 | call to DES_ecb_encrypt | Use of banned symmetric encryption algorithm: DES. | Test.cpp:49:2:49:16 | call to DES_ecb_encrypt | call to DES_ecb_encrypt |
+| Test.cpp:50:2:50:13 | call to DES_encrypt1 | Use of banned symmetric encryption algorithm: DES. | Test.cpp:50:2:50:13 | call to DES_encrypt1 | call to DES_encrypt1 |
+| Test.cpp:51:2:51:13 | call to DES_encrypt2 | Use of banned symmetric encryption algorithm: DES. | Test.cpp:51:2:51:13 | call to DES_encrypt2 | call to DES_encrypt2 |
+| Test.cpp:52:2:52:13 | call to DES_encrypt3 | Use of banned symmetric encryption algorithm: DES. | Test.cpp:52:2:52:13 | call to DES_encrypt3 | call to DES_encrypt3 |
+| Test.cpp:53:2:53:21 | call to DES_ede3_cbc_encrypt | Use of banned symmetric encryption algorithm: DES. | Test.cpp:53:2:53:21 | call to DES_ede3_cbc_encrypt | call to DES_ede3_cbc_encrypt |
+| Test.cpp:54:2:54:18 | call to DES_ofb64_encrypt | Use of banned symmetric encryption algorithm: DES. | Test.cpp:54:2:54:18 | call to DES_ofb64_encrypt | call to DES_ofb64_encrypt |
+| Test.cpp:55:2:55:17 | call to IDEA_ecb_encrypt | Use of banned symmetric encryption algorithm: IDEA. | Test.cpp:55:2:55:17 | call to IDEA_ecb_encrypt | call to IDEA_ecb_encrypt |
+| Test.cpp:56:2:56:17 | call to IDEA_cbc_encrypt | Use of banned symmetric encryption algorithm: IDEA. | Test.cpp:56:2:56:17 | call to IDEA_cbc_encrypt | call to IDEA_cbc_encrypt |
+| Test.cpp:57:2:57:19 | call to IDEA_cfb64_encrypt | Use of banned symmetric encryption algorithm: IDEA. | Test.cpp:57:2:57:19 | call to IDEA_cfb64_encrypt | call to IDEA_cfb64_encrypt |
+| Test.cpp:58:2:58:19 | call to IDEA_ofb64_encrypt | Use of banned symmetric encryption algorithm: IDEA. | Test.cpp:58:2:58:19 | call to IDEA_ofb64_encrypt | call to IDEA_ofb64_encrypt |
+| Test.cpp:59:2:59:13 | call to IDEA_encrypt | Use of banned symmetric encryption algorithm: IDEA. | Test.cpp:59:2:59:13 | call to IDEA_encrypt | call to IDEA_encrypt |
+| Test.cpp:60:2:60:16 | call to RC2_ecb_encrypt | Use of banned symmetric encryption algorithm: RC2. | Test.cpp:60:2:60:16 | call to RC2_ecb_encrypt | call to RC2_ecb_encrypt |
+| Test.cpp:61:2:61:12 | call to RC2_encrypt | Use of banned symmetric encryption algorithm: RC2. | Test.cpp:61:2:61:12 | call to RC2_encrypt | call to RC2_encrypt |
+| Test.cpp:62:2:62:16 | call to RC2_cbc_encrypt | Use of banned symmetric encryption algorithm: RC2. | Test.cpp:62:2:62:16 | call to RC2_cbc_encrypt | call to RC2_cbc_encrypt |
+| Test.cpp:63:2:63:18 | call to RC2_cfb64_encrypt | Use of banned symmetric encryption algorithm: RC2. | Test.cpp:63:2:63:18 | call to RC2_cfb64_encrypt | call to RC2_cfb64_encrypt |
+| Test.cpp:64:2:64:18 | call to RC2_ofb64_encrypt | Use of banned symmetric encryption algorithm: RC2. | Test.cpp:64:2:64:18 | call to RC2_ofb64_encrypt | call to RC2_ofb64_encrypt |
+| Test.cpp:65:2:65:19 | call to RC5_32_ecb_encrypt | Use of banned symmetric encryption algorithm: RC5. | Test.cpp:65:2:65:19 | call to RC5_32_ecb_encrypt | call to RC5_32_ecb_encrypt |
+| Test.cpp:66:2:66:15 | call to RC5_32_encrypt | Use of banned symmetric encryption algorithm: RC5. | Test.cpp:66:2:66:15 | call to RC5_32_encrypt | call to RC5_32_encrypt |
+| Test.cpp:67:2:67:19 | call to RC5_32_cbc_encrypt | Use of banned symmetric encryption algorithm: RC5. | Test.cpp:67:2:67:19 | call to RC5_32_cbc_encrypt | call to RC5_32_cbc_encrypt |
+| Test.cpp:68:2:68:21 | call to RC5_32_cfb64_encrypt | Use of banned symmetric encryption algorithm: RC5. | Test.cpp:68:2:68:21 | call to RC5_32_cfb64_encrypt | call to RC5_32_cfb64_encrypt |
+| Test.cpp:69:2:69:21 | call to RC5_32_ofb64_encrypt | Use of banned symmetric encryption algorithm: RC5. | Test.cpp:69:2:69:21 | call to RC5_32_ofb64_encrypt | call to RC5_32_ofb64_encrypt |
+| Test.cpp:70:2:70:12 | call to RC4_set_key | Use of banned symmetric encryption algorithm: RC4. | Test.cpp:70:2:70:12 | call to RC4_set_key | call to RC4_set_key |
+| Test.cpp:71:2:71:4 | call to RC4 | Use of banned symmetric encryption algorithm: RC4. | Test.cpp:71:2:71:4 | call to RC4 | call to RC4 |
+| Test.cpp:160:35:160:44 | des-ede3 | Use of banned symmetric encryption algorithm: DES. | Test.cpp:160:35:160:44 | des-ede3 | des-ede3 |
+| Test.cpp:161:33:161:46 | des-ede3-cbc | Use of banned symmetric encryption algorithm: DES. | Test.cpp:161:33:161:46 | des-ede3-cbc | des-ede3-cbc |
+| Test.cpp:162:32:162:33 | 31 | Use of banned symmetric encryption algorithm: DES. | Test.cpp:162:32:162:33 | 31 | 31 |
+| Test.cpp:164:14:164:15 | 30 | Use of banned symmetric encryption algorithm: DES. Algorithm used at sink: $@. | Test.cpp:165:32:165:34 | obj | obj |
+| Test.cpp:167:13:167:25 | camellia256 | Use of banned symmetric encryption algorithm: CAMELLIA256. Algorithm used at sink: $@. | Test.cpp:168:32:168:34 | obj | obj |
+| Test.cpp:169:35:169:39 | rc4 | Use of banned symmetric encryption algorithm: RC4. | Test.cpp:169:35:169:39 | rc4 | rc4 |
+| Test.cpp:170:33:170:40 | rc4-40 | Use of banned symmetric encryption algorithm: RC4. | Test.cpp:170:33:170:40 | rc4-40 | rc4-40 |
+| Test.cpp:171:32:171:32 | 5 | Use of banned symmetric encryption algorithm: RC4. | Test.cpp:171:32:171:32 | 5 | 5 |
+| Test.cpp:173:13:173:22 | desx-cbc | Use of banned symmetric encryption algorithm: DESX. Algorithm used at sink: $@. | Test.cpp:174:32:174:34 | obj | obj |
+| Test.cpp:175:35:175:42 | bf-cbc | Use of banned symmetric encryption algorithm: BF. | Test.cpp:175:35:175:42 | bf-cbc | bf-cbc |
+| Test.cpp:176:33:176:44 | rc2-64-cbc | Use of banned symmetric encryption algorithm: RC2. | Test.cpp:176:33:176:44 | rc2-64-cbc | rc2-64-cbc |
+| Test.cpp:177:32:177:35 | 1019 | Use of banned symmetric encryption algorithm: CHACHA20. | Test.cpp:177:32:177:35 | 1019 | 1019 |
+| Test.cpp:179:14:179:16 | 813 | Use of banned symmetric encryption algorithm: GOST89. Algorithm used at sink: $@. | Test.cpp:180:32:180:34 | obj | obj |
+| Test.cpp:179:14:179:16 | 813 | Use of banned symmetric encryption algorithm: GOST2814789. Algorithm used at sink: $@. | Test.cpp:180:32:180:34 | obj | obj |
+| Test.cpp:182:13:182:21 | sm4-cbc | Use of banned symmetric encryption algorithm: SM4. Algorithm used at sink: $@. | Test.cpp:183:32:183:34 | obj | obj |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/WeakEncryption.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/WeakEncryption.qlref
new file mode 100644
index 000000000000..cfdff69c3171
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/WeakEncryption.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Cryptography/BannedEncryption.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/openssl/other.h b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/openssl/other.h
new file mode 100644
index 000000000000..ff474684b74f
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedEncryption/modeled_apis/openssl/other.h
@@ -0,0 +1,272 @@
+struct asn1_object_st {
+ const char *sn, *ln;
+ int nid;
+ int length;
+ const unsigned char *data; /* data remains const after init */
+ int flags; /* Should we free this one */
+};
+typedef struct asn1_object_st ASN1_OBJECT;
+
+struct evp_cipher_st {
+ int nid;
+
+ int block_size;
+ /* Default value for variable length ciphers */
+ int key_len;
+ int iv_len;
+
+ // /* Legacy structure members */
+ // /* Various flags */
+ // unsigned long flags;
+ // /* How the EVP_CIPHER was created. */
+ // int origin;
+ // /* init key */
+ // int (*init) (EVP_CIPHER_CTX *ctx, const unsigned char *key,
+ // const unsigned char *iv, int enc);
+ // /* encrypt/decrypt data */
+ // int (*do_cipher) (EVP_CIPHER_CTX *ctx, unsigned char *out,
+ // const unsigned char *in, size_t inl);
+ // /* cleanup ctx */
+ // int (*cleanup) (EVP_CIPHER_CTX *);
+ // /* how big ctx->cipher_data needs to be */
+ // int ctx_size;
+ // /* Populate a ASN1_TYPE with parameters */
+ // int (*set_asn1_parameters) (EVP_CIPHER_CTX *, ASN1_TYPE *);
+ // /* Get parameters from a ASN1_TYPE */
+ // int (*get_asn1_parameters) (EVP_CIPHER_CTX *, ASN1_TYPE *);
+ // /* Miscellaneous operations */
+ // int (*ctrl) (EVP_CIPHER_CTX *, int type, int arg, void *ptr);
+ // /* Application data */
+ // void *app_data;
+
+ // /* New structure members */
+ // /* Above comment to be removed when legacy has gone */
+ // int name_id;
+ char *type_name;
+ const char *description;
+ // OSSL_PROVIDER *prov;
+ // CRYPTO_REF_COUNT refcnt;
+ // CRYPTO_RWLOCK *lock;
+ // OSSL_FUNC_cipher_newctx_fn *newctx;
+ // OSSL_FUNC_cipher_encrypt_init_fn *einit;
+ // OSSL_FUNC_cipher_decrypt_init_fn *dinit;
+ // OSSL_FUNC_cipher_update_fn *cupdate;
+ // OSSL_FUNC_cipher_final_fn *cfinal;
+ // OSSL_FUNC_cipher_cipher_fn *ccipher;
+ // OSSL_FUNC_cipher_freectx_fn *freectx;
+ // OSSL_FUNC_cipher_dupctx_fn *dupctx;
+ // OSSL_FUNC_cipher_get_params_fn *get_params;
+ // OSSL_FUNC_cipher_get_ctx_params_fn *get_ctx_params;
+ // OSSL_FUNC_cipher_set_ctx_params_fn *set_ctx_params;
+ // OSSL_FUNC_cipher_gettable_params_fn *gettable_params;
+ // OSSL_FUNC_cipher_gettable_ctx_params_fn *gettable_ctx_params;
+ // OSSL_FUNC_cipher_settable_ctx_params_fn *settable_ctx_params;
+} /* EVP_CIPHER */ ;
+
+typedef struct evp_cipher_st EVP_CIPHER;
+
+typedef struct rc4_key_st {
+ int x, y;
+ int data[256];
+} RC4_KEY;
+
+struct key_st {
+ unsigned long rd_key[4];
+ int rounds;
+};
+typedef struct key_st AES_KEY, BF_KEY, CAMELLIA_KEY, DES_key_schedule, IDEA_KEY_SCHEDULE, RC2_KEY, RC5_32_KEY;
+
+typedef unsigned int DES_LONG, BF_LONG;
+typedef unsigned char DES_cblock[8];
+typedef unsigned char const_DES_cblock[8];
+typedef unsigned int size_t;
+
+
+#define CAMELLIA_BLOCK_SIZE 4
+
+
+// Symmetric Cipher Algorithm sinks
+EVP_CIPHER *EVP_CIPHER_fetch(void *ctx, const char *algorithm, const char *properties);
+EVP_CIPHER *EVP_get_cipherbyname(const char *name);
+EVP_CIPHER *EVP_get_cipherbynid(int nid);
+EVP_CIPHER *EVP_get_cipherbyobj(const ASN1_OBJECT *a);
+
+// ----------https://www.openssl.org/docs/man1.1.1/man3/OBJ_obj2txt.html
+ASN1_OBJECT *OBJ_nid2obj(int n);
+char *OBJ_nid2ln(int n);
+char *OBJ_nid2sn(int n);
+
+int OBJ_obj2nid(const ASN1_OBJECT *o);
+int OBJ_ln2nid(const char *ln);
+int OBJ_sn2nid(const char *sn);
+
+int OBJ_txt2nid(const char *s);
+
+ASN1_OBJECT *OBJ_txt2obj(const char *s, int no_name);
+int OBJ_obj2txt(char *buf, int buf_len, const ASN1_OBJECT *a, int no_name);
+
+int i2t_ASN1_OBJECT(char *buf, int buf_len, const ASN1_OBJECT *a);
+
+int OBJ_cmp(const ASN1_OBJECT *a, const ASN1_OBJECT *b);
+ASN1_OBJECT *OBJ_dup(const ASN1_OBJECT *o);
+
+int OBJ_create(const char *oid, const char *sn, const char *ln);
+//-------------
+//https://www.openssl.org/docs/man3.0/man3/EVP_CIPHER_get0_name.html
+char *EVP_CIPHER_get0_name(const EVP_CIPHER *cipher);
+//-----
+
+void AES_encrypt(const unsigned char *in, unsigned char *out,
+ const AES_KEY *key);
+void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,
+ const AES_KEY *key, const int enc);
+void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const AES_KEY *key,
+ unsigned char *ivec, const int enc);
+void AES_cfb128_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const AES_KEY *key,
+ unsigned char *ivec, int *num, const int enc);
+void AES_cfb1_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const AES_KEY *key,
+ unsigned char *ivec, int *num, const int enc);
+void AES_cfb8_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const AES_KEY *key,
+ unsigned char *ivec, int *num, const int enc);
+void AES_ofb128_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const AES_KEY *key,
+ unsigned char *ivec, int *num);
+/* NB: the IV is _two_ blocks long */
+void AES_ige_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const AES_KEY *key,
+ unsigned char *ivec, const int enc);
+/* NB: the IV is _four_ blocks long */
+void AES_bi_ige_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const AES_KEY *key,
+ const AES_KEY *key2, const unsigned char *ivec,
+ const int enc);
+void BF_encrypt(BF_LONG *data, const BF_KEY *key);
+void BF_decrypt(BF_LONG *data, const BF_KEY *key);
+
+void BF_ecb_encrypt(const unsigned char *in, unsigned char *out,
+ const BF_KEY *key, int enc);
+void BF_cbc_encrypt(const unsigned char *in, unsigned char *out, long length,
+ const BF_KEY *schedule, unsigned char *ivec, int enc);
+void BF_cfb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, const BF_KEY *schedule,
+ unsigned char *ivec, int *num, int enc);
+void BF_ofb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, const BF_KEY *schedule,
+ unsigned char *ivec, int *num);
+void Camellia_encrypt(const unsigned char *in, unsigned char *out,
+ const CAMELLIA_KEY *key);
+void Camellia_ecb_encrypt(const unsigned char *in, unsigned char *out,
+ const CAMELLIA_KEY *key, const int enc);
+void Camellia_cbc_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const CAMELLIA_KEY *key,
+ unsigned char *ivec, const int enc);
+void Camellia_cfb128_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const CAMELLIA_KEY *key,
+ unsigned char *ivec, int *num, const int enc);
+void Camellia_cfb1_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const CAMELLIA_KEY *key,
+ unsigned char *ivec, int *num, const int enc);
+void Camellia_cfb8_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const CAMELLIA_KEY *key,
+ unsigned char *ivec, int *num, const int enc);
+void Camellia_ofb128_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const CAMELLIA_KEY *key,
+ unsigned char *ivec, int *num);
+void Camellia_ctr128_encrypt(const unsigned char *in, unsigned char *out,
+ size_t length, const CAMELLIA_KEY *key,
+ unsigned char ivec[CAMELLIA_BLOCK_SIZE],
+ unsigned char ecount_buf[CAMELLIA_BLOCK_SIZE],
+ unsigned int *num);
+void DES_ecb3_encrypt(const_DES_cblock *input, DES_cblock *output,
+ DES_key_schedule *ks1, DES_key_schedule *ks2,
+ DES_key_schedule *ks3, int enc);
+void DES_cbc_encrypt(const unsigned char *input, unsigned char *output,
+ long length, DES_key_schedule *schedule,
+ DES_cblock *ivec, int enc);
+void DES_ncbc_encrypt(const unsigned char *input, unsigned char *output,
+ long length, DES_key_schedule *schedule,
+ DES_cblock *ivec, int enc);
+void DES_xcbc_encrypt(const unsigned char *input, unsigned char *output,
+ long length, DES_key_schedule *schedule,
+ DES_cblock *ivec, const_DES_cblock *inw,
+ const_DES_cblock *outw, int enc);
+void DES_cfb_encrypt(const unsigned char *in, unsigned char *out, int numbits,
+ long length, DES_key_schedule *schedule,
+ DES_cblock *ivec, int enc);
+void DES_ecb_encrypt(const_DES_cblock *input, DES_cblock *output,
+ DES_key_schedule *ks, int enc);
+void DES_encrypt1(DES_LONG *data, DES_key_schedule *ks, int enc);
+void DES_encrypt2(DES_LONG *data, DES_key_schedule *ks, int enc);
+void DES_encrypt3(DES_LONG *data, DES_key_schedule *ks1,
+ DES_key_schedule *ks2, DES_key_schedule *ks3);
+void DES_ede3_cbc_encrypt(const unsigned char *input, unsigned char *output,
+ long length,
+ DES_key_schedule *ks1, DES_key_schedule *ks2,
+ DES_key_schedule *ks3, DES_cblock *ivec, int enc);
+void DES_ede3_cfb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, DES_key_schedule *ks1,
+ DES_key_schedule *ks2, DES_key_schedule *ks3,
+ DES_cblock *ivec, int *num, int enc);
+void DES_ede3_cfb_encrypt(const unsigned char *in, unsigned char *out,
+ int numbits, long length, DES_key_schedule *ks1,
+ DES_key_schedule *ks2, DES_key_schedule *ks3,
+ DES_cblock *ivec, int enc);
+void DES_ede3_ofb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, DES_key_schedule *ks1,
+ DES_key_schedule *ks2, DES_key_schedule *ks3,
+ DES_cblock *ivec, int *num);
+void DES_ofb_encrypt(const unsigned char *in, unsigned char *out, int numbits,
+ long length, DES_key_schedule *schedule,
+ DES_cblock *ivec);
+void DES_pcbc_encrypt(const unsigned char *input, unsigned char *output,
+ long length, DES_key_schedule *schedule,
+ DES_cblock *ivec, int enc);
+void DES_cfb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, DES_key_schedule *schedule,
+ DES_cblock *ivec, int *num, int enc);
+void DES_ofb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, DES_key_schedule *schedule,
+ DES_cblock *ivec, int *num);
+void IDEA_ecb_encrypt(const unsigned char *in, unsigned char *out,
+ IDEA_KEY_SCHEDULE *ks);
+void IDEA_set_encrypt_key(const unsigned char *key, IDEA_KEY_SCHEDULE *ks);
+void IDEA_cbc_encrypt(const unsigned char *in, unsigned char *out,
+ long length, IDEA_KEY_SCHEDULE *ks, unsigned char *iv,
+ int enc);
+void IDEA_cfb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, IDEA_KEY_SCHEDULE *ks, unsigned char *iv,
+ int *num, int enc);
+void IDEA_ofb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, IDEA_KEY_SCHEDULE *ks, unsigned char *iv,
+ int *num);
+void IDEA_encrypt(unsigned long *in, IDEA_KEY_SCHEDULE *ks);
+void RC2_ecb_encrypt(const unsigned char *in, unsigned char *out,
+ RC2_KEY *key, int enc);
+void RC2_encrypt(unsigned long *data, RC2_KEY *key);
+void RC2_cbc_encrypt(const unsigned char *in, unsigned char *out, long length,
+ RC2_KEY *ks, unsigned char *iv, int enc);
+void RC2_cfb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, RC2_KEY *schedule, unsigned char *ivec,
+ int *num, int enc);
+void RC2_ofb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, RC2_KEY *schedule, unsigned char *ivec,
+ int *num);
+void RC5_32_ecb_encrypt(const unsigned char *in, unsigned char *out,
+ RC5_32_KEY *key, int enc);
+void RC5_32_encrypt(unsigned long *data, RC5_32_KEY *key);
+void RC5_32_cbc_encrypt(const unsigned char *in, unsigned char *out,
+ long length, RC5_32_KEY *ks, unsigned char *iv,
+ int enc);
+void RC5_32_cfb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, RC5_32_KEY *schedule,
+ unsigned char *ivec, int *num, int enc);
+void RC5_32_ofb64_encrypt(const unsigned char *in, unsigned char *out,
+ long length, RC5_32_KEY *schedule,
+ unsigned char *ivec, int *num);
+void RC4_set_key(RC4_KEY *key, int len, const unsigned char *data);
+void RC4(RC4_KEY *key, size_t len, const unsigned char *indata,
+ unsigned char *outdata);
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCapi/BannedModesCapi.expected b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCapi/BannedModesCapi.expected
new file mode 100644
index 000000000000..dc6be6d265af
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCapi/BannedModesCapi.expected
@@ -0,0 +1,7 @@
+| Test.cpp:100:2:100:17 | call to CryptSetKeyParam | Call to 'CryptSetKeyParam' function with argument dwParam = KP_MODE is setting up a banned block cipher mode. |
+| Test.cpp:114:2:114:17 | call to CryptSetKeyParam | Call to 'CryptSetKeyParam' function with argument dwParam = KP_MODE is setting up a banned block cipher mode. |
+| Test.cpp:116:2:116:17 | call to CryptSetKeyParam | Call to 'CryptSetKeyParam' function with argument dwParam = KP_MODE is setting up a banned block cipher mode. |
+| Test.cpp:118:2:118:17 | call to CryptSetKeyParam | Call to 'CryptSetKeyParam' function with argument dwParam = KP_MODE is setting up a banned block cipher mode. |
+| Test.cpp:120:2:120:17 | call to CryptSetKeyParam | Call to 'CryptSetKeyParam' function with argument dwParam = KP_MODE is setting up a banned block cipher mode. |
+| Test.cpp:122:2:122:17 | call to CryptSetKeyParam | Call to 'CryptSetKeyParam' function with argument dwParam = KP_MODE is setting up a banned block cipher mode. |
+| Test.cpp:124:2:124:43 | call to CryptSetKeyParam | Call to 'CryptSetKeyParam' function with argument dwParam = KP_MODE is setting up a banned block cipher mode. |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCapi/BannedModesCapi.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCapi/BannedModesCapi.qlref
new file mode 100644
index 000000000000..c7c219aac416
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCapi/BannedModesCapi.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Cryptography/BannedModesCAPI.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCapi/Test.cpp b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCapi/Test.cpp
new file mode 100644
index 000000000000..997568c0b20f
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCapi/Test.cpp
@@ -0,0 +1,133 @@
+#define CONST const
+
+typedef unsigned long DWORD;
+typedef int BOOL;
+typedef unsigned char BYTE;
+typedef unsigned long ULONG_PTR;
+typedef unsigned long *PULONG_PTR;
+typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
+typedef void *PVOID;
+typedef CONST WCHAR *LPCWSTR, *PCWSTR;
+typedef PVOID BCRYPT_ALG_HANDLE;
+typedef long LONG;
+typedef unsigned long ULONG;
+typedef ULONG *PULONG;
+typedef LONG NTSTATUS;
+typedef ULONG_PTR HCRYPTHASH;
+typedef ULONG_PTR HCRYPTPROV;
+typedef ULONG_PTR HCRYPTKEY;
+typedef ULONG_PTR HCRYPTHASH;
+typedef unsigned int ALG_ID;
+
+// dwParam
+#define KP_IV 1 // Initialization vector
+#define KP_SALT 2 // Salt value
+#define KP_PADDING 3 // Padding values
+#define KP_MODE 4 // Mode of the cipher
+#define KP_MODE_BITS 5 // Number of bits to feedback
+#define KP_PERMISSIONS 6 // Key permissions DWORD
+#define KP_ALGID 7 // Key algorithm
+#define KP_BLOCKLEN 8 // Block size of the cipher
+#define KP_KEYLEN 9 // Length of key in bits
+#define KP_SALT_EX 10 // Length of salt in bytes
+#define KP_P 11 // DSS/Diffie-Hellman P value
+#define KP_G 12 // DSS/Diffie-Hellman G value
+#define KP_Q 13 // DSS Q value
+#define KP_X 14 // Diffie-Hellman X value
+#define KP_Y 15 // Y value
+#define KP_RA 16 // Fortezza RA value
+#define KP_RB 17 // Fortezza RB value
+#define KP_INFO 18 // for putting information into an RSA envelope
+#define KP_EFFECTIVE_KEYLEN 19 // setting and getting RC2 effective key length
+#define KP_SCHANNEL_ALG 20 // for setting the Secure Channel algorithms
+#define KP_CLIENT_RANDOM 21 // for setting the Secure Channel client random data
+#define KP_SERVER_RANDOM 22 // for setting the Secure Channel server random data
+#define KP_RP 23
+#define KP_PRECOMP_MD5 24
+#define KP_PRECOMP_SHA 25
+#define KP_CERTIFICATE 26 // for setting Secure Channel certificate data (PCT1)
+#define KP_CLEAR_KEY 27 // for setting Secure Channel clear key data (PCT1)
+#define KP_PUB_EX_LEN 28
+#define KP_PUB_EX_VAL 29
+#define KP_KEYVAL 30
+#define KP_ADMIN_PIN 31
+#define KP_KEYEXCHANGE_PIN 32
+#define KP_SIGNATURE_PIN 33
+#define KP_PREHASH 34
+#define KP_ROUNDS 35
+#define KP_OAEP_PARAMS 36 // for setting OAEP params on RSA keys
+#define KP_CMS_KEY_INFO 37
+#define KP_CMS_DH_KEY_INFO 38
+#define KP_PUB_PARAMS 39 // for setting public parameters
+#define KP_VERIFY_PARAMS 40 // for verifying DSA and DH parameters
+#define KP_HIGHEST_VERSION 41 // for TLS protocol version setting
+#define KP_GET_USE_COUNT 42 // for use with PP_CRYPT_COUNT_KEY_USE contexts
+#define KP_PIN_ID 43
+#define KP_PIN_INFO 44
+
+// KP_PADDING
+#define PKCS5_PADDING 1 // PKCS 5 (sec 6.2) padding method
+#define RANDOM_PADDING 2
+#define ZERO_PADDING 3
+
+// KP_MODE
+#define CRYPT_MODE_CBC 1 // Cipher block chaining
+#define CRYPT_MODE_ECB 2 // Electronic code book
+#define CRYPT_MODE_OFB 3 // Output feedback mode
+#define CRYPT_MODE_CFB 4 // Cipher feedback mode
+#define CRYPT_MODE_CTS 5 // Ciphertext stealing mode
+
+BOOL
+CryptSetKeyParam(
+ HCRYPTKEY hKey,
+ DWORD dwParam,
+ CONST BYTE *pbData,
+ DWORD dwFlags
+);
+
+BOOL
+SomeOtherFunction(
+ HCRYPTKEY hKey,
+ DWORD dwParam,
+ CONST BYTE *pbData,
+ DWORD dwFlags
+);
+void
+DummyFunction(
+ DWORD dwParam,
+ ALG_ID dwData)
+{
+ CryptSetKeyParam(0, dwParam, (BYTE*)&dwData, 0);
+}
+
+
+// Macro testing
+#define MACRO_INVOCATION_SETKPMODE(p) { DWORD dwData = p; \
+ CryptSetKeyParam(0, KP_MODE, (BYTE*)&dwData, 0); }
+
+int main()
+{
+ DWORD val = 0;
+ ////////////////////////////
+ // Should fire an event
+ val = CRYPT_MODE_ECB;
+ CryptSetKeyParam(0, KP_MODE, (BYTE*)&val, 0);
+ val = CRYPT_MODE_OFB;
+ CryptSetKeyParam(0, KP_MODE, (BYTE*)&val, 0);
+ val = CRYPT_MODE_CFB;
+ CryptSetKeyParam(0, KP_MODE, (BYTE*)&val, 0);
+ val = CRYPT_MODE_CTS;
+ CryptSetKeyParam(0, KP_MODE, (BYTE*)&val, 0);
+ val = 6;
+ CryptSetKeyParam(0, KP_MODE, (BYTE*)&val, 0);
+ DummyFunction(KP_MODE, CRYPT_MODE_ECB);
+ MACRO_INVOCATION_SETKPMODE(CRYPT_MODE_CTS)
+
+ ////////////////////////////
+ // Should not fire an event
+ val = CRYPT_MODE_CBC;
+ CryptSetKeyParam(0, KP_MODE, (BYTE*)&val, 0);
+ val = CRYPT_MODE_ECB;
+ CryptSetKeyParam(0, KP_PADDING, (BYTE*)&val, 0);
+ SomeOtherFunction(0, KP_MODE, (BYTE*)&val, 0);
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCng/BannedModesCng.expected b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCng/BannedModesCng.expected
new file mode 100644
index 000000000000..f3ba4ff16de3
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCng/BannedModesCng.expected
@@ -0,0 +1,8 @@
+| Test.cpp:57:2:57:18 | call to BCryptSetProperty | Call to 'BCryptSetProperty' function with argument pszProperty = "ChainingMode" is setting up a banned block cipher mode. |
+| Test.cpp:71:2:71:18 | call to BCryptSetProperty | Call to 'BCryptSetProperty' function with argument pszProperty = "ChainingMode" is setting up a banned block cipher mode. |
+| Test.cpp:73:2:73:18 | call to BCryptSetProperty | Call to 'BCryptSetProperty' function with argument pszProperty = "ChainingMode" is setting up a banned block cipher mode. |
+| Test.cpp:75:2:75:18 | call to BCryptSetProperty | Call to 'BCryptSetProperty' function with argument pszProperty = "ChainingMode" is setting up a banned block cipher mode. |
+| Test.cpp:77:2:77:18 | call to BCryptSetProperty | Call to 'BCryptSetProperty' function with argument pszProperty = "ChainingMode" is setting up a banned block cipher mode. |
+| Test.cpp:79:2:79:18 | call to BCryptSetProperty | Call to 'BCryptSetProperty' function with argument pszProperty = "ChainingMode" is setting up a banned block cipher mode. |
+| Test.cpp:81:2:81:18 | call to BCryptSetProperty | Call to 'BCryptSetProperty' function with argument pszProperty = "ChainingMode" is setting up a banned block cipher mode. |
+| Test.cpp:83:2:83:50 | call to BCryptSetProperty | Call to 'BCryptSetProperty' function with argument pszProperty = "ChainingMode" is setting up a banned block cipher mode. |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCng/BannedModesCng.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCng/BannedModesCng.qlref
new file mode 100644
index 000000000000..ed229dfac761
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCng/BannedModesCng.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Cryptography/BannedModesCNG.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCng/Test.cpp b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCng/Test.cpp
new file mode 100644
index 000000000000..8a260c480bd6
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/BannedModesCng/Test.cpp
@@ -0,0 +1,93 @@
+#define CONST const
+
+typedef unsigned long DWORD;
+typedef int BOOL;
+typedef unsigned char BYTE;
+typedef unsigned long ULONG_PTR;
+typedef unsigned long *PULONG_PTR;
+typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
+typedef void *PVOID;
+typedef CONST WCHAR *LPCWSTR, *PCWSTR;
+typedef PVOID BCRYPT_ALG_HANDLE;
+typedef long LONG;
+typedef unsigned long ULONG;
+typedef ULONG *PULONG;
+typedef LONG NTSTATUS;
+typedef ULONG_PTR HCRYPTHASH;
+typedef ULONG_PTR HCRYPTPROV;
+typedef ULONG_PTR HCRYPTKEY;
+typedef ULONG_PTR HCRYPTHASH;
+typedef unsigned int ALG_ID;
+typedef PVOID BCRYPT_HANDLE;
+typedef unsigned char UCHAR;
+typedef UCHAR *PUCHAR;
+
+// Property Strings
+#define BCRYPT_CHAIN_MODE_NA L"ChainingModeN/A"
+#define BCRYPT_CHAIN_MODE_CBC L"ChainingModeCBC"
+#define BCRYPT_CHAIN_MODE_ECB L"ChainingModeECB"
+#define BCRYPT_CHAIN_MODE_CFB L"ChainingModeCFB"
+#define BCRYPT_CHAIN_MODE_CCM L"ChainingModeCCM"
+#define BCRYPT_CHAIN_MODE_GCM L"ChainingModeGCM"
+
+#define BCRYPT_CHAINING_MODE L"ChainingMode"
+#define BCRYPT_PADDING_SCHEMES L"PaddingSchemes"
+
+NTSTATUS
+BCryptSetProperty(
+ BCRYPT_HANDLE hObject,
+ LPCWSTR pszProperty,
+ PUCHAR pbInput,
+ ULONG cbInput,
+ ULONG dwFlags);
+
+NTSTATUS
+AnyFunctionName(
+ BCRYPT_HANDLE hObject,
+ LPCWSTR pszProperty,
+ PUCHAR pbInput,
+ ULONG cbInput,
+ ULONG dwFlags);
+
+void
+DummyFunction(
+ LPCWSTR pszProperty,
+ LPCWSTR pszMode)
+{
+ BCryptSetProperty(0, pszProperty, (PUCHAR)&pszMode, 0, 0);
+}
+
+
+// Macro testing
+#define MACRO_INVOCATION_SETKPMODE(p) { LPCWSTR pszMode = p; \
+ BCryptSetProperty(0, BCRYPT_CHAINING_MODE, (PUCHAR)&pszMode, 0, 0); }
+
+int main()
+{
+ LPCWSTR val = 0;
+ ////////////////////////////
+ // Should fire an event
+ val = BCRYPT_CHAIN_MODE_NA;
+ BCryptSetProperty(0, BCRYPT_CHAINING_MODE, (PUCHAR)&val, 0, 0);
+ val = BCRYPT_CHAIN_MODE_ECB;
+ BCryptSetProperty(0, BCRYPT_CHAINING_MODE, (PUCHAR)&val, 0, 0);
+ val = BCRYPT_CHAIN_MODE_CFB;
+ BCryptSetProperty(0, BCRYPT_CHAINING_MODE, (PUCHAR)&val, 0, 0);
+ val = BCRYPT_CHAIN_MODE_CCM;
+ BCryptSetProperty(0, BCRYPT_CHAINING_MODE, (PUCHAR)&val, 0, 0);
+ val = BCRYPT_CHAIN_MODE_GCM;
+ BCryptSetProperty(0, BCRYPT_CHAINING_MODE, (PUCHAR)&val, 0, 0);
+ val = L"ChainingModeNEW";
+ BCryptSetProperty(0, BCRYPT_CHAINING_MODE, (PUCHAR)&val, 0, 0);
+ DummyFunction(BCRYPT_CHAINING_MODE, BCRYPT_CHAIN_MODE_GCM);
+ MACRO_INVOCATION_SETKPMODE(BCRYPT_CHAIN_MODE_ECB)
+
+ ////////////////////////////
+ // Should not fire an event
+ val = BCRYPT_CHAIN_MODE_CBC;
+ BCryptSetProperty(0, BCRYPT_CHAINING_MODE, (PUCHAR)&val, 0, 0);
+ val = BCRYPT_CHAIN_MODE_ECB;
+ BCryptSetProperty(0, BCRYPT_PADDING_SCHEMES, (PUCHAR)&val, 0, 0);
+ val = BCRYPT_CHAIN_MODE_ECB;
+ AnyFunctionName(0, BCRYPT_CHAINING_MODE, (PUCHAR)&val, 0, 0);
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/HardCodedIVCNG/HardCodedIVCNG.expected b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/HardCodedIVCNG/HardCodedIVCNG.expected
new file mode 100644
index 000000000000..093a13569963
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/HardCodedIVCNG/HardCodedIVCNG.expected
@@ -0,0 +1 @@
+| Test.cpp:56:16:60:2 | {...} | Calling BCryptEncrypt with a hard-coded IV on function |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/HardCodedIVCNG/HardCodedIVCNG.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/HardCodedIVCNG/HardCodedIVCNG.qlref
new file mode 100644
index 000000000000..a04eca59ce5f
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/HardCodedIVCNG/HardCodedIVCNG.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Cryptography/HardcodedIVCNG.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/HardCodedIVCNG/Test.cpp b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/HardCodedIVCNG/Test.cpp
new file mode 100644
index 000000000000..32502efeb032
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/HardCodedIVCNG/Test.cpp
@@ -0,0 +1,75 @@
+#define CONST const
+
+typedef unsigned long DWORD;
+typedef int BOOL;
+typedef unsigned char BYTE;
+typedef unsigned long ULONG_PTR;
+typedef unsigned long *PULONG_PTR;
+typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
+typedef void *PVOID;
+typedef CONST WCHAR *LPCWSTR, *PCWSTR;
+typedef PVOID BCRYPT_ALG_HANDLE;
+typedef PVOID BCRYPT_KEY_HANDLE;
+typedef long LONG;
+typedef unsigned long ULONG;
+typedef ULONG *PULONG;
+typedef LONG NTSTATUS;
+typedef ULONG_PTR HCRYPTHASH;
+typedef ULONG_PTR HCRYPTPROV;
+typedef ULONG_PTR HCRYPTKEY;
+typedef ULONG_PTR HCRYPTHASH;
+typedef unsigned int ALG_ID;
+
+typedef unsigned char UCHAR;
+typedef UCHAR *PUCHAR;
+#define VOID void
+
+NTSTATUS
+BCryptEncrypt(
+ BCRYPT_KEY_HANDLE hKey,
+ PUCHAR pbInput,
+ ULONG cbInput,
+ VOID *pPaddingInfo,
+ PUCHAR pbIV,
+ ULONG cbIV,
+ PUCHAR pbOutput,
+ ULONG cbOutput,
+ ULONG *pcbResult,
+ ULONG dwFlags);
+
+
+static unsigned long int next = 1;
+
+int rand(void) // RAND_MAX assumed to be 32767
+{
+ next = next * 1103515245 + 12345;
+ unsigned int tmp = (next / 65536) % 32768;
+ if (tmp % next)
+ {
+ next = (next / 65526) % tmp;
+ }
+ return next;
+}
+
+int main()
+{
+ BYTE rgbIV[] =
+ {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F
+ };
+
+ BYTE* pIV = new BYTE(16);
+ // rand() is not a good source for IV,
+ // but I am avoiding calling a CSPRGenerator for this test.
+ for (int i = 0; i < 16; i++)
+ {
+ pIV[i] = (BYTE)rand();
+ }
+
+ BCryptEncrypt(0, 0, 0, 0, rgbIV, 16, 0, 0, 0, 0); // Must be flagged
+
+ BCryptEncrypt(0, 0, 0, 0, pIV, 16, 0, 0, 0, 0); // Should not be flagged
+
+ delete[] pIV;
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFBannedHashAlgorithm.expected b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFBannedHashAlgorithm.expected
new file mode 100644
index 000000000000..7f732b4a391e
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFBannedHashAlgorithm.expected
@@ -0,0 +1 @@
+| test.cpp:31:36:31:41 | handle | BCRYPT_ALG_HANDLE is passed to this to KDF derived from insecure hashing function $@. Must use SHA256 or higher. | test.cpp:19:51:19:70 | MD5 | MD5 |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFBannedHashAlgorithm.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFBannedHashAlgorithm.qlref
new file mode 100644
index 000000000000..03460127fa91
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFBannedHashAlgorithm.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Cryptography/WeakKDFBannedHashAlgorithm.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFLowIterationCount.expected b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFLowIterationCount.expected
new file mode 100644
index 000000000000..9ecbbd43c49e
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFLowIterationCount.expected
@@ -0,0 +1 @@
+| test.cpp:31:97:31:100 | 2048 | Iteration count $@ is passed to this to KDF. Use at least 100000 iterations when deriving cryptographic key from password. | test.cpp:31:97:31:100 | 2048 | 2048 |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFLowIterationCount.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFLowIterationCount.qlref
new file mode 100644
index 000000000000..9f2dff690d78
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFLowIterationCount.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Cryptography/WeakKDFLowIterationCount.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallKeyLength.expected b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallKeyLength.expected
new file mode 100644
index 000000000000..2555150a2d95
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallKeyLength.expected
@@ -0,0 +1 @@
+| test.cpp:31:123:31:123 | 8 | Key size $@ is passed to this to KDF. Use at least 16 bytes for key length when deriving cryptographic key from password. | test.cpp:31:123:31:123 | 8 | 8 |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallKeyLength.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallKeyLength.qlref
new file mode 100644
index 000000000000..d0fe39707800
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallKeyLength.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Cryptography/WeakKDFSmallKeyLength.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallSaltSize.expected b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallSaltSize.expected
new file mode 100644
index 000000000000..d68b6d8274a4
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallSaltSize.expected
@@ -0,0 +1 @@
+| test.cpp:31:94:31:94 | 8 | Salt size $@ is passed to this to KDF. Use at least 16 bytes for salt size when deriving cryptographic key from password. | test.cpp:31:94:31:94 | 8 | 8 |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallSaltSize.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallSaltSize.qlref
new file mode 100644
index 000000000000..4f097d2b2abf
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/WeakKDFSmallSaltSize.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Cryptography/WeakKDFSmallSaltSize.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/bcrypt.h b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/bcrypt.h
new file mode 100644
index 000000000000..3e9fc08d7902
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/bcrypt.h
@@ -0,0 +1,69 @@
+#define CONST const
+
+typedef unsigned long DWORD;
+typedef int BOOL;
+typedef unsigned char BYTE;
+typedef unsigned long ULONG_PTR;
+typedef unsigned long *PULONG_PTR;
+typedef wchar_t WCHAR; // wc, 16-bit UNICODE character
+typedef void *PVOID;
+typedef CONST WCHAR *LPCWSTR, *PCWSTR;
+typedef int BCRYPT_ALG_HANDLE; // using int as a placeholder
+typedef long LONG;
+typedef unsigned long ULONG;
+typedef ULONG *PULONG;
+typedef LONG NTSTATUS;
+typedef ULONG_PTR HCRYPTHASH;
+typedef ULONG_PTR HCRYPTPROV;
+typedef ULONG_PTR HCRYPTKEY;
+typedef ULONG_PTR HCRYPTHASH;
+typedef unsigned int ALG_ID;
+typedef unsigned int UINT;
+typedef UINT UCHAR;
+typedef UCHAR *PUCHAR;
+typedef unsigned long long ULONGLONG;
+
+
+#define BCRYPT_MD2_ALGORITHM L"MD2"
+#define BCRYPT_MD4_ALGORITHM L"MD4"
+#define BCRYPT_MD5_ALGORITHM L"MD5"
+#define BCRYPT_SHA1_ALGORITHM L"SHA1"
+#define BCRYPT_SHA256_ALGORITHM L"SHA256"
+#define BCRYPT_SHA384_ALGORITHM L"SHA384"
+#define BCRYPT_SHA512_ALGORITHM L"SHA512"
+
+#define NULL 0
+
+int intgen();
+
+NTSTATUS BCryptOpenAlgorithmProvider(
+ BCRYPT_ALG_HANDLE *phAlgorithm,
+ LPCWSTR pszAlgId,
+ LPCWSTR pszImplementation,
+ ULONG dwFlags)
+{
+ return intgen();
+}
+
+
+NTSTATUS BCryptDeriveKeyPBKDF2(
+ BCRYPT_ALG_HANDLE hPrf,
+ PUCHAR pbPassword,
+ ULONG cbPassword,
+ PUCHAR pbSalt,
+ ULONG cbSalt,
+ ULONGLONG cIterations,
+ PUCHAR pbDerivedKey,
+ ULONG cbDerivedKey,
+ ULONG dwFlags)
+{
+ return intgen();
+}
+
+NTSTATUS BCryptCloseAlgorithmProvider(
+ BCRYPT_ALG_HANDLE hAlgorithm,
+ ULONG dwFlags
+)
+{
+ return intgen();
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/test.cpp b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/test.cpp
new file mode 100644
index 000000000000..85a114f14dc3
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Cryptography/WeakKDF/test.cpp
@@ -0,0 +1,82 @@
+
+#include "./bcrypt.h"
+using namespace std;
+
+char* getString();
+
+char* password = getString();
+char* salt = getString();
+
+int strlen(char *s);
+
+void test_bad1()
+{
+ NTSTATUS Status;
+ BYTE DerivedKey[64];
+
+ BCRYPT_ALG_HANDLE handle;
+ // BAD hash algorithm handle generated here
+ Status = BCryptOpenAlgorithmProvider(&handle, BCRYPT_MD5_ALGORITHM, NULL, 0);
+
+ if (Status != 0)
+ {
+ //std::cout << "BCryptOpenAlgorithmProvider exited with error message " << Status;
+ goto END;
+ }
+
+ // BAD Hash algorithm handle
+ // BAD salt length
+ // BAD iteration count
+ // BAD Key length
+ Status = BCryptDeriveKeyPBKDF2(handle, (PUCHAR)password, strlen(password), (PUCHAR)salt, 8, 2048, (PUCHAR)DerivedKey, 8, 0);
+ //Status = BCryptDeriveKeyPBKDF2(handle, (PUCHAR)password.data(), password.length(), (PUCHAR)salt.data(), 8, 2048, (PUCHAR)DerivedKey, 64, 0);
+
+ if (Status != 0)
+ {
+ //std::cout << "BCryptDeriveKeyPBKDF2 exited with error message " << Status;
+ goto END;
+ }
+
+ //else
+ //std::cout << "Operation completed successfully. Your encrypted key is in variable DerivedKey.";
+
+ BCryptCloseAlgorithmProvider(handle, 0);
+
+END:;
+}
+
+void test_good1()
+{
+ NTSTATUS Status;
+ BYTE DerivedKey[64];
+
+ BCRYPT_ALG_HANDLE handle;
+ // GOOD hash handle generated here
+ Status = BCryptOpenAlgorithmProvider(&handle, BCRYPT_SHA256_ALGORITHM, NULL, 0);
+
+ if (Status != 0)
+ {
+ //std::cout << "BCryptOpenAlgorithmProvider exited with error message " << Status;
+ goto END;
+ }
+
+ // GOOD Hash algorithm handle
+ // GOOD salt length
+ // GOOD iteration count
+ // GOOD Key length
+ Status = BCryptDeriveKeyPBKDF2(handle, (PUCHAR)password, strlen(password), (PUCHAR)salt, 64, 100000, (PUCHAR)DerivedKey, 64, 0);
+ //Status = BCryptDeriveKeyPBKDF2(handle, (PUCHAR)password.data(), password.length(), (PUCHAR)salt.data(), 8, 2048, (PUCHAR)DerivedKey, 64, 0);
+
+ if (Status != 0)
+ {
+ //std::cout << "BCryptDeriveKeyPBKDF2 exited with error message " << Status;
+ goto END;
+ }
+
+ //else
+ //std::cout << "Operation completed successfully. Your encrypted key is in variable DerivedKey.";
+
+ BCryptCloseAlgorithmProvider(handle, 0);
+
+END:;
+}
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.expected b/cpp/ql/test/query-tests/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.expected
new file mode 100644
index 000000000000..267bf9720731
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.expected
@@ -0,0 +1,4 @@
+| UncheckedBoundsEnumAsIndex_test.c:77:27:77:40 | CapabilityType | When accessing array PmiAcpiToCapabilities with index CapabilityType, the upper bound of an enum is used to check the upper bound of the array, but the lower bound is not checked. |
+| UncheckedBoundsEnumAsIndex_test.c:111:31:111:44 | CapabilityType | When accessing array PmiAcpiToCapabilities with index CapabilityType, the upper bound of an enum is used to check the upper bound of the array, but the lower bound is not checked. |
+| UncheckedBoundsEnumAsIndex_test.c:271:31:271:44 | CapabilityType | When accessing array PmiAcpiToCapabilities with index CapabilityType, the upper bound of an enum is used to check the upper bound of the array, but the lower bound is not checked. |
+| UncheckedBoundsEnumAsIndex_test.c:293:31:293:44 | CapabilityType | When accessing array PmiAcpiToCapabilities with index CapabilityType, the upper bound of an enum is used to check the upper bound of the array, but the lower bound is not checked. |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.qlref b/cpp/ql/test/query-tests/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.qlref
new file mode 100644
index 000000000000..ed446417bff7
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.qlref
@@ -0,0 +1 @@
+Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex_test.c b/cpp/ql/test/query-tests/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex_test.c
new file mode 100644
index 000000000000..7d919c14aedd
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/MemoryAccess/EnumIndex/UncheckedBoundsEnumAsIndex_test.c
@@ -0,0 +1,299 @@
+typedef unsigned long ULONG;
+typedef unsigned short USHORT;
+typedef unsigned long DWORD;
+typedef long NTSTATUS;
+#define STATUS_INVALID_PARAMETER ((DWORD )0xC000000DL)
+
+typedef enum {
+ PmiMeasurementConfiguration,
+ PmiBudgetingConfiguration,
+ PmiThresholdConfiguration,
+ PmiConfigurationMax
+} PMI_CONFIGURATION_TYPE;
+
+typedef struct _PMI_CONFIGURATION {
+ ULONG Version;
+ USHORT Size;
+ PMI_CONFIGURATION_TYPE ConfigurationType;
+} PMI_CONFIGURATION, *PPMI_CONFIGURATION;
+
+typedef
+NTSTATUS
+PMI_CONFIGURATION_TO_ACPI(
+ ULONG something
+);
+
+typedef
+NTSTATUS
+PMI_ACPI_TO_CAPABILITIES(
+ ULONG something
+);
+
+typedef PMI_ACPI_TO_CAPABILITIES *PPMI_ACPI_TO_CAPABILITIES;
+
+NTSTATUS
+AcpiPmipBuildReportedCapabilities(
+ ULONG something
+) {
+ return 0;
+}
+
+NTSTATUS
+AcpiPmipBuildMeteredHardwareInformation(
+ ULONG something
+) {
+ return 0;
+}
+
+typedef enum {
+ PmiReportedCapabilities,
+ PmiMeteredHardware,
+ PmiCapabilitiesMax
+} PMI_CAPABILITIES_TYPE;
+
+PPMI_ACPI_TO_CAPABILITIES PmiAcpiToCapabilities[PmiCapabilitiesMax] = {
+ AcpiPmipBuildReportedCapabilities, // PmiReportedCapabilities
+ AcpiPmipBuildMeteredHardwareInformation, // PmiMeteredHardware
+};
+
+typedef struct _PMI_CAPABILITIES {
+ ULONG Version;
+ ULONG Size;
+ PMI_CAPABILITIES_TYPE CapabilityType;
+} PMI_CAPABILITIES, *PPMI_CAPABILITIES;
+
+NTSTATUS Test_NoLowerBoundCheckUsageAfterIfBlock(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ int CapabilityType;
+
+ CapabilityType = PmiCapabilitiesInput->CapabilityType;
+ if (CapabilityType >= PmiCapabilitiesMax)
+ {
+ Status = STATUS_INVALID_PARAMETER;
+ goto IoctlGetCapabilitiesExit;
+ }
+ // ...
+ PmiAcpiToCapabilities[CapabilityType](0); // BUG
+
+IoctlGetCapabilitiesExit:
+ return Status;
+}
+
+// If it fires == false positive
+// unsigned type
+NTSTATUS Test_NoLowerBoundCheckUsageAfterIfBlock_FP(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ PMI_CAPABILITIES_TYPE CapabilityType;
+
+ CapabilityType = PmiCapabilitiesInput->CapabilityType;
+ if (CapabilityType >= PmiCapabilitiesMax)
+ {
+ Status = STATUS_INVALID_PARAMETER;
+ goto IoctlGetCapabilitiesExit;
+ }
+ // ...
+ PmiAcpiToCapabilities[CapabilityType](0); // NOT A BUG, CapabilityType is unsigned
+
+IoctlGetCapabilitiesExit:
+ return Status;
+}
+
+NTSTATUS Test_NoLowerBoundCheckUsageWithinIfBlock(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ int CapabilityType;
+
+ CapabilityType = PmiCapabilitiesInput->CapabilityType;
+ if (CapabilityType < PmiCapabilitiesMax)
+ {
+ PmiAcpiToCapabilities[CapabilityType](1); // BUG
+ }
+ else
+ {
+ Status = STATUS_INVALID_PARAMETER;
+ goto IoctlGetCapabilitiesExit;
+ }
+ // ...
+
+IoctlGetCapabilitiesExit:
+ return Status;
+}
+
+// Should not fire an event as this doesn't meet the criteria
+// CapabilityType is unsigned, so it will never be < 0
+// If it fires == false positive
+NTSTATUS Test_NoLowerBoundCheckUsageWithinIfBlock_FP(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ PMI_CAPABILITIES_TYPE CapabilityType;
+
+ CapabilityType = PmiCapabilitiesInput->CapabilityType;
+ if (CapabilityType < PmiCapabilitiesMax)
+ {
+ PmiAcpiToCapabilities[CapabilityType](1); // NOT A BUG, CapabilityType is unsigned
+ }
+ else
+ {
+ Status = STATUS_INVALID_PARAMETER;
+ goto IoctlGetCapabilitiesExit;
+ }
+ // ...
+
+IoctlGetCapabilitiesExit:
+ return Status;
+}
+
+// Should not fire an event as this doesn't meet the criteria
+// If it fires == false positive
+NTSTATUS Test_NotMeetingUpperboundCheckCritieria(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ PMI_CAPABILITIES_TYPE CapabilityType;
+
+ CapabilityType = PmiCapabilitiesInput->CapabilityType;
+ if (CapabilityType == PmiMeteredHardware)
+ {
+ PmiAcpiToCapabilities[CapabilityType](1);
+ }
+ else
+ {
+ Status = STATUS_INVALID_PARAMETER;
+ goto IoctlGetCapabilitiesExit;
+ }
+ // ...
+
+IoctlGetCapabilitiesExit:
+ return Status;
+}
+
+// No bug - Correct Usage
+NTSTATUS Test_CorrectUsage(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ DWORD x = 0;
+ PMI_CAPABILITIES_TYPE CapabilityType;
+
+ CapabilityType = PmiCapabilitiesInput->CapabilityType;
+ if (CapabilityType < 0 || CapabilityType >= PmiCapabilitiesMax)
+ {
+ Status = STATUS_INVALID_PARAMETER;
+ goto IoctlGetCapabilitiesExit;
+ }
+ // ...
+
+ x = 1;
+
+ PmiAcpiToCapabilities[CapabilityType](2);
+
+IoctlGetCapabilitiesExit:
+ return Status;
+}
+
+// No bug - Correct Usage
+NTSTATUS Test_CorrectUsage2(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ DWORD x = 0;
+ int CapabilityType;
+
+ CapabilityType = PmiCapabilitiesInput->CapabilityType;
+ if (CapabilityType < 0 || CapabilityType >= PmiCapabilitiesMax)
+ {
+ Status = STATUS_INVALID_PARAMETER;
+ goto IoctlGetCapabilitiesExit;
+ }
+ // ...
+
+ x = 1;
+
+ PmiAcpiToCapabilities[CapabilityType](2);
+
+IoctlGetCapabilitiesExit:
+ return Status;
+}
+
+// Should not fire as the Guard is not an If statement. The for loop has an implicit lower bound
+// If it fires == false positive
+NTSTATUS Test_GuardIsNotAnIfStatement(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ DWORD x = 0;
+ int CapabilityType;
+
+ for (CapabilityType = PmiReportedCapabilities; CapabilityType <= PmiCapabilitiesMax; CapabilityType++)
+ {
+ PmiAcpiToCapabilities[CapabilityType](2);
+ }
+ // ...
+ return Status;
+}
+
+
+// If it fires == false positive
+NTSTATUS Test_GuardIsAnIfStatementButVariableLowerBound(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ DWORD x = 0;
+ int CapabilityType = 0; //==> Lower bound
+
+ while (1)
+ {
+ if (CapabilityType >= PmiCapabilitiesMax)
+ {
+ break;
+ }
+ // ...
+
+ PmiAcpiToCapabilities[CapabilityType](0); // NOT A BUG - Lower bound == 0
+ // ...
+ CapabilityType++;
+ }
+ // ...
+ return Status;
+}
+
+NTSTATUS Test_GuardIsAnIfStatementButVariableLowerBound_notbound(PPMI_CAPABILITIES PmiCapabilitiesInput, int initialBound)
+{
+ NTSTATUS Status = 0;
+ DWORD x = 0;
+ int CapabilityType = initialBound; //==> Lower bound
+
+ while (1)
+ {
+ if (CapabilityType >= PmiCapabilitiesMax)
+ {
+ break;
+ }
+ // ...
+
+ PmiAcpiToCapabilities[CapabilityType](0); //BUG - Lowerbound is unknown
+ // ...
+ CapabilityType++;
+ }
+ // ...
+ return Status;
+}
+
+NTSTATUS Test_GuardIsAnIfStatementButVariableLowerBound_outofBounds(PPMI_CAPABILITIES PmiCapabilitiesInput)
+{
+ NTSTATUS Status = 0;
+ DWORD x = 0;
+ int CapabilityType = -1; //==> Lower bound
+
+ while (1)
+ {
+ if (CapabilityType >= PmiCapabilitiesMax)
+ {
+ break;
+ }
+ // ...
+
+ PmiAcpiToCapabilities[CapabilityType](0); // BUG - lower bound is < 0
+ // ...
+ CapabilityType++;
+ }
+ // ...
+ return Status;
+}
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/HardCodedSecurityProtocol.expected b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/HardCodedSecurityProtocol.expected
new file mode 100644
index 000000000000..9e2c180bba95
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/HardCodedSecurityProtocol.expected
@@ -0,0 +1,10 @@
+| test.cpp:50:43:50:61 | 1 | Hard-coded use of security protocol SP_PROT_PCT1_SERVER set here $@. | test.cpp:50:43:50:61 | 1 | 1 |
+| test.cpp:51:43:51:61 | 4 | Hard-coded use of security protocol SP_PROT_SSL2_SERVER set here $@. | test.cpp:51:43:51:61 | 4 | 4 |
+| test.cpp:52:43:52:61 | 16 | Hard-coded use of security protocol SP_PROT_SSL3_SERVER set here $@. | test.cpp:52:43:52:61 | 16 | 16 |
+| test.cpp:53:43:53:56 | ... \| ... | Hard-coded use of security protocol SP_PROT_TLS1_1 set here $@. | test.cpp:53:43:53:56 | ... \| ... | ... \| ... |
+| test.cpp:54:44:54:88 | ... \| ... | Hard-coded use of security protocol ... \| ... set here $@. | test.cpp:54:43:54:89 | ... \| ... | ... \| ... |
+| test.cpp:55:43:55:58 | ... \| ... | Hard-coded use of security protocol SP_PROT_SSL3TLS1 set here $@. | test.cpp:55:43:55:58 | ... \| ... | ... \| ... |
+| test.cpp:56:54:56:74 | 256 | Hard-coded use of security protocol SP_PROT_TLS1_1_SERVER set here $@. | test.cpp:56:43:56:98 | ... ? ... : ... | ... ? ... : ... |
+| test.cpp:56:78:56:98 | 512 | Hard-coded use of security protocol SP_PROT_TLS1_1_CLIENT set here $@. | test.cpp:56:43:56:98 | ... ? ... : ... | ... ? ... : ... |
+| test.cpp:58:43:58:56 | ... \| ... | Hard-coded use of security protocol SP_PROT_TLS1_2 set here $@. | test.cpp:58:43:58:56 | ... \| ... | ... \| ... |
+| test.cpp:59:43:59:56 | ... \| ... | Hard-coded use of security protocol SP_PROT_TLS1_3 set here $@. | test.cpp:59:43:59:56 | ... \| ... | ... \| ... |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/HardCodedSecurityProtocol.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/HardCodedSecurityProtocol.qlref
new file mode 100644
index 000000000000..a1a61b133f34
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/HardCodedSecurityProtocol.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Protocols/HardCodedSecurityProtocol.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/UseOfDeprecatedSecurityProtocol.expected b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/UseOfDeprecatedSecurityProtocol.expected
new file mode 100644
index 000000000000..337e2630cb81
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/UseOfDeprecatedSecurityProtocol.expected
@@ -0,0 +1,8 @@
+| test.cpp:50:43:50:61 | 1 | Hard-coded use of deprecated security protocol SP_PROT_PCT1_SERVER set here $@. | test.cpp:50:43:50:61 | 1 | SP_PROT_PCT1_SERVER |
+| test.cpp:51:43:51:61 | 4 | Hard-coded use of deprecated security protocol SP_PROT_SSL2_SERVER set here $@. | test.cpp:51:43:51:61 | 4 | SP_PROT_SSL2_SERVER |
+| test.cpp:52:43:52:61 | 16 | Hard-coded use of deprecated security protocol SP_PROT_SSL3_SERVER set here $@. | test.cpp:52:43:52:61 | 16 | SP_PROT_SSL3_SERVER |
+| test.cpp:53:43:53:56 | ... \| ... | Hard-coded use of deprecated security protocol SP_PROT_TLS1_1 set here $@. | test.cpp:53:43:53:56 | ... \| ... | SP_PROT_TLS1_1 |
+| test.cpp:54:44:54:88 | ... \| ... | Hard-coded use of deprecated security protocol ... \| ... set here $@. | test.cpp:54:44:54:88 | ... \| ... | ... \| ... |
+| test.cpp:55:43:55:58 | ... \| ... | Hard-coded use of deprecated security protocol SP_PROT_SSL3TLS1 set here $@. | test.cpp:55:43:55:58 | ... \| ... | SP_PROT_SSL3TLS1 |
+| test.cpp:56:54:56:74 | 256 | Hard-coded use of deprecated security protocol SP_PROT_TLS1_1_SERVER set here $@. | test.cpp:56:54:56:74 | 256 | SP_PROT_TLS1_1_SERVER |
+| test.cpp:56:78:56:98 | 512 | Hard-coded use of deprecated security protocol SP_PROT_TLS1_1_CLIENT set here $@. | test.cpp:56:78:56:98 | 512 | SP_PROT_TLS1_1_CLIENT |
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/UseOfDeprecatedSecurityProtocol.qlref b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/UseOfDeprecatedSecurityProtocol.qlref
new file mode 100644
index 000000000000..18e939dd1bed
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/UseOfDeprecatedSecurityProtocol.qlref
@@ -0,0 +1 @@
+Microsoft/Security/Protocols/UseOfDeprecatedSecurityProtocol.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/test.cpp b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/test.cpp
new file mode 100644
index 000000000000..d34bc3599180
--- /dev/null
+++ b/cpp/ql/test/query-tests/Microsoft/Security/Protocols/default/test.cpp
@@ -0,0 +1,65 @@
+// semmle-extractor-options: --microsoft
+
+typedef unsigned long DWORD;
+
+typedef struct _SCHANNEL_CRED {
+ // Note: Fields removed before/after to avoid needing to include headers for field types
+ DWORD grbitEnabledProtocols;
+} SCHANNEL_CRED, *PSCHANNEL_CRED;
+
+#define SP_PROT_PCT1_SERVER 0x00000001
+#define SP_PROT_PCT1_CLIENT 0x00000002
+#define SP_PROT_PCT1 (SP_PROT_PCT1_SERVER | SP_PROT_PCT1_CLIENT)
+
+#define SP_PROT_SSL2_SERVER 0x00000004
+#define SP_PROT_SSL2_CLIENT 0x00000008
+#define SP_PROT_SSL2 (SP_PROT_SSL2_SERVER | SP_PROT_SSL2_CLIENT)
+
+#define SP_PROT_SSL3_SERVER 0x00000010
+#define SP_PROT_SSL3_CLIENT 0x00000020
+#define SP_PROT_SSL3 (SP_PROT_SSL3_SERVER | SP_PROT_SSL3_CLIENT)
+
+#define SP_PROT_TLS1_SERVER 0x00000040
+#define SP_PROT_TLS1_CLIENT 0x00000080
+#define SP_PROT_TLS1 (SP_PROT_TLS1_SERVER | SP_PROT_TLS1_CLIENT)
+
+#define SP_PROT_TLS1_0_SERVER SP_PROT_TLS1_SERVER
+#define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT
+#define SP_PROT_TLS1_0 (SP_PROT_TLS1_0_SERVER | \
+ SP_PROT_TLS1_0_CLIENT)
+
+#define SP_PROT_TLS1_1_SERVER 0x00000100
+#define SP_PROT_TLS1_1_CLIENT 0x00000200
+#define SP_PROT_TLS1_1 (SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_1_CLIENT)
+
+#define SP_PROT_SSL3TLS1_CLIENTS (SP_PROT_TLS1_CLIENT | SP_PROT_SSL3_CLIENT)
+#define SP_PROT_SSL3TLS1_SERVERS (SP_PROT_TLS1_SERVER | SP_PROT_SSL3_SERVER)
+#define SP_PROT_SSL3TLS1 (SP_PROT_SSL3 | SP_PROT_TLS1)
+
+#define SP_PROT_TLS1_2_SERVER 0x00000400
+#define SP_PROT_TLS1_2_CLIENT 0x00000800
+#define SP_PROT_TLS1_2 (SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_2_CLIENT)
+
+#define SP_PROT_TLS1_3_SERVER 0x00001000
+#define SP_PROT_TLS1_3_CLIENT 0x00002000
+#define SP_PROT_TLS1_3 (SP_PROT_TLS1_3_SERVER | SP_PROT_TLS1_3_CLIENT)
+
+void testProtocols(bool isServer, DWORD cred) {
+ SCHANNEL_CRED testSChannelCred;
+ // BAD: Deprecated protocols
+ testSChannelCred.grbitEnabledProtocols = SP_PROT_PCT1_SERVER;
+ testSChannelCred.grbitEnabledProtocols = SP_PROT_SSL2_SERVER;
+ testSChannelCred.grbitEnabledProtocols = SP_PROT_SSL3_SERVER;
+ testSChannelCred.grbitEnabledProtocols = SP_PROT_TLS1_1;
+ testSChannelCred.grbitEnabledProtocols = (SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_1_CLIENT);
+ testSChannelCred.grbitEnabledProtocols = SP_PROT_SSL3TLS1;
+ testSChannelCred.grbitEnabledProtocols = isServer ? SP_PROT_TLS1_1_SERVER : SP_PROT_TLS1_1_CLIENT;
+ // BAD: hardcoded, but not deprecated, protocol
+ testSChannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2;
+ testSChannelCred.grbitEnabledProtocols = SP_PROT_TLS1_3;
+ // GOOD: system default protocol
+ testSChannelCred.grbitEnabledProtocols = 0;
+ // UNKNOWN: Do not flag SP_PROT_TLS1_1 here
+ // We do not know anything about cred, so don't flag it
+ testSChannelCred.grbitEnabledProtocols = cred & ~SP_PROT_TLS1_1;
+}
diff --git a/csharp/ql/integration-tests/posix/diag_autobuild_script/build.sh b/csharp/ql/integration-tests/posix/diag_autobuild_script/build.sh
old mode 100755
new mode 100644
diff --git a/csharp/ql/integration-tests/posix/diag_multiple_scripts/build.sh b/csharp/ql/integration-tests/posix/diag_multiple_scripts/build.sh
old mode 100755
new mode 100644
diff --git a/csharp/ql/integration-tests/posix/diag_multiple_scripts/scripts/build.sh b/csharp/ql/integration-tests/posix/diag_multiple_scripts/scripts/build.sh
old mode 100755
new mode 100644
diff --git a/csharp/ql/integration-tests/posix/warn_as_error/build.sh b/csharp/ql/integration-tests/posix/warn_as_error/build.sh
old mode 100755
new mode 100644
diff --git a/csharp/ql/lib/qlpack.yml b/csharp/ql/lib/qlpack.yml
index e80d0a3ebbda..6e3be2c91cb8 100644
--- a/csharp/ql/lib/qlpack.yml
+++ b/csharp/ql/lib/qlpack.yml
@@ -8,6 +8,7 @@ upgrades: upgrades
dependencies:
codeql/controlflow: ${workspace}
codeql/dataflow: ${workspace}
+ codeql/dataflowstack: ${workspace}
codeql/mad: ${workspace}
codeql/ssa: ${workspace}
codeql/threat-models: ${workspace}
diff --git a/csharp/ql/lib/semmle/code/csharp/commons/ComparisonTest.qll b/csharp/ql/lib/semmle/code/csharp/commons/ComparisonTest.qll
index b4641560892b..6a804f54490c 100644
--- a/csharp/ql/lib/semmle/code/csharp/commons/ComparisonTest.qll
+++ b/csharp/ql/lib/semmle/code/csharp/commons/ComparisonTest.qll
@@ -305,6 +305,7 @@ class ComparisonTest extends TComparisonTest {
}
/** Gets an argument of this comparison test. */
+ pragma[nomagic]
Expr getAnArgument() {
result = this.getFirstArgument() or
result = this.getSecondArgument()
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlowStack.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlowStack.qll
new file mode 100644
index 000000000000..0cbca2e92ff5
--- /dev/null
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/DataFlowStack.qll
@@ -0,0 +1,36 @@
+import csharp
+private import codeql.dataflow.DataFlow
+private import semmle.code.csharp.dataflow.internal.DataFlowImplSpecific
+private import codeql.dataflowstack.DataFlowStack as DFS
+private import DFS::DataFlowStackMake as DataFlowStackFactory
+
+private module DataFlowStackInput implements
+ DFS::DataFlowStackSig
+{
+ private module Flow = DataFlow::Global;
+
+ CsharpDataFlow::Node getNode(Flow::PathNode n) { result = n.getNode() }
+
+ predicate isSource(Flow::PathNode n) { n.isSource() }
+
+ Flow::PathNode getASuccessor(Flow::PathNode n) { result = n.getASuccessor() }
+
+ CsharpDataFlow::DataFlowCallable getARuntimeTarget(CsharpDataFlow::DataFlowCall call) {
+ result = call.getARuntimeTarget()
+ }
+
+ CsharpDataFlow::Node getAnArgumentNode(CsharpDataFlow::DataFlowCall call) {
+ result = call.getArgument(_)
+ }
+}
+
+module DataFlowStackMake {
+ import DataFlowStackFactory::FlowStack>
+}
+
+module BiStackAnalysisMake<
+ DataFlowStackFactory::DataFlow::ConfigSig ConfigA,
+ DataFlowStackFactory::DataFlow::ConfigSig ConfigB>
+{
+ import DataFlowStackFactory::BiStackAnalysis, ConfigB, DataFlowStackInput>
+}
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/TaintTrackingStack.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/TaintTrackingStack.qll
new file mode 100644
index 000000000000..e99deb958546
--- /dev/null
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/TaintTrackingStack.qll
@@ -0,0 +1,37 @@
+import csharp
+private import codeql.dataflow.DataFlow
+private import semmle.code.csharp.dataflow.internal.DataFlowImplSpecific
+private import semmle.code.csharp.dataflow.internal.TaintTrackingImplSpecific
+private import codeql.dataflowstack.TaintTrackingStack as TTS
+private import TTS::TaintTrackingStackMake as TaintTrackingStackFactory
+
+private module TaintTrackingStackInput
+ implements TTS::TaintTrackingStackSig
+{
+ private module Flow = TaintTracking::Global;
+
+ CsharpDataFlow::Node getNode(Flow::PathNode n) { result = n.getNode() }
+
+ predicate isSource(Flow::PathNode n) { n.isSource() }
+
+ Flow::PathNode getASuccessor(Flow::PathNode n) { result = n.getASuccessor() }
+
+ CsharpDataFlow::DataFlowCallable getARuntimeTarget(CsharpDataFlow::DataFlowCall call) {
+ result = call.getARuntimeTarget()
+ }
+
+ CsharpDataFlow::Node getAnArgumentNode(CsharpDataFlow::DataFlowCall call) {
+ result = call.getArgument(_)
+ }
+}
+
+module TaintTrackingStackMake {
+ import TaintTrackingStackFactory::FlowStack>
+}
+
+module BiStackAnalysisMake<
+ TaintTrackingStackFactory::DataFlow::ConfigSig ConfigA,
+ TaintTrackingStackFactory::DataFlow::ConfigSig ConfigB>
+{
+ import TaintTrackingStackFactory::BiStackAnalysis, ConfigB, TaintTrackingStackInput>
+}
\ No newline at end of file
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
index ff2bf7092515..9373c46466a0 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
@@ -2585,6 +2585,15 @@ class NodeRegion instanceof ControlFlow::BasicBlock {
string toString() { result = "NodeRegion" }
predicate contains(Node n) { this = n.getControlFlowNode().getBasicBlock() }
+
+ int totalOrder() {
+ this =
+ rank[result](ControlFlow::BasicBlock b, int startline, int startcolumn |
+ b.getLocation().hasLocationInfo(_, startline, startcolumn, _, _)
+ |
+ b order by startline, startcolumn
+ )
+ }
}
/**
diff --git a/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll b/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll
index eecbc35900aa..8547a6cbbc5f 100644
--- a/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll
+++ b/csharp/ql/lib/semmle/code/csharp/exprs/Call.qll
@@ -40,6 +40,7 @@ class Call extends Expr, @call {
Callable getTarget() { none() }
/** Gets the `i`th argument to this call, if any. */
+ pragma[nomagic]
Expr getArgument(int i) { result = this.getChild(i) and i >= 0 }
/**
diff --git a/csharp/ql/lib/semmle/code/csharp/hashcons/HashCons.qll b/csharp/ql/lib/semmle/code/csharp/hashcons/HashCons.qll
new file mode 100644
index 000000000000..a235f49ffd54
--- /dev/null
+++ b/csharp/ql/lib/semmle/code/csharp/hashcons/HashCons.qll
@@ -0,0 +1,743 @@
+import csharp
+
+/**
+ * A hash-cons representation of an expression.
+ *
+ * Note: Here is how you go about adding a hash cons for a new expression:
+ *
+ * Step 1: Add a branch to this IPA type.
+ * Step 2: Add a disjunct to `numberableExpr`.
+ * Step 3: Add a disjunct to `nonUniqueHashCons`.
+ *
+ * Notes on performance:
+ * - Care must be taken not to have `numberableExpr` depend on `THashCons`.
+ * Since `THashCons` already depends on `numberableExpr` this would introduce
+ * unnecessary recursion that would ruin performance.
+ * - This library uses lots of non-linear recursion (i.e., more than one
+ * recursive call in a single disjunct). Care must be taken to ensure good
+ * performance when dealing with non-linear recursion. For example, consider
+ * a snippet such as:
+ * ```ql
+ * predicate foo(BinaryExpr bin) {
+ * interesting(bin) and
+ * foo(bin.getLeft()) and
+ * foo(bin.getRight())
+ * }
+ * ```
+ * to ensure that `foo` is joined optimally it should be rewritten to:
+ *
+ * ```ql
+ * pragma[nomagic]
+ * predicate fooLeft(BinaryExpr bin) {
+ * interesting(bin) and
+ * foo(bin.getLeft())
+ * }
+ *
+ * pragma[nomagic]
+ * predicate fooRight(BinaryExpr bin) {
+ * interesting(bin) and
+ * foo(bin.getRight())
+ * }
+ *
+ * predicate foo(BinaryExpr bin) {
+ * fooLeft(bin) and
+ * fooRight(bin)
+ * }
+ * ```
+ */
+cached
+private newtype THashCons =
+ TVariableAccessHashCons(LocalScopeVariable v) { variableAccessHashCons(_, v) } or
+ TConstantHashCons(Type type, string value) { constantHashCons(_, type, value) } or
+ TFieldAccessHashCons(Field field, THashCons qualifier) {
+ fieldAccessHashCons(_, field, qualifier)
+ } or
+ TPropertyAccessHashCons(Property prop, THashCons qualifier) {
+ propertyAccessHashCons(_, prop, qualifier)
+ } or
+ TBinaryHashCons(string operator, THashCons left, THashCons right) {
+ binaryHashCons(_, operator, left, right)
+ } or
+ TThisHashCons() or
+ TBaseHashCons() or
+ TTypeAccessHashCons(Type t) { typeAccessHashCons(_, t) } or
+ TDefaultValueWithoutTypeHashCons() or
+ TDefaultValueWithTypeHashCons(THashCons typeAccess) {
+ defaultValueWithTypeHashCons(_, typeAccess)
+ } or
+ TIndexerAccessHashCons(Indexer i) { indexerAccessHashCons(_, i) } or
+ TEventAccessHashCons(Event ev) { eventAccessHashCons(_, ev) } or
+ TDynamicMemberAccessHashCons(DynamicMember dm) { dynamicMemberAccessHashCons(_, dm) } or
+ TTypeOfHashCons(THashCons typeAccess) { typeOfHashCons(_, typeAccess) } or
+ TUnaryHashCons(string operator, THashCons operand) { unaryHashCons(_, operator, operand) } or
+ TConditionalHashCons(THashCons cond, THashCons then_, THashCons else_) {
+ conditionalHashCons(_, cond, then_, else_)
+ } or
+ TMethodCallHashCons(string name, CallHashCons::TListPartialHashCons args) {
+ methodCallHashCons(_, name, args)
+ } or
+ TConstructorInitializerCallHashCons(string name, CallHashCons::TListPartialHashCons args) {
+ constructorInitializerCallHashCons(_, name, args)
+ } or
+ TOperatorCallHashCons(string name, CallHashCons::TListPartialHashCons args) {
+ operatorCallHashCons(_, name, args)
+ } or
+ TDelegateLikeCallHashCons(THashCons expr, CallHashCons::TListPartialHashCons args) {
+ delegateLikeCallHashCons(_, expr, args)
+ } or
+ TObjectCreationHashCons(string name, CallHashCons::TListPartialHashCons args) {
+ objectCreationHashCons(_, name, args)
+ } or
+ TCastHashCons(Type targetType, THashCons expr) { castHashCons(_, targetType, expr) } or
+ TAssignmentHashCons(string operator, THashCons left, THashCons right) {
+ assignmentHashCons(_, operator, left, right)
+ } or
+ TArrayAccessHashCons(THashCons index, THashCons qualifier) {
+ arrayAccessHashCons(_, index, qualifier)
+ } or
+ TArrayInitializerHashCons(ArrayInitializerHashCons::TListPartialHashCons list) {
+ arrayInitializerHashCons(_, list)
+ } or
+ TArrayCreationHashCons(THashCons initializer, ArrayCreationHashCons::TListPartialHashCons lengths) {
+ arrayCreationHashCons(_, initializer, lengths)
+ } or
+ TLocalVariableDeclWithInitializerHashCons(Variable v, THashCons initializer) {
+ localVariableDeclWithInitializerHashCons(_, v, initializer)
+ } or
+ TLocalVariableDeclWithoutInitializerHashCons(Variable v) {
+ localVariableDeclWithoutInitializerHashCons(_, v)
+ } or
+ TDefineSymbolHashCons(string name) { defineSymbolHashCons(_, name) } or
+ TUniqueHashCons(Expr e) { uniqueHashCons(e) }
+
+private predicate variableAccessHashCons(LocalScopeVariableAccess va, LocalScopeVariable v) {
+ numberableExpr(va) and
+ va.getTarget() = v
+}
+
+private predicate constantHashCons(Literal lit, Type t, string value) {
+ numberableExpr(lit) and
+ lit.getType() = t and
+ lit.getValue() = value
+}
+
+private predicate fieldAccessHashCons(FieldAccess fa, Field f, THashCons qualifier) {
+ numberableExpr(fa) and
+ hashCons(fa.getQualifier()) = qualifier and
+ fa.getTarget() = f
+}
+
+private predicate propertyAccessHashCons(PropertyAccess pa, Property prop, THashCons qualifier) {
+ numberableExpr(pa) and
+ hashCons(pa.getQualifier()) = qualifier and
+ pa.getTarget() = prop
+}
+
+pragma[nomagic]
+private predicate binaryHashConsLeft(BinaryOperation binary, THashCons h) {
+ numberableExpr(binary) and
+ hashCons(binary.getLeftOperand()) = h
+}
+
+pragma[nomagic]
+private predicate binaryHashConsRight(BinaryOperation binary, THashCons h) {
+ numberableExpr(binary) and
+ hashCons(binary.getRightOperand()) = h
+}
+
+private predicate binaryHashCons(
+ BinaryOperation binary, string operator, THashCons left, THashCons right
+) {
+ binaryHashConsLeft(binary, left) and
+ binaryHashConsRight(binary, right) and
+ binary.getOperator() = operator
+}
+
+private predicate unaryHashCons(UnaryOperation unary, string operator, THashCons operand) {
+ numberableExpr(unary) and
+ hashCons(unary.getOperand()) = operand and
+ unary.getOperator() = operator
+}
+
+pragma[nomagic]
+private predicate conditionalHashConsCond(ConditionalExpr condExpr, THashCons cond) {
+ numberableExpr(condExpr) and
+ hashCons(condExpr.getCondition()) = cond
+}
+
+pragma[nomagic]
+private predicate conditionalHashConsThen(ConditionalExpr condExpr, THashCons then_) {
+ numberableExpr(condExpr) and
+ hashCons(condExpr.getThen()) = then_
+}
+
+pragma[nomagic]
+private predicate conditionalHashConsElse(ConditionalExpr condExpr, THashCons else_) {
+ numberableExpr(condExpr) and
+ hashCons(condExpr.getElse()) = else_
+}
+
+private predicate conditionalHashCons(
+ ConditionalExpr condExpr, THashCons cond, THashCons then_, THashCons else_
+) {
+ numberableExpr(condExpr) and
+ conditionalHashConsCond(condExpr, cond) and
+ conditionalHashConsThen(condExpr, then_) and
+ conditionalHashConsElse(condExpr, else_)
+}
+
+private predicate typeAccessHashCons(TypeAccess ta, Type t) { ta.getTarget() = t }
+
+private predicate indexerAccessHashCons(IndexerAccess ia, Indexer i) { ia.getTarget() = i }
+
+private predicate eventAccessHashCons(EventAccess ea, Event e) { ea.getTarget() = e }
+
+private predicate dynamicMemberAccessHashCons(DynamicMemberAccess dma, DynamicMember dm) {
+ dma.getTarget() = dm
+}
+
+private predicate thisHashCons(ThisAccess ta) { any() }
+
+private predicate baseHashCons(BaseAccess ba) { any() }
+
+private predicate defaultValueWithTypeHashCons(DefaultValueExpr dve, THashCons typeAccess) {
+ hashCons(dve.getTypeAccess()) = typeAccess
+}
+
+private predicate defaultValueWithoutTypeHashCons(DefaultValueExpr dve) {
+ not exists(dve.getTypeAccess())
+}
+
+private predicate castHashCons(Cast cast, Type targetType, THashCons expr) {
+ // By not using hashCons(cast.getTypeAccess) we avoid unnecessary non-linear recursion
+ targetType = cast.getType() and
+ hashCons(cast.getExpr()) = expr
+}
+
+pragma[nomagic]
+private predicate assignmentHashConsLeft(Assignment a, THashCons left) {
+ numberableAssignment(a) and
+ hashCons(a.getLValue()) = left
+}
+
+pragma[nomagic]
+private predicate assignmentHashConsRight(Assignment a, THashCons right) {
+ numberableAssignment(a) and
+ hashCons(a.getRValue()) = right
+}
+
+private predicate assignmentHashCons(Assignment a, string operator, THashCons left, THashCons right) {
+ a.getOperator() = operator and
+ assignmentHashConsLeft(a, left) and
+ assignmentHashConsRight(a, right)
+}
+
+private predicate typeOfHashCons(TypeofExpr typeOf, THashCons typeAccess) {
+ numberableExpr(typeOf) and
+ hashCons(typeOf.getTypeAccess()) = typeAccess
+}
+
+private predicate arrayAccessHashCons(ArrayAccess aa, THashCons index, THashCons qualifier) {
+ numberableExpr(aa) and
+ // TODO: This is a bit lazy. We should really do something similar to what we do for all arguments
+ index = hashCons(unique( | | aa.getAnIndex())) and
+ qualifier = hashCons(aa.getQualifier())
+}
+
+private signature module ListHashConsInputSig {
+ class List {
+ string toString();
+ }
+
+ Expr getExpr(List l, int i);
+}
+
+private module ListHashCons {
+ private import Input
+
+ int getNumberOfExprs(List list) { result = count(int i | exists(getExpr(list, i)) | i) }
+
+ private predicate listArgsAreNumberable(List list, int remaining) {
+ getNumberOfExprs(list) = remaining
+ or
+ exists(Expr e |
+ listArgsAreNumberable(list, remaining + 1) and
+ e = getExpr(list, remaining) and
+ numberableExpr(e)
+ )
+ }
+
+ final class FinalList = List;
+
+ class NumberableList extends FinalList {
+ NumberableList() { listArgsAreNumberable(this, 0) }
+ }
+
+ pragma[nomagic]
+ predicate listHashCons(NumberableList list, TListPartialHashCons args) {
+ listPartialHashCons(list, getNumberOfExprs(list), args)
+ }
+
+ pragma[nomagic]
+ private predicate listPartialHashCons(NumberableList list, int index, TListPartialHashCons head) {
+ exists(list) and
+ index = 0 and
+ head = TNilArgument()
+ or
+ exists(TListPartialHashCons prev, THashCons prevHashCons |
+ listPartialHashCons(list, index - 1, pragma[only_bind_out](prev)) and
+ listArgHashCons(list, index - 1, pragma[only_bind_into](prevHashCons)) and
+ head = TArgument(prev, prevHashCons)
+ )
+ }
+
+ pragma[nomagic]
+ private predicate listArgHashCons(NumberableList list, int index, THashCons arg) {
+ hashCons(getExpr(list, index)) = arg
+ }
+
+ newtype TListPartialHashCons =
+ TNilArgument() or
+ TArgument(TListPartialHashCons head, THashCons arg) {
+ exists(NumberableList call, int index |
+ listArgHashCons(call, index, arg) and
+ listPartialHashCons(call, index, head)
+ )
+ }
+}
+
+private module CallHashConsInput implements ListHashConsInputSig {
+ class List = Call;
+
+ Expr getExpr(List l, int i) { result = l.getArgument(i) }
+}
+
+private module CallHashCons = ListHashCons;
+
+private predicate methodCallHashCons(
+ MethodCall call, string name, CallHashCons::TListPartialHashCons args
+) {
+ numberableExpr(call) and
+ call.getTarget().getName() = name and
+ CallHashCons::listHashCons(call, args)
+}
+
+private predicate constructorInitializerCallHashCons(
+ ConstructorInitializer call, string name, CallHashCons::TListPartialHashCons args
+) {
+ CallHashCons::listHashCons(call, args) and
+ call.getTarget().getName() = name
+}
+
+private predicate operatorCallHashCons(
+ OperatorCall call, string name, CallHashCons::TListPartialHashCons args
+) {
+ CallHashCons::listHashCons(call, args) and
+ call.getTarget().getName() = name
+}
+
+private predicate delegateLikeCallHashCons(
+ DelegateLikeCall call, THashCons expr, CallHashCons::TListPartialHashCons args
+) {
+ numberableExpr(call) and
+ CallHashCons::listHashCons(call, args) and
+ hashCons(call.getExpr()) = expr
+}
+
+private predicate objectCreationHashCons(
+ ObjectCreation oc, string name, CallHashCons::TListPartialHashCons args
+) {
+ oc.getTarget().getName() = name and
+ CallHashCons::listHashCons(oc, args)
+}
+
+private module ArrayInitializerHashConsInput implements ListHashConsInputSig {
+ class List extends ArrayInitializer {
+ List() {
+ // For performance reasons we restrict this to "small" array initializers.
+ this.getNumberOfElements() < 256
+ }
+ }
+
+ Expr getExpr(List l, int i) { result = l.getElement(i) }
+}
+
+private module ArrayInitializerHashCons = ListHashCons;
+
+private module ArrayCreationHashConsInput implements ListHashConsInputSig {
+ class List = ArrayCreation;
+
+ Expr getExpr(List l, int i) { result = l.getLengthArgument(i) }
+}
+
+private module ArrayCreationHashCons = ListHashCons;
+
+private predicate arrayCreationHashCons(
+ ArrayCreation ac, THashCons initializer, ArrayCreationHashCons::TListPartialHashCons lengths
+) {
+ tHashCons(ac.getInitializer()) = initializer and
+ ArrayCreationHashCons::listHashCons(ac, lengths)
+}
+
+private predicate arrayInitializerHashCons(
+ ArrayInitializer ai, ArrayInitializerHashCons::TListPartialHashCons list
+) {
+ ArrayInitializerHashCons::listHashCons(ai, list)
+}
+
+private predicate localVariableDeclWithInitializerHashCons(
+ LocalVariableDeclExpr lvd, LocalVariable v, THashCons initializer
+) {
+ lvd.getVariable() = v and
+ tHashCons(lvd.getInitializer()) = initializer
+}
+
+private predicate localVariableDeclWithoutInitializerHashCons(
+ LocalVariableDeclExpr lvd, LocalVariable v
+) {
+ lvd.getVariable() = v and
+ not exists(lvd.getInitializer())
+}
+
+private predicate defineSymbolHashCons(DefineSymbolExpr dse, string name) { dse.getName() = name }
+
+pragma[nomagic]
+private predicate numberableBinaryLeftExpr(BinaryOperation binary) {
+ numberableExpr(binary.getLeftOperand())
+}
+
+pragma[nomagic]
+private predicate numberableBinaryRightExpr(BinaryOperation binary) {
+ numberableExpr(binary.getRightOperand())
+}
+
+private predicate numberableBinaryExpr(BinaryOperation binary) {
+ numberableBinaryLeftExpr(binary) and
+ numberableBinaryRightExpr(binary)
+}
+
+pragma[nomagic]
+private predicate numberableDelegateLikeCallExpr(DelegateLikeCall dc) {
+ numberableExpr(dc.getExpr())
+}
+
+private predicate numberableCall(Call c) {
+ c instanceof CallHashCons::NumberableList and
+ (
+ c instanceof MethodCall
+ or
+ c instanceof ConstructorInitializer
+ or
+ c instanceof OperatorCall
+ or
+ numberableDelegateLikeCallExpr(c)
+ or
+ c instanceof ObjectCreation
+ )
+}
+
+pragma[nomagic]
+private predicate numberableConditionalCond(ConditionalExpr cond) {
+ numberableExpr(cond.getCondition())
+}
+
+pragma[nomagic]
+private predicate numberableConditionalThen(ConditionalExpr cond) { numberableExpr(cond.getThen()) }
+
+pragma[nomagic]
+private predicate numberableConditionalElse(ConditionalExpr cond) { numberableExpr(cond.getElse()) }
+
+private predicate numberableConditional(ConditionalExpr cond) {
+ numberableConditionalCond(cond) and
+ numberableConditionalThen(cond) and
+ numberableConditionalElse(cond)
+}
+
+private predicate numberableDefaultValue(DefaultValueExpr dve) {
+ not exists(dve.getTypeAccess())
+ or
+ numberableExpr(dve.getTypeAccess())
+}
+
+private predicate numberableCast(Cast cast) { numberableExpr(cast.getExpr()) }
+
+private predicate numberableAssignment(Assignment a) {
+ numberableExpr(a.getLValue()) and
+ numberableExpr(a.getRValue())
+}
+
+private predicate numberableTypeOfAccess(TypeofExpr typeOf) {
+ numberableExpr(typeOf.getTypeAccess())
+}
+
+private predicate numberableArrayAccess(ArrayAccess aa) {
+ numberableExpr(aa.getQualifier()) and
+ count(aa.getAnIndex()) = 1
+}
+
+private predicate numberableArrayInitializer(ArrayInitializer init) {
+ init.getNumberOfElements() < 256 and
+ init instanceof ArrayInitializerHashCons::NumberableList
+}
+
+private predicate numberableArrayCreation(ArrayCreation ac) {
+ numberableExpr(ac.getInitializer()) and
+ ac instanceof ArrayCreationHashCons::NumberableList
+}
+
+private predicate numberableLocalVariableDecl(LocalVariableDeclExpr lvd) {
+ not exists(lvd.getInitializer())
+ or
+ numberableExpr(lvd.getInitializer())
+}
+
+/**
+ * Holds if `e` can be assigned a non-unique hashcons.
+ *
+ * Note: This predicate _must not_ depend on `THashCons`.
+ */
+private predicate numberableExpr(Expr e) {
+ e instanceof LocalScopeVariableAccess
+ or
+ e instanceof FieldAccess
+ or
+ e instanceof Literal
+ or
+ e instanceof TypeAccess
+ or
+ e instanceof IndexerAccess
+ or
+ e instanceof EventAccess
+ or
+ e instanceof DynamicMemberAccess
+ or
+ e instanceof DefineSymbolExpr
+ or
+ numberableExpr(e.(FieldAccess).getQualifier())
+ or
+ numberableExpr(e.(PropertyAccess).getQualifier())
+ or
+ numberableBinaryExpr(e)
+ or
+ numberableExpr(e.(UnaryOperation).getOperand())
+ or
+ numberableCall(e)
+ or
+ numberableConditional(e)
+ or
+ e instanceof ThisAccess
+ or
+ e instanceof BaseAccess
+ or
+ numberableDefaultValue(e)
+ or
+ numberableCast(e)
+ or
+ numberableAssignment(e)
+ or
+ numberableTypeOfAccess(e)
+ or
+ numberableArrayAccess(e)
+ or
+ numberableArrayInitializer(e)
+ or
+ numberableArrayCreation(e)
+ or
+ numberableLocalVariableDecl(e)
+}
+
+/**
+ * Gets the non-unique hashcons for `e`, if any.
+ */
+private THashCons nonUniqueHashCons(Expr e) {
+ exists(LocalScopeVariable v |
+ variableAccessHashCons(e, v) and
+ result = TVariableAccessHashCons(v)
+ )
+ or
+ exists(Type type, string value |
+ constantHashCons(e, type, value) and
+ result = TConstantHashCons(type, value)
+ )
+ or
+ exists(Field field, THashCons qualifier |
+ fieldAccessHashCons(e, field, qualifier) and
+ result = TFieldAccessHashCons(field, qualifier)
+ )
+ or
+ exists(Property prop, THashCons qualifier |
+ propertyAccessHashCons(e, prop, qualifier) and
+ result = TPropertyAccessHashCons(prop, qualifier)
+ )
+ or
+ exists(string operator, THashCons left, THashCons right |
+ binaryHashCons(e, operator, left, right) and
+ result = TBinaryHashCons(operator, left, right)
+ )
+ or
+ exists(string operator, THashCons operand |
+ unaryHashCons(e, operator, operand) and
+ result = TUnaryHashCons(operator, operand)
+ )
+ or
+ exists(THashCons cond, THashCons then_, THashCons else_ |
+ conditionalHashCons(e, cond, then_, else_) and
+ result = TConditionalHashCons(cond, then_, else_)
+ )
+ or
+ exists(Type t |
+ typeAccessHashCons(e, t) and
+ result = TTypeAccessHashCons(t)
+ )
+ or
+ exists(Indexer i |
+ indexerAccessHashCons(e, i) and
+ result = TIndexerAccessHashCons(i)
+ )
+ or
+ exists(Event ev |
+ eventAccessHashCons(e, ev) and
+ result = TEventAccessHashCons(ev)
+ )
+ or
+ exists(DynamicMember dm |
+ dynamicMemberAccessHashCons(e, dm) and
+ result = TDynamicMemberAccessHashCons(dm)
+ )
+ or
+ exists(string name, CallHashCons::TListPartialHashCons args |
+ methodCallHashCons(e, name, args) and
+ result = TMethodCallHashCons(name, args)
+ )
+ or
+ exists(string name, CallHashCons::TListPartialHashCons args |
+ constructorInitializerCallHashCons(e, name, args) and
+ result = TConstructorInitializerCallHashCons(name, args)
+ )
+ or
+ exists(string name, CallHashCons::TListPartialHashCons args |
+ operatorCallHashCons(e, name, args) and
+ result = TOperatorCallHashCons(name, args)
+ )
+ or
+ exists(THashCons expr, CallHashCons::TListPartialHashCons args |
+ delegateLikeCallHashCons(e, expr, args) and
+ result = TDelegateLikeCallHashCons(expr, args)
+ )
+ or
+ exists(string name, CallHashCons::TListPartialHashCons args |
+ objectCreationHashCons(e, name, args) and
+ result = TObjectCreationHashCons(name, args)
+ )
+ or
+ thisHashCons(e) and
+ result = TThisHashCons()
+ or
+ baseHashCons(e) and
+ result = TBaseHashCons()
+ or
+ defaultValueWithoutTypeHashCons(e) and
+ result = TDefaultValueWithoutTypeHashCons()
+ or
+ exists(THashCons typeAccess |
+ defaultValueWithTypeHashCons(e, typeAccess) and
+ result = TDefaultValueWithTypeHashCons(typeAccess)
+ )
+ or
+ exists(THashCons operand, Type targetType |
+ castHashCons(e, targetType, operand) and
+ result = TCastHashCons(targetType, operand)
+ )
+ or
+ exists(string operator, THashCons left, THashCons right |
+ assignmentHashCons(e, operator, left, right) and
+ result = TAssignmentHashCons(operator, left, right)
+ )
+ or
+ exists(THashCons typeAccess |
+ typeOfHashCons(e, typeAccess) and
+ result = TTypeOfHashCons(typeAccess)
+ )
+ or
+ exists(THashCons index, THashCons qualifier |
+ arrayAccessHashCons(e, index, qualifier) and
+ result = TArrayAccessHashCons(index, qualifier)
+ )
+ or
+ exists(ArrayInitializerHashCons::TListPartialHashCons list |
+ arrayInitializerHashCons(e, list) and
+ result = TArrayInitializerHashCons(list)
+ )
+ or
+ exists(THashCons initializer, ArrayCreationHashCons::TListPartialHashCons lengths |
+ arrayCreationHashCons(e, initializer, lengths) and
+ result = TArrayCreationHashCons(initializer, lengths)
+ )
+ or
+ exists(Variable v, THashCons initializer |
+ localVariableDeclWithInitializerHashCons(e, v, initializer) and
+ result = TLocalVariableDeclWithInitializerHashCons(v, initializer)
+ )
+ or
+ exists(Variable v |
+ localVariableDeclWithoutInitializerHashCons(e, v) and
+ result = TLocalVariableDeclWithoutInitializerHashCons(v)
+ )
+ or
+ exists(string name |
+ defineSymbolHashCons(e, name) and
+ result = TDefineSymbolHashCons(name)
+ )
+}
+
+private predicate uniqueHashCons(Expr e) { not numberableExpr(e) }
+
+private THashCons tHashCons(Expr e) {
+ result = nonUniqueHashCons(e)
+ or
+ uniqueHashCons(e) and
+ result = TUniqueHashCons(e)
+}
+
+/**
+ * Gets the hashcons of `e`, if any.
+ *
+ * To check if `e1` has the same structure as `e2`
+ * use `hashCons(e1).getAnExpr() = e2`.
+ */
+cached
+HashCons hashCons(Expr e) { result = tHashCons(e) }
+
+/**
+ * A representation of the "structure" of an expression.
+ */
+class HashCons extends THashCons {
+ Expr getAnExpr() { this = hashCons(result) }
+
+ /** Gets the unique representative expression with this hashcons. */
+ private Expr getReprExpr() {
+ result =
+ min(Location loc, Expr e |
+ e = this.getAnExpr() and
+ loc = e.getLocation()
+ |
+ e order by loc.getFile().getAbsolutePath(), loc.getStartLine(), loc.getStartColumn()
+ )
+ }
+
+ /**
+ * Gets the string representation of this hash cons.
+ *
+ * This is the `toString` of an arbitrarily chosen expression with this
+ * hashcons.
+ */
+ string toString() { result = this.getReprExpr().toString() }
+
+ /**
+ * Gets the location of this hashcons.
+ *
+ * This is the location of an arbitrarily chosen expression with this
+ * hashcons.
+ */
+ Location getLocation() { result = this.getReprExpr().getLocation() }
+}
diff --git a/csharp/ql/lib/semmle/code/csharp/security/dataflow/TaintedPathQuery.qll b/csharp/ql/lib/semmle/code/csharp/security/dataflow/TaintedPathQuery.qll
index bdc7245aeb2d..704421c06275 100644
--- a/csharp/ql/lib/semmle/code/csharp/security/dataflow/TaintedPathQuery.qll
+++ b/csharp/ql/lib/semmle/code/csharp/security/dataflow/TaintedPathQuery.qll
@@ -4,6 +4,7 @@
*/
import csharp
+private import codeql.util.Unit
private import semmle.code.csharp.controlflow.Guards
private import semmle.code.csharp.security.dataflow.flowsinks.FlowSinks
private import semmle.code.csharp.security.dataflow.flowsources.FlowSources
@@ -24,23 +25,67 @@ abstract class Sink extends ApiSinkExprNode { }
/**
* A sanitizer for uncontrolled data in path expression vulnerabilities.
*/
-abstract class Sanitizer extends DataFlow::ExprNode { }
+abstract class Sanitizer extends DataFlow::ExprNode {
+ /** Holds if this is a sanitizer when the flow state is `state`. */
+ predicate isBarrier(TaintedPathConfig::FlowState state) { any() }
+}
+
+/** A path normalization step. */
+private class PathNormalizationStep extends Unit {
+ /**
+ * Holds if the flow step from `n1` to `n2` transforms the path into an
+ * absolute path.
+ *
+ * For example, the argument-to-return-value step through a call
+ * to `System.IO.Path.GetFullPath` is a normalization step.
+ */
+ abstract predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2);
+}
+
+private class GetFullPathStep extends PathNormalizationStep {
+ override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
+ exists(Call call |
+ call.getARuntimeTarget().hasFullyQualifiedName("System.IO.Path", "GetFullPath") and
+ n1.asExpr() = call.getArgument(0) and
+ n2.asExpr() = call
+ )
+ }
+}
/**
* A taint-tracking configuration for uncontrolled data in path expression vulnerabilities.
*/
-private module TaintedPathConfig implements DataFlow::ConfigSig {
- predicate isSource(DataFlow::Node source) { source instanceof Source }
+private module TaintedPathConfig implements DataFlow::StateConfigSig {
+ newtype FlowState =
+ additional NotNormalized() or
+ additional Normalized()
+
+ predicate isSource(DataFlow::Node source, FlowState state) {
+ source instanceof Source and state = NotNormalized()
+ }
- predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+ predicate isSink(DataFlow::Node sink, FlowState state) {
+ sink instanceof Sink and
+ exists(state)
+ }
+
+ predicate isAdditionalFlowStep(DataFlow::Node n1, FlowState s1, DataFlow::Node n2, FlowState s2) {
+ any(PathNormalizationStep step).isAdditionalFlowStep(n1, n2) and
+ s1 = NotNormalized() and
+ s2 = Normalized()
+ }
- predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+ predicate isBarrier(DataFlow::Node node, FlowState state) { node.(Sanitizer).isBarrier(state) }
+
+ predicate isBarrierOut(DataFlow::Node node, FlowState state) {
+ isAdditionalFlowStep(node, state, _, _)
+ }
}
/**
* A taint-tracking module for uncontrolled data in path expression vulnerabilities.
*/
-module TaintedPath = TaintTracking::Global;
+module TaintedPath = TaintTracking::GlobalWithState;
/**
* DEPRECATED: Use `ThreatModelSource` instead.
@@ -99,7 +144,7 @@ class StreamWriterTaintedPathSink extends Sink {
}
/**
- * A weak guard that is insufficient to prevent path tampering.
+ * A weak guard that may be insufficient to prevent path tampering.
*/
private class WeakGuard extends Guard {
WeakGuard() {
@@ -118,6 +163,14 @@ private class WeakGuard extends Guard {
or
this.(LogicalOperation).getAnOperand() instanceof WeakGuard
}
+
+ predicate isBarrier(TaintedPathConfig::FlowState state) {
+ state = TaintedPathConfig::Normalized() and
+ exists(Method m | this.(MethodCall).getTarget() = m |
+ m.getName() = "StartsWith" or
+ m.getName() = "EndsWith"
+ )
+ }
}
/**
@@ -126,12 +179,17 @@ private class WeakGuard extends Guard {
* A weak check is one that is insufficient to prevent path tampering.
*/
class PathCheck extends Sanitizer {
+ Guard g;
+
PathCheck() {
- // This expression is structurally replicated in a dominating guard which is not a "weak" check
- exists(Guard g, AbstractValues::BooleanValue v |
- g = this.(GuardedDataFlowNode).getAGuard(_, v) and
- not g instanceof WeakGuard
- )
+ // This expression is structurally replicated in a dominating guard
+ exists(AbstractValues::BooleanValue v | g = this.(GuardedDataFlowNode).getAGuard(_, v))
+ }
+
+ override predicate isBarrier(TaintedPathConfig::FlowState state) {
+ g.(WeakGuard).isBarrier(state)
+ or
+ not g instanceof WeakGuard
}
}
diff --git a/csharp/ql/lib/semmle/code/csharp/security/dataflow/ZipSlipQuery.qll b/csharp/ql/lib/semmle/code/csharp/security/dataflow/ZipSlipQuery.qll
index fad3917553dd..a4ff6a7b7e6a 100644
--- a/csharp/ql/lib/semmle/code/csharp/security/dataflow/ZipSlipQuery.qll
+++ b/csharp/ql/lib/semmle/code/csharp/security/dataflow/ZipSlipQuery.qll
@@ -6,144 +6,460 @@ import csharp
private import semmle.code.csharp.controlflow.Guards
private import semmle.code.csharp.security.dataflow.flowsinks.FlowSinks
+abstract private class AbstractSanitizerMethod extends Method { }
+
+class MethodSystemStringStartsWith extends AbstractSanitizerMethod {
+ MethodSystemStringStartsWith() { this.hasFullyQualifiedName("System.String", "StartsWith") }
+}
+
+abstract private class UnsanitizedPathCombiner extends Expr { }
+
+class PathCombinerViaMethodCall extends UnsanitizedPathCombiner {
+ PathCombinerViaMethodCall() {
+ this.(MethodCall).getTarget().hasFullyQualifiedName("System.IO.Path", "Combine")
+ }
+}
+
+class PathCombinerViaStringInterpolation extends UnsanitizedPathCombiner instanceof InterpolatedStringExpr {}
+
+class PathCombinerViaStringConcatenation extends UnsanitizedPathCombiner instanceof AddExpr {
+ PathCombinerViaStringConcatenation() {
+ this.getAnOperand() instanceof StringLiteral
+ }
+}
+
+class MethodCallGetFullPath extends MethodCall {
+ MethodCallGetFullPath() { this.getTarget().hasFullyQualifiedName("System.IO.Path", "GetFullPath") }
+}
+
/**
- * A data flow source for unsafe zip extraction.
+ * A taint tracking module for GetFullPath to String.StartsWith.
*/
-abstract class Source extends DataFlow::Node { }
+module GetFullPathToQualifierTT =
+ TaintTracking::Global;
+
+private module GetFullPathToQualifierTaintTrackingConfiguration implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node node) {
+ exists(MethodCallGetFullPath mcGetFullPath | node = DataFlow::exprNode(mcGetFullPath))
+ }
+
+ predicate isSink(DataFlow::Node node) {
+ exists(MethodCall mc |
+ mc.getTarget() instanceof MethodSystemStringStartsWith and
+ node.asExpr() = mc.getQualifier()
+ )
+ }
+}
+
+/** An access to the `FullName` property of a `ZipArchiveEntry`. */
+class ArchiveFullNameSource extends Source {
+ ArchiveFullNameSource() {
+ exists(PropertyAccess pa | this.asExpr() = pa |
+ pa.getTarget().getDeclaringType().hasFullyQualifiedName("System.IO.Compression", "ZipArchiveEntry") and
+ pa.getTarget().getName() = "FullName"
+ )
+ }
+}
/**
- * A data flow sink for unsafe zip extraction.
+ * A taint tracking module for String combining to GetFullPath.
*/
-abstract class Sink extends ApiSinkExprNode { }
+module PathCombinerToGetFullPathTT =
+ TaintTracking::Global;
/**
- * A sanitizer for unsafe zip extraction.
+ * PathCombinerToGetFullPathTaintTrackingConfiguration - A Taint Tracking configuration that tracks
+ * a File path combining expression (Such as string concatenation, Path.Combine, or string interpolation),
+ * to a Path.GetFullPath method call's argument.
+ *
+ * We need this because we need to find a safe sequence of operations wherein
+ * - An absolute path is created (uncanonicalized)
+ * - The Path is canonicalized
+ *
+ * If the operations are in the opposite order, the resultant may still contain path traversal characters,
+ * as you cannot fully resolve a relative path. So we must ascertain that they are conducted in this sequence.
*/
-abstract class Sanitizer extends DataFlow::ExprNode { }
+private module PathCombinerToGetFullPathTaintTrackingConfiguration implements DataFlow::ConfigSig {
+ /**
+ * We are looking for the result of some Path combining operation (String concat, Path.Combine, etc.)
+ */
+ predicate isSource(DataFlow::Node node) {
+ exists(UnsanitizedPathCombiner pathCombiner | node = DataFlow::exprNode(pathCombiner))
+ }
+
+ /**
+ * Find the first (and only) argument of Path.GetFullPath, so we make sure that our expression
+ * first goes through some path combining function, and then is canonicalized.
+ */
+ predicate isSink(DataFlow::Node node) {
+ exists(MethodCallGetFullPath mcGetFullPath |
+ node = DataFlow::exprNode(mcGetFullPath.getArgument(0))
+ )
+ }
+}
/**
- * A taint tracking configuration for Zip Slip.
+ * Predicate to check for a safe sequence of events
+ * Path.Combine THEN Path.GetFullPath is applied (with possibly arbitrary mutations)
*/
-private module ZipSlipConfig implements DataFlow::ConfigSig {
- predicate isSource(DataFlow::Node source) { source instanceof Source }
+private predicate safeCombineGetFullPathSequence(MethodCallGetFullPath mcGetFullPath, Expr q) {
+ exists(UnsanitizedPathCombiner source |
+ PathCombinerToGetFullPathTT::flow(DataFlow::exprNode(source),
+ DataFlow::exprNode(mcGetFullPath.getArgument(0)))
+ ) and
+ GetFullPathToQualifierTT::flow(DataFlow::exprNode(mcGetFullPath), DataFlow::exprNode(q))
+}
- predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+/**
+ * The set of /valid/ Guards of RootSanitizerMethodCall.
+ *
+ * IN CONJUNCTION with BOTH
+ * Path.Combine
+ * AND Path.GetFullPath
+ * OR
+ * There is a direct flow from Path.GetFullPath to qualifier of RootSanitizerMethodCall.
+ *
+ * It is not simply enough for the qualifier of String.StartsWith
+ * to pass through Path.Combine without also passing through GetFullPath AFTER.
+ */
+class RootSanitizerMethodCall extends SanitizerMethodCall {
+ RootSanitizerMethodCall() {
+ exists(MethodSystemStringStartsWith sm | this.getTarget() = sm) and
+ exists(Expr q, AbstractValue v |
+ this.getQualifier() = q and
+ v.(AbstractValues::BooleanValue).getValue() = true and
+ exists(MethodCallGetFullPath mcGetFullPath | safeCombineGetFullPathSequence(mcGetFullPath, q))
+ )
+ }
- predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+ override Expr getFilePathArgument() { result = this.getQualifier() }
}
/**
- * A taint tracking module for Zip Slip.
+ * The set of Guards of RootSanitizerMethodCall that are used IN CONJUNCTION with
+ * Path.GetFullPath - it is not simply enough for the qualifier of String.StartsWith
+ * to pass through Path.Combine without also passing through GetFullPath.
*/
-module ZipSlip = TaintTracking::Global;
+class ZipSlipGuard extends Guard {
+ ZipSlipGuard() { this instanceof SanitizerMethodCall }
-/** An access to the `FullName` property of a `ZipArchiveEntry`. */
-class ArchiveFullNameSource extends Source {
- ArchiveFullNameSource() {
- exists(PropertyAccess pa | this.asExpr() = pa |
- pa.getTarget()
- .getDeclaringType()
- .hasFullyQualifiedName("System.IO.Compression", "ZipArchiveEntry") and
- pa.getTarget().getName() = "FullName"
+ Expr getFilePathArgument() { result = this.(SanitizerMethodCall).getFilePathArgument() }
+}
+
+abstract private class SanitizerMethodCall extends MethodCall {
+ SanitizerMethodCall() { this instanceof MethodCall }
+
+ abstract Expr getFilePathArgument();
+}
+
+/**
+ * A taint tracking module for Zip Slip Guard.
+ */
+module SanitizedGuardTT = TaintTracking::Global;
+
+/**
+ * SanitizedGuardTaintTrackingConfiguration - A Taint Tracking configuration class to trace
+ * parameters of a function to calls to RootSanitizerMethodCall (String.StartsWith).
+ *
+ * For example, the following function:
+ * void exampleFn(String somePath){
+ * somePath = Path.GetFullPath(somePath);
+ * ...
+ * if(somePath.startsWith("aaaaa"))
+ * ...
+ * ...
+ * }
+ */
+private module SanitizedGuardTaintTrackingConfiguration implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) {
+ source instanceof DataFlow::ParameterNode and
+ exists(RootSanitizerMethodCall smc |
+ smc.getEnclosingCallable() = source.getEnclosingCallable()
+ )
+ }
+
+ predicate isSink(DataFlow::Node sink) {
+ exists(RootSanitizerMethodCall smc |
+ smc.getAnArgument() = sink.asExpr() or
+ smc.getQualifier() = sink.asExpr()
+ )
+ }
+}
+
+/**
+ * An AbstractWrapperSanitizerMethod is a Method that
+ * is a suitable sanitizer for a ZipSlip path that may not have been canonicalized prior.
+ *
+ * If the return value of this Method correctly validates if a file path is in a valid location,
+ * or is a restricted subset of that validation, then any use of this Method is as valid as the Root
+ * sanitizer (Path.StartsWith).
+ */
+abstract private class AbstractWrapperSanitizerMethod extends AbstractSanitizerMethod {
+ Parameter paramFilename;
+
+ AbstractWrapperSanitizerMethod() {
+ this.getReturnType() instanceof BoolType and
+ this.getAParameter() = paramFilename
+ }
+
+ Parameter paramFilePath() { result = paramFilename }
+}
+
+/* predicate aaaa(ZipSlipGuard g, DataFlow::ParameterNode source){
+ exists(DataFlow::Node sink |
+ sink = DataFlow::exprNode(g.getFilePathArgument()) and
+ SanitizedGuardTT::flow(source, sink) and
+ )
+} */
+
+/**
+ * A DirectWrapperSantizierMethod is a Method where
+ * The function can /only/ returns true when passes through the RootSanitizerGuard
+ *
+ * bool wrapperFn(a,b){
+ * if(guard(a,b))
+ * return true
+ * ....
+ * return false
+ * }
+ *
+ * bool wrapperFn(a,b){
+ * ...
+ * return guard(a,b)
+ * }
+ */
+class DirectWrapperSantizierMethod extends AbstractWrapperSanitizerMethod {
+ /**
+ * To be declared a Wrapper, a function must:
+ * - Be a predicate (return a boolean)
+ * - Accept and use a parameter which represents a File path
+ * - Contain a call to another sanitizer
+ * - And can only return true if the sanitizer also returns true.
+ */
+ DirectWrapperSantizierMethod() {
+ // For every return statement in this Method,
+ forex(ReturnStmt ret | ret.getEnclosingCallable() = this |
+ // The function returns false (Fails the Guard)
+ ret.getExpr().(BoolLiteral).getBoolValue() = false
+ or
+ // It passes the guard, contraining the function argument to the Guard argument.
+ exists(ZipSlipGuard g, DataFlow::ParameterNode source, DataFlow::Node sink |
+ g.getEnclosingCallable() = this and
+ source = DataFlow::parameterNode(paramFilename) and
+ sink = DataFlow::exprNode(g.getFilePathArgument()) and
+ SanitizedGuardTT::flow(source, sink) and
+ (
+ exists(AbstractValues::BooleanValue bv |
+ // If there exists a control block that guards against misuse
+ bv.getValue() = true and
+ g.controlsNode(ret.getAControlFlowNode(), bv)
+ )
+ or
+ // Or if the function returns the resultant of the guard call
+ DataFlow::localFlow(DataFlow::exprNode(g), DataFlow::exprNode(ret.getExpr()))
+ )
+ )
)
}
}
-/** An argument to the `ExtractToFile` extension method. */
-class ExtractToFileArgSink extends Sink {
- ExtractToFileArgSink() {
+/**
+ * An IndirectOverloadedWrapperSanitizerMethod is a Method in which simply wraps /another/ wrapper.class
+ *
+ * Usually this will look like the following stanza:
+ * boolean someWrapper(string s){
+ * return someWrapper(s, true);
+ * }
+ */
+class IndirectOverloadedWrapperSantizierMethod extends AbstractWrapperSanitizerMethod {
+ /**
+ * To be declared a Wrapper, a function must:
+ * - Be a predicate (return a boolean)
+ * - Accept and use a parameter which represents a File path (via delegation)
+ * - Contain a call to another sanitizer (via delegation)
+ * - And can only return true if the delegate sanitizer also returns true.
+ */
+ IndirectOverloadedWrapperSantizierMethod() {
+ // For every return statement in our Method,
+ forex(ReturnStmt ret | ret.getEnclosingCallable() = this |
+ // The Return statement returns false OR
+ ret.getExpr().(BoolLiteral).getBoolValue() = false
+ or
+ // The Method returns the result of calling another known-good sanitizer, connecting
+ // the parameters of this function to the sanitizer MethodCall.
+ exists(ZipSlipGuard g |
+ // If the parameter flows directly to SanitizerMethodCall, and the resultant is returned
+ DataFlow::localFlow(DataFlow::parameterNode(paramFilename),
+ DataFlow::exprNode(g.getFilePathArgument())) and
+ DataFlow::localFlow(DataFlow::exprNode(g), DataFlow::exprNode(ret.getExpr()))
+ )
+ )
+ }
+}
+
+/**
+ * A Wrapped Sanitizer Method call (some function that is equally or more restrictive than our root sanitizer)
+ *
+ * bool wrapperMethod(string path){
+ * return realSanitizer(path);
+ * }
+ */
+class WrapperSanitizerMethodCall extends SanitizerMethodCall {
+ AbstractWrapperSanitizerMethod wrapperMethod;
+
+ WrapperSanitizerMethodCall() {
+ exists(AbstractWrapperSanitizerMethod sm |
+ this.getTarget() = sm and
+ wrapperMethod = sm
+ )
+ }
+
+ pragma[nomagic]
+ private predicate paramFilePathIndex(int index) {
+ index = wrapperMethod.paramFilePath().getIndex()
+ }
+
+
+ override Expr getFilePathArgument() {
+ exists(int index |
+ this.paramFilePathIndex(index) and
+ result = this.getArgument(index)
+ )
+ }
+}
+
+private predicate wrapperCheckGuard(Guard g, Expr e, AbstractValue v) {
+ // A given wrapper method call, with the filePathArgument as a sink, that returns 'true'
+ g instanceof WrapperSanitizerMethodCall and
+ g.(WrapperSanitizerMethodCall).getFilePathArgument() = e and
+ v.(AbstractValues::BooleanValue).getValue() = true
+}
+
+/**
+ * A data flow sink for unsafe zip extraction.
+ */
+abstract class Sink extends ApiSinkExprNode { }
+
+/**
+ * A sanitizer for unsafe zip extraction.
+ */
+abstract private class Sanitizer extends DataFlow::ExprNode { }
+
+class WrapperCheckSanitizer extends Sanitizer {
+ // A Wrapped RootSanitizer that is an explicit subset of RootSanitizer
+ WrapperCheckSanitizer() { this = DataFlow::BarrierGuard::getABarrierNode() }
+}
+
+/**
+ * A data flow source for unsafe zip extraction.
+ */
+abstract private class Source extends DataFlow::Node { }
+
+/**
+ * Access to the `FullName` property of the archive item
+ */
+class ArchiveEntryFullName extends Source {
+ ArchiveEntryFullName() {
+ exists(PropertyAccess pa |
+ pa.getTarget().hasFullyQualifiedName("System.IO.Compression.ZipArchiveEntry", "FullName") and
+ this = DataFlow::exprNode(pa)
+ )
+ }
+}
+
+/**
+ * Argument to extract to file extension method
+ */
+class SinkCompressionExtractToFileArgument extends Sink {
+ SinkCompressionExtractToFileArgument() {
exists(MethodCall mc |
- mc.getTarget()
- .hasFullyQualifiedName("System.IO.Compression", "ZipFileExtensions", "ExtractToFile") and
+ mc.getTarget().hasFullyQualifiedName("System.IO.Compression.ZipFileExtensions", "ExtractToFile") and
this.asExpr() = mc.getArgumentForName("destinationFileName")
)
}
}
-/** A path argument to a `File.Open`, `File.OpenWrite`, or `File.Create` method call. */
-class FileOpenArgSink extends Sink {
- FileOpenArgSink() {
+/**
+ * File Stream created from tainted file name through File.Open/File.Create
+ */
+class SinkFileOpenArgument extends Sink {
+ SinkFileOpenArgument() {
exists(MethodCall mc |
- mc.getTarget().hasFullyQualifiedName("System.IO", "File", "Open") or
- mc.getTarget().hasFullyQualifiedName("System.IO", "File", "OpenWrite") or
- mc.getTarget().hasFullyQualifiedName("System.IO", "File", "Create")
- |
+ mc.getTarget().hasFullyQualifiedName("System.IO.File", ["Open", "OpenWrite", "Create"]) and
this.asExpr() = mc.getArgumentForName("path")
)
}
}
-/** A path argument to a call to the `FileStream` constructor. */
-class FileStreamArgSink extends Sink {
- FileStreamArgSink() {
+/**
+ * File Stream created from tainted file name passed directly to the constructor
+ */
+class SinkStreamConstructorArgument extends Sink {
+ SinkStreamConstructorArgument() {
exists(ObjectCreation oc |
- oc.getTarget().getDeclaringType().hasFullyQualifiedName("System.IO", "FileStream")
- |
+ oc.getTarget().getDeclaringType().hasFullyQualifiedName("System.IO", "FileStream") and
this.asExpr() = oc.getArgumentForName("path")
)
}
}
/**
- * A path argument to a call to the `FileStream` constructor.
- *
- * This constructor can accept a tainted file name and subsequently be used to open a file stream.
+ * Constructor to FileInfo can take tainted file name and subsequently be used to open file stream
*/
-class FileInfoArgSink extends Sink {
- FileInfoArgSink() {
+class SinkFileInfoConstructorArgument extends Sink {
+ SinkFileInfoConstructorArgument() {
exists(ObjectCreation oc |
- oc.getTarget().getDeclaringType().hasFullyQualifiedName("System.IO", "FileInfo")
- |
+ oc.getTarget().getDeclaringType().hasFullyQualifiedName("System.IO", "FileInfo") and
this.asExpr() = oc.getArgumentForName("fileName")
)
}
}
/**
- * A call to `GetFileName`.
- *
- * This is considered a sanitizer because it extracts just the file name, not the full path.
+ * Extracting just file name from a ZipEntry, not the full path
*/
-class GetFileNameSanitizer extends Sanitizer {
- GetFileNameSanitizer() {
+class FileNameExtrationSanitizer extends Sanitizer {
+ FileNameExtrationSanitizer() {
exists(MethodCall mc |
- mc.getTarget().hasFullyQualifiedName("System.IO", "Path", "GetFileName")
- |
- this.asExpr() = mc
+ mc.getTarget().hasFullyQualifiedName("System.IO.Path", "GetFileName") and
+ this = DataFlow::exprNode(mc.getAnArgument())
)
}
}
/**
- * A call to `Substring`.
- *
- * This is considered a sanitizer because `Substring` may be used to extract a single component
- * of a path to avoid ZipSlip.
+ * Checks the string for relative path,
+ * or checks the destination folder for whitelisted/target path, etc
*/
-class SubstringSanitizer extends Sanitizer {
- SubstringSanitizer() {
- exists(MethodCall mc | mc.getTarget().hasFullyQualifiedName("System", "String", "Substring") |
- this.asExpr() = mc
+class StringCheckSanitizer extends Sanitizer {
+ StringCheckSanitizer() {
+ exists(MethodCall mc |
+ (
+ mc instanceof RootSanitizerMethodCall or
+ mc.getTarget().hasFullyQualifiedName("System.String", "Substring")
+ ) and
+ this = DataFlow::exprNode(mc.getQualifier())
)
}
}
-private predicate stringCheckGuard(Guard g, Expr e, AbstractValue v) {
- g.(MethodCall).getTarget().hasFullyQualifiedName("System", "String", "StartsWith") and
- g.(MethodCall).getQualifier() = e and
- // A StartsWith check against Path.Combine is not sufficient, because the ".." elements have
- // not yet been resolved.
- not exists(MethodCall combineCall |
- combineCall.getTarget().hasFullyQualifiedName("System.IO", "Path", "Combine") and
- DataFlow::localExprFlow(combineCall, e)
- ) and
- v.(AbstractValues::BooleanValue).getValue() = true
+/**
+ * A taint tracking configuration for Zip Slip.
+ */
+private module ZipSlipConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer }
+
+ predicate isAdditionalFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
+ // If the sink is a method call, and the source is an argument to that method call
+ exists(MethodCall mc | succ.asExpr() = mc and pred.asExpr() = mc.getAnArgument())
+ }
}
/**
- * A call to `String.StartsWith()` that indicates that the tainted path value is being
- * validated to ensure that it occurs within a permitted output path.
+ * A taint tracking module for Zip Slip.
*/
-class StringCheckSanitizer extends Sanitizer {
- StringCheckSanitizer() { this = DataFlow::BarrierGuard::getABarrierNode() }
-}
+module ZipSlip = TaintTracking::Global;
\ No newline at end of file
diff --git a/csharp/ql/src/Security Features/CWE-022/ZipSlip.qhelp b/csharp/ql/src/Security Features/CWE-022/ZipSlip.qhelp
index d75ababa6a8b..fc1ebe1e4670 100644
--- a/csharp/ql/src/Security Features/CWE-022/ZipSlip.qhelp
+++ b/csharp/ql/src/Security Features/CWE-022/ZipSlip.qhelp
@@ -26,7 +26,7 @@ written to c:\sneaky-file
.
Ensure that output paths constructed from zip archive entries are validated to prevent writing
files to unexpected locations.
-The recommended way of writing an output file from a zip archive entry is to:
+The recommended way of writing an output file from a zip archive entry is to conduct the following in sequence:
- Use
Path.Combine(destinationDirectory, archiveEntry.FullName)
to determine the raw
diff --git a/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnection.ql b/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnection.ql
index adde1b948e4f..e5fc7e720925 100644
--- a/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnection.ql
+++ b/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnection.ql
@@ -12,34 +12,57 @@
import csharp
import InsecureSqlConnection::PathGraph
+import InsecureSQLConnection
-/**
- * A data flow configuration for tracking strings passed to `SqlConnection[StringBuilder]` instances.
- */
-module InsecureSqlConnectionConfig implements DataFlow::ConfigSig {
- predicate isSource(DataFlow::Node source) {
- exists(string s | s = source.asExpr().(StringLiteral).getValue().toLowerCase() |
- s.matches("%encrypt=false%")
- or
- not s.matches("%encrypt=%")
+class Source extends DataFlow::Node {
+ string sourcestring;
+
+ Source() {
+ sourcestring = this.asExpr().(StringLiteral).getValue().toLowerCase() and
+ (
+ not sourcestring.matches("%encrypt=%") or
+ sourcestring.matches("%encrypt=false%")
)
}
- predicate isSink(DataFlow::Node sink) {
+ predicate setsEncryptFalse() { sourcestring.matches("%encrypt=false%") }
+}
+
+class Sink extends DataFlow::Node {
+ Version version;
+
+ Sink() {
exists(ObjectCreation oc |
- oc.getRuntimeArgument(0) = sink.asExpr() and
+ oc.getRuntimeArgument(0) = this.asExpr() and
(
oc.getType().getName() = "SqlConnectionStringBuilder"
or
oc.getType().getName() = "SqlConnection"
) and
- not exists(MemberInitializer mi |
- mi = oc.getInitializer().(ObjectInitializer).getAMemberInitializer() and
- mi.getLValue().(PropertyAccess).getTarget().getName() = "Encrypt" and
- mi.getRValue().(BoolLiteral).getValue() = "true"
- )
+ version = oc.getType().getALocation().(Assembly).getVersion()
)
}
+
+ predicate isEncryptedByDefault() { version.compareTo("4.0") >= 0 }
+}
+
+predicate isEncryptTrue(Source source, Sink sink) {
+ sink.isEncryptedByDefault() and
+ not source.setsEncryptFalse()
+ or
+ exists(ObjectCreation oc, Expr e | oc.getRuntimeArgument(0) = sink.asExpr() |
+ getInfoForInitializedConnEncryption(oc, e) and
+ e.getValue().toLowerCase() = "true"
+ )
+}
+
+/**
+ * A data flow configuration for tracking strings passed to `SqlConnection[StringBuilder]` instances.
+ */
+module InsecureSqlConnectionConfig implements DataFlow::ConfigSig {
+ predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
}
/**
@@ -48,7 +71,9 @@ module InsecureSqlConnectionConfig implements DataFlow::ConfigSig {
module InsecureSqlConnection = DataFlow::Global;
from InsecureSqlConnection::PathNode source, InsecureSqlConnection::PathNode sink
-where InsecureSqlConnection::flowPath(source, sink)
+where
+ InsecureSqlConnection::flowPath(source, sink) and
+ not isEncryptTrue(source.getNode().(Source), sink.getNode().(Sink))
select sink.getNode(), source, sink,
"$@ flows to this SQL connection and does not specify `Encrypt=True`.", source.getNode(),
"Connection string"
diff --git a/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnection.qll b/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnection.qll
new file mode 100644
index 000000000000..1f30cb955cc5
--- /dev/null
+++ b/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnection.qll
@@ -0,0 +1,11 @@
+import csharp
+
+/**
+ * Holds if `ObjectCreation` has an initializer for a member named `Encrypt`, set to `e`
+ */
+predicate getInfoForInitializedConnEncryption(ObjectCreation oc, Expr e) {
+ exists(MemberInitializer mi | mi.getInitializedMember().hasName("Encrypt") |
+ e = mi.getRValue() and
+ oc.getInitializer().(ObjectInitializer).getAMemberInitializer() = mi
+ )
+}
diff --git a/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnectionInitializer.qhelp b/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnectionInitializer.qhelp
new file mode 100644
index 000000000000..74165ab248b5
--- /dev/null
+++ b/csharp/ql/src/Security Features/CWE-327/InsecureSQLConnectionInitializer.qhelp
@@ -0,0 +1,45 @@
+
+
+