Compare commits

..

131 Commits

Author SHA1 Message Date
Aditya Sharad
93efbfe1c7 WIP: Go: Add context query for retrieving call graph edges 2025-04-17 15:06:16 -07:00
Aditya Sharad
4a1b988f39 WIP: Ruby: Add context query for retrieving XSS sanitisers 2025-04-17 15:05:56 -07:00
Paolo Tranquilli
6176202d50 Merge pull request #19291 from github/redsun82/rust-pick-edition
Rust: pick correct edition for the files
2025-04-14 16:26:00 +02:00
Paolo Tranquilli
c9cff09f5d Merge branch 'main' into redsun82/rust-pick-edition 2025-04-14 15:19:33 +02:00
Michael Nebel
53c4b29b50 Merge pull request #19289 from michaelnebel/csharp/improveautobuilder
C#: Improve auto-builder to better detect SDK references.
2025-04-14 12:43:23 +02:00
Paolo Tranquilli
c245459e97 Merge pull request #19293 from github/redsun82/rust-fix-member-aggregation
Rust: fix workspace member aggregation when absolute path is a glob pattern
2025-04-14 12:08:43 +02:00
Geoffrey White
884c4a6e7b Merge pull request #19171 from geoffw0/badalloc
Rust: Query for uncontrolled allocation size
2025-04-14 10:10:53 +01:00
Geoffrey White
c821f27309 Merge branch 'main' into badalloc 2025-04-14 09:36:59 +01:00
Napalys Klicius
86313715a4 Merge pull request #19184 from Napalys/js/request_handlers
JS: Support for `Request` and `NextRequest`
2025-04-14 08:07:24 +02:00
yoff
85527101bd Merge pull request #19205 from yoff/ruby/refine-uninitialised-local
ruby: refine `rb/uninitialized-local-variable`
2025-04-11 23:08:01 +02:00
yoff
7517272d34 ruby: remove repetitive change note 2025-04-11 23:01:15 +02:00
yoff
b988be8ff6 ruby: improve help file
This has improved autofixes
I hope it also helps humans
2025-04-11 21:29:01 +02:00
yoff
85e27cae60 Merge branch 'main' into ruby/refine-uninitialised-local 2025-04-11 18:09:59 +02:00
Aditya Sharad
2dc88d87ae Merge pull request #19278 from adityasharad/actions/integration-test-filters
Actions: Fix handling of paths-ignore in autobuild scripts, add integration tests for configured path filters
2025-04-11 20:53:33 +05:30
Paolo Tranquilli
63e5f5a555 Rust: parametrize some integration tests on three editions 2025-04-11 16:50:23 +02:00
Paolo Tranquilli
868680f078 Merge branch 'redsun82/rust-fix-member-aggregation' into redsun82/rust-pick-edition 2025-04-11 16:46:16 +02:00
Paolo Tranquilli
60aa3a8d9d Rust: fix workspace member aggregation when absolute path is a glob pattern
We were interpreting the absolute path of a workspace as a glob pattern,
which doesn't work if the path has some special characters (e.g. `[` or
`]`).
2025-04-11 16:41:51 +02:00
yoff
eb0f8e9572 ruby: add rb/uninitialized-local-variable to quality suite 2025-04-11 16:27:21 +02:00
Mathias Vorreiter Pedersen
11aef7019e Merge pull request #19273 from MathiasVP/prepare-shared-mad-generation-for-cpp
Shared: Prepare model generation for C++ adoption
2025-04-11 07:22:56 -07:00
yoff
6a76a40cf4 ruby: adjust change notes 2025-04-11 16:18:03 +02:00
Paolo Tranquilli
dbbd80f4dc Rust: pick correct edition for the files
Previously we would unconditionally set the edition to the latest stable
according to rust-analyzer (2021 at the moment). Now we ask
rust-analyzer itself to pick the correct edition for the file.
2025-04-11 15:36:45 +02:00
yoff
2477233508 ruby: only report on method calls
Interviewing a Ruby developer, I learned that
dealing with nil is common practice.
So alerts are mostly useful, if we can point to a place where this has gone wrong.
2025-04-11 15:01:57 +02:00
Mathias Vorreiter Pedersen
877118fb3b Merge pull request #19274 from MathiasVP/prepare-cpp-for-mad-generation
C++: Prepare for model generation adoption
2025-04-11 05:11:36 -07:00
Michael Nebel
f349048e42 C#: Add change note. 2025-04-11 13:53:54 +02:00
Michael Nebel
31143b405e C#: Improve auto builder logic to detect Sdk reference. 2025-04-11 13:53:52 +02:00
Mathias Vorreiter Pedersen
deef95d384 Update cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
Co-authored-by: Taus <tausbn@github.com>
2025-04-11 12:43:59 +01:00
Mathias Vorreiter Pedersen
bfc494c0e1 Update cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
Co-authored-by: Taus <tausbn@github.com>
2025-04-11 12:43:51 +01:00
yoff
b641d5f177 ruby: fix FP 2025-04-11 13:22:42 +02:00
yoff
6e2cfab7b2 ruby: add test for for
found during triage
2025-04-11 12:46:25 +02:00
Michael Nebel
a5aef8c6f9 C#: Add some more DotNet autobuilder unit tests. 2025-04-11 12:03:06 +02:00
Paolo Tranquilli
4ae49cfe35 Merge pull request #19281 from github/redsun82/rust-setup
Rust: refine `ql/test/setup.sh`
2025-04-11 11:55:12 +02:00
Owen Mansel-Chan
472bfa2668 Merge pull request #19115 from owen-mc/java/port/java/string-replace-all-with-non-regex
Java: Add new quality query to detect `String#replaceAll` with non-regex first argument
2025-04-11 10:31:38 +01:00
Napalys Klicius
3d7c0201d9 Merge pull request #19231 from Napalys/js/typed_array
JS: Taint propagation from low-level `ArrayBuffer` to `Strings`
2025-04-11 11:29:01 +02:00
Napalys
11abbf8c4a Now nextUrl is of type parameter and loosen the restriction for NextAppRouteHandler 2025-04-11 11:19:12 +02:00
Napalys Klicius
92e4f112c0 Update javascript/ql/lib/semmle/javascript/frameworks/Next.qll
Co-authored-by: Asger F <asgerf@github.com>
2025-04-11 11:08:40 +02:00
Napalys Klicius
d0dcf897cb Update javascript/ql/lib/semmle/javascript/internal/flow_summaries/Strings.qll
Co-authored-by: Asger F <asgerf@github.com>
2025-04-11 11:04:08 +02:00
yoff
4167e96058 ruby: more complete impleemntation of isInBooleanContext
Co-authored-by: Tom Hvitved <hvitved@github.com>
2025-04-11 11:00:22 +02:00
yoff
f675a143d6 ruby: remove redundant cases
The CFG handles the negation
2025-04-11 10:48:41 +02:00
Napalys Klicius
d17d29a387 Merge pull request #19218 from Napalys/js/upgrade_websocket
JS: Refactor `WebSocket` to use `API` graphs
2025-04-11 10:05:54 +02:00
Napalys
e3f1720f9c RenamedDecodeLike to Decode and updated propagatesFlow 2025-04-11 10:04:09 +02:00
Arthur Baars
85940484ab Update rust/ql/test/setup.sh 2025-04-11 09:57:50 +02:00
Napalys
2c4b3527b4 Added change note 2025-04-11 09:42:12 +02:00
Napalys
678eccb417 Added searchParams.get as potential source for SSRF 2025-04-11 09:42:07 +02:00
Napalys
8674b61e5a Added SSRF test case with searchParams for NextRequest 2025-04-11 09:26:16 +02:00
Paolo Tranquilli
db1203acb3 Rust: reinstate adding rust-src for test toolchains 2025-04-11 08:57:14 +02:00
Napalys
6e09a65da0 Added support for NextRequest middleware SSRF. 2025-04-11 08:43:36 +02:00
Napalys
734ad2d767 Removed legacy Consistency check as it is redundant now with inline test expectations. 2025-04-11 08:43:08 +02:00
Napalys
208487f236 Added middleware test 2025-04-11 08:39:47 +02:00
Paolo Tranquilli
becea89a47 Rust: refine ql/test/setup.sh 2025-04-11 08:26:48 +02:00
yoff
8555e8c8c8 ruby: add change notes 2025-04-11 03:07:19 +02:00
yoff
53c88da91b ruby: refine query for uninitialised local variables
- there are places where uninitialised reads are intentional
- there are also some places where they are impossible
2025-04-11 03:07:19 +02:00
yoff
1ca25b2ccb ruby: add test of rb/uninitialized-local-variable 2025-04-11 03:00:05 +02:00
Aditya Sharad
283503b06d Actions: Fix handling of paths-ignore in autobuild scripts
Always concatenate the default filters with the user-provided filters.
This ensures that when `paths-ignore` is provided,
we begin with the default path inclusions,
not all YAML files.
This makes the `paths-ignore-only` integration test variant
under `filters` pass.

The handling of `paths` is unchanged:
if provided, this overrides the default filters.
2025-04-10 11:18:45 -07:00
Aditya Sharad
30ce0c5cbf Actions: Add integration tests for configured path filters
Use the common structure from the existing test
for default filters.

Check both query output finding workflows and actions,
and source archive output showing all extracted YAML files.

The test for only `paths-ignore` fails in this commit,
demonstrating a bug: we start with all YAML files
rather than starting with the default includes.

The tests for `paths` reflect current behaviour
which is consistent with other languages:
`paths` overrides the default inclusions,
and only files under `paths` are included.

This may not be the best user experience for Actions,
since we want to scan all workflow and action files
even in the presence of `paths`, but that is not
currently addressed.
2025-04-10 11:17:51 -07:00
Owen Mansel-Chan
4f5bdbb517 Add new query to java-code-quality.qls.expected 2025-04-10 14:37:11 +01:00
Mathias Vorreiter Pedersen
3bb249f580 C++: Ensure we always have 'Position's even if there are no calls in the DB. 2025-04-10 14:28:40 +01:00
Mathias Vorreiter Pedersen
b678112f4d C++: Add a few predicates to 'ReturnKind'. 2025-04-10 14:28:38 +01:00
Mathias Vorreiter Pedersen
960e9900af C++: Move the 'getArgumentIndex' into the abstract 'Position' class. It is implemented in all subclasses anyway. 2025-04-10 14:28:36 +01:00
Mathias Vorreiter Pedersen
94e08e318d C++: Expose a few predicates from 'ExternalFlow'. 2025-04-10 14:18:47 +01:00
Napalys
86b64afa13 Added NextResponse to the ResponseCall class it models similar near idential behaviour. 2025-04-10 15:06:44 +02:00
Mathias Vorreiter Pedersen
ea3bb8cf0c Shared: Provide a hook to MaD generation to modify the 'ReturnValue' string. 2025-04-10 14:02:31 +01:00
Mathias Vorreiter Pedersen
6c348b5855 Rust: Fixup MaD input. 2025-04-10 14:01:20 +01:00
Mathias Vorreiter Pedersen
b6c658767e Java: Fixup MaD input. 2025-04-10 14:01:11 +01:00
Mathias Vorreiter Pedersen
04bf908a4b C#: Fixup MaD input. 2025-04-10 14:01:00 +01:00
Mathias Vorreiter Pedersen
c484945f39 Shared: Move 'getEnclosingCallable' and 'getAsExprEnclosingCallable' out of the class signature. 2025-04-10 14:00:11 +01:00
Mathias Vorreiter Pedersen
732fcbf1c9 Shared: Move 'asParameter' out of the class signature. 2025-04-10 13:58:59 +01:00
Napalys
8acb0243ad Added test cases for NextResponse and Response 2025-04-10 14:57:40 +02:00
Mathias Vorreiter Pedersen
a1dc87496a Shared: Replace a 'count' with a 'strictcount' to prevent a CP when testing on C++. 2025-04-10 13:56:38 +01:00
Napalys
63a3953b0c Enhance Next.js API endpoint handling for compatibility with both Pages and App Router structures. 2025-04-10 14:48:17 +02:00
Napalys
81cba7fa2f Added test cases with missing alerts for Request and NextRequest. 2025-04-10 14:43:48 +02:00
Owen Mansel-Chan
acfcc6d490 Sort ids in java-code-quality.qls 2025-04-10 12:35:42 +01:00
Owen Mansel-Chan
576f4cf19f Update tags 2025-04-10 12:21:09 +01:00
Owen Mansel-Chan
ad89e7980e Tweak documentation 2025-04-10 12:21:08 +01:00
Owen Mansel-Chan
3ea5cc1b66 Add query to code-quality query suite 2025-04-10 12:21:06 +01:00
Owen Mansel-Chan
e1c5517de7 Keep COMPLIANT and NON_COMPLIANT comments in test 2025-04-10 12:21:05 +01:00
Owen Mansel-Chan
04ec1d7830 Update test expectations 2025-04-10 12:21:03 +01:00
Owen Mansel-Chan
626a7d5007 Fix punctuation 2025-04-10 12:21:02 +01:00
Owen Mansel-Chan
c4e56b1ec8 Add quality and cwe tag to query
CWE-1176: Inefficient CPU Computation
2025-04-10 12:21:00 +01:00
Owen Mansel-Chan
042fe07494 Adjust alert message 2025-04-10 11:47:19 +01:00
Owen Mansel-Chan
fea3d10b97 Update qhelp 2025-04-10 11:47:18 +01:00
Owen Mansel-Chan
441c79ebdf Use existing class StringReplaceAllCall 2025-04-10 11:47:17 +01:00
Owen Mansel-Chan
b5b252b10f Convert test to inline expectations 2025-04-10 11:47:15 +01:00
Owen Mansel-Chan
ff2947a0e5 Adjust query name 2025-04-10 11:47:14 +01:00
Owen Mansel-Chan
041adcd63a Java: Add initial version of string replaceAll with no regex query 2025-04-10 11:46:59 +01:00
Aditya Sharad
bd3342af8a Actions: Update integration test for default filters
Create a common file structure to be shared among multiple tests
for path filters, and rename accordingly.
Update test expectations with additional files.
Use pytest markers to indicate the expected outputs.

Add source archive checking in addition to checking query output.
This allows us to test which YAML files were extracted
separately from whether they are semantically meaningful
to the Actions analysis.
2025-04-09 20:59:42 -07:00
Napalys
5243f90c90 Brought back old methods and marked them as deprecated 2025-04-09 14:56:24 +02:00
Napalys Klicius
2dca95af92 Update javascript/ql/lib/change-notes/2025-04-07-websocket.md
Co-authored-by: Asger F <asgerf@github.com>
2025-04-09 14:26:00 +02:00
Napalys
0c52b5ad95 Added summary flow for StringFromCharCode 2025-04-09 14:24:43 +02:00
Napalys
a3e4e62eac Removed taint from ArrayBuffer constructor as it accepts length 2025-04-09 13:27:13 +02:00
Napalys
4bc3e9e736 Addressed comments
Co-authored-by: Asgerf <asgerf@github.com>
2025-04-09 12:31:45 +02:00
Geoffrey White
10ad5780b5 Rust: Try a different toolchain version to fix the test in CI? 2025-04-08 10:21:40 +01:00
Geoffrey White
e2f63db96d Merge branch 'main' into badalloc 2025-04-08 10:15:38 +01:00
Napalys
873db7c121 Added change note 2025-04-07 18:15:24 +02:00
Napalys
b97c61864e Add flow summaries and entry points for TextDecoder 2025-04-07 18:15:19 +02:00
Napalys
f28478e876 Add test cases from TypedArrays to strings. 2025-04-07 18:13:52 +02:00
Napalys
f4277204b7 Add flow summaries and entry points for ArrayBuffer and SharedArrayBuffer 2025-04-07 18:12:35 +02:00
Napalys
0e099474c5 Added test cases for ArrayBuffer and SharedArrayBuffer 2025-04-07 18:07:54 +02:00
Napalys
ff07ec8d8c Add flow summaries for TypedArray methods set and subarray 2025-04-07 18:06:40 +02:00
Geoffrey White
41f54d836e Rust: Tweak query description. 2025-04-07 14:33:31 +01:00
Geoffrey White
dad85854cd Apply suggestions from code review
Co-authored-by: mc <42146119+mchammer01@users.noreply.github.com>
2025-04-07 14:27:12 +01:00
Napalys
d689a55229 Added test cases for TypedArray methods 2025-04-07 15:15:29 +02:00
Napalys
e23ff9cf3e Add TypedArrays flow summaries for Uint8Array and buffer property 2025-04-07 15:15:24 +02:00
Napalys
93882263f9 Added test case for Uint8Array and TypedArray.prototype.buffer 2025-04-07 12:46:19 +02:00
Napalys
c4fa417680 Added change note 2025-04-07 12:11:33 +02:00
Napalys
6fb5376c5f Refactor ReceivedItemAsRemoteFlow to handle data from both client and server WebSocket sources 2025-04-07 11:44:40 +02:00
Geoffrey White
893e42315e Merge branch 'main' into badalloc 2025-04-07 09:33:23 +01:00
Geoffrey White
6ad7a950da Merge branch 'main' into badalloc 2025-04-04 21:57:04 +01:00
Geoffrey White
c9939387f8 Rust: Turn on PrettyPrintModels for RegexInjection so we hopefully don't have to deal with test result changes there as often. 2025-04-04 21:47:22 +01:00
Geoffrey White
a5883b1627 Rust: Accept test changes (due to added models?). 2025-04-04 21:45:37 +01:00
Geoffrey White
8e7e162ebc Merge branch 'main' into badalloc 2025-04-04 17:09:50 +01:00
Napalys
6bcfd8c91d Updated getAServer with API graphs. 2025-04-04 12:31:29 +02:00
Napalys
c5860e92ec Updated WebSocketReceiveNode to match bind functions. 2025-04-04 12:28:53 +02:00
Napalys
4b7a9cd399 Added test case with bind. 2025-04-04 12:26:58 +02:00
Napalys
49194b0340 Updated WebSocketReceiveNode with API graphs. 2025-04-04 12:26:52 +02:00
Geoffrey White
44b26e5ae6 Rust: Change the test copy of the example as well. 2025-04-04 09:54:41 +01:00
Geoffrey White
f96b00a62a Update rust/ql/src/queries/security/CWE-770/UncontrolledAllocationSizeGood.rs
Co-authored-by: Simon Friis Vindum <paldepind@github.com>
2025-04-04 09:53:13 +01:00
Napalys
0dbf951291 Updated ClientSocket and SendNode with API graphs. 2025-04-04 09:14:54 +02:00
Napalys
455ce59583 Added test cases with export of an instance. 2025-04-04 08:59:19 +02:00
Napalys
e16a20e69f Updated SocketClass to use API Graphs. 2025-04-04 08:47:27 +02:00
Napalys
c7fad09664 Added test cases with custom exports/imports. 2025-04-04 08:33:26 +02:00
Napalys
a572ac60d2 Added inline test expectations for WebSocket 2025-04-04 08:22:48 +02:00
Geoffrey White
fb22d55878 Rust: Remove duplicate models. 2025-03-31 18:26:26 +01:00
Geoffrey White
6a5a1001bb Rust: Refine the barrier guard. 2025-03-31 18:26:21 +01:00
Geoffrey White
f7d3a51f27 Rust: Implement barrier guard. 2025-03-31 17:53:17 +01:00
Geoffrey White
cdd5cb0523 Rust: More test cases for bounds / guards. 2025-03-31 17:53:14 +01:00
Geoffrey White
addc1d34d8 Rust: Add qhelp, examples, and tests of examples. 2025-03-31 17:53:10 +01:00
Geoffrey White
64aa4e8bae Rust: Ensure that the sinks for this query appear in metrics. 2025-03-31 17:39:24 +01:00
Geoffrey White
e49c1afe72 Rust: Add a few missing models. 2025-03-31 17:39:19 +01:00
Geoffrey White
03f94de3cb Rust: Add models. 2025-03-31 17:39:15 +01:00
Geoffrey White
9409cd6ed7 Rust: Prototype query. 2025-03-31 17:39:11 +01:00
Geoffrey White
ae555f2f2e Rust: Add a test for uncontrolled allocation size. 2025-03-31 17:37:21 +01:00
115 changed files with 2768 additions and 300 deletions

View File

@@ -1,21 +1,28 @@
if (($null -ne $env:LGTM_INDEX_INCLUDE) -or ($null -ne $env:LGTM_INDEX_EXCLUDE) -or ($null -ne $env:LGTM_INDEX_FILTERS)) {
Write-Output 'Path filters set. Passing them through to the JavaScript extractor.'
} else {
Write-Output 'No path filters set. Using the default filters.'
# Note: We're adding the `reusable_workflows` subdirectories to proactively
# record workflows that were called cross-repo, check them out locally,
# and enable an interprocedural analysis across the workflow files.
# These workflows follow the convention `.github/reusable_workflows/<nwo>/*.ya?ml`
$DefaultPathFilters = @(
'exclude:**/*',
'include:.github/workflows/*.yml',
'include:.github/workflows/*.yaml',
'include:.github/reusable_workflows/**/*.yml',
'include:.github/reusable_workflows/**/*.yaml',
'include:**/action.yml',
'include:**/action.yaml'
)
# Note: We're adding the `reusable_workflows` subdirectories to proactively
# record workflows that were called cross-repo, check them out locally,
# and enable an interprocedural analysis across the workflow files.
# These workflows follow the convention `.github/reusable_workflows/<nwo>/*.ya?ml`
$DefaultPathFilters = @(
'exclude:**/*',
'include:.github/workflows/*.yml',
'include:.github/workflows/*.yaml',
'include:.github/reusable_workflows/**/*.yml',
'include:.github/reusable_workflows/**/*.yaml',
'include:**/action.yml',
'include:**/action.yaml'
)
if ($null -ne $env:LGTM_INDEX_FILTERS) {
Write-Output 'LGTM_INDEX_FILTERS set. Using the default filters together with the user-provided filters, and passing through to the JavaScript extractor.'
# Begin with the default path inclusions only,
# followed by the user-provided filters.
# If the user provided `paths`, those patterns override the default inclusions
# (because `LGTM_INDEX_FILTERS` will begin with `exclude:**/*`).
# If the user provided `paths-ignore`, those patterns are excluded.
$PathFilters = ($DefaultPathFilters -join "`n") + "`n" + $env:LGTM_INDEX_FILTERS
$env:LGTM_INDEX_FILTERS = $PathFilters
} else {
Write-Output 'LGTM_INDEX_FILTERS not set. Using the default filters, and passing through to the JavaScript extractor.'
$env:LGTM_INDEX_FILTERS = $DefaultPathFilters -join "`n"
}

View File

@@ -17,10 +17,22 @@ include:**/action.yaml
END
)
if [ -n "${LGTM_INDEX_INCLUDE:-}" ] || [ -n "${LGTM_INDEX_EXCLUDE:-}" ] || [ -n "${LGTM_INDEX_FILTERS:-}" ] ; then
echo "Path filters set. Passing them through to the JavaScript extractor."
if [ -n "${LGTM_INDEX_FILTERS:-}" ]; then
echo "LGTM_INDEX_FILTERS set. Using the default filters together with the user-provided filters, and passing through to the JavaScript extractor."
# Begin with the default path inclusions only,
# followed by the user-provided filters.
# If the user provided `paths`, those patterns override the default inclusions
# (because `LGTM_INDEX_FILTERS` will begin with `exclude:**/*`).
# If the user provided `paths-ignore`, those patterns are excluded.
PATH_FILTERS="$(cat << END
${DEFAULT_PATH_FILTERS}
${LGTM_INDEX_FILTERS}
END
)"
LGTM_INDEX_FILTERS="${PATH_FILTERS}"
export LGTM_INDEX_FILTERS
else
echo "No path filters set. Using the default filters."
echo "LGTM_INDEX_FILTERS not set. Using the default filters, and passing through to the JavaScript extractor."
LGTM_INDEX_FILTERS="${DEFAULT_PATH_FILTERS}"
export LGTM_INDEX_FILTERS
fi

View File

@@ -1,2 +0,0 @@
def test(codeql, actions):
codeql.database.create(source_root="src")

View File

@@ -0,0 +1,6 @@
| src/.github/action.yaml:1:1:11:32 | name: ' ... action' |
| src/.github/actions/action-name/action.yml:1:1:11:32 | name: ' ... action' |
| src/.github/workflows/workflow.yml:1:1:12:33 | name: A workflow |
| src/action.yml:1:1:11:32 | name: ' ... action' |
| src/excluded/action.yml:1:1:11:32 | name: ' ... action' |
| src/included/action.yml:1:1:11:32 | name: ' ... action' |

View File

@@ -0,0 +1,2 @@
| src/included/action.yml:1:1:11:32 | name: ' ... action' |
| src/included/unreachable-workflow.yml:1:1:12:33 | name: A ... orkflow |

View File

@@ -2,3 +2,4 @@
| src/.github/actions/action-name/action.yml:1:1:11:32 | name: ' ... action' |
| src/.github/workflows/workflow.yml:1:1:12:33 | name: A workflow |
| src/action.yml:1:1:11:32 | name: ' ... action' |
| src/included/action.yml:1:1:11:32 | name: ' ... action' |

View File

@@ -0,0 +1,2 @@
| src/included/action.yml:1:1:11:32 | name: ' ... action' |
| src/included/unreachable-workflow.yml:1:1:12:33 | name: A ... orkflow |

View File

@@ -0,0 +1,5 @@
import actions
from AstNode n
where n instanceof Workflow or n instanceof CompositeAction
select n

View File

@@ -0,0 +1,4 @@
paths:
- 'included'
paths-ignore:
- 'excluded'

View File

@@ -0,0 +1,2 @@
paths-ignore:
- 'excluded'

View File

@@ -0,0 +1,2 @@
paths:
- 'included'

View File

@@ -0,0 +1,6 @@
src/.github/action.yaml
src/.github/actions/action-name/action.yml
src/.github/workflows/workflow.yml
src/action.yml
src/excluded/action.yml
src/included/action.yml

View File

@@ -0,0 +1,3 @@
src/included/action.yml
src/included/not-an-action.yml
src/included/unreachable-workflow.yml

View File

@@ -0,0 +1,5 @@
src/.github/action.yaml
src/.github/actions/action-name/action.yml
src/.github/workflows/workflow.yml
src/action.yml
src/included/action.yml

View File

@@ -0,0 +1,3 @@
src/included/action.yml
src/included/not-an-action.yml
src/included/unreachable-workflow.yml

View File

@@ -0,0 +1,11 @@
name: 'A composite action'
description: 'Do something'
runs:
using: "composite"
steps:
- name: Print
run: echo "Hello world"
shell: bash
- name: Checkout
uses: actions/checkout@v4

View File

@@ -0,0 +1,11 @@
name: 'A composite action'
description: 'Do something'
runs:
using: "composite"
steps:
- name: Print
run: echo "Hello world"
shell: bash
- name: Checkout
uses: actions/checkout@v4

View File

@@ -0,0 +1 @@
name: 'Not an action, just a YAML file'

View File

@@ -0,0 +1,12 @@
name: An unreachable workflow
on:
push:
branches:
- main
jobs:
job:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

View File

@@ -0,0 +1,12 @@
name: An unreachable workflow
on:
push:
branches:
- main
jobs:
job:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

View File

@@ -0,0 +1,18 @@
import pytest
@pytest.mark.ql_test(expected=".default-filters.expected")
def test_default_filters(codeql, actions, check_source_archive):
check_source_archive.expected_suffix = ".default-filters.expected"
codeql.database.create(source_root="src")
@pytest.mark.ql_test(expected=".paths-only.expected")
def test_config_paths_only(codeql, actions):
codeql.database.create(source_root="src", codescanning_config="codeql-config.paths-only.yml")
@pytest.mark.ql_test(expected=".paths-ignore-only.expected")
def test_config_paths_ignore_only(codeql, actions):
codeql.database.create(source_root="src", codescanning_config="codeql-config.paths-ignore-only.yml")
@pytest.mark.ql_test(expected=".paths-and-paths-ignore.expected")
def test_config_paths_and_paths_ignore(codeql, actions):
codeql.database.create(source_root="src", codescanning_config="codeql-config.paths-and-paths-ignore.yml")

View File

@@ -465,7 +465,7 @@ private predicate isFunctionConstructedFrom(Function f, Function templateFunc) {
}
/** Gets the fully templated version of `f`. */
private Function getFullyTemplatedFunction(Function f) {
Function getFullyTemplatedFunction(Function f) {
not f.isFromUninstantiatedTemplate(_) and
(
exists(Class c, Class templateClass, int i |
@@ -559,12 +559,15 @@ private string getTypeName(Type t, boolean needsSpace) {
/**
* Gets a type name for the `n`'th parameter of `f` without any template
* arguments. The result may be a string representing a type for which the
* typedefs have been resolved.
* arguments.
*
* If `canonical = false` then the result may be a string representing a type
* for which the typedefs have been resolved. If `canonical = true` then the
* result will be a string representing a type without resolving `typedefs`.
*/
bindingset[f]
pragma[inline_late]
string getParameterTypeWithoutTemplateArguments(Function f, int n) {
string getParameterTypeWithoutTemplateArguments(Function f, int n, boolean canonical) {
exists(string s, string base, string specifiers, Type t |
t = f.getParameter(n).getType() and
// The name of the string can either be the possibly typedefed name
@@ -572,14 +575,19 @@ string getParameterTypeWithoutTemplateArguments(Function f, int n) {
// `getTypeName(t, _)` is almost equal to `t.resolveTypedefs().getName()`,
// except that `t.resolveTypedefs()` doesn't have a result when the
// resulting type doesn't appear in the database.
s = [t.getName(), getTypeName(t, _)] and
(
s = t.getName() and canonical = true
or
s = getTypeName(t, _) and canonical = false
) and
parseAngles(s, base, _, specifiers) and
result = base + specifiers
)
or
f.isVarargs() and
n = f.getNumberOfParameters() and
result = "..."
result = "..." and
canonical = true
}
/**
@@ -590,7 +598,7 @@ private string getTypeNameWithoutFunctionTemplates(Function f, int n, int remain
exists(Function templateFunction |
templateFunction = getFullyTemplatedFunction(f) and
remaining = templateFunction.getNumberOfTemplateArguments() and
result = getParameterTypeWithoutTemplateArguments(templateFunction, n)
result = getParameterTypeWithoutTemplateArguments(templateFunction, n, _)
)
or
exists(string mid, TypeTemplateParameter tp, Function templateFunction |
@@ -627,7 +635,7 @@ private string getTypeNameWithoutClassTemplates(Function f, int n, int remaining
}
/** Gets the string representation of the `i`'th parameter of `c`. */
private string getParameterTypeName(Function c, int i) {
string getParameterTypeName(Function c, int i) {
result = getTypeNameWithoutClassTemplates(c, i, 0)
}

View File

@@ -371,7 +371,7 @@ private class PrimaryArgumentNode extends ArgumentNode, OperandNode {
PrimaryArgumentNode() { exists(CallInstruction call | op = call.getAnArgumentOperand()) }
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
op = call.getArgumentOperand(pos.(DirectPosition).getIndex())
op = call.getArgumentOperand(pos.(DirectPosition).getArgumentIndex())
}
}
@@ -410,8 +410,16 @@ class ParameterPosition = Position;
class ArgumentPosition = Position;
abstract class Position extends TPosition {
/** Gets a textual representation of this position. */
abstract string toString();
/**
* Gets the argument index of this position. The qualifier of a call has
* argument index `-1`.
*/
abstract int getArgumentIndex();
/** Gets the indirection index of this position. */
abstract int getIndirectionIndex();
}
@@ -428,7 +436,7 @@ class DirectPosition extends Position, TDirectPosition {
result = index.toString()
}
int getIndex() { result = index }
override int getArgumentIndex() { result = index }
final override int getIndirectionIndex() { result = 0 }
}
@@ -445,16 +453,29 @@ class IndirectionPosition extends Position, TIndirectionPosition {
else result = repeatStars(indirectionIndex) + argumentIndex.toString()
}
int getArgumentIndex() { result = argumentIndex }
override int getArgumentIndex() { result = argumentIndex }
final override int getIndirectionIndex() { result = indirectionIndex }
}
newtype TPosition =
TDirectPosition(int argumentIndex) { exists(any(CallInstruction c).getArgument(argumentIndex)) } or
TDirectPosition(int argumentIndex) {
exists(any(CallInstruction c).getArgument(argumentIndex))
or
// Handle the rare case where there is a function definition but no call to
// the function.
exists(any(Cpp::Function f).getParameter(argumentIndex))
} or
TIndirectionPosition(int argumentIndex, int indirectionIndex) {
Ssa::hasIndirectOperand(any(CallInstruction call).getArgumentOperand(argumentIndex),
indirectionIndex)
or
// Handle the rare case where there is a function definition but no call to
// the function.
exists(Cpp::Function f, Cpp::Parameter p |
p = f.getParameter(argumentIndex) and
indirectionIndex = [1 .. Ssa::getMaxIndirectionsForType(p.getUnspecifiedType()) - 1]
)
}
private newtype TReturnKind =
@@ -501,6 +522,15 @@ class ReturnKind extends TReturnKind {
/** Gets a textual representation of this return kind. */
abstract string toString();
/** Holds if this `ReturnKind` is generated from a `return` statement. */
abstract predicate isNormalReturn();
/**
* Holds if this `ReturnKind` is generated from a write to the parameter with
* index `argumentIndex`
*/
abstract predicate isIndirectReturn(int argumentIndex);
}
/**
@@ -514,6 +544,10 @@ class NormalReturnKind extends ReturnKind, TNormalReturnKind {
override int getIndirectionIndex() { result = indirectionIndex }
override string toString() { result = "indirect return" }
override predicate isNormalReturn() { any() }
override predicate isIndirectReturn(int argumentIndex) { none() }
}
/**
@@ -528,6 +562,10 @@ private class IndirectReturnKind extends ReturnKind, TIndirectReturnKind {
override int getIndirectionIndex() { result = indirectionIndex }
override string toString() { result = "indirect outparam[" + argumentIndex.toString() + "]" }
override predicate isNormalReturn() { none() }
override predicate isIndirectReturn(int argumentIndex_) { argumentIndex_ = argumentIndex }
}
/** A data flow node that occurs as the result of a `ReturnStmt`. */

View File

@@ -1445,7 +1445,7 @@ private class ExplicitParameterInstructionNode extends AbstractExplicitParameter
ExplicitParameterInstructionNode() { exists(instr.getParameter()) }
override predicate isSourceParameterOf(Function f, ParameterPosition pos) {
f.getParameter(pos.(DirectPosition).getIndex()) = instr.getParameter()
f.getParameter(pos.(DirectPosition).getArgumentIndex()) = instr.getParameter()
}
override string toStringImpl() { result = instr.getParameter().toString() }
@@ -1460,7 +1460,7 @@ class ThisParameterInstructionNode extends AbstractExplicitParameterNode,
ThisParameterInstructionNode() { instr.getIRVariable() instanceof IRThisVariable }
override predicate isSourceParameterOf(Function f, ParameterPosition pos) {
pos.(DirectPosition).getIndex() = -1 and
pos.(DirectPosition).getArgumentIndex() = -1 and
instr.getEnclosingFunction() = f
}
@@ -1494,7 +1494,7 @@ private class DirectBodyLessParameterNode extends AbstractExplicitParameterNode,
override predicate isSourceParameterOf(Function f, ParameterPosition pos) {
this.getFunction() = f and
f.getParameter(pos.(DirectPosition).getIndex()) = p
f.getParameter(pos.(DirectPosition).getArgumentIndex()) = p
}
override Parameter getParameter() { result = p }

View File

@@ -229,11 +229,11 @@ private module SpeculativeTaintFlow {
not exists(DataFlowDispatch::viableCallable(call)) and
src.(DataFlowPrivate::ArgumentNode).argumentOf(call, argpos)
|
not argpos.(DirectPosition).getIndex() = -1 and
not argpos.(DirectPosition).getArgumentIndex() = -1 and
sink.(PostUpdateNode)
.getPreUpdateNode()
.(DataFlowPrivate::ArgumentNode)
.argumentOf(call, any(DirectPosition qualpos | qualpos.getIndex() = -1))
.argumentOf(call, any(DirectPosition qualpos | qualpos.getArgumentIndex() = -1))
or
sink.(DataFlowPrivate::OutNode).getCall() = call
)

View File

@@ -424,8 +424,7 @@ namespace Semmle.Autobuild.CSharp.Tests
return new CSharpAutobuilder(actions, options);
}
[Fact]
public void TestDefaultCSharpAutoBuilder()
private void SetupActionForDotnet()
{
actions.RunProcess["cmd.exe /C dotnet --info"] = 0;
actions.RunProcess[@"cmd.exe /C dotnet clean C:\Project\test.csproj"] = 0;
@@ -438,20 +437,80 @@ namespace Semmle.Autobuild.CSharp.Tests
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SCRATCH_DIR"] = "scratch";
actions.EnumerateFiles[@"C:\Project"] = "foo.cs\nbar.cs\ntest.csproj";
actions.EnumerateDirectories[@"C:\Project"] = "";
var xml = new XmlDocument();
xml.LoadXml(@"<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
}
</Project>");
private void CreateAndVerifyDotnetScript(XmlDocument xml)
{
actions.LoadXml[@"C:\Project\test.csproj"] = xml;
var autobuilder = CreateAutoBuilder(true);
TestAutobuilderScript(autobuilder, 0, 4);
}
[Fact]
public void TestDefaultCSharpAutoBuilder1()
{
SetupActionForDotnet();
var xml = new XmlDocument();
xml.LoadXml(
"""
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
</Project>
""");
CreateAndVerifyDotnetScript(xml);
}
[Fact]
public void TestDefaultCSharpAutoBuilder2()
{
SetupActionForDotnet();
var xml = new XmlDocument();
xml.LoadXml(
"""
<Project>
<Sdk Name="Microsoft.NET.Sdk" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
"""
);
CreateAndVerifyDotnetScript(xml);
}
[Fact]
public void TestDefaultCSharpAutoBuilder3()
{
SetupActionForDotnet();
var xml = new XmlDocument();
xml.LoadXml(
"""
<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>
"""
);
CreateAndVerifyDotnetScript(xml);
}
[Fact]
public void TestLinuxCSharpAutoBuilder()
{

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using Semmle.Util.Logging;
namespace Semmle.Autobuild.Shared
{
@@ -26,6 +25,26 @@ namespace Semmle.Autobuild.Shared
private readonly Lazy<List<Project<TAutobuildOptions>>> includedProjectsLazy;
public override IEnumerable<IProjectOrSolution> IncludedProjects => includedProjectsLazy.Value;
private static bool HasSdkAttribute(XmlElement xml) =>
xml.HasAttribute("Sdk");
private static bool AnyElement(XmlNodeList l, Func<XmlElement, bool> f) =>
l.OfType<XmlElement>().Any(f);
/// <summary>
/// According to https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2022#reference-a-project-sdk
/// there are three ways to reference a project SDK:
/// 1. As an attribute on the <Project/>.
/// 2. As a top level element of <Project>.
/// 3. As an attribute on an <Import> element.
///
/// Returns true, if the Sdk attribute is used, otherwise false.
/// </summary>
private static bool ReferencesSdk(XmlElement xml) =>
HasSdkAttribute(xml) || // Case 1
AnyElement(xml.ChildNodes, e => e.Name == "Sdk") || // Case 2
AnyElement(xml.GetElementsByTagName("Import"), HasSdkAttribute); // Case 3
public Project(Autobuilder<TAutobuildOptions> builder, string path) : base(builder, path)
{
ToolsVersion = new Version();
@@ -49,7 +68,7 @@ namespace Semmle.Autobuild.Shared
if (root?.Name == "Project")
{
if (root.HasAttribute("Sdk"))
if (ReferencesSdk(root))
{
DotNetProject = true;
return;

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Improved autobuilder logic for detecting whether a project references a SDK (and should be built using `dotnet`).

View File

@@ -22,10 +22,16 @@ module ModelGeneratorInput implements ModelGeneratorInputSig<Location, CsharpDat
class Callable = CS::Callable;
class NodeExtended extends CS::DataFlow::Node {
Callable getAsExprEnclosingCallable() { result = this.asExpr().getEnclosingCallable() }
class NodeExtended = CS::DataFlow::Node;
Callable getAsExprEnclosingCallable(NodeExtended node) {
result = node.asExpr().getEnclosingCallable()
}
Callable getEnclosingCallable(NodeExtended node) { result = node.getEnclosingCallable() }
Parameter asParameter(NodeExtended node) { result = node.asParameter() }
/**
* Holds if any of the parameters of `api` are `System.Func<>`.
*/

15
go/ql/src/CallGraph.ql Normal file
View File

@@ -0,0 +1,15 @@
/**
* @name Call graph
* @description Generates the call graph for the program.
* @kind graph
* @id go/call-graph
*/
import go
import semmle.go.dataflow.DataFlow
query predicate edges(CallExpr call, Function f, string key, string value) {
call.getTarget() = f and
key = "semmle.label" and
value = f.getQualifiedName()
}

View File

@@ -9,3 +9,4 @@ ql/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql
ql/java/ql/src/Likely Bugs/Likely Typos/SuspiciousDateFormat.ql
ql/java/ql/src/Likely Bugs/Resource Leaks/CloseReader.ql
ql/java/ql/src/Likely Bugs/Resource Leaks/CloseWriter.ql
ql/java/ql/src/Performance/StringReplaceAllWithNonRegex.ql

View File

@@ -0,0 +1,29 @@
# Use of `String#replaceAll` with a first argument which is not a regular expression
Using `String#replaceAll` is less performant than `String#replace` when the first argument is not a regular expression.
## Overview
The `String#replaceAll` method is designed to work with regular expressions as its first parameter. When you use a simple string without any regex patterns (like special characters or syntax), it's more efficient to use `String#replace` instead. This is because `replaceAll` has to compile the input as a regular expression first, which adds unnecessary overhead when you are just replacing literal text.
## Recommendation
Use `String#replace` instead where a `replaceAll` call uses a trivial string as its first argument.
## Example
```java
public class Test {
void f() {
String s1 = "test";
s1 = s1.replaceAll("t", "x"); // NON_COMPLIANT
s1 = s1.replaceAll(".*", "x"); // COMPLIANT
}
}
```
## References
- Java SE Documentation: [String.replaceAll](https://docs.oracle.com/en/java/javase/20/docs/api/java.base/java/lang/String.html#replaceAll(java.lang.String,java.lang.String)).
- Common Weakness Enumeration: [CWE-1176](https://cwe.mitre.org/data/definitions/1176.html).

View File

@@ -0,0 +1,24 @@
/**
* @id java/string-replace-all-with-non-regex
* @name Use of `String#replaceAll` with a first argument which is not a regular expression
* @description Using `String#replaceAll` with a first argument which is not a regular expression
* is less efficient than using `String#replace`.
* @kind problem
* @precision very-high
* @problem.severity recommendation
* @tags quality
* reliability
* performance
* external/cwe/cwe-1176
*/
import java
from StringReplaceAllCall replaceAllCall, StringLiteral firstArg
where
firstArg = replaceAllCall.getArgument(0) and
//only contains characters that could be a simple string
firstArg.getValue().regexpMatch("^[a-zA-Z0-9]+$")
select replaceAllCall,
"This call to 'replaceAll' should be a call to 'replace' as its $@ is not a regular expression.",
firstArg, "first argument"

View File

@@ -1,14 +1,15 @@
- queries: .
- include:
id:
- java/suspicious-date-format
- java/integer-multiplication-cast-to-long
- java/equals-on-unrelated-types
- java/contradictory-type-checks
- java/reference-equality-of-boxed-types
- java/equals-on-unrelated-types
- java/inconsistent-equals-and-hashcode
- java/input-resource-leak
- java/integer-multiplication-cast-to-long
- java/output-resource-leak
- java/reference-equality-of-boxed-types
- java/string-replace-all-with-non-regex
- java/suspicious-date-format
- java/type-variable-hides-type
- java/unchecked-cast-in-equals
- java/unused-container
- java/input-resource-leak
- java/output-resource-leak
- java/type-variable-hides-type

View File

@@ -32,10 +32,16 @@ module ModelGeneratorInput implements ModelGeneratorInputSig<Location, JavaDataF
class Callable = J::Callable;
class NodeExtended extends DataFlow::Node {
Callable getAsExprEnclosingCallable() { result = this.asExpr().getEnclosingCallable() }
class NodeExtended = DataFlow::Node;
Callable getAsExprEnclosingCallable(NodeExtended node) {
result = node.asExpr().getEnclosingCallable()
}
Callable getEnclosingCallable(NodeExtended node) { result = node.getEnclosingCallable() }
Parameter asParameter(NodeExtended node) { result = node.asParameter() }
private predicate isInfrequentlyUsed(J::CompilationUnit cu) {
cu.getPackage().getName().matches("javax.swing%") or
cu.getPackage().getName().matches("java.awt%")

View File

@@ -0,0 +1 @@
| Test.java:4:14:4:36 | replaceAll(...) | This call to 'replaceAll' should be a call to 'replace' as its $@ is not a regular expression. | Test.java:4:28:4:30 | "t" | first argument |

View File

@@ -0,0 +1,2 @@
query: Performance/StringReplaceAllWithNonRegex.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -0,0 +1,7 @@
public class Test {
void f() {
String s1 = "test";
s1 = s1.replaceAll("t", "x"); // $ Alert // NON_COMPLIANT
s1 = s1.replaceAll(".*", "x"); // COMPLIANT
}
}

View File

@@ -0,0 +1,4 @@
---
category: minorAnalysis
---
* Added taint propagation for `Uint8Array`, `ArrayBuffer`, `SharedArrayBuffer` and `TextDecoder.decode()`.

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Improved detection of `WebSocket` and `SockJS` usage.
* Added data received from `WebSocket` clients as a remote flow source.

View File

@@ -0,0 +1,5 @@
---
category: minorAnalysis
---
* Data passed to the [NextResponse](https://nextjs.org/docs/app/api-reference/functions/next-response) constructor is now treated as a sink for `js/reflected-xss`.
* Data received from [NextRequest](https://nextjs.org/docs/app/api-reference/functions/next-request) and [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) is now treated as a remote user input `source`.

View File

@@ -213,10 +213,12 @@ module NextJS {
/**
* Gets a folder that contains API endpoints for a Next.js application.
* These API endpoints act as Express-like route-handlers.
* It matches both the Pages Router (`pages/api/`) Next.js 12 or earlier and
* the App Router (`app/api/`) Next.js 13+ structures.
*/
Folder apiFolder() {
result = getANextPackage().getFile().getParentContainer().getFolder("pages").getFolder("api")
or
result =
getANextPackage().getFile().getParentContainer().getFolder(["pages", "app"]).getFolder("api") or
result = apiFolder().getAFolder()
}
@@ -271,4 +273,64 @@ module NextJS {
override string getCredentialsKind() { result = "jwt key" }
}
}
/**
* A route handler for Next.js 13+ App Router API endpoints, which are defined by exporting
* HTTP method functions (like `GET`, `POST`, `PUT`, `DELETE`) from route.js files inside
* the `app/api/` directory.
*/
class NextAppRouteHandler extends DataFlow::FunctionNode, Http::Servers::StandardRouteHandler {
NextAppRouteHandler() {
exists(Module mod |
mod.getFile().getParentContainer() = apiFolder() or
mod.getFile().getStem() = "middleware"
|
this =
mod.getAnExportedValue([any(Http::RequestMethodName m), "middleware"]).getAFunctionValue()
)
}
/**
* Gets the request parameter, which is either a `NextRequest` object (from `next/server`) or a standard web `Request` object.
*/
DataFlow::SourceNode getRequest() { result = this.getParameter(0) }
}
/**
* A source of user-controlled data from a `NextRequest` object (from `next/server`) or a standard web `Request` object
* in a Next.js App Router route handler.
*/
class NextAppRequestSource extends Http::RequestInputAccess {
NextAppRouteHandler handler;
string kind;
NextAppRequestSource() {
(
this =
handler.getRequest().getAMethodCall(["json", "formData", "blob", "arrayBuffer", "text"])
or
this = handler.getRequest().getAPropertyRead("body")
) and
kind = "body"
or
this = handler.getRequest().getAPropertyRead(["url", "nextUrl"]) and
kind = "url"
or
this =
handler
.getRequest()
.getAPropertyRead("nextUrl")
.getAPropertyRead("searchParams")
.getAMemberCall("get") and
kind = "parameter"
or
this = handler.getRequest().getAPropertyRead("headers") and kind = "headers"
}
override string getKind() { result = kind }
override Http::RouteHandler getRouteHandler() { result = handler }
override string getSourceType() { result = "Next.js App Router request" }
}
}

View File

@@ -19,10 +19,13 @@ private class HeadersEntryPoint extends API::EntryPoint {
}
/**
* A call to the `Response` constructor.
* A call to the `Response` and `NextResponse` constructor.
*/
private class ResponseCall extends API::InvokeNode {
ResponseCall() { this = any(ResponseEntryPoint e).getANode().getAnInstantiation() }
ResponseCall() {
this = any(ResponseEntryPoint e).getANode().getAnInstantiation() or
this = API::moduleImport("next/server").getMember("NextResponse").getAnInstantiation()
}
}
/**

View File

@@ -47,6 +47,20 @@ private predicate areLibrariesCompatible(
(client = LibraryNames::ws() or client = LibraryNames::websocket())
}
/** Treats `WebSocket` as an entry point for API graphs. */
private class WebSocketEntryPoint extends API::EntryPoint {
WebSocketEntryPoint() { this = "global.WebSocket" }
override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("WebSocket") }
}
/** Treats `SockJS` as an entry point for API graphs. */
private class SockJSEntryPoint extends API::EntryPoint {
SockJSEntryPoint() { this = "global.SockJS" }
override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("SockJS") }
}
/**
* Provides classes that model WebSockets clients.
*/
@@ -56,7 +70,7 @@ module ClientWebSocket {
/**
* A class that can be used to instantiate a WebSocket instance.
*/
class SocketClass extends DataFlow::SourceNode {
deprecated class SocketClass extends DataFlow::SourceNode {
LibraryName library; // the name of the WebSocket library. Can be one of the libraries defined in `LibraryNames`.
SocketClass() {
@@ -78,13 +92,38 @@ module ClientWebSocket {
LibraryName getLibrary() { result = library }
}
/**
* A class that can be used to instantiate a WebSocket instance.
*/
class WebSocketClass extends API::Node {
LibraryName library; // the name of the WebSocket library. Can be one of the libraries defined in `LibraryNames`.
WebSocketClass() {
this = any(WebSocketEntryPoint e).getANode() and library = websocket()
or
this = API::moduleImport("ws") and library = ws()
or
// the sockjs-client library:https://www.npmjs.com/package/sockjs-client
library = sockjs() and
(
this = API::moduleImport("sockjs-client") or
this = any(SockJSEntryPoint e).getANode()
)
}
/**
* Gets the WebSocket library name.
*/
LibraryName getLibrary() { result = library }
}
/**
* A client WebSocket instance.
*/
class ClientSocket extends EventEmitter::Range, DataFlow::NewNode, ClientRequest::Range {
SocketClass socketClass;
class ClientSocket extends EventEmitter::Range, API::NewNode, ClientRequest::Range {
WebSocketClass socketClass;
ClientSocket() { this = socketClass.getAnInstantiation() }
ClientSocket() { this = socketClass.getAnInvocation() }
/**
* Gets the WebSocket library name.
@@ -115,10 +154,10 @@ module ClientWebSocket {
/**
* A message sent from a WebSocket client.
*/
class SendNode extends EventDispatch::Range, DataFlow::CallNode {
class SendNode extends EventDispatch::Range, API::CallNode {
override ClientSocket emitter;
SendNode() { this = emitter.getAMemberCall("send") }
SendNode() { this = emitter.getReturn().getMember("send").getACall() }
override string getChannel() { result = channelName() }
@@ -145,8 +184,8 @@ module ClientWebSocket {
private DataFlow::FunctionNode getAMessageHandler(
ClientWebSocket::ClientSocket emitter, string methodName
) {
exists(DataFlow::CallNode call |
call = emitter.getAMemberCall(methodName) and
exists(API::CallNode call |
call = emitter.getReturn().getMember(methodName).getACall() and
call.getArgument(0).mayHaveStringValue("message") and
result = call.getCallback(1)
)
@@ -161,7 +200,13 @@ module ClientWebSocket {
WebSocketReceiveNode() {
this = getAMessageHandler(emitter, "addEventListener")
or
this = emitter.getAPropertyWrite("onmessage").getRhs()
this = emitter.getReturn().getMember("onmessage").getAValueReachingSink()
or
exists(DataFlow::MethodCallNode bindCall |
bindCall = emitter.getReturn().getMember("onmessage").getAValueReachingSink() and
bindCall.getMethodName() = "bind" and
this = bindCall.getReceiver().getAFunctionValue()
)
}
override DataFlow::Node getReceivedItem(int i) {
@@ -192,7 +237,7 @@ module ServerWebSocket {
/**
* Gets a server created by a library named `library`.
*/
DataFlow::SourceNode getAServer(LibraryName library) {
deprecated DataFlow::SourceNode getAServer(LibraryName library) {
library = ws() and
result = DataFlow::moduleImport("ws").getAConstructorInvocation("Server")
or
@@ -200,11 +245,22 @@ module ServerWebSocket {
result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer")
}
/**
* Gets a server created by a library named `library`.
*/
API::InvokeNode getAServerInvocation(LibraryName library) {
library = ws() and
result = API::moduleImport("ws").getMember("Server").getAnInvocation()
or
library = sockjs() and
result = API::moduleImport("sockjs").getMember("createServer").getAnInvocation()
}
/**
* Gets a `socket.on("connection", (msg, req) => {})` call.
*/
private DataFlow::CallNode getAConnectionCall(LibraryName library) {
result = getAServer(library).getAMemberCall(EventEmitter::on()) and
result = getAServerInvocation(library).getReturn().getMember(EventEmitter::on()).getACall() and
result.getArgument(0).mayHaveStringValue("connection")
}
@@ -324,15 +380,18 @@ module ServerWebSocket {
result = this.getCallback(1).getParameter(0)
}
}
/**
* A data flow node representing data received from a client, viewed as remote user input.
*/
private class ReceivedItemAsRemoteFlow extends RemoteFlowSource {
ReceivedItemAsRemoteFlow() { this = any(ReceiveNode rercv).getReceivedItem(_) }
override string getSourceType() { result = "WebSocket client data" }
override predicate isUserControlledObject() { any() }
}
}
/**
* A data flow node representing data received from a client or server, viewed as remote user input.
*/
private class ReceivedItemAsRemoteFlow extends RemoteFlowSource {
ReceivedItemAsRemoteFlow() {
this = any(ClientWebSocket::ReceiveNode rercv).getReceivedItem(_) or
this = any(ServerWebSocket::ReceiveNode rercv).getReceivedItem(_)
}
override string getSourceType() { result = "WebSocket transmitted data" }
override predicate isUserControlledObject() { any() }
}

View File

@@ -12,3 +12,5 @@ private import Sets
private import Strings
private import DynamicImportStep
private import UrlSearchParams
private import TypedArrays
private import Decoders

View File

@@ -0,0 +1,28 @@
private import javascript
private import semmle.javascript.dataflow.FlowSummary
private import semmle.javascript.dataflow.InferredTypes
private import semmle.javascript.dataflow.internal.DataFlowPrivate as Private
private import FlowSummaryUtil
private class TextDecoderEntryPoint extends API::EntryPoint {
TextDecoderEntryPoint() { this = "global.TextDecoder" }
override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("TextDecoder") }
}
pragma[nomagic]
API::Node textDecoderConstructorRef() { result = any(TextDecoderEntryPoint e).getANode() }
class Decode extends SummarizedCallable {
Decode() { this = "TextDecoder#decode" }
override InstanceCall getACall() {
result = textDecoderConstructorRef().getInstance().getMember("decode").getACall()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = false and
input = "Argument[0].ArrayElement" and
output = "ReturnValue"
}
}

View File

@@ -99,3 +99,19 @@ class StringSplitHashOrQuestionMark extends SummarizedCallable {
)
}
}
class StringFromCharCode extends SummarizedCallable {
StringFromCharCode() { this = "String#fromCharCode" }
override DataFlow::CallNode getACall() {
result = DataFlow::globalVarRef("String").getAPropertyRead("fromCharCode").getACall()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = false and
(
input = "Argument[0..]" and
output = "ReturnValue"
)
}
}

View File

@@ -0,0 +1,89 @@
private import javascript
private import semmle.javascript.dataflow.FlowSummary
private import semmle.javascript.dataflow.InferredTypes
private import semmle.javascript.dataflow.internal.DataFlowPrivate as Private
private import FlowSummaryUtil
private class TypedArrayEntryPoint extends API::EntryPoint {
TypedArrayEntryPoint() { this = "global.Uint8Array" }
override DataFlow::SourceNode getASource() { result = DataFlow::globalVarRef("Uint8Array") }
}
pragma[nomagic]
API::Node typedArrayConstructorRef() { result = any(TypedArrayEntryPoint e).getANode() }
class TypedArrayConstructorSummary extends SummarizedCallable {
TypedArrayConstructorSummary() { this = "TypedArray constructor" }
override DataFlow::InvokeNode getACall() {
result = typedArrayConstructorRef().getAnInstantiation()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[0].ArrayElement" and
output = "ReturnValue.ArrayElement"
}
}
class BufferTypedArray extends DataFlow::AdditionalFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::PropRead p |
p = typedArrayConstructorRef().getInstance().getMember("buffer").asSource() and
pred = p.getBase() and
succ = p
)
}
}
class TypedArraySet extends SummarizedCallable {
TypedArraySet() { this = "TypedArray#set" }
override InstanceCall getACall() {
result = typedArrayConstructorRef().getInstance().getMember("set").getACall()
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[0].ArrayElement" and
output = "Argument[this].ArrayElement"
}
}
class TypedArraySubarray extends SummarizedCallable {
TypedArraySubarray() { this = "TypedArray#subarray" }
override InstanceCall getACall() { result.getMethodName() = "subarray" }
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this].ArrayElement" and
output = "ReturnValue.ArrayElement"
}
}
private class ArrayBufferEntryPoint extends API::EntryPoint {
ArrayBufferEntryPoint() { this = ["global.ArrayBuffer", "global.SharedArrayBuffer"] }
override DataFlow::SourceNode getASource() {
result = DataFlow::globalVarRef(["ArrayBuffer", "SharedArrayBuffer"])
}
}
pragma[nomagic]
API::Node arrayBufferConstructorRef() { result = any(ArrayBufferEntryPoint a).getANode() }
class TransferLike extends SummarizedCallable {
TransferLike() { this = "ArrayBuffer#transfer" }
override InstanceCall getACall() {
result.getMethodName() = ["transfer", "transferToFixedLength"]
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[this].ArrayElement" and
output = "ReturnValue.ArrayElement"
}
}

View File

@@ -17,7 +17,6 @@ import javascript
*
* For example, projecting out constant bit patterns less than 2<sup>31</sup>
* is safe, as are shifts by small constant integers.
* Dummy change
*/
predicate acceptableSignCheck(BitwiseExpr b) {
// projecting out constant bit patterns not containing the sign bit is fine

View File

@@ -0,0 +1,10 @@
import javascript
API::NewNode getAWebSocketInstance() { result instanceof ClientWebSocket::ClientSocket }
from DataFlow::Node handler
where
handler = getAWebSocketInstance().getReturn().getMember("onmessage").asSource()
or
handler = getAWebSocketInstance().getAPropertyWrite("onmessage").getRhs()
select handler, "This is a WebSocket onmessage handler."

View File

@@ -35,11 +35,23 @@ legacyDataFlowDifference
| spread.js:4:15:4:22 | source() | spread.js:18:8:18:8 | y | only flow with NEW data flow library |
| spread.js:4:15:4:22 | source() | spread.js:24:8:24:8 | y | only flow with NEW data flow library |
| tst.js:2:13:2:20 | source() | tst.js:17:10:17:10 | a | only flow with OLD data flow library |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:5:10:5:10 | y | only flow with NEW data flow library |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:7:10:7:17 | y.buffer | only flow with NEW data flow library |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:11:10:11:12 | arr | only flow with NEW data flow library |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:15:10:15:10 | z | only flow with NEW data flow library |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:18:10:18:12 | sub | only flow with NEW data flow library |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:42:10:42:30 | typedAr ... ring(y) | only flow with NEW data flow library |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:48:10:48:12 | str | only flow with NEW data flow library |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:52:10:52:13 | str2 | only flow with NEW data flow library |
| use-use-after-implicit-read.js:7:17:7:24 | source() | use-use-after-implicit-read.js:15:10:15:10 | x | only flow with NEW data flow library |
consistencyIssue
| nested-props.js:20 | expected an alert, but found none | NOT OK - but not found | Consistency |
| stringification-read-steps.js:17 | expected an alert, but found none | NOT OK | Consistency |
| stringification-read-steps.js:25 | expected an alert, but found none | NOT OK | Consistency |
| typed-arrays.js:23 | expected an alert, but found none | NOT OK -- Should be flagged but it is not. | Consistency |
| typed-arrays.js:28 | expected an alert, but found none | NOT OK -- Should be flagged but it is not. | Consistency |
| typed-arrays.js:32 | expected an alert, but found none | NOT OK -- Should be flagged but it is not. | Consistency |
| typed-arrays.js:36 | expected an alert, but found none | NOT OK -- Should be flagged but it is not. | Consistency |
flow
| access-path-sanitizer.js:2:18:2:25 | source() | access-path-sanitizer.js:4:8:4:12 | obj.x |
| addexpr.js:4:10:4:17 | source() | addexpr.js:7:8:7:8 | x |
@@ -325,6 +337,14 @@ flow
| tst.js:87:22:87:29 | source() | tst.js:90:14:90:25 | taintedValue |
| tst.js:93:22:93:29 | source() | tst.js:96:14:96:25 | taintedValue |
| tst.js:93:22:93:29 | source() | tst.js:97:14:97:26 | map.get(true) |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:5:10:5:10 | y |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:7:10:7:17 | y.buffer |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:11:10:11:12 | arr |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:15:10:15:10 | z |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:18:10:18:12 | sub |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:42:10:42:30 | typedAr ... ring(y) |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:48:10:48:12 | str |
| typed-arrays.js:2:13:2:20 | source() | typed-arrays.js:52:10:52:13 | str2 |
| use-use-after-implicit-read.js:7:17:7:24 | source() | use-use-after-implicit-read.js:8:10:8:17 | captured |
| use-use-after-implicit-read.js:7:17:7:24 | source() | use-use-after-implicit-read.js:15:10:15:10 | x |
| xml.js:5:18:5:25 | source() | xml.js:8:14:8:17 | text |

View File

@@ -0,0 +1,53 @@
function test() {
let x = source();
let y = new Uint8Array(x);
sink(y); // NOT OK
sink(y.buffer); // NOT OK
sink(y.length);
var arr = new Uint8Array(y.buffer, y.byteOffset, y.byteLength);
sink(arr); // NOT OK
const z = new Uint8Array([1, 2, 3]);
z.set(y, 3);
sink(z); // NOT OK
const sub = y.subarray(1, 3)
sink(sub); // NOT OK
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
view.set(x, 3);
sink(buffer); // NOT OK -- Should be flagged but it is not.
const sharedBuffer = new SharedArrayBuffer(8);
const view1 = new Uint8Array(sharedBuffer);
view1.set(x, 3);
sink(sharedBuffer); // NOT OK -- Should be flagged but it is not.
const transfered = buffer.transfer();
const transferedView = new Uint8Array(transfered);
sink(transferedView); // NOT OK -- Should be flagged but it is not.
const transfered2 = buffer.transferToFixedLength();
const transferedView2 = new Uint8Array(transfered2);
sink(transferedView2); // NOT OK -- Should be flagged but it is not.
var typedArrayToString = (function () {
return function (a) { return String.fromCharCode.apply(null, a); };
})();
sink(typedArrayToString(y)); // NOT OK
let str = '';
for (let i = 0; i < y.length; i++)
str += String.fromCharCode(y[i]);
sink(str); // NOT OK
const decoder = new TextDecoder('utf-8');
const str2 = decoder.decode(y);
sink(str2); // NOT OK
}

View File

@@ -0,0 +1,74 @@
import { MyWebSocket, MySockJS, myWebSocketInstance, mySockJSInstance } from './browser.js';
(function () {
const socket = new MyWebSocket('ws://localhost:9080'); // $ clientSocket
socket.addEventListener('open', function (event) {
socket.send('Hi from browser!'); // $ clientSend
});
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data); // $ remoteFlow
}); // $ clientReceive
socket.onmessage = function (event) {
console.log("Message from server 2", event.data); // $ remoteFlow
}; // $ clientReceive
})();
(function () {
var sock = new MySockJS('http://0.0.0.0:9999/echo'); // $ clientSocket
sock.onopen = function () {
sock.send('test'); // $ clientSend
};
sock.onmessage = function (e) {
console.log('message', e.data); // $ remoteFlow
sock.close();
}; // $ clientReceive
sock.addEventListener('message', function (event) {
console.log('Using addEventListener ', event.data); // $ remoteFlow
}); // $ clientReceive
})();
(function () {
myWebSocketInstance.addEventListener('open', function (event) {
myWebSocketInstance.send('Hi from browser!'); // $ clientSend
});
myWebSocketInstance.addEventListener('message', function (event) {
console.log('Message from server ', event.data); // $ remoteFlow
}); // $ clientReceive
myWebSocketInstance.onmessage = function (event) {
console.log("Message from server 2", event.data); // $ remoteFlow
}; // $ clientReceive
})();
(function () {
mySockJSInstance.onopen = function () {
mySockJSInstance.send('test'); // $ clientSend
};
mySockJSInstance.onmessage = function (e) {
console.log('message', e.data); // $ remoteFlow
mySockJSInstance.close();
}; // $ clientReceive
mySockJSInstance.addEventListener('message', function (event) {
console.log('Using addEventListener ', event.data); // $ remoteFlow
}); // $ clientReceive
})();
const recv_message = function (e) {
console.log('Received message:', e.data); // $ remoteFlow
}; // $ clientReceive
(function () {
myWebSocketInstance.onmessage = recv_message.bind(this);
})();

View File

@@ -1,32 +1,37 @@
(function () {
const socket = new WebSocket('ws://localhost:8080');
const socket = new WebSocket('ws://localhost:8080'); // $clientSocket
socket.addEventListener('open', function (event) {
socket.send('Hi from browser!');
socket.send('Hi from browser!'); // $clientSend
});
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});
console.log('Message from server ', event.data); // $ remoteFlow
}); // $clientReceive
socket.onmessage = function (event) {
console.log("Message from server 2", event.data)
};
console.log("Message from server 2", event.data); // $ remoteFlow
}; // $clientReceive
})();
(function () {
var sock = new SockJS('http://0.0.0.0:9999/echo');
var sock = new SockJS('http://0.0.0.0:9999/echo'); // $clientSocket
sock.onopen = function () {
sock.send('test');
sock.send('test'); // $clientSend
};
sock.onmessage = function (e) {
console.log('message', e.data);
console.log('message', e.data); // $ remoteFlow
sock.close();
};
}; // $clientReceive
sock.addEventListener('message', function (event) {
console.log('Using addEventListener ', event.data);
});
})
console.log('Using addEventListener ', event.data); // $ remoteFlow
}); // $clientReceive
})();
export const MyWebSocket = WebSocket;
export const MySockJS = SockJS;
export const myWebSocketInstance = new WebSocket('ws://localhost:8080'); // $ clientSocket
export const mySockJSInstance = new SockJS('http://0.0.0.0:9999/echo'); // $ clientSocket

View File

@@ -0,0 +1,23 @@
const { MyWebSocketWS, myWebSocketWSInstance } = require('./client.js');
(function () {
const ws = new MyWebSocketWS('ws://example.org'); // $ clientSocket
ws.on('open', function open() {
ws.send('Hi from client!'); // $ clientSend
});
ws.on('message', function incoming(data) { // $ remoteFlow
console.log(data);
}); // $ clientReceive
})();
(function () {
myWebSocketWSInstance.on('open', function open() {
myWebSocketWSInstance.send('Hi from client!'); // $ clientSend
});
myWebSocketWSInstance.on('message', function incoming(data) { // $ remoteFlow
console.log(data);
}); // $ clientReceive
})();

View File

@@ -1,13 +1,16 @@
(function () {
const WebSocket = require('ws');
const WebSocket = require('ws');
const ws = new WebSocket('ws://example.org');
(function () {
const ws = new WebSocket('ws://example.org'); // $clientSocket
ws.on('open', function open() {
ws.send('Hi from client!');
ws.send('Hi from client!'); // $clientSend
});
ws.on('message', function incoming(data) {
ws.on('message', function incoming(data) { // $ remoteFlow
console.log(data);
});
})();
}); // $clientReceive
})();
module.exports.MyWebSocketWS = require('ws');
module.exports.myWebSocketWSInstance = new WebSocket('ws://example.org'); // $ clientSocket

View File

@@ -0,0 +1,23 @@
const { MyWebSocketServer, myWebSocketServerInstance } = require('./server.js');
(function () {
const wss = new MyWebSocketServer({ port: 8080 });
wss.on('connection', function connection(ws) { // $ serverSocket
ws.on('message', function incoming(message) { // $ remoteFlow
console.log('received: %s', message);
}); // $ serverReceive
ws.send('Hi from server!'); // $ serverSend
});
})();
(function () {
myWebSocketServerInstance.on('connection', function connection(ws) { // $ serverSocket
ws.on('message', function incoming(message) { // $ remoteFlow
console.log('received: %s', message);
}); // $ serverReceive
ws.send('Hi from server!'); // $ serverSend
});
})();

View File

@@ -1,13 +1,16 @@
(function () {
const WebSocket = require('ws');
const WebSocket = require('ws');
(function () {
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
wss.on('connection', function connection(ws) { // $serverSocket
ws.on('message', function incoming(message) { // $remoteFlow
console.log('received: %s', message);
});
}); // $serverReceive
ws.send('Hi from server!');
ws.send('Hi from server!'); // $serverSend
});
})();
})();
module.exports.MyWebSocketServer = require('ws').Server;
module.exports.myWebSocketServerInstance = new WebSocket.Server({ port: 8080 });

View File

@@ -5,11 +5,11 @@ const sockjs = require('sockjs');
const app = express();
const server = http.createServer(app);
const sockjs_echo = sockjs.createServer({});
sockjs_echo.on('connection', function (conn) {
conn.on('data', function (message) {
sockjs_echo.on('connection', function (conn) { // $serverSocket
conn.on('data', function (message) { // $remoteFlow
var data = JSON.parse(message);
conn.write(JSON.stringify(eval(data.test)));
});
conn.write(JSON.stringify(eval(data.test))); // $serverSend
}); // $serverReceive
});
sockjs_echo.installHandlers(server, { prefix: '/echo' });

View File

@@ -1,35 +1,139 @@
clientSocket
| browser.js:2:17:2:52 | new Web ... :8080') |
| browser.js:19:13:19:50 | new Soc ... /echo') |
| client.js:4:13:4:45 | new Web ... e.org') |
clientReceive
| browser-custom.js:10:37:12:2 | functio ... Flow\\n\\t} |
| browser-custom.js:14:21:16:2 | functio ... Flow\\n\\t} |
| browser-custom.js:26:19:29:2 | functio ... e();\\n\\t} |
| browser-custom.js:31:35:33:2 | functio ... Flow\\n\\t} |
| browser-custom.js:42:53:44:5 | functio ... w\\n } |
| browser-custom.js:46:37:48:5 | functio ... w\\n } |
| browser-custom.js:57:34:60:5 | functio ... ;\\n } |
| browser-custom.js:62:50:64:5 | functio ... w\\n } |
| browser-custom.js:68:22:70:1 | functio ... eFlow\\n} |
| browser.js:8:37:10:2 | functio ... Flow\\n\\t} |
| browser.js:12:21:14:2 | functio ... Flow\\n\\t} |
| browser.js:24:19:27:2 | functio ... e();\\n\\t} |
| browser.js:29:35:31:2 | functio ... Flow\\n\\t} |
| client-custom.js:10:19:12:2 | functio ... ta);\\n\\t} |
| client-custom.js:20:38:22:2 | functio ... ta);\\n\\t} |
| client.js:10:19:12:2 | functio ... ta);\\n\\t} |
clientSend
| browser-custom.js:7:3:7:33 | socket. ... wser!') |
| browser-custom.js:23:3:23:19 | sock.send('test') |
| browser-custom.js:39:9:39:52 | myWebSo ... wser!') |
| browser-custom.js:54:9:54:37 | mySockJ ... 'test') |
| browser.js:5:3:5:33 | socket. ... wser!') |
| browser.js:21:3:21:19 | sock.send('test') |
| client-custom.js:7:3:7:28 | ws.send ... ient!') |
| client-custom.js:17:3:17:47 | myWebSo ... ient!') |
| client.js:7:3:7:28 | ws.send ... ient!') |
clientReceive
| browser.js:8:37:10:2 | functio ... ta);\\n\\t} |
| browser.js:12:21:14:2 | functio ... ata)\\n\\t} |
| browser.js:24:19:27:2 | functio ... e();\\n\\t} |
| browser.js:29:35:31:2 | functio ... ta);\\n\\t} |
| client.js:10:19:12:2 | functio ... ta);\\n\\t} |
serverSocket
| server.js:6:43:6:44 | ws |
| sockjs.js:8:40:8:43 | conn |
serverSend
| server.js:11:3:11:28 | ws.send ... rver!') |
| sockjs.js:11:9:11:51 | conn.wr ... test))) |
serverReceive
| server.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) |
| sockjs.js:9:5:12:6 | conn.on ... \\n }) |
clientSocket
| browser-custom.js:4:17:4:54 | new MyW ... :9080') |
| browser-custom.js:21:13:21:52 | new MyS ... /echo') |
| browser.js:2:17:2:52 | new Web ... :8080') |
| browser.js:19:13:19:50 | new Soc ... /echo') |
| browser.js:36:36:36:71 | new Web ... :8080') |
| browser.js:37:33:37:70 | new Soc ... /echo') |
| client-custom.js:4:13:4:49 | new MyW ... e.org') |
| client.js:4:13:4:45 | new Web ... e.org') |
| client.js:16:40:16:72 | new Web ... e.org') |
flowSteps
| browser-custom.js:1:10:1:20 | MyWebSocket | browser-custom.js:1:10:1:20 | MyWebSocket |
| browser-custom.js:1:23:1:30 | MySockJS | browser-custom.js:1:23:1:30 | MySockJS |
| browser-custom.js:1:33:1:51 | myWebSocketInstance | browser-custom.js:1:33:1:51 | myWebSocketInstance |
| browser-custom.js:1:54:1:69 | mySockJSInstance | browser-custom.js:1:54:1:69 | mySockJSInstance |
| browser-custom.js:7:15:7:32 | 'Hi from browser!' | server-custom.js:7:38:7:44 | message |
| browser-custom.js:7:15:7:32 | 'Hi from browser!' | server-custom.js:17:38:17:44 | message |
| browser-custom.js:7:15:7:32 | 'Hi from browser!' | server.js:7:38:7:44 | message |
| browser-custom.js:23:13:23:18 | 'test' | sockjs.js:9:31:9:37 | message |
| browser-custom.js:39:34:39:51 | 'Hi from browser!' | server-custom.js:7:38:7:44 | message |
| browser-custom.js:39:34:39:51 | 'Hi from browser!' | server-custom.js:17:38:17:44 | message |
| browser-custom.js:39:34:39:51 | 'Hi from browser!' | server.js:7:38:7:44 | message |
| browser-custom.js:54:31:54:36 | 'test' | sockjs.js:9:31:9:37 | message |
| browser.js:5:15:5:32 | 'Hi from browser!' | server-custom.js:7:38:7:44 | message |
| browser.js:5:15:5:32 | 'Hi from browser!' | server-custom.js:17:38:17:44 | message |
| browser.js:5:15:5:32 | 'Hi from browser!' | server.js:7:38:7:44 | message |
| browser.js:21:13:21:18 | 'test' | sockjs.js:9:31:9:37 | message |
| client-custom.js:7:11:7:27 | 'Hi from client!' | server-custom.js:7:38:7:44 | message |
| client-custom.js:7:11:7:27 | 'Hi from client!' | server-custom.js:17:38:17:44 | message |
| client-custom.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message |
| client-custom.js:17:30:17:46 | 'Hi from client!' | server-custom.js:7:38:7:44 | message |
| client-custom.js:17:30:17:46 | 'Hi from client!' | server-custom.js:17:38:17:44 | message |
| client-custom.js:17:30:17:46 | 'Hi from client!' | server.js:7:38:7:44 | message |
| client.js:7:11:7:27 | 'Hi from client!' | server-custom.js:7:38:7:44 | message |
| client.js:7:11:7:27 | 'Hi from client!' | server-custom.js:17:38:17:44 | message |
| client.js:7:11:7:27 | 'Hi from client!' | server.js:7:38:7:44 | message |
| client.js:15:32:15:44 | require('ws') | client-custom.js:1:9:1:21 | MyWebSocketWS |
| client.js:16:40:16:72 | new Web ... e.org') | client-custom.js:1:24:1:44 | myWebSo ... nstance |
| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:11:39:11:48 | event.data |
| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data |
| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:43:45:43:54 | event.data |
| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:47:46:47:55 | event.data |
| server-custom.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:69:38:69:43 | e.data |
| server-custom.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data |
| server-custom.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data |
| server-custom.js:11:11:11:27 | 'Hi from server!' | client-custom.js:10:37:10:40 | data |
| server-custom.js:11:11:11:27 | 'Hi from server!' | client-custom.js:20:56:20:59 | data |
| server-custom.js:11:11:11:27 | 'Hi from server!' | client.js:10:37:10:40 | data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:11:39:11:48 | event.data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:43:45:43:54 | event.data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:47:46:47:55 | event.data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | browser-custom.js:69:38:69:43 | e.data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | client-custom.js:10:37:10:40 | data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | client-custom.js:20:56:20:59 | data |
| server-custom.js:21:11:21:27 | 'Hi from server!' | client.js:10:37:10:40 | data |
| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:11:39:11:48 | event.data |
| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:15:40:15:49 | event.data |
| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:43:45:43:54 | event.data |
| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:47:46:47:55 | event.data |
| server.js:11:11:11:27 | 'Hi from server!' | browser-custom.js:69:38:69:43 | e.data |
| server.js:11:11:11:27 | 'Hi from server!' | browser.js:9:39:9:48 | event.data |
| server.js:11:11:11:27 | 'Hi from server!' | browser.js:13:40:13:49 | event.data |
| server.js:11:11:11:27 | 'Hi from server!' | client-custom.js:10:37:10:40 | data |
| server.js:11:11:11:27 | 'Hi from server!' | client-custom.js:20:56:20:59 | data |
| server.js:11:11:11:27 | 'Hi from server!' | client.js:10:37:10:40 | data |
| server.js:15:36:15:55 | require('ws').Server | server-custom.js:1:9:1:25 | MyWebSocketServer |
| server.js:16:44:16:79 | new Web ... 8080 }) | server-custom.js:1:28:1:52 | myWebSo ... nstance |
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:27:26:27:31 | e.data |
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:32:42:32:51 | event.data |
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:58:32:58:37 | e.data |
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser-custom.js:63:48:63:57 | event.data |
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:25:26:25:31 | e.data |
| sockjs.js:11:20:11:50 | JSON.st ... .test)) | browser.js:30:42:30:51 | event.data |
remoteFlow
| browser-custom.js:11:39:11:48 | event.data |
| browser-custom.js:15:40:15:49 | event.data |
| browser-custom.js:27:26:27:31 | e.data |
| browser-custom.js:32:42:32:51 | event.data |
| browser-custom.js:43:45:43:54 | event.data |
| browser-custom.js:47:46:47:55 | event.data |
| browser-custom.js:58:32:58:37 | e.data |
| browser-custom.js:63:48:63:57 | event.data |
| browser-custom.js:69:38:69:43 | e.data |
| browser.js:9:39:9:48 | event.data |
| browser.js:13:40:13:49 | event.data |
| browser.js:25:26:25:31 | e.data |
| browser.js:30:42:30:51 | event.data |
| client-custom.js:10:37:10:40 | data |
| client-custom.js:20:56:20:59 | data |
| client.js:10:37:10:40 | data |
| server-custom.js:7:38:7:44 | message |
| server-custom.js:17:38:17:44 | message |
| server.js:7:38:7:44 | message |
| sockjs.js:9:31:9:37 | message |
serverReceive
| server-custom.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) |
| server-custom.js:17:3:19:4 | ws.on(' ... );\\n\\t\\t}) |
| server.js:7:3:9:4 | ws.on(' ... );\\n\\t\\t}) |
| sockjs.js:9:5:12:6 | conn.on ... \\n }) |
serverSend
| server-custom.js:11:3:11:28 | ws.send ... rver!') |
| server-custom.js:21:3:21:28 | ws.send ... rver!') |
| server.js:11:3:11:28 | ws.send ... rver!') |
| sockjs.js:11:9:11:51 | conn.wr ... test))) |
serverSocket
| server-custom.js:6:43:6:44 | ws |
| server-custom.js:16:65:16:66 | ws |
| server.js:6:43:6:44 | ws |
| sockjs.js:8:40:8:43 | conn |

View File

@@ -0,0 +1,2 @@
query: test.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -27,6 +27,14 @@
| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to a $@. | ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | user-provided value |
| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to a $@. | ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | user-provided value |
| ReflectedXssGood3.js:139:12:139:27 | escapeHtml3(url) | ReflectedXssGood3.js:135:15:135:27 | req.params.id | ReflectedXssGood3.js:139:12:139:27 | escapeHtml3(url) | Cross-site scripting vulnerability due to a $@. | ReflectedXssGood3.js:135:15:135:27 | req.params.id | user-provided value |
| app/api/route.ts:5:18:5:21 | body | app/api/route.ts:2:24:2:33 | req.json() | app/api/route.ts:5:18:5:21 | body | Cross-site scripting vulnerability due to a $@. | app/api/route.ts:2:24:2:33 | req.json() | user-provided value |
| app/api/route.ts:13:18:13:21 | body | app/api/route.ts:2:24:2:33 | req.json() | app/api/route.ts:13:18:13:21 | body | Cross-site scripting vulnerability due to a $@. | app/api/route.ts:2:24:2:33 | req.json() | user-provided value |
| app/api/route.ts:25:18:25:21 | body | app/api/route.ts:2:24:2:33 | req.json() | app/api/route.ts:25:18:25:21 | body | Cross-site scripting vulnerability due to a $@. | app/api/route.ts:2:24:2:33 | req.json() | user-provided value |
| app/api/route.ts:29:25:29:28 | body | app/api/route.ts:2:24:2:33 | req.json() | app/api/route.ts:29:25:29:28 | body | Cross-site scripting vulnerability due to a $@. | app/api/route.ts:2:24:2:33 | req.json() | user-provided value |
| app/api/routeNextRequest.ts:7:20:7:23 | body | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | app/api/routeNextRequest.ts:7:20:7:23 | body | Cross-site scripting vulnerability due to a $@. | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | user-provided value |
| app/api/routeNextRequest.ts:15:20:15:23 | body | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | app/api/routeNextRequest.ts:15:20:15:23 | body | Cross-site scripting vulnerability due to a $@. | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | user-provided value |
| app/api/routeNextRequest.ts:27:20:27:23 | body | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | app/api/routeNextRequest.ts:27:20:27:23 | body | Cross-site scripting vulnerability due to a $@. | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | user-provided value |
| app/api/routeNextRequest.ts:31:27:31:30 | body | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | app/api/routeNextRequest.ts:31:27:31:30 | body | Cross-site scripting vulnerability due to a $@. | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | user-provided value |
| etherpad.js:11:12:11:19 | response | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:11:12:11:19 | response | Cross-site scripting vulnerability due to a $@. | etherpad.js:9:16:9:30 | req.query.jsonp | user-provided value |
| formatting.js:6:14:6:47 | util.fo ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to a $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
| formatting.js:7:14:7:53 | require ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to a $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
@@ -128,6 +136,18 @@ edges
| ReflectedXssGood3.js:135:15:135:27 | req.params.id | ReflectedXssGood3.js:135:9:135:27 | url | provenance | |
| ReflectedXssGood3.js:139:24:139:26 | url | ReflectedXssGood3.js:68:22:68:26 | value | provenance | |
| ReflectedXssGood3.js:139:24:139:26 | url | ReflectedXssGood3.js:139:12:139:27 | escapeHtml3(url) | provenance | |
| app/api/route.ts:2:11:2:33 | body | app/api/route.ts:5:18:5:21 | body | provenance | |
| app/api/route.ts:2:11:2:33 | body | app/api/route.ts:13:18:13:21 | body | provenance | |
| app/api/route.ts:2:11:2:33 | body | app/api/route.ts:25:18:25:21 | body | provenance | |
| app/api/route.ts:2:11:2:33 | body | app/api/route.ts:29:25:29:28 | body | provenance | |
| app/api/route.ts:2:18:2:33 | await req.json() | app/api/route.ts:2:11:2:33 | body | provenance | |
| app/api/route.ts:2:24:2:33 | req.json() | app/api/route.ts:2:18:2:33 | await req.json() | provenance | |
| app/api/routeNextRequest.ts:4:9:4:31 | body | app/api/routeNextRequest.ts:7:20:7:23 | body | provenance | |
| app/api/routeNextRequest.ts:4:9:4:31 | body | app/api/routeNextRequest.ts:15:20:15:23 | body | provenance | |
| app/api/routeNextRequest.ts:4:9:4:31 | body | app/api/routeNextRequest.ts:27:20:27:23 | body | provenance | |
| app/api/routeNextRequest.ts:4:9:4:31 | body | app/api/routeNextRequest.ts:31:27:31:30 | body | provenance | |
| app/api/routeNextRequest.ts:4:16:4:31 | await req.json() | app/api/routeNextRequest.ts:4:9:4:31 | body | provenance | |
| app/api/routeNextRequest.ts:4:22:4:31 | req.json() | app/api/routeNextRequest.ts:4:16:4:31 | await req.json() | provenance | |
| etherpad.js:9:5:9:53 | response | etherpad.js:11:12:11:19 | response | provenance | |
| etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:9:5:9:53 | response | provenance | |
| formatting.js:4:9:4:29 | evil | formatting.js:6:43:6:46 | evil | provenance | |
@@ -309,6 +329,20 @@ nodes
| ReflectedXssGood3.js:135:15:135:27 | req.params.id | semmle.label | req.params.id |
| ReflectedXssGood3.js:139:12:139:27 | escapeHtml3(url) | semmle.label | escapeHtml3(url) |
| ReflectedXssGood3.js:139:24:139:26 | url | semmle.label | url |
| app/api/route.ts:2:11:2:33 | body | semmle.label | body |
| app/api/route.ts:2:18:2:33 | await req.json() | semmle.label | await req.json() |
| app/api/route.ts:2:24:2:33 | req.json() | semmle.label | req.json() |
| app/api/route.ts:5:18:5:21 | body | semmle.label | body |
| app/api/route.ts:13:18:13:21 | body | semmle.label | body |
| app/api/route.ts:25:18:25:21 | body | semmle.label | body |
| app/api/route.ts:29:25:29:28 | body | semmle.label | body |
| app/api/routeNextRequest.ts:4:9:4:31 | body | semmle.label | body |
| app/api/routeNextRequest.ts:4:16:4:31 | await req.json() | semmle.label | await req.json() |
| app/api/routeNextRequest.ts:4:22:4:31 | req.json() | semmle.label | req.json() |
| app/api/routeNextRequest.ts:7:20:7:23 | body | semmle.label | body |
| app/api/routeNextRequest.ts:15:20:15:23 | body | semmle.label | body |
| app/api/routeNextRequest.ts:27:20:27:23 | body | semmle.label | body |
| app/api/routeNextRequest.ts:31:27:31:30 | body | semmle.label | body |
| etherpad.js:9:5:9:53 | response | semmle.label | response |
| etherpad.js:9:16:9:30 | req.query.jsonp | semmle.label | req.query.jsonp |
| etherpad.js:11:12:11:19 | response | semmle.label | response |

View File

@@ -26,6 +26,14 @@
| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | user-provided value |
| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | user-provided value |
| ReflectedXssGood3.js:139:12:139:27 | escapeHtml3(url) | Cross-site scripting vulnerability due to $@. | ReflectedXssGood3.js:135:15:135:27 | req.params.id | user-provided value |
| app/api/route.ts:5:18:5:21 | body | Cross-site scripting vulnerability due to $@. | app/api/route.ts:2:24:2:33 | req.json() | user-provided value |
| app/api/route.ts:13:18:13:21 | body | Cross-site scripting vulnerability due to $@. | app/api/route.ts:2:24:2:33 | req.json() | user-provided value |
| app/api/route.ts:25:18:25:21 | body | Cross-site scripting vulnerability due to $@. | app/api/route.ts:2:24:2:33 | req.json() | user-provided value |
| app/api/route.ts:29:25:29:28 | body | Cross-site scripting vulnerability due to $@. | app/api/route.ts:2:24:2:33 | req.json() | user-provided value |
| app/api/routeNextRequest.ts:7:20:7:23 | body | Cross-site scripting vulnerability due to $@. | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | user-provided value |
| app/api/routeNextRequest.ts:15:20:15:23 | body | Cross-site scripting vulnerability due to $@. | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | user-provided value |
| app/api/routeNextRequest.ts:27:20:27:23 | body | Cross-site scripting vulnerability due to $@. | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | user-provided value |
| app/api/routeNextRequest.ts:31:27:31:30 | body | Cross-site scripting vulnerability due to $@. | app/api/routeNextRequest.ts:4:22:4:31 | req.json() | user-provided value |
| formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
| formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value |
| live-server.js:6:13:6:50 | `<html> ... /html>` | Cross-site scripting vulnerability due to $@. | live-server.js:4:21:4:27 | req.url | user-provided value |

View File

@@ -0,0 +1,30 @@
export async function POST(req: Request) {
const body = await req.json(); // $ Source
new Response(body, {headers: { 'Content-Type': 'application/json' }});
new Response(body, {headers: { 'Content-Type': 'text/html' }}); // $ Alert
const headers2 = new Headers(req.headers);
headers2.append('Content-Type', 'application/json');
new Response(body, { headers: headers2 });
const headers3 = new Headers(req.headers);
headers3.append('Content-Type', 'text/html');
new Response(body, { headers: headers3 }); // $ Alert
const headers4 = new Headers({
...Object.fromEntries(req.headers),
'Content-Type': 'application/json'
});
new Response(body, { headers: headers4 });
const headers5 = new Headers({
...Object.fromEntries(req.headers),
'Content-Type': 'text/html'
});
new Response(body, { headers: headers5 }); // $ Alert
const headers = new Headers(req.headers);
headers.set('Content-Type', 'text/html');
return new Response(body, { headers }); // $ Alert
}

View File

@@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const body = await req.json(); // $ Source
new NextResponse(body, {headers: { 'Content-Type': 'application/json' }});
new NextResponse(body, {headers: { 'Content-Type': 'text/html' }}); // $ Alert
const headers2 = new Headers(req.headers);
headers2.append('Content-Type', 'application/json');
new NextResponse(body, { headers: headers2 });
const headers3 = new Headers(req.headers);
headers3.append('Content-Type', 'text/html');
new NextResponse(body, { headers: headers3 }); // $ Alert
const headers4 = new Headers({
...Object.fromEntries(req.headers),
'Content-Type': 'application/json'
});
new NextResponse(body, { headers: headers4 });
const headers5 = new Headers({
...Object.fromEntries(req.headers),
'Content-Type': 'text/html'
});
new NextResponse(body, { headers: headers5 }); // $ Alert
const headers = new Headers(req.headers);
headers.set('Content-Type', 'text/html');
return new NextResponse(body, { headers }); // $ Alert
}

View File

@@ -81,9 +81,12 @@ edges
| pako.js:18:48:18:66 | zipFile.data.buffer | pako.js:18:33:18:67 | new Uin ... buffer) | provenance | Config |
| pako.js:28:19:28:25 | zipFile | pako.js:29:36:29:42 | zipFile | provenance | |
| pako.js:29:11:29:62 | myArray | pako.js:32:31:32:37 | myArray | provenance | |
| pako.js:29:11:29:62 | myArray [ArrayElement] | pako.js:32:31:32:37 | myArray | provenance | |
| pako.js:29:21:29:55 | new Uin ... buffer) | pako.js:29:11:29:62 | myArray | provenance | |
| pako.js:29:21:29:55 | new Uin ... buffer) [ArrayElement] | pako.js:29:11:29:62 | myArray [ArrayElement] | provenance | |
| pako.js:29:36:29:42 | zipFile | pako.js:29:36:29:54 | zipFile.data.buffer | provenance | |
| pako.js:29:36:29:54 | zipFile.data.buffer | pako.js:29:21:29:55 | new Uin ... buffer) | provenance | Config |
| pako.js:29:36:29:54 | zipFile.data.buffer | pako.js:29:21:29:55 | new Uin ... buffer) [ArrayElement] | provenance | |
| unbzip2.js:12:5:12:43 | fs.crea ... lePath) | unbzip2.js:12:50:12:54 | bz2() | provenance | Config |
| unbzip2.js:12:25:12:42 | req.query.FilePath | unbzip2.js:12:5:12:43 | fs.crea ... lePath) | provenance | Config |
| unzipper.js:13:26:13:62 | Readabl ... e.data) | unzipper.js:16:23:16:63 | unzippe ... ath' }) | provenance | Config |
@@ -183,7 +186,9 @@ nodes
| pako.js:21:31:21:37 | myArray | semmle.label | myArray |
| pako.js:28:19:28:25 | zipFile | semmle.label | zipFile |
| pako.js:29:11:29:62 | myArray | semmle.label | myArray |
| pako.js:29:11:29:62 | myArray [ArrayElement] | semmle.label | myArray [ArrayElement] |
| pako.js:29:21:29:55 | new Uin ... buffer) | semmle.label | new Uin ... buffer) |
| pako.js:29:21:29:55 | new Uin ... buffer) [ArrayElement] | semmle.label | new Uin ... buffer) [ArrayElement] |
| pako.js:29:36:29:42 | zipFile | semmle.label | zipFile |
| pako.js:29:36:29:54 | zipFile.data.buffer | semmle.label | zipFile.data.buffer |
| pako.js:32:31:32:37 | myArray | semmle.label | myArray |

View File

@@ -1,2 +0,0 @@
consistencyIssue
resultInWrongFile

View File

@@ -1,25 +0,0 @@
import javascript
import semmle.javascript.security.dataflow.RequestForgeryQuery as RequestForgery
import semmle.javascript.security.dataflow.ClientSideRequestForgeryQuery as ClientSideRequestForgery
deprecated import utils.test.ConsistencyChecking
query predicate resultInWrongFile(DataFlow::Node node) {
exists(string filePattern |
RequestForgery::RequestForgeryFlow::flowTo(node) and
filePattern = ".*serverSide.*"
or
ClientSideRequestForgery::ClientSideRequestForgeryFlow::flowTo(node) and
filePattern = ".*clientSide.*"
|
not node.getFile().getRelativePath().regexpMatch(filePattern)
)
}
deprecated class Consistency extends ConsistencyConfiguration {
Consistency() { this = "Consistency" }
override DataFlow::Node getAnAlert() {
RequestForgery::RequestForgeryFlow::flowTo(result) or
ClientSideRequestForgery::ClientSideRequestForgeryFlow::flowTo(result)
}
}

View File

@@ -0,0 +1,5 @@
export async function POST(req: Request) {
const { url } = await req.json(); // $ Source[js/request-forgery]
const res = await fetch(url); // $ Alert[js/request-forgery] Sink[js/request-forgery]
return new Response(res.body, { headers: res.headers });
}

View File

@@ -0,0 +1,8 @@
import { NextRequest, NextResponse } from 'next/server';
export async function POST(req: NextRequest) {
const { url } = await req.json(); // $ Source[js/request-forgery]
const res = await fetch(url); // $ Alert[js/request-forgery] Sink[js/request-forgery]
const data = await res.text();
return new NextResponse(data, { headers: res.headers });
}

View File

@@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from 'next/server';
export async function middleware(req: NextRequest) {
const target = req.nextUrl // $ Source[js/request-forgery]
const target2 = target.searchParams.get('target'); // $ Source[js/request-forgery]
if (target) {
const res = await fetch(target) // $ Alert[js/request-forgery] Sink[js/request-forgery]
const data = await res.text()
return new NextResponse(data)
}
if (target2) {
const res = await fetch(target2); // $ Alert[js/request-forgery] Sink[js/request-forgery]
const data = await res.text();
return new NextResponse(data);
}
return NextResponse.next()
}

View File

@@ -0,0 +1,13 @@
{
"name": "next-edge-proxy-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "15.1.7"
}
}

View File

@@ -1,4 +1,8 @@
#select
| Request/app/api/proxy/route2.serverSide.ts:5:21:5:30 | fetch(url) | Request/app/api/proxy/route2.serverSide.ts:4:25:4:34 | req.json() | Request/app/api/proxy/route2.serverSide.ts:5:27:5:29 | url | The $@ of this request depends on a $@. | Request/app/api/proxy/route2.serverSide.ts:5:27:5:29 | url | URL | Request/app/api/proxy/route2.serverSide.ts:4:25:4:34 | req.json() | user-provided value |
| Request/app/api/proxy/route.serverSide.ts:3:21:3:30 | fetch(url) | Request/app/api/proxy/route.serverSide.ts:2:25:2:34 | req.json() | Request/app/api/proxy/route.serverSide.ts:3:27:3:29 | url | The $@ of this request depends on a $@. | Request/app/api/proxy/route.serverSide.ts:3:27:3:29 | url | URL | Request/app/api/proxy/route.serverSide.ts:2:25:2:34 | req.json() | user-provided value |
| Request/middleware.ts:7:25:7:37 | fetch(target) | Request/middleware.ts:4:20:4:30 | req.nextUrl | Request/middleware.ts:7:31:7:36 | target | The $@ of this request depends on a $@. | Request/middleware.ts:7:31:7:36 | target | URL | Request/middleware.ts:4:20:4:30 | req.nextUrl | user-provided value |
| Request/middleware.ts:12:27:12:40 | fetch(target2) | Request/middleware.ts:5:21:5:53 | target. ... arget') | Request/middleware.ts:12:33:12:39 | target2 | The $@ of this request depends on a $@. | Request/middleware.ts:12:33:12:39 | target2 | URL | Request/middleware.ts:5:21:5:53 | target. ... arget') | user-provided value |
| apollo.serverSide.ts:8:39:8:64 | get(fil ... => {}) | apollo.serverSide.ts:7:36:7:44 | { files } | apollo.serverSide.ts:8:43:8:50 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:8:43:8:50 | file.url | URL | apollo.serverSide.ts:7:36:7:44 | { files } | user-provided value |
| apollo.serverSide.ts:18:37:18:62 | get(fil ... => {}) | apollo.serverSide.ts:17:34:17:42 | { files } | apollo.serverSide.ts:18:41:18:48 | file.url | The $@ of this request depends on a $@. | apollo.serverSide.ts:18:41:18:48 | file.url | URL | apollo.serverSide.ts:17:34:17:42 | { files } | user-provided value |
| axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | The $@ of this request depends on a $@. | axiosInterceptors.serverSide.js:11:26:11:40 | userProvidedUrl | endpoint | axiosInterceptors.serverSide.js:19:21:19:28 | req.body | user-provided value |
@@ -27,6 +31,18 @@
| serverSide.js:125:5:128:6 | axios({ ... \\n }) | serverSide.js:123:29:123:35 | req.url | serverSide.js:127:14:127:20 | tainted | The $@ of this request depends on a $@. | serverSide.js:127:14:127:20 | tainted | URL | serverSide.js:123:29:123:35 | req.url | user-provided value |
| serverSide.js:131:5:131:20 | axios.get(myUrl) | serverSide.js:123:29:123:35 | req.url | serverSide.js:131:15:131:19 | myUrl | The $@ of this request depends on a $@. | serverSide.js:131:15:131:19 | myUrl | URL | serverSide.js:123:29:123:35 | req.url | user-provided value |
edges
| Request/app/api/proxy/route2.serverSide.ts:4:9:4:15 | { url } | Request/app/api/proxy/route2.serverSide.ts:4:9:4:34 | url | provenance | |
| Request/app/api/proxy/route2.serverSide.ts:4:9:4:34 | url | Request/app/api/proxy/route2.serverSide.ts:5:27:5:29 | url | provenance | |
| Request/app/api/proxy/route2.serverSide.ts:4:19:4:34 | await req.json() | Request/app/api/proxy/route2.serverSide.ts:4:9:4:15 | { url } | provenance | |
| Request/app/api/proxy/route2.serverSide.ts:4:25:4:34 | req.json() | Request/app/api/proxy/route2.serverSide.ts:4:19:4:34 | await req.json() | provenance | |
| Request/app/api/proxy/route.serverSide.ts:2:9:2:15 | { url } | Request/app/api/proxy/route.serverSide.ts:2:9:2:34 | url | provenance | |
| Request/app/api/proxy/route.serverSide.ts:2:9:2:34 | url | Request/app/api/proxy/route.serverSide.ts:3:27:3:29 | url | provenance | |
| Request/app/api/proxy/route.serverSide.ts:2:19:2:34 | await req.json() | Request/app/api/proxy/route.serverSide.ts:2:9:2:15 | { url } | provenance | |
| Request/app/api/proxy/route.serverSide.ts:2:25:2:34 | req.json() | Request/app/api/proxy/route.serverSide.ts:2:19:2:34 | await req.json() | provenance | |
| Request/middleware.ts:4:11:4:30 | target | Request/middleware.ts:7:31:7:36 | target | provenance | |
| Request/middleware.ts:4:20:4:30 | req.nextUrl | Request/middleware.ts:4:11:4:30 | target | provenance | |
| Request/middleware.ts:5:11:5:53 | target2 | Request/middleware.ts:12:33:12:39 | target2 | provenance | |
| Request/middleware.ts:5:21:5:53 | target. ... arget') | Request/middleware.ts:5:11:5:53 | target2 | provenance | |
| apollo.serverSide.ts:7:36:7:44 | files | apollo.serverSide.ts:8:13:8:17 | files | provenance | |
| apollo.serverSide.ts:7:36:7:44 | { files } | apollo.serverSide.ts:7:36:7:44 | files | provenance | |
| apollo.serverSide.ts:8:13:8:17 | files | apollo.serverSide.ts:8:28:8:31 | file | provenance | |
@@ -91,6 +107,22 @@ edges
| serverSide.js:130:9:130:45 | myUrl | serverSide.js:131:15:131:19 | myUrl | provenance | |
| serverSide.js:130:37:130:43 | tainted | serverSide.js:130:9:130:45 | myUrl | provenance | |
nodes
| Request/app/api/proxy/route2.serverSide.ts:4:9:4:15 | { url } | semmle.label | { url } |
| Request/app/api/proxy/route2.serverSide.ts:4:9:4:34 | url | semmle.label | url |
| Request/app/api/proxy/route2.serverSide.ts:4:19:4:34 | await req.json() | semmle.label | await req.json() |
| Request/app/api/proxy/route2.serverSide.ts:4:25:4:34 | req.json() | semmle.label | req.json() |
| Request/app/api/proxy/route2.serverSide.ts:5:27:5:29 | url | semmle.label | url |
| Request/app/api/proxy/route.serverSide.ts:2:9:2:15 | { url } | semmle.label | { url } |
| Request/app/api/proxy/route.serverSide.ts:2:9:2:34 | url | semmle.label | url |
| Request/app/api/proxy/route.serverSide.ts:2:19:2:34 | await req.json() | semmle.label | await req.json() |
| Request/app/api/proxy/route.serverSide.ts:2:25:2:34 | req.json() | semmle.label | req.json() |
| Request/app/api/proxy/route.serverSide.ts:3:27:3:29 | url | semmle.label | url |
| Request/middleware.ts:4:11:4:30 | target | semmle.label | target |
| Request/middleware.ts:4:20:4:30 | req.nextUrl | semmle.label | req.nextUrl |
| Request/middleware.ts:5:11:5:53 | target2 | semmle.label | target2 |
| Request/middleware.ts:5:21:5:53 | target. ... arget') | semmle.label | target. ... arget') |
| Request/middleware.ts:7:31:7:36 | target | semmle.label | target |
| Request/middleware.ts:12:33:12:39 | target2 | semmle.label | target2 |
| apollo.serverSide.ts:7:36:7:44 | files | semmle.label | files |
| apollo.serverSide.ts:7:36:7:44 | { files } | semmle.label | { files } |
| apollo.serverSide.ts:8:13:8:17 | files | semmle.label | files |

12
ruby/ql/src/Sanitizers.ql Normal file
View File

@@ -0,0 +1,12 @@
/**
* @name Sanitizers
* @id rb/meta/sanitizers
* @kind problem
* @severity info
*/
import codeql.ruby.DataFlow
import codeql.ruby.security.XSS
from StoredXss::Sanitizer s
where s instanceof DataFlow::CallNode
select s, "XSS sanitizer"

View File

@@ -0,0 +1,4 @@
---
category: majorAnalysis
---
* The query `rb/uninitialized-local-variable` now only produces alerts when the variable is the receiver of a method call and should produce very few false positives. It also now comes with a help file.

View File

@@ -2,4 +2,5 @@
- include:
id:
- rb/database-query-in-loop
- rb/useless-assignment-to-local
- rb/useless-assignment-to-local
- rb/uninitialized-local-variable

View File

@@ -0,0 +1,41 @@
# Method call on `nil`
## Description
In Ruby, it is not necessary to explicitly initialize variables.
If a local variable has not been explicitly initialized, it will have the value `nil`. If this happens unintended, though, the variable will not represent an object with the expected methods, and a method call on the variable will raise a `NoMethodError`.
## Recommendation
Ensure that the variable cannot be `nil` at the point hightligted by the alert.
This can be achieved by using a safe navigation or adding a check for `nil`.
Note: You do not need to explicitly initialize the variable, if you can make the program deal with the possible `nil` value. In particular, initializing the variable to `nil` will have no effect, as this is already the value of the variable. If `nil` is the only possibly default value, you need to handle the `nil` value instead of initializing the variable.
## Examples
In the following code, the call to `create_file` may fail and then the call `f.close` will raise a `NoMethodError` since `f` will be `nil` at that point.
```ruby
def dump(x)
f = create_file
f.puts(x)
ensure
f.close
end
```
We can fix this by using safe navigation:
```ruby
def dump(x)
f = create_file
f.puts(x)
ensure
f&.close
end
```
## References
- https://www.rubyguides.com/: [Nil](https://www.rubyguides.com/2018/01/ruby-nil/)
- https://ruby-doc.org/: [NoMethodError](https://ruby-doc.org/core-2.6.5/NoMethodError.html)

View File

@@ -5,20 +5,91 @@
* @kind problem
* @problem.severity error
* @id rb/uninitialized-local-variable
* @tags reliability
* @tags quality
* reliability
* correctness
* @precision low
* @precision high
*/
import codeql.ruby.AST
import codeql.ruby.dataflow.SSA
import codeql.ruby.controlflow.internal.Guards as Guards
import codeql.ruby.controlflow.CfgNodes
import codeql.ruby.ast.internal.Variable
class RelevantLocalVariableReadAccess extends LocalVariableReadAccess {
RelevantLocalVariableReadAccess() {
not exists(MethodCall c |
c.getReceiver() = this and
private predicate isInBooleanContext(AstNode n) {
exists(ConditionalExpr i |
n = i.getCondition()
or
isInBooleanContext(i) and
n = i.getBranch(_)
)
or
n = any(ConditionalLoop parent).getCondition()
or
n = any(InClause parent).getCondition()
or
n = any(LogicalAndExpr op).getAnOperand()
or
n = any(LogicalOrExpr op).getAnOperand()
or
n = any(NotExpr op).getOperand()
or
n = any(StmtSequence parent | isInBooleanContext(parent)).getLastStmt()
or
exists(CaseExpr c, WhenClause w |
not exists(c.getValue()) and
c.getABranch() = w
|
w.getPattern(_) = n
or
w = n
)
}
private predicate isGuarded(LocalVariableReadAccess read) {
exists(AstCfgNode guard, boolean branch |
Guards::guardControlsBlock(guard, read.getAControlFlowNode().getBasicBlock(), branch)
|
// guard is `var`
guard.getAstNode() = read.getVariable().getAnAccess() and
branch = true
or
// guard is `var.nil?`
exists(MethodCall c | guard.getAstNode() = c |
c.getReceiver() = read.getVariable().getAnAccess() and
c.getMethodName() = "nil?"
)
) and
branch = false
)
}
private predicate isNilChecked(LocalVariableReadAccess read) {
exists(MethodCall c | c.getReceiver() = read |
c.getMethodName() = "nil?"
or
c.isSafeNavigation()
)
}
/**
* Holds if `name` is the name of a method defined on `nil`.
* See https://ruby-doc.org/core-2.5.8/NilClass.html
*/
private predicate isNilMethodName(string name) {
name in [
"inspect", "instance_of?", "is_a?", "kind_of?", "method", "nil?", "rationalize", "to_a",
"to_c", "to_f", "to_h", "to_i", "to_r", "to_s"
]
}
class RelevantLocalVariableReadAccess extends LocalVariableReadAccess instanceof TVariableAccessReal
{
RelevantLocalVariableReadAccess() {
not isInBooleanContext(this) and
not isNilChecked(this) and
not isGuarded(this) and
this = any(MethodCall m | not isNilMethodName(m.getMethodName())).getReceiver()
}
}

View File

@@ -0,0 +1,14 @@
def m
puts "m"
end
def foo
m # calls m above
if false
m = "0"
m # reads local variable m
else
end
m.strip # reads uninitialized local variable m, `nil`, and crashes
m2 # undefined local variable or method 'm2' for main (NameError)
end

View File

@@ -0,0 +1,4 @@
| UninitializedLocal.rb:12:3:12:3 | m | Local variable $@ may be used before it is initialized. | UninitializedLocal.rb:8:7:8:7 | m | m |
| UninitializedLocal.rb:34:5:34:5 | b | Local variable $@ may be used before it is initialized. | UninitializedLocal.rb:27:9:27:9 | b | b |
| UninitializedLocal.rb:34:23:34:23 | b | Local variable $@ may be used before it is initialized. | UninitializedLocal.rb:27:9:27:9 | b | b |
| UninitializedLocal.rb:76:5:76:5 | i | Local variable $@ may be used before it is initialized. | UninitializedLocal.rb:73:9:73:9 | i | i |

View File

@@ -0,0 +1,2 @@
query: queries/variables/UninitializedLocal.ql
postprocess: utils/test/InlineExpectationsTestQuery.ql

View File

@@ -0,0 +1,77 @@
def m
puts "m"
end
def foo
m # calls m above
if false
m = "0"
m # reads local variable m
else
end
m.strip #$ Alert
m2 # undefined local variable or method 'm2' for main (NameError)
end
def test_guards
if (a = "3" && a) # OK - a is in a Boolean context
a.strip
end
if (a = "3") && a # OK - a is assigned in the previous conjunct
a.strip
end
if !(a = "3") or a # OK - a is assigned in the previous conjunct
a.strip
end
if false
b = "0"
end
b.nil?
b || 0 # OK
b&.strip # OK - safe navigation
b.strip if b # OK
b.close if b && !b.closed # OK
b.blowup if b || !b.blownup #$ Alert
if false
c = "0"
end
unless c
return
end
c.strip # OK - given above unless
if false
d = "0"
end
if (d.nil?)
return
end
d.strip # OK - given above check
if false
e = "0"
end
unless (!e.nil?)
return
end
e.strip # OK - given above unless
end
def test_loop
begin
if false
a = 0
else
set_a
end
end until a # OK
a.strip # OK - given previous until
end
def test_for
for i in ["foo", "bar"] # OK - since 0..10 cannot raise
puts i.strip
end
i.strip #$ SPURIOUS: Alert
end

View File

@@ -1,5 +1,5 @@
use itertools::Itertools;
use ra_ap_base_db::{EditionedFileId, RootQueryDb, SourceDatabase};
use ra_ap_base_db::{EditionedFileId, FileText, RootQueryDb, SourceDatabase};
use ra_ap_hir::Semantics;
use ra_ap_ide_db::RootDatabase;
use ra_ap_load_cargo::{LoadCargoConfig, load_workspace_at};
@@ -7,7 +7,6 @@ use ra_ap_paths::{AbsPath, Utf8PathBuf};
use ra_ap_project_model::ProjectManifest;
use ra_ap_project_model::{CargoConfig, ManifestPath};
use ra_ap_span::Edition;
use ra_ap_span::EditionedFileId as SpanEditionedFileId;
use ra_ap_span::TextRange;
use ra_ap_span::TextSize;
use ra_ap_syntax::SourceFile;
@@ -54,7 +53,6 @@ impl<'a> RustAnalyzer<'a> {
) -> Option<(RootDatabase, Vfs)> {
let progress = |t| (trace!("progress: {}", t));
let manifest = project.manifest_path();
match load_workspace_at(manifest.as_ref(), config, load_config, &progress) {
Ok((db, vfs, _macro_server)) => Some((db, vfs)),
Err(err) => {
@@ -66,67 +64,70 @@ impl<'a> RustAnalyzer<'a> {
pub fn new(vfs: &'a Vfs, semantics: &'a Semantics<'a, RootDatabase>) -> Self {
RustAnalyzer::WithSemantics { vfs, semantics }
}
pub fn parse(&self, path: &Path) -> ParseResult {
let no_semantics_reason;
fn get_file_data(
&self,
path: &Path,
) -> Result<(&Semantics<RootDatabase>, EditionedFileId, FileText), &str> {
match self {
RustAnalyzer::WithoutSemantics { reason } => Err(reason),
RustAnalyzer::WithSemantics { vfs, semantics } => {
if let Some(file_id) = path_to_file_id(path, vfs) {
if let Ok(input) = std::panic::catch_unwind(|| semantics.db.file_text(file_id))
{
let file_id = EditionedFileId::new(
semantics.db,
SpanEditionedFileId::current_edition(file_id),
);
let source_file = semantics.parse(file_id);
let errors = semantics
.db
.parse_errors(file_id)
.into_iter()
.flat_map(|x| x.to_vec())
.collect();
return ParseResult {
ast: source_file,
text: input.text(semantics.db),
errors,
semantics_info: Ok(FileSemanticInformation { file_id, semantics }),
};
}
debug!(
"No text available for file_id '{:?}', falling back to loading file '{}' from disk.",
file_id,
path.to_string_lossy()
);
no_semantics_reason = "no text available for the file in the project";
} else {
no_semantics_reason = "file not found in project";
}
}
RustAnalyzer::WithoutSemantics { reason } => {
no_semantics_reason = reason;
let file_id = path_to_file_id(path, vfs).ok_or("file not found in project")?;
let input = std::panic::catch_unwind(|| semantics.db.file_text(file_id))
.or(Err("no text available for the file in the project"))?;
let editioned_file_id = semantics
.attach_first_edition(file_id)
.ok_or("failed to determine rust edition")?;
Ok((
semantics,
EditionedFileId::new(semantics.db, editioned_file_id),
input,
))
}
}
let mut errors = Vec::new();
let input = match std::fs::read(path) {
Ok(data) => data,
Err(e) => {
errors.push(SyntaxError::new(
format!("Could not read {}: {}", path.to_string_lossy(), e),
TextRange::empty(TextSize::default()),
));
vec![]
}
};
let (input, err) = from_utf8_lossy(&input);
}
let parse = ra_ap_syntax::ast::SourceFile::parse(&input, Edition::CURRENT);
errors.extend(parse.errors());
errors.extend(err);
ParseResult {
ast: parse.tree(),
text: input.as_ref().into(),
errors,
semantics_info: Err(no_semantics_reason),
pub fn parse(&self, path: &Path) -> ParseResult {
match self.get_file_data(path) {
Ok((semantics, file_id, input)) => {
let source_file = semantics.parse(file_id);
let errors = semantics
.db
.parse_errors(file_id)
.into_iter()
.flat_map(|x| x.to_vec())
.collect();
ParseResult {
ast: source_file,
text: input.text(semantics.db),
errors,
semantics_info: Ok(FileSemanticInformation { file_id, semantics }),
}
}
Err(reason) => {
let mut errors = Vec::new();
let input = match std::fs::read(path) {
Ok(data) => data,
Err(e) => {
errors.push(SyntaxError::new(
format!("Could not read {}: {}", path.to_string_lossy(), e),
TextRange::empty(TextSize::default()),
));
vec![]
}
};
let (input, err) = from_utf8_lossy(&input);
let parse = ra_ap_syntax::ast::SourceFile::parse(&input, Edition::CURRENT);
errors.extend(parse.errors());
errors.extend(err);
ParseResult {
ast: parse.tree(),
text: input.as_ref().into(),
errors,
semantics_info: Err(reason),
}
}
}
}
}
@@ -173,8 +174,10 @@ impl TomlReader {
}
fn workspace_members_match(workspace_dir: &AbsPath, members: &[String], target: &AbsPath) -> bool {
members.iter().any(|p| {
glob::Pattern::new(workspace_dir.join(p).as_str()).is_ok_and(|p| p.matches(target.as_str()))
target.strip_prefix(workspace_dir).is_some_and(|rel_path| {
members
.iter()
.any(|p| glob::Pattern::new(p).is_ok_and(|p| p.matches(rel_path.as_str())))
})
}

View File

@@ -2,13 +2,35 @@ import pytest
import json
import commands
import pathlib
import tomllib
@pytest.fixture(params=[2018, 2021, 2024])
def rust_edition(request):
return request.param
@pytest.fixture
def cargo(cwd):
assert (cwd / "Cargo.toml").exists()
def cargo(cwd, rust_edition):
manifest_file = cwd / "Cargo.toml"
assert manifest_file.exists()
(cwd / "rust-project.json").unlink(missing_ok=True)
def update(file):
contents = file.read_text()
m = tomllib.loads(contents)
if 'package' in m:
# tomllib does not support writing, and we don't want to use further dependencies
# so we just do a dumb search and replace
contents = contents.replace(f'edition = "{m["package"]["edition"]}"', f'edition = "{rust_edition}"')
file.write_text(contents)
if 'members' in m.get('workspace', ()):
for member in m['workspace']['members']:
update(file.parent / member / "Cargo.toml")
update(manifest_file)
@pytest.fixture(scope="session")
def rust_sysroot_src() -> str:
rust_sysroot = pathlib.Path(commands.run("rustc --print sysroot", _capture=True))
@@ -16,15 +38,19 @@ def rust_sysroot_src() -> str:
assert ret.exists()
return str(ret)
@pytest.fixture
def rust_project(cwd, rust_sysroot_src):
def rust_project(cwd, rust_sysroot_src, rust_edition):
project_file = cwd / "rust-project.json"
assert project_file.exists()
project = json.loads(project_file.read_text())
project["sysroot_src"] = rust_sysroot_src
for c in project["crates"]:
c["edition"] = str(rust_edition)
project_file.write_text(json.dumps(project, indent=4))
(cwd / "Cargo.toml").unlink(missing_ok=True)
@pytest.fixture
def rust_check_diagnostics(check_diagnostics):
check_diagnostics.redact += [

View File

@@ -2,6 +2,6 @@
[package]
name = "hello-cargo"
version = "0.1.0"
edition = "2021"
edition = "2021" # replaced in test
[dependencies]

View File

@@ -1,7 +1,7 @@
[package]
name = "exe"
version = "0.1.0"
edition = "2021"
edition = "2021" # replaced in test
[dependencies]
lib = { path = "../lib" }

View File

@@ -1,6 +1,6 @@
[package]
name = "lib"
version = "0.1.0"
edition = "2021"
edition = "2021" # replaced in test
[dependencies]

View File

@@ -21,4 +21,4 @@
"deps": []
}
]
}
}

View File

@@ -1,6 +1,5 @@
import pytest
@pytest.mark.ql_test("steps.ql", expected=".cargo.expected")
@pytest.mark.ql_test("summary.qlref", expected=".cargo.expected")
def test_cargo(codeql, rust, cargo, check_source_archive, rust_check_diagnostics):

View File

@@ -3,5 +3,12 @@ extensions:
pack: codeql/rust-all
extensible: sourceModel
data:
# Alloc
- ["repo:https://github.com/rust-lang/libc:libc", "::free", "Argument[0]", "pointer-invalidate", "manual"]
- addsTo:
pack: codeql/rust-all
extensible: sinkModel
data:
- ["repo:https://github.com/rust-lang/libc:libc", "::malloc", "Argument[0]", "alloc-size", "manual"]
- ["repo:https://github.com/rust-lang/libc:libc", "::aligned_alloc", "Argument[1]", "alloc-size", "manual"]
- ["repo:https://github.com/rust-lang/libc:libc", "::calloc", "Argument[0,1]", "alloc-size", "manual"]
- ["repo:https://github.com/rust-lang/libc:libc", "::realloc", "Argument[1]", "alloc-size", "manual"]

View File

@@ -1,4 +1,30 @@
extensions:
- addsTo:
pack: codeql/rust-all
extensible: sourceModel
data:
# Alloc
- ["lang:alloc", "crate::alloc::dealloc", "Argument[0]", "pointer-invalidate", "manual"]
- addsTo:
pack: codeql/rust-all
extensible: sinkModel
data:
# Alloc
- ["lang:alloc", "crate::alloc::alloc", "Argument[0]", "alloc-layout", "manual"]
- ["lang:alloc", "crate::alloc::alloc_zeroed", "Argument[0]", "alloc-layout", "manual"]
- ["lang:alloc", "crate::alloc::realloc", "Argument[2]", "alloc-size", "manual"]
- ["lang:std", "<crate::alloc::System as crate::alloc::global::GlobalAlloc>::alloc", "Argument[0]", "alloc-layout", "manual"]
- ["lang:std", "<crate::alloc::System as crate::alloc::global::GlobalAlloc>::alloc_zeroed", "Argument[0]", "alloc-layout", "manual"]
- ["lang:std", "<crate::alloc::System as crate::alloc::Allocator>::allocate", "Argument[0]", "alloc-layout", "manual"]
- ["lang:std", "<crate::alloc::System as crate::alloc::Allocator>::allocate_zeroed", "Argument[0]", "alloc-layout", "manual"]
- ["lang:std", "<crate::alloc::System as crate::alloc::Allocator>::grow", "Argument[2]", "alloc-layout", "manual"]
- ["lang:std", "<crate::alloc::System as crate::alloc::Allocator>::grow_zeroed", "Argument[2]", "alloc-layout", "manual"]
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::global::GlobalAlloc>::alloc", "Argument[0]", "alloc-layout", "manual"]
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::global::GlobalAlloc>::alloc_zeroed", "Argument[0]", "alloc-layout", "manual"]
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::Allocator>::allocate", "Argument[0]", "alloc-layout", "manual"]
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::Allocator>::allocate_zeroed", "Argument[0]", "alloc-layout", "manual"]
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::Allocator>::grow", "Argument[2]", "alloc-layout", "manual"]
- ["lang:alloc", "<crate::alloc::Global as crate::alloc::Allocator>::grow_zeroed", "Argument[2]", "alloc-layout", "manual"]
- addsTo:
pack: codeql/rust-all
extensible: summaryModel
@@ -9,9 +35,3 @@ extensions:
- ["lang:alloc", "<crate::string::String>::as_str", "Argument[self]", "ReturnValue", "taint", "manual"]
- ["lang:alloc", "<crate::string::String>::as_bytes", "Argument[self]", "ReturnValue", "taint", "manual"]
- ["lang:alloc", "<_ as crate::string::ToString>::to_string", "Argument[self]", "ReturnValue", "taint", "manual"]
- addsTo:
pack: codeql/rust-all
extensible: sourceModel
data:
# Alloc
- ["lang:alloc", "crate::alloc::dealloc", "Argument[0]", "pointer-invalidate", "manual"]

View File

@@ -17,6 +17,21 @@ extensions:
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::collect", "Argument[self].Element", "ReturnValue.Element", "value", "manual"]
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::map", "Argument[self].Element", "Argument[0].Parameter[0]", "value", "manual"]
- ["lang:core", "<crate::slice::iter::Iter as crate::iter::traits::iterator::Iterator>::for_each", "Argument[self].Element", "Argument[0].Parameter[0]", "value", "manual"]
# Layout
- ["lang:core", "<crate::alloc::layout::Layout>::from_size_align", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::from_size_align_unchecked", "Argument[0]", "ReturnValue", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::array", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::repeat", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)].Field[0]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::repeat", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)].Field[0]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::repeat_packed", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::repeat_packed", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::extend", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)].Field[0]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::extend", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)].Field[0]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::extend_packed", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::extend_packed", "Argument[0]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::align_to", "Argument[self]", "ReturnValue.Field[crate::result::Result::Ok(0)]", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::pad_to_align", "Argument[self]", "ReturnValue", "taint", "manual"]
- ["lang:core", "<crate::alloc::layout::Layout>::size", "Argument[self]", "ReturnValue", "taint", "manual"]
# Ptr
- ["lang:core", "crate::ptr::read", "Argument[0].Reference", "ReturnValue", "value", "manual"]
- ["lang:core", "crate::ptr::read_unaligned", "Argument[0].Reference", "ReturnValue", "value", "manual"]

View File

@@ -0,0 +1,95 @@
/**
* Provides classes and predicates for reasoning about uncontrolled allocation
* size vulnerabilities.
*/
import rust
private import codeql.rust.Concepts
private import codeql.rust.dataflow.DataFlow
private import codeql.rust.dataflow.FlowSink
private import codeql.rust.controlflow.ControlFlowGraph as Cfg
private import codeql.rust.controlflow.CfgNodes as CfgNodes
/**
* Provides default sources, sinks and barriers for detecting uncontrolled
* allocation size vulnerabilities, as well as extension points for adding your own.
*/
module UncontrolledAllocationSize {
/**
* A data flow sink for uncontrolled allocation size vulnerabilities.
*/
abstract class Sink extends QuerySink::Range {
override string getSinkType() { result = "UncontrolledAllocationSize" }
}
/**
* A barrier for uncontrolled allocation size vulnerabilities.
*/
abstract class Barrier extends DataFlow::Node { }
/**
* A sink for uncontrolled allocation size from model data.
*/
private class ModelsAsDataSink extends Sink {
ModelsAsDataSink() { sinkNode(this, ["alloc-size", "alloc-layout"]) }
}
/**
* A barrier for uncontrolled allocation size that is an upper bound check / guard.
*/
private class UpperBoundCheckBarrier extends Barrier {
UpperBoundCheckBarrier() {
this = DataFlow::BarrierGuard<isUpperBoundCheck/3>::getABarrierNode()
}
}
/**
* Gets the operand on the "greater" (or "greater-or-equal") side
* of this relational expression, that is, the side that is larger
* if the overall expression evaluates to `true`; for example on
* `x <= 20` this is the `20`, and on `y > 0` it is `y`.
*/
private Expr getGreaterOperand(BinaryExpr op) {
op.getOperatorName() = ["<", "<="] and
result = op.getRhs()
or
op.getOperatorName() = [">", ">="] and
result = op.getLhs()
}
/**
* Gets the operand on the "lesser" (or "lesser-or-equal") side
* of this relational expression, that is, the side that is smaller
* if the overall expression evaluates to `true`; for example on
* `x <= 20` this is `x`, and on `y > 0` it is the `0`.
*/
private Expr getLesserOperand(BinaryExpr op) {
op.getOperatorName() = ["<", "<="] and
result = op.getLhs()
or
op.getOperatorName() = [">", ">="] and
result = op.getRhs()
}
/**
* Holds if comparison `g` having result `branch` indicates an upper bound for the sub-expression
* `node`. For example when the comparison `x < 10` is true, we have an upper bound for `x`.
*/
private predicate isUpperBoundCheck(CfgNodes::AstCfgNode g, Cfg::CfgNode node, boolean branch) {
exists(BinaryExpr cmp | g = cmp.getACfgNode() |
node = getLesserOperand(cmp).getACfgNode() and
branch = true
or
node = getGreaterOperand(cmp).getACfgNode() and
branch = false
or
cmp.getOperatorName() = "==" and
[cmp.getLhs(), cmp.getRhs()].getACfgNode() = node and
branch = true
or
cmp.getOperatorName() = "!=" and
[cmp.getLhs(), cmp.getRhs()].getACfgNode() = node and
branch = false
)
}
}

View File

@@ -0,0 +1,41 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Allocating memory with a size based on user input may allow arbitrary amounts of memory to be
allocated, leading to a crash or a denial-of-service (DoS) attack.</p>
<p>If the user input is multiplied by a constant, such as the size of a type, the result may
overflow. In a build with the <code>--release</code> flag, Rust performs two's complement wrapping,
with the result that less memory than expected may be allocated. This can lead to buffer overflow
incidents.</p>
</overview>
<recommendation>
<p>Implement a guard to limit the amount of memory that is allocated, and reject the request if
the guard is not met. Ensure that any multiplications in the calculation cannot overflow, either
by guarding their inputs, or using a multiplication routine such as <code>checked_mul</code> that
does not wrap around.</p>
</recommendation>
<example>
<p>In the following example, an arbitrary amount of memory is allocated based on user input. In
addition, due to the multiplication operation, the result may overflow if a very large value is
provided. This may lead to less memory being allocated than expected by other parts of the program.</p>
<sample src="UncontrolledAllocationSizeBad.rs" />
<p>In the fixed example, the user input is checked against a maximum value. If the check fails, an
error is returned, and both the multiplication and allocation do not take place.</p>
<sample src="UncontrolledAllocationSizeGood.rs" />
</example>
<references>
<li>The Rust Programming Language: <a href="https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-overflow">Data Types - Integer Overflow</a>.</li>
</references>
</qhelp>

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