Skip to content

Commit 0e94777

Browse files
committed
Merge branch 'master' into immutable-actions
2 parents 5bf02e7 + ae6856a commit 0e94777

File tree

74 files changed

+2950
-503
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+2950
-503
lines changed

ql/lib/codeql/actions/Bash.qll

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -256,20 +256,20 @@ class BashShellScript extends ShellScript {
256256

257257
override predicate getAWriteToGitHubEnv(string name, string data) {
258258
exists(string raw |
259-
Bash::extractFileWrite(this.getRawScript(), "GITHUB_ENV", raw) and
259+
Bash::extractFileWrite(this, "GITHUB_ENV", raw) and
260260
Bash::extractVariableAndValue(raw, name, data)
261261
)
262262
}
263263

264264
override predicate getAWriteToGitHubOutput(string name, string data) {
265265
exists(string raw |
266-
Bash::extractFileWrite(this.getRawScript(), "GITHUB_OUTPUT", raw) and
266+
Bash::extractFileWrite(this, "GITHUB_OUTPUT", raw) and
267267
Bash::extractVariableAndValue(raw, name, data)
268268
)
269269
}
270270

271271
override predicate getAWriteToGitHubPath(string data) {
272-
Bash::extractFileWrite(this.getRawScript(), "GITHUB_PATH", data)
272+
Bash::extractFileWrite(this, "GITHUB_PATH", data)
273273
}
274274

275275
override predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field) {
@@ -542,12 +542,12 @@ module Bash {
542542
blockFileWrite(script, cmd, file, content, filters)
543543
}
544544

545-
bindingset[script, file_var]
546-
predicate extractFileWrite(string script, string file_var, string content) {
545+
bindingset[file_var]
546+
predicate extractFileWrite(BashShellScript script, string file_var, string content) {
547547
// single line assignment
548548
exists(string file_expr, string raw_content |
549549
isParameterExpansion(file_expr, file_var, _, _) and
550-
singleLineFileWrite(script.splitAt("\n"), _, file_expr, raw_content, _) and
550+
singleLineFileWrite(script.getAStmt(), _, file_expr, raw_content, _) and
551551
content = trimQuotes(raw_content)
552552
)
553553
or
@@ -566,12 +566,12 @@ module Bash {
566566
cmd = "add-path" and
567567
content = value
568568
) and
569-
singleLineWorkflowCmd(script.splitAt("\n"), cmd, key, value)
569+
singleLineWorkflowCmd(script.getAStmt(), cmd, key, value)
570570
)
571571
or
572572
// multiline assignment
573573
exists(string file_expr, string raw_content |
574-
multiLineFileWrite(script, _, file_expr, raw_content, _) and
574+
multiLineFileWrite(script.getRawScript(), _, file_expr, raw_content, _) and
575575
isParameterExpansion(file_expr, file_var, _, _) and
576576
content = trimQuotes(raw_content)
577577
)
@@ -691,11 +691,32 @@ module Bash {
691691
// echo "FIELD=${VAR2:-default}" >> $GITHUB_ENV (field, file_write_value)
692692
script.getAnAssignment(var2, value2) and
693693
containsCmdSubstitution(value2, cmd) and
694-
containsParameterExpansion(expr, var2, _, _)
694+
containsParameterExpansion(expr, var2, _, _) and
695+
not varMatchesRegexTest(script, var2, alphaNumericRegex())
695696
)
696697
or
697698
// var reaches the file write directly
698699
// echo "FIELD=$(cmd)" >> $GITHUB_ENV (field, file_write_value)
699700
containsCmdSubstitution(expr, cmd)
700701
}
702+
703+
/**
704+
* Holds if there test command that checks a variable against a regex
705+
* eg: `[[ $VAR =~ ^[a-zA-Z0-9_]+$ ]]`
706+
*/
707+
bindingset[var, regex]
708+
predicate varMatchesRegexTest(BashShellScript script, string var, string regex) {
709+
exists(string lhs, string rhs |
710+
lhs = script.getACommand().regexpCapture(".*\\[\\[\\s*(.*?)\\s*=~\\s*(.*?)\\s*\\]\\].*", 1) and
711+
containsParameterExpansion(lhs, var, _, _) and
712+
rhs = script.getACommand().regexpCapture(".*\\[\\[\\s*(.*?)\\s*=~\\s*(.*?)\\s*\\]\\].*", 2) and
713+
trimQuotes(rhs).regexpMatch(regex)
714+
)
715+
}
716+
717+
/**
718+
* Holds if the given regex is used to match an alphanumeric string
719+
* eg: `^[0-9a-zA-Z]{40}$`, `^[0-9]+$` or `^[a-zA-Z0-9_]+$`
720+
*/
721+
string alphaNumericRegex() { result = "^\\^\\[([09azAZ_-]+)\\](\\+|\\{\\d+\\})\\$$" }
701722
}

ql/lib/codeql/actions/ast/internal/Ast.qll

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,21 @@ private import codeql.actions.Helper
44
private import codeql.actions.config.Config
55
private import codeql.actions.DataFlow
66

7+
bindingset[text]
8+
int numberOfLines(string text) { result = max(int i | exists(text.splitAt("\n", i))) }
9+
710
/**
811
* Gets the length of each line in the StringValue .
912
*/
1013
bindingset[text]
11-
int lineLength(string text, int idx) {
12-
exists(string line | line = text.splitAt("\n", idx) and result = line.length() + 1)
13-
}
14+
int lineLength(string text, int i) { result = text.splitAt("\n", i).length() + 1 }
1415

1516
/**
1617
* Gets the sum of the length of the lines up to the given index.
1718
*/
1819
bindingset[text]
1920
int partialLineLengthSum(string text, int i) {
20-
i in [0 .. count(text.splitAt("\n"))] and
21+
i in [0 .. numberOfLines(text)] and
2122
result = sum(int j, int length | j in [0 .. i] and length = lineLength(text, j) | length)
2223
}
2324

@@ -114,7 +115,11 @@ abstract class AstNodeImpl extends TAstNode {
114115
/**
115116
* Gets and Event triggering this node.
116117
*/
117-
EventImpl getATriggerEvent() { result = this.getEnclosingJob().getATriggerEvent() }
118+
EventImpl getATriggerEvent() {
119+
result = this.getEnclosingJob().getATriggerEvent()
120+
or
121+
not exists(this.getEnclosingJob()) and result = this.getEnclosingWorkflow().getATriggerEvent()
122+
}
118123

119124
/**
120125
* Gets the enclosing Step.
@@ -1631,7 +1636,7 @@ class StepsExpressionImpl extends SimpleReferenceExpressionImpl {
16311636
exists(string expr |
16321637
(
16331638
exists(getAJsonReferenceExpression(expression, _)) and
1634-
expr = normalizeExpr(expression).regexpCapture("(?i)fromjson\\((.*)\\).*", 1)
1639+
expr = normalizeExpr(expression).regexpCapture("(?i)(from|to)json\\((.*)\\).*", 2)
16351640
or
16361641
exists(getASimpleReferenceExpression(expression, _)) and
16371642
expr = normalizeExpr(expression)
@@ -1672,7 +1677,7 @@ class NeedsExpressionImpl extends SimpleReferenceExpressionImpl {
16721677
exists(string expr |
16731678
(
16741679
exists(getAJsonReferenceExpression(expression, _)) and
1675-
expr = normalizeExpr(expression).regexpCapture("(?i)fromjson\\((.*)\\).*", 1)
1680+
expr = normalizeExpr(expression).regexpCapture("(?i)(from|to)json\\((.*)\\).*", 2)
16761681
or
16771682
exists(getASimpleReferenceExpression(expression, _)) and
16781683
expr = normalizeExpr(expression)
@@ -1716,7 +1721,7 @@ class JobsExpressionImpl extends SimpleReferenceExpressionImpl {
17161721
exists(string expr |
17171722
(
17181723
exists(getAJsonReferenceExpression(expression, _)) and
1719-
expr = normalizeExpr(expression).regexpCapture("(?i)fromjson\\((.*)\\).*", 1)
1724+
expr = normalizeExpr(expression).regexpCapture("(?i)(from|to)json\\((.*)\\).*", 2)
17201725
or
17211726
exists(getASimpleReferenceExpression(expression, _)) and
17221727
expr = normalizeExpr(expression)
@@ -1775,7 +1780,7 @@ class EnvExpressionImpl extends SimpleReferenceExpressionImpl {
17751780
exists(string expr |
17761781
(
17771782
exists(getAJsonReferenceExpression(expression, _)) and
1778-
expr = normalizeExpr(expression).regexpCapture("(?i)fromjson\\((.*)\\).*", 1)
1783+
expr = normalizeExpr(expression).regexpCapture("(?i)(from|to)json\\((.*)\\).*", 2)
17791784
or
17801785
exists(getASimpleReferenceExpression(expression, _)) and
17811786
expr = normalizeExpr(expression)
@@ -1810,7 +1815,7 @@ class MatrixExpressionImpl extends SimpleReferenceExpressionImpl {
18101815
exists(string expr |
18111816
(
18121817
exists(getAJsonReferenceExpression(expression, _)) and
1813-
expr = normalizeExpr(expression).regexpCapture("(?i)fromjson\\((.*)\\).*", 1)
1818+
expr = normalizeExpr(expression).regexpCapture("(?i)(from|to)json\\((.*)\\).*", 2)
18141819
or
18151820
exists(getASimpleReferenceExpression(expression, _)) and
18161821
expr = normalizeExpr(expression)

ql/lib/codeql/actions/dataflow/FlowSources.qll

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,47 @@ abstract class RemoteFlowSource extends SourceNode {
1818
/** Gets a string that describes the type of this remote flow source. */
1919
abstract string getSourceType();
2020

21+
/** Gets the event that triggered the source. */
22+
abstract Event getEvent();
23+
2124
override string getThreatModel() { result = "remote" }
2225
}
2326

2427
class GitHubCtxSource extends RemoteFlowSource {
2528
string flag;
29+
Event event;
2630

2731
GitHubCtxSource() {
2832
exists(Expression e, string context, string context_prefix |
2933
this.asExpr() = e and
3034
context = e.getExpression() and
35+
event = e.getEnclosingWorkflow().getATriggerEvent() and
3136
normalizeExpr(context) = "github.head_ref" and
32-
contextTriggerDataModel(e.getEnclosingWorkflow().getATriggerEvent().getName(), context_prefix) and
37+
contextTriggerDataModel(event.getName(), context_prefix) and
3338
normalizeExpr(context).matches("%" + context_prefix + "%") and
3439
flag = "branch"
3540
)
3641
}
3742

3843
override string getSourceType() { result = flag }
44+
45+
override Event getEvent() { result = event }
3946
}
4047

4148
class GitHubEventCtxSource extends RemoteFlowSource {
4249
string flag;
4350
string context;
51+
Event event;
4452

4553
GitHubEventCtxSource() {
4654
exists(Expression e, string regexp |
4755
this.asExpr() = e and
4856
context = e.getExpression() and
57+
event = e.getATriggerEvent() and
4958
(
5059
// the context is available for the job trigger events
5160
exists(string context_prefix |
52-
contextTriggerDataModel(e.getEnclosingWorkflow().getATriggerEvent().getName(),
53-
context_prefix) and
61+
contextTriggerDataModel(event.getName(), context_prefix) and
5462
normalizeExpr(context).matches("%" + context_prefix + "%")
5563
)
5664
or
@@ -65,12 +73,16 @@ class GitHubEventCtxSource extends RemoteFlowSource {
6573
override string getSourceType() { result = flag }
6674

6775
string getContext() { result = context }
76+
77+
override Event getEvent() { result = event }
6878
}
6979

7080
abstract class CommandSource extends RemoteFlowSource {
7181
abstract string getCommand();
7282

7383
abstract Run getEnclosingRun();
84+
85+
override Event getEvent() { result = this.getEnclosingRun().getATriggerEvent() }
7486
}
7587

7688
class GitCommandSource extends RemoteFlowSource, CommandSource {
@@ -181,18 +193,19 @@ class GitHubEventPathSource extends RemoteFlowSource, CommandSource {
181193

182194
class GitHubEventJsonSource extends RemoteFlowSource {
183195
string flag;
196+
Event event;
184197

185198
GitHubEventJsonSource() {
186199
exists(Expression e, string context, string regexp |
187200
this.asExpr() = e and
188201
context = e.getExpression() and
202+
event = e.getEnclosingWorkflow().getATriggerEvent() and
189203
untrustedEventPropertiesDataModel(regexp, _) and
190204
(
191205
// only contexts for the triggering events are considered tainted.
192206
// eg: for `pull_request`, we only consider `github.event.pull_request`
193207
exists(string context_prefix |
194-
contextTriggerDataModel(e.getEnclosingWorkflow().getATriggerEvent().getName(),
195-
context_prefix) and
208+
contextTriggerDataModel(event.getName(), context_prefix) and
196209
normalizeExpr(context).matches("%" + context_prefix + "%")
197210
) and
198211
normalizeExpr(context).regexpMatch("(?i).*" + wrapJsonRegexp(regexp) + ".*")
@@ -206,6 +219,8 @@ class GitHubEventJsonSource extends RemoteFlowSource {
206219
}
207220

208221
override string getSourceType() { result = flag }
222+
223+
override Event getEvent() { result = event }
209224
}
210225

211226
/**
@@ -217,6 +232,8 @@ class MaDSource extends RemoteFlowSource {
217232
MaDSource() { madSource(this, sourceType, _) }
218233

219234
override string getSourceType() { result = sourceType }
235+
236+
override Event getEvent() { result = this.asExpr().getATriggerEvent() }
220237
}
221238

222239
abstract class FileSource extends RemoteFlowSource { }
@@ -228,12 +245,16 @@ class ArtifactSource extends RemoteFlowSource, FileSource {
228245
ArtifactSource() { this.asExpr() instanceof UntrustedArtifactDownloadStep }
229246

230247
override string getSourceType() { result = "artifact" }
248+
249+
override Event getEvent() { result = this.asExpr().getATriggerEvent() }
231250
}
232251

233252
/**
234253
* A file from an untrusted checkout.
235254
*/
236255
private class CheckoutSource extends RemoteFlowSource, FileSource {
256+
Event event;
257+
237258
CheckoutSource() {
238259
// This should be:
239260
// source instanceof PRHeadCheckoutStep
@@ -245,7 +266,8 @@ private class CheckoutSource extends RemoteFlowSource, FileSource {
245266
uses.getCallee() = "actions/checkout" and
246267
exists(uses.getArgument("ref")) and
247268
not uses.getArgument("ref").matches("%base%") and
248-
uses.getATriggerEvent().getName() = checkoutTriggers()
269+
event = uses.getATriggerEvent() and
270+
event.getName() = checkoutTriggers()
249271
)
250272
or
251273
this.asExpr() instanceof GitMutableRefCheckout
@@ -258,6 +280,8 @@ private class CheckoutSource extends RemoteFlowSource, FileSource {
258280
}
259281

260282
override string getSourceType() { result = "artifact" }
283+
284+
override Event getEvent() { result = event }
261285
}
262286

263287
/**
@@ -273,6 +297,8 @@ class DornyPathsFilterSource extends RemoteFlowSource {
273297
}
274298

275299
override string getSourceType() { result = "filename" }
300+
301+
override Event getEvent() { result = this.asExpr().getATriggerEvent() }
276302
}
277303

278304
/**
@@ -294,6 +320,8 @@ class TJActionsChangedFilesSource extends RemoteFlowSource {
294320
}
295321

296322
override string getSourceType() { result = "filename" }
323+
324+
override Event getEvent() { result = this.asExpr().getATriggerEvent() }
297325
}
298326

299327
/**
@@ -315,6 +343,8 @@ class TJActionsVerifyChangedFilesSource extends RemoteFlowSource {
315343
}
316344

317345
override string getSourceType() { result = "filename" }
346+
347+
override Event getEvent() { result = this.asExpr().getATriggerEvent() }
318348
}
319349

320350
class Xt0rtedSlashCommandSource extends RemoteFlowSource {
@@ -327,6 +357,22 @@ class Xt0rtedSlashCommandSource extends RemoteFlowSource {
327357
}
328358

329359
override string getSourceType() { result = "text" }
360+
361+
override Event getEvent() { result = this.asExpr().getATriggerEvent() }
362+
}
363+
364+
class ZenteredIssueFormBodyParserSource extends RemoteFlowSource {
365+
ZenteredIssueFormBodyParserSource() {
366+
exists(UsesStep u |
367+
u.getCallee() = "zentered/issue-forms-body-parser" and
368+
not exists(u.getArgument("body")) and
369+
this.asExpr() = u
370+
)
371+
}
372+
373+
override string getSourceType() { result = "text" }
374+
375+
override Event getEvent() { result = this.asExpr().getATriggerEvent() }
330376
}
331377

332378
class OctokitRequestActionSource extends RemoteFlowSource {
@@ -348,4 +394,6 @@ class OctokitRequestActionSource extends RemoteFlowSource {
348394
}
349395

350396
override string getSourceType() { result = "text" }
397+
398+
override Event getEvent() { result = this.asExpr().getATriggerEvent() }
351399
}

0 commit comments

Comments
 (0)