Skip to content

Commit 01cb408

Browse files
authored
Merge branch 'main' into experimental-decompression-api
2 parents 6416b8d + e95194c commit 01cb408

File tree

38 files changed

+1153
-123
lines changed

38 files changed

+1153
-123
lines changed

csharp/ql/src/utils/model-generator/CaptureSinkModels.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @name Capture sink models.
3-
* @description Finds public methods that act as sinks as they flow into a a known sink.
3+
* @description Finds public methods that act as sinks as they flow into a known sink.
44
* @kind diagnostic
55
* @id cs/utils/model-generator/sink-models
66
* @tags model-generator

java/kotlin-extractor/kotlin_plugin_versions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ def is_windows():
1515
return False
1616

1717
def version_tuple_to_string(version):
18-
return f'{version[0]}.{version[1]}.{version[2]}'
18+
return f'{version[0]}.{version[1]}.{version[2]}{version[3]}'
1919

2020
def version_string_to_tuple(version):
21-
m = re.match(r'([0-9]+)\.([0-9]+)\.([0-9]+)', version)
22-
return tuple([int(m.group(i)) for i in range(1, 4)])
21+
m = re.match(r'([0-9]+)\.([0-9]+)\.([0-9]+)(.*)', version)
22+
return tuple([int(m.group(i)) for i in range(1, 4)] + [m.group(4)])
2323

2424
many_versions = [ '1.4.32', '1.5.0', '1.5.10', '1.5.21', '1.5.31', '1.6.10', '1.7.0-RC', '1.6.20' ]
2525

java/ql/src/utils/model-generator/CaptureSinkModels.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @name Capture sink models.
3-
* @description Finds public methods that act as sinks as they flow into a a known sink.
3+
* @description Finds public methods that act as sinks as they flow into a known sink.
44
* @kind diagnostic
55
* @id java/utils/model-generator/sink-models
66
* @tags model-generator
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @name Library inputs
3+
* @description An input coming from the client of a library
4+
* @kind problem
5+
* @problem.severity recommendation
6+
* @id js/meta/alerts/library-inputs
7+
* @tags meta
8+
* @precision very-low
9+
*/
10+
11+
import javascript
12+
import semmle.javascript.PackageExports
13+
14+
select getALibraryInputParameter(), "Library input"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## 0.2.3
2+
3+
### Minor Analysis Improvements
4+
5+
- Calls to `Zip::File.open` and `Zip::File.new` have been added as `FileSystemAccess` sinks. As a result queries like `rb/path-injection` now flag up cases where users may access arbitrary archive files.

ruby/ql/lib/codeql/ruby/ApiGraphs.qll

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,24 @@ module API {
780780
or
781781
pos.isBlock() and
782782
result = Label::blockParameter()
783+
or
784+
pos.isAny() and
785+
(
786+
result = Label::parameter(_)
787+
or
788+
result = Label::keywordParameter(_)
789+
or
790+
result = Label::blockParameter()
791+
// NOTE: `self` should NOT be included, as described in the QLDoc for `isAny()`
792+
)
793+
or
794+
pos.isAnyNamed() and
795+
result = Label::keywordParameter(_)
796+
//
797+
// Note: there is currently no API graph label for `self`.
798+
// It was omitted since in practice it means going back to where you came from.
799+
// For example, `base.getMethod("foo").getSelf()` would just be `base`.
800+
// However, it's possible we'll need it later, for identifying `self` parameters or post-update nodes.
783801
}
784802

785803
/** Gets the API graph label corresponding to the given parameter position. */
@@ -796,6 +814,24 @@ module API {
796814
or
797815
pos.isBlock() and
798816
result = Label::blockParameter()
817+
or
818+
pos.isAny() and
819+
(
820+
result = Label::parameter(_)
821+
or
822+
result = Label::keywordParameter(_)
823+
or
824+
result = Label::blockParameter()
825+
// NOTE: `self` should NOT be included, as described in the QLDoc for `isAny()`
826+
)
827+
or
828+
pos.isAnyNamed() and
829+
result = Label::keywordParameter(_)
830+
//
831+
// Note: there is currently no API graph label for `self`.
832+
// It was omitted since in practice it means going back to where you came from.
833+
// For example, `base.getMethod("foo").getSelf()` would just be `base`.
834+
// However, it's possible we'll need it later, for identifying `self` parameters or post-update nodes.
799835
}
800836
}
801837
}

ruby/ql/lib/codeql/ruby/Frameworks.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ private import codeql.ruby.frameworks.ActiveRecord
88
private import codeql.ruby.frameworks.ActiveStorage
99
private import codeql.ruby.frameworks.ActionView
1010
private import codeql.ruby.frameworks.ActiveSupport
11+
private import codeql.ruby.frameworks.Archive
1112
private import codeql.ruby.frameworks.GraphQL
1213
private import codeql.ruby.frameworks.Rails
1314
private import codeql.ruby.frameworks.Stdlib
1415
private import codeql.ruby.frameworks.Files
1516
private import codeql.ruby.frameworks.HttpClients
1617
private import codeql.ruby.frameworks.XmlParsing
1718
private import codeql.ruby.frameworks.ActionDispatch
19+
private import codeql.ruby.frameworks.PosixSpawn

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,9 @@ private module Cached {
260260
or
261261
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
262262
} or
263-
THashSplatArgumentPosition()
263+
THashSplatArgumentPosition() or
264+
TAnyArgumentPosition() or
265+
TAnyKeywordArgumentPosition()
264266

265267
cached
266268
newtype TParameterPosition =
@@ -280,7 +282,8 @@ private module Cached {
280282
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name)
281283
} or
282284
THashSplatParameterPosition() or
283-
TAnyParameterPosition()
285+
TAnyParameterPosition() or
286+
TAnyKeywordParameterPosition()
284287
}
285288

286289
import Cached
@@ -482,11 +485,14 @@ class ParameterPosition extends TParameterPosition {
482485
predicate isHashSplat() { this = THashSplatParameterPosition() }
483486

484487
/**
485-
* Holds if this position represents any parameter. This includes both positional
486-
* and named parameters.
488+
* Holds if this position represents any parameter, except `self` parameters. This
489+
* includes both positional, named, and block parameters.
487490
*/
488491
predicate isAny() { this = TAnyParameterPosition() }
489492

493+
/** Holds if this position represents any positional parameter. */
494+
predicate isAnyNamed() { this = TAnyKeywordParameterPosition() }
495+
490496
/** Gets a textual representation of this position. */
491497
string toString() {
492498
this.isSelf() and result = "self"
@@ -502,6 +508,8 @@ class ParameterPosition extends TParameterPosition {
502508
this.isHashSplat() and result = "**"
503509
or
504510
this.isAny() and result = "any"
511+
or
512+
this.isAnyNamed() and result = "any-named"
505513
}
506514
}
507515

@@ -519,6 +527,15 @@ class ArgumentPosition extends TArgumentPosition {
519527
/** Holds if this position represents a keyword argument named `name`. */
520528
predicate isKeyword(string name) { this = TKeywordArgumentPosition(name) }
521529

530+
/**
531+
* Holds if this position represents any argument, except `self` arguments. This
532+
* includes both positional, named, and block arguments.
533+
*/
534+
predicate isAny() { this = TAnyArgumentPosition() }
535+
536+
/** Holds if this position represents any positional parameter. */
537+
predicate isAnyNamed() { this = TAnyKeywordArgumentPosition() }
538+
522539
/**
523540
* Holds if this position represents a synthesized argument containing all keyword
524541
* arguments wrapped in a hash.
@@ -535,12 +552,16 @@ class ArgumentPosition extends TArgumentPosition {
535552
or
536553
exists(string name | this.isKeyword(name) and result = "keyword " + name)
537554
or
555+
this.isAny() and result = "any"
556+
or
557+
this.isAnyNamed() and result = "any-named"
558+
or
538559
this.isHashSplat() and result = "**"
539560
}
540561
}
541562

542563
/** Holds if arguments at position `apos` match parameters at position `ppos`. */
543-
pragma[inline]
564+
pragma[nomagic]
544565
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
545566
ppos.isSelf() and apos.isSelf()
546567
or
@@ -556,5 +577,11 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
556577
or
557578
ppos.isHashSplat() and apos.isHashSplat()
558579
or
559-
ppos.isAny() and exists(apos)
580+
ppos.isAny() and not apos.isSelf()
581+
or
582+
apos.isAny() and not ppos.isSelf()
583+
or
584+
ppos.isAnyNamed() and apos.isKeyword(_)
585+
or
586+
apos.isAnyNamed() and ppos.isKeyword(_)
560587
}

ruby/ql/lib/codeql/ruby/dataflow/internal/FlowSummaryImplSpecific.qll

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@ ArgumentPosition parseParamBody(string s) {
293293
or
294294
s = "block" and
295295
result.isBlock()
296+
or
297+
s = "any" and
298+
result.isAny()
299+
or
300+
s = "any-named" and
301+
result.isAnyNamed()
296302
}
297303

298304
/** Gets the parameter position obtained by parsing `X` in `Argument[X]`. */
@@ -317,4 +323,10 @@ ParameterPosition parseArgBody(string s) {
317323
or
318324
s = "block" and
319325
result.isBlock()
326+
or
327+
s = "any" and
328+
result.isAny()
329+
or
330+
s = "any-named" and
331+
result.isAnyNamed()
320332
}

ruby/ql/lib/codeql/ruby/frameworks/ActiveSupport.qll

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
private import ruby
77
private import codeql.ruby.Concepts
88
private import codeql.ruby.DataFlow
9+
private import codeql.ruby.dataflow.FlowSummary
10+
private import codeql.ruby.Concepts
11+
private import codeql.ruby.ApiGraphs
12+
private import codeql.ruby.frameworks.stdlib.Logger::Logger as StdlibLogger
913

1014
/**
1115
* Modeling for `ActiveSupport`.
@@ -32,6 +36,107 @@ module ActiveSupport {
3236

3337
override DataFlow::Node getCode() { result = this.getReceiver() }
3438
}
39+
40+
/**
41+
* Flow summary for methods which transform the receiver in some way, possibly preserving taint.
42+
*/
43+
private class StringTransformSummary extends SummarizedCallable {
44+
// We're modelling a lot of different methods, so we make up a name for this summary.
45+
StringTransformSummary() { this = "ActiveSupportStringTransform" }
46+
47+
override MethodCall getACall() {
48+
result.getMethodName() =
49+
[
50+
"camelize", "camelcase", "classify", "dasherize", "deconstantize", "demodulize",
51+
"foreign_key", "humanize", "indent", "parameterize", "pluralize", "singularize",
52+
"squish", "strip_heredoc", "tableize", "titlecase", "titleize", "underscore",
53+
"upcase_first"
54+
]
55+
}
56+
57+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
58+
input = "Argument[self]" and output = "ReturnValue" and preservesValue = false
59+
}
60+
}
61+
}
62+
63+
/**
64+
* Extensions to the `Enumerable` module.
65+
*/
66+
module Enumerable {
67+
private class ArrayIndex extends int {
68+
ArrayIndex() { this = any(DataFlow::Content::KnownElementContent c).getIndex().getInt() }
69+
}
70+
71+
private class CompactBlankSummary extends SimpleSummarizedCallable {
72+
CompactBlankSummary() { this = "compact_blank" }
73+
74+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
75+
input = "Argument[self].Element[any]" and
76+
output = "ReturnValue.Element[?]" and
77+
preservesValue = true
78+
}
79+
}
80+
81+
private class ExcludingSummary extends SimpleSummarizedCallable {
82+
ExcludingSummary() { this = ["excluding", "without"] }
83+
84+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
85+
input = "Argument[self].Element[any]" and
86+
output = "ReturnValue.Element[?]" and
87+
preservesValue = true
88+
}
89+
}
90+
91+
private class InOrderOfSummary extends SimpleSummarizedCallable {
92+
InOrderOfSummary() { this = "in_order_of" }
93+
94+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
95+
input = "Argument[self].Element[any]" and
96+
output = "ReturnValue.Element[?]" and
97+
preservesValue = true
98+
}
99+
}
100+
101+
/**
102+
* Like `Array#push` but doesn't update the receiver.
103+
*/
104+
private class IncludingSummary extends SimpleSummarizedCallable {
105+
IncludingSummary() { this = "including" }
106+
107+
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
108+
(
109+
exists(ArrayIndex i |
110+
input = "Argument[self].Element[" + i + "]" and
111+
output = "ReturnValue.Element[" + i + "]"
112+
)
113+
or
114+
input = "Argument[self].Element[?]" and
115+
output = "ReturnValue.Element[?]"
116+
or
117+
exists(int i | i in [0 .. (mc.getNumberOfArguments() - 1)] |
118+
input = "Argument[" + i + "]" and
119+
output = "ReturnValue.Element[?]"
120+
)
121+
) and
122+
preservesValue = true
123+
}
124+
}
125+
// TODO: index_by, index_with, pick, pluck (they require Hash dataflow)
126+
}
127+
}
128+
129+
/**
130+
* `ActiveSupport::Logger`
131+
*/
132+
module Logger {
133+
private class ActiveSupportLoggerInstantiation extends StdlibLogger::LoggerInstantiation {
134+
ActiveSupportLoggerInstantiation() {
135+
this =
136+
API::getTopLevelMember("ActiveSupport")
137+
.getMember(["Logger", "TaggedLogging"])
138+
.getAnInstantiation()
139+
}
35140
}
36141
}
37142
}

0 commit comments

Comments
 (0)