Skip to content

Commit cbe43bf

Browse files
author
Alvaro Muñoz
authored
Merge pull request #24 from GitHubSecurityLab/matrix_ctx
matrix ctx
2 parents 447b65e + 5b40d98 commit cbe43bf

File tree

8 files changed

+161
-38
lines changed

8 files changed

+161
-38
lines changed

build-test-dbs.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#!/bin/bash
2+
rm -rf ql/lib/test/test.testproj || true
3+
rm -rf ql/src/test/test.testproj || true
24
rm -rf src-test.testproj || true
35
rm -rf lib-test.testproj || true
46
codeql database create src-test.testproj -l yaml -s ql/src/test

ql/lib/codeql/actions/Ast.qll

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ class WorkflowStmt extends Statement instanceof Actions::Workflow {
8181
}
8282

8383
Statement getPermissionsStmt() { result = this.(YamlMapping).lookup("permissions") }
84+
85+
StrategyStmt getStrategyStmt() { result = this.(YamlMapping).lookup("strategy") }
8486
}
8587

8688
class ReusableWorkflowStmt extends WorkflowStmt {
@@ -125,6 +127,23 @@ class OutputsStmt extends Statement instanceof YamlMapping {
125127
string getAnOutputName() { this.(YamlMapping).maps(any(YamlString s | s.getValue() = result), _) }
126128
}
127129

130+
class StrategyStmt extends Statement instanceof YamlMapping {
131+
YamlMapping parent;
132+
133+
StrategyStmt() { parent.lookup("strategy") = this }
134+
135+
/**
136+
* Gets a specific matric expression (YamlMapping) by name.
137+
*/
138+
MatrixVariableExpr getMatrixVariableExpr(string name) {
139+
this.(YamlMapping).lookup("matrix").(YamlMapping).lookup(name) = result
140+
}
141+
142+
string getAMatrixVariableName() {
143+
this.(YamlMapping).maps(any(YamlString s | s.getValue() = result), _)
144+
}
145+
}
146+
128147
class InputExpr extends Expression instanceof YamlString {
129148
InputExpr() { exists(InputsStmt inputs | inputs.(YamlMapping).maps(this, _)) }
130149
}
@@ -138,6 +157,14 @@ class OutputExpr extends Expression instanceof YamlString {
138157
}
139158
}
140159

160+
class MatrixVariableExpr extends Expression instanceof YamlString {
161+
MatrixVariableExpr() {
162+
exists(StrategyStmt outputs |
163+
outputs.(YamlMapping).lookup("matrix").(YamlMapping).lookup(_) = this
164+
)
165+
}
166+
}
167+
141168
/**
142169
* A Job is a collection of steps that run in an execution environment.
143170
*/
@@ -191,6 +218,8 @@ class JobStmt extends Statement instanceof Actions::Job {
191218
IfStmt getIfStmt() { result = super.getIf() }
192219

193220
Statement getPermissionsStmt() { result = this.(YamlMapping).lookup("permissions") }
221+
222+
StrategyStmt getStrategyStmt() { result = this.(YamlMapping).lookup("strategy") }
194223
}
195224

196225
/**
@@ -332,7 +361,8 @@ class ExprAccessExpr extends Expression instanceof YamlString {
332361
class CtxAccessExpr extends ExprAccessExpr {
333362
CtxAccessExpr() {
334363
expr.regexpMatch([
335-
stepsCtxRegex(), needsCtxRegex(), jobsCtxRegex(), envCtxRegex(), inputsCtxRegex()
364+
stepsCtxRegex(), needsCtxRegex(), jobsCtxRegex(), envCtxRegex(), inputsCtxRegex(),
365+
matrixCtxRegex()
336366
])
337367
}
338368

@@ -342,22 +372,28 @@ class CtxAccessExpr extends ExprAccessExpr {
342372
}
343373

344374
private string stepsCtxRegex() {
345-
result = "\\bsteps\\.([A-Za-z0-9_-]+)\\.outputs\\.([A-Za-z0-9_-]+)\\b"
375+
result = wrapRegexp("steps\\.([A-Za-z0-9_-]+)\\.outputs\\.([A-Za-z0-9_-]+)")
346376
}
347377

348378
private string needsCtxRegex() {
349-
result = "\\bneeds\\.([A-Za-z0-9_-]+)\\.outputs\\.([A-Za-z0-9_-]+)\\b"
379+
result = wrapRegexp("needs\\.([A-Za-z0-9_-]+)\\.outputs\\.([A-Za-z0-9_-]+)")
350380
}
351381

352382
private string jobsCtxRegex() {
353-
result = "\\bjobs\\.([A-Za-z0-9_-]+)\\.outputs\\.([A-Za-z0-9_-]+)\\b"
383+
result = wrapRegexp("jobs\\.([A-Za-z0-9_-]+)\\.outputs\\.([A-Za-z0-9_-]+)")
354384
}
355385

356-
private string envCtxRegex() { result = "\\benv\\.([A-Za-z0-9_-]+)\\b" }
386+
private string envCtxRegex() { result = wrapRegexp("env\\.([A-Za-z0-9_-]+)") }
387+
388+
private string matrixCtxRegex() { result = wrapRegexp("matrix\\.([A-Za-z0-9_-]+)") }
357389

358390
private string inputsCtxRegex() {
359-
result = "\\binputs\\.([A-Za-z0-9_-]+)\\b" or
360-
result = "\\bgithub\\.event\\.inputs\\.([A-Za-z0-9_-]+)\\b"
391+
result = wrapRegexp(["inputs\\.([A-Za-z0-9_-]+)", "github\\.event\\.inputs\\.([A-Za-z0-9_-]+)"])
392+
}
393+
394+
bindingset[regex]
395+
private string wrapRegexp(string regex) {
396+
result = ["\\b" + regex + "\\b", "fromJSON\\(" + regex + "\\)", "toJSON\\(" + regex + "\\)"]
361397
}
362398

363399
/**
@@ -487,3 +523,31 @@ class EnvCtxAccessExpr extends CtxAccessExpr {
487523
)
488524
}
489525
}
526+
527+
/**
528+
* Holds for an expression accesing the `matrix` context.
529+
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
530+
* e.g. `${{ matrix.foo }}`
531+
*/
532+
class MatrixCtxAccessExpr extends CtxAccessExpr {
533+
string fieldName;
534+
535+
MatrixCtxAccessExpr() {
536+
expr.regexpMatch(matrixCtxRegex()) and
537+
fieldName = expr.regexpCapture(matrixCtxRegex(), 1)
538+
}
539+
540+
override string getFieldName() { result = fieldName }
541+
542+
override Expression getRefExpr() {
543+
exists(WorkflowStmt w |
544+
w.getStrategyStmt().getMatrixVariableExpr(fieldName) = result and
545+
w.getAChildNode*() = this
546+
)
547+
or
548+
exists(JobStmt j |
549+
j.getStrategyStmt().getMatrixVariableExpr(fieldName) = result and
550+
j.getAChildNode*() = this
551+
)
552+
}
553+
}

ql/lib/codeql/actions/controlflow/internal/Cfg.qll

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ private class WorkflowTree extends StandardPreOrderTree instanceof WorkflowStmt
174174
(
175175
child = this.(ReusableWorkflowStmt).getInputsStmt() or
176176
child = this.(ReusableWorkflowStmt).getOutputsStmt() or
177+
child = this.(ReusableWorkflowStmt).getStrategyStmt() or
177178
child = this.(ReusableWorkflowStmt).getAJobStmt()
178179
) and
179180
l = child.getLocation()
@@ -185,7 +186,10 @@ private class WorkflowTree extends StandardPreOrderTree instanceof WorkflowStmt
185186
else
186187
result =
187188
rank[i](Expression child, Location l |
188-
child = super.getAJobStmt() and
189+
(
190+
child = super.getAJobStmt() or
191+
child = super.getStrategyStmt()
192+
) and
189193
l = child.getLocation()
190194
|
191195
child
@@ -225,13 +229,29 @@ private class OutputsTree extends StandardPreOrderTree instanceof OutputsStmt {
225229

226230
private class OutputExprTree extends LeafTree instanceof OutputExpr { }
227231

232+
private class StrategyTree extends StandardPreOrderTree instanceof StrategyStmt {
233+
override ControlFlowTree getChildNode(int i) {
234+
result =
235+
rank[i](Expression child, Location l |
236+
child = super.getMatrixVariableExpr(_) and l = child.getLocation()
237+
|
238+
child
239+
order by
240+
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
241+
)
242+
}
243+
}
244+
245+
private class MatrixVariableExprTree extends LeafTree instanceof MatrixVariableExpr { }
246+
228247
private class JobTree extends StandardPreOrderTree instanceof JobStmt {
229248
override ControlFlowTree getChildNode(int i) {
230249
result =
231250
rank[i](Expression child, Location l |
232251
(
233252
child = super.getAStepStmt() or
234253
child = super.getOutputsStmt() or
254+
child = super.getStrategyStmt() or
235255
child = super.getUsesExpr()
236256
) and
237257
l = child.getLocation()

ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
133133

134134
newtype TContent =
135135
TFieldContent(string name) {
136-
// We only use field flow for steps and jobs outputs, not for accessing other context fields such as env or inputs
136+
// We only use field flow for steps and jobs outputs, not for accessing other context fields such as env, matrix or inputs
137137
name = any(StepsCtxAccessExpr a).getFieldName() or
138138
name = any(NeedsCtxAccessExpr a).getFieldName() or
139139
name = any(JobsCtxAccessExpr a).getFieldName()
@@ -209,6 +209,18 @@ predicate inputsCtxLocalStep(Node nodeFrom, Node nodeTo) {
209209
)
210210
}
211211

212+
/**
213+
* Holds if there is a local flow step between a ${{}} expression accesing a matrix variable and the matrix itself
214+
* e.g. ${{ matrix.foo }}
215+
*/
216+
predicate matrixCtxLocalStep(Node nodeFrom, Node nodeTo) {
217+
exists(Expression astFrom, MatrixCtxAccessExpr astTo |
218+
astFrom = nodeFrom.asExpr() and
219+
astTo = nodeTo.asExpr() and
220+
astTo.getRefExpr() = astFrom
221+
)
222+
}
223+
212224
/**
213225
* Holds if there is a local flow step between a ${{}} expression accesing an env var and the var definition itself
214226
* e.g. ${{ env.foo }}
@@ -234,6 +246,7 @@ predicate localFlowStep(Node nodeFrom, Node nodeTo) {
234246
stepsCtxLocalStep(nodeFrom, nodeTo) or
235247
needsCtxLocalStep(nodeFrom, nodeTo) or
236248
inputsCtxLocalStep(nodeFrom, nodeTo) or
249+
matrixCtxLocalStep(nodeFrom, nodeTo) or
237250
envCtxLocalStep(nodeFrom, nodeTo)
238251
}
239252

ql/src/test/partial.ql renamed to ql/src/Debug/partial.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import PartialFlow::PartialPathGraph
1515
private module MyConfig implements DataFlow::ConfigSig {
1616
predicate isSource(DataFlow::Node source) {
1717
source instanceof RemoteFlowSource and
18-
source.getLocation().getFile().getBaseName() = "calling_workflow.yml"
18+
source.getLocation().getFile().getBaseName() = "matrix.yml"
1919
}
2020

2121
predicate isSink(DataFlow::Node sink) { none() }
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: "CodeQL Auto Language"
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
schedule:
9+
- cron: '17 19 * * 6'
10+
11+
jobs:
12+
create-matrix:
13+
runs-on: ubuntu-latest
14+
outputs:
15+
matrix: ${{ steps.set-matrix.outputs.all_changed_files }}
16+
steps:
17+
- name: Get changed files
18+
id: set-matrix
19+
uses: tj-actions/changed-files@v40
20+
21+
analyze:
22+
needs: create-matrix
23+
if: ${{ needs.create-matrix.outputs.matrix != '[]' }}
24+
name: Analyze
25+
runs-on: ubuntu-latest
26+
permissions:
27+
actions: read
28+
contents: read
29+
security-events: write
30+
31+
strategy:
32+
fail-fast: false
33+
matrix:
34+
language: ${{ fromJSON(needs.create-matrix.outputs.matrix) }}
35+
36+
steps:
37+
- name: Checkout repository
38+
uses: actions/checkout@v3
39+
40+
# Initializes the CodeQL tools for scanning.
41+
- run: |
42+
${{ matrix.language }}

ql/src/test/partial.expected

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)