diff --git a/actions/ql/lib/CHANGELOG.md b/actions/ql/lib/CHANGELOG.md index 0adf75f4cc0..e16567daffd 100644 --- a/actions/ql/lib/CHANGELOG.md +++ b/actions/ql/lib/CHANGELOG.md @@ -4,7 +4,9 @@ No user-facing changes. ## 0.4.7 -No user-facing changes. +### New Features + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. ## 0.4.6 diff --git a/actions/ql/lib/change-notes/released/0.4.7.md b/actions/ql/lib/change-notes/released/0.4.7.md index e9bb7a76bcb..90e0acec41c 100644 --- a/actions/ql/lib/change-notes/released/0.4.7.md +++ b/actions/ql/lib/change-notes/released/0.4.7.md @@ -1,3 +1,5 @@ ## 0.4.7 -No user-facing changes. +### New Features + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. diff --git a/actions/ql/src/CHANGELOG.md b/actions/ql/src/CHANGELOG.md index 4faccf28ebd..73cc65ebe21 100644 --- a/actions/ql/src/CHANGELOG.md +++ b/actions/ql/src/CHANGELOG.md @@ -20,6 +20,10 @@ ## 0.5.4 +### New Features + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. + ### Bug Fixes * Alerts produced by the query `actions/missing-workflow-permissions` now include a minimal set of recommended permissions in the alert message, based on well-known actions seen within the workflow file. diff --git a/actions/ql/src/change-notes/released/0.5.4.md b/actions/ql/src/change-notes/released/0.5.4.md index d34090f9955..a6174ef6d1d 100644 --- a/actions/ql/src/change-notes/released/0.5.4.md +++ b/actions/ql/src/change-notes/released/0.5.4.md @@ -1,5 +1,9 @@ ## 0.5.4 +### New Features + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. + ### Bug Fixes * Alerts produced by the query `actions/missing-workflow-permissions` now include a minimal set of recommended permissions in the alert message, based on well-known actions seen within the workflow file. diff --git a/actions/ql/src/codeql-suites/actions-code-quality.qls b/actions/ql/src/codeql-suites/actions-code-quality.qls index 0637a088a01..2074f9378cf 100644 --- a/actions/ql/src/codeql-suites/actions-code-quality.qls +++ b/actions/ql/src/codeql-suites/actions-code-quality.qls @@ -1 +1,3 @@ -[] \ No newline at end of file +- queries: . +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/a.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/a.c new file mode 100644 index 00000000000..f80dfe40160 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/a.c @@ -0,0 +1,2 @@ +#include "a.h" +#define FOUR 4 diff --git a/cpp/ql/test/header-variant-tests/clang-pch/a.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/a.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/a.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/a.h diff --git a/cpp/ql/test/header-variant-tests/clang-pch/b.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/b.c similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/b.c rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/b.c diff --git a/cpp/ql/test/header-variant-tests/clang-pch/b.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/b.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/b.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/b.h diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/c.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/c.c new file mode 100644 index 00000000000..e8c42c9c4b2 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/c.c @@ -0,0 +1,3 @@ +int main() { + return ONE + FOUR; +} diff --git a/cpp/ql/test/header-variant-tests/clang-pch/clang-pch.expected b/cpp/ql/integration-tests/header-variant-tests/clang-pch/clang-pch.expected similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/clang-pch.expected rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/clang-pch.expected diff --git a/cpp/ql/test/header-variant-tests/clang-pch/clang-pch.ql b/cpp/ql/integration-tests/header-variant-tests/clang-pch/clang-pch.ql similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/clang-pch.ql rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/clang-pch.ql diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/d.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/d.c new file mode 100644 index 00000000000..35e2312fd89 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/d.c @@ -0,0 +1 @@ +#import "d.h" diff --git a/cpp/ql/test/header-variant-tests/clang-pch/d.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/d.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/d.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/d.h diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/e.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/e.c new file mode 100644 index 00000000000..571efb6f271 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/e.c @@ -0,0 +1,3 @@ +int main() { + return SEVENTEEN; +} diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/f.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/f.c new file mode 100644 index 00000000000..613824dc783 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/f.c @@ -0,0 +1,5 @@ +#if 1 +#pragma hdrstop +extern int x; +#define SEEN_F +#endif diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/g.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/g.c new file mode 100644 index 00000000000..bf105c29e95 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/g.c @@ -0,0 +1,5 @@ +#ifdef SEEN_F +static int g() { + return 20; +} +#endif diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/h.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/h.c new file mode 100644 index 00000000000..6f183a7070f --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/h.c @@ -0,0 +1,4 @@ +#include "h1.h" +#pragma hdrstop +#include "h2.h" +#define SEEN_H diff --git a/cpp/ql/test/header-variant-tests/clang-pch/h1.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/h1.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/h1.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/h1.h diff --git a/cpp/ql/test/header-variant-tests/clang-pch/h2.h b/cpp/ql/integration-tests/header-variant-tests/clang-pch/h2.h similarity index 100% rename from cpp/ql/test/header-variant-tests/clang-pch/h2.h rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/h2.h diff --git a/cpp/ql/test/header-variant-tests/clang-pch/i.c b/cpp/ql/integration-tests/header-variant-tests/clang-pch/i.c similarity index 64% rename from cpp/ql/test/header-variant-tests/clang-pch/i.c rename to cpp/ql/integration-tests/header-variant-tests/clang-pch/i.c index 05aa74b3047..1274d675000 100644 --- a/cpp/ql/test/header-variant-tests/clang-pch/i.c +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/i.c @@ -13,4 +13,3 @@ static int h2() { return 32; } #endif -// semmle-extractor-options: --clang -include-pch ${testdir}/clang-pch.testproj/h.pch diff --git a/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py b/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py new file mode 100644 index 00000000000..0a48fc3a79c --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/clang-pch/test.py @@ -0,0 +1,17 @@ +import os + + +def test(codeql, cpp): + os.mkdir("pch") + extractor = cpp.get_tool("extractor") + codeql.database.create(command=[ + f'"{extractor}" --mimic-clang -emit-pch -o pch/a.pch a.c', + f'"{extractor}" --mimic-clang -include-pch pch/a.pch -Iextra_dummy_path b.c', + f'"{extractor}" --mimic-clang -include pch/a -Iextra_dummy_path c.c', + f'"{extractor}" --mimic-clang -emit-pch -o pch/d.pch d.c', + f'"{extractor}" --mimic-clang -include-pch pch/d.pch e.c', + f'"{extractor}" --mimic-clang -emit-pch -o pch/f.pch f.c', + f'"{extractor}" --mimic-clang -include-pch pch/f.pch g.c', + f'"{extractor}" --mimic-clang -emit-pch -o pch/h.pch h.c', + f'"{extractor}" --mimic-clang -include-pch pch/h.pch i.c', + ]) diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.c b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.c new file mode 100644 index 00000000000..2243de1baf9 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.c @@ -0,0 +1 @@ +#include "a.h" diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/a.h b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.h similarity index 100% rename from cpp/ql/test/header-variant-tests/microsoft-pch/a.h rename to cpp/ql/integration-tests/header-variant-tests/microsoft-pch/a.h diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/b.c b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/b.c new file mode 100644 index 00000000000..ef8f5d3ca55 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/b.c @@ -0,0 +1,6 @@ +#pragma hdrstop +#include "b.h" + +int b() { + return A; +} diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/c.c b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/c.c new file mode 100644 index 00000000000..b270ddd6478 --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/c.c @@ -0,0 +1,6 @@ +#include "d.h" +#include "c.h" + +int c() { + return A; +} diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/microsoft-pch.expected b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/microsoft-pch.expected similarity index 100% rename from cpp/ql/test/header-variant-tests/microsoft-pch/microsoft-pch.expected rename to cpp/ql/integration-tests/header-variant-tests/microsoft-pch/microsoft-pch.expected diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/microsoft-pch.ql b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/microsoft-pch.ql similarity index 100% rename from cpp/ql/test/header-variant-tests/microsoft-pch/microsoft-pch.ql rename to cpp/ql/integration-tests/header-variant-tests/microsoft-pch/microsoft-pch.ql diff --git a/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py new file mode 100644 index 00000000000..89bceec397e --- /dev/null +++ b/cpp/ql/integration-tests/header-variant-tests/microsoft-pch/test.py @@ -0,0 +1,11 @@ +import os + + +def test(codeql, cpp): + os.mkdir("pch") + extractor = cpp.get_tool("extractor") + codeql.database.create(command=[ + f'"{extractor}" --mimic-cl /Yca.h /Fppch/a.pch a.c', + f'"{extractor}" --mimic-cl /Yub.h /Fppch/a.pch b.c', + f'"{extractor}" --mimic-cl /Yuc.h /Fppch/a.pch c.c', + ]) diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll index dfd207ed7e5..1b23cf0b9ae 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplSpecific.qll @@ -31,4 +31,6 @@ module CppDataFlow implements InputSig { predicate viableImplInCallContext = Private::viableImplInCallContext/2; predicate neverSkipInPathGraph = Private::neverSkipInPathGraph/1; + + int defaultFieldFlowBranchLimit() { result = 3 } } diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll index c5024d07dcb..39975d8883c 100644 --- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll +++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll @@ -1652,8 +1652,6 @@ predicate validParameterAliasStep(Node node1, Node node2) { ) } -private predicate isTopLevel(Cpp::Stmt s) { any(Function f).getBlock().getAStmt() = s } - private Cpp::Stmt getAChainedBranch(Cpp::IfStmt s) { result = s.getThen() or @@ -1684,11 +1682,9 @@ private Instruction getAnInstruction(Node n) { } private newtype TDataFlowSecondLevelScope = - TTopLevelIfBranch(Cpp::Stmt s) { - exists(Cpp::IfStmt ifstmt | s = getAChainedBranch(ifstmt) and isTopLevel(ifstmt)) - } or + TTopLevelIfBranch(Cpp::Stmt s) { s = getAChainedBranch(_) } or TTopLevelSwitchCase(Cpp::SwitchCase s) { - exists(Cpp::SwitchStmt switchstmt | s = switchstmt.getASwitchCase() and isTopLevel(switchstmt)) + exists(Cpp::SwitchStmt switchstmt | s = switchstmt.getASwitchCase()) } /** diff --git a/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql b/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql index 7dda356353e..d1645007a32 100644 --- a/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql +++ b/cpp/ql/src/Likely Bugs/Conversion/CastArrayPointerArithmetic.ql @@ -44,6 +44,10 @@ module CastToPointerArithFlowConfig implements DataFlow::StateConfigSig { ) and getFullyConvertedType(node) = state } + + predicate isBarrierIn(DataFlow::Node node) { isSource(node, _) } + + predicate isBarrierOut(DataFlow::Node node) { isSink(node, _) } } /** diff --git a/cpp/ql/src/Security/CWE/CWE-014/MemsetMayBeDeleted.ql b/cpp/ql/src/Security/CWE/CWE-014/MemsetMayBeDeleted.ql index 33c31972295..3aff4e1dcc2 100644 --- a/cpp/ql/src/Security/CWE/CWE-014/MemsetMayBeDeleted.ql +++ b/cpp/ql/src/Security/CWE/CWE-014/MemsetMayBeDeleted.ql @@ -8,7 +8,7 @@ * @security-severity 7.8 * @precision high * @tags security - * external/cwe/cwe-14 + * external/cwe/cwe-014 */ import cpp diff --git a/cpp/ql/src/Security/CWE/CWE-020/CountUntrustedDataToExternalAPI.ql b/cpp/ql/src/Security/CWE/CWE-020/CountUntrustedDataToExternalAPI.ql index bebff32a5c1..80bf2b8a8b3 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/CountUntrustedDataToExternalAPI.ql +++ b/cpp/ql/src/Security/CWE/CWE-020/CountUntrustedDataToExternalAPI.ql @@ -5,7 +5,7 @@ * to it. * @id cpp/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import cpp diff --git a/cpp/ql/src/Security/CWE/CWE-020/IRCountUntrustedDataToExternalAPI.ql b/cpp/ql/src/Security/CWE/CWE-020/IRCountUntrustedDataToExternalAPI.ql index 69911c22c6a..3bc364c4116 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/IRCountUntrustedDataToExternalAPI.ql +++ b/cpp/ql/src/Security/CWE/CWE-020/IRCountUntrustedDataToExternalAPI.ql @@ -5,7 +5,7 @@ * to it. * @id cpp/count-untrusted-data-external-api-ir * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import cpp diff --git a/cpp/ql/src/Security/CWE/CWE-020/IRUntrustedDataToExternalAPI.ql b/cpp/ql/src/Security/CWE/CWE-020/IRUntrustedDataToExternalAPI.ql index 432f47f6735..07c97ed77fd 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/IRUntrustedDataToExternalAPI.ql +++ b/cpp/ql/src/Security/CWE/CWE-020/IRUntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import cpp diff --git a/cpp/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql b/cpp/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql index 1cfd0a7132f..34ea739e675 100644 --- a/cpp/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql +++ b/cpp/ql/src/Security/CWE/CWE-020/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import cpp diff --git a/cpp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/cpp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..daefff65c31 --- /dev/null +++ b/cpp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,9 @@ +--- +category: queryMetadata +--- +* The tag `external/cwe/cwe-14` has been removed from `cpp/memset-may-be-deleted` and the tag `external/cwe/cwe-014` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/count-untrusted-data-external-api-ir` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/untrusted-data-to-external-api-ir` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cpp/late-check-of-function-argument` and the tag `external/cwe/cwe-020` has been added. diff --git a/cpp/ql/src/codeql-suites/cpp-code-quality.qls b/cpp/ql/src/codeql-suites/cpp-code-quality.qls index 0637a088a01..2074f9378cf 100644 --- a/cpp/ql/src/codeql-suites/cpp-code-quality.qls +++ b/cpp/ql/src/codeql-suites/cpp-code-quality.qls @@ -1 +1,3 @@ -[] \ No newline at end of file +- queries: . +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-020/LateCheckOfFunctionArgument.ql b/cpp/ql/src/experimental/Security/CWE/CWE-020/LateCheckOfFunctionArgument.ql index 07d18992db6..be53ba1fc68 100644 --- a/cpp/ql/src/experimental/Security/CWE/CWE-020/LateCheckOfFunctionArgument.ql +++ b/cpp/ql/src/experimental/Security/CWE/CWE-020/LateCheckOfFunctionArgument.ql @@ -10,7 +10,7 @@ * @tags correctness * security * experimental - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import cpp diff --git a/cpp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql index d49ace967ca..c9ded748466 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = ContentSensitive::captureFlow(api, _, _) diff --git a/cpp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql index e9a5ea24dec..813eece65b9 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string noflow where noflow = captureNeutral(api) diff --git a/cpp/ql/src/utils/modelgenerator/CaptureSinkModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureSinkModels.ql index 5485a2645bc..1ed9b032070 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureSinkModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureSinkModels.ql @@ -7,8 +7,8 @@ */ import internal.CaptureModels -import Heuristic +import SinkModels from DataFlowSinkTargetApi api, string sink -where sink = captureSink(api) +where sink = Heuristic::captureSink(api) select sink order by sink diff --git a/cpp/ql/src/utils/modelgenerator/CaptureSourceModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureSourceModels.ql index c2240c64688..4f86fb6b6fb 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureSourceModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureSourceModels.ql @@ -7,8 +7,8 @@ */ import internal.CaptureModels -import Heuristic +import SourceModels from DataFlowSourceTargetApi api, string source -where source = captureSource(api) +where source = Heuristic::captureSource(api) select source order by source diff --git a/cpp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql b/cpp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql index 60341abc0b5..a023afabd31 100644 --- a/cpp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql +++ b/cpp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = captureFlow(api, _) diff --git a/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll index 58acfa01118..93abe205f1a 100644 --- a/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/cpp/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -2,7 +2,7 @@ * Provides predicates related to capturing summary models of the Standard or a 3rd party library. */ -private import cpp +private import cpp as Cpp private import semmle.code.cpp.ir.IR private import semmle.code.cpp.dataflow.ExternalFlow as ExternalFlow private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon @@ -10,113 +10,67 @@ private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplSpecific private import semmle.code.cpp.ir.dataflow.internal.DataFlowPrivate as DataFlowPrivate private import semmle.code.cpp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl private import semmle.code.cpp.ir.dataflow.internal.TaintTrackingImplSpecific -private import semmle.code.cpp.dataflow.new.TaintTracking +private import semmle.code.cpp.dataflow.new.TaintTracking as Tt +private import semmle.code.cpp.dataflow.new.DataFlow as Df private import codeql.mad.modelgenerator.internal.ModelGeneratorImpl -module ModelGeneratorInput implements ModelGeneratorInputSig { +/** + * Holds if `f` is a "private" function. + * + * A "private" function does not contribute any models as it is assumed + * to be an implementation detail of some other "public" function for which + * we will generate a summary. + */ +private predicate isPrivateOrProtected(Cpp::Function f) { + f.getNamespace().getParentNamespace*().isAnonymous() + or + exists(Cpp::MemberFunction mf | mf = f | + mf.isPrivate() + or + mf.isProtected() + ) + or + f.isStatic() +} + +private predicate isUninterestingForModels(Callable api) { + // Note: This also makes all global/static-local variables + // not relevant (which is good!) + not api.(Cpp::Function).hasDefinition() + or + isPrivateOrProtected(api) + or + api instanceof Cpp::Destructor + or + api = any(Cpp::LambdaExpression lambda).getLambdaFunction() + or + api.isFromUninstantiatedTemplate(_) +} + +private predicate relevant(Callable api) { + api.fromSource() and + not isUninterestingForModels(api) +} + +module ModelGeneratorCommonInput implements ModelGeneratorCommonInputSig +{ + private module DataFlow = Df::DataFlow; + class Type = DataFlowPrivate::DataFlowType; // Note: This also includes `this` class Parameter = DataFlow::ParameterNode; - class Callable = Declaration; + class Callable = Cpp::Declaration; class NodeExtended extends DataFlow::Node { Callable getAsExprEnclosingCallable() { result = this.asExpr().getEnclosingDeclaration() } } - Parameter asParameter(NodeExtended n) { result = n } - Callable getEnclosingCallable(NodeExtended n) { result = n.getEnclosingCallable().asSourceCallable() } - Callable getAsExprEnclosingCallable(NodeExtended n) { - result = n.asExpr().getEnclosingDeclaration() - } - - /** Gets `api` if it is relevant. */ - private Callable liftedImpl(Callable api) { result = api and relevant(api) } - - private predicate hasManualSummaryModel(Callable api) { - api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) or - api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) - } - - private predicate hasManualSourceModel(Callable api) { - api = any(FlowSummaryImpl::Public::NeutralSourceCallable sc | sc.hasManualModel()) - } - - private predicate hasManualSinkModel(Callable api) { - api = any(FlowSummaryImpl::Public::NeutralSinkCallable sc | sc.hasManualModel()) - } - - /** - * Holds if `f` is a "private" function. - * - * A "private" function does not contribute any models as it is assumed - * to be an implementation detail of some other "public" function for which - * we will generate a summary. - */ - private predicate isPrivateOrProtected(Function f) { - f.getNamespace().getParentNamespace*().isAnonymous() - or - exists(MemberFunction mf | mf = f | - mf.isPrivate() - or - mf.isProtected() - ) - or - f.isStatic() - } - - private predicate isUninterestingForModels(Callable api) { - // Note: This also makes all global/static-local variables - // not relevant (which is good!) - not api.(Function).hasDefinition() - or - isPrivateOrProtected(api) - or - api instanceof Destructor - or - api = any(LambdaExpression lambda).getLambdaFunction() - or - api.isFromUninstantiatedTemplate(_) - } - - private predicate relevant(Callable api) { - api.fromSource() and - not isUninterestingForModels(api) - } - - class SummaryTargetApi extends Callable { - private Callable lift; - - SummaryTargetApi() { - lift = liftedImpl(this) and - not hasManualSummaryModel(lift) - } - - Callable lift() { result = lift } - - predicate isRelevant() { - relevant(this) and - not hasManualSummaryModel(this) - } - } - - class SourceOrSinkTargetApi extends Callable { - SourceOrSinkTargetApi() { relevant(this) } - } - - class SinkTargetApi extends SourceOrSinkTargetApi { - SinkTargetApi() { not hasManualSinkModel(this) } - } - - class SourceTargetApi extends SourceOrSinkTargetApi { - SourceTargetApi() { not hasManualSourceModel(this) } - } - class InstanceParameterNode extends DataFlow::ParameterNode { InstanceParameterNode() { DataFlowPrivate::nodeHasInstruction(this, @@ -124,7 +78,7 @@ module ModelGeneratorInput implements ModelGeneratorInputSig" @@ -166,7 +120,7 @@ module ModelGeneratorInput implements ModelGeneratorInputSig + +private module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { + private module DataFlow = Df::DataFlow; + + Parameter asParameter(NodeExtended n) { result = n } + + Callable getAsExprEnclosingCallable(NodeExtended n) { + result = n.asExpr().getEnclosingDeclaration() + } + + private predicate hasManualSummaryModel(Callable api) { + api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) or + api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) + } + + /** Gets `api` if it is relevant. */ + private Callable liftedImpl(Callable api) { result = api and relevant(api) } + + class SummaryTargetApi extends Callable { + private Callable lift; + + SummaryTargetApi() { + lift = liftedImpl(this) and + not hasManualSummaryModel(lift) + } + + Callable lift() { result = lift } + + predicate isRelevant() { + relevant(this) and + not hasManualSummaryModel(this) + } + } + predicate isAdditionalContentFlowStep(DataFlow::Node node1, DataFlow::Node node2) { - TaintTracking::defaultAdditionalTaintStep(node1, node2, _) and + Tt::TaintTracking::defaultAdditionalTaintStep(node1, node2, _) and not exists(DataFlow::Content f | DataFlowPrivate::readStep(node1, f, node2) and containerContent(f) ) @@ -341,7 +328,7 @@ module ModelGeneratorInput implements ModelGeneratorInputSig +import MakeSummaryModelGenerator as SummaryModels +import MakeSourceModelGenerator as SourceModels +import MakeSinkModelGenerator as SinkModels diff --git a/cpp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll b/cpp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll index 7841f8ed1a4..43342aa671e 100644 --- a/cpp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll +++ b/cpp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll @@ -1,6 +1,6 @@ private import cpp as Cpp private import codeql.mad.modelgenerator.internal.ModelPrinting -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput private module ModelPrintingLang implements ModelPrintingLangSig { class Callable = Cpp::Declaration; diff --git a/cpp/ql/test/header-variant-tests/clang-pch/_.c b/cpp/ql/test/header-variant-tests/clang-pch/_.c deleted file mode 100644 index 6186edd586f..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/_.c +++ /dev/null @@ -1,3 +0,0 @@ -// This file exists to ensure that the output subdirectory exists prior to -// a.c being indexed, as said directory needs to exist for the PCH file to -// be created, and will be created by running the extractor. diff --git a/cpp/ql/test/header-variant-tests/clang-pch/a.c b/cpp/ql/test/header-variant-tests/clang-pch/a.c deleted file mode 100644 index 9164cdc19a5..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/a.c +++ /dev/null @@ -1,3 +0,0 @@ -#include "a.h" -#define FOUR 4 -// semmle-extractor-options: --clang -emit-pch -o ${testdir}/clang-pch.testproj/a.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/c.c b/cpp/ql/test/header-variant-tests/clang-pch/c.c deleted file mode 100644 index 0a8b9766713..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/c.c +++ /dev/null @@ -1,4 +0,0 @@ -int main() { - return ONE + FOUR; -} -// semmle-extractor-options: --clang -include ${testdir}/clang-pch.testproj/a -Iextra_dummy_path diff --git a/cpp/ql/test/header-variant-tests/clang-pch/d.c b/cpp/ql/test/header-variant-tests/clang-pch/d.c deleted file mode 100644 index f81af3d1b63..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/d.c +++ /dev/null @@ -1,2 +0,0 @@ -#import "d.h" -// semmle-extractor-options: --clang -emit-pch -o ${testdir}/clang-pch.testproj/d.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/e.c b/cpp/ql/test/header-variant-tests/clang-pch/e.c deleted file mode 100644 index abdb5900200..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/e.c +++ /dev/null @@ -1,4 +0,0 @@ -int main() { - return SEVENTEEN; -} -// semmle-extractor-options: --clang -include-pch ${testdir}/clang-pch.testproj/d.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/f.c b/cpp/ql/test/header-variant-tests/clang-pch/f.c deleted file mode 100644 index cca56931acd..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/f.c +++ /dev/null @@ -1,6 +0,0 @@ -#if 1 -#pragma hdrstop -extern int x; -#define SEEN_F -#endif -// semmle-extractor-options: --clang -emit-pch -o ${testdir}/clang-pch.testproj/f.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/g.c b/cpp/ql/test/header-variant-tests/clang-pch/g.c deleted file mode 100644 index b16ca33ab36..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/g.c +++ /dev/null @@ -1,6 +0,0 @@ -#ifdef SEEN_F -static int g() { - return 20; -} -#endif -// semmle-extractor-options: --clang -include-pch ${testdir}/clang-pch.testproj/f.pch diff --git a/cpp/ql/test/header-variant-tests/clang-pch/h.c b/cpp/ql/test/header-variant-tests/clang-pch/h.c deleted file mode 100644 index 7ffc9b6133c..00000000000 --- a/cpp/ql/test/header-variant-tests/clang-pch/h.c +++ /dev/null @@ -1,5 +0,0 @@ -#include "h1.h" -#pragma hdrstop -#include "h2.h" -#define SEEN_H -// semmle-extractor-options: --clang -emit-pch -o ${testdir}/clang-pch.testproj/h.pch diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/_.c b/cpp/ql/test/header-variant-tests/microsoft-pch/_.c deleted file mode 100644 index 6186edd586f..00000000000 --- a/cpp/ql/test/header-variant-tests/microsoft-pch/_.c +++ /dev/null @@ -1,3 +0,0 @@ -// This file exists to ensure that the output subdirectory exists prior to -// a.c being indexed, as said directory needs to exist for the PCH file to -// be created, and will be created by running the extractor. diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/a.c b/cpp/ql/test/header-variant-tests/microsoft-pch/a.c deleted file mode 100644 index ba41bafcaad..00000000000 --- a/cpp/ql/test/header-variant-tests/microsoft-pch/a.c +++ /dev/null @@ -1,2 +0,0 @@ -#include "a.h" -// semmle-extractor-options: --microsoft /Yca.h /Fp${testdir}/microsoft-pch.testproj/a.pch diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/b.c b/cpp/ql/test/header-variant-tests/microsoft-pch/b.c deleted file mode 100644 index b6ea5085fd1..00000000000 --- a/cpp/ql/test/header-variant-tests/microsoft-pch/b.c +++ /dev/null @@ -1,7 +0,0 @@ -#pragma hdrstop -#include "b.h" - -int b() { - return A; -} -// semmle-extractor-options: --microsoft /Yub.h /Fp${testdir}/microsoft-pch.testproj/a.pch diff --git a/cpp/ql/test/header-variant-tests/microsoft-pch/c.c b/cpp/ql/test/header-variant-tests/microsoft-pch/c.c deleted file mode 100644 index aaddbae8688..00000000000 --- a/cpp/ql/test/header-variant-tests/microsoft-pch/c.c +++ /dev/null @@ -1,7 +0,0 @@ -#include "d.h" -#include "c.h" - -int c() { - return A; -} -// semmle-extractor-options: --microsoft /Yuc.h /Fp${testdir}/microsoft-pch.testproj/a.pch diff --git a/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureContentSummaryModels.ql b/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureContentSummaryModels.ql index a73cc163198..a7d6e0ad4ec 100644 --- a/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureContentSummaryModels.ql +++ b/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureContentSummaryModels.ql @@ -1,5 +1,6 @@ import cpp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import InlineModelsAsDataTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql b/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql index 14423e8c078..6c35b568f96 100644 --- a/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql +++ b/cpp/ql/test/library-tests/dataflow/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql @@ -1,5 +1,6 @@ import cpp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import InlineModelsAsDataTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/cpp/ql/test/query-tests/Likely Bugs/Conversion/CastArrayPointerArithmetic/CastArrayPointerArithmetic.expected b/cpp/ql/test/query-tests/Likely Bugs/Conversion/CastArrayPointerArithmetic/CastArrayPointerArithmetic.expected index 3f66b2c20b3..75e2e581664 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Conversion/CastArrayPointerArithmetic/CastArrayPointerArithmetic.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Conversion/CastArrayPointerArithmetic/CastArrayPointerArithmetic.expected @@ -3,22 +3,13 @@ edges | test.cpp:30:34:30:34 | b | test.cpp:31:2:31:2 | b | provenance | | | test.cpp:34:31:34:31 | b | test.cpp:35:2:35:2 | b | provenance | | | test.cpp:57:19:57:19 | d | test.cpp:26:29:26:29 | b | provenance | | -| test.cpp:57:19:57:19 | d | test.cpp:58:25:58:25 | d | provenance | | -| test.cpp:57:19:57:19 | d | test.cpp:59:21:59:21 | d | provenance | | | test.cpp:58:25:58:25 | d | test.cpp:30:34:30:34 | b | provenance | | -| test.cpp:58:25:58:25 | d | test.cpp:59:21:59:21 | d | provenance | | | test.cpp:59:21:59:21 | d | test.cpp:34:31:34:31 | b | provenance | | | test.cpp:74:19:74:21 | dss | test.cpp:26:29:26:29 | b | provenance | | -| test.cpp:74:19:74:21 | dss | test.cpp:75:25:75:27 | dss | provenance | | -| test.cpp:74:19:74:21 | dss | test.cpp:76:21:76:23 | dss | provenance | | | test.cpp:75:25:75:27 | dss | test.cpp:30:34:30:34 | b | provenance | | -| test.cpp:75:25:75:27 | dss | test.cpp:76:21:76:23 | dss | provenance | | | test.cpp:76:21:76:23 | dss | test.cpp:34:31:34:31 | b | provenance | | | test.cpp:86:19:86:20 | d2 | test.cpp:26:29:26:29 | b | provenance | | -| test.cpp:86:19:86:20 | d2 | test.cpp:87:25:87:26 | d2 | provenance | | -| test.cpp:86:19:86:20 | d2 | test.cpp:88:21:88:22 | d2 | provenance | | | test.cpp:87:25:87:26 | d2 | test.cpp:30:34:30:34 | b | provenance | | -| test.cpp:87:25:87:26 | d2 | test.cpp:88:21:88:22 | d2 | provenance | | | test.cpp:88:21:88:22 | d2 | test.cpp:34:31:34:31 | b | provenance | | nodes | test.cpp:26:29:26:29 | b | semmle.label | b | @@ -41,18 +32,9 @@ subpaths | test.cpp:27:2:27:2 | b | test.cpp:57:19:57:19 | d | test.cpp:27:2:27:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:57:19:57:19 | d | this cast | | test.cpp:27:2:27:2 | b | test.cpp:74:19:74:21 | dss | test.cpp:27:2:27:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:74:19:74:21 | dss | this cast | | test.cpp:27:2:27:2 | b | test.cpp:86:19:86:20 | d2 | test.cpp:27:2:27:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:86:19:86:20 | d2 | this cast | -| test.cpp:31:2:31:2 | b | test.cpp:57:19:57:19 | d | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:57:19:57:19 | d | this cast | | test.cpp:31:2:31:2 | b | test.cpp:58:25:58:25 | d | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:58:25:58:25 | d | this cast | -| test.cpp:31:2:31:2 | b | test.cpp:74:19:74:21 | dss | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:74:19:74:21 | dss | this cast | | test.cpp:31:2:31:2 | b | test.cpp:75:25:75:27 | dss | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:75:25:75:27 | dss | this cast | -| test.cpp:31:2:31:2 | b | test.cpp:86:19:86:20 | d2 | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:86:19:86:20 | d2 | this cast | | test.cpp:31:2:31:2 | b | test.cpp:87:25:87:26 | d2 | test.cpp:31:2:31:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:87:25:87:26 | d2 | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:57:19:57:19 | d | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:57:19:57:19 | d | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:58:25:58:25 | d | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:58:25:58:25 | d | this cast | | test.cpp:35:2:35:2 | b | test.cpp:59:21:59:21 | d | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:59:21:59:21 | d | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:74:19:74:21 | dss | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:74:19:74:21 | dss | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:75:25:75:27 | dss | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:75:25:75:27 | dss | this cast | | test.cpp:35:2:35:2 | b | test.cpp:76:21:76:23 | dss | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:76:21:76:23 | dss | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:86:19:86:20 | d2 | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:86:19:86:20 | d2 | this cast | -| test.cpp:35:2:35:2 | b | test.cpp:87:25:87:26 | d2 | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:87:25:87:26 | d2 | this cast | | test.cpp:35:2:35:2 | b | test.cpp:88:21:88:22 | d2 | test.cpp:35:2:35:2 | b | This pointer arithmetic may be done with the wrong type because of $@. | test.cpp:88:21:88:22 | d2 | this cast | diff --git a/csharp/ql/src/API Abuse/FormatInvalid.ql b/csharp/ql/src/API Abuse/FormatInvalid.ql index a2b8ef5e222..056730a577d 100644 --- a/csharp/ql/src/API Abuse/FormatInvalid.ql +++ b/csharp/ql/src/API Abuse/FormatInvalid.ql @@ -8,6 +8,7 @@ * @id cs/invalid-string-formatting * @tags reliability * maintainability + * quality */ import csharp diff --git a/csharp/ql/src/API Abuse/NoDisposeCallOnLocalIDisposable.ql b/csharp/ql/src/API Abuse/NoDisposeCallOnLocalIDisposable.ql index e5826c42342..f02b0d49b54 100644 --- a/csharp/ql/src/API Abuse/NoDisposeCallOnLocalIDisposable.ql +++ b/csharp/ql/src/API Abuse/NoDisposeCallOnLocalIDisposable.ql @@ -8,6 +8,7 @@ * @id cs/local-not-disposed * @tags efficiency * maintainability + * quality * external/cwe/cwe-404 * external/cwe/cwe-459 * external/cwe/cwe-460 diff --git a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql index 88d938e399f..7bfdea1e906 100644 --- a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql +++ b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql @@ -9,6 +9,7 @@ * @id cs/constant-condition * @tags maintainability * readability + * quality * external/cwe/cwe-835 */ diff --git a/csharp/ql/src/Configuration/PasswordInConfigurationFile.ql b/csharp/ql/src/Configuration/PasswordInConfigurationFile.ql index c6f004789a7..a2fe7cf2290 100644 --- a/csharp/ql/src/Configuration/PasswordInConfigurationFile.ql +++ b/csharp/ql/src/Configuration/PasswordInConfigurationFile.ql @@ -7,7 +7,7 @@ * @precision medium * @id cs/password-in-configuration * @tags security - * external/cwe/cwe-13 + * external/cwe/cwe-013 * external/cwe/cwe-256 * external/cwe/cwe-313 */ diff --git a/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql b/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql index 1e2eaad1aa1..5be820ee74c 100644 --- a/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql +++ b/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql @@ -6,6 +6,7 @@ * @problem.severity warning * @id cs/useless-assignment-to-local * @tags maintainability + * quality * external/cwe/cwe-563 * @precision very-high */ diff --git a/csharp/ql/src/Likely Bugs/Collections/ContainerLengthCmpOffByOne.ql b/csharp/ql/src/Likely Bugs/Collections/ContainerLengthCmpOffByOne.ql index 615f0634f16..754ed8ad773 100644 --- a/csharp/ql/src/Likely Bugs/Collections/ContainerLengthCmpOffByOne.ql +++ b/csharp/ql/src/Likely Bugs/Collections/ContainerLengthCmpOffByOne.ql @@ -9,6 +9,7 @@ * @tags reliability * correctness * logic + * quality * external/cwe/cwe-193 */ diff --git a/csharp/ql/src/Likely Bugs/Collections/ContainerSizeCmpZero.ql b/csharp/ql/src/Likely Bugs/Collections/ContainerSizeCmpZero.ql index 6ba109713ae..90f5b981e24 100644 --- a/csharp/ql/src/Likely Bugs/Collections/ContainerSizeCmpZero.ql +++ b/csharp/ql/src/Likely Bugs/Collections/ContainerSizeCmpZero.ql @@ -8,6 +8,7 @@ * @tags reliability * correctness * logic + * quality */ import csharp diff --git a/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql b/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql index 6091b0f79a3..f3defa47a43 100644 --- a/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql +++ b/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql @@ -9,6 +9,7 @@ * @tags reliability * correctness * logic + * quality * external/cwe/cwe-480 * external/cwe/cwe-691 */ diff --git a/csharp/ql/src/Likely Bugs/EqualityCheckOnFloats.ql b/csharp/ql/src/Likely Bugs/EqualityCheckOnFloats.ql index 1109201fbe1..108e6f45f94 100644 --- a/csharp/ql/src/Likely Bugs/EqualityCheckOnFloats.ql +++ b/csharp/ql/src/Likely Bugs/EqualityCheckOnFloats.ql @@ -9,6 +9,7 @@ * @id cs/equality-on-floats * @tags reliability * correctness + * quality */ import csharp diff --git a/csharp/ql/src/Likely Bugs/ReferenceEqualsOnValueTypes.ql b/csharp/ql/src/Likely Bugs/ReferenceEqualsOnValueTypes.ql index f038117aff3..de29f6109ad 100644 --- a/csharp/ql/src/Likely Bugs/ReferenceEqualsOnValueTypes.ql +++ b/csharp/ql/src/Likely Bugs/ReferenceEqualsOnValueTypes.ql @@ -7,6 +7,7 @@ * @id cs/reference-equality-on-valuetypes * @tags reliability * correctness + * quality * external/cwe/cwe-595 */ diff --git a/csharp/ql/src/Likely Bugs/SelfAssignment.ql b/csharp/ql/src/Likely Bugs/SelfAssignment.ql index dd63ba87627..e010d18292c 100644 --- a/csharp/ql/src/Likely Bugs/SelfAssignment.ql +++ b/csharp/ql/src/Likely Bugs/SelfAssignment.ql @@ -8,6 +8,7 @@ * @tags reliability * correctness * logic + * quality */ import csharp diff --git a/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql b/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql index aa244147e3e..d1e2dbdf056 100644 --- a/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql +++ b/csharp/ql/src/Likely Bugs/UncheckedCastInEquals.ql @@ -7,6 +7,7 @@ * @id cs/unchecked-cast-in-equals * @tags reliability * maintainability + * quality */ import csharp diff --git a/csharp/ql/src/Performance/UseTryGetValue.ql b/csharp/ql/src/Performance/UseTryGetValue.ql index 67b57e90347..c33c0d1e8d7 100644 --- a/csharp/ql/src/Performance/UseTryGetValue.ql +++ b/csharp/ql/src/Performance/UseTryGetValue.ql @@ -6,7 +6,9 @@ * @problem.severity recommendation * @precision high * @id cs/inefficient-containskey - * @tags maintainability efficiency + * @tags maintainability + * efficiency + * quality */ import csharp diff --git a/csharp/ql/src/Security Features/CWE-011/ASPNetDebug.ql b/csharp/ql/src/Security Features/CWE-011/ASPNetDebug.ql index 308f3eeeac2..4e0e52352b4 100644 --- a/csharp/ql/src/Security Features/CWE-011/ASPNetDebug.ql +++ b/csharp/ql/src/Security Features/CWE-011/ASPNetDebug.ql @@ -10,7 +10,7 @@ * @tags security * maintainability * frameworks/asp.net - * external/cwe/cwe-11 + * external/cwe/cwe-011 * external/cwe/cwe-532 */ diff --git a/csharp/ql/src/Security Features/CWE-016/ASPNetMaxRequestLength.ql b/csharp/ql/src/Security Features/CWE-016/ASPNetMaxRequestLength.ql index 89bd133d59a..b9ac41e0e39 100644 --- a/csharp/ql/src/Security Features/CWE-016/ASPNetMaxRequestLength.ql +++ b/csharp/ql/src/Security Features/CWE-016/ASPNetMaxRequestLength.ql @@ -8,7 +8,7 @@ * @id cs/web/large-max-request-length * @tags security * frameworks/asp.net - * external/cwe/cwe-16 + * external/cwe/cwe-016 */ import csharp diff --git a/csharp/ql/src/Security Features/CWE-016/ASPNetPagesValidateRequest.ql b/csharp/ql/src/Security Features/CWE-016/ASPNetPagesValidateRequest.ql index 68902a0622d..3c51b7a8e2e 100644 --- a/csharp/ql/src/Security Features/CWE-016/ASPNetPagesValidateRequest.ql +++ b/csharp/ql/src/Security Features/CWE-016/ASPNetPagesValidateRequest.ql @@ -8,7 +8,7 @@ * @id cs/web/request-validation-disabled * @tags security * frameworks/asp.net - * external/cwe/cwe-16 + * external/cwe/cwe-016 */ import csharp diff --git a/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql b/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql index b07b1093ec8..8427ceb87eb 100644 --- a/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql +++ b/csharp/ql/src/Security Features/CWE-020/ExternalAPIsUsedWithUntrustedData.ql @@ -5,7 +5,7 @@ * to it. * @id cs/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import csharp diff --git a/csharp/ql/src/Security Features/CWE-020/RuntimeChecksBypass.ql b/csharp/ql/src/Security Features/CWE-020/RuntimeChecksBypass.ql index 6148f0f6ae9..af029459033 100644 --- a/csharp/ql/src/Security Features/CWE-020/RuntimeChecksBypass.ql +++ b/csharp/ql/src/Security Features/CWE-020/RuntimeChecksBypass.ql @@ -7,7 +7,7 @@ * @security-severity 7.8 * @precision medium * @tags security - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import semmle.code.csharp.serialization.Serialization diff --git a/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql b/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql index a71a2705bdd..0543f198d22 100644 --- a/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql +++ b/csharp/ql/src/Security Features/CWE-020/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import csharp diff --git a/csharp/ql/src/Security Features/CWE-248/MissingASPNETGlobalErrorHandler.ql b/csharp/ql/src/Security Features/CWE-248/MissingASPNETGlobalErrorHandler.ql index f37d4c497de..14d73c02e1e 100644 --- a/csharp/ql/src/Security Features/CWE-248/MissingASPNETGlobalErrorHandler.ql +++ b/csharp/ql/src/Security Features/CWE-248/MissingASPNETGlobalErrorHandler.ql @@ -8,7 +8,7 @@ * @precision high * @id cs/web/missing-global-error-handler * @tags security - * external/cwe/cwe-12 + * external/cwe/cwe-012 * external/cwe/cwe-248 */ diff --git a/csharp/ql/src/Useless code/DefaultToString.ql b/csharp/ql/src/Useless code/DefaultToString.ql index 544347576fe..ea33fe7874b 100644 --- a/csharp/ql/src/Useless code/DefaultToString.ql +++ b/csharp/ql/src/Useless code/DefaultToString.ql @@ -8,6 +8,7 @@ * @id cs/call-to-object-tostring * @tags reliability * maintainability + * quality */ import DefaultToStringQuery diff --git a/csharp/ql/src/Useless code/IntGetHashCode.ql b/csharp/ql/src/Useless code/IntGetHashCode.ql index 85d0f56aae0..847443d018e 100644 --- a/csharp/ql/src/Useless code/IntGetHashCode.ql +++ b/csharp/ql/src/Useless code/IntGetHashCode.ql @@ -8,6 +8,7 @@ * @id cs/useless-gethashcode-call * @tags readability * useless-code + * quality */ import csharp diff --git a/csharp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/csharp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..8b84ae3f077 --- /dev/null +++ b/csharp/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,12 @@ +--- +category: queryMetadata +--- + +* The tag `external/cwe/cwe-13` has been removed from `cs/password-in-configuration` and the tag `external/cwe/cwe-013` has been added. +* The tag `external/cwe/cwe-11` has been removed from `cs/web/debug-binary` and the tag `external/cwe/cwe-011` has been added. +* The tag `external/cwe/cwe-16` has been removed from `cs/web/large-max-request-length` and the tag `external/cwe/cwe-016` has been added. +* The tag `external/cwe/cwe-16` has been removed from `cs/web/request-validation-disabled` and the tag `external/cwe/cwe-016` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cs/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cs/serialization-check-bypass` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `cs/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-12` has been removed from `cs/web/missing-global-error-handler` and the tag `external/cwe/cwe-012` has been added. diff --git a/csharp/ql/src/codeql-suites/csharp-code-quality.qls b/csharp/ql/src/codeql-suites/csharp-code-quality.qls index 85bbe4db683..2074f9378cf 100644 --- a/csharp/ql/src/codeql-suites/csharp-code-quality.qls +++ b/csharp/ql/src/codeql-suites/csharp-code-quality.qls @@ -1,17 +1,3 @@ - queries: . -- include: - id: - - cs/index-out-of-bounds - - cs/test-for-negative-container-size - - cs/unchecked-cast-in-equals - - cs/reference-equality-on-valuetypes - - cs/self-assignment - - cs/inefficient-containskey - - cs/call-to-object-tostring - - cs/local-not-disposed - - cs/constant-condition - - cs/useless-gethashcode-call - - cs/non-short-circuit - - cs/useless-assignment-to-local - - cs/invalid-string-formatting - - cs/equality-on-floats +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/csharp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql index c108029e3df..cc36c15d6ad 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = ContentSensitive::captureFlow(api, _, _) diff --git a/csharp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql index 2afb0ea0284..c74240bedea 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureNeutralModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string noflow where noflow = captureNeutral(api) diff --git a/csharp/ql/src/utils/modelgenerator/CaptureSinkModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureSinkModels.ql index f4c9405c96a..f0d3294cd8a 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureSinkModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureSinkModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SinkModels from DataFlowSinkTargetApi api, string sink where sink = Heuristic::captureSink(api) diff --git a/csharp/ql/src/utils/modelgenerator/CaptureSourceModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureSourceModels.ql index 70f853b35a9..97688d6eb42 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureSourceModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureSourceModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SourceModels from DataFlowSourceTargetApi api, string source where source = Heuristic::captureSource(api) diff --git a/csharp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql b/csharp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql index a0193397eb2..61656c200ad 100644 --- a/csharp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql +++ b/csharp/ql/src/utils/modelgenerator/CaptureSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = captureFlow(api, _) diff --git a/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql b/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql index 979a129e565..bcf061eed45 100644 --- a/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql +++ b/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql @@ -10,6 +10,7 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import PartialFlow::PartialPathGraph int explorationLimit() { result = 3 } diff --git a/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql b/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql index 9d51b60ec2e..fa7921d9b63 100644 --- a/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql +++ b/csharp/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql @@ -10,6 +10,7 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import Heuristic import PropagateTaintFlow::PathGraph diff --git a/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll index ce83369df07..b0300e4a87f 100644 --- a/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/csharp/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -15,7 +15,41 @@ private import semmle.code.csharp.frameworks.System private import semmle.code.csharp.Location private import codeql.mad.modelgenerator.internal.ModelGeneratorImpl -module ModelGeneratorInput implements ModelGeneratorInputSig { +private predicate irrelevantAccessor(CS::Accessor a) { + a.getDeclaration().(CS::Property).isReadWrite() +} + +private predicate isUninterestingForModels(Callable api) { + api.getDeclaringType().getNamespace().getFullName() = "" + or + api instanceof CS::ConversionOperator + or + api instanceof Util::MainMethod + or + api instanceof CS::Destructor + or + api instanceof CS::AnonymousFunctionExpr + or + api.(CS::Constructor).isParameterless() + or + exists(Type decl | decl = api.getDeclaringType() | + decl instanceof SystemObjectClass or + decl instanceof SystemValueTypeClass + ) + or + // Disregard properties that have both a get and a set accessor, + // which implicitly means auto implemented properties. + irrelevantAccessor(api) +} + +private predicate relevant(Callable api) { + [api.(CS::Modifiable), api.(CS::Accessor).getDeclaration()].isEffectivelyPublic() and + api.fromSource() and + api.isUnboundDeclaration() and + not isUninterestingForModels(api) +} + +module ModelGeneratorCommonInput implements ModelGeneratorCommonInputSig { class Type = CS::Type; class Parameter = CS::Parameter; @@ -24,127 +58,8 @@ module ModelGeneratorInput implements ModelGeneratorInputSig`. - */ - private predicate isHigherOrder(Callable api) { - exists(Type t | t = api.getAParameter().getType().getUnboundDeclaration() | - t instanceof SystemLinqExpressions::DelegateExtType - ) - } - - private predicate irrelevantAccessor(CS::Accessor a) { - a.getDeclaration().(CS::Property).isReadWrite() - } - - private predicate isUninterestingForModels(Callable api) { - api.getDeclaringType().getNamespace().getFullName() = "" - or - api instanceof CS::ConversionOperator - or - api instanceof Util::MainMethod - or - api instanceof CS::Destructor - or - api instanceof CS::AnonymousFunctionExpr - or - api.(CS::Constructor).isParameterless() - or - exists(Type decl | decl = api.getDeclaringType() | - decl instanceof SystemObjectClass or - decl instanceof SystemValueTypeClass - ) - or - // Disregard properties that have both a get and a set accessor, - // which implicitly means auto implemented properties. - irrelevantAccessor(api) - } - - private predicate relevant(Callable api) { - [api.(CS::Modifiable), api.(CS::Accessor).getDeclaration()].isEffectivelyPublic() and - api.fromSource() and - api.isUnboundDeclaration() and - not isUninterestingForModels(api) - } - - private Callable getARelevantOverrideeOrImplementee(Overridable m) { - m.overridesOrImplements(result) and relevant(result) - } - - /** - * Gets the super implementation of `api` if it is relevant. - * If such a super implementation does not exist, returns `api` if it is relevant. - */ - private Callable liftedImpl(Callable api) { - ( - result = getARelevantOverrideeOrImplementee(api) - or - result = api and relevant(api) - ) and - not exists(getARelevantOverrideeOrImplementee(result)) - } - - private predicate hasManualSummaryModel(Callable api) { - api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) or - api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) - } - - private predicate hasManualSourceModel(Callable api) { - api = any(ExternalFlow::SourceCallable sc | sc.hasManualModel()) or - api = any(FlowSummaryImpl::Public::NeutralSourceCallable sc | sc.hasManualModel()) - } - - private predicate hasManualSinkModel(Callable api) { - api = any(ExternalFlow::SinkCallable sc | sc.hasManualModel()) or - api = any(FlowSummaryImpl::Public::NeutralSinkCallable sc | sc.hasManualModel()) - } - - predicate isUninterestingForDataFlowModels(Callable api) { none() } - - predicate isUninterestingForHeuristicDataFlowModels(Callable api) { isHigherOrder(api) } - - class SourceOrSinkTargetApi extends Callable { - SourceOrSinkTargetApi() { relevant(this) } - } - - class SinkTargetApi extends SourceOrSinkTargetApi { - SinkTargetApi() { not hasManualSinkModel(this) } - } - - class SourceTargetApi extends SourceOrSinkTargetApi { - SourceTargetApi() { - not hasManualSourceModel(this) and - // Do not generate source models for overridable callables - // as virtual dispatch implies that too many methods - // will be considered sources. - not this.(Overridable).overridesOrImplements(_) - } - } - - class SummaryTargetApi extends Callable { - private Callable lift; - - SummaryTargetApi() { - lift = liftedImpl(this) and - not hasManualSummaryModel(lift) - } - - Callable lift() { result = lift } - - predicate isRelevant() { - relevant(this) and - not hasManualSummaryModel(this) - } - } - /** * Holds if `t` is a type that is generally used for bulk data in collection types. * Eg. char[] is roughly equivalent to string and thus a highly @@ -205,6 +120,8 @@ module ModelGeneratorInput implements ModelGeneratorInputSig { @@ -251,63 +166,92 @@ module ModelGeneratorInput implements ModelGeneratorInputSig + +module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { + Callable getAsExprEnclosingCallable(NodeExtended node) { + result = node.asExpr().getEnclosingCallable() + } + + Parameter asParameter(NodeExtended node) { result = node.asParameter() } + + /** + * Holds if any of the parameters of `api` are `System.Func<>`. + */ + private predicate isHigherOrder(Callable api) { + exists(Type t | t = api.getAParameter().getType().getUnboundDeclaration() | + t instanceof SystemLinqExpressions::DelegateExtType + ) + } + + private Callable getARelevantOverrideeOrImplementee(Overridable m) { + m.overridesOrImplements(result) and relevant(result) + } + + /** + * Gets the super implementation of `api` if it is relevant. + * If such a super implementation does not exist, returns `api` if it is relevant. + */ + private Callable liftedImpl(Callable api) { + ( + result = getARelevantOverrideeOrImplementee(api) + or + result = api and relevant(api) + ) and + not exists(getARelevantOverrideeOrImplementee(result)) + } + + private predicate hasManualSummaryModel(Callable api) { + api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()) or + api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()) + } + + predicate isUninterestingForHeuristicDataFlowModels(Callable api) { isHigherOrder(api) } + + class SummaryTargetApi extends Callable { + private Callable lift; + + SummaryTargetApi() { + lift = liftedImpl(this) and + not hasManualSummaryModel(lift) + } + + Callable lift() { result = lift } + + predicate isRelevant() { + relevant(this) and + not hasManualSummaryModel(this) + } + } + predicate isAdditionalContentFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { TaintTrackingPrivate::defaultAdditionalTaintStep(nodeFrom, nodeTo, _) and not nodeTo.asExpr() instanceof CS::ElementAccess and @@ -370,34 +314,88 @@ module ModelGeneratorInput implements ModelGeneratorInputSig +import MakeSummaryModelGenerator as SummaryModels +import MakeSourceModelGenerator as SourceModels +import MakeSinkModelGenerator as SinkModels diff --git a/csharp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll b/csharp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll index 97e58d439f0..52611279b27 100644 --- a/csharp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll +++ b/csharp/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll @@ -1,6 +1,6 @@ private import csharp as CS private import codeql.mad.modelgenerator.internal.ModelPrinting -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput private module ModelPrintingLang implements ModelPrintingLangSig { class Callable = CS::Callable; diff --git a/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll b/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll index f7b0633ddd3..baba462c8a2 100644 --- a/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll +++ b/csharp/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll @@ -2,7 +2,8 @@ private import csharp private import semmle.code.csharp.frameworks.system.collections.Generic as GenericCollections private import semmle.code.csharp.dataflow.internal.DataFlowPrivate private import semmle.code.csharp.frameworks.system.linq.Expressions -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput +private import CaptureModels::SummaryModelGeneratorInput as SummaryModelGeneratorInput private import CaptureModelsPrinting /** @@ -177,21 +178,19 @@ private predicate output(Callable callable, TypeParameter tp, string output) { delegateSink(callable, tp, output) } -private module ModelPrintingInput implements ModelPrintingSig { +private module ModelPrintingInput implements ModelPrintingSummarySig { class SummaryApi = TypeBasedFlowTargetApi; - class SourceOrSinkApi = TypeBasedFlowTargetApi; - string getProvenance() { result = "tb-generated" } } -private module Printing = ModelPrinting; +private module Printing = ModelPrintingSummary; /** * A class of callables that are relevant generating summaries for based * on the Theorems for Free approach. */ -class TypeBasedFlowTargetApi extends ModelGeneratorInput::SummaryTargetApi { +class TypeBasedFlowTargetApi extends SummaryModelGeneratorInput::SummaryTargetApi { /** * Gets the string representation of all type based summaries for `this` * inspired by the Theorems for Free approach. diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql index fe575790af0..7a385bc70ac 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql index d8e71b5e720..c21a53dd844 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql index d5aa685bfe3..e79cab74560 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql index cc84ede4235..0cc8dd6d08d 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SinkModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql index 4c10362960a..2a54abf9b72 100644 --- a/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql +++ b/csharp/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql @@ -1,5 +1,6 @@ import csharp import utils.modelgenerator.internal.CaptureModels +import SourceModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.0.rst b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.0.rst index 7d62123a49a..aa604d702e7 100644 --- a/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.0.rst +++ b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.0.rst @@ -165,7 +165,7 @@ Java/Kotlin """"""""""" * Java extraction is now able to download Maven 3.9.x if a Maven Enforcer Plugin configuration indicates it is necessary. Maven 3.8.x is still preferred if the enforcer-plugin configuration (if any) permits it. -* Added a path injection sanitizer for calls to :code:`java.lang.String.matches`, :code:`java.lang.String.replace`, and :code:`java.lang.String.replaceAll` that make sure '/', '\', '..' are not in the path. +* Added a path injection sanitizer for calls to :code:`java.lang.String.matches`, :code:`java.lang.String.replace`, and :code:`java.lang.String.replaceAll` that make sure :code:`/`, :code:`\\`, :code:`..` are not in the path. JavaScript/TypeScript """"""""""""""""""""" @@ -207,5 +207,5 @@ JavaScript/TypeScript * Intersection :code:`&&` * Subtraction :code:`--` - * :code:`\q` quoted string + * :code:`\\q` quoted string diff --git a/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.1.rst b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.1.rst index 1c28523b18f..2a8e20d84d1 100644 --- a/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.1.rst +++ b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.21.1.rst @@ -37,6 +37,14 @@ Bug Fixes Query Packs ----------- +New Features +~~~~~~~~~~~~ + +GitHub Actions +"""""""""""""" + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. + Bug Fixes ~~~~~~~~~ @@ -123,6 +131,11 @@ Ruby New Features ~~~~~~~~~~~~ +GitHub Actions +"""""""""""""" + +* CodeQL and Copilot Autofix support for GitHub Actions is now Generally Available. + C/C++ """"" diff --git a/docs/codeql/reusables/supported-frameworks.rst b/docs/codeql/reusables/supported-frameworks.rst index 402a3b9ee3d..07a5e509fec 100644 --- a/docs/codeql/reusables/supported-frameworks.rst +++ b/docs/codeql/reusables/supported-frameworks.rst @@ -254,6 +254,7 @@ and the CodeQL library pack ``codeql/python-all`` (`changelog { +private predicate isInfrequentlyUsed(J::CompilationUnit cu) { + cu.getPackage().getName().matches("javax.swing%") or + cu.getPackage().getName().matches("java.awt%") +} + +private predicate relevant(Callable api) { + api.isPublic() and + api.getDeclaringType().isPublic() and + api.fromSource() and + not isUninterestingForModels(api) and + not isInfrequentlyUsed(api.getCompilationUnit()) +} + +module ModelGeneratorCommonInput implements ModelGeneratorCommonInputSig { class Type = J::Type; class Parameter = J::Parameter; @@ -34,96 +47,8 @@ module ModelGeneratorInput implements ModelGeneratorInputSig= 0 then result = "Argument[" + pos + "]" else result = qualifierString() - ) - or - source.asExpr() instanceof J::FieldAccess and - result = qualifierString() - } - - bindingset[kind] - predicate isRelevantSinkKind(string kind) { - not kind = "log-injection" and - not kind.matches("regex-use%") and - not kind = "file-content-store" - } - - bindingset[kind] - predicate isRelevantSourceKind(string kind) { any() } - predicate containerContent = DataFlowPrivate::containerContent/1; + string partialModelRow(Callable api, int i) { + i = 0 and qualifiedName(api, result, _) // package + or + i = 1 and qualifiedName(api, _, result) // type + or + i = 2 and result = isExtensible(api) // extensible + or + i = 3 and result = api.getName() // name + or + i = 4 and result = ExternalFlow::paramsString(api) // parameters + or + i = 5 and result = "" and exists(api) // ext + } + + string partialNeutralModelRow(Callable api, int i) { + i = 0 and qualifiedName(api, result, _) // package + or + i = 1 and qualifiedName(api, _, result) // type + or + i = 2 and result = api.getName() // name + or + i = 3 and result = ExternalFlow::paramsString(api) // parameters + } +} + +private import ModelGeneratorCommonInput +private import MakeModelGeneratorFactory + +module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { + Callable getAsExprEnclosingCallable(NodeExtended node) { + result = node.asExpr().getEnclosingCallable() + } + + Parameter asParameter(NodeExtended node) { result = node.asParameter() } + + private J::Method getARelevantOverride(J::Method m) { + result = m.getAnOverride() and + relevant(result) and + // Other exclusions for overrides. + not m instanceof J::ToStringMethod + } + + /** + * Gets the super implementation of `m` if it is relevant. + * If such a super implementations does not exist, returns `m` if it is relevant. + */ + private J::Callable liftedImpl(J::Callable m) { + ( + result = getARelevantOverride(m) + or + result = m and relevant(m) + ) and + not exists(getARelevantOverride(result)) + } + + private predicate hasManualSummaryModel(Callable api) { + api = any(FlowSummaryImpl::Public::SummarizedCallable sc | sc.applyManualModel()).asCallable() or + api = any(FlowSummaryImpl::Public::NeutralSummaryCallable sc | sc.hasManualModel()).asCallable() + } + + class SummaryTargetApi extends Callable { + private Callable lift; + + SummaryTargetApi() { + lift = liftedImpl(this) and + not hasManualSummaryModel(lift) + } + + Callable lift() { result = lift } + + predicate isRelevant() { + relevant(this) and + not hasManualSummaryModel(this) + } + } + + predicate isUninterestingForDataFlowModels(Callable api) { + api.getDeclaringType() instanceof J::Interface and not exists(api.getBody()) + } + predicate isAdditionalContentFlowStep(DataFlow::Node node1, DataFlow::Node node2) { TaintTracking::defaultAdditionalTaintStep(node1, node2, _) and not exists(DataFlow::Content f | @@ -287,34 +250,71 @@ module ModelGeneratorInput implements ModelGeneratorInputSig= 0 then result = "Argument[" + pos + "]" else result = qualifierString() + ) + or + source.asExpr() instanceof J::FieldAccess and + result = qualifierString() + } + + bindingset[kind] + predicate isRelevantSinkKind(string kind) { + not kind = "log-injection" and + not kind.matches("regex-use%") and + not kind = "file-content-store" + } predicate sinkNode = ExternalFlow::sinkNode/2; } -import MakeModelGenerator +import MakeSummaryModelGenerator as SummaryModels +import MakeSourceModelGenerator as SourceModels +import MakeSinkModelGenerator as SinkModels diff --git a/java/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll b/java/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll index 4b376654afb..0d26f36c690 100644 --- a/java/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll +++ b/java/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll @@ -1,6 +1,6 @@ private import java as J private import codeql.mad.modelgenerator.internal.ModelPrinting -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput private module ModelPrintingLang implements ModelPrintingLangSig { class Callable = J::Callable; diff --git a/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll b/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll index 36aec805319..00c8c686c2d 100644 --- a/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll +++ b/java/ql/src/utils/modelgenerator/internal/CaptureTypeBasedSummaryModels.qll @@ -2,7 +2,8 @@ private import java private import semmle.code.java.Collections private import semmle.code.java.dataflow.internal.ContainerFlow private import CaptureModels as CaptureModels -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput +private import CaptureModels::SummaryModelGeneratorInput as SummaryModelGeneratorInput private import CaptureModelsPrinting /** @@ -284,21 +285,19 @@ private predicate output(Callable callable, TypeVariable tv, string output) { functionalSink(callable, tv, output) } -module ModelPrintingInput implements ModelPrintingSig { +module ModelPrintingInput implements ModelPrintingSummarySig { class SummaryApi = TypeBasedFlowTargetApi; - class SourceOrSinkApi = ModelGeneratorInput::SourceOrSinkTargetApi; - string getProvenance() { result = "tb-generated" } } -private module Printing = ModelPrinting; +private module Printing = ModelPrintingSummary; /** * A class of callables that are relevant generating summaries for based * on the Theorems for Free approach. */ -class TypeBasedFlowTargetApi extends ModelGeneratorInput::SummaryTargetApi { +class TypeBasedFlowTargetApi extends SummaryModelGeneratorInput::SummaryTargetApi { /** * Gets the string representation of all type based summaries for `this` * inspired by the Theorems for Free approach. diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql index 1ee494a849a..1954bc8cd96 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureContentSummaryModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql index 6b07aa87da8..7b40492d35a 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureHeuristicSummaryModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql index 3578153ddb8..ad567051922 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureNeutralModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql index 027670316c3..3d21b2e4f7d 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureSinkModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SinkModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/java/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql b/java/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql index d8346f0e3dc..bc95ecf3f2d 100644 --- a/java/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql +++ b/java/ql/test/utils/modelgenerator/dataflow/CaptureSourceModels.ql @@ -1,5 +1,6 @@ import java import utils.modelgenerator.internal.CaptureModels +import SourceModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/javascript/ql/lib/change-notes/2025-04-14-fastify-addhook.md b/javascript/ql/lib/change-notes/2025-04-14-fastify-addhook.md new file mode 100644 index 00000000000..a9e754bd56e --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-04-14-fastify-addhook.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added support for the `fastify` `addHook` method. diff --git a/javascript/ql/lib/change-notes/2025-04-30-fastify-all.md b/javascript/ql/lib/change-notes/2025-04-30-fastify-all.md new file mode 100644 index 00000000000..a49092f6ba4 --- /dev/null +++ b/javascript/ql/lib/change-notes/2025-04-30-fastify-all.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Enhanced modeling of the [fastify](https://www.npmjs.com/package/fastify) framework to support the `all` route handler method. diff --git a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll index 974fdd7c0cb..276fe5a0169 100644 --- a/javascript/ql/lib/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/lib/semmle/javascript/ApiGraphs.qll @@ -850,10 +850,10 @@ module API { ) or lbl = Label::promised() and - PromiseFlow::storeStep(rhs, pred, Promises::valueProp()) + SharedTypeTrackingStep::storeStep(rhs, pred, Promises::valueProp()) or lbl = Label::promisedError() and - PromiseFlow::storeStep(rhs, pred, Promises::errorProp()) + SharedTypeTrackingStep::storeStep(rhs, pred, Promises::errorProp()) or // The return-value of a getter G counts as a definition of property G // (Ordinary methods and properties are handled as PropWrite nodes) @@ -1008,11 +1008,11 @@ module API { propDesc = "" ) or - PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and + SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::valueProp()) and lbl = Label::promised() and (propDesc = Promises::valueProp() or propDesc = "") or - PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::errorProp()) and + SharedTypeTrackingStep::loadStep(pred.getALocalUse(), ref, Promises::errorProp()) and lbl = Label::promisedError() and (propDesc = Promises::errorProp() or propDesc = "") } diff --git a/javascript/ql/lib/semmle/javascript/Routing.qll b/javascript/ql/lib/semmle/javascript/Routing.qll index 29700a255d6..530322a2d2c 100644 --- a/javascript/ql/lib/semmle/javascript/Routing.qll +++ b/javascript/ql/lib/semmle/javascript/Routing.qll @@ -139,6 +139,8 @@ module Routing { predicate mayResumeDispatch() { this.getLastChild().mayResumeDispatch() or + isInMiddlewareSetup(this) + or exists(this.(RouteHandler).getAContinuationInvocation()) or // Leaf nodes that aren't functions are assumed to invoke their continuation @@ -155,6 +157,8 @@ module Routing { predicate definitelyResumesDispatch() { this.getLastChild().definitelyResumesDispatch() or + isInMiddlewareSetup(this) + or exists(this.(RouteHandler).getAContinuationInvocation()) or this instanceof MkRouter @@ -325,6 +329,19 @@ module Routing { DataFlow::Node getValueImplicitlyStoredInAccessPath(int n, string path) { none() } } + /** + * Holds if `node` is installed at a route handler that is declared to be a middleware setup, + * and is therefore assume to resume dispatch. + */ + private predicate isInMiddlewareSetup(Node node) { + exists(RouteSetup::Range range | + node = getRouteSetupNode(range) and + range.isMiddlewareSetup() + ) + or + isInMiddlewareSetup(node.getParent()) + } + /** Holds if `pred` and `succ` are adjacent siblings and `succ` is installed after `pred`. */ private predicate areSiblings(Node pred, Node succ) { exists(ValueNode::Range base, int n | @@ -612,6 +629,20 @@ module Routing { * Holds if this route setup targets `router` and occurs at the given `cfgNode`. */ abstract predicate isInstalledAt(Router::Range router, ControlFlowNode cfgNode); + + /** + * Holds if this is a middleware setup, meaning dispatch will resume after the + * route handlers in this route setup have completed (usually meaning that they have returned a promise, which has resolved). + * + * This should only be overridden when the route setup itself determines whether subsequent + * route handlers are invoked afterwards. + * - For Express-like libraries, the route _handler_ determines whether to resume dispatch, + * based on whether the `next` callback is invoked. For such libraries, do not override `isMiddlewareSetup`. + * - For Fastify-like libraries, the route _setup_ determines whether to resume dispatch. + * For example, `.addHook()` will resume dispatch whereas `.get()` will not. `isMiddlewareSetup()` should thus + * hold for `.addHook()` but not for `.get()` calls. + */ + predicate isMiddlewareSetup() { none() } } /** @@ -892,10 +923,14 @@ module Routing { * based on `Node::Range::getValueAtAccessPath`. */ private DataFlow::Node getAnAccessPathRhs(Node base, int n, string path) { - // Assigned in the body of a route handler function, whi + // Assigned in the body of a route handler function, which is a middleware exists(RouteHandler handler | base = handler | result = AccessPath::getAnAssignmentTo(handler.getParameter(n).ref(), path) and - exists(handler.getAContinuationInvocation()) + ( + exists(handler.getAContinuationInvocation()) + or + isInMiddlewareSetup(handler) + ) ) or // Implicit assignment contributed by framework model diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll index 2b8d6287d78..dafc38ca857 100644 --- a/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll +++ b/javascript/ql/lib/semmle/javascript/frameworks/Fastify.qll @@ -138,7 +138,8 @@ module Fastify { RouteSetup() { this = server(server).getAMethodCall(methodName) and - methodName = ["route", "get", "head", "post", "put", "delete", "options", "patch"] + methodName = + ["route", "get", "head", "post", "put", "delete", "options", "patch", "addHook", "all"] } override DataFlow::SourceNode getARouteHandler() { @@ -164,13 +165,19 @@ module Fastify { private class ShorthandRoutingTreeSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup { - ShorthandRoutingTreeSetup() { not this.getMethodName() = "route" } + ShorthandRoutingTreeSetup() { not this.getMethodName() = ["route", "addHook"] } override string getRelativePath() { result = this.getArgument(0).getStringValue() } override Http::RequestMethodName getHttpMethod() { result = this.getMethodName().toUpperCase() } } + private class AddHookRouteSetup extends Routing::RouteSetup::MethodCall instanceof RouteSetup { + AddHookRouteSetup() { this.getMethodName() = "addHook" } + + override predicate isMiddlewareSetup() { any() } + } + /** Gets the name of the `n`th handler function that can be installed a route setup, in order of execution. */ private string getNthHandlerName(int n) { result = @@ -322,7 +329,11 @@ module Fastify { ResponseSendArgument() { this = rh.getAResponseSource().ref().getAMethodCall("send").getArgument(0) or - this = rh.(DataFlow::FunctionNode).getAReturn() + exists(RouteSetup setup | + rh = setup.getARouteHandler() and + this = rh.(DataFlow::FunctionNode).getAReturn() and + setup.getMethodName() != "addHook" + ) } override RouteHandler getRouteHandler() { result = rh } diff --git a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll index 33299a3f5c0..1122c38320a 100644 --- a/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll +++ b/javascript/ql/lib/semmle/javascript/internal/flow_summaries/Promises.qll @@ -4,6 +4,7 @@ private import javascript private import semmle.javascript.dataflow.FlowSummary +private import semmle.javascript.dataflow.TypeTracking private import FlowSummaryUtil DataFlow::SourceNode promiseConstructorRef() { @@ -211,12 +212,57 @@ private class PromiseReject extends SummarizedCallable { } } +/** + * A call to `Promise.all()`. + */ +class PromiseAllCall extends DataFlow::CallNode { + PromiseAllCall() { this = promiseConstructorRef().getAMemberCall("all") } + + /** Gets the source of the input array */ + DataFlow::ArrayCreationNode getInputArray() { result = this.getArgument(0).getALocalSource() } + + /** Gets the `n`th element of the input array */ + DataFlow::Node getNthInput(int n) { result = this.getInputArray().getElement(n) } + + /** Gets a reference to the output array. */ + DataFlow::SourceNode getOutputArray() { + exists(AwaitExpr await | + this.flowsToExpr(await.getOperand()) and + result = await.flow() + ) + or + result = this.getAMethodCall("then").getCallback(0).getParameter(0) + } + + /** Gets the `n`th output */ + DataFlow::SourceNode getNthOutput(int n) { + exists(string prop | + result = this.getOutputArray().getAPropertyRead(prop) and + n = prop.toInt() + ) + } +} + +/** + * Helps type-tracking simple uses of `Promise.all()` such as `const [a, b] = await Promise.all([x, y])`. + * + * Due to limited access path depth, type tracking can't track things that are in a promise and an array + * at once. This generates a step directly from the input array to the output array. + */ +private class PromiseAllStep extends SharedTypeTrackingStep { + override predicate loadStep(DataFlow::Node node1, DataFlow::Node node2, string prop) { + exists(PromiseAllCall call, int n | + node1 = call.getNthInput(n) and + node2 = call.getNthOutput(n) and + prop = Promises::valueProp() + ) + } +} + private class PromiseAll extends SummarizedCallable { PromiseAll() { this = "Promise.all()" } - override DataFlow::InvokeNode getACallSimple() { - result = promiseConstructorRef().getAMemberCall("all") - } + override DataFlow::InvokeNode getACallSimple() { result instanceof PromiseAllCall } override predicate propagatesFlow(string input, string output, boolean preservesValue) { preservesValue = true and diff --git a/javascript/ql/src/Declarations/IneffectiveParameterType.ql b/javascript/ql/src/Declarations/IneffectiveParameterType.ql index da8d610c974..18899bd0c4e 100644 --- a/javascript/ql/src/Declarations/IneffectiveParameterType.ql +++ b/javascript/ql/src/Declarations/IneffectiveParameterType.ql @@ -7,6 +7,7 @@ * @precision high * @tags correctness * typescript + * quality */ import javascript diff --git a/javascript/ql/src/Electron/DisablingWebSecurity.ql b/javascript/ql/src/Electron/DisablingWebSecurity.ql index a2b0c0a8a01..392d8fb7322 100644 --- a/javascript/ql/src/Electron/DisablingWebSecurity.ql +++ b/javascript/ql/src/Electron/DisablingWebSecurity.ql @@ -7,7 +7,7 @@ * @precision very-high * @tags security * frameworks/electron - * external/cwe/cwe-79 + * external/cwe/cwe-079 * @id js/disabling-electron-websecurity */ diff --git a/javascript/ql/src/Expressions/MissingAwait.ql b/javascript/ql/src/Expressions/MissingAwait.ql index 08b6c7af0bb..d97c006a7bd 100644 --- a/javascript/ql/src/Expressions/MissingAwait.ql +++ b/javascript/ql/src/Expressions/MissingAwait.ql @@ -5,6 +5,7 @@ * @problem.severity warning * @id js/missing-await * @tags correctness + * quality * @precision high */ diff --git a/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql b/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql index fd3914c9023..fd493a247a4 100644 --- a/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql +++ b/javascript/ql/src/LanguageFeatures/SpuriousArguments.ql @@ -7,6 +7,7 @@ * @tags maintainability * correctness * language-features + * quality * external/cwe/cwe-685 * @precision very-high */ diff --git a/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql b/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql index 04756158f55..1d063534903 100644 --- a/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql +++ b/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql @@ -6,6 +6,7 @@ * @id js/regex/always-matches * @tags correctness * regular-expressions + * quality * @precision high */ diff --git a/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql b/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql index 045edc172f3..9f811c85c97 100644 --- a/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql +++ b/javascript/ql/src/Security/CWE-020/ExternalAPIsUsedWithUntrustedData.ql @@ -5,7 +5,7 @@ * to it. * @id js/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import javascript diff --git a/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql b/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql index 30931a6a582..1fd1df14887 100644 --- a/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql +++ b/javascript/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import javascript diff --git a/javascript/ql/src/change-notes/2025-04-30-promise-all.md b/javascript/ql/src/change-notes/2025-04-30-promise-all.md new file mode 100644 index 00000000000..a50e31ea01d --- /dev/null +++ b/javascript/ql/src/change-notes/2025-04-30-promise-all.md @@ -0,0 +1,5 @@ +--- +category: minorAnalysis +--- +* Type information is now propagated more precisely through `Promise.all()` calls, + leading to more resolved calls and more sources and sinks being detected. diff --git a/javascript/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/javascript/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..01e53adf5f5 --- /dev/null +++ b/javascript/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,8 @@ +--- +category: queryMetadata +--- + +* The tag `external/cwe/cwe-79` has been removed from `js/disabling-electron-websecurity` and the tag `external/cwe/cwe-079` has been added. +* The tag `external/cwe/cwe-20` has been removed from `js/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `js/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `js/untrusted-data-to-external-api-more-sources` and the tag `external/cwe/cwe-020` has been added. diff --git a/javascript/ql/src/codeql-suites/javascript-code-quality.qls b/javascript/ql/src/codeql-suites/javascript-code-quality.qls index 5570d91c4f7..2074f9378cf 100644 --- a/javascript/ql/src/codeql-suites/javascript-code-quality.qls +++ b/javascript/ql/src/codeql-suites/javascript-code-quality.qls @@ -1,8 +1,3 @@ - queries: . -- include: - id: - - js/missing-await - - js/regex/always-matches - - js/superfluous-trailing-arguments - - js/useless-expression - - js/ineffective-parameter-type \ No newline at end of file +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql b/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql index 4bf06b54447..fa7f313e9e5 100644 --- a/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql +++ b/javascript/ql/src/experimental/heuristics/ql/src/Security/CWE-020/UntrustedDataToExternalAPI.ql @@ -7,7 +7,7 @@ * @problem.severity error * @security-severity 7.8 * @tags experimental - * security external/cwe/cwe-20 + * security external/cwe/cwe-020 */ import javascript diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected index a81b9dbcce0..4d54adb2724 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/CodeInjection.expected @@ -27,6 +27,40 @@ | express.js:20:34:20:38 | taint | express.js:19:17:19:35 | req.param("wobble") | express.js:20:34:20:38 | taint | This code execution depends on a $@. | express.js:19:17:19:35 | req.param("wobble") | user-provided value | | express.js:36:15:36:19 | taint | express.js:27:17:27:35 | req.param("wobble") | express.js:36:15:36:19 | taint | This code execution depends on a $@. | express.js:27:17:27:35 | req.param("wobble") | user-provided value | | express.js:43:10:43:12 | msg | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | This code execution depends on a $@. | express.js:42:30:42:32 | msg | user-provided value | +| fastify.js:5:44:5:52 | userInput | fastify.js:4:21:4:33 | request.query | fastify.js:5:44:5:52 | userInput | This code execution depends on a $@. | fastify.js:4:21:4:33 | request.query | user-provided value | +| fastify.js:5:44:5:52 | userInput | fastify.js:4:21:4:43 | request ... Request | fastify.js:5:44:5:52 | userInput | This code execution depends on a $@. | fastify.js:4:21:4:43 | request ... Request | user-provided value | +| fastify.js:10:44:10:52 | userInput | fastify.js:9:21:9:33 | request.query | fastify.js:10:44:10:52 | userInput | This code execution depends on a $@. | fastify.js:9:21:9:33 | request.query | user-provided value | +| fastify.js:10:44:10:52 | userInput | fastify.js:9:21:9:40 | request.query.onSend | fastify.js:10:44:10:52 | userInput | This code execution depends on a $@. | fastify.js:9:21:9:40 | request.query.onSend | user-provided value | +| fastify.js:16:44:16:52 | userInput | fastify.js:15:21:15:33 | request.query | fastify.js:16:44:16:52 | userInput | This code execution depends on a $@. | fastify.js:15:21:15:33 | request.query | user-provided value | +| fastify.js:16:44:16:52 | userInput | fastify.js:15:21:15:44 | request ... Parsing | fastify.js:16:44:16:52 | userInput | This code execution depends on a $@. | fastify.js:15:21:15:44 | request ... Parsing | user-provided value | +| fastify.js:22:44:22:52 | userInput | fastify.js:21:21:21:33 | request.query | fastify.js:22:44:22:52 | userInput | This code execution depends on a $@. | fastify.js:21:21:21:33 | request.query | user-provided value | +| fastify.js:22:44:22:52 | userInput | fastify.js:21:21:21:47 | request ... idation | fastify.js:22:44:22:52 | userInput | This code execution depends on a $@. | fastify.js:21:21:21:47 | request ... idation | user-provided value | +| fastify.js:27:44:27:52 | userInput | fastify.js:26:21:26:33 | request.query | fastify.js:27:44:27:52 | userInput | This code execution depends on a $@. | fastify.js:26:21:26:33 | request.query | user-provided value | +| fastify.js:27:44:27:52 | userInput | fastify.js:26:21:26:44 | request ... Handler | fastify.js:27:44:27:52 | userInput | This code execution depends on a $@. | fastify.js:26:21:26:44 | request ... Handler | user-provided value | +| fastify.js:32:44:32:52 | userInput | fastify.js:31:21:31:33 | request.query | fastify.js:32:44:32:52 | userInput | This code execution depends on a $@. | fastify.js:31:21:31:33 | request.query | user-provided value | +| fastify.js:32:44:32:52 | userInput | fastify.js:31:21:31:50 | request ... ization | fastify.js:32:44:32:52 | userInput | This code execution depends on a $@. | fastify.js:31:21:31:50 | request ... ization | user-provided value | +| fastify.js:38:44:38:52 | userInput | fastify.js:37:21:37:33 | request.query | fastify.js:38:44:38:52 | userInput | This code execution depends on a $@. | fastify.js:37:21:37:33 | request.query | user-provided value | +| fastify.js:38:44:38:52 | userInput | fastify.js:37:21:37:44 | request ... esponse | fastify.js:38:44:38:52 | userInput | This code execution depends on a $@. | fastify.js:37:21:37:44 | request ... esponse | user-provided value | +| fastify.js:43:44:43:52 | userInput | fastify.js:42:21:42:33 | request.query | fastify.js:43:44:43:52 | userInput | This code execution depends on a $@. | fastify.js:42:21:42:33 | request.query | user-provided value | +| fastify.js:43:44:43:52 | userInput | fastify.js:42:21:42:41 | request ... onError | fastify.js:43:44:43:52 | userInput | This code execution depends on a $@. | fastify.js:42:21:42:41 | request ... onError | user-provided value | +| fastify.js:48:44:48:52 | userInput | fastify.js:47:21:47:33 | request.query | fastify.js:48:44:48:52 | userInput | This code execution depends on a $@. | fastify.js:47:21:47:33 | request.query | user-provided value | +| fastify.js:48:44:48:52 | userInput | fastify.js:47:21:47:43 | request ... Timeout | fastify.js:48:44:48:52 | userInput | This code execution depends on a $@. | fastify.js:47:21:47:43 | request ... Timeout | user-provided value | +| fastify.js:53:46:53:54 | userInput | fastify.js:52:23:52:35 | request.query | fastify.js:53:46:53:54 | userInput | This code execution depends on a $@. | fastify.js:52:23:52:35 | request.query | user-provided value | +| fastify.js:53:46:53:54 | userInput | fastify.js:52:23:52:50 | request ... stAbort | fastify.js:53:46:53:54 | userInput | This code execution depends on a $@. | fastify.js:52:23:52:50 | request ... stAbort | user-provided value | +| fastify.js:58:44:58:52 | userInput | fastify.js:57:21:57:33 | request.query | fastify.js:58:44:58:52 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:33 | request.query | user-provided value | +| fastify.js:58:44:58:52 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:58:44:58:52 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | +| fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:33 | request.query | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:33 | request.query | user-provided value | +| fastify.js:59:23:59:31 | userInput | fastify.js:57:21:57:39 | request.query.input | fastify.js:59:23:59:31 | userInput | This code execution depends on a $@. | fastify.js:57:21:57:39 | request.query.input | user-provided value | +| fastify.js:71:34:71:51 | request.storedCode | fastify.js:66:24:66:36 | request.query | fastify.js:71:34:71:51 | request.storedCode | This code execution depends on a $@. | fastify.js:66:24:66:36 | request.query | user-provided value | +| fastify.js:71:34:71:51 | request.storedCode | fastify.js:66:24:66:47 | request ... redCode | fastify.js:71:34:71:51 | request.storedCode | This code execution depends on a $@. | fastify.js:66:24:66:47 | request ... redCode | user-provided value | +| fastify.js:84:30:84:43 | reply.userCode | fastify.js:79:20:79:32 | request.query | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:79:20:79:32 | request.query | user-provided value | +| fastify.js:84:30:84:43 | reply.userCode | fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | This code execution depends on a $@. | fastify.js:79:20:79:42 | request ... plyCode | user-provided value | +| fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:41 | request.query | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:41 | request.query | user-provided value | +| fastify.js:99:30:99:52 | reply.l ... tedCode | fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:51 | request ... plyCode | user-provided value | +| fastify.js:107:23:107:31 | userInput | fastify.js:106:21:106:33 | request.query | fastify.js:107:23:107:31 | userInput | This code execution depends on a $@. | fastify.js:106:21:106:33 | request.query | user-provided value | +| fastify.js:107:23:107:31 | userInput | fastify.js:106:21:106:38 | request.query.code | fastify.js:107:23:107:31 | userInput | This code execution depends on a $@. | fastify.js:106:21:106:38 | request.query.code | user-provided value | +| fastify.js:108:28:108:50 | reply.l ... tedCode | fastify.js:94:29:94:41 | request.query | fastify.js:108:28:108:50 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:41 | request.query | user-provided value | +| fastify.js:108:28:108:50 | reply.l ... tedCode | fastify.js:94:29:94:51 | request ... plyCode | fastify.js:108:28:108:50 | reply.l ... tedCode | This code execution depends on a $@. | fastify.js:94:29:94:51 | request ... plyCode | user-provided value | | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | module.js:9:16:9:29 | req.query.code | This code execution depends on a $@. | module.js:9:16:9:29 | req.query.code | user-provided value | | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | module.js:11:17:11:30 | req.query.code | This code execution depends on a $@. | module.js:11:17:11:30 | req.query.code | user-provided value | | react-native.js:8:32:8:38 | tainted | react-native.js:7:17:7:33 | req.param("code") | react-native.js:8:32:8:38 | tainted | This code execution depends on a $@. | react-native.js:7:17:7:33 | req.param("code") | user-provided value | @@ -75,6 +109,50 @@ edges | express.js:27:9:27:35 | taint | express.js:36:15:36:19 | taint | provenance | | | express.js:27:17:27:35 | req.param("wobble") | express.js:27:9:27:35 | taint | provenance | | | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | provenance | | +| fastify.js:4:9:4:43 | userInput | fastify.js:5:44:5:52 | userInput | provenance | | +| fastify.js:4:21:4:33 | request.query | fastify.js:4:9:4:43 | userInput | provenance | | +| fastify.js:4:21:4:43 | request ... Request | fastify.js:4:9:4:43 | userInput | provenance | | +| fastify.js:9:9:9:40 | userInput | fastify.js:10:44:10:52 | userInput | provenance | | +| fastify.js:9:21:9:33 | request.query | fastify.js:9:9:9:40 | userInput | provenance | | +| fastify.js:9:21:9:40 | request.query.onSend | fastify.js:9:9:9:40 | userInput | provenance | | +| fastify.js:15:9:15:44 | userInput | fastify.js:16:44:16:52 | userInput | provenance | | +| fastify.js:15:21:15:33 | request.query | fastify.js:15:9:15:44 | userInput | provenance | | +| fastify.js:15:21:15:44 | request ... Parsing | fastify.js:15:9:15:44 | userInput | provenance | | +| fastify.js:21:9:21:47 | userInput | fastify.js:22:44:22:52 | userInput | provenance | | +| fastify.js:21:21:21:33 | request.query | fastify.js:21:9:21:47 | userInput | provenance | | +| fastify.js:21:21:21:47 | request ... idation | fastify.js:21:9:21:47 | userInput | provenance | | +| fastify.js:26:9:26:44 | userInput | fastify.js:27:44:27:52 | userInput | provenance | | +| fastify.js:26:21:26:33 | request.query | fastify.js:26:9:26:44 | userInput | provenance | | +| fastify.js:26:21:26:44 | request ... Handler | fastify.js:26:9:26:44 | userInput | provenance | | +| fastify.js:31:9:31:50 | userInput | fastify.js:32:44:32:52 | userInput | provenance | | +| fastify.js:31:21:31:33 | request.query | fastify.js:31:9:31:50 | userInput | provenance | | +| fastify.js:31:21:31:50 | request ... ization | fastify.js:31:9:31:50 | userInput | provenance | | +| fastify.js:37:9:37:44 | userInput | fastify.js:38:44:38:52 | userInput | provenance | | +| fastify.js:37:21:37:33 | request.query | fastify.js:37:9:37:44 | userInput | provenance | | +| fastify.js:37:21:37:44 | request ... esponse | fastify.js:37:9:37:44 | userInput | provenance | | +| fastify.js:42:9:42:41 | userInput | fastify.js:43:44:43:52 | userInput | provenance | | +| fastify.js:42:21:42:33 | request.query | fastify.js:42:9:42:41 | userInput | provenance | | +| fastify.js:42:21:42:41 | request ... onError | fastify.js:42:9:42:41 | userInput | provenance | | +| fastify.js:47:9:47:43 | userInput | fastify.js:48:44:48:52 | userInput | provenance | | +| fastify.js:47:21:47:33 | request.query | fastify.js:47:9:47:43 | userInput | provenance | | +| fastify.js:47:21:47:43 | request ... Timeout | fastify.js:47:9:47:43 | userInput | provenance | | +| fastify.js:52:11:52:50 | userInput | fastify.js:53:46:53:54 | userInput | provenance | | +| fastify.js:52:23:52:35 | request.query | fastify.js:52:11:52:50 | userInput | provenance | | +| fastify.js:52:23:52:50 | request ... stAbort | fastify.js:52:11:52:50 | userInput | provenance | | +| fastify.js:57:9:57:39 | userInput | fastify.js:58:44:58:52 | userInput | provenance | | +| fastify.js:57:9:57:39 | userInput | fastify.js:59:23:59:31 | userInput | provenance | | +| fastify.js:57:21:57:33 | request.query | fastify.js:57:9:57:39 | userInput | provenance | | +| fastify.js:57:21:57:39 | request.query.input | fastify.js:57:9:57:39 | userInput | provenance | | +| fastify.js:66:24:66:36 | request.query | fastify.js:66:24:66:47 | request ... redCode | provenance | | +| fastify.js:66:24:66:47 | request ... redCode | fastify.js:71:34:71:51 | request.storedCode | provenance | | +| fastify.js:79:20:79:32 | request.query | fastify.js:79:20:79:42 | request ... plyCode | provenance | | +| fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | provenance | | +| fastify.js:94:29:94:41 | request.query | fastify.js:94:29:94:51 | request ... plyCode | provenance | | +| fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | provenance | | +| fastify.js:94:29:94:51 | request ... plyCode | fastify.js:108:28:108:50 | reply.l ... tedCode | provenance | | +| fastify.js:106:9:106:38 | userInput | fastify.js:107:23:107:31 | userInput | provenance | | +| fastify.js:106:21:106:33 | request.query | fastify.js:106:9:106:38 | userInput | provenance | | +| fastify.js:106:21:106:38 | request.query.code | fastify.js:106:9:106:38 | userInput | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:8:32:8:38 | tainted | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:10:23:10:29 | tainted | provenance | | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | provenance | | @@ -144,6 +222,65 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| fastify.js:4:9:4:43 | userInput | semmle.label | userInput | +| fastify.js:4:21:4:33 | request.query | semmle.label | request.query | +| fastify.js:4:21:4:43 | request ... Request | semmle.label | request ... Request | +| fastify.js:5:44:5:52 | userInput | semmle.label | userInput | +| fastify.js:9:9:9:40 | userInput | semmle.label | userInput | +| fastify.js:9:21:9:33 | request.query | semmle.label | request.query | +| fastify.js:9:21:9:40 | request.query.onSend | semmle.label | request.query.onSend | +| fastify.js:10:44:10:52 | userInput | semmle.label | userInput | +| fastify.js:15:9:15:44 | userInput | semmle.label | userInput | +| fastify.js:15:21:15:33 | request.query | semmle.label | request.query | +| fastify.js:15:21:15:44 | request ... Parsing | semmle.label | request ... Parsing | +| fastify.js:16:44:16:52 | userInput | semmle.label | userInput | +| fastify.js:21:9:21:47 | userInput | semmle.label | userInput | +| fastify.js:21:21:21:33 | request.query | semmle.label | request.query | +| fastify.js:21:21:21:47 | request ... idation | semmle.label | request ... idation | +| fastify.js:22:44:22:52 | userInput | semmle.label | userInput | +| fastify.js:26:9:26:44 | userInput | semmle.label | userInput | +| fastify.js:26:21:26:33 | request.query | semmle.label | request.query | +| fastify.js:26:21:26:44 | request ... Handler | semmle.label | request ... Handler | +| fastify.js:27:44:27:52 | userInput | semmle.label | userInput | +| fastify.js:31:9:31:50 | userInput | semmle.label | userInput | +| fastify.js:31:21:31:33 | request.query | semmle.label | request.query | +| fastify.js:31:21:31:50 | request ... ization | semmle.label | request ... ization | +| fastify.js:32:44:32:52 | userInput | semmle.label | userInput | +| fastify.js:37:9:37:44 | userInput | semmle.label | userInput | +| fastify.js:37:21:37:33 | request.query | semmle.label | request.query | +| fastify.js:37:21:37:44 | request ... esponse | semmle.label | request ... esponse | +| fastify.js:38:44:38:52 | userInput | semmle.label | userInput | +| fastify.js:42:9:42:41 | userInput | semmle.label | userInput | +| fastify.js:42:21:42:33 | request.query | semmle.label | request.query | +| fastify.js:42:21:42:41 | request ... onError | semmle.label | request ... onError | +| fastify.js:43:44:43:52 | userInput | semmle.label | userInput | +| fastify.js:47:9:47:43 | userInput | semmle.label | userInput | +| fastify.js:47:21:47:33 | request.query | semmle.label | request.query | +| fastify.js:47:21:47:43 | request ... Timeout | semmle.label | request ... Timeout | +| fastify.js:48:44:48:52 | userInput | semmle.label | userInput | +| fastify.js:52:11:52:50 | userInput | semmle.label | userInput | +| fastify.js:52:23:52:35 | request.query | semmle.label | request.query | +| fastify.js:52:23:52:50 | request ... stAbort | semmle.label | request ... stAbort | +| fastify.js:53:46:53:54 | userInput | semmle.label | userInput | +| fastify.js:57:9:57:39 | userInput | semmle.label | userInput | +| fastify.js:57:21:57:33 | request.query | semmle.label | request.query | +| fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | +| fastify.js:58:44:58:52 | userInput | semmle.label | userInput | +| fastify.js:59:23:59:31 | userInput | semmle.label | userInput | +| fastify.js:66:24:66:36 | request.query | semmle.label | request.query | +| fastify.js:66:24:66:47 | request ... redCode | semmle.label | request ... redCode | +| fastify.js:71:34:71:51 | request.storedCode | semmle.label | request.storedCode | +| fastify.js:79:20:79:32 | request.query | semmle.label | request.query | +| fastify.js:79:20:79:42 | request ... plyCode | semmle.label | request ... plyCode | +| fastify.js:84:30:84:43 | reply.userCode | semmle.label | reply.userCode | +| fastify.js:94:29:94:41 | request.query | semmle.label | request.query | +| fastify.js:94:29:94:51 | request ... plyCode | semmle.label | request ... plyCode | +| fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | +| fastify.js:106:9:106:38 | userInput | semmle.label | userInput | +| fastify.js:106:21:106:33 | request.query | semmle.label | request.query | +| fastify.js:106:21:106:38 | request.query.code | semmle.label | request.query.code | +| fastify.js:107:23:107:31 | userInput | semmle.label | userInput | +| fastify.js:108:28:108:50 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected index ba973943e12..a1c8354ecf7 100644 --- a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/HeuristicSourceCodeInjection.expected @@ -11,6 +11,50 @@ edges | express.js:27:9:27:35 | taint | express.js:36:15:36:19 | taint | provenance | | | express.js:27:17:27:35 | req.param("wobble") | express.js:27:9:27:35 | taint | provenance | | | express.js:42:30:42:32 | msg | express.js:43:10:43:12 | msg | provenance | | +| fastify.js:4:9:4:43 | userInput | fastify.js:5:44:5:52 | userInput | provenance | | +| fastify.js:4:21:4:33 | request.query | fastify.js:4:9:4:43 | userInput | provenance | | +| fastify.js:4:21:4:43 | request ... Request | fastify.js:4:9:4:43 | userInput | provenance | | +| fastify.js:9:9:9:40 | userInput | fastify.js:10:44:10:52 | userInput | provenance | | +| fastify.js:9:21:9:33 | request.query | fastify.js:9:9:9:40 | userInput | provenance | | +| fastify.js:9:21:9:40 | request.query.onSend | fastify.js:9:9:9:40 | userInput | provenance | | +| fastify.js:15:9:15:44 | userInput | fastify.js:16:44:16:52 | userInput | provenance | | +| fastify.js:15:21:15:33 | request.query | fastify.js:15:9:15:44 | userInput | provenance | | +| fastify.js:15:21:15:44 | request ... Parsing | fastify.js:15:9:15:44 | userInput | provenance | | +| fastify.js:21:9:21:47 | userInput | fastify.js:22:44:22:52 | userInput | provenance | | +| fastify.js:21:21:21:33 | request.query | fastify.js:21:9:21:47 | userInput | provenance | | +| fastify.js:21:21:21:47 | request ... idation | fastify.js:21:9:21:47 | userInput | provenance | | +| fastify.js:26:9:26:44 | userInput | fastify.js:27:44:27:52 | userInput | provenance | | +| fastify.js:26:21:26:33 | request.query | fastify.js:26:9:26:44 | userInput | provenance | | +| fastify.js:26:21:26:44 | request ... Handler | fastify.js:26:9:26:44 | userInput | provenance | | +| fastify.js:31:9:31:50 | userInput | fastify.js:32:44:32:52 | userInput | provenance | | +| fastify.js:31:21:31:33 | request.query | fastify.js:31:9:31:50 | userInput | provenance | | +| fastify.js:31:21:31:50 | request ... ization | fastify.js:31:9:31:50 | userInput | provenance | | +| fastify.js:37:9:37:44 | userInput | fastify.js:38:44:38:52 | userInput | provenance | | +| fastify.js:37:21:37:33 | request.query | fastify.js:37:9:37:44 | userInput | provenance | | +| fastify.js:37:21:37:44 | request ... esponse | fastify.js:37:9:37:44 | userInput | provenance | | +| fastify.js:42:9:42:41 | userInput | fastify.js:43:44:43:52 | userInput | provenance | | +| fastify.js:42:21:42:33 | request.query | fastify.js:42:9:42:41 | userInput | provenance | | +| fastify.js:42:21:42:41 | request ... onError | fastify.js:42:9:42:41 | userInput | provenance | | +| fastify.js:47:9:47:43 | userInput | fastify.js:48:44:48:52 | userInput | provenance | | +| fastify.js:47:21:47:33 | request.query | fastify.js:47:9:47:43 | userInput | provenance | | +| fastify.js:47:21:47:43 | request ... Timeout | fastify.js:47:9:47:43 | userInput | provenance | | +| fastify.js:52:11:52:50 | userInput | fastify.js:53:46:53:54 | userInput | provenance | | +| fastify.js:52:23:52:35 | request.query | fastify.js:52:11:52:50 | userInput | provenance | | +| fastify.js:52:23:52:50 | request ... stAbort | fastify.js:52:11:52:50 | userInput | provenance | | +| fastify.js:57:9:57:39 | userInput | fastify.js:58:44:58:52 | userInput | provenance | | +| fastify.js:57:9:57:39 | userInput | fastify.js:59:23:59:31 | userInput | provenance | | +| fastify.js:57:21:57:33 | request.query | fastify.js:57:9:57:39 | userInput | provenance | | +| fastify.js:57:21:57:39 | request.query.input | fastify.js:57:9:57:39 | userInput | provenance | | +| fastify.js:66:24:66:36 | request.query | fastify.js:66:24:66:47 | request ... redCode | provenance | | +| fastify.js:66:24:66:47 | request ... redCode | fastify.js:71:34:71:51 | request.storedCode | provenance | | +| fastify.js:79:20:79:32 | request.query | fastify.js:79:20:79:42 | request ... plyCode | provenance | | +| fastify.js:79:20:79:42 | request ... plyCode | fastify.js:84:30:84:43 | reply.userCode | provenance | | +| fastify.js:94:29:94:41 | request.query | fastify.js:94:29:94:51 | request ... plyCode | provenance | | +| fastify.js:94:29:94:51 | request ... plyCode | fastify.js:99:30:99:52 | reply.l ... tedCode | provenance | | +| fastify.js:94:29:94:51 | request ... plyCode | fastify.js:108:28:108:50 | reply.l ... tedCode | provenance | | +| fastify.js:106:9:106:38 | userInput | fastify.js:107:23:107:31 | userInput | provenance | | +| fastify.js:106:21:106:33 | request.query | fastify.js:106:9:106:38 | userInput | provenance | | +| fastify.js:106:21:106:38 | request.query.code | fastify.js:106:9:106:38 | userInput | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:8:32:8:38 | tainted | provenance | | | react-native.js:7:7:7:33 | tainted | react-native.js:10:23:10:29 | tainted | provenance | | | react-native.js:7:17:7:33 | req.param("code") | react-native.js:7:7:7:33 | tainted | provenance | | @@ -82,6 +126,65 @@ nodes | express.js:36:15:36:19 | taint | semmle.label | taint | | express.js:42:30:42:32 | msg | semmle.label | msg | | express.js:43:10:43:12 | msg | semmle.label | msg | +| fastify.js:4:9:4:43 | userInput | semmle.label | userInput | +| fastify.js:4:21:4:33 | request.query | semmle.label | request.query | +| fastify.js:4:21:4:43 | request ... Request | semmle.label | request ... Request | +| fastify.js:5:44:5:52 | userInput | semmle.label | userInput | +| fastify.js:9:9:9:40 | userInput | semmle.label | userInput | +| fastify.js:9:21:9:33 | request.query | semmle.label | request.query | +| fastify.js:9:21:9:40 | request.query.onSend | semmle.label | request.query.onSend | +| fastify.js:10:44:10:52 | userInput | semmle.label | userInput | +| fastify.js:15:9:15:44 | userInput | semmle.label | userInput | +| fastify.js:15:21:15:33 | request.query | semmle.label | request.query | +| fastify.js:15:21:15:44 | request ... Parsing | semmle.label | request ... Parsing | +| fastify.js:16:44:16:52 | userInput | semmle.label | userInput | +| fastify.js:21:9:21:47 | userInput | semmle.label | userInput | +| fastify.js:21:21:21:33 | request.query | semmle.label | request.query | +| fastify.js:21:21:21:47 | request ... idation | semmle.label | request ... idation | +| fastify.js:22:44:22:52 | userInput | semmle.label | userInput | +| fastify.js:26:9:26:44 | userInput | semmle.label | userInput | +| fastify.js:26:21:26:33 | request.query | semmle.label | request.query | +| fastify.js:26:21:26:44 | request ... Handler | semmle.label | request ... Handler | +| fastify.js:27:44:27:52 | userInput | semmle.label | userInput | +| fastify.js:31:9:31:50 | userInput | semmle.label | userInput | +| fastify.js:31:21:31:33 | request.query | semmle.label | request.query | +| fastify.js:31:21:31:50 | request ... ization | semmle.label | request ... ization | +| fastify.js:32:44:32:52 | userInput | semmle.label | userInput | +| fastify.js:37:9:37:44 | userInput | semmle.label | userInput | +| fastify.js:37:21:37:33 | request.query | semmle.label | request.query | +| fastify.js:37:21:37:44 | request ... esponse | semmle.label | request ... esponse | +| fastify.js:38:44:38:52 | userInput | semmle.label | userInput | +| fastify.js:42:9:42:41 | userInput | semmle.label | userInput | +| fastify.js:42:21:42:33 | request.query | semmle.label | request.query | +| fastify.js:42:21:42:41 | request ... onError | semmle.label | request ... onError | +| fastify.js:43:44:43:52 | userInput | semmle.label | userInput | +| fastify.js:47:9:47:43 | userInput | semmle.label | userInput | +| fastify.js:47:21:47:33 | request.query | semmle.label | request.query | +| fastify.js:47:21:47:43 | request ... Timeout | semmle.label | request ... Timeout | +| fastify.js:48:44:48:52 | userInput | semmle.label | userInput | +| fastify.js:52:11:52:50 | userInput | semmle.label | userInput | +| fastify.js:52:23:52:35 | request.query | semmle.label | request.query | +| fastify.js:52:23:52:50 | request ... stAbort | semmle.label | request ... stAbort | +| fastify.js:53:46:53:54 | userInput | semmle.label | userInput | +| fastify.js:57:9:57:39 | userInput | semmle.label | userInput | +| fastify.js:57:21:57:33 | request.query | semmle.label | request.query | +| fastify.js:57:21:57:39 | request.query.input | semmle.label | request.query.input | +| fastify.js:58:44:58:52 | userInput | semmle.label | userInput | +| fastify.js:59:23:59:31 | userInput | semmle.label | userInput | +| fastify.js:66:24:66:36 | request.query | semmle.label | request.query | +| fastify.js:66:24:66:47 | request ... redCode | semmle.label | request ... redCode | +| fastify.js:71:34:71:51 | request.storedCode | semmle.label | request.storedCode | +| fastify.js:79:20:79:32 | request.query | semmle.label | request.query | +| fastify.js:79:20:79:42 | request ... plyCode | semmle.label | request ... plyCode | +| fastify.js:84:30:84:43 | reply.userCode | semmle.label | reply.userCode | +| fastify.js:94:29:94:41 | request.query | semmle.label | request.query | +| fastify.js:94:29:94:51 | request ... plyCode | semmle.label | request ... plyCode | +| fastify.js:99:30:99:52 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | +| fastify.js:106:9:106:38 | userInput | semmle.label | userInput | +| fastify.js:106:21:106:33 | request.query | semmle.label | request.query | +| fastify.js:106:21:106:38 | request.query.code | semmle.label | request.query.code | +| fastify.js:107:23:107:31 | userInput | semmle.label | userInput | +| fastify.js:108:28:108:50 | reply.l ... tedCode | semmle.label | reply.l ... tedCode | | module.js:9:16:9:29 | req.query.code | semmle.label | req.query.code | | module.js:11:17:11:30 | req.query.code | semmle.label | req.query.code | | react-native.js:7:7:7:33 | tainted | semmle.label | tainted | diff --git a/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js new file mode 100644 index 00000000000..05dd3f6eb46 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-094/CodeInjection/fastify.js @@ -0,0 +1,110 @@ +const fastify = require('fastify')({ logger: true }); + +fastify.addHook('onRequest', async (request, reply) => { + const userInput = request.query.onRequest; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] +}); + +fastify.addHook('onSend', async (request, reply, payload) => { + const userInput = request.query.onSend; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] + return JSON.stringify({ ...JSON.parse(payload), onSend: request.evalResult }); +}); + +fastify.addHook('preParsing', async (request, reply, payload) => { + const userInput = request.query.preParsing; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] + return payload; +}); + +fastify.addHook('preValidation', async (request, reply) => { + const userInput = request.query.preValidation; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] +}); + +fastify.addHook('preHandler', async (request, reply) => { + const userInput = request.query.preHandler; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] +}); + +fastify.addHook('preSerialization', async (request, reply, payload) => { + const userInput = request.query.preSerialization; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] + return payload; +}); + +fastify.addHook('onResponse', async (request, reply) => { + const userInput = request.query.onResponse; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] +}); + +fastify.addHook('onError', async (request, reply, error) => { + const userInput = request.query.onError; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] +}); + +fastify.addHook('onTimeout', async (request, reply) => { + const userInput = request.query.onTimeout; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] +}); + +fastify.addHook('onRequestAbort', (request, done) => { + const userInput = request.query.onRequestAbort; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] +}); + +fastify.get('/dangerous', async (request, reply) => { + const userInput = request.query.input; // $ Source[js/code-injection] + if (userInput) request.evalResult = eval(userInput); // $ Alert[js/code-injection] + const result = eval(userInput); // $ Alert[js/code-injection] + return { result }; +}); + + +// Store user input in request object +fastify.addHook('preHandler', async (request, reply) => { + request.storedCode = request.query.storedCode; // $ Source[js/code-injection] +}); +fastify.get('/flow-through-request', async (request, reply) => { + // Use the stored code from previous hook + if (request.storedCode) { + const evaluatedResult = eval(request.storedCode); // $ Alert[js/code-injection] + return { result: evaluatedResult }; + } + return { result: null }; +}); + +// Store user input in reply object +fastify.addHook('onRequest', async (request, reply) => { + reply.userCode = request.query.replyCode; // $ Source[js/code-injection] +}); +fastify.get('/flow-through-reply', async (request, reply) => { + // Use the code stored in reply object + if (reply.userCode) { + const replyResult = eval(reply.userCode); // $ Alert[js/code-injection] + return { result: replyResult }; + } + return { result: null }; +}); + + +// Store user input in reply object +fastify.addHook('onRequest', async (request, reply) => { + reply.locals = reply.locals || {}; + reply.locals.nestedCode = request.query.replyCode; // $ Source[js/code-injection] +}); +fastify.get('/flow-through-reply', async (request, reply) => { + // Use the code stored in reply object + if (reply.locals && reply.locals.nestedCode) { + const replyResult = eval(reply.locals.nestedCode); // $ Alert[js/code-injection] + return { result: replyResult }; + } + return { result: null }; +}); + +fastify.all('/eval', async (request, reply) => { + const userInput = request.query.code; // $ Source[js/code-injection] + const result = eval(userInput); // $ Alert[js/code-injection] + const replyResult = eval(reply.locals.nestedCode); // $ Alert[js/code-injection] + return { method: request.method, result }; +}); diff --git a/misc/codegen/generators/qlgen.py b/misc/codegen/generators/qlgen.py index eefcad3e943..7e898135d01 100755 --- a/misc/codegen/generators/qlgen.py +++ b/misc/codegen/generators/qlgen.py @@ -24,6 +24,7 @@ Moreover in the test directory for each in it will generate bene import logging import pathlib import re +import shutil import subprocess import typing import itertools @@ -257,6 +258,15 @@ def format(codeql, files): if not ql_files: return format_cmd = [codeql, "query", "format", "--in-place", "--"] + ql_files + if "/" in codeql or "\\" in codeql: + if not pathlib.Path(codeql).exists(): + raise FormatError(f"Provided CodeQL binary `{codeql}` does not exist") + else: + codeql_path = shutil.which(codeql) + if not codeql_path: + raise FormatError( + f"`{codeql}` not found in PATH. Either install it, or pass `-- --codeql-binary` with a full path") + codeql = codeql_path res = subprocess.run(format_cmd, stderr=subprocess.PIPE, text=True) if res.returncode: for line in res.stderr.splitlines(): diff --git a/misc/codegen/test/test_qlgen.py b/misc/codegen/test/test_qlgen.py index 01dee251999..75e587fbd5e 100644 --- a/misc/codegen/test/test_qlgen.py +++ b/misc/codegen/test/test_qlgen.py @@ -52,6 +52,8 @@ def qlgen_opts(opts): opts.ql_format = True opts.root_dir = paths.root_dir opts.force = False + opts.codeql_binary = "./my_fake_codeql" + pathlib.Path(opts.codeql_binary).touch() return opts @@ -499,7 +501,6 @@ def test_class_dir_imports(generate_import_list): def test_format(opts, generate, render_manager, run_mock): - opts.codeql_binary = "my_fake_codeql" run_mock.return_value.stderr = "some\nlines\n" render_manager.written = [ pathlib.Path("x", "foo.ql"), @@ -508,13 +509,12 @@ def test_format(opts, generate, render_manager, run_mock): ] generate([schema.Class('A')]) assert run_mock.mock_calls == [ - mock.call(["my_fake_codeql", "query", "format", "--in-place", "--", "x/foo.ql", "bar.qll"], + mock.call([opts.codeql_binary, "query", "format", "--in-place", "--", "x/foo.ql", "bar.qll"], stderr=subprocess.PIPE, text=True), ] def test_format_error(opts, generate, render_manager, run_mock): - opts.codeql_binary = "my_fake_codeql" run_mock.return_value.stderr = "some\nlines\n" run_mock.return_value.returncode = 1 render_manager.written = [ @@ -526,6 +526,24 @@ def test_format_error(opts, generate, render_manager, run_mock): generate([schema.Class('A')]) +def test_format_no_codeql(opts, generate, render_manager, run_mock): + pathlib.Path(opts.codeql_binary).unlink() + render_manager.written = [ + pathlib.Path("bar.qll"), + ] + with pytest.raises(qlgen.FormatError): + generate([schema.Class('A')]) + + +def test_format_no_codeql_in_path(opts, generate, render_manager, run_mock): + opts.codeql_binary = "my_fake_codeql" + render_manager.written = [ + pathlib.Path("bar.qll"), + ] + with pytest.raises(qlgen.FormatError): + generate([schema.Class('A')]) + + @pytest.mark.parametrize("force", [False, True]) def test_manage_parameters(opts, generate, renderer, force): opts.force = force diff --git a/misc/scripts/calculate_mrva_totals.py b/misc/scripts/calculate_mrva_totals.py new file mode 100644 index 00000000000..cec1b8b54bf --- /dev/null +++ b/misc/scripts/calculate_mrva_totals.py @@ -0,0 +1,131 @@ +import os +import subprocess +import tempfile +import argparse +from collections import defaultdict + +help_text = """ +To use this script, pass the URL of a GitHub Gist as an argument. The Gist should contain the +exported MarkDown output of a MRVA run. + +The script expects the query to produce an output table of the form +``` +| header0 | header1 | header2 | header3 | ... +|----------|----------|----------|----------|---- +| message1 | value11 | value12 | value13 | ... +| message2 | value21 | value22 | value23 | ... +... +``` +The script will calculate the totals for each message and header, and put a table containing these +totals in the `_summary.md` file in the Gist. By default it will then commit and push these changes +to the Gist (having first displayed a diff of the changes). +""" + +first_header = "" + +def split_line(line): + return [item.strip() for item in line.strip('|').split('|')] + +def parse_markdown_table(stream): + global first_header + iterator = (line.strip() for line in stream) + + # Skip irrelevant lines until we find the header line + for line in iterator: + if line.startswith('|'): + first_header, *headers = split_line(line) + break + + # Skip the separator line + next(iterator) + + data_dict = {} + + # Process the remaining lines + for line in iterator: + if line.startswith('|'): + message, *values = [value.strip('`') for value in split_line(line)] + data_dict[message] = { + headers[i]: int(value) if value.isdigit() else value + for i, value in enumerate(values) + } + + return data_dict + +def clone_gist(gist_url, repo_dir): + try: + subprocess.run(["gh", "gist", "clone", gist_url, repo_dir], check=True) + except subprocess.CalledProcessError: + print(f"Failed to clone the gist from {gist_url}") + subprocess.run(["rm", "-rf", repo_dir]) + exit(1) + +def process_gist_files(repo_dir): + total_data = defaultdict(lambda: defaultdict(int)) + + for filename in os.listdir(repo_dir): + if filename.endswith(".md") and filename != "_summary.md": + with open(os.path.join(repo_dir, filename), "r") as file: + data_dict = parse_markdown_table(file) + + for message, values in data_dict.items(): + for header, value in values.items(): + if isinstance(value, int): + total_data[message][header] += value + + return total_data + +def append_totals_to_summary(total_data, repo_dir): + global first_header + summary_path = os.path.join(repo_dir, "_summary.md") + with open(summary_path, "r") as summary_file: + content = summary_file.read() + + totals_table = "\n\n### Totals\n\n" + headers = [first_header] + list(next(iter(total_data.values())).keys()) + totals_table += "| " + " | ".join(headers) + " |\n" + totals_table += "| " + "|".join(["---"] + ["---:"] * (len(headers) - 1)) + " |\n" # Right align all but the first column + for message, values in total_data.items(): + row = [message] + [f"{values[header]:,}" for header in headers[1:]] + totals_table += "| " + " | ".join(row) + " |\n" + + new_content = content.replace("### Summary", totals_table + "\n### Summary") + + with open(summary_path, "w") as summary_file: + summary_file.write(new_content) + +def commit_and_push_changes(repo_dir): + subprocess.run(["git", "add", "_summary.md"], cwd=repo_dir, check=True) + subprocess.run(["git", "commit", "-m", "Update summary with totals"], cwd=repo_dir, check=True) + subprocess.run(["git", "push"], cwd=repo_dir, check=True) + +def show_git_diff(repo_dir): + subprocess.run(["git", "diff", "_summary.md"], cwd=repo_dir, check=True) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Calculate MRVA totals from a GitHub Gist", epilog=help_text, formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument("gist_url", nargs='?', help="URL of the GitHub Gist") + parser.add_argument("--keep-dir", action="store_true", help="Keep the temporary directory") + + args = parser.parse_args() + + if not args.gist_url: + parser.print_help() + exit(1) + + repo_dir = tempfile.mkdtemp(dir=".") + clone_gist(args.gist_url, repo_dir) + + total_data = process_gist_files(repo_dir) + + append_totals_to_summary(total_data, repo_dir) + + show_git_diff(repo_dir) + + if input("Do you want to push the changes to the gist? (Y/n): ").strip().lower() in ['y', '']: + commit_and_push_changes(repo_dir) + + if args.keep_dir: + print(f"Temporary directory retained at: {repo_dir}") + else: + subprocess.run(["rm", "-rf", repo_dir]) diff --git a/misc/suite-helpers/code-quality-selectors.yml b/misc/suite-helpers/code-quality-selectors.yml new file mode 100644 index 00000000000..ddaf3366599 --- /dev/null +++ b/misc/suite-helpers/code-quality-selectors.yml @@ -0,0 +1,10 @@ +- description: Selectors for selecting the Code-Quality-relevant queries for a language +- include: + kind: + - problem + - path-problem + precision: + - high + - very-high + tags contain: + - quality diff --git a/python/ql/integration-tests/query-suite/python-code-quality.qls.expected b/python/ql/integration-tests/query-suite/python-code-quality.qls.expected index 47643f6a319..b81d300d024 100644 --- a/python/ql/integration-tests/query-suite/python-code-quality.qls.expected +++ b/python/ql/integration-tests/query-suite/python-code-quality.qls.expected @@ -1,4 +1,6 @@ ql/python/ql/src/Functions/NonCls.ql ql/python/ql/src/Functions/NonSelf.ql +ql/python/ql/src/Functions/ReturnConsistentTupleSizes.ql ql/python/ql/src/Functions/SignatureSpecialMethods.ql ql/python/ql/src/Resources/FileNotAlwaysClosed.ql +ql/python/ql/src/Variables/LoopVariableCapture/LoopVariableCapture.ql diff --git a/python/ql/lib/change-notes/2025-04-30-model-send-header.md b/python/ql/lib/change-notes/2025-04-30-model-send-header.md new file mode 100644 index 00000000000..032e984bdf3 --- /dev/null +++ b/python/ql/lib/change-notes/2025-04-30-model-send-header.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added header write model for `send_header` in `http.server`. \ No newline at end of file diff --git a/python/ql/lib/change-notes/2025-05-01-hdbcli.md b/python/ql/lib/change-notes/2025-05-01-hdbcli.md new file mode 100644 index 00000000000..5b9afe4618b --- /dev/null +++ b/python/ql/lib/change-notes/2025-05-01-hdbcli.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Added modeling for the `hdbcli` PyPI package as a database library implementing PEP 249. diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll index e6af222a615..955385141f7 100644 --- a/python/ql/lib/semmle/python/Frameworks.qll +++ b/python/ql/lib/semmle/python/Frameworks.qll @@ -35,6 +35,7 @@ private import semmle.python.frameworks.FlaskAdmin private import semmle.python.frameworks.FlaskSqlAlchemy private import semmle.python.frameworks.Genshi private import semmle.python.frameworks.Gradio +private import semmle.python.frameworks.Hdbcli private import semmle.python.frameworks.Httpx private import semmle.python.frameworks.Idna private import semmle.python.frameworks.Invoke diff --git a/python/ql/lib/semmle/python/frameworks/Hdbcli.qll b/python/ql/lib/semmle/python/frameworks/Hdbcli.qll new file mode 100644 index 00000000000..6b91519ae63 --- /dev/null +++ b/python/ql/lib/semmle/python/frameworks/Hdbcli.qll @@ -0,0 +1,24 @@ +/** + * Provides classes modeling security-relevant aspects of the `hdbcli` PyPI package. + * See https://pypi.org/project/hdbcli/ + */ + +private import python +private import semmle.python.dataflow.new.RemoteFlowSources +private import semmle.python.Concepts +private import semmle.python.ApiGraphs +private import semmle.python.frameworks.PEP249 + +/** + * Provides models for the `hdbcli` PyPI package. + * See https://pypi.org/project/hdbcli/ + */ +private module Hdbcli { + /** + * A model of `hdbcli` as a module that implements PEP 249, providing ways to execute SQL statements + * against a database. + */ + class HdbcliPEP249 extends PEP249::PEP249ModuleApiNode { + HdbcliPEP249() { this = API::moduleImport("hdbcli").getMember("dbapi") } + } +} diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll index 4ad671bb19a..4a3c346fb01 100644 --- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll @@ -1963,6 +1963,21 @@ module StdlibPrivate { /** Gets a reference to an instance of the `BaseHttpRequestHandler` class or any subclass. */ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) } + /** A call to a method that writes to a response header. */ + private class HeaderWriteCall extends Http::Server::ResponseHeaderWrite::Range, + DataFlow::MethodCallNode + { + HeaderWriteCall() { this.calls(instance(), "send_header") } + + override DataFlow::Node getNameArg() { result = this.getArg(0) } + + override DataFlow::Node getValueArg() { result = this.getArg(1) } + + override predicate nameAllowsNewline() { any() } + + override predicate valueAllowsNewline() { any() } + } + private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep { override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) { nodeFrom = instance() and diff --git a/python/ql/src/Expressions/UseofInput.ql b/python/ql/src/Expressions/UseofInput.ql index b7e9b6f7d9d..b5e49a65f98 100644 --- a/python/ql/src/Expressions/UseofInput.ql +++ b/python/ql/src/Expressions/UseofInput.ql @@ -4,8 +4,8 @@ * @kind problem * @tags security * correctness - * security/cwe/cwe-94 - * security/cwe/cwe-95 + * external/cwe/cwe-094 + * external/cwe/cwe-095 * @problem.severity error * @security-severity 9.8 * @sub-severity high diff --git a/python/ql/src/Functions/NonCls.ql b/python/ql/src/Functions/NonCls.ql index d36eeb9a6ec..0808da00e0a 100644 --- a/python/ql/src/Functions/NonCls.ql +++ b/python/ql/src/Functions/NonCls.ql @@ -5,6 +5,7 @@ * @tags maintainability * readability * convention + * quality * @problem.severity recommendation * @sub-severity high * @precision high diff --git a/python/ql/src/Functions/NonSelf.ql b/python/ql/src/Functions/NonSelf.ql index cea15d3661a..35b7af9b800 100644 --- a/python/ql/src/Functions/NonSelf.ql +++ b/python/ql/src/Functions/NonSelf.ql @@ -5,6 +5,7 @@ * @tags maintainability * readability * convention + * quality * @problem.severity recommendation * @sub-severity high * @precision very-high diff --git a/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll b/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll index fe1d6578e11..af31ec6ea4f 100644 --- a/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll +++ b/python/ql/src/Resources/FileNotAlwaysClosedQuery.qll @@ -52,14 +52,29 @@ class FileWrapperCall extends DataFlow::CallCfgNode { abstract class FileClose extends DataFlow::CfgNode { /** Holds if this file close will occur if an exception is thrown at `raises`. */ predicate guardsExceptions(DataFlow::CfgNode raises) { - this.asCfgNode() = raises.asCfgNode().getAnExceptionalSuccessor().getASuccessor*() + cfgGetASuccessorStar(raises.asCfgNode().getAnExceptionalSuccessor(), this.asCfgNode()) or // The expression is after the close call. // This also covers the body of a `with` statement. - raises.asCfgNode() = this.asCfgNode().getASuccessor*() + cfgGetASuccessorStar(this.asCfgNode(), raises.asCfgNode()) } } +private predicate cfgGetASuccessor(ControlFlowNode src, ControlFlowNode sink) { + sink = src.getASuccessor() +} + +pragma[inline] +private predicate cfgGetASuccessorPlus(ControlFlowNode src, ControlFlowNode sink) = + fastTC(cfgGetASuccessor/2)(src, sink) + +pragma[inline] +private predicate cfgGetASuccessorStar(ControlFlowNode src, ControlFlowNode sink) { + src = sink + or + cfgGetASuccessorPlus(src, sink) +} + /** A call to the `.close()` method of a file object. */ class FileCloseCall extends FileClose { FileCloseCall() { exists(DataFlow::MethodCallNode mc | mc.calls(this, "close")) } diff --git a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql index ac374311ee8..a0905e6626d 100644 --- a/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql +++ b/python/ql/src/Security/CWE-020-ExternalAPIs/ExternalAPIsUsedWithUntrustedData.ql @@ -5,7 +5,7 @@ * to it. * @id py/count-untrusted-data-external-api * @kind table - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import python diff --git a/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql b/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql index f5706ccc3a6..feb5b77c02a 100644 --- a/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql +++ b/python/ql/src/Security/CWE-020-ExternalAPIs/UntrustedDataToExternalAPI.ql @@ -6,7 +6,7 @@ * @precision low * @problem.severity error * @security-severity 7.8 - * @tags security external/cwe/cwe-20 + * @tags security external/cwe/cwe-020 */ import python diff --git a/python/ql/src/Security/CWE-020/CookieInjection.ql b/python/ql/src/Security/CWE-020/CookieInjection.ql index 0cb9c2dadbb..e0600648eac 100644 --- a/python/ql/src/Security/CWE-020/CookieInjection.ql +++ b/python/ql/src/Security/CWE-020/CookieInjection.ql @@ -7,7 +7,7 @@ * @security-severity 5.0 * @id py/cookie-injection * @tags security - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import python diff --git a/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql b/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql index 5ab77438d63..1dbd95d5533 100644 --- a/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql +++ b/python/ql/src/Security/CWE-020/IncompleteUrlSubstringSanitization.ql @@ -8,7 +8,7 @@ * @id py/incomplete-url-substring-sanitization * @tags correctness * security - * external/cwe/cwe-20 + * external/cwe/cwe-020 */ import python diff --git a/python/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/python/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..0267e9a3fbb --- /dev/null +++ b/python/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,10 @@ +--- +category: queryMetadata +--- + +* The tags `security/cwe/cwe-94` and `security/cwe/cwe-95` have been removed from `py/use-of-input` and the tags `external/cwe/cwe-094` and `external/cwe/cwe-095` have been added. +* The tag `external/cwe/cwe-20` has been removed from `py/count-untrusted-data-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `py/untrusted-data-to-external-api` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `py/cookie-injection` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-20` has been removed from `py/incomplete-url-substring-sanitization` and the tag `external/cwe/cwe-020` has been added. +* The tag `external/cwe/cwe-94` has been removed from `py/js2py-rce` and the tag `external/cwe/cwe-094` has been added. diff --git a/python/ql/src/codeql-suites/python-code-quality.qls b/python/ql/src/codeql-suites/python-code-quality.qls index 3ada7e8eb4c..2074f9378cf 100644 --- a/python/ql/src/codeql-suites/python-code-quality.qls +++ b/python/ql/src/codeql-suites/python-code-quality.qls @@ -1,7 +1,3 @@ - queries: . -- include: - id: - - py/not-named-self - - py/not-named-cls - - py/file-not-closed - - py/special-method-wrong-signature +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/python/ql/src/experimental/Security/CWE-094/Js2Py.ql b/python/ql/src/experimental/Security/CWE-094/Js2Py.ql index 2bb3fea1b32..53c919d9732 100644 --- a/python/ql/src/experimental/Security/CWE-094/Js2Py.ql +++ b/python/ql/src/experimental/Security/CWE-094/Js2Py.ql @@ -8,7 +8,7 @@ * @id py/js2py-rce * @tags security * experimental - * external/cwe/cwe-94 + * external/cwe/cwe-094 */ import python diff --git a/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.ql new file mode 100644 index 00000000000..b557a0bccb6 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/hdbcli/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/hdbcli/pep249.py b/python/ql/test/library-tests/frameworks/hdbcli/pep249.py new file mode 100644 index 00000000000..713f15cb6d4 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/hdbcli/pep249.py @@ -0,0 +1,9 @@ +from hdbcli import dbapi + +conn = dbapi.connect(address="hostname", port=300, user="username", password="password") +cursor = conn.cursor() + +cursor.execute("some sql", (42,)) # $ getSql="some sql" +cursor.executemany("some sql", (42,)) # $ getSql="some sql" + +cursor.close() diff --git a/python/ql/test/library-tests/frameworks/stdlib/http_server.py b/python/ql/test/library-tests/frameworks/stdlib/http_server.py index 9110aa6a26a..8e9fd925c24 100644 --- a/python/ql/test/library-tests/frameworks/stdlib/http_server.py +++ b/python/ql/test/library-tests/frameworks/stdlib/http_server.py @@ -83,7 +83,7 @@ class MyHandler(BaseHTTPRequestHandler): def do_GET(self): # $ requestHandler # send_response will log a line to stderr self.send_response(200) - self.send_header("Content-type", "text/plain; charset=utf-8") + self.send_header("Content-type", "text/plain; charset=utf-8") # $ headerWriteNameUnsanitized="Content-type" headerWriteValueUnsanitized="text/plain; charset=utf-8" self.end_headers() self.wfile.write(b"Hello BaseHTTPRequestHandler\n") self.wfile.writelines([b"1\n", b"2\n", b"3\n"]) diff --git a/ruby/ql/integration-tests/query-suite/ruby-code-quality.qls.expected b/ruby/ql/integration-tests/query-suite/ruby-code-quality.qls.expected index 94b2f19caaa..db6b7590220 100644 --- a/ruby/ql/integration-tests/query-suite/ruby-code-quality.qls.expected +++ b/ruby/ql/integration-tests/query-suite/ruby-code-quality.qls.expected @@ -1,3 +1,2 @@ ql/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql -ql/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql ql/ruby/ql/src/queries/variables/UninitializedLocal.ql diff --git a/ruby/ql/lib/codeql/ruby/security/regexp/MissingFullAnchorQuery.qll b/ruby/ql/lib/codeql/ruby/security/regexp/MissingFullAnchorQuery.qll index d511c6f3fbf..febfa0712d9 100644 --- a/ruby/ql/lib/codeql/ruby/security/regexp/MissingFullAnchorQuery.qll +++ b/ruby/ql/lib/codeql/ruby/security/regexp/MissingFullAnchorQuery.qll @@ -17,16 +17,6 @@ private module MissingFullAnchorConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node sink) { sink instanceof Sink } predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } - - predicate observeDiffInformedIncrementalMode() { any() } - - Location getASelectedSinkLocation(DataFlow::Node sink) { - result = sink.(Sink).getLocation() - or - result = sink.(Sink).getCallNode().getLocation() - or - result = sink.(Sink).getRegex().getLocation() - } } /** diff --git a/ruby/ql/lib/codeql/ruby/security/regexp/PolynomialReDoSQuery.qll b/ruby/ql/lib/codeql/ruby/security/regexp/PolynomialReDoSQuery.qll index 562b5dad37b..98a42fcf5e7 100644 --- a/ruby/ql/lib/codeql/ruby/security/regexp/PolynomialReDoSQuery.qll +++ b/ruby/ql/lib/codeql/ruby/security/regexp/PolynomialReDoSQuery.qll @@ -18,16 +18,6 @@ private module PolynomialReDoSConfig implements DataFlow::ConfigSig { predicate isSink(DataFlow::Node sink) { sink instanceof Sink } predicate isBarrier(DataFlow::Node node) { node instanceof Sanitizer } - - predicate observeDiffInformedIncrementalMode() { any() } - - Location getASelectedSinkLocation(DataFlow::Node sink) { - result = sink.(Sink).getLocation() - or - result = sink.(Sink).getHighlight().getLocation() - or - result = sink.(Sink).getRegExp().getLocation() - } } /** diff --git a/ruby/ql/src/change-notes/2025-05-01-cwe-tag-changed.md b/ruby/ql/src/change-notes/2025-05-01-cwe-tag-changed.md new file mode 100644 index 00000000000..c8fd8d62686 --- /dev/null +++ b/ruby/ql/src/change-notes/2025-05-01-cwe-tag-changed.md @@ -0,0 +1,5 @@ +--- +category: queryMetadata +--- + +* The tag `external/cwe/cwe-94` has been removed from `rb/server-side-template-injection` and the tag `external/cwe/cwe-094` has been added. diff --git a/ruby/ql/src/codeql-suites/ruby-code-quality.qls b/ruby/ql/src/codeql-suites/ruby-code-quality.qls index 2111c6979ef..2074f9378cf 100644 --- a/ruby/ql/src/codeql-suites/ruby-code-quality.qls +++ b/ruby/ql/src/codeql-suites/ruby-code-quality.qls @@ -1,6 +1,3 @@ - queries: . -- include: - id: - - rb/database-query-in-loop - - rb/useless-assignment-to-local - - rb/uninitialized-local-variable \ No newline at end of file +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/ruby/ql/src/experimental/template-injection/TemplateInjection.ql b/ruby/ql/src/experimental/template-injection/TemplateInjection.ql index 7ad81c34123..7ad670d6ec1 100644 --- a/ruby/ql/src/experimental/template-injection/TemplateInjection.ql +++ b/ruby/ql/src/experimental/template-injection/TemplateInjection.ql @@ -8,7 +8,7 @@ * @precision high * @id rb/server-side-template-injection * @tags security - * external/cwe/cwe-94 + * external/cwe/cwe-094 */ import codeql.ruby.DataFlow diff --git a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql index b17c5ecd9ba..835fe620984 100644 --- a/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql +++ b/ruby/ql/src/queries/performance/DatabaseQueryInLoop.ql @@ -6,6 +6,7 @@ * @precision high * @id rb/database-query-in-loop * @tags performance + * quality */ import ruby diff --git a/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql b/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql index 547d7d3cd89..a7b37515d7f 100644 --- a/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql +++ b/ruby/ql/src/queries/variables/DeadStoreOfLocal.ql @@ -6,6 +6,7 @@ * @problem.severity warning * @id rb/useless-assignment-to-local * @tags maintainability + * quality * external/cwe/cwe-563 * @precision medium */ diff --git a/rust/extractor/src/crate_graph.rs b/rust/extractor/src/crate_graph.rs index 0203e8adc47..8122248aba3 100644 --- a/rust/extractor/src/crate_graph.rs +++ b/rust/extractor/src/crate_graph.rs @@ -383,25 +383,54 @@ fn emit_function( assert_eq!(sig.binders.len(Interner), parameters.len()); let sig = sig.skip_binders(); let ty_vars = &[parameters]; + let function_data = db.function_data(function); + let mut self_param = None; let params = sig .params() .iter() - .map(|p| { + .enumerate() + .filter_map(|(idx, p)| { let type_repr = emit_hir_ty(trap, db, ty_vars, p); - trap.emit(generated::Param { - id: trap::TrapId::Star, - attrs: vec![], - type_repr, - pat: None, - }) + + if idx == 0 && function_data.has_self_param() { + // Check if the self parameter is a reference + let (is_ref, is_mut) = match p.kind(Interner) { + chalk_ir::TyKind::Ref(mutability, _, _) => { + (true, matches!(mutability, chalk_ir::Mutability::Mut)) + } + chalk_ir::TyKind::Raw(mutability, _) => { + (false, matches!(mutability, chalk_ir::Mutability::Mut)) + } + _ => (false, false), + }; + + self_param = Some(trap.emit(generated::SelfParam { + id: trap::TrapId::Star, + attrs: vec![], + type_repr, + is_ref, + is_mut, + lifetime: None, + name: None, + })); + None + } else { + Some(trap.emit(generated::Param { + id: trap::TrapId::Star, + attrs: vec![], + type_repr, + pat: None, + })) + } }) .collect(); let ret_type = emit_hir_ty(trap, db, ty_vars, sig.ret()); + let param_list = trap.emit(generated::ParamList { id: trap::TrapId::Star, params, - self_param: None, + self_param, }); let ret_type = ret_type.map(|ret_type| { trap.emit(generated::RetTypeRepr { diff --git a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll index 4376df7caf8..b2950622440 100644 --- a/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll +++ b/rust/ql/lib/codeql/rust/dataflow/internal/DataFlowImpl.qll @@ -11,6 +11,7 @@ private import rust private import SsaImpl as SsaImpl private import codeql.rust.controlflow.internal.Scope as Scope private import codeql.rust.internal.PathResolution +private import codeql.rust.internal.TypeInference as TypeInference private import codeql.rust.controlflow.ControlFlowGraph private import codeql.rust.controlflow.CfgNodes private import codeql.rust.dataflow.Ssa @@ -321,23 +322,6 @@ predicate lambdaCallExpr(CallExprCfgNode call, LambdaCallKind kind, ExprCfgNode exists(kind) } -/** Holds if `mc` implicitly borrows its receiver. */ -private predicate implicitBorrow(MethodCallExpr mc) { - // Determining whether an implicit borrow happens depends on the type of the - // receiever as well as the target. As a heuristic we simply check if the - // target takes `self` as a borrow and limit the approximation to cases where - // the receiver is a simple variable. - mc.getReceiver() instanceof VariableAccess and - mc.getStaticTarget().getParamList().getSelfParam().isRef() -} - -/** Holds if `mc` implicitly dereferences its receiver. */ -private predicate implicitDeref(MethodCallExpr mc) { - // Similarly to `implicitBorrow` this is an approximation. - mc.getReceiver() instanceof VariableAccess and - not mc.getStaticTarget().getParamList().getSelfParam().isRef() -} - // Defines a set of aliases needed for the `RustDataFlow` module private module Aliases { class DataFlowCallableAlias = DataFlowCallable; @@ -520,15 +504,15 @@ module RustDataFlow implements InputSig { pragma[nomagic] private predicate implicitDerefToReceiver(Node node1, ReceiverNode node2, ReferenceContent c) { + TypeInference::receiverHasImplicitDeref(node1.asExpr().getExpr()) and node1.asExpr() = node2.getReceiver() and - implicitDeref(node2.getMethodCall().getMethodCallExpr()) and exists(c) } pragma[nomagic] private predicate implicitBorrowToReceiver(Node node1, ReceiverNode node2, ReferenceContent c) { + TypeInference::receiverHasImplicitBorrow(node1.asExpr().getExpr()) and node1.asExpr() = node2.getReceiver() and - implicitBorrow(node2.getMethodCall().getMethodCallExpr()) and exists(c) } diff --git a/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll b/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll index 0798343837e..8d3c41c4708 100644 --- a/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll +++ b/rust/ql/lib/codeql/rust/frameworks/stdlib/Clone.qll @@ -18,7 +18,7 @@ final class CloneCallable extends SummarizedCallable::Range { final override predicate propagatesFlow( string input, string output, boolean preservesValue, string model ) { - input = "Argument[self]" and + input = "Argument[self].Reference" and output = "ReturnValue" and preservesValue = true and model = "generated" diff --git a/rust/ql/lib/codeql/rust/internal/CachedStages.qll b/rust/ql/lib/codeql/rust/internal/CachedStages.qll index 4041b2731f9..2a7447ed7a3 100644 --- a/rust/ql/lib/codeql/rust/internal/CachedStages.qll +++ b/rust/ql/lib/codeql/rust/internal/CachedStages.qll @@ -120,7 +120,7 @@ module Stages { or exists(resolvePath(_)) or - exists(any(ItemNode i).getASuccessor(_)) + exists(any(ItemNode i).getASuccessorFull(_)) or exists(any(ItemNode i).getASuccessorRec(_)) or diff --git a/rust/ql/lib/codeql/rust/internal/PathResolution.qll b/rust/ql/lib/codeql/rust/internal/PathResolution.qll index a6cc51a21c5..5bc45afecf1 100644 --- a/rust/ql/lib/codeql/rust/internal/PathResolution.qll +++ b/rust/ql/lib/codeql/rust/internal/PathResolution.qll @@ -172,9 +172,14 @@ abstract class ItemNode extends Locatable { result = this.(TypeParamItemNode).resolveABound().getASuccessorRec(name).(AssocItemNode) } - /** Gets a successor named `name` of this item, if any. */ + /** + * Gets a successor named `name` of this item, if any. + * + * Whenever a function exists in both source code and in library code, + * both are included + */ cached - ItemNode getASuccessor(string name) { + ItemNode getASuccessorFull(string name) { Stages::PathResolutionStage::ref() and result = this.getASuccessorRec(name) or @@ -202,6 +207,22 @@ abstract class ItemNode extends Locatable { result.(CrateItemNode).isPotentialDollarCrateTarget() } + /** Gets a successor named `name` of this item, if any. */ + pragma[nomagic] + ItemNode getASuccessor(string name) { + result = this.getASuccessorFull(name) and + ( + // when a function exists in both source code and in library code, it is because + // we also extracted the source code as library code, and hence we only want + // the function from source code + result.fromSource() + or + not result instanceof Function + or + not this.getASuccessorFull(name).(Function).fromSource() + ) + } + /** Gets the location of this item. */ Location getLocation() { result = super.getLocation() } } @@ -234,7 +255,7 @@ abstract private class ModuleLikeNode extends ItemNode { private class SourceFileItemNode extends ModuleLikeNode, SourceFile { pragma[nomagic] ModuleLikeNode getSuper() { - result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessor("super") + result = any(ModuleItemNode mod | fileImport(mod, this)).getASuccessorFull("super") } override string getName() { result = "(source file)" } @@ -297,7 +318,7 @@ class CrateItemNode extends ItemNode instanceof Crate { predicate isPotentialDollarCrateTarget() { exists(string name, RelevantPath p | p.isDollarCrateQualifiedPath(name) and - exists(this.getASuccessor(name)) + exists(this.getASuccessorFull(name)) ) } @@ -323,7 +344,14 @@ abstract private class AssocItemNode extends ItemNode, AssocItem { private class ConstItemNode extends AssocItemNode instanceof Const { override string getName() { result = Const.super.getName().getText() } - override predicate hasImplementation() { super.hasBody() } + override predicate hasImplementation() { + super.hasBody() + or + // for trait items from library code, we do not currently know if they + // have default implementations or not, so we assume they do + not this.fromSource() and + this = any(TraitItemNode t).getAnAssocItem() + } override Namespace getNamespace() { result.isValue() } @@ -359,7 +387,14 @@ private class VariantItemNode extends ItemNode instanceof Variant { class FunctionItemNode extends AssocItemNode instanceof Function { override string getName() { result = Function.super.getName().getText() } - override predicate hasImplementation() { super.hasBody() } + override predicate hasImplementation() { + super.hasBody() + or + // for trait items from library code, we do not currently know if they + // have default implementations or not, so we assume they do + not this.fromSource() and + this = any(TraitItemNode t).getAnAssocItem() + } override Namespace getNamespace() { result.isValue() } @@ -862,6 +897,12 @@ class RelevantPath extends Path { this.getQualifier().(RelevantPath).isCratePath("$crate", _) and this.getText() = name } + + // TODO: Remove once the crate graph extractor generates publicly visible paths + predicate requiresExtractorWorkaround() { + not this.fromSource() and + this = any(RelevantPath p).getQualifier() + } } private predicate isModule(ItemNode m) { m instanceof Module } @@ -916,8 +957,8 @@ private predicate unqualifiedPathLookup(ItemNode encl, string name, Namespace ns } pragma[nomagic] -private ItemNode getASuccessor(ItemNode pred, string name, Namespace ns) { - result = pred.getASuccessor(name) and +private ItemNode getASuccessorFull(ItemNode pred, string name, Namespace ns) { + result = pred.getASuccessorFull(name) and ns = result.getNamespace() } @@ -954,7 +995,7 @@ private predicate keywordLookup(ItemNode encl, string name, Namespace ns, Releva pragma[nomagic] private ItemNode unqualifiedPathLookup(RelevantPath p, Namespace ns) { - exists(ItemNode encl, string name | result = getASuccessor(encl, name, ns) | + exists(ItemNode encl, string name | result = getASuccessorFull(encl, name, ns) | unqualifiedPathLookup(encl, name, ns, p) or keywordLookup(encl, name, ns, p) @@ -978,7 +1019,7 @@ private ItemNode resolvePath0(RelevantPath path, Namespace ns) { or exists(ItemNode q, string name | q = resolvePathQualifier(path, name) and - result = getASuccessor(q, name, ns) + result = getASuccessorFull(q, name, ns) ) or result = resolveUseTreeListItem(_, _, path) and @@ -1029,6 +1070,7 @@ pragma[nomagic] private ItemNode resolvePathPrivate( RelevantPath path, ModuleLikeNode itemParent, ModuleLikeNode pathParent ) { + not path.requiresExtractorWorkaround() and result = resolvePath1(path) and itemParent = result.getImmediateParentModule() and not result.isPublic() and @@ -1062,7 +1104,11 @@ private ModuleLikeNode getAPrivateVisibleModule(ModuleLikeNode itemParent) { cached ItemNode resolvePath(RelevantPath path) { result = resolvePath1(path) and - result.isPublic() + ( + result.isPublic() + or + path.requiresExtractorWorkaround() + ) or exists(ModuleLikeNode itemParent, ModuleLikeNode pathParent | result = resolvePathPrivate(path, itemParent, pathParent) and @@ -1098,12 +1144,12 @@ private ItemNode resolveUseTreeListItem(Use use, UseTree tree, RelevantPath path mid = resolveUseTreeListItem(use, midTree) and tree = midTree.getUseTreeList().getAUseTree() and isUseTreeSubPathUnqualified(tree, path, pragma[only_bind_into](name)) and - result = mid.getASuccessor(pragma[only_bind_into](name)) + result = mid.getASuccessorFull(pragma[only_bind_into](name)) ) or exists(ItemNode q, string name | q = resolveUseTreeListItemQualifier(use, tree, path, name) and - result = q.getASuccessor(name) + result = q.getASuccessorFull(name) ) } @@ -1133,7 +1179,7 @@ private predicate useImportEdge(Use use, string name, ItemNode item) { then exists(ItemNode encl, Namespace ns | encl.getADescendant() = use and - item = getASuccessor(used, name, ns) and + item = getASuccessorFull(used, name, ns) and // glob imports can be shadowed not declares(encl, ns, name) and not name = ["super", "self", "Self", "$crate", "crate"] diff --git a/rust/ql/lib/codeql/rust/internal/TypeInference.qll b/rust/ql/lib/codeql/rust/internal/TypeInference.qll index 2fae9ef1f5b..cb9450a84d7 100644 --- a/rust/ql/lib/codeql/rust/internal/TypeInference.qll +++ b/rust/ql/lib/codeql/rust/internal/TypeInference.qll @@ -6,6 +6,7 @@ private import Type private import Type as T private import TypeMention private import codeql.typeinference.internal.TypeInference +private import codeql.rust.frameworks.stdlib.Stdlib class Type = T::Type; @@ -662,15 +663,6 @@ private module CallExprBaseMatchingInput implements MatchingInputSig { tAdj = t ) } - - pragma[nomagic] - additional Type inferReceiverType(AstNode n) { - exists(Access a, AccessPosition apos | - result = inferType(n) and - n = a.getNodeAt(apos) and - apos.isSelf() - ) - } } private module CallExprBaseMatching = Matching; @@ -690,7 +682,7 @@ private Type inferCallExprBaseType(AstNode n, TypePath path) { | if apos.isSelf() then - exists(Type receiverType | receiverType = CallExprBaseMatchingInput::inferReceiverType(n) | + exists(Type receiverType | receiverType = inferType(n) | if receiverType = TRefType() then path = path0 and @@ -813,15 +805,6 @@ private module FieldExprMatchingInput implements MatchingInputSig { tAdj = t ) } - - pragma[nomagic] - additional Type inferReceiverType(AstNode n) { - exists(Access a, AccessPosition apos | - result = inferType(n) and - n = a.getNodeAt(apos) and - apos.isSelf() - ) - } } private module FieldExprMatching = Matching; @@ -840,7 +823,7 @@ private Type inferFieldExprType(AstNode n, TypePath path) { | if apos.isSelf() then - exists(Type receiverType | receiverType = FieldExprMatchingInput::inferReceiverType(n) | + exists(Type receiverType | receiverType = inferType(n) | if receiverType = TRefType() then // adjust for implicit deref @@ -891,10 +874,43 @@ private Type inferRefExprType(Expr e, TypePath path) { ) } +pragma[nomagic] +private Type inferTryExprType(TryExpr te, TypePath path) { + exists(TypeParam tp | + result = inferType(te.getExpr(), TypePath::cons(TTypeParamTypeParameter(tp), path)) + | + tp = any(ResultEnum r).getGenericParamList().getGenericParam(0) + or + tp = any(OptionEnum o).getGenericParamList().getGenericParam(0) + ) +} + cached private module Cached { private import codeql.rust.internal.CachedStages + /** Holds if `receiver` is the receiver of a method call with an implicit dereference. */ + cached + predicate receiverHasImplicitDeref(AstNode receiver) { + exists(CallExprBaseMatchingInput::Access a, CallExprBaseMatchingInput::AccessPosition apos | + apos.isSelf() and + receiver = a.getNodeAt(apos) and + inferType(receiver) = TRefType() and + CallExprBaseMatching::inferAccessType(a, apos, TypePath::nil()) != TRefType() + ) + } + + /** Holds if `receiver` is the receiver of a method call with an implicit borrow. */ + cached + predicate receiverHasImplicitBorrow(AstNode receiver) { + exists(CallExprBaseMatchingInput::Access a, CallExprBaseMatchingInput::AccessPosition apos | + apos.isSelf() and + receiver = a.getNodeAt(apos) and + CallExprBaseMatching::inferAccessType(a, apos, TypePath::nil()) = TRefType() and + inferType(receiver) != TRefType() + ) + } + pragma[inline] private Type getLookupType(AstNode n) { exists(Type t | @@ -1008,6 +1024,8 @@ private module Cached { result = inferFieldExprType(n, path) or result = inferRefExprType(n, path) + or + result = inferTryExprType(n, path) } } @@ -1017,3 +1035,24 @@ import Cached * Gets a type that `n` infers to, if any. */ Type inferType(AstNode n) { result = inferType(n, TypePath::nil()) } + +/** Provides predicates for debugging the type inference implementation. */ +private module Debug { + private Locatable getRelevantLocatable() { + exists(string filepath, int startline, int startcolumn, int endline, int endcolumn | + result.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and + filepath.matches("%/main.rs") and + startline = 28 + ) + } + + Type debugInferType(AstNode n, TypePath path) { + n = getRelevantLocatable() and + result = inferType(n, path) + } + + Function debugResolveMethodCallExpr(MethodCallExpr mce) { + mce = getRelevantLocatable() and + result = resolveMethodCallExpr(mce) + } +} diff --git a/rust/ql/src/codeql-suites/rust-code-quality.qls b/rust/ql/src/codeql-suites/rust-code-quality.qls index 0637a088a01..2074f9378cf 100644 --- a/rust/ql/src/codeql-suites/rust-code-quality.qls +++ b/rust/ql/src/codeql-suites/rust-code-quality.qls @@ -1 +1,3 @@ -[] \ No newline at end of file +- queries: . +- apply: code-quality-selectors.yml + from: codeql/suite-helpers diff --git a/rust/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql b/rust/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql index a19a1b2398c..e88efe80b8e 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureContentSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = ContentSensitive::captureFlow(api, _, _) diff --git a/rust/ql/src/utils/modelgenerator/CaptureNeutralModels.ql b/rust/ql/src/utils/modelgenerator/CaptureNeutralModels.ql index 8efc8a485e1..556d1624f39 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureNeutralModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureNeutralModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string noflow where noflow = Heuristic::captureNoFlow(api) diff --git a/rust/ql/src/utils/modelgenerator/CaptureSinkModels.ql b/rust/ql/src/utils/modelgenerator/CaptureSinkModels.ql index 36b1b813297..989637a867e 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureSinkModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureSinkModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SinkModels from DataFlowSinkTargetApi api, string sink where sink = Heuristic::captureSink(api) diff --git a/rust/ql/src/utils/modelgenerator/CaptureSourceModels.ql b/rust/ql/src/utils/modelgenerator/CaptureSourceModels.ql index 7086f719b2d..2ffc4894b18 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureSourceModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureSourceModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SourceModels from DataFlowSourceTargetApi api, string source where source = Heuristic::captureSource(api) diff --git a/rust/ql/src/utils/modelgenerator/CaptureSummaryModels.ql b/rust/ql/src/utils/modelgenerator/CaptureSummaryModels.ql index 8947dd01531..d0b3152c9be 100644 --- a/rust/ql/src/utils/modelgenerator/CaptureSummaryModels.ql +++ b/rust/ql/src/utils/modelgenerator/CaptureSummaryModels.ql @@ -7,6 +7,7 @@ */ import internal.CaptureModels +import SummaryModels from DataFlowSummaryTargetApi api, string flow where flow = captureFlow(api, _) diff --git a/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql b/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql index 49b8d56fdff..b10b638129b 100644 --- a/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql +++ b/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPartialPath.ql @@ -10,6 +10,7 @@ private import codeql.rust.dataflow.DataFlow import utils.modelgenerator.internal.CaptureModels +import SummaryModels import PartialFlow::PartialPathGraph int explorationLimit() { result = 3 } diff --git a/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql b/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql index 611faae5b41..8ae02ce1d69 100644 --- a/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql +++ b/rust/ql/src/utils/modelgenerator/debug/CaptureSummaryModelsPath.ql @@ -10,6 +10,7 @@ private import codeql.rust.dataflow.DataFlow import utils.modelgenerator.internal.CaptureModels +import SummaryModels import Heuristic import PropagateTaintFlow::PathGraph diff --git a/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll b/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll index 237da46750b..99e1c527b54 100644 --- a/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll +++ b/rust/ql/src/utils/modelgenerator/internal/CaptureModels.qll @@ -2,7 +2,7 @@ private import codeql.util.Unit private import rust private import rust as R private import codeql.rust.dataflow.DataFlow -private import codeql.rust.dataflow.internal.DataFlowImpl +private import codeql.rust.dataflow.internal.DataFlowImpl as DataFlowImpl private import codeql.rust.dataflow.internal.Node as Node private import codeql.rust.dataflow.internal.Content private import codeql.rust.dataflow.FlowSource as FlowSource @@ -11,7 +11,25 @@ private import codeql.rust.dataflow.internal.TaintTrackingImpl private import codeql.mad.modelgenerator.internal.ModelGeneratorImpl private import codeql.rust.dataflow.internal.FlowSummaryImpl as FlowSummary -module ModelGeneratorInput implements ModelGeneratorInputSig { +private predicate relevant(Function api) { + // Only include functions that have a resolved path. + api.hasCrateOrigin() and + api.hasExtendedCanonicalPath() and + ( + // This excludes closures (these are not exported API endpoints) and + // functions without a `pub` visiblity. A function can be `pub` without + // ultimately being exported by a crate, so this is an overapproximation. + api.hasVisibility() + or + // If a method implements a public trait it is exposed through the trait. + // We overapproximate this by including all trait method implementations. + exists(Impl impl | impl.hasTrait() and impl.getAssocItemList().getAssocItem(_) = api) + ) +} + +module ModelGeneratorCommonInput implements + ModelGeneratorCommonInputSig +{ // NOTE: We are not using type information for now. class Type = Unit; @@ -23,42 +41,71 @@ module ModelGeneratorInput implements ModelGeneratorInputSig + +private module SummaryModelGeneratorInput implements SummaryModelGeneratorInputSig { class SummaryTargetApi extends Callable { private Callable lift; @@ -72,74 +119,9 @@ module ModelGeneratorInput implements ModelGeneratorInputSig +import MakeSummaryModelGenerator as SummaryModels +import MakeSourceModelGenerator as SourceModels +import MakeSinkModelGenerator as SinkModels diff --git a/rust/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll b/rust/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll index 4c2da918f73..789113f7580 100644 --- a/rust/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll +++ b/rust/ql/src/utils/modelgenerator/internal/CaptureModelsPrinting.qll @@ -1,6 +1,6 @@ private import rust as R private import codeql.mad.modelgenerator.internal.ModelPrinting -private import CaptureModels::ModelGeneratorInput as ModelGeneratorInput +private import CaptureModels::ModelGeneratorCommonInput as ModelGeneratorInput private module ModelPrintingLang implements ModelPrintingLangSig { class Callable = R::Callable; diff --git a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected index d9f17dbf4c4..6c2a7c2ba85 100644 --- a/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected +++ b/rust/ql/test/library-tests/dataflow/local/DataFlowStep.expected @@ -1931,33 +1931,16 @@ readStep | main.rs:221:9:221:23 | ...::Some(...) | Some | main.rs:221:22:221:22 | n | | main.rs:230:9:230:15 | Some(...) | Some | main.rs:230:14:230:14 | n | | main.rs:234:9:234:15 | Some(...) | Some | main.rs:234:14:234:14 | n | -| main.rs:241:10:241:11 | s1 | &ref | main.rs:241:10:241:11 | receiver for s1 | -| main.rs:246:10:246:11 | s1 | &ref | main.rs:246:10:246:11 | receiver for s1 | -| main.rs:249:10:249:11 | s2 | &ref | main.rs:249:10:249:11 | receiver for s2 | -| main.rs:254:10:254:11 | s1 | &ref | main.rs:254:10:254:11 | receiver for s1 | -| main.rs:257:10:257:11 | s2 | &ref | main.rs:257:10:257:11 | receiver for s2 | | main.rs:263:14:263:15 | s1 | Ok | main.rs:263:14:263:16 | TryExpr | | main.rs:263:14:263:15 | s1 | Some | main.rs:263:14:263:16 | TryExpr | | main.rs:265:10:265:11 | s2 | Ok | main.rs:265:10:265:12 | TryExpr | | main.rs:265:10:265:11 | s2 | Some | main.rs:265:10:265:12 | TryExpr | -| main.rs:271:29:271:30 | r1 | &ref | main.rs:271:29:271:30 | receiver for r1 | -| main.rs:272:29:272:30 | r1 | &ref | main.rs:272:29:272:30 | receiver for r1 | -| main.rs:273:10:273:12 | o1a | &ref | main.rs:273:10:273:12 | receiver for o1a | -| main.rs:274:10:274:12 | o1b | &ref | main.rs:274:10:274:12 | receiver for o1b | -| main.rs:277:29:277:30 | r2 | &ref | main.rs:277:29:277:30 | receiver for r2 | -| main.rs:278:29:278:30 | r2 | &ref | main.rs:278:29:278:30 | receiver for r2 | -| main.rs:279:10:279:12 | o2a | &ref | main.rs:279:10:279:12 | receiver for o2a | -| main.rs:280:10:280:12 | o2b | &ref | main.rs:280:10:280:12 | receiver for o2b | | main.rs:287:14:287:15 | s1 | Ok | main.rs:287:14:287:16 | TryExpr | | main.rs:287:14:287:15 | s1 | Some | main.rs:287:14:287:16 | TryExpr | | main.rs:288:14:288:15 | s2 | Ok | main.rs:288:14:288:16 | TryExpr | | main.rs:288:14:288:15 | s2 | Some | main.rs:288:14:288:16 | TryExpr | | main.rs:291:14:291:15 | s3 | Ok | main.rs:291:14:291:16 | TryExpr | | main.rs:291:14:291:15 | s3 | Some | main.rs:291:14:291:16 | TryExpr | -| main.rs:298:10:298:11 | s1 | &ref | main.rs:298:10:298:11 | receiver for s1 | -| main.rs:299:10:299:11 | s1 | &ref | main.rs:299:10:299:11 | receiver for s1 | -| main.rs:302:10:302:11 | s2 | &ref | main.rs:302:10:302:11 | receiver for s2 | -| main.rs:303:10:303:11 | s2 | &ref | main.rs:303:10:303:11 | receiver for s2 | | main.rs:315:9:315:25 | ...::A(...) | A | main.rs:315:24:315:24 | n | | main.rs:316:9:316:25 | ...::B(...) | B | main.rs:316:24:316:24 | n | | main.rs:319:9:319:25 | ...::A(...) | A | main.rs:319:24:319:24 | n | @@ -1997,40 +1980,22 @@ readStep | main.rs:442:9:442:20 | TuplePat | tuple.0 | main.rs:442:10:442:13 | cond | | main.rs:442:9:442:20 | TuplePat | tuple.1 | main.rs:442:16:442:19 | name | | main.rs:442:25:442:29 | names | element | main.rs:442:9:442:20 | TuplePat | -| main.rs:444:21:444:24 | name | &ref | main.rs:444:21:444:24 | receiver for name | | main.rs:444:41:444:67 | [post] \|...\| ... | captured default_name | main.rs:444:41:444:67 | [post] default_name | -| main.rs:444:44:444:55 | default_name | &ref | main.rs:444:44:444:55 | receiver for default_name | | main.rs:444:44:444:55 | this | captured default_name | main.rs:444:44:444:55 | default_name | -| main.rs:445:18:445:18 | n | &ref | main.rs:445:18:445:18 | receiver for n | -| main.rs:468:13:468:13 | a | &ref | main.rs:468:13:468:13 | receiver for a | -| main.rs:469:13:469:13 | b | &ref | main.rs:469:13:469:13 | receiver for b | -| main.rs:470:19:470:19 | b | &ref | main.rs:470:19:470:19 | receiver for b | | main.rs:481:10:481:11 | vs | element | main.rs:481:10:481:14 | vs[0] | -| main.rs:482:11:482:12 | vs | &ref | main.rs:482:11:482:12 | receiver for vs | | main.rs:482:11:482:35 | ... .unwrap() | &ref | main.rs:482:10:482:35 | * ... | -| main.rs:483:11:483:12 | vs | &ref | main.rs:483:11:483:12 | receiver for vs | | main.rs:483:11:483:35 | ... .unwrap() | &ref | main.rs:483:10:483:35 | * ... | | main.rs:485:14:485:15 | vs | element | main.rs:485:9:485:9 | v | | main.rs:488:9:488:10 | &... | &ref | main.rs:488:10:488:10 | v | -| main.rs:488:15:488:16 | vs | &ref | main.rs:488:15:488:16 | receiver for vs | | main.rs:488:15:488:23 | vs.iter() | element | main.rs:488:9:488:10 | &... | -| main.rs:492:27:492:28 | vs | &ref | main.rs:492:27:492:28 | receiver for vs | | main.rs:493:9:493:10 | &... | &ref | main.rs:493:10:493:10 | v | | main.rs:493:15:493:17 | vs2 | element | main.rs:493:9:493:10 | &... | -| main.rs:497:5:497:6 | vs | &ref | main.rs:497:5:497:6 | receiver for vs | | main.rs:497:29:497:29 | x | &ref | main.rs:497:28:497:29 | * ... | -| main.rs:498:5:498:6 | vs | &ref | main.rs:498:5:498:6 | receiver for vs | | main.rs:498:34:498:34 | x | &ref | main.rs:498:33:498:34 | * ... | -| main.rs:500:14:500:15 | vs | &ref | main.rs:500:14:500:15 | receiver for vs | | main.rs:500:14:500:27 | vs.into_iter() | element | main.rs:500:9:500:9 | v | | main.rs:506:10:506:15 | vs_mut | element | main.rs:506:10:506:18 | vs_mut[0] | -| main.rs:507:11:507:16 | vs_mut | &ref | main.rs:507:11:507:16 | receiver for vs_mut | | main.rs:507:11:507:39 | ... .unwrap() | &ref | main.rs:507:10:507:39 | * ... | -| main.rs:508:11:508:16 | vs_mut | &ref | main.rs:508:11:508:16 | receiver for vs_mut | | main.rs:508:11:508:39 | ... .unwrap() | &ref | main.rs:508:10:508:39 | * ... | | main.rs:510:9:510:14 | &mut ... | &ref | main.rs:510:14:510:14 | v | -| main.rs:510:19:510:24 | vs_mut | &ref | main.rs:510:19:510:24 | receiver for vs_mut | | main.rs:510:19:510:35 | vs_mut.iter_mut() | element | main.rs:510:9:510:14 | &mut ... | | main.rs:524:11:524:15 | c_ref | &ref | main.rs:524:10:524:15 | * ... | -| main.rs:531:10:531:10 | a | &ref | main.rs:531:10:531:10 | receiver for a | -| main.rs:537:10:537:10 | b | &ref | main.rs:537:10:537:10 | receiver for b | diff --git a/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected b/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected index a7d350ee5ac..198f8ab9cd8 100644 --- a/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected +++ b/rust/ql/test/library-tests/dataflow/modeled/inline-flow.expected @@ -8,36 +8,29 @@ models | 7 | Summary: lang:core; crate::ptr::write; Argument[1]; Argument[0].Reference; value | edges | main.rs:12:9:12:9 | a [Some] | main.rs:13:10:13:19 | a.unwrap() | provenance | MaD:2 | +| main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:13 | a [Some] | provenance | | | main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:21 | a.clone() [Some] | provenance | MaD:1 | -| main.rs:12:9:12:9 | a [Some] | main.rs:14:13:14:21 | a.clone() [Some] | provenance | generated | | main.rs:12:13:12:28 | Some(...) [Some] | main.rs:12:9:12:9 | a [Some] | provenance | | | main.rs:12:18:12:27 | source(...) | main.rs:12:13:12:28 | Some(...) [Some] | provenance | | | main.rs:14:9:14:9 | b [Some] | main.rs:15:10:15:19 | b.unwrap() | provenance | MaD:2 | +| main.rs:14:13:14:13 | a [Some] | main.rs:14:13:14:21 | a.clone() [Some] | provenance | generated | | main.rs:14:13:14:21 | a.clone() [Some] | main.rs:14:9:14:9 | b [Some] | provenance | | | main.rs:19:9:19:9 | a [Ok] | main.rs:20:10:20:19 | a.unwrap() | provenance | MaD:5 | +| main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:13 | a [Ok] | provenance | | | main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | MaD:4 | -| main.rs:19:9:19:9 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | generated | | main.rs:19:31:19:44 | Ok(...) [Ok] | main.rs:19:9:19:9 | a [Ok] | provenance | | | main.rs:19:34:19:43 | source(...) | main.rs:19:31:19:44 | Ok(...) [Ok] | provenance | | | main.rs:21:9:21:9 | b [Ok] | main.rs:22:10:22:19 | b.unwrap() | provenance | MaD:5 | +| main.rs:21:13:21:13 | a [Ok] | main.rs:21:13:21:21 | a.clone() [Ok] | provenance | generated | | main.rs:21:13:21:21 | a.clone() [Ok] | main.rs:21:9:21:9 | b [Ok] | provenance | | | main.rs:26:9:26:9 | a | main.rs:27:10:27:10 | a | provenance | | -| main.rs:26:9:26:9 | a | main.rs:28:13:28:21 | a.clone() | provenance | generated | | main.rs:26:13:26:22 | source(...) | main.rs:26:9:26:9 | a | provenance | | -| main.rs:28:9:28:9 | b | main.rs:29:10:29:10 | b | provenance | | -| main.rs:28:13:28:21 | a.clone() | main.rs:28:9:28:9 | b | provenance | | | main.rs:41:13:41:13 | w [Wrapper] | main.rs:42:15:42:15 | w [Wrapper] | provenance | | | main.rs:41:17:41:41 | Wrapper {...} [Wrapper] | main.rs:41:13:41:13 | w [Wrapper] | provenance | | | main.rs:41:30:41:39 | source(...) | main.rs:41:17:41:41 | Wrapper {...} [Wrapper] | provenance | | | main.rs:42:15:42:15 | w [Wrapper] | main.rs:43:13:43:28 | Wrapper {...} [Wrapper] | provenance | | -| main.rs:42:15:42:15 | w [Wrapper] | main.rs:45:17:45:25 | w.clone() [Wrapper] | provenance | generated | | main.rs:43:13:43:28 | Wrapper {...} [Wrapper] | main.rs:43:26:43:26 | n | provenance | | | main.rs:43:26:43:26 | n | main.rs:43:38:43:38 | n | provenance | | -| main.rs:45:13:45:13 | u [Wrapper] | main.rs:46:15:46:15 | u [Wrapper] | provenance | | -| main.rs:45:17:45:25 | w.clone() [Wrapper] | main.rs:45:13:45:13 | u [Wrapper] | provenance | | -| main.rs:46:15:46:15 | u [Wrapper] | main.rs:47:13:47:28 | Wrapper {...} [Wrapper] | provenance | | -| main.rs:47:13:47:28 | Wrapper {...} [Wrapper] | main.rs:47:26:47:26 | n | provenance | | -| main.rs:47:26:47:26 | n | main.rs:47:38:47:38 | n | provenance | | | main.rs:58:13:58:13 | b [Some] | main.rs:59:23:59:23 | b [Some] | provenance | | | main.rs:58:17:58:32 | Some(...) [Some] | main.rs:58:13:58:13 | b [Some] | provenance | | | main.rs:58:22:58:31 | source(...) | main.rs:58:17:58:32 | Some(...) [Some] | provenance | | @@ -57,6 +50,7 @@ nodes | main.rs:12:18:12:27 | source(...) | semmle.label | source(...) | | main.rs:13:10:13:19 | a.unwrap() | semmle.label | a.unwrap() | | main.rs:14:9:14:9 | b [Some] | semmle.label | b [Some] | +| main.rs:14:13:14:13 | a [Some] | semmle.label | a [Some] | | main.rs:14:13:14:21 | a.clone() [Some] | semmle.label | a.clone() [Some] | | main.rs:15:10:15:19 | b.unwrap() | semmle.label | b.unwrap() | | main.rs:19:9:19:9 | a [Ok] | semmle.label | a [Ok] | @@ -64,14 +58,12 @@ nodes | main.rs:19:34:19:43 | source(...) | semmle.label | source(...) | | main.rs:20:10:20:19 | a.unwrap() | semmle.label | a.unwrap() | | main.rs:21:9:21:9 | b [Ok] | semmle.label | b [Ok] | +| main.rs:21:13:21:13 | a [Ok] | semmle.label | a [Ok] | | main.rs:21:13:21:21 | a.clone() [Ok] | semmle.label | a.clone() [Ok] | | main.rs:22:10:22:19 | b.unwrap() | semmle.label | b.unwrap() | | main.rs:26:9:26:9 | a | semmle.label | a | | main.rs:26:13:26:22 | source(...) | semmle.label | source(...) | | main.rs:27:10:27:10 | a | semmle.label | a | -| main.rs:28:9:28:9 | b | semmle.label | b | -| main.rs:28:13:28:21 | a.clone() | semmle.label | a.clone() | -| main.rs:29:10:29:10 | b | semmle.label | b | | main.rs:41:13:41:13 | w [Wrapper] | semmle.label | w [Wrapper] | | main.rs:41:17:41:41 | Wrapper {...} [Wrapper] | semmle.label | Wrapper {...} [Wrapper] | | main.rs:41:30:41:39 | source(...) | semmle.label | source(...) | @@ -79,12 +71,6 @@ nodes | main.rs:43:13:43:28 | Wrapper {...} [Wrapper] | semmle.label | Wrapper {...} [Wrapper] | | main.rs:43:26:43:26 | n | semmle.label | n | | main.rs:43:38:43:38 | n | semmle.label | n | -| main.rs:45:13:45:13 | u [Wrapper] | semmle.label | u [Wrapper] | -| main.rs:45:17:45:25 | w.clone() [Wrapper] | semmle.label | w.clone() [Wrapper] | -| main.rs:46:15:46:15 | u [Wrapper] | semmle.label | u [Wrapper] | -| main.rs:47:13:47:28 | Wrapper {...} [Wrapper] | semmle.label | Wrapper {...} [Wrapper] | -| main.rs:47:26:47:26 | n | semmle.label | n | -| main.rs:47:38:47:38 | n | semmle.label | n | | main.rs:58:13:58:13 | b [Some] | semmle.label | b [Some] | | main.rs:58:17:58:32 | Some(...) [Some] | semmle.label | Some(...) [Some] | | main.rs:58:22:58:31 | source(...) | semmle.label | source(...) | @@ -108,8 +94,6 @@ testFailures | main.rs:20:10:20:19 | a.unwrap() | main.rs:19:34:19:43 | source(...) | main.rs:20:10:20:19 | a.unwrap() | $@ | main.rs:19:34:19:43 | source(...) | source(...) | | main.rs:22:10:22:19 | b.unwrap() | main.rs:19:34:19:43 | source(...) | main.rs:22:10:22:19 | b.unwrap() | $@ | main.rs:19:34:19:43 | source(...) | source(...) | | main.rs:27:10:27:10 | a | main.rs:26:13:26:22 | source(...) | main.rs:27:10:27:10 | a | $@ | main.rs:26:13:26:22 | source(...) | source(...) | -| main.rs:29:10:29:10 | b | main.rs:26:13:26:22 | source(...) | main.rs:29:10:29:10 | b | $@ | main.rs:26:13:26:22 | source(...) | source(...) | | main.rs:43:38:43:38 | n | main.rs:41:30:41:39 | source(...) | main.rs:43:38:43:38 | n | $@ | main.rs:41:30:41:39 | source(...) | source(...) | -| main.rs:47:38:47:38 | n | main.rs:41:30:41:39 | source(...) | main.rs:47:38:47:38 | n | $@ | main.rs:41:30:41:39 | source(...) | source(...) | | main.rs:63:22:63:22 | m | main.rs:58:22:58:31 | source(...) | main.rs:63:22:63:22 | m | $@ | main.rs:58:22:58:31 | source(...) | source(...) | | main.rs:85:18:85:34 | ...::read(...) | main.rs:84:32:84:41 | source(...) | main.rs:85:18:85:34 | ...::read(...) | $@ | main.rs:84:32:84:41 | source(...) | source(...) | diff --git a/rust/ql/test/library-tests/dataflow/modeled/main.rs b/rust/ql/test/library-tests/dataflow/modeled/main.rs index cb955ce32bd..648ffd0a307 100644 --- a/rust/ql/test/library-tests/dataflow/modeled/main.rs +++ b/rust/ql/test/library-tests/dataflow/modeled/main.rs @@ -26,7 +26,7 @@ fn i64_clone() { let a = source(12); sink(a); // $ hasValueFlow=12 let b = a.clone(); - sink(b); // $ hasValueFlow=12 + sink(b); // $ MISSING: hasValueFlow=12 - lack of builtins means that we cannot resolve clone call above, and hence not insert implicit borrow } mod my_clone { @@ -44,7 +44,7 @@ mod my_clone { } let u = w.clone(); match u { - Wrapper { n: n } => sink(n), // $ hasValueFlow=73 + Wrapper { n: n } => sink(n), // $ MISSING: hasValueFlow=73 - lack of expanded derives means that we cannot resolve clone call above, and hence not insert implicit borrow } } } diff --git a/rust/ql/test/library-tests/frameworks/postgres/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/library-tests/frameworks/postgres/CONSISTENCY/PathResolutionConsistency.expected new file mode 100644 index 00000000000..c47d16d6875 --- /dev/null +++ b/rust/ql/test/library-tests/frameworks/postgres/CONSISTENCY/PathResolutionConsistency.expected @@ -0,0 +1,17 @@ +multipleMethodCallTargets +| main.rs:11:5:18:5 | conn.execute(...) | file://:0:0:0:0 | fn execute | +| main.rs:11:5:18:5 | conn.execute(...) | file://:0:0:0:0 | fn execute | +| main.rs:22:5:22:37 | conn.execute(...) | file://:0:0:0:0 | fn execute | +| main.rs:22:5:22:37 | conn.execute(...) | file://:0:0:0:0 | fn execute | +| main.rs:23:5:23:38 | conn.batch_execute(...) | file://:0:0:0:0 | fn batch_execute | +| main.rs:23:5:23:38 | conn.batch_execute(...) | file://:0:0:0:0 | fn batch_execute | +| main.rs:25:5:25:32 | conn.prepare(...) | file://:0:0:0:0 | fn prepare | +| main.rs:25:5:25:32 | conn.prepare(...) | file://:0:0:0:0 | fn prepare | +| main.rs:28:5:28:35 | conn.query(...) | file://:0:0:0:0 | fn query | +| main.rs:28:5:28:35 | conn.query(...) | file://:0:0:0:0 | fn query | +| main.rs:29:5:29:39 | conn.query_one(...) | file://:0:0:0:0 | fn query_one | +| main.rs:29:5:29:39 | conn.query_one(...) | file://:0:0:0:0 | fn query_one | +| main.rs:30:5:30:39 | conn.query_opt(...) | file://:0:0:0:0 | fn query_opt | +| main.rs:30:5:30:39 | conn.query_opt(...) | file://:0:0:0:0 | fn query_opt | +| main.rs:35:17:35:67 | conn.query(...) | file://:0:0:0:0 | fn query | +| main.rs:35:17:35:67 | conn.query(...) | file://:0:0:0:0 | fn query | diff --git a/rust/ql/test/library-tests/type-inference/main.rs b/rust/ql/test/library-tests/type-inference/main.rs index 1972b181c83..fa16b626474 100644 --- a/rust/ql/test/library-tests/type-inference/main.rs +++ b/rust/ql/test/library-tests/type-inference/main.rs @@ -919,6 +919,65 @@ mod borrowed_typed { } } +mod try_expressions { + use std::fmt::Debug; + + #[derive(Debug)] + struct S1; + + #[derive(Debug)] + struct S2; + + // Simple function using ? operator with same error types + fn try_same_error() -> Result { + let x = Result::Ok(S1)?; // $ type=x:S1 + Result::Ok(S1) + } + + // Function using ? operator with different error types that need conversion + fn try_convert_error() -> Result { + let x = Result::Ok(S1); + let y = x?; // $ type=y:S1 + Result::Ok(S1) + } + + // Chained ? operations + fn try_chained() -> Result { + let x = Result::Ok(Result::Ok(S1)); + // First ? returns Result, second ? returns S1 + let y = x?.map(|s| s)?; // $ method=map + Result::Ok(S1) + } + + // Function that uses ? with closures and complex error cases + fn try_complex(input: Result) -> Result { + let value = input?; + let mapped = Result::Ok(value).and_then(|v| { + println!("{:?}", v); + Result::Ok::<_, S1>(v) + })?; // $ method=and_then + Result::Err(S1) + } + + pub fn f() { + if let Result::Ok(result) = try_same_error() { + println!("{:?}", result); + } + + if let Result::Ok(result) = try_convert_error() { + println!("{:?}", result); + } + + if let Result::Ok(result) = try_chained() { + println!("{:?}", result); + } + + if let Result::Ok(result) = try_complex(Result::Ok(S1)) { + println!("{:?}", result); + } + } +} + fn main() { field_access::f(); method_impl::f(); @@ -935,4 +994,5 @@ fn main() { trait_implicit_self_borrow::f(); implicit_self_borrow::f(); borrowed_typed::f(); + try_expressions::f(); } diff --git a/rust/ql/test/library-tests/type-inference/type-inference.expected b/rust/ql/test/library-tests/type-inference/type-inference.expected index c91b3cef3dc..42e5d90701b 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.expected +++ b/rust/ql/test/library-tests/type-inference/type-inference.expected @@ -1005,7 +1005,96 @@ inferType | main.rs:918:15:918:16 | &x | | file://:0:0:0:0 | & | | main.rs:918:15:918:16 | &x | &T | main.rs:894:5:894:13 | S | | main.rs:918:16:918:16 | x | | main.rs:894:5:894:13 | S | -| main.rs:924:5:924:20 | ...::f(...) | | main.rs:67:5:67:21 | Foo | -| main.rs:925:5:925:60 | ...::g(...) | | main.rs:67:5:67:21 | Foo | -| main.rs:925:20:925:38 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | -| main.rs:925:41:925:59 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | +| main.rs:932:43:935:5 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:932:43:935:5 | { ... } | E | main.rs:925:5:926:14 | S1 | +| main.rs:932:43:935:5 | { ... } | T | main.rs:925:5:926:14 | S1 | +| main.rs:933:13:933:13 | x | | main.rs:925:5:926:14 | S1 | +| main.rs:933:17:933:30 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:933:17:933:30 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:933:17:933:31 | TryExpr | | main.rs:925:5:926:14 | S1 | +| main.rs:933:28:933:29 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:934:9:934:22 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:934:9:934:22 | ...::Ok(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:934:9:934:22 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:934:20:934:21 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:938:46:942:5 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:938:46:942:5 | { ... } | E | main.rs:928:5:929:14 | S2 | +| main.rs:938:46:942:5 | { ... } | T | main.rs:925:5:926:14 | S1 | +| main.rs:939:13:939:13 | x | | file://:0:0:0:0 | Result | +| main.rs:939:13:939:13 | x | T | main.rs:925:5:926:14 | S1 | +| main.rs:939:17:939:30 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:939:17:939:30 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:939:28:939:29 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:940:13:940:13 | y | | main.rs:925:5:926:14 | S1 | +| main.rs:940:17:940:17 | x | | file://:0:0:0:0 | Result | +| main.rs:940:17:940:17 | x | T | main.rs:925:5:926:14 | S1 | +| main.rs:940:17:940:18 | TryExpr | | main.rs:925:5:926:14 | S1 | +| main.rs:941:9:941:22 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:941:9:941:22 | ...::Ok(...) | E | main.rs:928:5:929:14 | S2 | +| main.rs:941:9:941:22 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:941:20:941:21 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:945:40:950:5 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:945:40:950:5 | { ... } | E | main.rs:928:5:929:14 | S2 | +| main.rs:945:40:950:5 | { ... } | T | main.rs:925:5:926:14 | S1 | +| main.rs:946:13:946:13 | x | | file://:0:0:0:0 | Result | +| main.rs:946:13:946:13 | x | T | file://:0:0:0:0 | Result | +| main.rs:946:13:946:13 | x | T.T | main.rs:925:5:926:14 | S1 | +| main.rs:946:17:946:42 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:946:17:946:42 | ...::Ok(...) | T | file://:0:0:0:0 | Result | +| main.rs:946:17:946:42 | ...::Ok(...) | T.T | main.rs:925:5:926:14 | S1 | +| main.rs:946:28:946:41 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:946:28:946:41 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:946:39:946:40 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:948:17:948:17 | x | | file://:0:0:0:0 | Result | +| main.rs:948:17:948:17 | x | T | file://:0:0:0:0 | Result | +| main.rs:948:17:948:17 | x | T.T | main.rs:925:5:926:14 | S1 | +| main.rs:948:17:948:18 | TryExpr | | file://:0:0:0:0 | Result | +| main.rs:948:17:948:18 | TryExpr | T | main.rs:925:5:926:14 | S1 | +| main.rs:948:17:948:29 | ... .map(...) | | file://:0:0:0:0 | Result | +| main.rs:949:9:949:22 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:949:9:949:22 | ...::Ok(...) | E | main.rs:928:5:929:14 | S2 | +| main.rs:949:9:949:22 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:949:20:949:21 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:953:30:953:34 | input | | file://:0:0:0:0 | Result | +| main.rs:953:30:953:34 | input | E | main.rs:925:5:926:14 | S1 | +| main.rs:953:30:953:34 | input | T | main.rs:953:20:953:27 | T | +| main.rs:953:69:960:5 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:953:69:960:5 | { ... } | E | main.rs:925:5:926:14 | S1 | +| main.rs:953:69:960:5 | { ... } | T | main.rs:953:20:953:27 | T | +| main.rs:954:13:954:17 | value | | main.rs:953:20:953:27 | T | +| main.rs:954:21:954:25 | input | | file://:0:0:0:0 | Result | +| main.rs:954:21:954:25 | input | E | main.rs:925:5:926:14 | S1 | +| main.rs:954:21:954:25 | input | T | main.rs:953:20:953:27 | T | +| main.rs:954:21:954:26 | TryExpr | | main.rs:953:20:953:27 | T | +| main.rs:955:22:955:38 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:955:22:955:38 | ...::Ok(...) | T | main.rs:953:20:953:27 | T | +| main.rs:955:22:958:10 | ... .and_then(...) | | file://:0:0:0:0 | Result | +| main.rs:955:33:955:37 | value | | main.rs:953:20:953:27 | T | +| main.rs:955:53:958:9 | { ... } | | file://:0:0:0:0 | Result | +| main.rs:955:53:958:9 | { ... } | E | main.rs:925:5:926:14 | S1 | +| main.rs:957:13:957:34 | ...::Ok::<...>(...) | | file://:0:0:0:0 | Result | +| main.rs:957:13:957:34 | ...::Ok::<...>(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:959:9:959:23 | ...::Err(...) | | file://:0:0:0:0 | Result | +| main.rs:959:9:959:23 | ...::Err(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:959:9:959:23 | ...::Err(...) | T | main.rs:953:20:953:27 | T | +| main.rs:959:21:959:22 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:963:37:963:52 | try_same_error(...) | | file://:0:0:0:0 | Result | +| main.rs:963:37:963:52 | try_same_error(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:963:37:963:52 | try_same_error(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:967:37:967:55 | try_convert_error(...) | | file://:0:0:0:0 | Result | +| main.rs:967:37:967:55 | try_convert_error(...) | E | main.rs:928:5:929:14 | S2 | +| main.rs:967:37:967:55 | try_convert_error(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:971:37:971:49 | try_chained(...) | | file://:0:0:0:0 | Result | +| main.rs:971:37:971:49 | try_chained(...) | E | main.rs:928:5:929:14 | S2 | +| main.rs:971:37:971:49 | try_chained(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:975:37:975:63 | try_complex(...) | | file://:0:0:0:0 | Result | +| main.rs:975:37:975:63 | try_complex(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:975:37:975:63 | try_complex(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:975:49:975:62 | ...::Ok(...) | | file://:0:0:0:0 | Result | +| main.rs:975:49:975:62 | ...::Ok(...) | E | main.rs:925:5:926:14 | S1 | +| main.rs:975:49:975:62 | ...::Ok(...) | T | main.rs:925:5:926:14 | S1 | +| main.rs:975:60:975:61 | S1 | | main.rs:925:5:926:14 | S1 | +| main.rs:983:5:983:20 | ...::f(...) | | main.rs:67:5:67:21 | Foo | +| main.rs:984:5:984:60 | ...::g(...) | | main.rs:67:5:67:21 | Foo | +| main.rs:984:20:984:38 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | +| main.rs:984:41:984:59 | ...::Foo {...} | | main.rs:67:5:67:21 | Foo | diff --git a/rust/ql/test/library-tests/type-inference/type-inference.ql b/rust/ql/test/library-tests/type-inference/type-inference.ql index d83900e5840..2652699558f 100644 --- a/rust/ql/test/library-tests/type-inference/type-inference.ql +++ b/rust/ql/test/library-tests/type-inference/type-inference.ql @@ -4,7 +4,8 @@ import codeql.rust.internal.TypeInference as TypeInference import TypeInference query predicate inferType(AstNode n, TypePath path, Type t) { - t = TypeInference::inferType(n, path) + t = TypeInference::inferType(n, path) and + n.fromSource() } module ResolveTest implements TestSig { diff --git a/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected b/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected new file mode 100644 index 00000000000..9567c4ea517 --- /dev/null +++ b/rust/ql/test/query-tests/security/CWE-089/CONSISTENCY/PathResolutionConsistency.expected @@ -0,0 +1,13 @@ +multipleMethodCallTargets +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | +| sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | file://:0:0:0:0 | fn as_str | diff --git a/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected b/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected index b5aa4a386f2..7c3c1419474 100644 --- a/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected +++ b/rust/ql/test/query-tests/security/CWE-089/SqlInjection.expected @@ -31,15 +31,11 @@ edges | sqlx.rs:52:32:52:87 | ...::must_use(...) | sqlx.rs:52:9:52:20 | safe_query_3 | provenance | | | sqlx.rs:52:32:52:87 | MacroExpr | sqlx.rs:52:32:52:87 | ...::format(...) | provenance | MaD:4 | | sqlx.rs:52:32:52:87 | { ... } | sqlx.rs:52:32:52:87 | ...::must_use(...) | provenance | MaD:9 | -| sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | sqlx.rs:63:26:63:39 | unsafe_query_1 [&ref] | provenance | | | sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | sqlx.rs:63:26:63:48 | unsafe_query_1.as_str() | provenance | MaD:3 | -| sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | sqlx.rs:74:25:74:38 | unsafe_query_1 [&ref] | provenance | | | sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | sqlx.rs:74:25:74:47 | unsafe_query_1.as_str() | provenance | MaD:3 | | sqlx.rs:53:26:53:36 | &arg_string [&ref] | sqlx.rs:53:9:53:22 | unsafe_query_1 [&ref] | provenance | | | sqlx.rs:53:27:53:36 | arg_string | sqlx.rs:53:26:53:36 | &arg_string [&ref] | provenance | | -| sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | sqlx.rs:65:30:65:43 | unsafe_query_2 [&ref] | provenance | | | sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | provenance | MaD:3 | -| sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | sqlx.rs:76:29:76:42 | unsafe_query_2 [&ref] | provenance | | | sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | provenance | MaD:3 | | sqlx.rs:54:26:54:39 | &remote_string [&ref] | sqlx.rs:54:9:54:22 | unsafe_query_2 [&ref] | provenance | | | sqlx.rs:54:27:54:39 | remote_string | sqlx.rs:54:26:54:39 | &remote_string [&ref] | provenance | | @@ -50,10 +46,6 @@ edges | sqlx.rs:56:34:56:89 | ...::must_use(...) | sqlx.rs:56:9:56:22 | unsafe_query_4 | provenance | | | sqlx.rs:56:34:56:89 | MacroExpr | sqlx.rs:56:34:56:89 | ...::format(...) | provenance | MaD:4 | | sqlx.rs:56:34:56:89 | { ... } | sqlx.rs:56:34:56:89 | ...::must_use(...) | provenance | MaD:9 | -| sqlx.rs:63:26:63:39 | unsafe_query_1 [&ref] | sqlx.rs:63:26:63:48 | unsafe_query_1.as_str() | provenance | MaD:3 | -| sqlx.rs:65:30:65:43 | unsafe_query_2 [&ref] | sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | provenance | MaD:3 | -| sqlx.rs:74:25:74:38 | unsafe_query_1 [&ref] | sqlx.rs:74:25:74:47 | unsafe_query_1.as_str() | provenance | MaD:3 | -| sqlx.rs:76:29:76:42 | unsafe_query_2 [&ref] | sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | provenance | MaD:3 | models | 1 | Source: lang:std; crate::env::args; commandargs; ReturnValue.Element | | 2 | Source: repo:https://github.com/seanmonstar/reqwest:reqwest; crate::blocking::get; remote; ReturnValue.Field[crate::result::Result::Ok(0)] | @@ -100,15 +92,11 @@ nodes | sqlx.rs:56:34:56:89 | MacroExpr | semmle.label | MacroExpr | | sqlx.rs:56:34:56:89 | { ... } | semmle.label | { ... } | | sqlx.rs:62:26:62:46 | safe_query_3.as_str() | semmle.label | safe_query_3.as_str() | -| sqlx.rs:63:26:63:39 | unsafe_query_1 [&ref] | semmle.label | unsafe_query_1 [&ref] | | sqlx.rs:63:26:63:48 | unsafe_query_1.as_str() | semmle.label | unsafe_query_1.as_str() | -| sqlx.rs:65:30:65:43 | unsafe_query_2 [&ref] | semmle.label | unsafe_query_2 [&ref] | | sqlx.rs:65:30:65:52 | unsafe_query_2.as_str() | semmle.label | unsafe_query_2.as_str() | | sqlx.rs:67:30:67:52 | unsafe_query_4.as_str() | semmle.label | unsafe_query_4.as_str() | | sqlx.rs:73:25:73:45 | safe_query_3.as_str() | semmle.label | safe_query_3.as_str() | -| sqlx.rs:74:25:74:38 | unsafe_query_1 [&ref] | semmle.label | unsafe_query_1 [&ref] | | sqlx.rs:74:25:74:47 | unsafe_query_1.as_str() | semmle.label | unsafe_query_1.as_str() | -| sqlx.rs:76:29:76:42 | unsafe_query_2 [&ref] | semmle.label | unsafe_query_2 [&ref] | | sqlx.rs:76:29:76:51 | unsafe_query_2.as_str() | semmle.label | unsafe_query_2.as_str() | | sqlx.rs:78:29:78:51 | unsafe_query_4.as_str() | semmle.label | unsafe_query_4.as_str() | subpaths diff --git a/rust/ql/test/utils-tests/modelgenerator/CaptureSinkModels.ql b/rust/ql/test/utils-tests/modelgenerator/CaptureSinkModels.ql index 14edea3af8a..4b8041fb444 100644 --- a/rust/ql/test/utils-tests/modelgenerator/CaptureSinkModels.ql +++ b/rust/ql/test/utils-tests/modelgenerator/CaptureSinkModels.ql @@ -1,5 +1,6 @@ import rust import utils.modelgenerator.internal.CaptureModels +import SinkModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/rust/ql/test/utils-tests/modelgenerator/CaptureSourceModels.ql b/rust/ql/test/utils-tests/modelgenerator/CaptureSourceModels.ql index 66f0780448c..c535ce78704 100644 --- a/rust/ql/test/utils-tests/modelgenerator/CaptureSourceModels.ql +++ b/rust/ql/test/utils-tests/modelgenerator/CaptureSourceModels.ql @@ -1,5 +1,6 @@ import rust import utils.modelgenerator.internal.CaptureModels +import SourceModels import utils.test.InlineMadTest import codeql.rust.dataflow.internal.ModelsAsData diff --git a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected index b05e99f352b..b3a3717c930 100644 --- a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected +++ b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.expected @@ -1,12 +1,4 @@ unexpectedModel | Unexpected summary found: repo::test;::clone;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated | -| Unexpected summary found: repo::test;::from;Argument[0].Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)].Reference;value;dfc-generated | -| Unexpected summary found: repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated | -| Unexpected summary found: repo::test;::get_or_insert;Argument[0];Argument[self].Field[crate::option::MyOption::MySome(0)];value;dfc-generated | -| Unexpected summary found: repo::test;::get_or_insert;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Reference;value;dfc-generated | -| Unexpected summary found: repo::test;::get_or_insert_default;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Reference;value;dfc-generated | -| Unexpected summary found: repo::test;::get_or_insert_with;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Reference;value;dfc-generated | -| Unexpected summary found: repo::test;::insert;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Reference;value;dfc-generated | expectedModel -| Expected summary missing: repo::test;::take_if;Argument[self].Reference.Field[crate::option::MyOption::MySome(0)];Argument[0].Parameter[0].Reference;value;dfc-generated | -| Expected summary missing: repo::test;::take_if;Argument[self].Reference;ReturnValue;value;dfc-generated | +| Expected summary missing: repo::test;::clone;Argument[self].Reference.Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated | diff --git a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.ql b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.ql index 2ea8bd1ce6d..c68b3b18b8c 100644 --- a/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.ql +++ b/rust/ql/test/utils-tests/modelgenerator/CaptureSummaryModels.ql @@ -1,5 +1,6 @@ import rust import utils.modelgenerator.internal.CaptureModels +import SummaryModels import utils.test.InlineMadTest module InlineMadTestConfig implements InlineMadTestConfigSig { diff --git a/rust/ql/test/utils-tests/modelgenerator/option.rs b/rust/ql/test/utils-tests/modelgenerator/option.rs index 5e2e2391ba7..19b4a92fa37 100644 --- a/rust/ql/test/utils-tests/modelgenerator/option.rs +++ b/rust/ql/test/utils-tests/modelgenerator/option.rs @@ -414,7 +414,7 @@ impl MyOption<&T> { } } - // summary=repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated + // summary=repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated pub fn cloned(self) -> MyOption where T: Clone, @@ -438,7 +438,7 @@ impl MyOption<&mut T> { } } - // summary=repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated + // summary=repo::test;::cloned;Argument[self].Field[crate::option::MyOption::MySome(0)].Reference;ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated pub fn cloned(self) -> MyOption where T: Clone, @@ -466,7 +466,7 @@ impl Clone for MyOption where T: Clone, { - // summary=repo::test;::clone;Argument[self].Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated + // summary=repo::test;::clone;Argument[self].Reference.Field[crate::option::MyOption::MySome(0)];ReturnValue.Field[crate::option::MyOption::MySome(0)];value;dfc-generated fn clone(&self) -> Self { match self { MySome(x) => MySome(x.clone()), diff --git a/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll b/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll index 1856908206f..0c9e4349dfa 100644 --- a/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll +++ b/shared/mad/codeql/mad/modelgenerator/internal/ModelGeneratorImpl.qll @@ -16,7 +16,7 @@ private import codeql.util.Unit /** * Provides language-specific model generator parameters. */ -signature module ModelGeneratorInputSig Lang> { +signature module ModelGeneratorCommonInputSig Lang> { /** * A Type. */ @@ -50,52 +50,6 @@ signature module ModelGeneratorInputSig /** Gets the enclosing callable of `node`. */ Callable getEnclosingCallable(NodeExtended node); - /** - * Gets the enclosing callable of `node`, when considered as an expression. - */ - Callable getAsExprEnclosingCallable(NodeExtended node); - - /** Gets the parameter corresponding to this node, if any. */ - Parameter asParameter(NodeExtended n); - - /** - * A class of callables that are potentially relevant for generating summary or - * neutral models. - * - * In the Standard library and 3rd party libraries it is the callables (or callables that have a - * super implementation) that can be called from outside the library itself. - */ - class SummaryTargetApi extends Callable { - /** - * Gets the callable that a model will be lifted to. - * - * The lifted callable is relevant in terms of model - * generation (this is ensured by `liftedImpl`). - */ - Callable lift(); - - /** - * Holds if `this` is relevant in terms of model generation. - */ - predicate isRelevant(); - } - - /** - * A class of callables that are potentially relevant for generating source or - * sink models. - */ - class SourceOrSinkTargetApi extends Callable; - - /** - * A class of callables that are potentially relevant for generating source models. - */ - class SourceTargetApi extends SourceOrSinkTargetApi; - - /** - * A class of callables that are potentially relevant for generating sink models. - */ - class SinkTargetApi extends SourceOrSinkTargetApi; - /** * An instance parameter node. */ @@ -114,22 +68,6 @@ signature module ModelGeneratorInputSig */ Type getUnderlyingContentType(Lang::ContentSet c); - /** - * Gets the MaD string representation of the qualifier. - */ - string qualifierString(); - - /** - * Gets the MaD string representation of the parameter `p`. - */ - string parameterAccess(Parameter p); - - /** - * Gets the MaD string representation of the parameter `p` - * when used in content flow. - */ - string parameterContentAccess(Parameter p); - /** * Gets the MaD string representation of return through parameter at position * `pos` of callable `c`. @@ -155,69 +93,26 @@ signature module ModelGeneratorInputSig predicate isOwnInstanceAccessNode(Lang::ReturnNode node); /** - * Holds if `node` is a sanitizer for sink model construction. + * Gets the MaD string representation of the parameter `p`. */ - predicate sinkModelSanitizer(Lang::Node node); + string parameterAccess(Parameter p); /** - * Holds if `source` is an api entrypoint relevant for creating sink models. + * Gets the MaD string representation of the parameter `p` + * when used in content flow. */ - predicate apiSource(Lang::Node source); + string parameterContentAccess(Parameter p); /** - * Gets the MaD input string representation of `source`. + * Gets the MaD string representation of the qualifier. */ - string getInputArgument(Lang::Node source); - - /** - * Holds if it is not relevant to generate a source model for `api`, even - * if flow is detected from a node within `source` to a sink within `api`. - */ - bindingset[sourceEnclosing, api] - predicate irrelevantSourceSinkApi(Callable sourceEnclosing, SourceTargetApi api); - - /** - * Holds if `kind` is a relevant sink kind for creating sink models. - */ - bindingset[kind] - predicate isRelevantSinkKind(string kind); - - /** - * Holds if `kind` is a relevant source kind for creating source models. - */ - bindingset[kind] - predicate isRelevantSourceKind(string kind); + string qualifierString(); /** * Holds if the the content `c` is a container. */ predicate containerContent(Lang::ContentSet c); - /** - * Holds if there is a taint step from `node1` to `node2` in content flow. - */ - predicate isAdditionalContentFlowStep(Lang::Node nodeFrom, Lang::Node nodeTo); - - /** - * Holds if the content set `c` is field like. - */ - predicate isField(Lang::ContentSet c); - - /** - * Holds if the content set `c` is callback like. - */ - predicate isCallback(Lang::ContentSet c); - - /** - * Gets the MaD synthetic name string representation for the content set `c`, if any. - */ - string getSyntheticName(Lang::ContentSet c); - - /** - * Gets the MaD string representation of the content set `c`. - */ - string printContent(Lang::ContentSet c); - /** * Gets the parameter position of the return kind, if any. */ @@ -231,22 +126,6 @@ signature module ModelGeneratorInputSig */ default string getReturnValueString(Lang::ReturnKind kind) { result = "ReturnValue" } - /** - * Holds if it is irrelevant to generate models for `api` based on data flow analysis. - * - * This serves as an extra filter for the `relevant` predicate. - */ - predicate isUninterestingForDataFlowModels(Callable api); - - /** - * Holds if it is irrelevant to generate models for `api` based on the heuristic - * (non-content) flow analysis. - * - * This serves as an extra filter for the `relevant` - * and `isUninterestingForDataFlowModels` predicates. - */ - predicate isUninterestingForHeuristicDataFlowModels(Callable api); - /** * Gets the string representation for the `i`th column in the MaD row for `api`. */ @@ -256,23 +135,14 @@ signature module ModelGeneratorInputSig * Gets the string representation for the `i`th column in the neutral MaD row for `api`. */ string partialNeutralModelRow(Callable api, int i); - - /** - * Holds if `node` is specified as a source with the given kind in a MaD flow - * model. - */ - predicate sourceNode(Lang::Node node, string kind); - - /** - * Holds if `node` is specified as a sink with the given kind in a MaD flow - * model. - */ - predicate sinkNode(Lang::Node node, string kind); } -module MakeModelGenerator< +/** + * Make a factory for constructing different model generators. + */ +module MakeModelGeneratorFactory< LocationSig Location, InputSig Lang, Tt::InputSig TaintLang, - ModelGeneratorInputSig ModelGeneratorInput> + ModelGeneratorCommonInputSig ModelGeneratorInput> { private module DataFlow { import Lang @@ -339,16 +209,6 @@ module MakeModelGenerator< } } - final private class SummaryTargetApiFinal = SummaryTargetApi; - - class DataFlowSummaryTargetApi extends SummaryTargetApiFinal { - DataFlowSummaryTargetApi() { not isUninterestingForDataFlowModels(this) } - } - - class DataFlowSourceTargetApi = SourceTargetApi; - - class DataFlowSinkTargetApi = SinkTargetApi; - /** * Holds if `c` is a relevant content kind, where the underlying type is relevant. */ @@ -366,814 +226,1011 @@ module MakeModelGenerator< containerContent(c) } + private string getOutput(ReturnNodeExt node) { + result = PrintReturnNodeExt::getOutput(node) + } + /** - * Provides classes and predicates related to capturing models - * based on heuristic data flow. + * Provides language-specific summary model generator parameters. */ - module Heuristic { - private module ModelPrintingInput implements Printing::ModelPrintingSig { - class SummaryApi = DataFlowSummaryTargetApi; - - class SourceOrSinkApi = SourceOrSinkTargetApi; - - string getProvenance() { result = "df-generated" } - } - - module ModelPrinting = Printing::ModelPrinting; - - private string getOutput(ReturnNodeExt node) { - result = PrintReturnNodeExt::getOutput(node) - } - + signature module SummaryModelGeneratorInputSig { /** - * Holds if data can flow from `node1` to `node2` either via a read or a write of an intermediate field `f`. - */ - private predicate isRelevantTaintStep(DataFlow::Node node1, DataFlow::Node node2) { - exists(DataFlow::ContentSet f | - DataFlow::readStep(node1, f, node2) and - // Partially restrict the content types used for intermediate steps. - (not exists(getUnderlyingContentType(f)) or isRelevantTypeInContent(f)) - ) - or - exists(DataFlow::ContentSet f | DataFlow::storeStep(node1, f, node2) | containerContent(f)) - } - - /** - * Gets the MaD string representation of the parameter node `p`. - */ - string parameterNodeAsInput(DataFlow::ParameterNode p) { - result = parameterAccess(asParameter(p)) - or - result = qualifierString() and p instanceof InstanceParameterNode - } - - /** - * Gets the MaD input string representation of `source`. - */ - private string asInputArgument(NodeExtended source) { result = getInputArgument(source) } - - /** - * Gets the summary model of `api`, if it follows the `fluent` programming pattern (returns `this`). - */ - private string captureQualifierFlow(DataFlowSummaryTargetApi api) { - exists(ReturnNodeExt ret | - api = returnNodeEnclosingCallable(ret) and - isOwnInstanceAccessNode(ret) - ) and - result = ModelPrinting::asLiftedValueModel(api, qualifierString(), "ReturnValue") - } - - private int accessPathLimit0() { result = 2 } - - private newtype TTaintState = - TTaintRead(int n) { n in [0 .. accessPathLimit0()] } or - TTaintStore(int n) { n in [1 .. accessPathLimit0()] } - - abstract private class TaintState extends TTaintState { - abstract string toString(); - } - - /** - * A FlowState representing a tainted read. - */ - private class TaintRead extends TaintState, TTaintRead { - private int step; - - TaintRead() { this = TTaintRead(step) } - - /** - * Gets the flow state step number. - */ - int getStep() { result = step } - - override string toString() { result = "TaintRead(" + step + ")" } - } - - /** - * A FlowState representing a tainted write. - */ - private class TaintStore extends TaintState, TTaintStore { - private int step; - - TaintStore() { this = TTaintStore(step) } - - /** - * Gets the flow state step number. - */ - int getStep() { result = step } - - override string toString() { result = "TaintStore(" + step + ")" } - } - - private signature module PropagateFlowConfigInputSig { - class FlowState; - - FlowState initialState(); - - default predicate isAdditionalFlowStep( - DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 - ) { - none() - } - } - - private module PropagateFlowConfig - implements DataFlow::StateConfigSig - { - import PropagateFlowConfigInput - - predicate isSource(DataFlow::Node source, FlowState state) { - source instanceof DataFlow::ParameterNode and - exists(Callable c | - c = getEnclosingCallable(source) and - c instanceof DataFlowSummaryTargetApi and - not isUninterestingForHeuristicDataFlowModels(c) - ) and - state = initialState() - } - - predicate isSink(DataFlow::Node sink, FlowState state) { - // Sinks are provided by `isSink/1` - none() - } - - predicate isSink(DataFlow::Node sink) { - sink instanceof ReturnNodeExt and - not isOwnInstanceAccessNode(sink) and - not exists(captureQualifierFlow(getAsExprEnclosingCallable(sink))) - } - - predicate isAdditionalFlowStep = PropagateFlowConfigInput::isAdditionalFlowStep/4; - - predicate isBarrier(DataFlow::Node n) { - exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) - } - - DataFlow::FlowFeature getAFeature() { - result instanceof DataFlow::FeatureEqualSourceSinkCallContext - } - } - - /** - * A module used to construct a data flow configuration for tracking taint- - * flow through APIs. - * The sources are the parameters of an API and the sinks are the return - * values (excluding `this`) and parameters. + * A class of callables that are potentially relevant for generating summary or + * neutral models. * - * This can be used to generate flow summaries for APIs from parameter to - * return. + * In the Standard library and 3rd party libraries it is the callables (or callables that have a + * super implementation) that can be called from outside the library itself. */ - module PropagateFlowConfigInputTaintInput implements PropagateFlowConfigInputSig { - class FlowState = TaintState; + class SummaryTargetApi extends Callable { + /** + * Gets the callable that a model will be lifted to. + * + * The lifted callable is relevant in terms of model + * generation (this is ensured by `liftedImpl`). + */ + Callable lift(); - FlowState initialState() { result.(TaintRead).getStep() = 0 } + /** + * Holds if `this` is relevant in terms of model generation. + */ + predicate isRelevant(); + } - predicate isAdditionalFlowStep( - DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 + /** + * Gets the enclosing callable of `node`, when considered as an expression. + */ + Callable getAsExprEnclosingCallable(NodeExtended node); + + /** + * Gets the parameter corresponding to this node, if any. + */ + Parameter asParameter(NodeExtended n); + + /** + * Holds if there is a taint step from `node1` to `node2` in content flow. + */ + predicate isAdditionalContentFlowStep(Lang::Node nodeFrom, Lang::Node nodeTo); + + /** + * Holds if the content set `c` is field like. + */ + predicate isField(Lang::ContentSet c); + + /** + * Holds if the content set `c` is callback like. + */ + predicate isCallback(Lang::ContentSet c); + + /** + * Gets the MaD synthetic name string representation for the content set `c`, if any. + */ + string getSyntheticName(Lang::ContentSet c); + + /** + * Gets the MaD string representation of the content set `c`. + */ + string printContent(Lang::ContentSet c); + + /** + * Holds if it is irrelevant to generate models for `api` based on data flow analysis. + * + * This serves as an extra filter for the `relevant` predicate. + */ + default predicate isUninterestingForDataFlowModels(Callable api) { none() } + + /** + * Holds if it is irrelevant to generate models for `api` based on the heuristic + * (non-content) flow analysis. + * + * This serves as an extra filter for the `relevant` + * and `isUninterestingForDataFlowModels` predicates. + */ + default predicate isUninterestingForHeuristicDataFlowModels(Callable api) { none() } + } + + /** + * Make a summary model generator. + */ + module MakeSummaryModelGenerator { + private import SummaryModelGeneratorInput + + final private class SummaryTargetApiFinal = SummaryTargetApi; + + class DataFlowSummaryTargetApi extends SummaryTargetApiFinal { + DataFlowSummaryTargetApi() { not isUninterestingForDataFlowModels(this) } + } + + /** + * Provides classes and predicates related to capturing summary models + * based on heuristic data flow. + */ + module Heuristic { + private module ModelPrintingSummaryInput implements Printing::ModelPrintingSummarySig { + class SummaryApi = DataFlowSummaryTargetApi; + + string getProvenance() { result = "df-generated" } + } + + module ModelPrintingSummary = Printing::ModelPrintingSummary; + + /** + * Gets the MaD string representation of the parameter node `p`. + */ + string parameterNodeAsInput(DataFlow::ParameterNode p) { + result = parameterAccess(asParameter(p)) + or + result = qualifierString() and p instanceof InstanceParameterNode + } + + /** + * Gets the summary model of `api`, if it follows the `fluent` programming pattern (returns `this`). + */ + private string captureQualifierFlow(DataFlowSummaryTargetApi api) { + exists(ReturnNodeExt ret | + api = returnNodeEnclosingCallable(ret) and + isOwnInstanceAccessNode(ret) + ) and + result = ModelPrintingSummary::asLiftedValueModel(api, qualifierString(), "ReturnValue") + } + + private int accessPathLimit0() { result = 2 } + + private newtype TTaintState = + TTaintRead(int n) { n in [0 .. accessPathLimit0()] } or + TTaintStore(int n) { n in [1 .. accessPathLimit0()] } + + abstract private class TaintState extends TTaintState { + abstract string toString(); + } + + /** + * A FlowState representing a tainted read. + */ + private class TaintRead extends TaintState, TTaintRead { + private int step; + + TaintRead() { this = TTaintRead(step) } + + /** + * Gets the flow state step number. + */ + int getStep() { result = step } + + override string toString() { result = "TaintRead(" + step + ")" } + } + + /** + * A FlowState representing a tainted write. + */ + private class TaintStore extends TaintState, TTaintStore { + private int step; + + TaintStore() { this = TTaintStore(step) } + + /** + * Gets the flow state step number. + */ + int getStep() { result = step } + + override string toString() { result = "TaintStore(" + step + ")" } + } + + private signature module PropagateFlowConfigInputSig { + class FlowState; + + FlowState initialState(); + + default predicate isAdditionalFlowStep( + DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 + ) { + none() + } + } + + private module PropagateFlowConfig + implements DataFlow::StateConfigSig + { + import PropagateFlowConfigInput + + predicate isSource(DataFlow::Node source, FlowState state) { + source instanceof DataFlow::ParameterNode and + exists(Callable c | + c = getEnclosingCallable(source) and + c instanceof DataFlowSummaryTargetApi and + not isUninterestingForHeuristicDataFlowModels(c) + ) and + state = initialState() + } + + predicate isSink(DataFlow::Node sink, FlowState state) { + // Sinks are provided by `isSink/1` + none() + } + + predicate isSink(DataFlow::Node sink) { + sink instanceof ReturnNodeExt and + not isOwnInstanceAccessNode(sink) and + not exists(captureQualifierFlow(getAsExprEnclosingCallable(sink))) + } + + predicate isAdditionalFlowStep = PropagateFlowConfigInput::isAdditionalFlowStep/4; + + predicate isBarrier(DataFlow::Node n) { + exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) + } + + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureEqualSourceSinkCallContext + } + } + + /** + * A module used to construct a data flow configuration for tracking taint- + * flow through APIs. + * The sources are the parameters of an API and the sinks are the return + * values (excluding `this`) and parameters. + * + * This can be used to generate flow summaries for APIs from parameter to + * return. + */ + module PropagateFlowConfigInputTaintInput implements PropagateFlowConfigInputSig { + class FlowState = TaintState; + + FlowState initialState() { result.(TaintRead).getStep() = 0 } + + predicate isAdditionalFlowStep( + DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2 + ) { + exists(DataFlow::NodeEx n1, DataFlow::NodeEx n2, DataFlow::ContentSet c | + node1 = n1.asNode() and + node2 = n2.asNode() and + DataFlow::storeEx(n1, c.getAStoreContent(), n2, _, _) and + isRelevantContent0(c) and + ( + state1 instanceof TaintRead and state2.(TaintStore).getStep() = 1 + or + state1.(TaintStore).getStep() + 1 = state2.(TaintStore).getStep() + ) + ) + or + exists(DataFlow::ContentSet c | + DataFlow::readStep(node1, c, node2) and + isRelevantContent0(c) and + state1.(TaintRead).getStep() + 1 = state2.(TaintRead).getStep() + ) + } + } + + /** + * A data flow configuration for tracking taint-flow through APIs. + * The sources are the parameters of an API and the sinks are the return + * values (excluding `this`) and parameters. + * + * This can be used to generate flow summaries for APIs from parameter to + * return. + */ + private module PropagateTaintFlowConfig = + PropagateFlowConfig; + + module PropagateTaintFlow = TaintTracking::GlobalWithState; + + /** + * A module used to construct a data flow configuration for tracking + * data flow through APIs. + * The sources are the parameters of an API and the sinks are the return + * values (excluding `this`) and parameters. + * + * This can be used to generate value-preserving flow summaries for APIs + * from parameter to return. + */ + module PropagateFlowConfigInputDataFlowInput implements PropagateFlowConfigInputSig { + class FlowState = Unit; + + FlowState initialState() { any() } + } + + /** + * A data flow configuration for tracking data flow through APIs. + * The sources are the parameters of an API and the sinks are the return + * values (excluding `this`) and parameters. + * + * This can be used to generate flow summaries for APIs from parameter to + * return. + */ + private module PropagateDataFlowConfig = + PropagateFlowConfig; + + module PropagateDataFlow = DataFlow::GlobalWithState; + + predicate captureThroughFlow0( + DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt ) { - exists(DataFlow::NodeEx n1, DataFlow::NodeEx n2, DataFlow::ContentSet c | - node1 = n1.asNode() and - node2 = n2.asNode() and - DataFlow::storeEx(n1, c.getAStoreContent(), n2, _, _) and - isRelevantContent0(c) and - ( - state1 instanceof TaintRead and state2.(TaintStore).getStep() = 1 + captureThroughFlow0(api, p, _, returnNodeExt, _, _) + } + + /** + * Holds if there should be a summary of `api` specifying flow + * from `p` (with summary component `input`) to `returnNodeExt` (with + * summary component `output`). + * + * `preservesValue` is true if the summary is value-preserving, or `false` + * otherwise. + */ + private predicate captureThroughFlow0( + DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, string input, + ReturnNodeExt returnNodeExt, string output, boolean preservesValue + ) { + ( + PropagateDataFlow::flow(p, returnNodeExt) and preservesValue = true + or + not PropagateDataFlow::flow(p, returnNodeExt) and + PropagateTaintFlow::flow(p, returnNodeExt) and + preservesValue = false + ) and + getEnclosingCallable(p) = api and + getEnclosingCallable(returnNodeExt) = api and + input = parameterNodeAsInput(p) and + output = getOutput(returnNodeExt) and + input != output + } + + /** + * Gets the summary model(s) of `api`, if there is flow from parameters to return value or parameter. + * + * `preservesValue` is `true` if the summary is value-preserving, and `false` otherwise. + */ + private string captureThroughFlow(DataFlowSummaryTargetApi api, boolean preservesValue) { + exists(string input, string output | + preservesValue = max(boolean b | captureThroughFlow0(api, _, input, _, output, b)) and + result = ModelPrintingSummary::asLiftedTaintModel(api, input, output, preservesValue) + ) + } + + /** + * Gets the summary model(s) of `api`, if there is flow from parameters to the + * return value or parameter or if `api` is a fluent API. + * + * `preservesValue` is `true` if the summary is value-preserving, and `false` otherwise. + */ + string captureHeuristicFlow(DataFlowSummaryTargetApi api, boolean preservesValue) { + result = captureQualifierFlow(api) and preservesValue = true + or + result = captureThroughFlow(api, preservesValue) + } + + /** + * Gets the neutral summary model for `api`, if any. + * A neutral summary model is generated, if we are not generating + * a summary model that applies to `api`. + */ + string captureNoFlow(DataFlowSummaryTargetApi api) { + not exists(DataFlowSummaryTargetApi api0 | + exists(captureFlow(api0, _)) and api0.lift() = api.lift() + ) and + api.isRelevant() and + result = ModelPrintingSummary::asNeutralSummaryModel(api) + } + } + + /** + * Provides classes and predicates related to capturing summary models + * based on content data flow. + */ + module ContentSensitive { + private import MakeImplContentDataFlow as ContentDataFlow + + private module PropagateContentFlowConfig implements ContentDataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + source instanceof DataFlow::ParameterNode and + getEnclosingCallable(source) instanceof DataFlowSummaryTargetApi + } + + predicate isSink(DataFlow::Node sink) { + sink instanceof ReturnNodeExt and + getEnclosingCallable(sink) instanceof DataFlowSummaryTargetApi + } + + predicate isAdditionalFlowStep = isAdditionalContentFlowStep/2; + + predicate isBarrier(DataFlow::Node n) { + exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) + } + + int accessPathLimit() { result = 2 } + + predicate isRelevantContent(DataFlow::ContentSet s) { isRelevantContent0(s) } + + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureEqualSourceSinkCallContext + } + } + + private module PropagateContentFlow = ContentDataFlow::Global; + + private module ContentModelPrintingInput implements Printing::ModelPrintingSummarySig { + class SummaryApi = DataFlowSummaryTargetApi; + + string getProvenance() { result = "dfc-generated" } + } + + private module ContentModelPrinting = + Printing::ModelPrintingSummary; + + private string getContentOutput(ReturnNodeExt node) { + result = PrintReturnNodeExt::getOutput(node) + } + + /** + * Gets the MaD string representation of the parameter `p` + * when used in content flow. + */ + private string parameterNodeAsContentInput(DataFlow::ParameterNode p) { + result = parameterContentAccess(asParameter(p)) + or + result = qualifierString() and p instanceof InstanceParameterNode + } + + private string getContent(PropagateContentFlow::AccessPath ap, int i) { + result = "." + printContent(ap.getAtIndex(i)) + } + + /** + * Gets the MaD string representation of a store step access path. + */ + private string printStoreAccessPath(PropagateContentFlow::AccessPath ap) { + result = concat(int i | | getContent(ap, i), "" order by i) + } + + /** + * Gets the MaD string representation of a read step access path. + */ + private string printReadAccessPath(PropagateContentFlow::AccessPath ap) { + result = concat(int i | | getContent(ap, i), "" order by i desc) + } + + /** + * Holds if the access path `ap` contains a field or synthetic field access. + */ + private predicate mentionsField(PropagateContentFlow::AccessPath ap) { + isField(ap.getAtIndex(_)) + } + + /** + * Holds if this access path `ap` mentions a callback. + */ + private predicate mentionsCallback(PropagateContentFlow::AccessPath ap) { + isCallback(ap.getAtIndex(_)) + } + + /** + * Holds if the access path `ap` is not a parameter or returnvalue of a callback + * stored in a field. + * + * That is, we currently don't include summaries that rely on parameters or return values + * of callbacks stored in fields. + */ + private predicate validateAccessPath(PropagateContentFlow::AccessPath ap) { + not (mentionsField(ap) and mentionsCallback(ap)) + } + + private predicate apiFlow( + DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, + PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath stores, boolean preservesValue + ) { + PropagateContentFlow::flow(p, reads, returnNodeExt, stores, preservesValue) and + getEnclosingCallable(returnNodeExt) = api and + getEnclosingCallable(p) = api + } + + /** + * A class of APIs relevant for modeling using content flow. + * The following heuristic is applied: + * Content flow is only relevant for an API on a parameter, if + * #content flow from parameter <= 3 + * If an API produces more content flow on a parameter, it is likely that + * 1. Types are not sufficiently constrained on the parameter leading to a combinatorial + * explosion in dispatch and thus in the generated summaries. + * 2. It is a reasonable approximation to use the heuristic based flow + * detection instead, as reads and stores would use a significant + * part of an objects internal state. + */ + private class ContentDataFlowSummaryTargetApi extends DataFlowSummaryTargetApi { + private DataFlow::ParameterNode parameter; + + ContentDataFlowSummaryTargetApi() { + strictcount(string input, string output | + exists( + PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath stores + | + apiFlow(this, parameter, reads, returnNodeExt, stores, _) and + input = parameterNodeAsContentInput(parameter) + printReadAccessPath(reads) and + output = getContentOutput(returnNodeExt) + printStoreAccessPath(stores) + ) + ) <= 3 + } + + /** + * Gets a parameter node of `this` api, where there are less than 3 possible models, if any. + */ + DataFlow::ParameterNode getARelevantParameterNode() { result = parameter } + } + + pragma[nomagic] + private predicate apiContentFlow( + ContentDataFlowSummaryTargetApi api, DataFlow::ParameterNode p, + PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath stores, boolean preservesValue + ) { + PropagateContentFlow::flow(p, reads, returnNodeExt, stores, preservesValue) and + getEnclosingCallable(returnNodeExt) = api and + getEnclosingCallable(p) = api and + p = api.getARelevantParameterNode() + } + + /** + * Holds if any of the content sets in `path` translates into a synthetic field. + */ + private predicate hasSyntheticContent(PropagateContentFlow::AccessPath path) { + exists(getSyntheticName(path.getAtIndex(_))) + } + + private string getHashAtIndex(PropagateContentFlow::AccessPath ap, int i) { + result = getSyntheticName(ap.getAtIndex(i)) + } + + private string getReversedHash(PropagateContentFlow::AccessPath ap) { + result = strictconcat(int i | | getHashAtIndex(ap, i), "." order by i desc) + } + + private string getHash(PropagateContentFlow::AccessPath ap) { + result = strictconcat(int i | | getHashAtIndex(ap, i), "." order by i) + } + + /** + * Gets all access paths that contain the synthetic fields + * from `ap` in reverse order (if `ap` contains at least one synthetic field). + * These are the possible candidates for synthetic path continuations. + */ + private PropagateContentFlow::AccessPath getSyntheticPathCandidate( + PropagateContentFlow::AccessPath ap + ) { + getHash(ap) = getReversedHash(result) + } + + /** + * A module containing predicates for validating access paths containing content sets + * that translates into synthetic fields, when used for generated summary models. + */ + private module AccessPathSyntheticValidation { + /** + * Holds if there exists an API that has content flow from `read` (on type `t1`) + * to `store` (on type `t2`). + */ + private predicate step( + Type t1, PropagateContentFlow::AccessPath read, Type t2, + PropagateContentFlow::AccessPath store + ) { + exists(DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt | + p.(NodeExtended).getType() = t1 and + returnNodeExt.getType() = t2 and + apiContentFlow(_, p, read, returnNodeExt, store, _) + ) + } + + /** + * Holds if there exists an API that has content flow from `read` (on type `t1`) + * to `store` (on type `t2`), where `read` does not have synthetic content and `store` does. + * + * Step A -> Synth. + */ + private predicate synthPathEntry( + Type t1, PropagateContentFlow::AccessPath read, Type t2, + PropagateContentFlow::AccessPath store + ) { + not hasSyntheticContent(read) and + hasSyntheticContent(store) and + step(t1, read, t2, store) + } + + /** + * Holds if there exists an API that has content flow from `read` (on type `t1`) + * to `store` (on type `t2`), where `read` has synthetic content + * and `store` does not. + * + * Step Synth -> A. + */ + private predicate synthPathExit( + Type t1, PropagateContentFlow::AccessPath read, Type t2, + PropagateContentFlow::AccessPath store + ) { + hasSyntheticContent(read) and + not hasSyntheticContent(store) and + step(t1, read, t2, store) + } + + /** + * Holds if there exists a path of steps from `read` to an exit. + * + * read ->* Synth -> A + */ + private predicate reachesSynthExit(Type t, PropagateContentFlow::AccessPath read) { + synthPathExit(t, read, _, _) + or + hasSyntheticContent(read) and + exists(PropagateContentFlow::AccessPath mid, Type midType | + hasSyntheticContent(mid) and + step(t, read, midType, mid) and + reachesSynthExit(midType, getSyntheticPathCandidate(mid)) + ) + } + + /** + * Holds if there exists a path of steps from an entry to `store`. + * + * A -> Synth ->* store + */ + private predicate synthEntryReaches(Type t, PropagateContentFlow::AccessPath store) { + synthPathEntry(_, _, t, store) + or + hasSyntheticContent(store) and + exists(PropagateContentFlow::AccessPath mid, Type midType | + hasSyntheticContent(mid) and + step(midType, mid, t, store) and + synthEntryReaches(midType, getSyntheticPathCandidate(mid)) + ) + } + + /** + * Holds if at least one of the access paths `read` (on type `t1`) and `store` (on type `t2`) + * contain content that will be translated into a synthetic field, when being used in + * a MaD summary model, and if there is a range of APIs, such that + * when chaining their flow access paths, there exists access paths `A` and `B` where + * A ->* read -> store ->* B and where `A` and `B` do not contain content that will + * be translated into a synthetic field. + * + * This is needed because we don't want to include summaries that reads from or + * stores into an "internal" synthetic field. + * + * Example: + * Assume we have a type `t` (in this case `t1` = `t2`) with methods `getX` and + * `setX`, which gets and sets a private field `X` on `t`. + * This would lead to the following content flows + * getX : Argument[this].SyntheticField[t.X] -> ReturnValue. + * setX : Argument[0] -> Argument[this].SyntheticField[t.X] + * As the reads and stores are on synthetic fields we should only make summaries + * if both of these methods exist. + */ + pragma[nomagic] + predicate acceptReadStore( + Type t1, PropagateContentFlow::AccessPath read, Type t2, + PropagateContentFlow::AccessPath store + ) { + synthPathEntry(t1, read, t2, store) and + reachesSynthExit(t2, getSyntheticPathCandidate(store)) + or + exists(PropagateContentFlow::AccessPath store0 | + getSyntheticPathCandidate(store0) = read + | + synthEntryReaches(t1, store0) and synthPathExit(t1, read, t2, store) or - state1.(TaintStore).getStep() + 1 = state2.(TaintStore).getStep() + synthEntryReaches(t1, store0) and + step(t1, read, t2, store) and + reachesSynthExit(t2, getSyntheticPathCandidate(store)) + ) + } + } + + /** + * Holds, if the API `api` has relevant flow from `read` on `p` to `store` on `returnNodeExt`. + * Flow is considered relevant, + * 1. If `read` or `store` do not contain a content set that translates into a synthetic field. + * 2. If `read` or `store` contain a content set that translates into a synthetic field, and if + * the synthetic content is "live" on the relevant declaring type. + */ + private predicate apiRelevantContentFlow( + ContentDataFlowSummaryTargetApi api, DataFlow::ParameterNode p, + PropagateContentFlow::AccessPath read, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath store, boolean preservesValue + ) { + apiContentFlow(api, p, read, returnNodeExt, store, preservesValue) and + ( + not hasSyntheticContent(read) and not hasSyntheticContent(store) + or + AccessPathSyntheticValidation::acceptReadStore(p.(NodeExtended).getType(), read, + returnNodeExt.getType(), store) + ) + } + + pragma[nomagic] + private predicate captureFlow0( + ContentDataFlowSummaryTargetApi api, string input, string output, boolean preservesValue, + boolean lift + ) { + exists( + DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt, + PropagateContentFlow::AccessPath reads, PropagateContentFlow::AccessPath stores + | + apiRelevantContentFlow(api, p, reads, returnNodeExt, stores, preservesValue) and + input = parameterNodeAsContentInput(p) + printReadAccessPath(reads) and + output = getContentOutput(returnNodeExt) + printStoreAccessPath(stores) and + input != output and + validateAccessPath(reads) and + validateAccessPath(stores) and + ( + if mentionsField(reads) or mentionsField(stores) + then lift = false and api.isRelevant() + else lift = true ) ) - or - exists(DataFlow::ContentSet c | - DataFlow::readStep(node1, c, node2) and - isRelevantContent0(c) and - state1.(TaintRead).getStep() + 1 = state2.(TaintRead).getStep() + } + + /** + * Gets the content based summary model(s) of the API `api` (if there is flow from a parameter to + * the return value or a parameter). `lift` is true, if the model should be lifted, otherwise false. + * `preservesValue` is `true` if the summary is value-preserving, and `false` otherwise. + * + * Models are lifted to the best type in case the read and store access paths do not + * contain a field or synthetic field access. + */ + string captureFlow(ContentDataFlowSummaryTargetApi api, boolean lift, boolean preservesValue) { + exists(string input, string output | + captureFlow0(api, input, output, _, lift) and + preservesValue = max(boolean p | captureFlow0(api, input, output, p, lift)) and + result = ContentModelPrinting::asModel(api, input, output, preservesValue, lift) ) } } /** - * A data flow configuration for tracking taint-flow through APIs. - * The sources are the parameters of an API and the sinks are the return - * values (excluding `this`) and parameters. - * - * This can be used to generate flow summaries for APIs from parameter to - * return. + * Gets the summary model(s) for `api`, if any. `lift` is true if the model is lifted + * otherwise false. + * The following heuristic is applied: + * 1. If content based flow yields at lease one summary for an API, then we use that. + * 2. If content based flow does not yield any summary for an API, then we try and + * generate flow summaries using the heuristic based summary generator. */ - private module PropagateTaintFlowConfig = - PropagateFlowConfig; - - module PropagateTaintFlow = TaintTracking::GlobalWithState; - - /** - * A module used to construct a data flow configuration for tracking - * data flow through APIs. - * The sources are the parameters of an API and the sinks are the return - * values (excluding `this`) and parameters. - * - * This can be used to generate value-preserving flow summaries for APIs - * from parameter to return. - */ - module PropagateFlowConfigInputDataFlowInput implements PropagateFlowConfigInputSig { - class FlowState = Unit; - - FlowState initialState() { any() } - } - - /** - * A data flow configuration for tracking data flow through APIs. - * The sources are the parameters of an API and the sinks are the return - * values (excluding `this`) and parameters. - * - * This can be used to generate flow summaries for APIs from parameter to - * return. - */ - private module PropagateDataFlowConfig = - PropagateFlowConfig; - - module PropagateDataFlow = DataFlow::GlobalWithState; - - /** - * Holds if there should be a summary of `api` specifying flow from `p` - * to `returnNodeExt`. - */ - predicate captureThroughFlow0( - DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt - ) { - captureThroughFlow0(api, p, _, returnNodeExt, _, _) - } - - /** - * Holds if there should be a summary of `api` specifying flow - * from `p` (with summary component `input`) to `returnNodeExt` (with - * summary component `output`). - * - * `preservesValue` is true if the summary is value-preserving, or `false` - * otherwise. - */ - private predicate captureThroughFlow0( - DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, string input, - ReturnNodeExt returnNodeExt, string output, boolean preservesValue - ) { - ( - PropagateDataFlow::flow(p, returnNodeExt) and preservesValue = true + string captureFlow(DataFlowSummaryTargetApi api, boolean lift) { + exists(boolean preservesValue | + result = ContentSensitive::captureFlow(api, lift, preservesValue) or - not PropagateDataFlow::flow(p, returnNodeExt) and - PropagateTaintFlow::flow(p, returnNodeExt) and - preservesValue = false - ) and - getEnclosingCallable(p) = api and - getEnclosingCallable(returnNodeExt) = api and - input = parameterNodeAsInput(p) and - output = getOutput(returnNodeExt) and - input != output - } - - /** - * Gets the summary model(s) of `api`, if there is flow from parameters to return value or parameter. - * - * `preservesValue` is `true` if the summary is value-preserving, and `false` otherwise. - */ - private string captureThroughFlow(DataFlowSummaryTargetApi api, boolean preservesValue) { - exists(string input, string output | - preservesValue = max(boolean b | captureThroughFlow0(api, _, input, _, output, b)) and - result = ModelPrinting::asLiftedTaintModel(api, input, output, preservesValue) + not exists(DataFlowSummaryTargetApi api0 | + // If the heuristic summary is value-preserving then we keep both + // summaries. However, if we can generate any content-sensitive + // summary (value-preserving or not) then we don't include any taint- + // based heuristic summary. + preservesValue = false + | + (api0 = api or api.lift() = api0) and + exists(ContentSensitive::captureFlow(api0, false, _)) + or + api0.lift() = api.lift() and + exists(ContentSensitive::captureFlow(api0, true, _)) + ) and + result = Heuristic::captureHeuristicFlow(api, preservesValue) and + lift = true ) } - /** - * Gets the summary model(s) of `api`, if there is flow from parameters to the - * return value or parameter or if `api` is a fluent API. - * - * `preservesValue` is `true` if the summary is value-preserving, and `false` otherwise. - */ - string captureHeuristicFlow(DataFlowSummaryTargetApi api, boolean preservesValue) { - result = captureQualifierFlow(api) and preservesValue = true - or - result = captureThroughFlow(api, preservesValue) - } - /** * Gets the neutral summary model for `api`, if any. * A neutral summary model is generated, if we are not generating - * a summary model that applies to `api`. + * a mixed summary model that applies to `api`. */ - string captureNoFlow(DataFlowSummaryTargetApi api) { - not exists(DataFlowSummaryTargetApi api0 | - exists(captureFlow(api0, _)) and api0.lift() = api.lift() + string captureNeutral(DataFlowSummaryTargetApi api) { + not exists(DataFlowSummaryTargetApi api0, boolean lift | + exists(captureFlow(api0, lift)) and + ( + lift = false and + (api0 = api or api0 = api.lift()) + or + lift = true and api0.lift() = api.lift() + ) ) and api.isRelevant() and - result = ModelPrinting::asNeutralSummaryModel(api) - } - - /** - * A data flow configuration used for finding new sources. - * The sources are the already known existing sources and the sinks are the API return nodes. - * - * This can be used to generate Source summaries for an API, if the API expose an already known source - * via its return (then the API itself becomes a source). - */ - module PropagateFromSourceConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - exists(string kind | - isRelevantSourceKind(kind) and - sourceNode(source, kind) - ) - } - - predicate isSink(DataFlow::Node sink) { - sink instanceof ReturnNodeExt and - getEnclosingCallable(sink) instanceof DataFlowSourceTargetApi - } - - DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSinkCallContext } - - predicate isBarrier(DataFlow::Node n) { - exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) - } - - predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { - isRelevantTaintStep(node1, node2) - } - } - - private module PropagateFromSource = TaintTracking::Global; - - /** - * Gets the source model(s) of `api`, if there is flow from an existing known source to the return of `api`. - */ - string captureSource(DataFlowSourceTargetApi api) { - exists(NodeExtended source, ReturnNodeExt sink, string kind | - PropagateFromSource::flow(source, sink) and - sourceNode(source, kind) and - api = getEnclosingCallable(sink) and - not irrelevantSourceSinkApi(getEnclosingCallable(source), api) and - result = ModelPrinting::asSourceModel(api, getOutput(sink), kind) - ) - } - - /** - * A data flow configuration used for finding new sinks. - * The sources are the parameters of the API and the fields of the enclosing type. - * - * This can be used to generate Sink summaries for APIs, if the API propagates a parameter (or enclosing type field) - * into an existing known sink (then the API itself becomes a sink). - */ - module PropagateToSinkConfig implements DataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - apiSource(source) and - getEnclosingCallable(source) instanceof DataFlowSinkTargetApi - } - - predicate isSink(DataFlow::Node sink) { - exists(string kind | isRelevantSinkKind(kind) and sinkNode(sink, kind)) - } - - predicate isBarrier(DataFlow::Node node) { - exists(Type t | t = node.(NodeExtended).getType() and not isRelevantType(t)) - or - sinkModelSanitizer(node) - } - - DataFlow::FlowFeature getAFeature() { - result instanceof DataFlow::FeatureHasSourceCallContext - } - - predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { - isRelevantTaintStep(node1, node2) - } - } - - private module PropagateToSink = TaintTracking::Global; - - /** - * Gets the sink model(s) of `api`, if there is flow from a parameter to an existing known sink. - */ - string captureSink(DataFlowSinkTargetApi api) { - exists(NodeExtended src, NodeExtended sink, string kind | - PropagateToSink::flow(src, sink) and - sinkNode(sink, kind) and - api = getEnclosingCallable(src) and - result = ModelPrinting::asSinkModel(api, asInputArgument(src), kind) - ) + result = Heuristic::ModelPrintingSummary::asNeutralSummaryModel(api) } } /** - * Provides classes and predicates related to capturing summary models - * based on content data flow. + * Holds if data can flow from `node1` to `node2` either via a read or a write of an intermediate field `f`. */ - module ContentSensitive { - private import MakeImplContentDataFlow as ContentDataFlow - - private module PropagateContentFlowConfig implements ContentDataFlow::ConfigSig { - predicate isSource(DataFlow::Node source) { - source instanceof DataFlow::ParameterNode and - getEnclosingCallable(source) instanceof DataFlowSummaryTargetApi - } - - predicate isSink(DataFlow::Node sink) { - sink instanceof ReturnNodeExt and - getEnclosingCallable(sink) instanceof DataFlowSummaryTargetApi - } - - predicate isAdditionalFlowStep = isAdditionalContentFlowStep/2; - - predicate isBarrier(DataFlow::Node n) { - exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) - } - - int accessPathLimit() { result = 2 } - - predicate isRelevantContent(DataFlow::ContentSet s) { isRelevantContent0(s) } - - DataFlow::FlowFeature getAFeature() { - result instanceof DataFlow::FeatureEqualSourceSinkCallContext - } - } - - private module PropagateContentFlow = ContentDataFlow::Global; - - private module ContentModelPrintingInput implements Printing::ModelPrintingSig { - class SummaryApi = DataFlowSummaryTargetApi; - - class SourceOrSinkApi = SourceOrSinkTargetApi; - - string getProvenance() { result = "dfc-generated" } - } - - private module ContentModelPrinting = Printing::ModelPrinting; - - private string getContentOutput(ReturnNodeExt node) { - result = PrintReturnNodeExt::getOutput(node) - } - - /** - * Gets the MaD string representation of the parameter `p` - * when used in content flow. - */ - private string parameterNodeAsContentInput(DataFlow::ParameterNode p) { - result = parameterContentAccess(asParameter(p)) - or - result = qualifierString() and p instanceof InstanceParameterNode - } - - private string getContent(PropagateContentFlow::AccessPath ap, int i) { - result = "." + printContent(ap.getAtIndex(i)) - } - - /** - * Gets the MaD string representation of a store step access path. - */ - private string printStoreAccessPath(PropagateContentFlow::AccessPath ap) { - result = concat(int i | | getContent(ap, i), "" order by i) - } - - /** - * Gets the MaD string representation of a read step access path. - */ - private string printReadAccessPath(PropagateContentFlow::AccessPath ap) { - result = concat(int i | | getContent(ap, i), "" order by i desc) - } - - /** - * Holds if the access path `ap` contains a field or synthetic field access. - */ - private predicate mentionsField(PropagateContentFlow::AccessPath ap) { - isField(ap.getAtIndex(_)) - } - - /** - * Holds if this access path `ap` mentions a callback. - */ - private predicate mentionsCallback(PropagateContentFlow::AccessPath ap) { - isCallback(ap.getAtIndex(_)) - } - - /** - * Holds if the access path `ap` is not a parameter or returnvalue of a callback - * stored in a field. - * - * That is, we currently don't include summaries that rely on parameters or return values - * of callbacks stored in fields. - */ - private predicate validateAccessPath(PropagateContentFlow::AccessPath ap) { - not (mentionsField(ap) and mentionsCallback(ap)) - } - - private predicate apiFlow( - DataFlowSummaryTargetApi api, DataFlow::ParameterNode p, - PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath stores, boolean preservesValue - ) { - PropagateContentFlow::flow(p, reads, returnNodeExt, stores, preservesValue) and - getEnclosingCallable(returnNodeExt) = api and - getEnclosingCallable(p) = api - } - - /** - * A class of APIs relevant for modeling using content flow. - * The following heuristic is applied: - * Content flow is only relevant for an API on a parameter, if - * #content flow from parameter <= 3 - * If an API produces more content flow on a parameter, it is likely that - * 1. Types are not sufficiently constrained on the parameter leading to a combinatorial - * explosion in dispatch and thus in the generated summaries. - * 2. It is a reasonable approximation to use the heuristic based flow - * detection instead, as reads and stores would use a significant - * part of an objects internal state. - */ - private class ContentDataFlowSummaryTargetApi extends DataFlowSummaryTargetApi { - private DataFlow::ParameterNode parameter; - - ContentDataFlowSummaryTargetApi() { - strictcount(string input, string output | - exists( - PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath stores - | - apiFlow(this, parameter, reads, returnNodeExt, stores, _) and - input = parameterNodeAsContentInput(parameter) + printReadAccessPath(reads) and - output = getContentOutput(returnNodeExt) + printStoreAccessPath(stores) - ) - ) <= 3 - } - - /** - * Gets a parameter node of `this` api, where there are less than 3 possible models, if any. - */ - DataFlow::ParameterNode getARelevantParameterNode() { result = parameter } - } - - pragma[nomagic] - private predicate apiContentFlow( - ContentDataFlowSummaryTargetApi api, DataFlow::ParameterNode p, - PropagateContentFlow::AccessPath reads, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath stores, boolean preservesValue - ) { - PropagateContentFlow::flow(p, reads, returnNodeExt, stores, preservesValue) and - getEnclosingCallable(returnNodeExt) = api and - getEnclosingCallable(p) = api and - p = api.getARelevantParameterNode() - } - - /** - * Holds if any of the content sets in `path` translates into a synthetic field. - */ - private predicate hasSyntheticContent(PropagateContentFlow::AccessPath path) { - exists(getSyntheticName(path.getAtIndex(_))) - } - - private string getHashAtIndex(PropagateContentFlow::AccessPath ap, int i) { - result = getSyntheticName(ap.getAtIndex(i)) - } - - private string getReversedHash(PropagateContentFlow::AccessPath ap) { - result = strictconcat(int i | | getHashAtIndex(ap, i), "." order by i desc) - } - - private string getHash(PropagateContentFlow::AccessPath ap) { - result = strictconcat(int i | | getHashAtIndex(ap, i), "." order by i) - } - - /** - * Gets all access paths that contain the synthetic fields - * from `ap` in reverse order (if `ap` contains at least one synthetic field). - * These are the possible candidates for synthetic path continuations. - */ - private PropagateContentFlow::AccessPath getSyntheticPathCandidate( - PropagateContentFlow::AccessPath ap - ) { - getHash(ap) = getReversedHash(result) - } - - /** - * A module containing predicates for validating access paths containing content sets - * that translates into synthetic fields, when used for generated summary models. - */ - private module AccessPathSyntheticValidation { - /** - * Holds if there exists an API that has content flow from `read` (on type `t1`) - * to `store` (on type `t2`). - */ - private predicate step( - Type t1, PropagateContentFlow::AccessPath read, Type t2, - PropagateContentFlow::AccessPath store - ) { - exists(DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt | - p.(NodeExtended).getType() = t1 and - returnNodeExt.getType() = t2 and - apiContentFlow(_, p, read, returnNodeExt, store, _) - ) - } - - /** - * Holds if there exists an API that has content flow from `read` (on type `t1`) - * to `store` (on type `t2`), where `read` does not have synthetic content and `store` does. - * - * Step A -> Synth. - */ - private predicate synthPathEntry( - Type t1, PropagateContentFlow::AccessPath read, Type t2, - PropagateContentFlow::AccessPath store - ) { - not hasSyntheticContent(read) and - hasSyntheticContent(store) and - step(t1, read, t2, store) - } - - /** - * Holds if there exists an API that has content flow from `read` (on type `t1`) - * to `store` (on type `t2`), where `read` has synthetic content - * and `store` does not. - * - * Step Synth -> A. - */ - private predicate synthPathExit( - Type t1, PropagateContentFlow::AccessPath read, Type t2, - PropagateContentFlow::AccessPath store - ) { - hasSyntheticContent(read) and - not hasSyntheticContent(store) and - step(t1, read, t2, store) - } - - /** - * Holds if there exists a path of steps from `read` to an exit. - * - * read ->* Synth -> A - */ - private predicate reachesSynthExit(Type t, PropagateContentFlow::AccessPath read) { - synthPathExit(t, read, _, _) - or - hasSyntheticContent(read) and - exists(PropagateContentFlow::AccessPath mid, Type midType | - hasSyntheticContent(mid) and - step(t, read, midType, mid) and - reachesSynthExit(midType, getSyntheticPathCandidate(mid)) - ) - } - - /** - * Holds if there exists a path of steps from an entry to `store`. - * - * A -> Synth ->* store - */ - private predicate synthEntryReaches(Type t, PropagateContentFlow::AccessPath store) { - synthPathEntry(_, _, t, store) - or - hasSyntheticContent(store) and - exists(PropagateContentFlow::AccessPath mid, Type midType | - hasSyntheticContent(mid) and - step(midType, mid, t, store) and - synthEntryReaches(midType, getSyntheticPathCandidate(mid)) - ) - } - - /** - * Holds if at least one of the access paths `read` (on type `t1`) and `store` (on type `t2`) - * contain content that will be translated into a synthetic field, when being used in - * a MaD summary model, and if there is a range of APIs, such that - * when chaining their flow access paths, there exists access paths `A` and `B` where - * A ->* read -> store ->* B and where `A` and `B` do not contain content that will - * be translated into a synthetic field. - * - * This is needed because we don't want to include summaries that reads from or - * stores into an "internal" synthetic field. - * - * Example: - * Assume we have a type `t` (in this case `t1` = `t2`) with methods `getX` and - * `setX`, which gets and sets a private field `X` on `t`. - * This would lead to the following content flows - * getX : Argument[this].SyntheticField[t.X] -> ReturnValue. - * setX : Argument[0] -> Argument[this].SyntheticField[t.X] - * As the reads and stores are on synthetic fields we should only make summaries - * if both of these methods exist. - */ - pragma[nomagic] - predicate acceptReadStore( - Type t1, PropagateContentFlow::AccessPath read, Type t2, - PropagateContentFlow::AccessPath store - ) { - synthPathEntry(t1, read, t2, store) and - reachesSynthExit(t2, getSyntheticPathCandidate(store)) - or - exists(PropagateContentFlow::AccessPath store0 | getSyntheticPathCandidate(store0) = read | - synthEntryReaches(t1, store0) and synthPathExit(t1, read, t2, store) - or - synthEntryReaches(t1, store0) and - step(t1, read, t2, store) and - reachesSynthExit(t2, getSyntheticPathCandidate(store)) - ) - } - } - - /** - * Holds, if the API `api` has relevant flow from `read` on `p` to `store` on `returnNodeExt`. - * Flow is considered relevant, - * 1. If `read` or `store` do not contain a content set that translates into a synthetic field. - * 2. If `read` or `store` contain a content set that translates into a synthetic field, and if - * the synthetic content is "live" on the relevant declaring type. - */ - private predicate apiRelevantContentFlow( - ContentDataFlowSummaryTargetApi api, DataFlow::ParameterNode p, - PropagateContentFlow::AccessPath read, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath store, boolean preservesValue - ) { - apiContentFlow(api, p, read, returnNodeExt, store, preservesValue) and - ( - not hasSyntheticContent(read) and not hasSyntheticContent(store) - or - AccessPathSyntheticValidation::acceptReadStore(p.(NodeExtended).getType(), read, - returnNodeExt.getType(), store) - ) - } - - pragma[nomagic] - private predicate captureFlow0( - ContentDataFlowSummaryTargetApi api, string input, string output, boolean preservesValue, - boolean lift - ) { - exists( - DataFlow::ParameterNode p, ReturnNodeExt returnNodeExt, - PropagateContentFlow::AccessPath reads, PropagateContentFlow::AccessPath stores - | - apiRelevantContentFlow(api, p, reads, returnNodeExt, stores, preservesValue) and - input = parameterNodeAsContentInput(p) + printReadAccessPath(reads) and - output = getContentOutput(returnNodeExt) + printStoreAccessPath(stores) and - input != output and - validateAccessPath(reads) and - validateAccessPath(stores) and - ( - if mentionsField(reads) or mentionsField(stores) - then lift = false and api.isRelevant() - else lift = true - ) - ) - } - - /** - * Gets the content based summary model(s) of the API `api` (if there is flow from a parameter to - * the return value or a parameter). `lift` is true, if the model should be lifted, otherwise false. - * `preservesValue` is `true` if the summary is value-preserving, and `false` otherwise. - * - * Models are lifted to the best type in case the read and store access paths do not - * contain a field or synthetic field access. - */ - string captureFlow(ContentDataFlowSummaryTargetApi api, boolean lift, boolean preservesValue) { - exists(string input, string output | - captureFlow0(api, input, output, _, lift) and - preservesValue = max(boolean p | captureFlow0(api, input, output, p, lift)) and - result = ContentModelPrinting::asModel(api, input, output, preservesValue, lift) - ) - } - } - - /** - * Gets the summary model(s) for `api`, if any. `lift` is true if the model is lifted - * otherwise false. - * The following heuristic is applied: - * 1. If content based flow yields at lease one summary for an API, then we use that. - * 2. If content based flow does not yield any summary for an API, then we try and - * generate flow summaries using the heuristic based summary generator. - */ - string captureFlow(DataFlowSummaryTargetApi api, boolean lift) { - exists(boolean preservesValue | - result = ContentSensitive::captureFlow(api, lift, preservesValue) - or - not exists(DataFlowSummaryTargetApi api0 | - // If the heuristic summary is value-preserving then we keep both - // summaries. However, if we can generate any content-sensitive - // summary (value-preserving or not) then we don't include any taint- - // based heuristic summary. - preservesValue = false - | - (api0 = api or api.lift() = api0) and - exists(ContentSensitive::captureFlow(api0, false, _)) - or - api0.lift() = api.lift() and - exists(ContentSensitive::captureFlow(api0, true, _)) - ) and - result = Heuristic::captureHeuristicFlow(api, preservesValue) and - lift = true + private predicate isRelevantTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + exists(DataFlow::ContentSet f | + DataFlow::readStep(node1, f, node2) and + // Partially restrict the content types used for intermediate steps. + (not exists(getUnderlyingContentType(f)) or isRelevantTypeInContent(f)) ) + or + exists(DataFlow::ContentSet f | DataFlow::storeStep(node1, f, node2) | containerContent(f)) } /** - * Gets the neutral summary model for `api`, if any. - * A neutral summary model is generated, if we are not generating - * a mixed summary model that applies to `api`. + * Provides language-specific source model generator parameters. */ - string captureNeutral(DataFlowSummaryTargetApi api) { - not exists(DataFlowSummaryTargetApi api0, boolean lift | - exists(captureFlow(api0, lift)) and - ( - lift = false and - (api0 = api or api0 = api.lift()) - or - lift = true and api0.lift() = api.lift() - ) - ) and - api.isRelevant() and - result = Heuristic::ModelPrinting::asNeutralSummaryModel(api) + signature module SourceModelGeneratorInputSig { + /** + * A class of callables that are potentially relevant for generating source models. + */ + class SourceTargetApi extends Callable; + + /** + * Holds if `node` is specified as a source with the given kind in a MaD flow + * model. + */ + predicate sourceNode(Lang::Node node, string kind); + + /** + * Holds if it is not relevant to generate a source model for `api`, even + * if flow is detected from a node within `source` to a sink within `api`. + */ + bindingset[sourceEnclosing, api] + default predicate irrelevantSourceSinkApi(Callable sourceEnclosing, SourceTargetApi api) { + none() + } + } + + /** + * Provides language-specific sink model generator parameters. + */ + signature module SinkModelGeneratorInputSig { + /** + * A class of callables that are potentially relevant for generating sink models. + */ + class SinkTargetApi extends Callable; + + /** + * Holds if `node` is specified as a sink with the given kind in a MaD flow + * model. + */ + predicate sinkNode(Lang::Node node, string kind); + + /** + * Gets the MaD input string representation of `source`. + */ + string getInputArgument(Lang::Node source); + + /** + * Holds if `source` is an api entrypoint relevant for creating sink models. + */ + predicate apiSource(Lang::Node source); + + /** + * Holds if `node` is a sanitizer for sink model construction. + */ + default predicate sinkModelSanitizer(Lang::Node node) { none() } + + /** + * Holds if `kind` is a relevant sink kind for creating sink models. + */ + bindingset[kind] + default predicate isRelevantSinkKind(string kind) { any() } + } + + /** + * Make a source model generator. + */ + module MakeSourceModelGenerator { + private import SourceModelGeneratorInput + + class DataFlowSourceTargetApi = SourceTargetApi; + + /** + * Provides classes and predicates related to capturing source models + * based on heuristic data flow. + */ + module Heuristic { + private module ModelPrintingSourceOrSinkInput implements + Printing::ModelPrintingSourceOrSinkSig + { + class SourceOrSinkApi = DataFlowSourceTargetApi; + + string getProvenance() { result = "df-generated" } + } + + private module ModelPrintingSourceOrSink = + Printing::ModelPrintingSourceOrSink; + + /** + * A data flow configuration used for finding new sources. + * The sources are the already known existing sources and the sinks are the API return nodes. + * + * This can be used to generate Source summaries for an API, if the API expose an already known source + * via its return (then the API itself becomes a source). + */ + module PropagateFromSourceConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { sourceNode(source, _) } + + predicate isSink(DataFlow::Node sink) { + sink instanceof ReturnNodeExt and + getEnclosingCallable(sink) instanceof DataFlowSourceTargetApi + } + + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureHasSinkCallContext + } + + predicate isBarrier(DataFlow::Node n) { + exists(Type t | t = n.(NodeExtended).getType() and not isRelevantType(t)) + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + isRelevantTaintStep(node1, node2) + } + } + + private module PropagateFromSource = TaintTracking::Global; + + /** + * Gets the source model(s) of `api`, if there is flow from an existing known source to the return of `api`. + */ + string captureSource(DataFlowSourceTargetApi api) { + exists(NodeExtended source, ReturnNodeExt sink, string kind | + PropagateFromSource::flow(source, sink) and + sourceNode(source, kind) and + api = getEnclosingCallable(sink) and + not irrelevantSourceSinkApi(getEnclosingCallable(source), api) and + result = ModelPrintingSourceOrSink::asSourceModel(api, getOutput(sink), kind) + ) + } + } + } + + /** + * Make a sink model generator. + */ + module MakeSinkModelGenerator { + private import SinkModelGeneratorInput + + class DataFlowSinkTargetApi = SinkTargetApi; + + /** + * Provides classes and predicates related to capturing sink models + * based on heuristic data flow. + */ + module Heuristic { + private module ModelPrintingSourceOrSinkInput implements + Printing::ModelPrintingSourceOrSinkSig + { + class SourceOrSinkApi = DataFlowSinkTargetApi; + + string getProvenance() { result = "df-generated" } + } + + private module ModelPrintingSourceOrSink = + Printing::ModelPrintingSourceOrSink; + + /** + * Gets the MaD input string representation of `source`. + */ + private string asInputArgument(NodeExtended source) { result = getInputArgument(source) } + + /** + * A data flow configuration used for finding new sinks. + * The sources are the parameters of the API and the fields of the enclosing type. + * + * This can be used to generate Sink summaries for APIs, if the API propagates a parameter (or enclosing type field) + * into an existing known sink (then the API itself becomes a sink). + */ + module PropagateToSinkConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { + apiSource(source) and + getEnclosingCallable(source) instanceof DataFlowSinkTargetApi + } + + predicate isSink(DataFlow::Node sink) { + exists(string kind | isRelevantSinkKind(kind) and sinkNode(sink, kind)) + } + + predicate isBarrier(DataFlow::Node node) { + exists(Type t | t = node.(NodeExtended).getType() and not isRelevantType(t)) + or + sinkModelSanitizer(node) + } + + DataFlow::FlowFeature getAFeature() { + result instanceof DataFlow::FeatureHasSourceCallContext + } + + predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) { + isRelevantTaintStep(node1, node2) + } + } + + private module PropagateToSink = TaintTracking::Global; + + /** + * Gets the sink model(s) of `api`, if there is flow from a parameter to an existing known sink. + */ + string captureSink(DataFlowSinkTargetApi api) { + exists(NodeExtended src, NodeExtended sink, string kind | + PropagateToSink::flow(src, sink) and + sinkNode(sink, kind) and + api = getEnclosingCallable(src) and + result = ModelPrintingSourceOrSink::asSinkModel(api, asInputArgument(src), kind) + ) + } + } } } diff --git a/shared/mad/codeql/mad/modelgenerator/internal/ModelPrinting.qll b/shared/mad/codeql/mad/modelgenerator/internal/ModelPrinting.qll index 0bce2ed50d1..23bca7e930b 100644 --- a/shared/mad/codeql/mad/modelgenerator/internal/ModelPrinting.qll +++ b/shared/mad/codeql/mad/modelgenerator/internal/ModelPrinting.qll @@ -16,7 +16,7 @@ signature module ModelPrintingLangSig { } module ModelPrintingImpl { - signature module ModelPrintingSig { + signature module ModelPrintingSummarySig { /** * The class of APIs relevant for model generation. */ @@ -24,6 +24,16 @@ module ModelPrintingImpl { Lang::Callable lift(); } + /** + * Gets the string representation of the provenance of the models. + */ + string getProvenance(); + } + + signature module ModelPrintingSourceOrSinkSig { + /** + * The class of APIs relevant for model generation. + */ class SourceOrSinkApi extends Lang::Callable; /** @@ -32,14 +42,14 @@ module ModelPrintingImpl { string getProvenance(); } - module ModelPrinting { - /** - * Computes the first columns for MaD rows used for summaries, sources and sinks. - */ - private string asPartialModel(Lang::Callable api) { - result = strictconcat(int i | | Lang::partialModelRow(api, i), ";" order by i) + ";" - } + /** + * Computes the first columns for MaD rows used for summaries, sources and sinks. + */ + private string asPartialModel(Lang::Callable api) { + result = strictconcat(int i | | Lang::partialModelRow(api, i), ";" order by i) + ";" + } + module ModelPrintingSummary { /** * Computes the first columns for neutral MaD rows. */ @@ -108,7 +118,9 @@ module ModelPrintingImpl { preservesValue = false and result = asSummaryModel(api, input, output, "taint", lift) } + } + module ModelPrintingSourceOrSink { /** * Gets the sink model for `api` with `input` and `kind`. */ diff --git a/swift/ql/src/codeql-suites/swift-code-quality.qls b/swift/ql/src/codeql-suites/swift-code-quality.qls index 0637a088a01..2074f9378cf 100644 --- a/swift/ql/src/codeql-suites/swift-code-quality.qls +++ b/swift/ql/src/codeql-suites/swift-code-quality.qls @@ -1 +1,3 @@ -[] \ No newline at end of file +- queries: . +- apply: code-quality-selectors.yml + from: codeql/suite-helpers