Compare commits

...

228 Commits

Author SHA1 Message Date
Nick Rolfe
ea2814c03d fix import 2022-07-25 12:00:24 +01:00
Nick Rolfe
2f1ba20165 Revert "Ruby: separate trap-writer into its own module"
This reverts commit 388c9ffb74.
2022-07-25 08:54:30 +01:00
Nick Rolfe
8eddf62b22 Revert "Ruby: compute path string only once"
This reverts commit 0a8ecd3cf7.
2022-07-25 08:52:53 +01:00
Nick Rolfe
81954c3a8d Revert "Ruby: avoid repeated construction of table name strings"
This reverts commit 8dae85e1b1.
2022-07-25 08:52:42 +01:00
Nick Rolfe
ced07b52a5 Revert "Ruby/QL: speed up trap writing by putting BufWriter in front of GzEncoder"
This reverts commit 4767d5a1ba.
2022-07-25 08:52:12 +01:00
Nick Rolfe
a61ec78f03 Merge pull request #9883 from github/nickrolfe/trap-buffering
Ruby/QL: speed up trap writing by putting BufWriter in front of GzEncoder
2022-07-25 08:48:54 +01:00
Paolo Tranquilli
fe73601a4e Merge pull request #9805 from github/redsun82/swift-type-repr-collapse
Swift: collapse `TypeRepr` hierarchy
2022-07-25 09:31:41 +02:00
Harry Maclean
681e58c8e0 Merge pull request #9850 from hmac/hmac/arel
Ruby: Model Arel.sql
2022-07-25 12:09:18 +12:00
Harry Maclean
cb3ebeedf9 Merge pull request #9696 from thiggy1342/experimental-strong-params
RB: Experimental strong params query
2022-07-25 12:08:55 +12:00
Harry Maclean
db41ce5f76 Merge pull request #9605 from thiggy1342/experimental-manually-check-request-verb
RB: Experimental query to manually check request verb
2022-07-25 12:08:11 +12:00
thiggy1342
6cfde70898 Merge branch 'main' into experimental-strong-params 2022-07-22 20:41:33 -04:00
thiggy1342
b4d762fb21 Merge branch 'main' into experimental-manually-check-request-verb 2022-07-22 20:41:23 -04:00
thiggy1342
0c0ba925a7 this one should have no tag 2022-07-22 18:44:03 +00:00
thiggy1342
f39ca1aad2 correct cwe tagged 2022-07-22 18:36:25 +00:00
Robert Marsh
0a35f97074 Merge pull request #9872 from jketema/return-join
C++: Fix join-order problem in `cpp/return-stack-allocated-memory`
2022-07-22 14:32:10 -04:00
thiggy1342
c2710fb038 Update ruby/ql/src/change-notes/2022-07-21-check-http-verb.md
Co-authored-by: Harry Maclean <hmac@github.com>
2022-07-22 13:52:00 -04:00
thiggy1342
2c095cf166 Update ruby/ql/src/change-notes/2022-07-21-weak-params.md
Co-authored-by: Harry Maclean <hmac@github.com>
2022-07-22 13:51:38 -04:00
Jeroen Ketema
23c19311fb Merge pull request #9700 from jketema/resolve-global-variable
C++: Ensure only one `Variable` exists for every global variable
2022-07-22 17:57:21 +02:00
Nick Rolfe
4767d5a1ba Ruby/QL: speed up trap writing by putting BufWriter in front of GzEncoder 2022-07-22 15:37:53 +01:00
Arthur Baars
43266b75a1 Merge pull request #9866 from aibaars/encoding
Ruby: handle magic coding: comments
2022-07-22 14:33:46 +02:00
Taus
5f9a03f103 Merge pull request #9880 from github/nickrolfe/ql-ql-extractor-cleanup
QL: sync Ruby extractor changes
2022-07-22 14:15:04 +02:00
Paolo Tranquilli
77401ded4e Swift: reflow comment 2022-07-22 13:54:32 +02:00
Arthur Baars
d44bf326f0 Update ruby/extractor/src/main.rs
Co-authored-by: Nick Rolfe <nickrolfe@github.com>
2022-07-22 13:36:22 +02:00
Paolo Tranquilli
7e67338fb5 Update swift/extractor/infra/SwiftDispatcher.h
Co-authored-by: Jeroen Ketema <93738568+jketema@users.noreply.github.com>
2022-07-22 13:34:11 +02:00
thiggy1342
871b6515d5 Merge branch 'main' into experimental-manually-check-request-verb 2022-07-21 18:47:07 -04:00
thiggy1342
1842bde879 add change note 2022-07-21 22:13:53 +00:00
thiggy1342
c1a6ca5f94 add change note 2022-07-21 22:11:14 +00:00
thiggy1342
486a394a7f Update ruby/ql/src/experimental/weak-params/WeakParams.ql
Co-authored-by: Harry Maclean <hmac@github.com>
2022-07-21 17:26:09 -04:00
thiggy1342
8fabc06d37 fix test assertion 2022-07-21 21:25:44 +00:00
thiggy1342
cc958dc171 Update ruby/ql/src/experimental/manually-check-http-verb/ManuallyCheckHttpVerb.ql
Co-authored-by: Harry Maclean <hmac@github.com>
2022-07-21 17:19:33 -04:00
Arthur Baars
1399610bd4 Merge branch 'main' into encoding 2022-07-21 21:21:17 +02:00
Nick Rolfe
5f96c92fac QL: sync Ruby extractor changes 2022-07-21 17:38:33 +01:00
Nick Rolfe
ed0325f162 Merge pull request #9878 from github/nickrolfe/extractor-cleanup
Ruby: some extractor refactoring
2022-07-21 17:18:24 +01:00
Arthur Baars
7be106d7bb Ruby: handle magic coding: comments 2022-07-21 16:33:18 +02:00
Arthur Baars
27be3dff54 Merge pull request #9868 from aibaars/update-tree-sitter-ruby-3
Ruby: update tree-sitter-ruby
2022-07-21 16:08:32 +02:00
Nick Rolfe
8dae85e1b1 Ruby: avoid repeated construction of table name strings 2022-07-21 12:21:06 +01:00
Nick Rolfe
0a8ecd3cf7 Ruby: compute path string only once 2022-07-21 10:44:30 +01:00
Nick Rolfe
388c9ffb74 Ruby: separate trap-writer into its own module 2022-07-21 10:44:00 +01:00
Jeroen Ketema
ad8335d6f3 C++: Fix join-order problem in cpp/return-stack-allocated-memory
Before on Abseil:
```
Evaluated relational algebra for predicate #select#cpe#12356#fffff@3ffb21o1 with tuple counts:
         1235939  ~0%    {2} r1 = SCAN functions OUTPUT In.0, In.0
         1235939  ~0%    {2} r2 = JOIN r1 WITH functions ON FIRST 1 OUTPUT Lhs.1, Lhs.0
        33500841  ~0%    {2} r3 = JOIN r2 WITH DataFlowUtil::Node::getEnclosingCallable#dispred#f0820431#ff_10#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.1
          280683  ~3%    {3} r4 = JOIN r3 WITH MustFlow::MkLocalPathNode#0227f5a1#fff ON FIRST 1 OUTPUT Rhs.2, Lhs.1, Lhs.0
           40970  ~2%    {4} r5 = JOIN r4 WITH MustFlow::MustFlowConfiguration::hasFlowPath#dispred#f0820431#fff#cpe#23_10#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.0
           40970  ~0%    {5} r6 = JOIN r5 WITH MustFlow::MkLocalPathNode#0227f5a1#fff_20#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3, Lhs.0
           40970  ~1%    {5} r7 = JOIN r6 WITH DataFlowUtil::Cached::TInstructionNode#47741e1f#ff_10#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3, Lhs.4
           40970  ~1%    {5} r8 = JOIN r7 WITH project#Instruction::VariableAddressInstruction#class#577b6a83#ff ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.4
           40970  ~0%    {6} r9 = JOIN r8 WITH SSAConstruction::Cached::getInstructionAst#2b11997e#ff ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.4, Rhs.1
           40970  ~2%    {7} r10 = JOIN r9 WITH SSAConstruction::Cached::getInstructionAst#2b11997e#ff ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.4, Lhs.5, Rhs.1
               0  ~0%    {6} r11 = JOIN r10 WITH Instruction::Instruction::getEnclosingFunction#dispred#f0820431#3#ff ON FIRST 2 OUTPUT Rhs.1, Lhs.2, Lhs.3, Lhs.4, Lhs.5, Lhs.6
               0  ~0%    {5} r12 = JOIN r11 WITH functions ON FIRST 1 OUTPUT Lhs.5, Lhs.1, Lhs.2, Lhs.3, Lhs.4
               0  ~0%    {5} r13 = JOIN r12 WITH Element::ElementBase::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.1, Lhs.3, Lhs.2, Lhs.4, Rhs.1
                         return r13
```

After:
```
Evaluated relational algebra for predicate #select#cpe#12356#fffff@1dbc97kv with tuple counts:
        40970  ~0%    {2} r1 = SCAN MustFlow::MustFlowConfiguration::hasFlowPath#dispred#f0820431#fff#cpe#23 OUTPUT In.1, In.0
        40970  ~0%    {3} r2 = JOIN r1 WITH MustFlow::MkLocalPathNode#0227f5a1#fff_20#join_rhs ON FIRST 1 OUTPUT Lhs.1, Lhs.0, Rhs.1
        40970  ~7%    {4} r3 = JOIN r2 WITH MustFlow::MkLocalPathNode#0227f5a1#fff_20#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.0, Lhs.1, Lhs.2
        40970  ~2%    {4} r4 = JOIN r3 WITH DataFlowUtil::Cached::TInstructionNode#47741e1f#ff_10#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.3
        40970  ~2%    {4} r5 = JOIN r4 WITH project#Instruction::VariableAddressInstruction#class#577b6a83#ff ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3
        40970  ~0%    {5} r6 = JOIN r5 WITH SSAConstruction::Cached::getInstructionAst#2b11997e#ff ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3, Rhs.1
        40970  ~1%    {6} r7 = JOIN r6 WITH SSAConstruction::Cached::getInstructionAst#2b11997e#ff ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.4, Rhs.1
        40970  ~0%    {6} r8 = JOIN r7 WITH Instruction::Instruction::getEnclosingFunction#dispred#f0820431#3#ff ON FIRST 1 OUTPUT Lhs.3, Rhs.1, Lhs.1, Lhs.2, Lhs.4, Lhs.5
            0  ~0%    {5} r9 = JOIN r8 WITH DataFlowUtil::Node::getEnclosingCallable#dispred#f0820431#fb ON FIRST 2 OUTPUT Lhs.5, Lhs.2, Lhs.3, Lhs.0, Lhs.4
            0  ~0%    {5} r10 = JOIN r9 WITH Element::ElementBase::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.3, Lhs.1, Lhs.2, Lhs.4, Rhs.1
                      return r10
```
2022-07-21 11:27:02 +02:00
Jeroen Ketema
466eb4a845 Merge pull request #9870 from jketema/exec-tainted-join
C++: Fix join-order problem in `cpp/command-line-injection`
2022-07-21 11:22:02 +02:00
Cornelius Riemenschneider
a437fcbbcc Merge pull request #9705 from github/criemen/csharp-lua-tracing
C#: Implement correct behavior for `dotnet build` tracing
2022-07-21 11:01:33 +02:00
Harry Maclean
4d0f6a0b96 Merge pull request #9788 from thiggy1342/add-activerecord-annotate
RB: Add ActiveRecord::Relation#annotate to sqlFragmentArgument()
2022-07-21 15:37:03 +12:00
thiggy1342
a10370f813 Merge branch 'main' into experimental-manually-check-request-verb 2022-07-20 16:33:36 -04:00
thiggy1342
b3f2159a7e Merge branch 'main' into experimental-strong-params 2022-07-20 16:33:32 -04:00
thiggy1342
17c80336f5 Merge branch 'main' into add-activerecord-annotate 2022-07-20 16:33:30 -04:00
Arthur Baars
8d80e0332e Ruby: update tree-sitter-ruby 2022-07-20 18:16:30 +02:00
Aditya Sharad
a1d9228a66 Merge pull request #9831 from adityasharad/docs/supported-frameworks-changelog-links
Docs: Update supported languages page with links to CLI and pack information
2022-07-20 07:36:37 -07:00
Jeroen Ketema
694d6395d5 C++: Fix join-order problem in cpp/command-line-injection
Before on Abseil Linux:
```
Evaluated relational algebra for predicate ExecTainted::ExecState#class#91000ffb#fff@41084cm7 with tuple counts:
        40879811  ~0%    {2} r1 = SCAN DataFlowUtil::Node::getLocation#dispred#f0820431#ff OUTPUT In.1, In.0
        40879811  ~0%    {2} r2 = JOIN r1 WITH Location::Location::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.1, Rhs.1
            7527  ~3%    {3} r3 = JOIN r2 WITH ExecTainted::interestingConcatenation#91000ffb#ff_10#join_rhs ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.0
            7527  ~0%    {4} r4 = JOIN r3 WITH DataFlowUtil::Node::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.2, Lhs.1, Lhs.0, Rhs.1
            7527  ~0%    {5} r5 = JOIN r4 WITH DataFlowUtil::Node::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.2, Lhs.1, Lhs.0, Lhs.3, Rhs.1
            7527  ~0%    {6} r6 = JOIN r5 WITH DataFlowUtil::Node::getLocation#dispred#f0820431#ff ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.2, Lhs.0, Lhs.3, Lhs.4
            7527  ~0%    {3} r7 = JOIN r6 WITH Location::Location::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT ((((((("ExecState (" ++ Rhs.1) ++ " | ") ++ Lhs.4) ++ ", ") ++ Lhs.1) ++ " | ") ++ Lhs.5 ++ ")"), Lhs.3, Lhs.2
                         return r7
```

After:
```
Evaluated relational algebra for predicate ExecTainted::ExecState#class#91000ffb#fff@1ffe61ps with tuple counts:
        7527  ~0%    {3} r1 = JOIN ExecTainted::interestingConcatenation#91000ffb#ff WITH DataFlowUtil::Node::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.1, Lhs.0, Rhs.1
        7527  ~0%    {4} r2 = JOIN r1 WITH DataFlowUtil::Node::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.0, Lhs.1, Lhs.2, Rhs.1
        7527  ~1%    {5} r3 = JOIN r2 WITH DataFlowUtil::Node::getLocation#dispred#f0820431#ff ON FIRST 1 OUTPUT Rhs.1, Lhs.1, Lhs.0, Lhs.2, Lhs.3
        7527  ~0%    {5} r4 = JOIN r3 WITH Location::Location::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT Lhs.1, Lhs.2, Lhs.3, Lhs.4, Rhs.1
        7527  ~4%    {6} r5 = JOIN r4 WITH DataFlowUtil::Node::getLocation#dispred#f0820431#ff ON FIRST 1 OUTPUT Rhs.1, Lhs.0, Lhs.1, Lhs.2, Lhs.3, Lhs.4
        7527  ~0%    {3} r6 = JOIN r5 WITH Location::Location::toString#dispred#f0820431#ff ON FIRST 1 OUTPUT ((((((("ExecState (" ++ Rhs.1) ++ " | ") ++ Lhs.3) ++ ", ") ++ Lhs.5) ++ " | ") ++ Lhs.4 ++ ")"), Lhs.1, Lhs.2
                     return r6
```
2022-07-20 16:27:47 +02:00
thiggy1342
8c55a15fa6 Merge branch 'main' into experimental-manually-check-request-verb 2022-07-20 10:27:40 -04:00
thiggy1342
6f74a2609c Merge branch 'main' into experimental-strong-params 2022-07-20 10:26:49 -04:00
thiggy1342
f54fc1a88d Merge branch 'main' into add-activerecord-annotate 2022-07-20 10:26:44 -04:00
Jeroen Ketema
c2b7300709 Merge pull request #9848 from geoffw0/stringlengthconflation5
Swift: More improvements for the string length conflation query
2022-07-20 14:05:05 +02:00
Cornelius Riemenschneider
e9e5d948b3 C#: Implement proper dotnet build handling in the Lua tracing config.
For proper C# tracing, `dotnet build` needs the parameter
/p:UseSharedCompilation=false. However, we can't pass that to the other
subcommands of `dotnet`, therefore we need to figure out which subcommand
of `dotnet` is being invoked.
2022-07-20 10:11:36 +00:00
Cornelius Riemenschneider
ca819573f5 Merge pull request #9862 from github/adityasharad/codeql-cli-2.10.1-mergeback
Merge codeql-cli-2.10.1 into main
2022-07-20 10:42:34 +02:00
Paolo Tranquilli
3527897eff Swift: make type optional in TypeRepr
A type representation may not have a type in unresolved things, which
for example pop up in inactive `#if` clauses.
2022-07-20 09:13:34 +02:00
Aditya Sharad
7620a6f653 Docs: Update supported languages page with links to CLI and pack information
Include links to the CLI changelog, CLI releases, bundle releases,
pack changelogs, and pack source.

Clarify that this support information applies to the current version of
the CLI, bundle, query packs, and library packs.
2022-07-19 14:58:27 -07:00
Asger F
aa53841466 Merge pull request #9828 from github/post-release-prep/codeql-cli-2.10.1
Post-release preparation for codeql-cli-2.10.1
2022-07-19 19:49:50 +02:00
Henti Smith
018a76bb17 Merge pull request #9857 from github/henti/new_actions_predicates
Added Workflow.getName and Step.GetId
2022-07-19 16:12:54 +01:00
Henti Smith
dcc76ddf36 Apply suggestions from code review
Co-authored-by: Henry Mercer <henrymercer@github.com>
2022-07-19 15:53:12 +01:00
Henti Smith
0828474192 Added Workflow::getName and Step::GetId 2022-07-19 15:34:10 +01:00
thiggy1342
43a9b8960e Merge branch 'main' into experimental-manually-check-request-verb 2022-07-19 10:29:48 -04:00
thiggy1342
cf23d338f3 Merge branch 'main' into experimental-strong-params 2022-07-19 10:29:36 -04:00
thiggy1342
6bc2fe513d Merge branch 'main' into add-activerecord-annotate 2022-07-19 10:29:24 -04:00
Asger F
b9bdee6651 Merge branch 'main' into post-release-prep/codeql-cli-2.10.1 2022-07-19 16:24:35 +02:00
Cornelius Riemenschneider
03bf9eb166 Merge pull request #9837 from github/aeisenberg/definitions.ql
Move definitions.ql back to src
2022-07-19 14:43:10 +02:00
Taus
bfe90413e2 Merge pull request #9847 from alexet/alexet/fix-predicate-binding
Python: Fix binding incorrect predicate.
2022-07-19 13:59:13 +02:00
Arthur Baars
dcbd82907f Merge pull request #9845 from aibaars/skip-dotgit
Ruby: skip .git folder
2022-07-19 11:58:43 +02:00
Harry Maclean
ec1d1eb547 Ruby: Add change note 2022-07-19 14:33:51 +12:00
thiggy1342
962155fd61 fix changenotes 2022-07-19 00:33:04 +00:00
thiggy1342
9586259706 style tweak for checking multiple method names 2022-07-19 00:29:30 +00:00
thiggy1342
304203ad2f fix path problem output 2022-07-19 00:25:50 +00:00
Harry Maclean
7b8603c89b Ruby: Model Arel.sql 2022-07-19 11:27:15 +12:00
alexet
f9b6ca76e5 Python: Fix binding incorrect predicate. 2022-07-18 16:28:19 +01:00
thiggy1342
fc00e56058 Merge branch 'main' into experimental-manually-check-request-verb 2022-07-18 10:58:13 -04:00
Arthur Baars
c9e5206396 Ruby: skip .git folder 2022-07-18 15:26:38 +02:00
Geoffrey White
541df9b550 Swift: Remove TODO comment. We have a test for this problem now. 2022-07-18 14:26:12 +01:00
Geoffrey White
336548f746 Swift: Improve comments. 2022-07-18 14:24:16 +01:00
Geoffrey White
9474e63faf Swift: Clean up isSink (4 - move common code out). 2022-07-18 14:24:15 +01:00
Geoffrey White
b136790efd Swift: Clean up isSink (3 - rename f -> funcDecl and move that out as well; in the other two cases this variable didn't exist, now it does). 2022-07-18 14:24:14 +01:00
Geoffrey White
0bd94a6307 Swift: Clean up isSink (2 - rename methodName -> funcName and move that out as well). 2022-07-18 14:24:13 +01:00
Geoffrey White
4854679a40 Swift: Clean up isSink (1 - move common variables to an outer exists). 2022-07-18 14:24:13 +01:00
Geoffrey White
39fb714ad1 Swift: Add test with substring declared differently. 2022-07-18 14:24:12 +01:00
Paolo Tranquilli
e1bd4a78ff Merge branch 'main' into redsun82/swift-type-repr-collapse 2022-07-18 14:05:43 +02:00
Paolo Tranquilli
410167671f Merge pull request #9795 from github/redsun82/swift-extraction
Swift: extract more entities
2022-07-18 13:37:43 +02:00
Nick Rolfe
eebba36b18 Merge pull request #9708 from github/nickrolfe/pathname
Ruby: model the standard library's `Pathname` class
2022-07-18 11:29:30 +01:00
Paolo Tranquilli
98fc8812fc Merge 'main' into redsun82/swift-extraction 2022-07-18 11:55:21 +02:00
Paolo Tranquilli
c779936ee8 Swift: commit forgotten files 2022-07-18 11:19:40 +02:00
Paolo Tranquilli
c08c3955d6 Swift: add UnresolvedPatternExpr test 2022-07-18 10:37:54 +02:00
Paolo Tranquilli
78fc356feb Swift: address review comments 2022-07-18 10:29:20 +02:00
Nick Rolfe
dbd6607875 Ruby: use ASCII dash in comment
Co-authored-by: Harry Maclean <hmac@github.com>
2022-07-18 08:54:58 +01:00
Harry Maclean
cc5f59f313 Merge pull request #9138 from hmac/hmac/array-inclusion-guard-local-flow
Ruby: Make StringArrayInclusion more sensitive
2022-07-18 10:11:49 +12:00
yo-h
d4443592eb Merge pull request #9776 from raulgarciamsft/azure-sdk-client-encryption-version
New queries to detect unsafe client side encryption in Azure Storage
2022-07-16 14:59:51 -04:00
Raul Garcia
6b17890e4f Fixing warning on usage of a deprecated feature. 2022-07-16 08:30:06 -07:00
Raul Garcia
eefa659503 Update java/ql/src/experimental/Security/CWE/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: yo-h <55373593+yo-h@users.noreply.github.com>
2022-07-16 08:23:59 -07:00
Raul Garcia
fe789c8aa9 Update java/ql/src/experimental/Security/CWE/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: yo-h <55373593+yo-h@users.noreply.github.com>
2022-07-16 08:22:18 -07:00
Andrew Eisenberg
2f50549184 Move definitions.ql back to src 2022-07-15 11:48:15 -07:00
thiggy1342
a1df1d1119 Merge branch 'main' into experimental-strong-params 2022-07-15 11:17:57 -04:00
thiggy1342
ee1c09329f Merge branch 'main' into add-activerecord-annotate 2022-07-15 11:17:48 -04:00
Aditya Sharad
d50816a284 Merge pull request #9802 from adityasharad/docs/language-pack-changelogs
Docs: Add links from query help to query pack changelog for each language
2022-07-14 08:52:50 -07:00
github-actions[bot]
0ee476129a Post-release preparation for codeql-cli-2.10.1 2022-07-14 14:38:49 +00:00
Aditya Sharad
d13f9d5d71 Update docs/codeql/query-help/javascript.rst
Co-authored-by: Felicity Chapman <felicitymay@github.com>
2022-07-14 07:29:29 -07:00
Paolo Tranquilli
7fbe4f8547 Merge pull request #9815 from github/redsun82/swift-exclusive-file
Swift: trap output rework
2022-07-14 16:23:44 +02:00
thiggy1342
592ce3ec58 Merge branch 'main' into add-activerecord-annotate 2022-07-14 09:55:25 -04:00
Paolo Tranquilli
22ff8c2c7e Swift: remove redundant braces 2022-07-14 15:40:48 +02:00
Paolo Tranquilli
3e06455ac1 Swift: delete TargetFile's move assignment 2022-07-14 15:39:36 +02:00
Anders Schack-Mulligen
21066d277f Merge pull request #9819 from github/workflow/coverage/update
Update CSV framework coverage reports
2022-07-14 15:13:37 +02:00
Erik Krogh Kristensen
5ba4f6dae8 Merge pull request #9826 from erik-krogh/combineWork
QL: rewrite the QL-for-QL workflow to just do everything in one go
2022-07-14 14:24:31 +02:00
Erik Krogh Kristensen
a7a9428dc1 split the sarif file into languages 2022-07-14 13:20:52 +02:00
Erik Krogh Kristensen
47c9b446f0 exclude upgrade scripts from QL-for-QL 2022-07-14 13:01:40 +02:00
Erik Krogh Kristensen
380070f2e4 rewrite the QL-for-QL workflow to just do everything in one go 2022-07-14 12:54:27 +02:00
Erik Krogh Kristensen
33fdcf1e4f Merge pull request #9794 from erik-krogh/unusedVue
JS: exclude variables in .vue files form js/unused-local-variable
2022-07-14 10:57:06 +02:00
Asger F
855d4c2ea1 Merge pull request #9718 from asgerf/js/case-sensitive-middleware
JS: Add 'case sensitive middleware' query
2022-07-14 10:47:58 +02:00
Erik Krogh Kristensen
43a82004b2 Merge pull request #9798 from erik-krogh/backtrackers
JS: use small steps in TypeBackTracker correctly
2022-07-14 10:28:07 +02:00
Asger F
18c5a8c8da Merge branch 'main' into js/case-sensitive-middleware 2022-07-14 09:38:35 +02:00
Asger F
da8123072d Apply suggestions from doc review
Co-authored-by: mc <42146119+mchammer01@users.noreply.github.com>
2022-07-14 09:38:10 +02:00
Paolo Tranquilli
f1144b9672 Swift: small TypeRepr visit rewording 2022-07-14 06:18:51 +02:00
Paolo Tranquilli
d748cb483d Swift: include cleanup
Fix a problem with `sstream` not being transitively included on macOS.
2022-07-14 06:10:12 +02:00
Paolo Tranquilli
4c53c341f6 Swift: make TargetFile::good() a class invariant
Fallible initialization has been moved to a factory function, and
`commit` has been moved to the destructor.
2022-07-14 06:02:35 +02:00
thiggy1342
62a10e20b2 Merge branch 'main' into experimental-manually-check-request-verb 2022-07-13 20:28:09 -04:00
thiggy1342
8ca7d7d775 update change note 2022-07-14 00:22:38 +00:00
thiggy1342
9d277027a3 Merge branch 'main' into experimental-strong-params 2022-07-13 20:19:50 -04:00
thiggy1342
3dd61cadf4 formatting query 2022-07-14 00:19:36 +00:00
github-actions[bot]
9a186ba5d2 Add changed framework coverage reports 2022-07-14 00:18:56 +00:00
thiggy1342
ee79834cc8 formatting in qhelp 2022-07-14 00:15:39 +00:00
thiggy1342
ae634367c9 add qhelp file 2022-07-14 00:11:52 +00:00
thiggy1342
2cc703387b use taint config for data flow 2022-07-14 00:11:52 +00:00
thiggy1342
f5301aa478 Merge branch 'main' into add-activerecord-annotate 2022-07-13 14:35:44 -04:00
Raul Garcia
f7c47b6c75 Update python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.py
Co-authored-by: Taus <tausbn@github.com>
2022-07-13 08:34:48 -07:00
Paolo Tranquilli
f7dca4d70f Swift: trap output rework
Firstly, this change reworks how inter-process races are resolved.
Moreover some responsability reorganization has led to merging
`TrapArena` and `TrapOutput` again into a `TrapDomain` class.

A `TargetFile` class is introduced, that is successfully created
only for the first process that starts processing a given trap output
file. From then on `TargetFile` simply wraps around `<<` stream
operations, dumping them to a temporary file. When `TargetFile::commit`
is called, the temporary file is moved on to the actual target trap
file.

Processes that lose the race can now just ignore the unneeded
extraction and go on, while previously all processes would carry out
all extractions overwriting each other at the end.

Some of the file system logic contained in `SwiftExtractor.cpp` has been
moved to this class, and two TODOs are solved:
* introducing a better inter process file collision avoidance strategy
* better error handling for trap output operations: if unable to write
  to the trap file (or carry out other basic file operations), we just
  abort.

The changes to `ExprVisitor` and `StmtVisitor` are due to wanting to
hide the raw `TrapDomain::createLabel` from them, and bring more
funcionality under the generic caching/dispatching mechanism.
2022-07-13 11:19:57 +02:00
Harry Maclean
1fa2144716 Ruby: Update test fixtures 2022-07-13 21:02:08 +12:00
Erik Krogh Kristensen
fd10947ca0 use small steps in TypeBackTracker correctly 2022-07-13 10:29:57 +02:00
Harry Maclean
49aab51893 Ruby: Make helper predicate private 2022-07-13 18:20:27 +12:00
Harry Maclean
ea95e2e1d0 Ruby: Use InclusionTests library in barrier guards 2022-07-13 18:20:27 +12:00
Harry Maclean
b9fc82a741 Ruby: Test both old and new-style barrier guards 2022-07-13 18:20:25 +12:00
Harry Maclean
4cfaa86d5d Ruby: Update new-style barrier-guard 2022-07-13 18:20:14 +12:00
Harry Maclean
5f17d8370c Ruby: Small change to isArrayExpr 2022-07-13 18:20:14 +12:00
Harry Maclean
63dcce9a31 Ruby: Refactor isArrayConstant 2022-07-13 18:20:14 +12:00
Harry Maclean
b5a3d3c488 Ruby: Extract isArrayConstant
This predicate might be useful elsewhere.
2022-07-13 18:20:14 +12:00
Harry Maclean
301914d80c Ruby: Add an extra barrier guard test 2022-07-13 18:20:14 +12:00
Harry Maclean
706d1d2eee Ruby: Make StringArrayInclusion more sensitive
We now recognise the following pattern as a barrier guard for `x`:

    values = ["foo", "bar"]

    if values.include? x
      sink x
    end
2022-07-13 18:20:12 +12:00
Raul Garcia
0dbb03f732 Adding CVE information. 2022-07-12 21:49:19 -07:00
thiggy1342
7df7b92d86 Merge branch 'main' into experimental-manually-check-request-verb 2022-07-12 20:36:34 -04:00
thiggy1342
7129002573 tweak tests more 2022-07-13 00:33:58 +00:00
thiggy1342
b3f1a513d1 Update tests 2022-07-13 00:25:43 +00:00
thiggy1342
9a0a9491da Merge branch 'main' into add-activerecord-annotate 2022-07-12 20:13:56 -04:00
thiggy1342
2566ae9889 Merge branch 'main' into experimental-strong-params 2022-07-12 20:12:51 -04:00
thiggy1342
db5f63b208 add tests 2022-07-12 23:14:16 +00:00
thiggy1342
7facc63699 remove predicate 2022-07-12 22:59:48 +00:00
thiggy1342
74d6061082 Merge branch 'main' into experimental-manually-check-request-verb 2022-07-12 17:15:54 -04:00
Raul Garcia
a4adf06713 Addressing feedback for the qhelp file. 2022-07-12 13:51:12 -07:00
Raul Garcia
d929b1338b Addressing API::Node feedback for all predicates 2022-07-12 11:55:06 -07:00
Raul Garcia
64343e00f4 Update java/ql/src/experimental/Security/CWE/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: Chris Smowton <smowton@github.com>
2022-07-12 08:14:25 -07:00
Raul Garcia
8a48708014 Update java/ql/src/experimental/Security/CWE/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: Chris Smowton <smowton@github.com>
2022-07-12 08:14:13 -07:00
Raul Garcia
2bac181094 Update java/ql/src/experimental/Security/CWE/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: Chris Smowton <smowton@github.com>
2022-07-12 08:13:53 -07:00
Raul Garcia
a4e35a97ea Update java/ql/src/experimental/Security/CWE/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: Chris Smowton <smowton@github.com>
2022-07-12 08:13:38 -07:00
Raul Garcia
a51d713925 Update java/ql/src/experimental/Security/CWE/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: Chris Smowton <smowton@github.com>
2022-07-12 08:13:12 -07:00
Paolo Tranquilli
48c71c9407 Swift: add comment about TypeRepr in ASTNode fetching 2022-07-12 12:10:22 +02:00
Paolo Tranquilli
033b239b22 Swift: collapse TypeRepr hierarchy
Now `TypeRepr` is a final class in the AST, which is more or less just
a type with a location in code.

As the frontend does not provide a direct way to get a type from a
type representation, this information must be provided when fetching
the label of a type repr.

This meant:
* removing the type repr field from `EnumIsCaseExpr`: this is a virtual
  AST node introduced in place of some kinds of `IsEpxr`. The type
  repr is still available from the `ConditionalCheckedCastExpr` wrapped
  by this virtual node, and we will rebuild the original `IsExpr` with
  the IPA layer.
* some logic to get the type of keypath roots has been added to
  `KeyPathExpr`. This was done to keep the `TypeRepr` to `Type` relation
  total in the DB, but goes against the design of a dumb extractor. The
  logic could be moved to QL in the future
* in the control flow library, `TypeRepr` children are now ignored. As
  far as I can tell, there is no runtime evaluation going on in
  `TypeRepr`s, so it does not make much sense to have control flow
  through them.
2022-07-12 10:49:14 +02:00
Paolo Tranquilli
47a4cac8ee Merge branch 'main' into redsun82/swift-extraction 2022-07-12 09:29:10 +02:00
Raul Garcia
d5791e2d56 Addressing feedback from the PR 2022-07-11 15:45:15 -07:00
Aditya Sharad
02e11b7ee9 Docs: Add links from query help to query pack changelog for each language 2022-07-11 13:59:38 -07:00
Raul Garcia
ac05577966 Making various changes based on the feedback. Pending: 2 non-trivial fixes for Java & Python. 2022-07-11 13:25:35 -07:00
Raul Garcia
e5702d0e15 Update python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: Taus <tausbn@github.com>
2022-07-11 13:07:37 -07:00
Raul Garcia
7fc9ae6c49 Update python/ql/src/experimental/Security/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: Taus <tausbn@github.com>
2022-07-11 13:07:20 -07:00
Raul Garcia
5d89a5d164 Update csharp/ql/src/experimental/Security Features/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.ql
Co-authored-by: Taus <tausbn@github.com>
2022-07-11 08:42:50 -07:00
Raul Garcia
156bc34cda Update UnsafeUsageOfClientSideEncryptionVersion.qhelp 2022-07-11 08:41:05 -07:00
thiggy1342
ad7c3e7217 Merge branch 'main' into experimental-manually-check-request-verb 2022-07-11 10:20:07 -04:00
thiggy1342
539fbbc126 Merge branch 'main' into experimental-strong-params 2022-07-11 10:20:00 -04:00
Paolo Tranquilli
39406436bf Swift: extract IfConfigDecl
This also adds `UnresolvedDeclRefExpr` tests, as `IfConfigDecl`
consistently introduces those.
2022-07-11 15:11:13 +02:00
Erik Krogh Kristensen
9ed7aa9fae exclude variables in .vue files form js/unused-local-variable 2022-07-11 12:52:23 +02:00
Chris Smowton
74641ccfee Simplify test for no-arg constructor 2022-07-11 11:01:19 +01:00
Paolo Tranquilli
7d5dd384c3 Swift: extract UnresolvedPatternExpr 2022-07-11 10:59:00 +02:00
Paolo Tranquilli
7c3cadc9b6 Swift: extract OpenedArchetypeType 2022-07-11 10:48:21 +02:00
thiggy1342
e8e8da1b31 fix lib test expect for ActionController 2022-07-08 19:01:01 +00:00
thiggy1342
5d3232c614 refactor to use data flow 2022-07-08 18:53:24 +00:00
thiggy1342
96e66c4a50 move tests 2022-07-08 18:39:04 +00:00
thiggy1342
0435105d16 Merge remote-tracking branch 'upstream/main' into experimental-strong-params 2022-07-08 18:36:09 +00:00
thiggy1342
6aab970a9e refactor query to use cfg and dataflow 2022-07-08 18:32:54 +00:00
thiggy1342
bd50fd7f1e format fix 2022-07-08 17:20:41 +00:00
thiggy1342
11e39aa030 Add changelog 2022-07-07 21:40:16 +00:00
thiggy1342
940254d251 update framework tests 2022-07-07 19:39:59 +00:00
thiggy1342
b4869158f2 expand query tests for cwe-089 2022-07-07 19:23:57 +00:00
thiggy1342
2f1cfa816f Add annotate arguments as sqli sink 2022-07-07 19:23:06 +00:00
Raul Garcia
f8994d04d6 Clean up 2022-07-07 11:49:05 -07:00
Raul Garcia
01da877d0e Moving the new query to experimental. It was added to the wrong folder initially. 2022-07-06 14:07:14 -07:00
Raul Garcia
dd1a9a22e3 Update UnsafeUsageOfClientSideEncryptionVersion.qhelp 2022-07-05 13:58:38 -07:00
Raul Garcia
f5c6b45014 Update UnsafeUsageOfClientSideEncryptionVersion.qhelp 2022-07-05 13:58:11 -07:00
Raul Garcia
56060e0610 Update csharp/ql/src/experimental/Security Features/CWE-327/Azure/UnsafeUsageOfClientSideEncryptionVersion.qhelp
Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com>
2022-07-05 13:57:28 -07:00
Raul Garcia
e43e5810cf New queries to detect unsafe client side encryption in Azure Storage 2022-07-01 17:08:35 -07:00
Nick Rolfe
02dd933e5f Ruby: move Pathname from core to stdlib 2022-06-30 10:08:25 +01:00
Nick Rolfe
5db2f9a768 Merge remote-tracking branch 'origin/main' into nickrolfe/pathname 2022-06-29 13:16:49 +01:00
Nick Rolfe
c1302a90e0 Ruby: use MaD for more precise Pathname flow summaries 2022-06-29 13:16:18 +01:00
Jeroen Ketema
a7956ad422 C++: Add change note 2022-06-28 15:32:43 +02:00
Jeroen Ketema
82c9b8b494 C++: Ensure only one Variable exists for every global variable
Depending on the extraction order, before this change there might be multiple
`GlobalVariable`s per declared global variable. See the tests in
`cpp/ql/test/library-tests/variables/global`. This change ensures that only one
of those `GlobalVariable`s is visible to the user if we can locate a unique
definition. If not, the old situation persists.

Note that an exception needs to be made for templated variables. Here, the
definition refers to the non-instantiated template, while a declaration that
is not a definition refers to an instantiation. In case the instantiation refers
to a template parameter, the mangled names of the template and the instantiation
will be identical. This happens for example in the following case:
```
template <typename T>
T x = T(42);           // Uninstantiated templated variable

template <typename T>
class C {
  T y = x<T>;          // Instantiation using a template parameter
};
```
Since the uninstantiated template and the instantiation are two different
entities, we do not unify them as described above.
2022-06-28 15:32:43 +02:00
Asger F
c33690381e JS: Add explicit 'this' 2022-06-28 10:21:44 +02:00
Asger F
c1a2e2abe0 JS: Rename to isLikelyCaseSensitiveRegExp 2022-06-28 10:21:33 +02:00
Asger F
fd28397056 JS: Fix typo 2022-06-28 10:10:23 +02:00
Asger F
9cf48fc804 JS: Clarify that strings are case insensitive by default 2022-06-28 10:09:56 +02:00
Asger F
b1251f0c63 JS: invertCase -> toOtherCase 2022-06-28 10:07:57 +02:00
Asger F
3c9e743495 JS: Add change note 2022-06-27 16:16:38 +02:00
Asger F
17d139c87d JS: Add qhelp 2022-06-27 16:14:30 +02:00
Nick Rolfe
280c959dc8 Merge branch 'main' into nickrolfe/pathname 2022-06-27 11:11:17 +01:00
Asger F
d92430b0e7 JS: Fix FP from char class 2022-06-27 09:08:37 +02:00
Asger F
9e4116618a JS: Add CaseSensitiveMiddlewarePath query 2022-06-27 09:08:37 +02:00
Nick Rolfe
c1515db09c Ruby: modeling of some file-related concepts for the Pathname class 2022-06-24 14:14:07 +01:00
Nick Rolfe
03d0f66247 Ruby: add flow summaries for Pathname class 2022-06-24 14:14:06 +01:00
thiggy1342
6ea1aad5fc more style fixes 2022-06-23 22:57:51 -04:00
thiggy1342
ce2edd4b28 style tweaks 2022-06-24 02:46:48 +00:00
thiggy1342
ca074e2275 add qhelp file 2022-06-24 02:19:06 +00:00
thiggy1342
cf36333082 forgot to finish this test 2022-06-24 02:18:48 +00:00
thiggy1342
45dd38df6e polish up dataflow query 2022-06-24 01:50:20 +00:00
thiggy1342
e838b83f5f attempt to introduce dataflow tracking 2022-06-23 02:21:47 +00:00
thiggy1342
995f365568 just check string literal 2022-06-22 02:17:01 +00:00
thiggy1342
c767f241ad narrow query scope 2022-06-22 02:12:23 +00:00
thiggy1342
f6c4b5c44b Merge branch 'experimental-manually-check-request-verb' of https://github.com/thiggy1342/codeql into experimental-manually-check-request-verb 2022-06-21 21:27:39 +00:00
thiggy1342
990747cd22 Limit findings to just those called in Controllers 2022-06-21 21:27:18 +00:00
thiggy1342
53729f99c5 restrict findings to just controller classes 2022-06-21 20:28:29 +00:00
thiggy1342
bbe17b3667 Merge branch 'experimental-strong-params' of https://github.com/thiggy1342/codeql into experimental-strong-params 2022-06-21 19:31:18 +00:00
thiggy1342
83b720d730 first draft of weak params query 2022-06-21 19:28:53 +00:00
thiggy1342
3478e7e910 first draft of weak params query 2022-06-18 20:43:58 +00:00
thiggy1342
0456870136 Merge branch 'main' into experimental-manually-check-request-verb 2022-06-18 15:21:53 -04:00
thiggy1342
ecb2114b7b replace duplicate post with put 2022-06-18 19:21:17 +00:00
thiggy1342
8b36191023 drop precision to low for now 2022-06-18 18:38:58 +00:00
thiggy1342
059c4d38ad refine query to use appropriate types 2022-06-18 18:26:45 +00:00
thiggy1342
8aa2602d9e trying to hone in on eq comparison and include? 2022-06-18 03:09:04 +00:00
thiggy1342
6bef71ea2c tweaks to tests 2022-06-14 02:17:12 +00:00
thiggy1342
7bdec98e6f draft tests 2022-06-14 02:13:15 +00:00
thiggy1342
c012c235c6 rough draft of check request verb query 2022-06-14 01:45:02 +00:00
274 changed files with 4816 additions and 1558 deletions

View File

@@ -10,9 +10,10 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
jobs: jobs:
queries: analyze:
runs-on: ubuntu-latest runs-on: ubuntu-latest-xl
steps: steps:
### Build the queries ###
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Find codeql - name: Find codeql
id: find-codeql id: find-codeql
@@ -48,11 +49,7 @@ jobs:
name: query-pack-zip name: query-pack-zip
path: ${{ runner.temp }}/query-pack.zip path: ${{ runner.temp }}/query-pack.zip
extractors: ### Build the extractor ###
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache entire extractor - name: Cache entire extractor
id: cache-extractor id: cache-extractor
uses: actions/cache@v3 uses: actions/cache@v3
@@ -96,15 +93,8 @@ jobs:
ql/target/release/ql-extractor ql/target/release/ql-extractor
ql/target/release/ql-extractor.exe ql/target/release/ql-extractor.exe
retention-days: 1 retention-days: 1
package:
runs-on: ubuntu-latest
needs: ### Package the queries and extractor ###
- extractors
- queries
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
with: with:
name: query-pack-zip name: query-pack-zip
@@ -132,16 +122,8 @@ jobs:
name: codeql-ql-pack name: codeql-ql-pack
path: codeql-ql.zip path: codeql-ql.zip
retention-days: 1 retention-days: 1
analyze:
runs-on: ubuntu-latest
strategy:
matrix:
folder: [cpp, csharp, java, javascript, python, ql, ruby, swift, go]
needs: ### Run the analysis ###
- package
steps:
- name: Download pack - name: Download pack
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
@@ -161,14 +143,11 @@ jobs:
env: env:
PACK: ${{ runner.temp }}/pack PACK: ${{ runner.temp }}/pack
- name: Checkout repository
uses: actions/checkout@v3
- name: Create CodeQL config file - name: Create CodeQL config file
run: | run: |
echo "paths:" > ${CONF}
echo " - ${FOLDER}" >> ${CONF}
echo "paths-ignore:" >> ${CONF} echo "paths-ignore:" >> ${CONF}
echo " - ql/ql/test" >> ${CONF} echo " - ql/ql/test" >> ${CONF}
echo " - \"*/ql/lib/upgrades/\"" >> ${CONF}
echo "disable-default-queries: true" >> ${CONF} echo "disable-default-queries: true" >> ${CONF}
echo "packs:" >> ${CONF} echo "packs:" >> ${CONF}
echo " - codeql/ql" >> ${CONF} echo " - codeql/ql" >> ${CONF}
@@ -176,7 +155,6 @@ jobs:
cat ${CONF} cat ${CONF}
env: env:
CONF: ./ql-for-ql-config.yml CONF: ./ql-for-ql-config.yml
FOLDER: ${{ matrix.folder }}
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@aa93aea877e5fb8841bcb1193f672abf6e9f2980 uses: github/codeql-action/init@aa93aea877e5fb8841bcb1193f672abf6e9f2980
with: with:
@@ -187,39 +165,24 @@ jobs:
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@aa93aea877e5fb8841bcb1193f672abf6e9f2980 uses: github/codeql-action/analyze@aa93aea877e5fb8841bcb1193f672abf6e9f2980
with: with:
category: "ql-for-ql-${{ matrix.folder }}" category: "ql-for-ql"
- name: Copy sarif file to CWD - name: Copy sarif file to CWD
run: cp ../results/ql.sarif ./${{ matrix.folder }}.sarif run: cp ../results/ql.sarif ./ql-for-ql.sarif
- name: Fixup the $scema in sarif # Until https://github.com/microsoft/sarif-vscode-extension/pull/436/ is part in a stable release - name: Fixup the $scema in sarif # Until https://github.com/microsoft/sarif-vscode-extension/pull/436/ is part in a stable release
run: | run: |
sed -i 's/\$schema.*/\$schema": "https:\/\/raw.githubusercontent.com\/oasis-tcs\/sarif-spec\/master\/Schemata\/sarif-schema-2.1.0",/' ${{ matrix.folder }}.sarif sed -i 's/\$schema.*/\$schema": "https:\/\/raw.githubusercontent.com\/oasis-tcs\/sarif-spec\/master\/Schemata\/sarif-schema-2.1.0",/' ql-for-ql.sarif
- name: Sarif as artifact - name: Sarif as artifact
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: ${{ matrix.folder }}.sarif name: ql-for-ql.sarif
path: ${{ matrix.folder }}.sarif path: ql-for-ql.sarif
- name: Split out the sarif file into langs
combine: run: |
runs-on: ubuntu-latest mkdir split-sarif
needs: node ./ql/scripts/split-sarif.js ql-for-ql.sarif split-sarif
- analyze - name: Upload langs as artifacts
steps:
- uses: actions/checkout@v3
- name: Make a folder for artifacts.
run: mkdir -p results
- name: Download all sarif files
uses: actions/download-artifact@v3
with:
path: results
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Combine all sarif files
run: |
node ./ql/scripts/merge-sarif.js results/**/*.sarif combined.sarif
- name: Upload combined sarif file
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: combined.sarif name: ql-for-ql-langs
path: combined.sarif path: split-sarif
retention-days: 1

View File

@@ -0,0 +1,4 @@
---
category: fix
---
* Under certain circumstances a variable declaration that is not also a definition could be associated with a `Variable` that did not have the definition as a `VariableDeclarationEntry`. This is now fixed, and a unique `Variable` will exist that has both the declaration and the definition as a `VariableDeclarationEntry`.

View File

@@ -1,5 +1,5 @@
name: codeql/cpp-all name: codeql/cpp-all
version: 0.3.1 version: 0.3.2-dev
groups: cpp groups: cpp
dbscheme: semmlecode.cpp.dbscheme dbscheme: semmlecode.cpp.dbscheme
extractor: cpp extractor: cpp

View File

@@ -6,6 +6,7 @@
import semmle.code.cpp.Location import semmle.code.cpp.Location
private import semmle.code.cpp.Enclosing private import semmle.code.cpp.Enclosing
private import semmle.code.cpp.internal.ResolveClass private import semmle.code.cpp.internal.ResolveClass
private import semmle.code.cpp.internal.ResolveGlobalVariable
/** /**
* Get the `Element` that represents this `@element`. * Get the `Element` that represents this `@element`.
@@ -28,9 +29,12 @@ Element mkElement(@element e) { unresolveElement(result) = e }
pragma[inline] pragma[inline]
@element unresolveElement(Element e) { @element unresolveElement(Element e) {
not result instanceof @usertype and not result instanceof @usertype and
not result instanceof @variable and
result = e result = e
or or
e = resolveClass(result) e = resolveClass(result)
or
e = resolveGlobalVariable(result)
} }
/** /**

View File

@@ -6,6 +6,7 @@ import semmle.code.cpp.Element
import semmle.code.cpp.exprs.Access import semmle.code.cpp.exprs.Access
import semmle.code.cpp.Initializer import semmle.code.cpp.Initializer
private import semmle.code.cpp.internal.ResolveClass private import semmle.code.cpp.internal.ResolveClass
private import semmle.code.cpp.internal.ResolveGlobalVariable
/** /**
* A C/C++ variable. For example, in the following code there are four * A C/C++ variable. For example, in the following code there are four
@@ -32,6 +33,8 @@ private import semmle.code.cpp.internal.ResolveClass
* can have multiple declarations. * can have multiple declarations.
*/ */
class Variable extends Declaration, @variable { class Variable extends Declaration, @variable {
Variable() { isVariable(underlyingElement(this)) }
override string getAPrimaryQlClass() { result = "Variable" } override string getAPrimaryQlClass() { result = "Variable" }
/** Gets the initializer of this variable, if any. */ /** Gets the initializer of this variable, if any. */

View File

@@ -0,0 +1,60 @@
private predicate hasDefinition(@globalvariable g) {
exists(@var_decl vd | var_decls(vd, g, _, _, _) | var_def(vd))
}
pragma[noinline]
private predicate onlyOneCompleteGlobalVariableExistsWithMangledName(@mangledname name) {
strictcount(@globalvariable g | hasDefinition(g) and mangled_name(g, name)) = 1
}
/** Holds if `g` is a unique global variable with a definition named `name`. */
pragma[noinline]
private predicate isGlobalWithMangledNameAndWithDefinition(@mangledname name, @globalvariable g) {
hasDefinition(g) and
mangled_name(g, name) and
onlyOneCompleteGlobalVariableExistsWithMangledName(name)
}
/** Holds if `g` is a global variable without a definition named `name`. */
pragma[noinline]
private predicate isGlobalWithMangledNameAndWithoutDefinition(@mangledname name, @globalvariable g) {
not hasDefinition(g) and
mangled_name(g, name)
}
/**
* Holds if `incomplete` is a global variable without a definition, and there exists
* a unique global variable `complete` with the same name that does have a definition.
*/
private predicate hasTwinWithDefinition(@globalvariable incomplete, @globalvariable complete) {
exists(@mangledname name |
not variable_instantiation(incomplete, complete) and
isGlobalWithMangledNameAndWithoutDefinition(name, incomplete) and
isGlobalWithMangledNameAndWithDefinition(name, complete)
)
}
import Cached
cached
private module Cached {
/**
* If `v` is a global variable without a definition, and there exists a unique
* global variable with the same name that does have a definition, then the
* result is that unique global variable. Otherwise, the result is `v`.
*/
cached
@variable resolveGlobalVariable(@variable v) {
hasTwinWithDefinition(v, result)
or
not hasTwinWithDefinition(v, _) and
result = v
}
cached
predicate isVariable(@variable v) {
not v instanceof @globalvariable
or
v = resolveGlobalVariable(_)
}
}

View File

@@ -74,13 +74,12 @@ class ReturnStackAllocatedMemoryConfig extends MustFlowConfiguration {
from from
MustFlowPathNode source, MustFlowPathNode sink, VariableAddressInstruction var, MustFlowPathNode source, MustFlowPathNode sink, VariableAddressInstruction var,
ReturnStackAllocatedMemoryConfig conf, Function f ReturnStackAllocatedMemoryConfig conf
where where
conf.hasFlowPath(source, sink) and conf.hasFlowPath(pragma[only_bind_into](source), pragma[only_bind_into](sink)) and
source.getNode().asInstruction() = var and source.getNode().asInstruction() = var and
// Only raise an alert if we're returning from the _same_ callable as the on that // Only raise an alert if we're returning from the _same_ callable as the on that
// declared the stack variable. // declared the stack variable.
var.getEnclosingFunction() = pragma[only_bind_into](f) and var.getEnclosingFunction() = sink.getNode().getEnclosingCallable()
sink.getNode().getEnclosingCallable() = pragma[only_bind_into](f)
select sink.getNode(), source, sink, "May return stack-allocated memory from $@.", var.getAst(), select sink.getNode(), source, sink, "May return stack-allocated memory from $@.", var.getAst(),
var.getAst().toString() var.getAst().toString()

View File

@@ -77,7 +77,7 @@ class ExecState extends DataFlow::FlowState {
ExecState() { ExecState() {
this = this =
"ExecState (" + fst.getLocation() + " | " + fst + ", " + snd.getLocation() + " | " + snd + ")" and "ExecState (" + fst.getLocation() + " | " + fst + ", " + snd.getLocation() + " | " + snd + ")" and
interestingConcatenation(fst, snd) interestingConcatenation(pragma[only_bind_into](fst), pragma[only_bind_into](snd))
} }
DataFlow::Node getFstNode() { result = fst } DataFlow::Node getFstNode() { result = fst }

View File

@@ -1,5 +1,5 @@
name: codeql/cpp-queries name: codeql/cpp-queries
version: 0.3.0 version: 0.3.1-dev
groups: groups:
- cpp - cpp
- queries - queries

View File

@@ -4,11 +4,7 @@
| c.c:6:5:6:6 | ls | array of 4 {int} | 1 | | c.c:6:5:6:6 | ls | array of 4 {int} | 1 |
| c.c:8:5:8:7 | iss | array of 4 {array of 2 {int}} | 1 | | c.c:8:5:8:7 | iss | array of 4 {array of 2 {int}} | 1 |
| c.c:12:11:12:11 | i | typedef {int} as "int_alias" | 1 | | c.c:12:11:12:11 | i | typedef {int} as "int_alias" | 1 |
| c.h:4:12:4:13 | ks | array of {int} | 1 |
| c.h:8:12:8:14 | iss | array of {array of 2 {int}} | 1 |
| c.h:10:12:10:12 | i | int | 1 |
| d.cpp:3:7:3:8 | xs | array of {int} | 1 | | d.cpp:3:7:3:8 | xs | array of {int} | 1 |
| d.h:3:14:3:15 | xs | array of 2 {int} | 1 |
| file://:0:0:0:0 | (unnamed parameter 0) | reference to {const {struct __va_list_tag}} | 1 | | file://:0:0:0:0 | (unnamed parameter 0) | reference to {const {struct __va_list_tag}} | 1 |
| file://:0:0:0:0 | (unnamed parameter 0) | rvalue reference to {struct __va_list_tag} | 1 | | file://:0:0:0:0 | (unnamed parameter 0) | rvalue reference to {struct __va_list_tag} | 1 |
| file://:0:0:0:0 | fp_offset | unsigned int | 1 | | file://:0:0:0:0 | fp_offset | unsigned int | 1 |

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-solorigate-all name: codeql/csharp-solorigate-all
version: 1.2.1 version: 1.2.2-dev
groups: groups:
- csharp - csharp
- solorigate - solorigate

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-solorigate-queries name: codeql/csharp-solorigate-queries
version: 1.2.1 version: 1.2.2-dev
groups: groups:
- csharp - csharp
- solorigate - solorigate

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-all name: codeql/csharp-all
version: 0.3.1 version: 0.3.2-dev
groups: csharp groups: csharp
dbscheme: semmlecode.csharp.dbscheme dbscheme: semmlecode.csharp.dbscheme
extractor: csharp extractor: csharp

View File

@@ -0,0 +1,44 @@
{
SymmetricKey aesKey = new SymmetricKey(kid: "symencryptionkey");
// BAD: Using the outdated client side encryption version V1_0
BlobEncryptionPolicy uploadPolicy = new BlobEncryptionPolicy(key: aesKey, keyResolver: null);
BlobRequestOptions uploadOptions = new BlobRequestOptions() { EncryptionPolicy = uploadPolicy };
MemoryStream stream = new MemoryStream(buffer);
blob.UploadFromStream(stream, length: size, accessCondition: null, options: uploadOptions);
}
var client = new BlobClient(myConnectionString, new SpecializedBlobClientOptions()
{
// BAD: Using an outdated SDK that does not support client side encryption version V2_0
ClientSideEncryption = new ClientSideEncryptionOptions()
{
KeyEncryptionKey = myKey,
KeyResolver = myKeyResolver,
KeyWrapAlgorihm = myKeyWrapAlgorithm
}
});
var client = new BlobClient(myConnectionString, new SpecializedBlobClientOptions()
{
// BAD: Using the outdated client side encryption version V1_0
ClientSideEncryption = new ClientSideEncryptionOptions(ClientSideEncryptionVersion.V1_0)
{
KeyEncryptionKey = myKey,
KeyResolver = myKeyResolver,
KeyWrapAlgorihm = myKeyWrapAlgorithm
}
});
var client = new BlobClient(myConnectionString, new SpecializedBlobClientOptions()
{
// GOOD: Using client side encryption version V2_0
ClientSideEncryption = new ClientSideEncryptionOptions(ClientSideEncryptionVersion.V2_0)
{
KeyEncryptionKey = myKey,
KeyResolver = myKeyResolver,
KeyWrapAlgorihm = myKeyWrapAlgorithm
}
});

View File

@@ -0,0 +1,29 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>Azure Storage .NET, Java, and Python SDKs support encryption on the client with a customer-managed key that is maintained in Azure Key Vault or another key store.</p>
<p>Current release versions of the Azure Storage SDKs use cipher block chaining (CBC mode) for client-side encryption (referred to as <code>v1</code>).</p>
</overview>
<recommendation>
<p>Consider switching to <code>v2</code> client-side encryption.</p>
</recommendation>
<example>
<sample src="UnsafeUsageOfClientSideEncryptionVersion.cs" />
</example>
<references>
<li>
<a href="http://aka.ms/azstorageclientencryptionblog">Azure Storage Client Encryption Blog.</a>
</li>
<li>
<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30187">CVE-2022-30187</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,81 @@
/**
* @name Unsafe usage of v1 version of Azure Storage client-side encryption (CVE-2022-30187).
* @description Unsafe usage of v1 version of Azure Storage client-side encryption, please refer to http://aka.ms/azstorageclientencryptionblog
* @kind problem
* @tags security
* cryptography
* external/cwe/cwe-327
* @id cs/azure-storage/unsafe-usage-of-client-side-encryption-version
* @problem.severity error
* @precision high
*/
import csharp
/**
* Holds if `oc` is creating an object of type `c` = `Azure.Storage.ClientSideEncryptionOptions`
* and `e` is the `version` argument to the constructor
*/
predicate isCreatingAzureClientSideEncryptionObject(ObjectCreation oc, Class c, Expr e) {
exists(Parameter p | p.hasName("version") |
c.hasQualifiedName("Azure.Storage.ClientSideEncryptionOptions") and
oc.getTarget() = c.getAConstructor() and
e = oc.getArgumentForParameter(p)
)
}
/**
* Holds if `oc` is an object creation of the outdated type `c` = `Microsoft.Azure.Storage.Blob.BlobEncryptionPolicy`
*/
predicate isCreatingOutdatedAzureClientSideEncryptionObject(ObjectCreation oc, Class c) {
c.hasQualifiedName("Microsoft.Azure.Storage.Blob.BlobEncryptionPolicy") and
oc.getTarget() = c.getAConstructor()
}
/**
* Holds if the Azure.Storage assembly for `c` is a version known to support
* version 2+ for client-side encryption
*/
predicate doesAzureStorageAssemblySupportSafeClientSideEncryption(Assembly asm) {
exists(int versionCompare |
versionCompare = asm.getVersion().compareTo("12.12.0.0") and
versionCompare >= 0
) and
asm.getName() = "Azure.Storage.Common"
}
/**
* Holds if the Azure.Storage assembly for `c` is a version known to support
* version 2+ for client-side encryption and if the argument for the constructor `version`
* is set to a secure value.
*/
predicate isObjectCreationArgumentSafeAndUsingSafeVersionOfAssembly(Expr versionExpr, Assembly asm) {
// Check if the Azure.Storage assembly version has the fix
doesAzureStorageAssemblySupportSafeClientSideEncryption(asm) and
// and that the version argument for the constructor is guaranteed to be Version2
isExprAnAccessToSafeClientSideEncryptionVersionValue(versionExpr)
}
/**
* Holds if the expression `e` is an access to a safe version of the enum `ClientSideEncryptionVersion`
* or an equivalent numeric value
*/
predicate isExprAnAccessToSafeClientSideEncryptionVersionValue(Expr e) {
exists(EnumConstant ec |
ec.hasQualifiedName("Azure.Storage.ClientSideEncryptionVersion.V2_0") and
ec.getAnAccess() = e
)
}
from Expr e, Class c, Assembly asm
where
asm = c.getLocation() and
(
exists(Expr e2 |
isCreatingAzureClientSideEncryptionObject(e, c, e2) and
not isObjectCreationArgumentSafeAndUsingSafeVersionOfAssembly(e2, asm)
)
or
isCreatingOutdatedAzureClientSideEncryptionObject(e, c)
)
select e, "Unsafe usage of v1 version of Azure Storage client-side encryption."

View File

@@ -1,5 +1,5 @@
name: codeql/csharp-queries name: codeql/csharp-queries
version: 0.3.0 version: 0.3.1-dev
groups: groups:
- csharp - csharp
- queries - queries

View File

@@ -2,7 +2,54 @@ function RegisterExtractorPack(id)
local extractor = GetPlatformToolsDirectory() .. local extractor = GetPlatformToolsDirectory() ..
'Semmle.Extraction.CSharp.Driver' 'Semmle.Extraction.CSharp.Driver'
if OperatingSystem == 'windows' then extractor = extractor .. '.exe' end if OperatingSystem == 'windows' then extractor = extractor .. '.exe' end
function DotnetMatcherBuild(compilerName, compilerPath, compilerArguments,
_languageId)
if compilerName ~= 'dotnet' and compilerName ~= 'dotnet.exe' then
return nil
end
-- The dotnet CLI has the following usage instructions:
-- dotnet [sdk-options] [command] [command-options] [arguments]
-- we are interested in dotnet build, which has the following usage instructions:
-- dotnet [options] build [<PROJECT | SOLUTION>...]
-- For now, parse the command line as follows:
-- Everything that starts with `-` (or `/`) will be ignored.
-- The first non-option argument is treated as the command.
-- if that's `build`, we append `/p:UseSharedCompilation=false` to the command line,
-- otherwise we do nothing.
local match = false
local argv = compilerArguments.argv
if OperatingSystem == 'windows' then
-- let's hope that this split matches the escaping rules `dotnet` applies to command line arguments
-- or, at least, that it is close enough
argv =
NativeArgumentsToArgv(compilerArguments.nativeArgumentPointer)
end
for i, arg in ipairs(argv) do
-- dotnet options start with either - or / (both are legal)
local firstCharacter = string.sub(arg, 1, 1)
if not (firstCharacter == '-') and not (firstCharacter == '/') then
Log(1, 'Dotnet subcommand detected: %s', arg)
if arg == 'build' then match = true end
break
end
end
if match then
return {
order = ORDER_REPLACE,
invocation = BuildExtractorInvocation(id, compilerPath,
compilerPath,
compilerArguments, nil, {
'/p:UseSharedCompilation=false'
})
}
end
return nil
end
local windowsMatchers = { local windowsMatchers = {
DotnetMatcherBuild,
CreatePatternMatcher({'^dotnet%.exe$'}, MatchCompilerName, extractor, { CreatePatternMatcher({'^dotnet%.exe$'}, MatchCompilerName, extractor, {
prepend = {'--dotnetexec', '--cil'}, prepend = {'--dotnetexec', '--cil'},
order = ORDER_BEFORE order = ORDER_BEFORE
@@ -10,22 +57,21 @@ function RegisterExtractorPack(id)
CreatePatternMatcher({'^csc.*%.exe$'}, MatchCompilerName, extractor, { CreatePatternMatcher({'^csc.*%.exe$'}, MatchCompilerName, extractor, {
prepend = {'--compiler', '"${compiler}"', '--cil'}, prepend = {'--compiler', '"${compiler}"', '--cil'},
order = ORDER_BEFORE order = ORDER_BEFORE
}), }),
CreatePatternMatcher({'^fakes.*%.exe$', 'moles.*%.exe'}, CreatePatternMatcher({'^fakes.*%.exe$', 'moles.*%.exe'},
MatchCompilerName, nil, {trace = false}) MatchCompilerName, nil, {trace = false})
} }
local posixMatchers = { local posixMatchers = {
CreatePatternMatcher({'^mcs%.exe$', '^csc%.exe$'}, MatchCompilerName, DotnetMatcherBuild,
extractor, {
prepend = {'--compiler', '"${compiler}"', '--cil'},
order = ORDER_BEFORE
}),
CreatePatternMatcher({'^mono', '^dotnet$'}, MatchCompilerName, CreatePatternMatcher({'^mono', '^dotnet$'}, MatchCompilerName,
extractor, { extractor, {
prepend = {'--dotnetexec', '--cil'}, prepend = {'--dotnetexec', '--cil'},
order = ORDER_BEFORE order = ORDER_BEFORE
}),
CreatePatternMatcher({'^mcs%.exe$', '^csc%.exe$'}, MatchCompilerName,
extractor, {
prepend = {'--compiler', '"${compiler}"', '--cil'},
order = ORDER_BEFORE
}), function(compilerName, compilerPath, compilerArguments, _languageId) }), function(compilerName, compilerPath, compilerArguments, _languageId)
if MatchCompilerName('^msbuild$', compilerName, compilerPath, if MatchCompilerName('^msbuild$', compilerName, compilerPath,
compilerArguments) or compilerArguments) or
@@ -49,7 +95,6 @@ function RegisterExtractorPack(id)
else else
return posixMatchers return posixMatchers
end end
end end
-- Return a list of minimum supported versions of the configuration file format -- Return a list of minimum supported versions of the configuration file format

View File

@@ -11,14 +11,17 @@ CodeQL.
Languages and compilers Languages and compilers
####################### #######################
CodeQL supports the following languages and compilers. The current versions of the CodeQL CLI (`changelog <https://github.com/github/codeql-cli-binaries/blob/main/CHANGELOG.md>`__, `releases <https://github.com/github/codeql-cli-binaries/releases>`__),
CodeQL library packs (`source <https://github.com/github/codeql/tree/codeql-cli/latest>`__),
and CodeQL bundle (`releases <https://github.com/github/codeql-action/releases>`__)
support the following languages and compilers.
.. include:: ../support/reusables/versions-compilers.rst .. include:: ../support/reusables/versions-compilers.rst
Frameworks and libraries Frameworks and libraries
######################## ########################
The libraries and queries in the current version of CodeQL have been explicitly checked against the libraries and frameworks listed below. The current versions of the CodeQL library and query packs (`source <https://github.com/github/codeql/tree/codeql-cli/latest>`__) have been explicitly checked against the libraries and frameworks listed below.
.. pull-quote:: .. pull-quote::

View File

@@ -3,7 +3,9 @@ CodeQL query help for C and C++
.. include:: ../reusables/query-help-overview.rst .. include:: ../reusables/query-help-overview.rst
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/cpp/ql/examples>`__. These queries are published in the CodeQL query pack ``codeql/cpp-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/src>`__).
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/examples>`__.
.. include:: toc-cpp.rst .. include:: toc-cpp.rst

View File

@@ -3,6 +3,8 @@ CodeQL query help for C#
.. include:: ../reusables/query-help-overview.rst .. include:: ../reusables/query-help-overview.rst
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/csharp/ql/examples>`__. These queries are published in the CodeQL query pack ``codeql/csharp-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/src>`__).
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/examples>`__.
.. include:: toc-csharp.rst .. include:: toc-csharp.rst

View File

@@ -3,6 +3,8 @@ CodeQL query help for Go
.. include:: ../reusables/query-help-overview.rst .. include:: ../reusables/query-help-overview.rst
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/go/ql/examples>`__. These queries are published in the CodeQL query pack ``codeql/go-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/src>`__).
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/examples>`__.
.. include:: toc-go.rst .. include:: toc-go.rst

View File

@@ -3,6 +3,8 @@ CodeQL query help for Java
.. include:: ../reusables/query-help-overview.rst .. include:: ../reusables/query-help-overview.rst
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/java/ql/examples>`__. These queries are published in the CodeQL query pack ``codeql/java-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/src>`__).
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/examples>`__.
.. include:: toc-java.rst .. include:: toc-java.rst

View File

@@ -3,6 +3,8 @@ CodeQL query help for JavaScript
.. include:: ../reusables/query-help-overview.rst .. include:: ../reusables/query-help-overview.rst
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/javascript/ql/examples>`__. These queries are published in the CodeQL query pack ``codeql/javascript-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/src>`__).
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/examples>`__.
.. include:: toc-javascript.rst .. include:: toc-javascript.rst

View File

@@ -3,6 +3,8 @@ CodeQL query help for Python
.. include:: ../reusables/query-help-overview.rst .. include:: ../reusables/query-help-overview.rst
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/python/ql/examples>`__. These queries are published in the CodeQL query pack ``codeql/python-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/src>`__).
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/examples>`__.
.. include:: toc-python.rst .. include:: toc-python.rst

View File

@@ -3,6 +3,8 @@ CodeQL query help for Ruby
.. include:: ../reusables/query-help-overview.rst .. include:: ../reusables/query-help-overview.rst
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/main/ruby/ql/examples>`__. These queries are published in the CodeQL query pack ``codeql/ruby-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/ruby/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/ruby/ql/src>`__).
For shorter queries that you can use as building blocks when writing your own queries, see the `example queries in the CodeQL repository <https://github.com/github/codeql/tree/codeql-cli/latest/ruby/ql/examples>`__.
.. include:: toc-ruby.rst .. include:: toc-ruby.rst

View File

@@ -1,6 +1,10 @@
C and C++ built-in support C and C++ built-in support
================================ ================================
Provided by the current versions of the
CodeQL query pack ``codeql/cpp-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/src>`__)
and the CodeQL library pack ``codeql/cpp-all`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/lib/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/cpp/ql/lib>`__).
.. csv-table:: .. csv-table::
:header-rows: 1 :header-rows: 1
:class: fullWidthTable :class: fullWidthTable
@@ -14,6 +18,10 @@ C and C++ built-in support
C# built-in support C# built-in support
================================ ================================
Provided by the current versions of the
CodeQL query pack ``codeql/csharp-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/src>`__)
and the CodeQL library pack ``codeql/csharp-all`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/lib/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/csharp/ql/lib>`__).
.. csv-table:: .. csv-table::
:header-rows: 1 :header-rows: 1
:class: fullWidthTable :class: fullWidthTable
@@ -33,6 +41,10 @@ C# built-in support
Go built-in support Go built-in support
================================ ================================
Provided by the current versions of the
CodeQL query pack ``codeql/go-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/src>`__)
and the CodeQL library pack ``codeql/go-all`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/lib/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/go/ql/lib>`__).
.. csv-table:: .. csv-table::
:header-rows: 1 :header-rows: 1
:class: fullWidthTable :class: fullWidthTable
@@ -84,6 +96,10 @@ Go built-in support
Java built-in support Java built-in support
================================== ==================================
Provided by the current versions of the
CodeQL query pack ``codeql/java-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/src>`__)
and the CodeQL library pack ``codeql/java-all`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/lib/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/java/ql/lib>`__).
.. csv-table:: .. csv-table::
:header-rows: 1 :header-rows: 1
:class: fullWidthTable :class: fullWidthTable
@@ -113,6 +129,10 @@ Java built-in support
JavaScript and TypeScript built-in support JavaScript and TypeScript built-in support
======================================================= =======================================================
Provided by the current versions of the
CodeQL query pack ``codeql/javascript-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/src>`__)
and the CodeQL library pack ``codeql/javascript-all`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/lib/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/javascript/ql/lib>`__).
.. csv-table:: .. csv-table::
:header-rows: 1 :header-rows: 1
:class: fullWidthTable :class: fullWidthTable
@@ -156,6 +176,10 @@ JavaScript and TypeScript built-in support
Python built-in support Python built-in support
==================================== ====================================
Provided by the current versions of the
CodeQL query pack ``codeql/python-queries`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/src/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/src>`__)
and the CodeQL library pack ``codeql/python-all`` (`changelog <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/lib/CHANGELOG.md>`__, `source <https://github.com/github/codeql/tree/codeql-cli/latest/python/ql/lib>`__).
.. csv-table:: .. csv-table::
:header-rows: 1 :header-rows: 1
:class: fullWidthTable :class: fullWidthTable

View File

@@ -1,5 +1,5 @@
name: codeql/go-all name: codeql/go-all
version: 0.2.1 version: 0.2.2-dev
groups: go groups: go
dbscheme: go.dbscheme dbscheme: go.dbscheme
extractor: go extractor: go

View File

@@ -1,5 +1,5 @@
name: codeql/go-queries name: codeql/go-queries
version: 0.2.1 version: 0.2.2-dev
groups: groups:
- go - go
- queries - queries

View File

@@ -36,7 +36,7 @@ java.lang,13,,58,,,,,,,,,,,8,,,,,4,,,1,,,,,,,,,,,,,,,46,12
java.net,10,3,7,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,,,,,3,7, java.net,10,3,7,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,,,,,3,7,
java.nio,15,,6,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,6, java.nio,15,,6,,13,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,6,
java.sql,11,,,,,,,,,4,,,,,,,,,,,,,,,,7,,,,,,,,,,,, java.sql,11,,,,,,,,,4,,,,,,,,,,,,,,,,7,,,,,,,,,,,,
java.util,44,,438,,,,,,,,,,,34,,,,,,5,2,,1,2,,,,,,,,,,,,,24,414 java.util,44,,441,,,,,,,,,,,34,,,,,,5,2,,1,2,,,,,,,,,,,,,24,417
javax.faces.context,2,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,7,, javax.faces.context,2,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,7,,
javax.jms,,9,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,57, javax.jms,,9,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,57,
javax.json,,,123,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,100,23 javax.json,,,123,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,100,23
1 package sink source summary sink:bean-validation sink:create-file sink:groovy sink:header-splitting sink:information-leak sink:intent-start sink:jdbc-url sink:jexl sink:jndi-injection sink:ldap sink:logging sink:mvel sink:ognl-injection sink:open-url sink:pending-intent-sent sink:regex-use[-1] sink:regex-use[0] sink:regex-use[] sink:regex-use[f-1] sink:regex-use[f1] sink:regex-use[f] sink:set-hostname-verifier sink:sql sink:url-open-stream sink:url-redirect sink:write-file sink:xpath sink:xslt sink:xss source:android-external-storage-dir source:android-widget source:contentprovider source:remote summary:taint summary:value
36 java.net 10 3 7 10 3 7
37 java.nio 15 6 13 2 6
38 java.sql 11 4 7
39 java.util 44 438 441 34 5 2 1 2 24 414 417
40 javax.faces.context 2 7 2 7
41 javax.jms 9 57 9 57
42 javax.json 123 100 23

View File

@@ -15,9 +15,9 @@ Java framework & library support
`Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,136,28,,,3,,,,25 `Apache HttpComponents <https://hc.apache.org/>`_,"``org.apache.hc.core5.*``, ``org.apache.http``",5,136,28,,,3,,,,25
`Google Guava <https://guava.dev/>`_,``com.google.common.*``,,728,39,,6,,,,, `Google Guava <https://guava.dev/>`_,``com.google.common.*``,,728,39,,6,,,,,
`JSON-java <https://github.com/stleary/JSON-java>`_,``org.json``,,236,,,,,,,, `JSON-java <https://github.com/stleary/JSON-java>`_,``org.json``,,236,,,,,,,,
Java Standard Library,``java.*``,3,549,130,28,,,7,,,10 Java Standard Library,``java.*``,3,552,130,28,,,7,,,10
Java extensions,"``javax.*``, ``jakarta.*``",63,609,32,,,4,,1,1,2 Java extensions,"``javax.*``, ``jakarta.*``",63,609,32,,,4,,1,1,2
`Spring <https://spring.io/>`_,``org.springframework.*``,29,476,101,,,,19,14,,29 `Spring <https://spring.io/>`_,``org.springframework.*``,29,476,101,,,,19,14,,29
Others,"``androidx.slice``, ``cn.hutool.core.codec``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.opensymphony.xwork2.ognl``, ``com.rabbitmq.client``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``groovy.lang``, ``groovy.util``, ``jodd.json``, ``kotlin.jvm.internal``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.apache.commons.codec``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.logging``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.ibatis.jdbc``, ``org.apache.log4j``, ``org.apache.logging.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.hibernate``, ``org.jboss.logging``, ``org.jdbi.v3.core``, ``org.jooq``, ``org.mvel2``, ``org.scijava.log``, ``org.slf4j``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``",65,395,932,,,,14,18,,3 Others,"``androidx.slice``, ``cn.hutool.core.codec``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.fasterxml.jackson.core``, ``com.fasterxml.jackson.databind``, ``com.opensymphony.xwork2.ognl``, ``com.rabbitmq.client``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``groovy.lang``, ``groovy.util``, ``jodd.json``, ``kotlin.jvm.internal``, ``net.sf.saxon.s9api``, ``ognl``, ``okhttp3``, ``org.apache.commons.codec``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.logging``, ``org.apache.commons.ognl``, ``org.apache.directory.ldap.client.api``, ``org.apache.ibatis.jdbc``, ``org.apache.log4j``, ``org.apache.logging.log4j``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.codehaus.groovy.control``, ``org.dom4j``, ``org.hibernate``, ``org.jboss.logging``, ``org.jdbi.v3.core``, ``org.jooq``, ``org.mvel2``, ``org.scijava.log``, ``org.slf4j``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``retrofit2``",65,395,932,,,,14,18,,3
Totals,,217,6410,1474,117,6,10,107,33,1,84 Totals,,217,6413,1474,117,6,10,107,33,1,84

View File

@@ -1,5 +1,5 @@
name: codeql/java-all name: codeql/java-all
version: 0.3.1 version: 0.3.2-dev
groups: java groups: java
dbscheme: config/semmlecode.dbscheme dbscheme: config/semmlecode.dbscheme
extractor: java extractor: java

View File

@@ -0,0 +1,46 @@
// BAD: Using an outdated SDK that does not support client side encryption version V2_0
new EncryptedBlobClientBuilder()
.blobClient(blobClient)
.key(resolver.buildAsyncKeyEncryptionKey(keyid).block(), keyWrapAlgorithm)
.buildEncryptedBlobClient()
.uploadWithResponse(new BlobParallelUploadOptions(data)
.setMetadata(metadata)
.setHeaders(headers)
.setTags(tags)
.setTier(tier)
.setRequestConditions(requestConditions)
.setComputeMd5(computeMd5)
.setParallelTransferOptions(parallelTransferOptions),
timeout, context);
// BAD: Using the deprecatedd client side encryption version V1_0
new EncryptedBlobClientBuilder(EncryptionVersion.V1)
.blobClient(blobClient)
.key(resolver.buildAsyncKeyEncryptionKey(keyid).block(), keyWrapAlgorithm)
.buildEncryptedBlobClient()
.uploadWithResponse(new BlobParallelUploadOptions(data)
.setMetadata(metadata)
.setHeaders(headers)
.setTags(tags)
.setTier(tier)
.setRequestConditions(requestConditions)
.setComputeMd5(computeMd5)
.setParallelTransferOptions(parallelTransferOptions),
timeout, context);
// GOOD: Using client side encryption version V2_0
new EncryptedBlobClientBuilder(EncryptionVersion.V2)
.blobClient(blobClient)
.key(resolver.buildAsyncKeyEncryptionKey(keyid).block(), keyWrapAlgorithm)
.buildEncryptedBlobClient()
.uploadWithResponse(new BlobParallelUploadOptions(data)
.setMetadata(metadata)
.setHeaders(headers)
.setTags(tags)
.setTier(tier)
.setRequestConditions(requestConditions)
.setComputeMd5(computeMd5)
.setParallelTransferOptions(parallelTransferOptions),
timeout, context);

View File

@@ -0,0 +1,29 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>Azure Storage .NET, Java, and Python SDKs support encryption on the client with a customer-managed key that is maintained in Azure Key Vault or another key store.</p>
<p>The Azure Storage SDK version 12.18.0 or later supports version <code>V2</code> for client-side encryption. All previous versions of Azure Storage SDK only support client-side encryption <code>V1</code> which is unsafe.</p>
</overview>
<recommendation>
<p>Consider switching to <code>V2</code> client-side encryption.</p>
</recommendation>
<example>
<sample src="UnsafeUsageOfClientSideEncryptionVersion.java" />
</example>
<references>
<li>
<a href="http://aka.ms/azstorageclientencryptionblog">Azure Storage Client Encryption Blog.</a>
</li>
<li>
<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30187">CVE-2022-30187</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,92 @@
/**
* @name Unsafe usage of v1 version of Azure Storage client-side encryption (CVE-2022-30187).
* @description Unsafe usage of v1 version of Azure Storage client-side encryption, please refer to http://aka.ms/azstorageclientencryptionblog
* @kind problem
* @tags security
* cryptography
* external/cwe/cwe-327
* @id java/azure-storage/unsafe-client-side-encryption-in-use
* @problem.severity error
* @precision high
*/
import java
import semmle.code.java.dataflow.DataFlow
/**
* Holds if `call` is an object creation for a class `EncryptedBlobClientBuilder`
* that takes no arguments, which means that it is using V1 encryption.
*/
predicate isCreatingOutdatedAzureClientSideEncryptionObject(Call call, Class c) {
exists(string package, string type, Constructor constructor |
c.hasQualifiedName(package, type) and
c.getAConstructor() = constructor and
call.getCallee() = constructor and
(
type = "EncryptedBlobClientBuilder" and
package = "com.azure.storage.blob.specialized.cryptography" and
constructor.hasNoParameters()
or
type = "BlobEncryptionPolicy" and package = "com.microsoft.azure.storage.blob"
)
)
}
/**
* Holds if `call` is an object creation for a class `EncryptedBlobClientBuilder`
* that takes `versionArg` as the argument specifying the encryption version.
*/
predicate isCreatingAzureClientSideEncryptionObjectNewVersion(Call call, Class c, Expr versionArg) {
exists(string package, string type, Constructor constructor |
c.hasQualifiedName(package, type) and
c.getAConstructor() = constructor and
call.getCallee() = constructor and
type = "EncryptedBlobClientBuilder" and
package = "com.azure.storage.blob.specialized.cryptography" and
versionArg = call.getArgument(0)
)
}
/**
* A dataflow config that tracks `EncryptedBlobClientBuilder.version` argument initialization.
*/
private class EncryptedBlobClientBuilderSafeEncryptionVersionConfig extends DataFlow::Configuration {
EncryptedBlobClientBuilderSafeEncryptionVersionConfig() {
this = "EncryptedBlobClientBuilderSafeEncryptionVersionConfig"
}
override predicate isSource(DataFlow::Node source) {
exists(FieldRead fr, Field f | fr = source.asExpr() |
f.getAnAccess() = fr and
f.hasQualifiedName("com.azure.storage.blob.specialized.cryptography", "EncryptionVersion",
"V2")
)
}
override predicate isSink(DataFlow::Node sink) {
isCreatingAzureClientSideEncryptionObjectNewVersion(_, _, sink.asExpr())
}
}
/**
* Holds if `call` is an object creation for a class `EncryptedBlobClientBuilder`
* that takes `versionArg` as the argument specifying the encryption version, and that version is safe.
*/
predicate isCreatingSafeAzureClientSideEncryptionObject(Call call, Class c, Expr versionArg) {
isCreatingAzureClientSideEncryptionObjectNewVersion(call, c, versionArg) and
exists(EncryptedBlobClientBuilderSafeEncryptionVersionConfig config, DataFlow::Node sink |
sink.asExpr() = versionArg
|
config.hasFlow(_, sink)
)
}
from Expr e, Class c
where
exists(Expr argVersion |
isCreatingAzureClientSideEncryptionObjectNewVersion(e, c, argVersion) and
not isCreatingSafeAzureClientSideEncryptionObject(e, c, argVersion)
)
or
isCreatingOutdatedAzureClientSideEncryptionObject(e, c)
select e, "Unsafe usage of v1 version of Azure Storage client-side encryption."

View File

@@ -1,5 +1,5 @@
name: codeql/java-queries name: codeql/java-queries
version: 0.3.0 version: 0.3.1-dev
groups: groups:
- java - java
- queries - queries

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-all name: codeql/javascript-all
version: 0.2.1 version: 0.2.2-dev
groups: javascript groups: javascript
dbscheme: semmlecode.javascript.dbscheme dbscheme: semmlecode.javascript.dbscheme
extractor: javascript extractor: javascript

View File

@@ -28,6 +28,9 @@ module Actions {
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */ /** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
YAMLMapping getJobs() { result = this.lookup("jobs") } YAMLMapping getJobs() { result = this.lookup("jobs") }
/** Gets the name of the workflow. */
string getName() { result = this.lookup("name").(YAMLString).getValue() }
/** Gets the name of the workflow file. */ /** Gets the name of the workflow file. */
string getFileName() { result = this.getFile().getBaseName() } string getFileName() { result = this.getFile().getBaseName() }
@@ -129,6 +132,9 @@ module Actions {
/** Gets the value of the `if` field in this step, if any. */ /** Gets the value of the `if` field in this step, if any. */
StepIf getIf() { result.getStep() = this } StepIf getIf() { result.getStep() = this }
/** Gets the ID of this step, if any. */
string getId() { result = this.lookup("id").(YAMLString).getValue() }
} }
/** /**

View File

@@ -148,6 +148,18 @@ module Routing {
this instanceof MkRouter this instanceof MkRouter
} }
/**
* Like `mayResumeDispatch` but without the assumption that functions with an unknown
* implementation invoke their continuation.
*/
predicate definitelyResumesDispatch() {
this.getLastChild().definitelyResumesDispatch()
or
exists(this.(RouteHandler).getAContinuationInvocation())
or
this instanceof MkRouter
}
/** Gets the parent of this node, provided that this node may invoke its continuation. */ /** Gets the parent of this node, provided that this node may invoke its continuation. */
private Node getContinuationParent() { private Node getContinuationParent() {
result = this.getParent() and result = this.getParent() and

View File

@@ -312,7 +312,7 @@ class TypeBackTracker extends TTypeBackTracker {
* result = < some API call >.getArgument(< n >) * result = < some API call >.getArgument(< n >)
* or * or
* exists (DataFlow::TypeBackTracker t2 | * exists (DataFlow::TypeBackTracker t2 |
* t = t2.smallstep(result, myType(t2)) * t2 = t.smallstep(result, myType(t2))
* ) * )
* } * }
* *

View File

@@ -33,6 +33,11 @@ module Express {
or or
// `app = [new] express.Router()` // `app = [new] express.Router()`
result = DataFlow::moduleMember("express", "Router").getAnInvocation() result = DataFlow::moduleMember("express", "Router").getAnInvocation()
or
exists(DataFlow::SourceNode app |
app.hasUnderlyingType("probot/lib/application", "Application") and
result = app.getAMethodCall("route")
)
} }
/** /**
@@ -1043,4 +1048,22 @@ module Express {
override DataFlow::SourceNode getOutput() { result = this.getCallback(2).getParameter(1) } override DataFlow::SourceNode getOutput() { result = this.getCallback(2).getParameter(1) }
} }
private class ResumeDispatchRefinement extends Routing::RouteHandler {
ResumeDispatchRefinement() { this.getFunction() instanceof RouteHandler }
override predicate mayResumeDispatch() { this.getAParameter().getName() = "next" }
override predicate definitelyResumesDispatch() { this.getAParameter().getName() = "next" }
}
private class ExpressStaticResumeDispatchRefinement extends Routing::Node {
ExpressStaticResumeDispatchRefinement() {
this = Routing::getNode(DataFlow::moduleMember("express", "static").getACall())
}
override predicate mayResumeDispatch() { none() }
override predicate definitelyResumesDispatch() { none() }
}
} }

View File

@@ -80,7 +80,7 @@ module UnsafeHtmlConstruction {
t.start() and t.start() and
result = sink result = sink
or or
exists(DataFlow::TypeBackTracker t2 | t = t2.smallstep(result, isUsedInXssSink(t2, sink))) exists(DataFlow::TypeBackTracker t2 | t2 = t.smallstep(result, isUsedInXssSink(t2, sink)))
or or
exists(DataFlow::TypeBackTracker t2 | exists(DataFlow::TypeBackTracker t2 |
t.continue() = t2 and t.continue() = t2 and

View File

@@ -144,6 +144,9 @@ predicate whitelisted(UnusedLocal v) {
// exclude variables mentioned in JSDoc comments in externs // exclude variables mentioned in JSDoc comments in externs
mentionedInJSDocComment(v) mentionedInJSDocComment(v)
or or
// the attributes in .vue files are not extracted, so we can get false positives in those.
v.getADeclaration().getFile().getExtension() = "vue"
or
// exclude variables used to filter out unwanted properties // exclude variables used to filter out unwanted properties
isPropertyFilter(v) isPropertyFilter(v)
or or

View File

@@ -50,7 +50,7 @@ private DataFlow::Node endsInCodeInjectionSink(DataFlow::TypeBackTracker t) {
not result instanceof StringOps::ConcatenationRoot // the heuristic CodeInjection sink looks for string-concats, we are not interrested in those here. not result instanceof StringOps::ConcatenationRoot // the heuristic CodeInjection sink looks for string-concats, we are not interrested in those here.
) )
or or
exists(DataFlow::TypeBackTracker t2 | t = t2.smallstep(result, endsInCodeInjectionSink(t2))) exists(DataFlow::TypeBackTracker t2 | t2 = t.smallstep(result, endsInCodeInjectionSink(t2)))
} }
/** /**

View File

@@ -0,0 +1,44 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Using a case-sensitive regular expression path in a middleware route enables an attacker to bypass that middleware
when accessing an endpoint with a case-insensitive path.
Paths specified using a string are case-insensitive, whereas regular expressions are case-sensitive by default.
</p>
</overview>
<recommendation>
<p>
When using a regular expression as a middleware path, make sure the regular expression is
case-insensitive by adding the <code>i</code> flag.
</p>
</recommendation>
<example>
<p>
The following example restricts access to paths in the <code>/admin</code> path to users logged in as
administrators:
</p>
<sample src="examples/CaseSensitiveMiddlewarePath.js" />
<p>
A path such as <code>/admin/users/45</code> can only be accessed by an administrator. However, the path
<code>/ADMIN/USERS/45</code> can be accessed by anyone because the upper-case path doesn't match the case-sensitive regular expression, whereas
Express considers it to match the path string <code>/admin/users</code>.
</p>
<p>
The issue can be fixed by adding the <code>i</code> flag to the regular expression:
</p>
<sample src="examples/CaseSensitiveMiddlewarePathGood.js" />
</example>
<references>
<li>
MDN
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags">Regular Expression Flags</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,121 @@
/**
* @name Case-sensitive middleware path
* @description Middleware with case-sensitive paths do not protect endpoints with case-insensitive paths.
* @kind problem
* @problem.severity warning
* @security-severity 7.3
* @precision high
* @id js/case-sensitive-middleware-path
* @tags security
* external/cwe/cwe-178
*/
import javascript
/**
* Converts `s` to upper case, or to lower-case if it was already upper case.
*/
bindingset[s]
string toOtherCase(string s) {
if s.regexpMatch(".*[a-z].*") then result = s.toUpperCase() else result = s.toLowerCase()
}
RegExpCharacterClass getEnclosingClass(RegExpTerm term) {
term = result.getAChild()
or
term = result.getAChild().(RegExpRange).getAChild()
}
/**
* Holds if `term` seems to distinguish between upper and lower case letters, assuming the `i` flag is not present.
*/
pragma[inline]
predicate isLikelyCaseSensitiveRegExp(RegExpTerm term) {
exists(RegExpConstant const |
const = term.getAChild*() and
const.getValue().regexpMatch(".*[a-zA-Z].*") and
not getEnclosingClass(const).getAChild().(RegExpConstant).getValue() =
toOtherCase(const.getValue()) and
not const.getParent*() instanceof RegExpNegativeLookahead and
not const.getParent*() instanceof RegExpNegativeLookbehind
)
}
/**
* Gets a string matched by `term`, or part of such a string.
*/
string getExampleString(RegExpTerm term) {
result = term.getAMatchedString()
or
// getAMatchedString does not recurse into sequences. Perform one step manually.
exists(RegExpSequence seq | seq = term |
result =
strictconcat(RegExpTerm child, int i, string text |
child = seq.getChild(i) and
(
text = child.getAMatchedString()
or
not exists(child.getAMatchedString()) and
text = ""
)
|
text order by i
)
)
}
string getCaseSensitiveBypassExample(RegExpTerm term) {
exists(string example |
example = getExampleString(term) and
result = toOtherCase(example) and
result != example // getting an example string is approximate; ensure we got a proper case-change example
)
}
/**
* Holds if `setup` has a path-argument `arg` referring to the given case-sensitive `regexp`.
*/
predicate isCaseSensitiveMiddleware(
Routing::RouteSetup setup, DataFlow::RegExpCreationNode regexp, DataFlow::Node arg
) {
exists(DataFlow::MethodCallNode call |
setup = Routing::getRouteSetupNode(call) and
(
setup.definitelyResumesDispatch()
or
// If applied to all HTTP methods, be a bit more lenient in detecting middleware
setup.mayResumeDispatch() and
not exists(setup.getOwnHttpMethod())
) and
arg = call.getArgument(0) and
regexp.getAReference().flowsTo(arg) and
isLikelyCaseSensitiveRegExp(regexp.getRoot()) and
exists(string flags |
flags = regexp.getFlags() and
not RegExp::isIgnoreCase(flags)
)
)
}
predicate isGuardedCaseInsensitiveEndpoint(
Routing::RouteSetup endpoint, Routing::RouteSetup middleware
) {
isCaseSensitiveMiddleware(middleware, _, _) and
exists(DataFlow::MethodCallNode call |
endpoint = Routing::getRouteSetupNode(call) and
endpoint.isGuardedByNode(middleware) and
call.getArgument(0).mayHaveStringValue(_)
)
}
from
DataFlow::RegExpCreationNode regexp, Routing::RouteSetup middleware, Routing::RouteSetup endpoint,
DataFlow::Node arg, string example
where
isCaseSensitiveMiddleware(middleware, regexp, arg) and
example = getCaseSensitiveBypassExample(regexp.getRoot()) and
isGuardedCaseInsensitiveEndpoint(endpoint, middleware) and
exists(endpoint.getRelativePath().toLowerCase().indexOf(example.toLowerCase()))
select arg,
"This route uses a case-sensitive path $@, but is guarding a case-insensitive path $@. A path such as '"
+ example + "' will bypass the middleware.", regexp, "pattern", endpoint, "here"

View File

@@ -0,0 +1,13 @@
const app = require('express')();
app.use(/\/admin\/.*/, (req, res, next) => {
if (!req.user.isAdmin) {
res.status(401).send('Unauthorized');
} else {
next();
}
});
app.get('/admin/users/:id', (req, res) => {
res.send(app.database.users[req.params.id]);
});

View File

@@ -0,0 +1,13 @@
const app = require('express')();
app.use(/\/admin\/.*/i, (req, res, next) => {
if (!req.user.isAdmin) {
res.status(401).send('Unauthorized');
} else {
next();
}
});
app.get('/admin/users/:id', (req, res) => {
res.send(app.database.users[req.params.id]);
});

View File

@@ -0,0 +1,6 @@
---
category: newQuery
---
- A new query "Case-sensitive middleware path" (`js/case-sensitive-middleware-path`) has been added.
It highlights middleware routes that can be bypassed due to having a case-sensitive regular expression path.

View File

@@ -1,5 +1,5 @@
name: codeql/javascript-queries name: codeql/javascript-queries
version: 0.3.0 version: 0.3.1-dev
groups: groups:
- javascript - javascript
- queries - queries

View File

@@ -50,6 +50,12 @@ nodes
| main.js:79:34:79:36 | val | | main.js:79:34:79:36 | val |
| main.js:81:35:81:37 | val | | main.js:81:35:81:37 | val |
| main.js:81:35:81:37 | val | | main.js:81:35:81:37 | val |
| main.js:89:21:89:21 | x |
| main.js:90:23:90:23 | x |
| main.js:90:23:90:23 | x |
| main.js:93:43:93:43 | x |
| main.js:93:43:93:43 | x |
| main.js:94:31:94:31 | x |
| typed.ts:1:39:1:39 | s | | typed.ts:1:39:1:39 | s |
| typed.ts:1:39:1:39 | s | | typed.ts:1:39:1:39 | s |
| typed.ts:2:29:2:29 | s | | typed.ts:2:29:2:29 | s |
@@ -115,6 +121,11 @@ edges
| main.js:79:34:79:36 | val | main.js:81:35:81:37 | val | | main.js:79:34:79:36 | val | main.js:81:35:81:37 | val |
| main.js:79:34:79:36 | val | main.js:81:35:81:37 | val | | main.js:79:34:79:36 | val | main.js:81:35:81:37 | val |
| main.js:79:34:79:36 | val | main.js:81:35:81:37 | val | | main.js:79:34:79:36 | val | main.js:81:35:81:37 | val |
| main.js:89:21:89:21 | x | main.js:90:23:90:23 | x |
| main.js:89:21:89:21 | x | main.js:90:23:90:23 | x |
| main.js:93:43:93:43 | x | main.js:94:31:94:31 | x |
| main.js:93:43:93:43 | x | main.js:94:31:94:31 | x |
| main.js:94:31:94:31 | x | main.js:89:21:89:21 | x |
| typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s | | typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s |
| typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s | | typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s |
| typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s | | typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s |
@@ -141,5 +152,6 @@ edges
| main.js:62:19:62:31 | settings.name | main.js:56:28:56:34 | options | main.js:62:19:62:31 | settings.name | $@ based on $@ might later cause $@. | main.js:62:19:62:31 | settings.name | HTML construction | main.js:56:28:56:34 | options | library input | main.js:62:11:62:40 | "<b>" + ... "</b>" | cross-site scripting | | main.js:62:19:62:31 | settings.name | main.js:56:28:56:34 | options | main.js:62:19:62:31 | settings.name | $@ based on $@ might later cause $@. | main.js:62:19:62:31 | settings.name | HTML construction | main.js:56:28:56:34 | options | library input | main.js:62:11:62:40 | "<b>" + ... "</b>" | cross-site scripting |
| main.js:67:63:67:69 | attrVal | main.js:66:35:66:41 | attrVal | main.js:67:63:67:69 | attrVal | $@ based on $@ might later cause $@. | main.js:67:63:67:69 | attrVal | HTML construction | main.js:66:35:66:41 | attrVal | library input | main.js:67:47:67:78 | "<img a ... "\\"/>" | cross-site scripting | | main.js:67:63:67:69 | attrVal | main.js:66:35:66:41 | attrVal | main.js:67:63:67:69 | attrVal | $@ based on $@ might later cause $@. | main.js:67:63:67:69 | attrVal | HTML construction | main.js:66:35:66:41 | attrVal | library input | main.js:67:47:67:78 | "<img a ... "\\"/>" | cross-site scripting |
| main.js:81:35:81:37 | val | main.js:79:34:79:36 | val | main.js:81:35:81:37 | val | $@ based on $@ might later cause $@. | main.js:81:35:81:37 | val | HTML construction | main.js:79:34:79:36 | val | library input | main.js:81:24:81:49 | "<span> ... /span>" | cross-site scripting | | main.js:81:35:81:37 | val | main.js:79:34:79:36 | val | main.js:81:35:81:37 | val | $@ based on $@ might later cause $@. | main.js:81:35:81:37 | val | HTML construction | main.js:79:34:79:36 | val | library input | main.js:81:24:81:49 | "<span> ... /span>" | cross-site scripting |
| main.js:90:23:90:23 | x | main.js:93:43:93:43 | x | main.js:90:23:90:23 | x | $@ based on $@ might later cause $@. | main.js:90:23:90:23 | x | HTML construction | main.js:93:43:93:43 | x | library input | main.js:94:20:94:32 | createHTML(x) | cross-site scripting |
| typed.ts:2:29:2:29 | s | typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s | $@ based on $@ might later cause $@. | typed.ts:2:29:2:29 | s | HTML construction | typed.ts:1:39:1:39 | s | library input | typed.ts:3:31:3:34 | html | cross-site scripting | | typed.ts:2:29:2:29 | s | typed.ts:1:39:1:39 | s | typed.ts:2:29:2:29 | s | $@ based on $@ might later cause $@. | typed.ts:2:29:2:29 | s | HTML construction | typed.ts:1:39:1:39 | s | library input | typed.ts:3:31:3:34 | html | cross-site scripting |
| typed.ts:8:40:8:40 | s | typed.ts:6:43:6:43 | s | typed.ts:8:40:8:40 | s | $@ based on $@ might later cause $@. | typed.ts:8:40:8:40 | s | HTML construction | typed.ts:6:43:6:43 | s | library input | typed.ts:8:29:8:52 | "<span> ... /span>" | cross-site scripting | | typed.ts:8:40:8:40 | s | typed.ts:6:43:6:43 | s | typed.ts:8:40:8:40 | s | $@ based on $@ might later cause $@. | typed.ts:8:40:8:40 | s | HTML construction | typed.ts:6:43:6:43 | s | library input | typed.ts:8:29:8:52 | "<span> ... /span>" | cross-site scripting |

View File

@@ -85,3 +85,11 @@ module.exports.types = function (val) {
$("#foo").html("<span>" + val + "</span>"); // OK $("#foo").html("<span>" + val + "</span>"); // OK
} }
} }
function createHTML(x) {
return "<span>" + x + "</span>"; // NOT OK
}
module.exports.usesCreateHTML = function (x) {
$("#foo").html(createHTML(x));
}

View File

@@ -0,0 +1,3 @@
| tst.js:8:9:8:19 | /\\/foo\\/.*/ | This route uses a case-sensitive path $@, but is guarding a case-insensitive path $@. A path such as '/FOO/' will bypass the middleware. | tst.js:8:9:8:19 | /\\/foo\\/.*/ | pattern | tst.js:60:1:61:2 | app.get ... ware\\n}) | here |
| tst.js:14:5:14:28 | new Reg ... (.*)?') | This route uses a case-sensitive path $@, but is guarding a case-insensitive path $@. A path such as '/FOO' will bypass the middleware. | tst.js:14:5:14:28 | new Reg ... (.*)?') | pattern | tst.js:60:1:61:2 | app.get ... ware\\n}) | here |
| tst.js:41:9:41:25 | /\\/foo\\/([0-9]+)/ | This route uses a case-sensitive path $@, but is guarding a case-insensitive path $@. A path such as '/FOO/' will bypass the middleware. | tst.js:41:9:41:25 | /\\/foo\\/([0-9]+)/ | pattern | tst.js:60:1:61:2 | app.get ... ware\\n}) | here |

View File

@@ -0,0 +1 @@
Security/CWE-178/CaseSensitiveMiddlewarePath.ql

View File

@@ -0,0 +1,9 @@
const express = require('express');
const app = express();
app.get(/\/[a-zA-Z]+/, (req, res, next) => { // OK - regexp term is case insensitive
next();
});
app.get('/foo', (req, res) => {
});

View File

@@ -0,0 +1,61 @@
const express = require('express');
const app = express();
const unknown = require('~something/blah');
app.all(/\/.*/, unknown()); // OK - does not contain letters
app.all(/\/.*/i, unknown()); // OK
app.all(/\/foo\/.*/, unknown()); // NOT OK
app.all(/\/foo\/.*/i, unknown()); // OK - case insensitive
app.use(/\/x\/#\d{6}/, express.static('images/')); // OK - not a middleware
app.get(
new RegExp('^/foo(.*)?'), // NOT OK - case sensitive
unknown(),
function(req, res, next) {
if (req.params.blah) {
next();
}
}
);
app.get(
new RegExp('^/foo(.*)?', 'i'), // OK - case insensitive
unknown(),
function(req, res, next) {
if (req.params.blah) {
next();
}
}
);
app.get(
new RegExp('^/foo(.*)?'), // OK - not a middleware
unknown(),
function(req,res) {
res.send('Hello World!');
}
);
app.use(/\/foo\/([0-9]+)/, (req, res, next) => { // NOT OK - case sensitive
unknown(req);
next();
});
app.use(/\/foo\/([0-9]+)/i, (req, res, next) => { // OK - case insensitive
unknown(req);
next();
});
app.use(/\/foo\/([0-9]+)/, (req, res) => { // OK - not middleware
unknown(req, res);
});
app.use(/\/foo\/([0-9]+)/i, (req, res) => { // OK - not middleware (also case insensitive)
unknown(req, res);
});
app.get('/foo/:param', (req, res) => { // OK - not a middleware
});

View File

@@ -1,5 +1,5 @@
name: codeql/python-all name: codeql/python-all
version: 0.5.1 version: 0.5.2-dev
groups: python groups: python
dbscheme: semmlecode.python.dbscheme dbscheme: semmlecode.python.dbscheme
extractor: python extractor: python

View File

@@ -74,9 +74,7 @@ private module NotExposed {
} }
/** DEPRECATED: Alias for fullyQualifiedToApiGraphPath */ /** DEPRECATED: Alias for fullyQualifiedToApiGraphPath */
deprecated string fullyQualifiedToAPIGraphPath(string fullyQaulified) { deprecated predicate fullyQualifiedToAPIGraphPath = fullyQualifiedToApiGraphPath/1;
result = fullyQualifiedToApiGraphPath(fullyQaulified)
}
bindingset[this] bindingset[this]
abstract class FindSubclassesSpec extends string { abstract class FindSubclassesSpec extends string {

View File

@@ -0,0 +1,7 @@
blob_client = blob_service_client.get_blob_client(container=container_name, blob=blob_name)
blob_client.require_encryption = True
blob_client.key_encryption_key = kek
# GOOD: Must use `encryption_version` set to `2.0`
blob_client.encryption_version = '2.0' # Use Version 2.0!
with open("decryptedcontentfile.txt", "rb") as stream:
blob_client.upload_blob(stream, overwrite=OVERWRITE_EXISTING)

View File

@@ -0,0 +1,29 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>Azure Storage .NET, Java, and Python SDKs support encryption on the client with a customer-managed key that is maintained in Azure Key Vault or another key store.</p>
<p>Current release versions of the Azure Storage SDKs use cipher block chaining (CBC mode) for client-side encryption (referred to as <code>v1</code>).</p>
</overview>
<recommendation>
<p>Consider switching to <code>v2</code> client-side encryption.</p>
</recommendation>
<example>
<sample src="UnsafeUsageOfClientSideEncryptionVersion.py" />
</example>
<references>
<li>
<a href="http://aka.ms/azstorageclientencryptionblog">Azure Storage Client Encryption Blog.</a>
</li>
<li>
<a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30187">CVE-2022-30187</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,90 @@
/**
* @name Unsafe usage of v1 version of Azure Storage client-side encryption.
* @description Using version v1 of Azure Storage client-side encryption is insecure, and may enable an attacker to decrypt encrypted data
* @kind problem
* @tags security
* cryptography
* external/cwe/cwe-327
* @id py/azure-storage/unsafe-client-side-encryption-in-use
* @problem.severity error
* @precision medium
*/
import python
import semmle.python.ApiGraphs
predicate isUnsafeClientSideAzureStorageEncryptionViaAttributes(Call call, AttrNode node) {
exists(
API::Node n, API::Node n2, Attribute a, AssignStmt astmt, API::Node uploadBlob,
ControlFlowNode ctrlFlowNode, string s
|
s in ["key_encryption_key", "key_resolver_function"] and
n =
API::moduleImport("azure")
.getMember("storage")
.getMember("blob")
.getMember("BlobClient")
.getReturn()
.getMember(s) and
n2 =
API::moduleImport("azure")
.getMember("storage")
.getMember("blob")
.getMember("BlobClient")
.getReturn()
.getMember("upload_blob") and
n.getAValueReachableFromSource().asExpr() = a and
astmt.getATarget() = a and
a.getAFlowNode() = node and
uploadBlob =
API::moduleImport("azure")
.getMember("storage")
.getMember("blob")
.getMember("BlobClient")
.getReturn()
.getMember("upload_blob") and
uploadBlob.getACall().asExpr() = call and
ctrlFlowNode = call.getAFlowNode() and
node.strictlyReaches(ctrlFlowNode) and
node != ctrlFlowNode and
not exists(
AssignStmt astmt2, Attribute a2, AttrNode encryptionVersionSet, StrConst uc,
API::Node encryptionVersion
|
uc = astmt2.getValue() and
uc.getText() in ["'2.0'", "2.0"] and
encryptionVersion =
API::moduleImport("azure")
.getMember("storage")
.getMember("blob")
.getMember("BlobClient")
.getReturn()
.getMember("encryption_version") and
encryptionVersion.getAValueReachableFromSource().asExpr() = a2 and
astmt2.getATarget() = a2 and
a2.getAFlowNode() = encryptionVersionSet and
encryptionVersionSet.strictlyReaches(ctrlFlowNode)
)
)
}
predicate isUnsafeClientSideAzureStorageEncryptionViaObjectCreation(Call call, ControlFlowNode node) {
exists(API::Node c, string s, Keyword k | k.getAFlowNode() = node |
c.getACall().asExpr() = call and
c = API::moduleImport("azure").getMember("storage").getMember("blob").getMember(s) and
s in ["ContainerClient", "BlobClient", "BlobServiceClient"] and
k.getArg() = "key_encryption_key" and
k = call.getANamedArg() and
not k.getValue() instanceof None and
not exists(Keyword k2 | k2 = call.getANamedArg() |
k2.getArg() = "encryption_version" and
k2.getValue().(StrConst).getText() in ["'2.0'", "2.0"]
)
)
}
from Call call, ControlFlowNode node
where
isUnsafeClientSideAzureStorageEncryptionViaAttributes(call, node) or
isUnsafeClientSideAzureStorageEncryptionViaObjectCreation(call, node)
select node, "Unsafe usage of v1 version of Azure Storage client-side encryption."

View File

@@ -1,5 +1,5 @@
name: codeql/python-queries name: codeql/python-queries
version: 0.3.0 version: 0.3.1-dev
groups: groups:
- python - python
- queries - queries

View File

@@ -1,161 +1,112 @@
use crate::trap;
use node_types::{EntryKind, Field, NodeTypeMap, Storage, TypeName}; use node_types::{EntryKind, Field, NodeTypeMap, Storage, TypeName};
use std::borrow::Cow;
use std::collections::BTreeMap as Map; use std::collections::BTreeMap as Map;
use std::collections::BTreeSet as Set; use std::collections::BTreeSet as Set;
use std::fmt; use std::fmt;
use std::io::Write;
use std::path::Path; use std::path::Path;
use tracing::{error, info, span, Level}; use tracing::{error, info, span, Level};
use tree_sitter::{Language, Node, Parser, Range, Tree}; use tree_sitter::{Language, Node, Parser, Range, Tree};
pub struct TrapWriter { pub fn populate_file(writer: &mut trap::Writer, absolute_path: &Path) -> trap::Label {
/// The accumulated trap entries let (file_label, fresh) =
trap_output: Vec<TrapEntry>, writer.global_id(&trap::full_id_for_file(&normalize_path(absolute_path)));
/// A counter for generating fresh labels if fresh {
counter: u32, writer.add_tuple(
/// cache of global keys "files",
global_keys: std::collections::HashMap<String, Label>, vec![
trap::Arg::Label(file_label),
trap::Arg::String(normalize_path(absolute_path)),
],
);
populate_parent_folders(writer, file_label, absolute_path.parent());
}
file_label
} }
pub fn new_trap_writer() -> TrapWriter { fn populate_empty_file(writer: &mut trap::Writer) -> trap::Label {
TrapWriter { let (file_label, fresh) = writer.global_id("empty;sourcefile");
counter: 0, if fresh {
trap_output: Vec::new(), writer.add_tuple(
global_keys: std::collections::HashMap::new(), "files",
vec![
trap::Arg::Label(file_label),
trap::Arg::String("".to_string()),
],
);
} }
file_label
} }
impl TrapWriter { pub fn populate_empty_location(writer: &mut trap::Writer) {
/// Gets a label that will hold the unique ID of the passed string at import time. let file_label = populate_empty_file(writer);
/// This can be used for incrementally importable TRAP files -- use globally unique location(writer, file_label, 0, 0, 0, 0);
/// strings to compute a unique ID for table tuples. }
///
/// Note: You probably want to make sure that the key strings that you use are disjoint
/// for disjoint column types; the standard way of doing this is to prefix (or append)
/// the column type name to the ID. Thus, you might identify methods in Java by the
/// full ID "methods_com.method.package.DeclaringClass.method(argumentList)".
fn fresh_id(&mut self) -> Label { pub fn populate_parent_folders(
let label = Label(self.counter); writer: &mut trap::Writer,
self.counter += 1; child_label: trap::Label,
self.trap_output.push(TrapEntry::FreshId(label)); path: Option<&Path>,
label ) {
} let mut path = path;
let mut child_label = child_label;
fn global_id(&mut self, key: &str) -> (Label, bool) { loop {
if let Some(label) = self.global_keys.get(key) { match path {
return (*label, false); None => break,
} Some(folder) => {
let label = Label(self.counter); let (folder_label, fresh) =
self.counter += 1; writer.global_id(&trap::full_id_for_folder(&normalize_path(folder)));
self.global_keys.insert(key.to_owned(), label); writer.add_tuple(
self.trap_output "containerparent",
.push(TrapEntry::MapLabelToKey(label, key.to_owned())); vec![
(label, true) trap::Arg::Label(folder_label),
} trap::Arg::Label(child_label),
],
fn add_tuple(&mut self, table_name: &str, args: Vec<Arg>) { );
self.trap_output if fresh {
.push(TrapEntry::GenericTuple(table_name.to_owned(), args)) writer.add_tuple(
} "folders",
vec![
fn populate_file(&mut self, absolute_path: &Path) -> Label { trap::Arg::Label(folder_label),
let (file_label, fresh) = self.global_id(&full_id_for_file(absolute_path)); trap::Arg::String(normalize_path(folder)),
if fresh { ],
self.add_tuple(
"files",
vec![
Arg::Label(file_label),
Arg::String(normalize_path(absolute_path)),
],
);
self.populate_parent_folders(file_label, absolute_path.parent());
}
file_label
}
fn populate_empty_file(&mut self) -> Label {
let (file_label, fresh) = self.global_id("empty;sourcefile");
if fresh {
self.add_tuple(
"files",
vec![Arg::Label(file_label), Arg::String("".to_string())],
);
}
file_label
}
pub fn populate_empty_location(&mut self) {
let file_label = self.populate_empty_file();
self.location(file_label, 0, 0, 0, 0);
}
fn populate_parent_folders(&mut self, child_label: Label, path: Option<&Path>) {
let mut path = path;
let mut child_label = child_label;
loop {
match path {
None => break,
Some(folder) => {
let (folder_label, fresh) = self.global_id(&full_id_for_folder(folder));
self.add_tuple(
"containerparent",
vec![Arg::Label(folder_label), Arg::Label(child_label)],
); );
if fresh { path = folder.parent();
self.add_tuple( child_label = folder_label;
"folders", } else {
vec![ break;
Arg::Label(folder_label),
Arg::String(normalize_path(folder)),
],
);
path = folder.parent();
child_label = folder_label;
} else {
break;
}
} }
} }
} }
} }
}
fn location( fn location(
&mut self, writer: &mut trap::Writer,
file_label: Label, file_label: trap::Label,
start_line: usize, start_line: usize,
start_column: usize, start_column: usize,
end_line: usize, end_line: usize,
end_column: usize, end_column: usize,
) -> Label { ) -> trap::Label {
let (loc_label, fresh) = self.global_id(&format!( let (loc_label, fresh) = writer.global_id(&format!(
"loc,{{{}}},{},{},{},{}", "loc,{{{}}},{},{},{},{}",
file_label, start_line, start_column, end_line, end_column file_label, start_line, start_column, end_line, end_column
)); ));
if fresh { if fresh {
self.add_tuple( writer.add_tuple(
"locations_default", "locations_default",
vec![ vec![
Arg::Label(loc_label), trap::Arg::Label(loc_label),
Arg::Label(file_label), trap::Arg::Label(file_label),
Arg::Int(start_line), trap::Arg::Int(start_line),
Arg::Int(start_column), trap::Arg::Int(start_column),
Arg::Int(end_line), trap::Arg::Int(end_line),
Arg::Int(end_column), trap::Arg::Int(end_column),
], ],
); );
}
loc_label
}
fn comment(&mut self, text: String) {
self.trap_output.push(TrapEntry::Comment(text));
}
pub fn output(self, writer: &mut dyn Write) -> std::io::Result<()> {
write!(writer, "{}", Program(self.trap_output))
} }
loc_label
} }
/// Extracts the source file at `path`, which is assumed to be canonicalized. /// Extracts the source file at `path`, which is assumed to be canonicalized.
@@ -163,71 +114,43 @@ pub fn extract(
language: Language, language: Language,
language_prefix: &str, language_prefix: &str,
schema: &NodeTypeMap, schema: &NodeTypeMap,
trap_writer: &mut TrapWriter, trap_writer: &mut trap::Writer,
path: &Path, path: &Path,
source: &[u8], source: &[u8],
ranges: &[Range], ranges: &[Range],
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let path_str = format!("{}", path.display());
let span = span!( let span = span!(
Level::TRACE, Level::TRACE,
"extract", "extract",
file = %path.display() file = %path_str
); );
let _enter = span.enter(); let _enter = span.enter();
info!("extracting: {}", path.display()); info!("extracting: {}", path_str);
let mut parser = Parser::new(); let mut parser = Parser::new();
parser.set_language(language).unwrap(); parser.set_language(language).unwrap();
parser.set_included_ranges(ranges).unwrap(); parser.set_included_ranges(ranges).unwrap();
let tree = parser.parse(&source, None).expect("Failed to parse file"); let tree = parser.parse(&source, None).expect("Failed to parse file");
trap_writer.comment(format!("Auto-generated TRAP file for {}", path.display())); trap_writer.comment(format!("Auto-generated TRAP file for {}", path_str));
let file_label = &trap_writer.populate_file(path); let file_label = populate_file(trap_writer, path);
let mut visitor = Visitor { let mut visitor = Visitor::new(
source, source,
trap_writer, trap_writer,
// TODO: should we handle path strings that are not valid UTF8 better? // TODO: should we handle path strings that are not valid UTF8 better?
path: format!("{}", path.display()), &path_str,
file_label: *file_label, file_label,
toplevel_child_counter: 0,
stack: Vec::new(),
language_prefix, language_prefix,
schema, schema,
}; );
traverse(&tree, &mut visitor); traverse(&tree, &mut visitor);
parser.reset(); parser.reset();
Ok(()) Ok(())
} }
/// Escapes a string for use in a TRAP key, by replacing special characters with
/// HTML entities.
fn escape_key<'a, S: Into<Cow<'a, str>>>(key: S) -> Cow<'a, str> {
fn needs_escaping(c: char) -> bool {
matches!(c, '&' | '{' | '}' | '"' | '@' | '#')
}
let key = key.into();
if key.contains(needs_escaping) {
let mut escaped = String::with_capacity(2 * key.len());
for c in key.chars() {
match c {
'&' => escaped.push_str("&amp;"),
'{' => escaped.push_str("&lbrace;"),
'}' => escaped.push_str("&rbrace;"),
'"' => escaped.push_str("&quot;"),
'@' => escaped.push_str("&commat;"),
'#' => escaped.push_str("&num;"),
_ => escaped.push(c),
}
}
Cow::Owned(escaped)
} else {
key
}
}
/// Normalizes the path according the common CodeQL specification. Assumes that /// Normalizes the path according the common CodeQL specification. Assumes that
/// `path` has already been canonicalized using `std::fs::canonicalize`. /// `path` has already been canonicalized using `std::fs::canonicalize`.
fn normalize_path(path: &Path) -> String { fn normalize_path(path: &Path) -> String {
@@ -267,34 +190,28 @@ fn normalize_path(path: &Path) -> String {
} }
} }
fn full_id_for_file(path: &Path) -> String {
format!("{};sourcefile", escape_key(&normalize_path(path)))
}
fn full_id_for_folder(path: &Path) -> String {
format!("{};folder", escape_key(&normalize_path(path)))
}
struct ChildNode { struct ChildNode {
field_name: Option<&'static str>, field_name: Option<&'static str>,
label: Label, label: trap::Label,
type_name: TypeName, type_name: TypeName,
} }
struct Visitor<'a> { struct Visitor<'a> {
/// The file path of the source code (as string) /// The file path of the source code (as string)
path: String, path: &'a str,
/// The label to use whenever we need to refer to the `@file` entity of this /// The label to use whenever we need to refer to the `@file` entity of this
/// source file. /// source file.
file_label: Label, file_label: trap::Label,
/// The source code as a UTF-8 byte array /// The source code as a UTF-8 byte array
source: &'a [u8], source: &'a [u8],
/// A TrapWriter to accumulate trap entries /// A trap::Writer to accumulate trap entries
trap_writer: &'a mut TrapWriter, trap_writer: &'a mut trap::Writer,
/// A counter for top-level child nodes /// A counter for top-level child nodes
toplevel_child_counter: usize, toplevel_child_counter: usize,
/// Language prefix /// Language-specific name of the AST info table
language_prefix: &'a str, ast_node_info_table_name: String,
/// Language-specific name of the tokeninfo table
tokeninfo_table_name: String,
/// A lookup table from type name to node types /// A lookup table from type name to node types
schema: &'a NodeTypeMap, schema: &'a NodeTypeMap,
/// A stack for gathering information from child nodes. Whenever a node is /// A stack for gathering information from child nodes. Whenever a node is
@@ -303,27 +220,48 @@ struct Visitor<'a> {
/// node the list containing the child data is popped from the stack and /// node the list containing the child data is popped from the stack and
/// matched against the dbscheme for the node. If the expectations are met /// matched against the dbscheme for the node. If the expectations are met
/// the corresponding row definitions are added to the trap_output. /// the corresponding row definitions are added to the trap_output.
stack: Vec<(Label, usize, Vec<ChildNode>)>, stack: Vec<(trap::Label, usize, Vec<ChildNode>)>,
} }
impl Visitor<'_> { impl<'a> Visitor<'a> {
fn new(
source: &'a [u8],
trap_writer: &'a mut trap::Writer,
path: &'a str,
file_label: trap::Label,
language_prefix: &str,
schema: &'a NodeTypeMap,
) -> Visitor<'a> {
Visitor {
path,
file_label,
source,
trap_writer,
toplevel_child_counter: 0,
ast_node_info_table_name: format!("{}_ast_node_info", language_prefix),
tokeninfo_table_name: format!("{}_tokeninfo", language_prefix),
schema,
stack: Vec::new(),
}
}
fn record_parse_error( fn record_parse_error(
&mut self, &mut self,
error_message: String, error_message: String,
full_error_message: String, full_error_message: String,
loc: Label, loc: trap::Label,
) { ) {
error!("{}", full_error_message); error!("{}", full_error_message);
let id = self.trap_writer.fresh_id(); let id = self.trap_writer.fresh_id();
self.trap_writer.add_tuple( self.trap_writer.add_tuple(
"diagnostics", "diagnostics",
vec![ vec![
Arg::Label(id), trap::Arg::Label(id),
Arg::Int(40), // severity 40 = error trap::Arg::Int(40), // severity 40 = error
Arg::String("parse_error".to_string()), trap::Arg::String("parse_error".to_string()),
Arg::String(error_message), trap::Arg::String(error_message),
Arg::String(full_error_message), trap::Arg::String(full_error_message),
Arg::Label(loc), trap::Arg::Label(loc),
], ],
); );
} }
@@ -335,7 +273,8 @@ impl Visitor<'_> {
node: Node, node: Node,
) { ) {
let (start_line, start_column, end_line, end_column) = location_for(self.source, node); let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
let loc = self.trap_writer.location( let loc = location(
self.trap_writer,
self.file_label, self.file_label,
start_line, start_line,
start_column, start_column,
@@ -374,7 +313,8 @@ impl Visitor<'_> {
} }
let (id, _, child_nodes) = self.stack.pop().expect("Vistor: empty stack"); let (id, _, child_nodes) = self.stack.pop().expect("Vistor: empty stack");
let (start_line, start_column, end_line, end_column) = location_for(self.source, node); let (start_line, start_column, end_line, end_column) = location_for(self.source, node);
let loc = self.trap_writer.location( let loc = location(
self.trap_writer,
self.file_label, self.file_label,
start_line, start_line,
start_column, start_column,
@@ -402,19 +342,19 @@ impl Visitor<'_> {
match &table.kind { match &table.kind {
EntryKind::Token { kind_id, .. } => { EntryKind::Token { kind_id, .. } => {
self.trap_writer.add_tuple( self.trap_writer.add_tuple(
&format!("{}_ast_node_info", self.language_prefix), &self.ast_node_info_table_name,
vec![ vec![
Arg::Label(id), trap::Arg::Label(id),
Arg::Label(parent_id), trap::Arg::Label(parent_id),
Arg::Int(parent_index), trap::Arg::Int(parent_index),
Arg::Label(loc), trap::Arg::Label(loc),
], ],
); );
self.trap_writer.add_tuple( self.trap_writer.add_tuple(
&format!("{}_tokeninfo", self.language_prefix), &self.tokeninfo_table_name,
vec![ vec![
Arg::Label(id), trap::Arg::Label(id),
Arg::Int(*kind_id), trap::Arg::Int(*kind_id),
sliced_source_arg(self.source, node), sliced_source_arg(self.source, node),
], ],
); );
@@ -425,15 +365,15 @@ impl Visitor<'_> {
} => { } => {
if let Some(args) = self.complex_node(&node, fields, &child_nodes, id) { if let Some(args) = self.complex_node(&node, fields, &child_nodes, id) {
self.trap_writer.add_tuple( self.trap_writer.add_tuple(
&format!("{}_ast_node_info", self.language_prefix), &self.ast_node_info_table_name,
vec![ vec![
Arg::Label(id), trap::Arg::Label(id),
Arg::Label(parent_id), trap::Arg::Label(parent_id),
Arg::Int(parent_index), trap::Arg::Int(parent_index),
Arg::Label(loc), trap::Arg::Label(loc),
], ],
); );
let mut all_args = vec![Arg::Label(id)]; let mut all_args = vec![trap::Arg::Label(id)];
all_args.extend(args); all_args.extend(args);
self.trap_writer.add_tuple(table_name, all_args); self.trap_writer.add_tuple(table_name, all_args);
} }
@@ -472,9 +412,9 @@ impl Visitor<'_> {
node: &Node, node: &Node,
fields: &[Field], fields: &[Field],
child_nodes: &[ChildNode], child_nodes: &[ChildNode],
parent_id: Label, parent_id: trap::Label,
) -> Option<Vec<Arg>> { ) -> Option<Vec<trap::Arg>> {
let mut map: Map<&Option<String>, (&Field, Vec<Arg>)> = Map::new(); let mut map: Map<&Option<String>, (&Field, Vec<trap::Arg>)> = Map::new();
for field in fields { for field in fields {
map.insert(&field.name, (field, Vec::new())); map.insert(&field.name, (field, Vec::new()));
} }
@@ -488,9 +428,9 @@ impl Visitor<'_> {
{ {
// We can safely unwrap because type_matches checks the key is in the map. // We can safely unwrap because type_matches checks the key is in the map.
let (int_value, _) = int_mapping.get(&child_node.type_name.kind).unwrap(); let (int_value, _) = int_mapping.get(&child_node.type_name.kind).unwrap();
values.push(Arg::Int(*int_value)); values.push(trap::Arg::Int(*int_value));
} else { } else {
values.push(Arg::Label(child_node.label)); values.push(trap::Arg::Label(child_node.label));
} }
} else if field.name.is_some() { } else if field.name.is_some() {
let error_message = format!( let error_message = format!(
@@ -569,9 +509,9 @@ impl Visitor<'_> {
); );
break; break;
} }
let mut args = vec![Arg::Label(parent_id)]; let mut args = vec![trap::Arg::Label(parent_id)];
if *has_index { if *has_index {
args.push(Arg::Int(index)) args.push(trap::Arg::Int(index))
} }
args.push(child_value.clone()); args.push(child_value.clone());
self.trap_writer.add_tuple(table_name, args); self.trap_writer.add_tuple(table_name, args);
@@ -625,9 +565,9 @@ impl Visitor<'_> {
} }
// Emit a slice of a source file as an Arg. // Emit a slice of a source file as an Arg.
fn sliced_source_arg(source: &[u8], n: Node) -> Arg { fn sliced_source_arg(source: &[u8], n: Node) -> trap::Arg {
let range = n.byte_range(); let range = n.byte_range();
Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned()) trap::Arg::String(String::from_utf8_lossy(&source[range.start..range.end]).into_owned())
} }
// Emit a pair of `TrapEntry`s for the provided node, appropriately calibrated. // Emit a pair of `TrapEntry`s for the provided node, appropriately calibrated.
@@ -699,59 +639,6 @@ fn traverse(tree: &Tree, visitor: &mut Visitor) {
} }
} }
pub struct Program(Vec<TrapEntry>);
impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut text = String::new();
for trap_entry in &self.0 {
text.push_str(&format!("{}\n", trap_entry));
}
write!(f, "{}", text)
}
}
enum TrapEntry {
/// Maps the label to a fresh id, e.g. `#123=*`.
FreshId(Label),
/// Maps the label to a key, e.g. `#7=@"foo"`.
MapLabelToKey(Label, String),
/// foo_bar(arg*)
GenericTuple(String, Vec<Arg>),
Comment(String),
}
impl fmt::Display for TrapEntry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TrapEntry::FreshId(label) => write!(f, "{}=*", label),
TrapEntry::MapLabelToKey(label, key) => {
write!(f, "{}=@\"{}\"", label, key.replace("\"", "\"\""))
}
TrapEntry::GenericTuple(name, args) => {
write!(f, "{}(", name)?;
for (index, arg) in args.iter().enumerate() {
if index > 0 {
write!(f, ",")?;
}
write!(f, "{}", arg)?;
}
write!(f, ")")
}
TrapEntry::Comment(line) => write!(f, "// {}", line),
}
}
}
#[derive(Debug, Copy, Clone)]
// Identifiers of the form #0, #1...
struct Label(u32);
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "#{:x}", self.0)
}
}
// Numeric indices. // Numeric indices.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
struct Index(usize); struct Index(usize);
@@ -761,69 +648,3 @@ impl fmt::Display for Index {
write!(f, "{}", self.0) write!(f, "{}", self.0)
} }
} }
// Some untyped argument to a TrapEntry.
#[derive(Debug, Clone)]
enum Arg {
Label(Label),
Int(usize),
String(String),
}
const MAX_STRLEN: usize = 1048576;
impl fmt::Display for Arg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Arg::Label(x) => write!(f, "{}", x),
Arg::Int(x) => write!(f, "{}", x),
Arg::String(x) => write!(
f,
"\"{}\"",
limit_string(x, MAX_STRLEN).replace("\"", "\"\"")
),
}
}
}
/// Limit the length (in bytes) of a string. If the string's length in bytes is
/// less than or equal to the limit then the entire string is returned. Otherwise
/// the string is sliced at the provided limit. If there is a multi-byte character
/// at the limit then the returned slice will be slightly shorter than the limit to
/// avoid splitting that multi-byte character.
fn limit_string(string: &str, max_size: usize) -> &str {
if string.len() <= max_size {
return string;
}
let p = string.as_bytes();
let mut index = max_size;
// We want to clip the string at [max_size]; however, the character at that position
// may span several bytes. We need to find the first byte of the character. In UTF-8
// encoded data any byte that matches the bit pattern 10XXXXXX is not a start byte.
// Therefore we decrement the index as long as there are bytes matching this pattern.
// This ensures we cut the string at the border between one character and another.
while index > 0 && (p[index] & 0b11000000) == 0b10000000 {
index -= 1;
}
&string[0..index]
}
#[test]
fn limit_string_test() {
assert_eq!("hello", limit_string(&"hello world".to_owned(), 5));
assert_eq!("hi ☹", limit_string(&"hi ☹☹".to_owned(), 6));
assert_eq!("hi ", limit_string(&"hi ☹☹".to_owned(), 5));
}
#[test]
fn escape_key_test() {
assert_eq!("foo!", escape_key("foo!"));
assert_eq!("foo&lbrace;&rbrace;", escape_key("foo{}"));
assert_eq!("&lbrace;&rbrace;", escape_key("{}"));
assert_eq!("", escape_key(""));
assert_eq!("/path/to/foo.rb", escape_key("/path/to/foo.rb"));
assert_eq!(
"/path/to/foo&amp;&lbrace;&rbrace;&quot;&commat;&num;.rb",
escape_key("/path/to/foo&{}\"@#.rb")
);
}

View File

@@ -1,49 +1,13 @@
mod extractor; mod extractor;
mod trap;
extern crate num_cpus; extern crate num_cpus;
use flate2::write::GzEncoder;
use rayon::prelude::*; use rayon::prelude::*;
use std::fs; use std::fs;
use std::io::{BufRead, BufWriter}; use std::io::BufRead;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
enum TrapCompression {
None,
Gzip,
}
impl TrapCompression {
fn from_env() -> TrapCompression {
match std::env::var("CODEQL_QL_TRAP_COMPRESSION") {
Ok(method) => match TrapCompression::from_string(&method) {
Some(c) => c,
None => {
tracing::error!("Unknown compression method '{}'; using gzip.", &method);
TrapCompression::Gzip
}
},
// Default compression method if the env var isn't set:
Err(_) => TrapCompression::Gzip,
}
}
fn from_string(s: &str) -> Option<TrapCompression> {
match s.to_lowercase().as_ref() {
"none" => Some(TrapCompression::None),
"gzip" => Some(TrapCompression::Gzip),
_ => None,
}
}
fn extension(&self) -> &str {
match self {
TrapCompression::None => "trap",
TrapCompression::Gzip => "trap.gz",
}
}
}
/** /**
* Gets the number of threads the extractor should use, by reading the * Gets the number of threads the extractor should use, by reading the
* CODEQL_THREADS environment variable and using it as described in the * CODEQL_THREADS environment variable and using it as described in the
@@ -115,7 +79,7 @@ fn main() -> std::io::Result<()> {
.value_of("output-dir") .value_of("output-dir")
.expect("missing --output-dir"); .expect("missing --output-dir");
let trap_dir = PathBuf::from(trap_dir); let trap_dir = PathBuf::from(trap_dir);
let trap_compression = TrapCompression::from_env(); let trap_compression = trap::Compression::from_env("CODEQL_QL_TRAP_COMPRESSION");
let file_list = matches.value_of("file-list").expect("missing --file-list"); let file_list = matches.value_of("file-list").expect("missing --file-list");
let file_list = fs::File::open(file_list)?; let file_list = fs::File::open(file_list)?;
@@ -140,7 +104,7 @@ fn main() -> std::io::Result<()> {
let src_archive_file = path_for(&src_archive_dir, &path, ""); let src_archive_file = path_for(&src_archive_dir, &path, "");
let source = std::fs::read(&path)?; let source = std::fs::read(&path)?;
let code_ranges = vec![]; let code_ranges = vec![];
let mut trap_writer = extractor::new_trap_writer(); let mut trap_writer = trap::Writer::new();
extractor::extract( extractor::extract(
language, language,
"ql", "ql",
@@ -152,33 +116,25 @@ fn main() -> std::io::Result<()> {
)?; )?;
std::fs::create_dir_all(&src_archive_file.parent().unwrap())?; std::fs::create_dir_all(&src_archive_file.parent().unwrap())?;
std::fs::copy(&path, &src_archive_file)?; std::fs::copy(&path, &src_archive_file)?;
write_trap(&trap_dir, path, trap_writer, &trap_compression) write_trap(&trap_dir, path, &trap_writer, trap_compression)
}) })
.expect("failed to extract files"); .expect("failed to extract files");
let path = PathBuf::from("extras"); let path = PathBuf::from("extras");
let mut trap_writer = extractor::new_trap_writer(); let mut trap_writer = trap::Writer::new();
trap_writer.populate_empty_location(); extractor::populate_empty_location(&mut trap_writer);
write_trap(&trap_dir, path, trap_writer, &trap_compression) write_trap(&trap_dir, path, &trap_writer, trap_compression)
} }
fn write_trap( fn write_trap(
trap_dir: &Path, trap_dir: &Path,
path: PathBuf, path: PathBuf,
trap_writer: extractor::TrapWriter, trap_writer: &trap::Writer,
trap_compression: &TrapCompression, trap_compression: trap::Compression,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let trap_file = path_for(trap_dir, &path, trap_compression.extension()); let trap_file = path_for(trap_dir, &path, trap_compression.extension());
std::fs::create_dir_all(&trap_file.parent().unwrap())?; std::fs::create_dir_all(&trap_file.parent().unwrap())?;
let trap_file = std::fs::File::create(&trap_file)?; trap_writer.write_to_file(&trap_file, trap_compression)
let mut trap_file = BufWriter::new(trap_file);
match trap_compression {
TrapCompression::None => trap_writer.output(&mut trap_file),
TrapCompression::Gzip => {
let mut compressed_writer = GzEncoder::new(trap_file, flate2::Compression::fast());
trap_writer.output(&mut compressed_writer)
}
}
} }
fn path_for(dir: &Path, path: &Path, ext: &str) -> PathBuf { fn path_for(dir: &Path, path: &Path, ext: &str) -> PathBuf {

272
ql/extractor/src/trap.rs Normal file
View File

@@ -0,0 +1,272 @@
use std::borrow::Cow;
use std::fmt;
use std::io::{BufWriter, Write};
use std::path::Path;
use flate2::write::GzEncoder;
pub struct Writer {
/// The accumulated trap entries
trap_output: Vec<Entry>,
/// A counter for generating fresh labels
counter: u32,
/// cache of global keys
global_keys: std::collections::HashMap<String, Label>,
}
impl Writer {
pub fn new() -> Writer {
Writer {
counter: 0,
trap_output: Vec::new(),
global_keys: std::collections::HashMap::new(),
}
}
pub fn fresh_id(&mut self) -> Label {
let label = Label(self.counter);
self.counter += 1;
self.trap_output.push(Entry::FreshId(label));
label
}
/// Gets a label that will hold the unique ID of the passed string at import time.
/// This can be used for incrementally importable TRAP files -- use globally unique
/// strings to compute a unique ID for table tuples.
///
/// Note: You probably want to make sure that the key strings that you use are disjoint
/// for disjoint column types; the standard way of doing this is to prefix (or append)
/// the column type name to the ID. Thus, you might identify methods in Java by the
/// full ID "methods_com.method.package.DeclaringClass.method(argumentList)".
pub fn global_id(&mut self, key: &str) -> (Label, bool) {
if let Some(label) = self.global_keys.get(key) {
return (*label, false);
}
let label = Label(self.counter);
self.counter += 1;
self.global_keys.insert(key.to_owned(), label);
self.trap_output
.push(Entry::MapLabelToKey(label, key.to_owned()));
(label, true)
}
pub fn add_tuple(&mut self, table_name: &str, args: Vec<Arg>) {
self.trap_output
.push(Entry::GenericTuple(table_name.to_owned(), args))
}
pub fn comment(&mut self, text: String) {
self.trap_output.push(Entry::Comment(text));
}
pub fn write_to_file(&self, path: &Path, compression: Compression) -> std::io::Result<()> {
let trap_file = std::fs::File::create(path)?;
let mut trap_file = BufWriter::new(trap_file);
match compression {
Compression::None => self.write_trap_entries(&mut trap_file),
Compression::Gzip => {
let mut compressed_writer = GzEncoder::new(trap_file, flate2::Compression::fast());
self.write_trap_entries(&mut compressed_writer)
}
}
}
fn write_trap_entries<W: Write>(&self, file: &mut W) -> std::io::Result<()> {
for trap_entry in &self.trap_output {
writeln!(file, "{}", trap_entry)?;
}
std::io::Result::Ok(())
}
}
pub enum Entry {
/// Maps the label to a fresh id, e.g. `#123=*`.
FreshId(Label),
/// Maps the label to a key, e.g. `#7=@"foo"`.
MapLabelToKey(Label, String),
/// foo_bar(arg*)
GenericTuple(String, Vec<Arg>),
Comment(String),
}
impl fmt::Display for Entry {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Entry::FreshId(label) => write!(f, "{}=*", label),
Entry::MapLabelToKey(label, key) => {
write!(f, "{}=@\"{}\"", label, key.replace("\"", "\"\""))
}
Entry::GenericTuple(name, args) => {
write!(f, "{}(", name)?;
for (index, arg) in args.iter().enumerate() {
if index > 0 {
write!(f, ",")?;
}
write!(f, "{}", arg)?;
}
write!(f, ")")
}
Entry::Comment(line) => write!(f, "// {}", line),
}
}
}
#[derive(Debug, Copy, Clone)]
// Identifiers of the form #0, #1...
pub struct Label(u32);
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "#{:x}", self.0)
}
}
// Some untyped argument to a TrapEntry.
#[derive(Debug, Clone)]
pub enum Arg {
Label(Label),
Int(usize),
String(String),
}
const MAX_STRLEN: usize = 1048576;
impl fmt::Display for Arg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Arg::Label(x) => write!(f, "{}", x),
Arg::Int(x) => write!(f, "{}", x),
Arg::String(x) => write!(
f,
"\"{}\"",
limit_string(x, MAX_STRLEN).replace("\"", "\"\"")
),
}
}
}
pub struct Program(Vec<Entry>);
impl fmt::Display for Program {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut text = String::new();
for trap_entry in &self.0 {
text.push_str(&format!("{}\n", trap_entry));
}
write!(f, "{}", text)
}
}
pub fn full_id_for_file(normalized_path: &str) -> String {
format!("{};sourcefile", escape_key(normalized_path))
}
pub fn full_id_for_folder(normalized_path: &str) -> String {
format!("{};folder", escape_key(normalized_path))
}
/// Escapes a string for use in a TRAP key, by replacing special characters with
/// HTML entities.
fn escape_key<'a, S: Into<Cow<'a, str>>>(key: S) -> Cow<'a, str> {
fn needs_escaping(c: char) -> bool {
matches!(c, '&' | '{' | '}' | '"' | '@' | '#')
}
let key = key.into();
if key.contains(needs_escaping) {
let mut escaped = String::with_capacity(2 * key.len());
for c in key.chars() {
match c {
'&' => escaped.push_str("&amp;"),
'{' => escaped.push_str("&lbrace;"),
'}' => escaped.push_str("&rbrace;"),
'"' => escaped.push_str("&quot;"),
'@' => escaped.push_str("&commat;"),
'#' => escaped.push_str("&num;"),
_ => escaped.push(c),
}
}
Cow::Owned(escaped)
} else {
key
}
}
/// Limit the length (in bytes) of a string. If the string's length in bytes is
/// less than or equal to the limit then the entire string is returned. Otherwise
/// the string is sliced at the provided limit. If there is a multi-byte character
/// at the limit then the returned slice will be slightly shorter than the limit to
/// avoid splitting that multi-byte character.
fn limit_string(string: &str, max_size: usize) -> &str {
if string.len() <= max_size {
return string;
}
let p = string.as_bytes();
let mut index = max_size;
// We want to clip the string at [max_size]; however, the character at that position
// may span several bytes. We need to find the first byte of the character. In UTF-8
// encoded data any byte that matches the bit pattern 10XXXXXX is not a start byte.
// Therefore we decrement the index as long as there are bytes matching this pattern.
// This ensures we cut the string at the border between one character and another.
while index > 0 && (p[index] & 0b11000000) == 0b10000000 {
index -= 1;
}
&string[0..index]
}
#[derive(Clone, Copy)]
pub enum Compression {
None,
Gzip,
}
impl Compression {
pub fn from_env(var_name: &str) -> Compression {
match std::env::var(var_name) {
Ok(method) => match Compression::from_string(&method) {
Some(c) => c,
None => {
tracing::error!("Unknown compression method '{}'; using gzip.", &method);
Compression::Gzip
}
},
// Default compression method if the env var isn't set:
Err(_) => Compression::Gzip,
}
}
pub fn from_string(s: &str) -> Option<Compression> {
match s.to_lowercase().as_ref() {
"none" => Some(Compression::None),
"gzip" => Some(Compression::Gzip),
_ => None,
}
}
pub fn extension(&self) -> &str {
match self {
Compression::None => "trap",
Compression::Gzip => "trap.gz",
}
}
}
#[test]
fn limit_string_test() {
assert_eq!("hello", limit_string(&"hello world".to_owned(), 5));
assert_eq!("hi ☹", limit_string(&"hi ☹☹".to_owned(), 6));
assert_eq!("hi ", limit_string(&"hi ☹☹".to_owned(), 5));
}
#[test]
fn escape_key_test() {
assert_eq!("foo!", escape_key("foo!"));
assert_eq!("foo&lbrace;&rbrace;", escape_key("foo{}"));
assert_eq!("&lbrace;&rbrace;", escape_key("{}"));
assert_eq!("", escape_key(""));
assert_eq!("/path/to/foo.rb", escape_key("/path/to/foo.rb"));
assert_eq!(
"/path/to/foo&amp;&lbrace;&rbrace;&quot;&commat;&num;.rb",
escape_key("/path/to/foo&{}\"@#.rb")
);
}

30
ql/scripts/split-sarif.js Normal file
View File

@@ -0,0 +1,30 @@
var fs = require("fs");
// the .sarif file to split, and then the directory to put the split files in.
async function main(inputs) {
const sarifFile = JSON.parse(fs.readFileSync(inputs[0]));
const outFolder = inputs[1];
const out = {};
for (const result of sarifFile.runs[0].results) {
const lang = getLanguage(result);
if (!out[lang]) {
out[lang] = [];
}
out[lang].push(result);
}
for (const lang in out) {
const outSarif = JSON.parse(JSON.stringify(sarifFile));
outSarif.runs[0].results = out[lang];
fs.writeFileSync(`${outFolder}/${lang}.sarif`, JSON.stringify(outSarif, null, 2));
}
}
function getLanguage(result) {
return result.locations[0].physicalLocation.artifactLocation.uri.split(
"/"
)[0];
}
main(process.argv.splice(2));

BIN
ruby/Cargo.lock generated

Binary file not shown.

View File

@@ -19,6 +19,7 @@ fn main() -> std::io::Result<()> {
.arg("--include-extension=.erb") .arg("--include-extension=.erb")
.arg("--include-extension=.gemspec") .arg("--include-extension=.gemspec")
.arg("--include=**/Gemfile") .arg("--include=**/Gemfile")
.arg("--exclude=**/.git")
.arg("--size-limit=5m") .arg("--size-limit=5m")
.arg("--language=ruby") .arg("--language=ruby")
.arg("--working-dir=.") .arg("--working-dir=.")

View File

@@ -11,10 +11,12 @@ flate2 = "1.0"
node-types = { path = "../node-types" } node-types = { path = "../node-types" }
tree-sitter = "0.19" tree-sitter = "0.19"
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" } tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "5b305c3cd32db10494cedd2743de6bbe32f1a573" } tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "e75d04404c9dd71ad68850d5c672b226d5e694f3" }
clap = "3.0" clap = "3.0"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
rayon = "1.5.0" rayon = "1.5.0"
num_cpus = "1.13.0" num_cpus = "1.13.0"
regex = "1.5.5" regex = "1.5.5"
encoding = "0.2"
lazy_static = "1.4.0"

View File

@@ -1,10 +1,13 @@
mod extractor; mod extractor;
#[macro_use]
extern crate lazy_static;
extern crate num_cpus; extern crate num_cpus;
use clap::arg; use clap::arg;
use flate2::write::GzEncoder; use flate2::write::GzEncoder;
use rayon::prelude::*; use rayon::prelude::*;
use std::borrow::Cow;
use std::fs; use std::fs;
use std::io::{BufRead, BufWriter}; use std::io::{BufRead, BufWriter};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@@ -75,6 +78,21 @@ fn num_codeql_threads() -> usize {
} }
} }
lazy_static! {
static ref CP_NUMBER: regex::Regex = regex::Regex::new("cp([0-9]+)").unwrap();
}
fn encoding_from_name(encoding_name: &str) -> Option<&(dyn encoding::Encoding + Send + Sync)> {
match encoding::label::encoding_from_whatwg_label(encoding_name) {
s @ Some(_) => s,
None => CP_NUMBER.captures(encoding_name).and_then(|cap| {
encoding::label::encoding_from_windows_code_page(
str::parse(cap.get(1).unwrap().as_str()).unwrap(),
)
}),
}
}
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_target(false) .with_target(false)
@@ -140,6 +158,7 @@ fn main() -> std::io::Result<()> {
let path = PathBuf::from(line).canonicalize()?; let path = PathBuf::from(line).canonicalize()?;
let src_archive_file = path_for(&src_archive_dir, &path, ""); let src_archive_file = path_for(&src_archive_dir, &path, "");
let mut source = std::fs::read(&path)?; let mut source = std::fs::read(&path)?;
let mut needs_conversion = false;
let code_ranges; let code_ranges;
let mut trap_writer = extractor::new_trap_writer(); let mut trap_writer = extractor::new_trap_writer();
if path.extension().map_or(false, |x| x == "erb") { if path.extension().map_or(false, |x| x == "erb") {
@@ -168,6 +187,43 @@ fn main() -> std::io::Result<()> {
} }
code_ranges = ranges; code_ranges = ranges;
} else { } else {
if let Some(encoding_name) = scan_coding_comment(&source) {
// If the input is already UTF-8 then there is no need to recode the source
// If the declared encoding is 'binary' or 'ascii-8bit' then it is not clear how
// to interpret characters. In this case it is probably best to leave the input
// unchanged.
if !encoding_name.eq_ignore_ascii_case("utf-8")
&& !encoding_name.eq_ignore_ascii_case("ascii-8bit")
&& !encoding_name.eq_ignore_ascii_case("binary")
{
if let Some(encoding) = encoding_from_name(&encoding_name) {
needs_conversion =
encoding.whatwg_name().unwrap_or_default() != "utf-8";
if needs_conversion {
match encoding
.decode(&source, encoding::types::DecoderTrap::Replace)
{
Ok(str) => source = str.as_bytes().to_owned(),
Err(msg) => {
needs_conversion = false;
tracing::warn!(
"{}: character decoding failure: {} ({})",
&path.to_string_lossy(),
msg,
&encoding_name
);
}
}
}
} else {
tracing::warn!(
"{}: unknown character encoding: '{}'",
&path.to_string_lossy(),
&encoding_name
);
}
}
}
code_ranges = vec![]; code_ranges = vec![];
} }
extractor::extract( extractor::extract(
@@ -180,7 +236,11 @@ fn main() -> std::io::Result<()> {
&code_ranges, &code_ranges,
)?; )?;
std::fs::create_dir_all(&src_archive_file.parent().unwrap())?; std::fs::create_dir_all(&src_archive_file.parent().unwrap())?;
std::fs::copy(&path, &src_archive_file)?; if needs_conversion {
std::fs::write(&src_archive_file, &source)?;
} else {
std::fs::copy(&path, &src_archive_file)?;
}
write_trap(&trap_dir, path, trap_writer, &trap_compression) write_trap(&trap_dir, path, trap_writer, &trap_compression)
}) })
.expect("failed to extract files"); .expect("failed to extract files");
@@ -299,3 +359,143 @@ fn path_for(dir: &Path, path: &Path, ext: &str) -> PathBuf {
} }
result result
} }
fn skip_space(content: &[u8], index: usize) -> usize {
let mut index = index;
while index < content.len() {
let c = content[index] as char;
// white space except \n
let is_space = c == ' ' || ('\t'..='\r').contains(&c) && c != '\n';
if !is_space {
break;
}
index += 1;
}
index
}
fn scan_coding_comment(content: &[u8]) -> std::option::Option<Cow<str>> {
let mut index = 0;
// skip UTF-8 BOM marker if there is one
if content.len() >= 3 && content[0] == 0xef && content[1] == 0xbb && content[2] == 0xbf {
index += 3;
}
// skip #! line if there is one
if index + 1 < content.len()
&& content[index] as char == '#'
&& content[index + 1] as char == '!'
{
index += 2;
while index < content.len() && content[index] as char != '\n' {
index += 1
}
index += 1
}
index = skip_space(content, index);
if index >= content.len() || content[index] as char != '#' {
return None;
}
index += 1;
const CODING: [char; 12] = ['C', 'c', 'O', 'o', 'D', 'd', 'I', 'i', 'N', 'n', 'G', 'g'];
let mut word_index = 0;
while index < content.len() && word_index < CODING.len() && content[index] as char != '\n' {
if content[index] as char == CODING[word_index]
|| content[index] as char == CODING[word_index + 1]
{
word_index += 2
} else {
word_index = 0;
}
index += 1;
}
if word_index < CODING.len() {
return None;
}
index = skip_space(content, index);
if index < content.len() && content[index] as char != ':' && content[index] as char != '=' {
return None;
}
index += 1;
index = skip_space(content, index);
let start = index;
while index < content.len() {
let c = content[index] as char;
if c == '-' || c == '_' || c.is_ascii_alphanumeric() {
index += 1;
} else {
break;
}
}
if index > start {
return Some(String::from_utf8_lossy(&content[start..index]));
}
None
}
#[test]
fn test_scan_coding_comment() {
let text = "# encoding: utf-8";
let result = scan_coding_comment(text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "#coding:utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "# foo\n# encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, None);
let text = "# encoding: latin1 encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("latin1".into()));
let text = "# encoding: nonsense";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("nonsense".into()));
let text = "# coding = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "# CODING = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "# CoDiNg = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "# blah blahblahcoding = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
// unicode BOM is ignored
let text = "\u{FEFF}# encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "\u{FEFF} # encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "#! /usr/bin/env ruby\n # encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = "\u{FEFF}#! /usr/bin/env ruby\n # encoding: utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
// A #! must be the first thing on a line, otherwise it's a normal comment
let text = " #! /usr/bin/env ruby encoding = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, Some("utf-8".into()));
let text = " #! /usr/bin/env ruby \n # encoding = utf-8";
let result = scan_coding_comment(&text.as_bytes());
assert_eq!(result, None);
}

View File

@@ -12,4 +12,4 @@ node-types = { path = "../node-types" }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" } tree-sitter-embedded-template = { git = "https://github.com/tree-sitter/tree-sitter-embedded-template.git", rev = "1a538da253d73f896b9f6c0c7d79cda58791ac5c" }
tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "5b305c3cd32db10494cedd2743de6bbe32f1a573" } tree-sitter-ruby = { git = "https://github.com/tree-sitter/tree-sitter-ruby.git", rev = "e75d04404c9dd71ad68850d5c672b226d5e694f3" }

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
- Calls to `ActiveRecord::Relation#annotate` are now recognized as`SqlExecution`s so that it will be considered as a sink for queries like rb/sql-injection.

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Calls to `Arel.sql` are now recognised as propagating taint from their argument.

View File

@@ -10,6 +10,7 @@ private import codeql.ruby.frameworks.ActiveStorage
private import codeql.ruby.frameworks.ActionView private import codeql.ruby.frameworks.ActionView
private import codeql.ruby.frameworks.ActiveSupport private import codeql.ruby.frameworks.ActiveSupport
private import codeql.ruby.frameworks.Archive private import codeql.ruby.frameworks.Archive
private import codeql.ruby.frameworks.Arel
private import codeql.ruby.frameworks.GraphQL private import codeql.ruby.frameworks.GraphQL
private import codeql.ruby.frameworks.Rails private import codeql.ruby.frameworks.Rails
private import codeql.ruby.frameworks.Railties private import codeql.ruby.frameworks.Railties

View File

@@ -36,7 +36,7 @@ private import ExprNodes
* constant value in some cases. * constant value in some cases.
*/ */
private module Propagation { private module Propagation {
private ExprCfgNode getSource(VariableReadAccessCfgNode read) { ExprCfgNode getSource(VariableReadAccessCfgNode read) {
exists(Ssa::WriteDefinition def | exists(Ssa::WriteDefinition def |
def.assigns(result) and def.assigns(result) and
read = def.getARead() read = def.getARead()
@@ -509,3 +509,53 @@ private module Cached {
} }
import Cached import Cached
/**
* Holds if the control flow node `e` refers to an array constructed from the
* array literal `arr`.
* Example:
* ```rb
* [1, 2, 3]
* C = [1, 2, 3]; C
* x = [1, 2, 3]; x
* ```
*/
predicate isArrayConstant(ExprCfgNode e, ArrayLiteralCfgNode arr) {
// [...]
e = arr
or
// e = [...]; e
isArrayConstant(getSource(e), arr)
or
isArrayExpr(e.getExpr(), arr)
}
/**
* Holds if the expression `e` refers to an array constructed from the array literal `arr`.
*/
private predicate isArrayExpr(Expr e, ArrayLiteralCfgNode arr) {
// e = [...]
e = arr.getExpr()
or
// Like above, but handles the desugaring of array literals to Array.[] calls.
e.getDesugared() = arr.getExpr()
or
// A = [...]; A
// A = a; A
isArrayExpr(e.(ConstantReadAccess).getValue(), arr)
or
// Recurse via CFG nodes. Necessary for example in:
// a = [...]
// A = a
// A
//
// We map from A to a via ConstantReadAccess::getValue, yielding the Expr a.
// To get to [...] we need to go via getSource(ExprCfgNode e), so we find a
// CFG node for a and call `isArrayConstant`.
//
// The use of `forex` is intended to ensure that a is an array constant in all
// control flow paths.
// Note(hmac): I don't think this is necessary, as `getSource` will not return
// results if the source is a phi node.
forex(ExprCfgNode n | n = e.getAControlFlowNode() | isArrayConstant(n, arr))
}

View File

@@ -374,6 +374,9 @@ module ExprNodes {
MethodCallCfgNode() { super.getExpr() instanceof MethodCall } MethodCallCfgNode() { super.getExpr() instanceof MethodCall }
override MethodCall getExpr() { result = super.getExpr() } override MethodCall getExpr() { result = super.getExpr() }
/** Gets the name of this method call. */
string getMethodName() { result = this.getExpr().getMethodName() }
} }
private class CaseExprChildMapping extends ExprChildMapping, CaseExpr { private class CaseExprChildMapping extends ExprChildMapping, CaseExpr {

View File

@@ -3,6 +3,10 @@
private import ruby private import ruby
private import codeql.ruby.DataFlow private import codeql.ruby.DataFlow
private import codeql.ruby.CFG private import codeql.ruby.CFG
private import codeql.ruby.controlflow.CfgNodes
private import codeql.ruby.dataflow.SSA
private import codeql.ruby.ast.internal.Constant
private import codeql.ruby.InclusionTests
private predicate stringConstCompare(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) { private predicate stringConstCompare(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
exists(CfgNodes::ExprNodes::ComparisonOperationCfgNode c | exists(CfgNodes::ExprNodes::ComparisonOperationCfgNode c |
@@ -61,19 +65,7 @@ deprecated class StringConstCompare extends DataFlow::BarrierGuard,
// The value of the condition that results in the node being validated. // The value of the condition that results in the node being validated.
private boolean checkedBranch; private boolean checkedBranch;
StringConstCompare() { StringConstCompare() { stringConstCompare(this, checkedNode, checkedBranch) }
exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode |
this.getExpr() instanceof EqExpr and checkedBranch = true
or
this.getExpr() instanceof CaseEqExpr and checkedBranch = true
or
this.getExpr() instanceof NEExpr and checkedBranch = false
|
this.getLeftOperand() = strLitNode and this.getRightOperand() = checkedNode
or
this.getLeftOperand() = checkedNode and this.getRightOperand() = strLitNode
)
}
override predicate checks(CfgNode expr, boolean branch) { override predicate checks(CfgNode expr, boolean branch) {
expr = checkedNode and branch = checkedBranch expr = checkedNode and branch = checkedBranch
@@ -81,15 +73,19 @@ deprecated class StringConstCompare extends DataFlow::BarrierGuard,
} }
private predicate stringConstArrayInclusionCall(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) { private predicate stringConstArrayInclusionCall(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) {
exists(CfgNodes::ExprNodes::MethodCallCfgNode mc, ArrayLiteral aLit | exists(InclusionTest t |
mc = g and t.asExpr() = g and
mc.getExpr().getMethodName() = "include?" and e = t.getContainedNode().asExpr() and
[mc.getExpr().getReceiver(), mc.getExpr().getReceiver().(ConstantReadAccess).getValue()] = aLit branch = t.getPolarity()
| |
forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and exists(ExprNodes::ArrayLiteralCfgNode arr |
mc.getArgument(0) = e isArrayConstant(t.getContainerNode().asExpr(), arr)
) and |
branch = true forall(ExprCfgNode elem | elem = arr.getAnArgument() |
elem instanceof ExprNodes::StringLiteralCfgNode
)
)
)
} }
/** /**
@@ -132,16 +128,7 @@ deprecated class StringConstArrayInclusionCall extends DataFlow::BarrierGuard,
CfgNodes::ExprNodes::MethodCallCfgNode { CfgNodes::ExprNodes::MethodCallCfgNode {
private CfgNode checkedNode; private CfgNode checkedNode;
StringConstArrayInclusionCall() { StringConstArrayInclusionCall() { stringConstArrayInclusionCall(this, checkedNode, true) }
exists(ArrayLiteral aLit |
this.getExpr().getMethodName() = "include?" and
[this.getExpr().getReceiver(), this.getExpr().getReceiver().(ConstantReadAccess).getValue()] =
aLit
|
forall(Expr elem | elem = aLit.getAnElement() | elem instanceof StringLiteral) and
this.getArgument(0) = checkedNode
)
}
override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true } override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true }
} }

View File

@@ -133,6 +133,11 @@ private Expr sqlFragmentArgument(MethodCall call) {
or or
methodName = "reload" and methodName = "reload" and
result = call.getKeywordArgument("lock") result = call.getKeywordArgument("lock")
or
// Calls to `annotate` can be used to add block comments to SQL queries. These are potentially vulnerable to
// SQLi if user supplied input is passed in as an argument.
methodName = "annotate" and
result = call.getArgument(_)
) )
) )
} }

View File

@@ -0,0 +1,31 @@
/**
* Provides modeling for Arel, a low level SQL library that powers ActiveRecord.
* Version: 7.0.3
* https://api.rubyonrails.org/classes/Arel.html
*/
private import codeql.ruby.ApiGraphs
private import codeql.ruby.dataflow.FlowSummary
/**
* Provides modeling for Arel, a low level SQL library that powers ActiveRecord.
* Version: 7.0.3
* https://api.rubyonrails.org/classes/Arel.html
*/
module Arel {
/**
* Flow summary for `Arel.sql`. This method wraps a SQL string, marking it as
* safe.
*/
private class SqlSummary extends SummarizedCallable {
SqlSummary() { this = "Arel.sql" }
override MethodCall getACall() {
result = API::getTopLevelMember("Arel").getAMethodCall("sql").asExpr().getExpr()
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
input = "Argument[0]" and output = "ReturnValue" and preservesValue = false
}
}
}

View File

@@ -4,3 +4,4 @@
import stdlib.Open3 import stdlib.Open3
import stdlib.Logger import stdlib.Logger
import stdlib.Pathname

View File

@@ -0,0 +1,188 @@
/** Modeling of the `Pathname` class from the Ruby standard library. */
private import codeql.ruby.AST
private import codeql.ruby.ApiGraphs
private import codeql.ruby.Concepts
private import codeql.ruby.DataFlow
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowDispatch
private import codeql.ruby.frameworks.data.ModelsAsData
/**
* Modeling of the `Pathname` class from the Ruby standard library.
*
* https://docs.ruby-lang.org/en/3.1/Pathname.html
*/
module Pathname {
/**
* An instance of the `Pathname` class. For example, in
*
* ```rb
* pn = Pathname.new "foo.txt'"
* puts pn.read
* ```
*
* there are three `PathnameInstance`s - the call to `Pathname.new`, the
* assignment `pn = ...`, and the read access to `pn` on the second line.
*
* Every `PathnameInstance` is considered to be a `FileNameSource`.
*/
class PathnameInstance extends FileNameSource, DataFlow::Node {
PathnameInstance() { this = pathnameInstance() }
}
private DataFlow::Node pathnameInstance() {
// A call to `Pathname.new`.
result = API::getTopLevelMember("Pathname").getAnInstantiation()
or
// Class methods on `Pathname` that return a new `Pathname`.
result = API::getTopLevelMember("Pathname").getAMethodCall(["getwd", "pwd",])
or
// Instance methods on `Pathname` that return a new `Pathname`.
exists(DataFlow::CallNode c | result = c |
c.getReceiver() = pathnameInstance() and
c.getMethodName() =
[
"+", "/", "basename", "cleanpath", "expand_path", "join", "realpath",
"relative_path_from", "sub", "sub_ext", "to_path"
]
)
or
exists(DataFlow::Node inst |
inst = pathnameInstance() and
inst.(DataFlow::LocalSourceNode).flowsTo(result)
)
}
/** A call where the receiver is a `Pathname`. */
class PathnameCall extends DataFlow::CallNode {
PathnameCall() { this.getReceiver() instanceof PathnameInstance }
}
/**
* A call to `Pathname#open` or `Pathname#opendir`, considered as a
* `FileSystemAccess`.
*/
class PathnameOpen extends FileSystemAccess::Range, PathnameCall {
PathnameOpen() { this.getMethodName() = ["open", "opendir"] }
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
}
/** A call to `Pathname#read`, considered as a `FileSystemReadAccess`. */
class PathnameRead extends FileSystemReadAccess::Range, PathnameCall {
PathnameRead() { this.getMethodName() = "read" }
// The path is the receiver (the `Pathname` object).
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
// The read data is the return value of the call.
override DataFlow::Node getADataNode() { result = this }
}
/** A call to `Pathname#write`, considered as a `FileSystemWriteAccess`. */
class PathnameWrite extends FileSystemWriteAccess::Range, PathnameCall {
PathnameWrite() { this.getMethodName() = "write" }
// The path is the receiver (the `Pathname` object).
override DataFlow::Node getAPathArgument() { result = this.getReceiver() }
// The data to write is the 0th argument.
override DataFlow::Node getADataNode() { result = this.getArgument(0) }
}
/** A call to `Pathname#to_s`, considered as a `FileNameSource`. */
class PathnameToSFilenameSource extends FileNameSource, PathnameCall {
PathnameToSFilenameSource() { this.getMethodName() = "to_s" }
}
private class PathnamePermissionModification extends FileSystemPermissionModification::Range,
PathnameCall {
private DataFlow::Node permissionArg;
PathnamePermissionModification() {
exists(string methodName | this.getMethodName() = methodName |
methodName = ["chmod", "mkdir"] and permissionArg = this.getArgument(0)
or
methodName = "mkpath" and permissionArg = this.getKeywordArgument("mode")
or
methodName = "open" and permissionArg = this.getArgument(1)
// TODO: defaults for optional args? This may depend on the umask
)
}
override DataFlow::Node getAPermissionNode() { result = permissionArg }
}
/**
* Type summaries for the `Pathname` class, i.e. method calls that produce new
* `Pathname` instances.
*/
private class PathnameTypeSummary extends ModelInput::TypeModelCsv {
override predicate row(string row) {
// package1;type1;package2;type2;path
row =
[
// Pathname.new : Pathname
";Pathname;;;Member[Pathname].Instance",
// Pathname#+(path) : Pathname
";Pathname;;Pathname;Method[+].ReturnValue",
// Pathname#/(path) : Pathname
";Pathname;;Pathname;Method[/].ReturnValue",
// Pathname#basename(path) : Pathname
";Pathname;;Pathname;Method[basename].ReturnValue",
// Pathname#cleanpath(path) : Pathname
";Pathname;;Pathname;Method[cleanpath].ReturnValue",
// Pathname#expand_path(path) : Pathname
";Pathname;;Pathname;Method[expand_path].ReturnValue",
// Pathname#join(path) : Pathname
";Pathname;;Pathname;Method[join].ReturnValue",
// Pathname#realpath(path) : Pathname
";Pathname;;Pathname;Method[realpath].ReturnValue",
// Pathname#relative_path_from(path) : Pathname
";Pathname;;Pathname;Method[relative_path_from].ReturnValue",
// Pathname#sub(path) : Pathname
";Pathname;;Pathname;Method[sub].ReturnValue",
// Pathname#sub_ext(path) : Pathname
";Pathname;;Pathname;Method[sub_ext].ReturnValue",
// Pathname#to_path(path) : Pathname
";Pathname;;Pathname;Method[to_path].ReturnValue",
]
}
}
/** Taint flow summaries for the `Pathname` class. */
private class PathnameTaintSummary extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
// Pathname.new(path)
";;Member[Pathname].Method[new];Argument[0];ReturnValue;taint",
// Pathname#dirname
";Pathname;Method[dirname];Argument[self];ReturnValue;taint",
// Pathname#each_filename
";Pathname;Method[each_filename];Argument[self];Argument[block].Parameter[0];taint",
// Pathname#expand_path
";Pathname;Method[expand_path];Argument[self];ReturnValue;taint",
// Pathname#join
";Pathname;Method[join];Argument[self,any];ReturnValue;taint",
// Pathname#parent
";Pathname;Method[parent];Argument[self];ReturnValue;taint",
// Pathname#realpath
";Pathname;Method[realpath];Argument[self];ReturnValue;taint",
// Pathname#relative_path_from
";Pathname;Method[relative_path_from];Argument[self];ReturnValue;taint",
// Pathname#to_path
";Pathname;Method[to_path];Argument[self];ReturnValue;taint",
// Pathname#basename
";Pathname;Method[basename];Argument[self];ReturnValue;taint",
// Pathname#cleanpath
";Pathname;Method[cleanpath];Argument[self];ReturnValue;taint",
// Pathname#sub
";Pathname;Method[sub];Argument[self];ReturnValue;taint",
// Pathname#sub_ext
";Pathname;Method[sub_ext];Argument[self];ReturnValue;taint",
]
}
}
}

View File

@@ -1,5 +1,5 @@
name: codeql/ruby-all name: codeql/ruby-all
version: 0.3.1 version: 0.3.2-dev
groups: ruby groups: ruby
extractor: ruby extractor: ruby
dbscheme: ruby.dbscheme dbscheme: ruby.dbscheme

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* Added a new experimental query, `rb/manually-checking-http-verb`, to detect cases when the HTTP verb for an incoming request is checked and then used as part of control flow.

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* Added a new experimental query, `rb/weak-params`, to detect cases when the rails strong parameters pattern isn't followed and values flow into persistent store writes.

View File

@@ -0,0 +1,23 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Manually checking the HTTP request verb inside of a controller method can lead to
CSRF bypass if GET or HEAD requests are handled improperly.
</p>
</overview>
<recommendation>
<p>
It is better to use different controller methods for each resource/http verb combination
and configure the Rails routes in your application to call them accordingly.
</p>
</recommendation>
<references>
<li>
See https://guides.rubyonrails.org/routing.html for more information.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,96 @@
/**
* @name Manually checking http verb instead of using built in rails routes and protections
* @description Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods.
* @kind path-problem
* @problem.severity error
* @security-severity 5.0
* @precision low
* @id rb/manually-checking-http-verb
* @tags security
*/
import ruby
import codeql.ruby.DataFlow
import codeql.ruby.controlflow.CfgNodes
import codeql.ruby.frameworks.ActionController
import codeql.ruby.TaintTracking
import DataFlow::PathGraph
// any `request` calls in an action method
class Request extends DataFlow::CallNode {
Request() {
this.getMethodName() = "request" and
this.asExpr().getExpr().getEnclosingMethod() instanceof ActionControllerActionMethod
}
}
// `request.env`
class RequestEnvMethod extends DataFlow::CallNode {
RequestEnvMethod() {
this.getMethodName() = "env" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.request_method`
class RequestRequestMethod extends DataFlow::CallNode {
RequestRequestMethod() {
this.getMethodName() = "request_method" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.method`
class RequestMethod extends DataFlow::CallNode {
RequestMethod() {
this.getMethodName() = "method" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.raw_request_method`
class RequestRawRequestMethod extends DataFlow::CallNode {
RequestRawRequestMethod() {
this.getMethodName() = "raw_request_method" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.request_method_symbol`
class RequestRequestMethodSymbol extends DataFlow::CallNode {
RequestRequestMethodSymbol() {
this.getMethodName() = "request_method_symbol" and
any(Request r).flowsTo(this.getReceiver())
}
}
// `request.get?`
class RequestGet extends DataFlow::CallNode {
RequestGet() {
this.getMethodName() = "get?" and
any(Request r).flowsTo(this.getReceiver())
}
}
class HttpVerbConfig extends TaintTracking::Configuration {
HttpVerbConfig() { this = "HttpVerbConfig" }
override predicate isSource(DataFlow::Node source) {
source instanceof RequestMethod or
source instanceof RequestRequestMethod or
source instanceof RequestEnvMethod or
source instanceof RequestRawRequestMethod or
source instanceof RequestRequestMethodSymbol or
source instanceof RequestGet
}
override predicate isSink(DataFlow::Node sink) {
exists(ExprNodes::ConditionalExprCfgNode c | c.getCondition() = sink.asExpr()) or
exists(ExprNodes::CaseExprCfgNode c | c.getValue() = sink.asExpr())
}
}
from HttpVerbConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"Manually checking HTTP verbs is an indication that multiple requests are routed to the same controller action. This could lead to bypassing necessary authorization methods and other protections, like CSRF protection. Prefer using different controller actions for each HTTP method and relying Rails routing to handle mappting resources and verbs to specific methods."

View File

@@ -0,0 +1,28 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Directly checking request parameters without following a strong params
pattern can lead to unintentional avenues for injection attacks.
</p>
</overview>
<recommendation>
<p>
Instead of manually checking parameters from the `param` object, it is
recommended that you follow the strong parameters pattern established in
Rails: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html
</p>
<p>
In the strong parameters pattern, you are able to specify required and allowed
parameters for each action called by your controller methods. This acts as an
additional layer of data validation before being passed along to other areas
of your application, such as the model.
</p>
</recommendation>
<references>
</references>
</qhelp>

View File

@@ -0,0 +1,61 @@
/**
* @name Weak or direct parameter references are used
* @description Directly checking request parameters without following a strong params pattern can lead to unintentional avenues for injection attacks.
* @kind path-problem
* @problem.severity error
* @security-severity 5.0
* @precision medium
* @id rb/weak-params
* @tags security
*/
import ruby
import codeql.ruby.Concepts
import codeql.ruby.DataFlow
import codeql.ruby.TaintTracking
import codeql.ruby.frameworks.ActionController
import DataFlow::PathGraph
/**
* A call to `request` in an ActionController controller class.
* This probably refers to the incoming HTTP request object.
*/
class ActionControllerRequest extends DataFlow::Node {
ActionControllerRequest() {
exists(DataFlow::CallNode c |
c.asExpr().getExpr().getEnclosingModule() instanceof ActionControllerControllerClass and
c.getMethodName() = "request"
|
c.flowsTo(this)
)
}
}
/**
* A direct parameters reference that happens inside a controller class.
*/
class WeakParams extends DataFlow::CallNode {
WeakParams() {
this.getReceiver() instanceof ActionControllerRequest and
this.getMethodName() =
["path_parameters", "query_parameters", "request_parameters", "GET", "POST"]
}
}
/**
* A Taint tracking config where the source is a weak params access in a controller and the sink
* is a method call of a model class
*/
class Configuration extends TaintTracking::Configuration {
Configuration() { this = "WeakParamsConfiguration" }
override predicate isSource(DataFlow::Node node) { node instanceof WeakParams }
// the sink is an instance of a Model class that receives a method call
override predicate isSink(DataFlow::Node node) { node = any(PersistentWriteAccess a).getValue() }
}
from Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink,
"By exposing all keys in request parameters or by blindy accessing them, unintended parameters could be used and lead to mass-assignment or have other unexpected side-effects. It is safer to follow the 'strong parameters' pattern in Rails, which is outlined here: https://api.rubyonrails.org/classes/ActionController/StrongParameters.html"

View File

@@ -1,5 +1,5 @@
name: codeql/ruby-queries name: codeql/ruby-queries
version: 0.3.0 version: 0.3.1-dev
groups: groups:
- ruby - ruby
- queries - queries

View File

@@ -1556,6 +1556,35 @@ constants/constants.rb:
# 73| getAnOperand/getLeftOperand: [ClassVariableAccess] @@fourty_six # 73| getAnOperand/getLeftOperand: [ClassVariableAccess] @@fourty_six
# 73| getAnOperand/getRightOperand: [ConstantReadAccess] FOURTY_SIX # 73| getAnOperand/getRightOperand: [ConstantReadAccess] FOURTY_SIX
# 73| getScopeExpr: [ConstantReadAccess] Mod3 # 73| getScopeExpr: [ConstantReadAccess] Mod3
# 78| getStmt: [AssignExpr] ... = ...
# 78| getAnOperand/getLeftOperand: [LocalVariableAccess] a
# 78| getAnOperand/getRightOperand: [ArrayLiteral] [...]
# 78| getElement: [IntegerLiteral] 1
# 78| getElement: [IntegerLiteral] 2
# 78| getElement: [IntegerLiteral] 3
# 79| getStmt: [AssignExpr] ... = ...
# 79| getAnOperand/getLeftOperand: [ConstantAssignment] A
# 79| getAnOperand/getRightOperand: [ArrayLiteral] [...]
# 79| getElement: [IntegerLiteral] 1
# 79| getElement: [IntegerLiteral] 2
# 79| getElement: [IntegerLiteral] 3
# 80| getStmt: [AssignExpr] ... = ...
# 80| getAnOperand/getLeftOperand: [ConstantAssignment] B
# 80| getAnOperand/getRightOperand: [LocalVariableAccess] a
# 81| getStmt: [AssignExpr] ... = ...
# 81| getAnOperand/getLeftOperand: [ConstantAssignment] C
# 81| getAnOperand/getRightOperand: [ConstantReadAccess] A
# 82| getStmt: [AssignExpr] ... = ...
# 82| getAnOperand/getLeftOperand: [LocalVariableAccess] b
# 82| getAnOperand/getRightOperand: [ConstantReadAccess] B
# 84| getStmt: [IfExpr] if ...
# 84| getCondition: [MethodCall] call to condition
# 84| getReceiver: [SelfVariableAccess] self
# 84| getBranch/getThen: [StmtSequence] then ...
# 85| getStmt: [AssignExpr] ... = ...
# 85| getAnOperand/getLeftOperand: [LocalVariableAccess] c
# 85| getAnOperand/getRightOperand: [LocalVariableAccess] b
# 87| getStmt: [LocalVariableAccess] c
escape_sequences/escapes.rb: escape_sequences/escapes.rb:
# 1| [Toplevel] escapes.rb # 1| [Toplevel] escapes.rb
# 6| getStmt: [StringLiteral] "\'" # 6| getStmt: [StringLiteral] "\'"
@@ -1733,6 +1762,12 @@ escape_sequences/escapes.rb:
# 93| getStmt: [SymbolLiteral] :"\C-?" # 93| getStmt: [SymbolLiteral] :"\C-?"
# 93| getComponent: [StringEscapeSequenceComponent] \C # 93| getComponent: [StringEscapeSequenceComponent] \C
# 93| getComponent: [StringTextComponent] -? # 93| getComponent: [StringTextComponent] -?
misc/iso-8859-15.rb:
# 1| [Toplevel] iso-8859-15.rb
# 4| getStmt: [MethodCall] call to print
# 4| getReceiver: [SelfVariableAccess] self
# 4| getArgument: [StringLiteral] "EUR = €"
# 4| getComponent: [StringTextComponent] EUR = €
literals/literals.rb: literals/literals.rb:
# 1| [Toplevel] literals.rb # 1| [Toplevel] literals.rb
# 2| getStmt: [NilLiteral] nil # 2| getStmt: [NilLiteral] nil

View File

@@ -336,6 +336,18 @@ constants/constants.rb:
# 20| getComponent: [StringTextComponent] Chuck # 20| getComponent: [StringTextComponent] Chuck
# 20| getArgument: [StringLiteral] "Dave" # 20| getArgument: [StringLiteral] "Dave"
# 20| getComponent: [StringTextComponent] Dave # 20| getComponent: [StringTextComponent] Dave
# 78| [ArrayLiteral] [...]
# 78| getDesugared: [MethodCall] call to []
# 78| getReceiver: [ConstantReadAccess] Array
# 78| getArgument: [IntegerLiteral] 1
# 78| getArgument: [IntegerLiteral] 2
# 78| getArgument: [IntegerLiteral] 3
# 79| [ArrayLiteral] [...]
# 79| getDesugared: [MethodCall] call to []
# 79| getReceiver: [ConstantReadAccess] Array
# 79| getArgument: [IntegerLiteral] 1
# 79| getArgument: [IntegerLiteral] 2
# 79| getArgument: [IntegerLiteral] 3
escape_sequences/escapes.rb: escape_sequences/escapes.rb:
# 58| [ArrayLiteral] %w(...) # 58| [ArrayLiteral] %w(...)
# 58| getDesugared: [MethodCall] call to [] # 58| getDesugared: [MethodCall] call to []

View File

@@ -1656,10 +1656,56 @@ constants/constants.rb:
# 73| 1: [ReservedWord] :: # 73| 1: [ReservedWord] ::
# 73| 2: [Constant] FOURTY_SIX # 73| 2: [Constant] FOURTY_SIX
# 74| 5: [ReservedWord] end # 74| 5: [ReservedWord] end
# 78| 13: [Assignment] Assignment
# 78| 0: [Identifier] a
# 78| 1: [ReservedWord] =
# 78| 2: [Array] Array
# 78| 0: [ReservedWord] [
# 78| 1: [Integer] 1
# 78| 2: [ReservedWord] ,
# 78| 3: [Integer] 2
# 78| 4: [ReservedWord] ,
# 78| 5: [Integer] 3
# 78| 6: [ReservedWord] ]
# 79| 14: [Assignment] Assignment
# 79| 0: [Constant] A
# 79| 1: [ReservedWord] =
# 79| 2: [Array] Array
# 79| 0: [ReservedWord] [
# 79| 1: [Integer] 1
# 79| 2: [ReservedWord] ,
# 79| 3: [Integer] 2
# 79| 4: [ReservedWord] ,
# 79| 5: [Integer] 3
# 79| 6: [ReservedWord] ]
# 80| 15: [Assignment] Assignment
# 80| 0: [Constant] B
# 80| 1: [ReservedWord] =
# 80| 2: [Identifier] a
# 81| 16: [Assignment] Assignment
# 81| 0: [Constant] C
# 81| 1: [ReservedWord] =
# 81| 2: [Constant] A
# 82| 17: [Assignment] Assignment
# 82| 0: [Identifier] b
# 82| 1: [ReservedWord] =
# 82| 2: [Constant] B
# 84| 18: [If] If
# 84| 0: [ReservedWord] if
# 84| 1: [Identifier] condition
# 84| 2: [Then] Then
# 85| 0: [Assignment] Assignment
# 85| 0: [Identifier] c
# 85| 1: [ReservedWord] =
# 85| 2: [Identifier] b
# 86| 3: [ReservedWord] end
# 87| 19: [Identifier] c
# 26| [Comment] # A call to Kernel::Array; despite beginning with an upper-case character, # 26| [Comment] # A call to Kernel::Array; despite beginning with an upper-case character,
# 27| [Comment] # we don't consider this to be a constant access. # 27| [Comment] # we don't consider this to be a constant access.
# 55| [Comment] # refers to ::ModuleA::FOURTY_FOUR # 55| [Comment] # refers to ::ModuleA::FOURTY_FOUR
# 57| [Comment] # refers to ::ModuleA::ModuleB::ClassB::FOURTY_FOUR # 57| [Comment] # refers to ::ModuleA::ModuleB::ClassB::FOURTY_FOUR
# 76| [Comment] # Array constants
# 87| [Comment] # not recognised
control/cases.rb: control/cases.rb:
# 1| [Program] Program # 1| [Program] Program
# 2| 0: [Assignment] Assignment # 2| 0: [Assignment] Assignment
@@ -4558,6 +4604,17 @@ literals/literals.rb:
# 193| cat file.txt # 193| cat file.txt
# 193| # 193|
# 195| 1: [HeredocEnd] SCRIPT # 195| 1: [HeredocEnd] SCRIPT
misc/iso-8859-15.rb:
# 1| [Program] Program
# 4| 0: [Call] Call
# 4| 0: [Identifier] print
# 4| 1: [ArgumentList] ArgumentList
# 4| 0: [String] String
# 4| 0: [ReservedWord] "
# 4| 1: [StringContent] EUR = €
# 4| 2: [ReservedWord] "
# 1| [Comment] #! /usr/bin/ruby
# 2| [Comment] # coding: iso-8859-15
misc/misc.erb: misc/misc.erb:
# 2| [Program] Program # 2| [Program] Program
# 2| 0: [Call] Call # 2| 0: [Call] Call

View File

@@ -109,6 +109,12 @@ exprValue
| constants/constants.rb:63:19:63:20 | 45 | 45 | int | | constants/constants.rb:63:19:63:20 | 45 | 45 | int |
| constants/constants.rb:65:19:65:35 | FOURTY_FIVE | 45 | int | | constants/constants.rb:65:19:65:35 | FOURTY_FIVE | 45 | int |
| constants/constants.rb:71:18:71:19 | 46 | 46 | int | | constants/constants.rb:71:18:71:19 | 46 | 46 | int |
| constants/constants.rb:78:6:78:6 | 1 | 1 | int |
| constants/constants.rb:78:9:78:9 | 2 | 2 | int |
| constants/constants.rb:78:12:78:12 | 3 | 3 | int |
| constants/constants.rb:79:6:79:6 | 1 | 1 | int |
| constants/constants.rb:79:9:79:9 | 2 | 2 | int |
| constants/constants.rb:79:12:79:12 | 3 | 3 | int |
| control/cases.rb:2:5:2:5 | 0 | 0 | int | | control/cases.rb:2:5:2:5 | 0 | 0 | int |
| control/cases.rb:3:5:3:5 | 0 | 0 | int | | control/cases.rb:3:5:3:5 | 0 | 0 | int |
| control/cases.rb:4:5:4:5 | 0 | 0 | int | | control/cases.rb:4:5:4:5 | 0 | 0 | int |
@@ -711,6 +717,7 @@ exprValue
| literals/literals.rb:198:8:198:8 | 5 | 5 | int | | literals/literals.rb:198:8:198:8 | 5 | 5 | int |
| literals/literals.rb:199:2:199:2 | :y | :y | symbol | | literals/literals.rb:199:2:199:2 | :y | :y | symbol |
| literals/literals.rb:199:7:199:7 | :Z | :Z | symbol | | literals/literals.rb:199:7:199:7 | :Z | :Z | symbol |
| misc/iso-8859-15.rb:4:7:4:17 | "EUR = \u20ac" | EUR = \u20ac | string |
| misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js | string | | misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js | string |
| misc/misc.rb:1:7:1:11 | "bar" | bar | string | | misc/misc.rb:1:7:1:11 | "bar" | bar | string |
| misc/misc.rb:3:7:3:9 | foo | foo | string | | misc/misc.rb:3:7:3:9 | foo | foo | string |
@@ -1004,6 +1011,12 @@ exprCfgNodeValue
| constants/constants.rb:63:19:63:20 | 45 | 45 | int | | constants/constants.rb:63:19:63:20 | 45 | 45 | int |
| constants/constants.rb:65:19:65:35 | FOURTY_FIVE | 45 | int | | constants/constants.rb:65:19:65:35 | FOURTY_FIVE | 45 | int |
| constants/constants.rb:71:18:71:19 | 46 | 46 | int | | constants/constants.rb:71:18:71:19 | 46 | 46 | int |
| constants/constants.rb:78:6:78:6 | 1 | 1 | int |
| constants/constants.rb:78:9:78:9 | 2 | 2 | int |
| constants/constants.rb:78:12:78:12 | 3 | 3 | int |
| constants/constants.rb:79:6:79:6 | 1 | 1 | int |
| constants/constants.rb:79:9:79:9 | 2 | 2 | int |
| constants/constants.rb:79:12:79:12 | 3 | 3 | int |
| control/cases.rb:2:5:2:5 | 0 | 0 | int | | control/cases.rb:2:5:2:5 | 0 | 0 | int |
| control/cases.rb:3:5:3:5 | 0 | 0 | int | | control/cases.rb:3:5:3:5 | 0 | 0 | int |
| control/cases.rb:4:5:4:5 | 0 | 0 | int | | control/cases.rb:4:5:4:5 | 0 | 0 | int |
@@ -1580,6 +1593,7 @@ exprCfgNodeValue
| literals/literals.rb:198:8:198:8 | 5 | 5 | int | | literals/literals.rb:198:8:198:8 | 5 | 5 | int |
| literals/literals.rb:199:2:199:2 | :y | :y | symbol | | literals/literals.rb:199:2:199:2 | :y | :y | symbol |
| literals/literals.rb:199:7:199:7 | :Z | :Z | symbol | | literals/literals.rb:199:7:199:7 | :Z | :Z | symbol |
| misc/iso-8859-15.rb:4:7:4:17 | "EUR = \u20ac" | EUR = \u20ac | string |
| misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js | string | | misc/misc.erb:2:15:2:37 | "main_include_admin.js" | main_include_admin.js | string |
| misc/misc.rb:1:7:1:11 | "bar" | bar | string | | misc/misc.rb:1:7:1:11 | "bar" | bar | string |
| misc/misc.rb:3:7:3:9 | foo | foo | string | | misc/misc.rb:3:7:3:9 | foo | foo | string |

View File

@@ -61,6 +61,13 @@ constantAccess
| constants.rb:71:5:71:14 | FOURTY_SIX | write | FOURTY_SIX | ConstantAssignment | | constants.rb:71:5:71:14 | FOURTY_SIX | write | FOURTY_SIX | ConstantAssignment |
| constants.rb:73:18:73:21 | Mod3 | read | Mod3 | ConstantReadAccess | | constants.rb:73:18:73:21 | Mod3 | read | Mod3 | ConstantReadAccess |
| constants.rb:73:18:73:33 | FOURTY_SIX | read | FOURTY_SIX | ConstantReadAccess | | constants.rb:73:18:73:33 | FOURTY_SIX | read | FOURTY_SIX | ConstantReadAccess |
| constants.rb:78:5:78:13 | Array | read | Array | ConstantReadAccess |
| constants.rb:79:1:79:1 | A | write | A | ConstantAssignment |
| constants.rb:79:5:79:13 | Array | read | Array | ConstantReadAccess |
| constants.rb:80:1:80:1 | B | write | B | ConstantAssignment |
| constants.rb:81:1:81:1 | C | write | C | ConstantAssignment |
| constants.rb:81:5:81:5 | A | read | A | ConstantReadAccess |
| constants.rb:82:5:82:5 | B | read | B | ConstantReadAccess |
getConst getConst
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" | | constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
| constants.rb:1:1:15:3 | ModuleA | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" | | constants.rb:1:1:15:3 | ModuleA | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" |
@@ -71,23 +78,41 @@ getConst
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_ONE | constants.rb:48:18:48:19 | 41 | | constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_ONE | constants.rb:48:18:48:19 | 41 |
| constants.rb:62:3:64:5 | Mod1::Mod3 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 | | constants.rb:62:3:64:5 | Mod1::Mod3 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 |
| constants.rb:70:3:72:5 | Mod1::Mod3::Mod5 | FOURTY_SIX | constants.rb:71:18:71:19 | 46 | | constants.rb:70:3:72:5 | Mod1::Mod3::Mod5 | FOURTY_SIX | constants.rb:71:18:71:19 | 46 |
| file://:0:0:0:0 | Object | A | constants.rb:79:5:79:13 | [...] |
| file://:0:0:0:0 | Object | B | constants.rb:80:5:80:5 | a |
| file://:0:0:0:0 | Object | C | constants.rb:81:5:81:5 | A |
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... | | file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
lookupConst lookupConst
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" | | constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
| constants.rb:1:1:15:3 | ModuleA | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" | | constants.rb:1:1:15:3 | ModuleA | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" |
| constants.rb:2:5:4:7 | ModuleA::ClassA | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:2:5:4:7 | ModuleA::ClassA | B | constants.rb:80:5:80:5 | a |
| constants.rb:2:5:4:7 | ModuleA::ClassA | C | constants.rb:81:5:81:5 | A |
| constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" | | constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" |
| constants.rb:2:5:4:7 | ModuleA::ClassA | GREETING | constants.rb:17:12:17:64 | ... + ... | | constants.rb:2:5:4:7 | ModuleA::ClassA | GREETING | constants.rb:17:12:17:64 | ... + ... |
| constants.rb:8:5:14:7 | ModuleA::ModuleB | MAX_SIZE | constants.rb:39:30:39:33 | 1024 | | constants.rb:8:5:14:7 | ModuleA::ModuleB | MAX_SIZE | constants.rb:39:30:39:33 | 1024 |
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | B | constants.rb:80:5:80:5 | a |
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | C | constants.rb:81:5:81:5 | A |
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | GREETING | constants.rb:17:12:17:64 | ... + ... | | constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | GREETING | constants.rb:17:12:17:64 | ... + ... |
| constants.rb:31:1:33:3 | ModuleA::ClassD | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:31:1:33:3 | ModuleA::ClassD | B | constants.rb:80:5:80:5 | a |
| constants.rb:31:1:33:3 | ModuleA::ClassD | C | constants.rb:81:5:81:5 | A |
| constants.rb:31:1:33:3 | ModuleA::ClassD | CONST_A | constants.rb:3:19:3:27 | "const_a" | | constants.rb:31:1:33:3 | ModuleA::ClassD | CONST_A | constants.rb:3:19:3:27 | "const_a" |
| constants.rb:31:1:33:3 | ModuleA::ClassD | FOURTY_TWO | constants.rb:32:16:32:17 | 42 | | constants.rb:31:1:33:3 | ModuleA::ClassD | FOURTY_TWO | constants.rb:32:16:32:17 | 42 |
| constants.rb:31:1:33:3 | ModuleA::ClassD | GREETING | constants.rb:17:12:17:64 | ... + ... | | constants.rb:31:1:33:3 | ModuleA::ClassD | GREETING | constants.rb:17:12:17:64 | ... + ... |
| constants.rb:35:1:37:3 | ModuleA::ModuleC | FOURTY_THREE | constants.rb:36:18:36:19 | 43 | | constants.rb:35:1:37:3 | ModuleA::ModuleC | FOURTY_THREE | constants.rb:36:18:36:19 | 43 |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | B | constants.rb:80:5:80:5 | a |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | C | constants.rb:81:5:81:5 | A |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_FOUR | constants.rb:56:19:56:20 | 44 | | constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_FOUR | constants.rb:56:19:56:20 | 44 |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_ONE | constants.rb:48:18:48:19 | 41 | | constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | FOURTY_ONE | constants.rb:48:18:48:19 | 41 |
| constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | GREETING | constants.rb:17:12:17:64 | ... + ... | | constants.rb:54:3:58:5 | ModuleA::ModuleB::ClassB | GREETING | constants.rb:17:12:17:64 | ... + ... |
| constants.rb:62:3:64:5 | Mod1::Mod3 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 | | constants.rb:62:3:64:5 | Mod1::Mod3 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 |
| constants.rb:70:3:72:5 | Mod1::Mod3::Mod5 | FOURTY_SIX | constants.rb:71:18:71:19 | 46 | | constants.rb:70:3:72:5 | Mod1::Mod3::Mod5 | FOURTY_SIX | constants.rb:71:18:71:19 | 46 |
| file://:0:0:0:0 | Object | A | constants.rb:79:5:79:13 | [...] |
| file://:0:0:0:0 | Object | B | constants.rb:80:5:80:5 | a |
| file://:0:0:0:0 | Object | C | constants.rb:81:5:81:5 | A |
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... | | file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
constantValue constantValue
| constants.rb:17:22:17:45 | CONST_A | constants.rb:3:19:3:27 | "const_a" | | constants.rb:17:22:17:45 | CONST_A | constants.rb:3:19:3:27 | "const_a" |
@@ -101,6 +126,8 @@ constantValue
| constants.rb:57:21:57:31 | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" | | constants.rb:57:21:57:31 | FOURTY_FOUR | constants.rb:53:17:53:29 | "fourty-four" |
| constants.rb:57:21:57:31 | FOURTY_FOUR | constants.rb:56:19:56:20 | 44 | | constants.rb:57:21:57:31 | FOURTY_FOUR | constants.rb:56:19:56:20 | 44 |
| constants.rb:65:19:65:35 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 | | constants.rb:65:19:65:35 | FOURTY_FIVE | constants.rb:63:19:63:20 | 45 |
| constants.rb:81:5:81:5 | A | constants.rb:79:5:79:13 | [...] |
| constants.rb:82:5:82:5 | B | constants.rb:80:5:80:5 | a |
constantWriteAccessQualifiedName constantWriteAccessQualifiedName
| constants.rb:1:1:15:3 | ModuleA | ModuleA | | constants.rb:1:1:15:3 | ModuleA | ModuleA |
| constants.rb:2:5:4:7 | ClassA | ModuleA::ClassA | | constants.rb:2:5:4:7 | ClassA | ModuleA::ClassA |
@@ -133,3 +160,14 @@ constantWriteAccessQualifiedName
| constants.rb:70:3:72:5 | Mod5 | Mod3::Mod5 | | constants.rb:70:3:72:5 | Mod5 | Mod3::Mod5 |
| constants.rb:71:5:71:14 | FOURTY_SIX | Mod1::Mod3::Mod5::FOURTY_SIX | | constants.rb:71:5:71:14 | FOURTY_SIX | Mod1::Mod3::Mod5::FOURTY_SIX |
| constants.rb:71:5:71:14 | FOURTY_SIX | Mod3::Mod5::FOURTY_SIX | | constants.rb:71:5:71:14 | FOURTY_SIX | Mod3::Mod5::FOURTY_SIX |
| constants.rb:79:1:79:1 | A | A |
| constants.rb:80:1:80:1 | B | B |
| constants.rb:81:1:81:1 | C | C |
arrayConstant
| constants.rb:20:13:20:37 | call to [] | constants.rb:20:13:20:37 | call to [] |
| constants.rb:78:5:78:13 | call to [] | constants.rb:78:5:78:13 | call to [] |
| constants.rb:79:5:79:13 | call to [] | constants.rb:79:5:79:13 | call to [] |
| constants.rb:80:5:80:5 | a | constants.rb:78:5:78:13 | call to [] |
| constants.rb:81:5:81:5 | A | constants.rb:79:5:79:13 | call to [] |
| constants.rb:82:5:82:5 | B | constants.rb:78:5:78:13 | call to [] |
| constants.rb:85:7:85:7 | b | constants.rb:78:5:78:13 | call to [] |

View File

@@ -1,5 +1,6 @@
import ruby import ruby
import codeql.ruby.ast.internal.Module as M import codeql.ruby.ast.internal.Module as M
import codeql.ruby.ast.internal.Constant
query predicate constantAccess(ConstantAccess a, string kind, string name, string cls) { query predicate constantAccess(ConstantAccess a, string kind, string name, string cls) {
( (
@@ -20,3 +21,5 @@ query predicate constantValue(ConstantReadAccess a, Expr e) { e = a.getValue() }
query predicate constantWriteAccessQualifiedName(ConstantWriteAccess w, string qualifiedName) { query predicate constantWriteAccessQualifiedName(ConstantWriteAccess w, string qualifiedName) {
w.getAQualifiedName() = qualifiedName w.getAQualifiedName() = qualifiedName
} }
query predicate arrayConstant = isArrayConstant/2;

View File

@@ -72,3 +72,16 @@ module Mod4
end end
@@fourty_six = Mod3::FOURTY_SIX @@fourty_six = Mod3::FOURTY_SIX
end end
# Array constants
a = [1, 2, 3]
A = [1, 2, 3]
B = a
C = A
b = B
if condition
c = b
end
c # not recognised

View File

@@ -0,0 +1,4 @@
#! /usr/bin/ruby
# coding: iso-8859-15
print "EUR = <20>"

View File

@@ -0,0 +1,22 @@
WARNING: Type BarrierGuard has been deprecated and may be removed in future (barrier-guards.ql:8,3-15)
oldStyleBarrierGuards
| barrier-guards.rb:3:4:3:15 | ... == ... | barrier-guards.rb:4:5:4:7 | foo | barrier-guards.rb:3:4:3:6 | foo | true |
| barrier-guards.rb:9:4:9:24 | call to include? | barrier-guards.rb:10:5:10:7 | foo | barrier-guards.rb:9:21:9:23 | foo | true |
| barrier-guards.rb:15:4:15:15 | ... != ... | barrier-guards.rb:18:5:18:7 | foo | barrier-guards.rb:15:4:15:6 | foo | false |
| barrier-guards.rb:21:8:21:19 | ... == ... | barrier-guards.rb:24:5:24:7 | foo | barrier-guards.rb:21:8:21:10 | foo | true |
| barrier-guards.rb:27:8:27:19 | ... != ... | barrier-guards.rb:28:5:28:7 | foo | barrier-guards.rb:27:8:27:10 | foo | false |
| barrier-guards.rb:37:4:37:20 | call to include? | barrier-guards.rb:38:5:38:7 | foo | barrier-guards.rb:37:17:37:19 | foo | true |
| barrier-guards.rb:43:4:43:15 | ... == ... | barrier-guards.rb:45:9:45:11 | foo | barrier-guards.rb:43:4:43:6 | foo | true |
| barrier-guards.rb:70:4:70:21 | call to include? | barrier-guards.rb:71:5:71:7 | foo | barrier-guards.rb:70:18:70:20 | foo | true |
| barrier-guards.rb:82:4:82:25 | ... != ... | barrier-guards.rb:83:5:83:7 | foo | barrier-guards.rb:82:15:82:17 | foo | true |
newStyleBarrierGuards
| barrier-guards.rb:4:5:4:7 | foo |
| barrier-guards.rb:10:5:10:7 | foo |
| barrier-guards.rb:18:5:18:7 | foo |
| barrier-guards.rb:24:5:24:7 | foo |
| barrier-guards.rb:28:5:28:7 | foo |
| barrier-guards.rb:38:5:38:7 | foo |
| barrier-guards.rb:45:9:45:11 | foo |
| barrier-guards.rb:71:5:71:7 | foo |
| barrier-guards.rb:83:5:83:7 | foo |
| barrier-guards.rb:91:5:91:7 | foo |

Some files were not shown because too many files have changed in this diff Show More