mirror of
https://github.com/github/codeql.git
synced 2026-05-26 09:01:22 +02:00
Compare commits
131 Commits
navntoft/p
...
adityashar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93efbfe1c7 | ||
|
|
4a1b988f39 | ||
|
|
6176202d50 | ||
|
|
c9cff09f5d | ||
|
|
53c4b29b50 | ||
|
|
c245459e97 | ||
|
|
884c4a6e7b | ||
|
|
c821f27309 | ||
|
|
86313715a4 | ||
|
|
85527101bd | ||
|
|
7517272d34 | ||
|
|
b988be8ff6 | ||
|
|
85e27cae60 | ||
|
|
2dc88d87ae | ||
|
|
63e5f5a555 | ||
|
|
868680f078 | ||
|
|
60aa3a8d9d | ||
|
|
eb0f8e9572 | ||
|
|
11aef7019e | ||
|
|
6a76a40cf4 | ||
|
|
dbbd80f4dc | ||
|
|
2477233508 | ||
|
|
877118fb3b | ||
|
|
f349048e42 | ||
|
|
31143b405e | ||
|
|
deef95d384 | ||
|
|
bfc494c0e1 | ||
|
|
b641d5f177 | ||
|
|
6e2cfab7b2 | ||
|
|
a5aef8c6f9 | ||
|
|
4ae49cfe35 | ||
|
|
472bfa2668 | ||
|
|
3d7c0201d9 | ||
|
|
11abbf8c4a | ||
|
|
92e4f112c0 | ||
|
|
d0dcf897cb | ||
|
|
4167e96058 | ||
|
|
f675a143d6 | ||
|
|
d17d29a387 | ||
|
|
e3f1720f9c | ||
|
|
85940484ab | ||
|
|
2c4b3527b4 | ||
|
|
678eccb417 | ||
|
|
8674b61e5a | ||
|
|
db1203acb3 | ||
|
|
6e09a65da0 | ||
|
|
734ad2d767 | ||
|
|
208487f236 | ||
|
|
becea89a47 | ||
|
|
8555e8c8c8 | ||
|
|
53c88da91b | ||
|
|
1ca25b2ccb | ||
|
|
283503b06d | ||
|
|
30ce0c5cbf | ||
|
|
4f5bdbb517 | ||
|
|
3bb249f580 | ||
|
|
b678112f4d | ||
|
|
960e9900af | ||
|
|
94e08e318d | ||
|
|
86b64afa13 | ||
|
|
ea3bb8cf0c | ||
|
|
6c348b5855 | ||
|
|
b6c658767e | ||
|
|
04bf908a4b | ||
|
|
c484945f39 | ||
|
|
732fcbf1c9 | ||
|
|
8acb0243ad | ||
|
|
a1dc87496a | ||
|
|
63a3953b0c | ||
|
|
81cba7fa2f | ||
|
|
acfcc6d490 | ||
|
|
576f4cf19f | ||
|
|
ad89e7980e | ||
|
|
3ea5cc1b66 | ||
|
|
e1c5517de7 | ||
|
|
04ec1d7830 | ||
|
|
626a7d5007 | ||
|
|
c4e56b1ec8 | ||
|
|
042fe07494 | ||
|
|
fea3d10b97 | ||
|
|
441c79ebdf | ||
|
|
b5b252b10f | ||
|
|
ff2947a0e5 | ||
|
|
041adcd63a | ||
|
|
bd3342af8a | ||
|
|
5243f90c90 | ||
|
|
2dca95af92 | ||
|
|
0c52b5ad95 | ||
|
|
a3e4e62eac | ||
|
|
4bc3e9e736 | ||
|
|
10ad5780b5 | ||
|
|
e2f63db96d | ||
|
|
873db7c121 | ||
|
|
b97c61864e | ||
|
|
f28478e876 | ||
|
|
f4277204b7 | ||
|
|
0e099474c5 | ||
|
|
ff07ec8d8c | ||
|
|
41f54d836e | ||
|
|
dad85854cd | ||
|
|
d689a55229 | ||
|
|
e23ff9cf3e | ||
|
|
93882263f9 | ||
|
|
c4fa417680 | ||
|
|
6fb5376c5f | ||
|
|
893e42315e | ||
|
|
6ad7a950da | ||
|
|
c9939387f8 | ||
|
|
a5883b1627 | ||
|
|
8e7e162ebc | ||
|
|
6bcfd8c91d | ||
|
|
c5860e92ec | ||
|
|
4b7a9cd399 | ||
|
|
49194b0340 | ||
|
|
44b26e5ae6 | ||
|
|
f96b00a62a | ||
|
|
0dbf951291 | ||
|
|
455ce59583 | ||
|
|
e16a20e69f | ||
|
|
c7fad09664 | ||
|
|
a572ac60d2 | ||
|
|
fb22d55878 | ||
|
|
6a5a1001bb | ||
|
|
f7d3a51f27 | ||
|
|
cdd5cb0523 | ||
|
|
addc1d34d8 | ||
|
|
64aa4e8bae | ||
|
|
e49c1afe72 | ||
|
|
03f94de3cb | ||
|
|
9409cd6ed7 | ||
|
|
ae555f2f2e |
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
def test(codeql, actions):
|
||||
codeql.database.create(source_root="src")
|
||||
@@ -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' |
|
||||
@@ -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 |
|
||||
@@ -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' |
|
||||
@@ -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 |
|
||||
5
actions/ql/integration-tests/filters/actions.ql
Normal file
5
actions/ql/integration-tests/filters/actions.ql
Normal file
@@ -0,0 +1,5 @@
|
||||
import actions
|
||||
|
||||
from AstNode n
|
||||
where n instanceof Workflow or n instanceof CompositeAction
|
||||
select n
|
||||
@@ -0,0 +1,4 @@
|
||||
paths:
|
||||
- 'included'
|
||||
paths-ignore:
|
||||
- 'excluded'
|
||||
@@ -0,0 +1,2 @@
|
||||
paths-ignore:
|
||||
- 'excluded'
|
||||
@@ -0,0 +1,2 @@
|
||||
paths:
|
||||
- 'included'
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
src/included/action.yml
|
||||
src/included/not-an-action.yml
|
||||
src/included/unreachable-workflow.yml
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
src/included/action.yml
|
||||
src/included/not-an-action.yml
|
||||
src/included/unreachable-workflow.yml
|
||||
11
actions/ql/integration-tests/filters/src/excluded/action.yml
Normal file
11
actions/ql/integration-tests/filters/src/excluded/action.yml
Normal 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
|
||||
11
actions/ql/integration-tests/filters/src/included/action.yml
Normal file
11
actions/ql/integration-tests/filters/src/included/action.yml
Normal 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
|
||||
@@ -0,0 +1 @@
|
||||
name: 'Not an action, just a YAML 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
|
||||
@@ -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
|
||||
18
actions/ql/integration-tests/filters/test.py
Executable file
18
actions/ql/integration-tests/filters/test.py
Executable 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")
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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`. */
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved autobuilder logic for detecting whether a project references a SDK (and should be built using `dotnet`).
|
||||
@@ -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
15
go/ql/src/CallGraph.ql
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
29
java/ql/src/Performance/StringReplaceAllWithNonRegex.md
Normal file
29
java/ql/src/Performance/StringReplaceAllWithNonRegex.md
Normal 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).
|
||||
24
java/ql/src/Performance/StringReplaceAllWithNonRegex.ql
Normal file
24
java/ql/src/Performance/StringReplaceAllWithNonRegex.ql
Normal 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"
|
||||
@@ -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
|
||||
@@ -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%")
|
||||
|
||||
@@ -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 |
|
||||
@@ -0,0 +1,2 @@
|
||||
query: Performance/StringReplaceAllWithNonRegex.ql
|
||||
postprocess: utils/test/InlineExpectationsTestQuery.ql
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Added taint propagation for `Uint8Array`, `ArrayBuffer`, `SharedArrayBuffer` and `TextDecoder.decode()`.
|
||||
5
javascript/ql/lib/change-notes/2025-04-07-websocket.md
Normal file
5
javascript/ql/lib/change-notes/2025-04-07-websocket.md
Normal 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.
|
||||
5
javascript/ql/lib/change-notes/2025-04-11-nextrequest.md
Normal file
5
javascript/ql/lib/change-notes/2025-04-11-nextrequest.md
Normal 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`.
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
|
||||
@@ -12,3 +12,5 @@ private import Sets
|
||||
private import Strings
|
||||
private import DynamicImportStep
|
||||
private import UrlSearchParams
|
||||
private import TypedArrays
|
||||
private import Decoders
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
10
javascript/ql/src/Security/trest/test.ql
Normal file
10
javascript/ql/src/Security/trest/test.ql
Normal 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."
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
})();
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
})();
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
});
|
||||
})();
|
||||
@@ -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 });
|
||||
|
||||
@@ -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' });
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
query: test.ql
|
||||
postprocess: utils/test/InlineExpectationsTestQuery.ql
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 |
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
consistencyIssue
|
||||
resultInWrongFile
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
12
ruby/ql/src/Sanitizers.ql
Normal 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"
|
||||
@@ -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.
|
||||
@@ -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
|
||||
41
ruby/ql/src/queries/variables/UninitializedLocal.md
Normal file
41
ruby/ql/src/queries/variables/UninitializedLocal.md
Normal 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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
14
ruby/ql/src/queries/variables/examples/UninitializedLocal.rb
Normal file
14
ruby/ql/src/queries/variables/examples/UninitializedLocal.rb
Normal 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
|
||||
@@ -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 |
|
||||
@@ -0,0 +1,2 @@
|
||||
query: queries/variables/UninitializedLocal.ql
|
||||
postprocess: utils/test/InlineExpectationsTestQuery.ql
|
||||
@@ -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
|
||||
@@ -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())))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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 += [
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
[package]
|
||||
name = "hello-cargo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2021" # replaced in test
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "exe"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2021" # replaced in test
|
||||
|
||||
[dependencies]
|
||||
lib = { path = "../lib" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lib"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2021" # replaced in test
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -21,4 +21,4 @@
|
||||
"deps": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user