Skip to content

Commit 9030cb3

Browse files
author
Alvaro Muñoz
authored
Merge pull request #5 from GitHubSecurityLab/env_context
Implement support for env context
2 parents 70d1741 + 99358c6 commit 9030cb3

File tree

6 files changed

+152
-79
lines changed

6 files changed

+152
-79
lines changed

ql/lib/codeql/actions/Ast.qll

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -184,26 +184,6 @@ class StepStmt extends Statement instanceof Actions::Step {
184184
string getId() { result = super.getId() }
185185

186186
JobStmt getJobStmt() { result = super.getJob() }
187-
188-
/**
189-
* Gets a environment variable expression by name in the scope of the current step.
190-
*/
191-
Expression getEnvExpr(string name) {
192-
exists(Actions::StepEnv env |
193-
env.getStep() = this and
194-
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
195-
)
196-
or
197-
exists(Actions::JobEnv env |
198-
env.getJob() = this.getJobStmt() and
199-
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
200-
)
201-
or
202-
exists(Actions::WorkflowEnv env |
203-
env.getWorkflow() = this.getJobStmt().getWorkflowStmt() and
204-
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
205-
)
206-
}
207187
}
208188

209189
/**
@@ -238,7 +218,25 @@ class StepUsesExpr extends StepStmt, UsesExpr {
238218
)
239219
}
240220

241-
override Expression getEnvExpr(string name) { result = this.(StepStmt).getEnvExpr(name) }
221+
/**
222+
* Gets a environment variable expression by name in the scope of the current step.
223+
*/
224+
override Expression getEnvExpr(string name) {
225+
exists(Actions::StepEnv env |
226+
env.getStep() = this and
227+
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
228+
)
229+
or
230+
exists(Actions::JobEnv env |
231+
env.getJob() = this.getJobStmt() and
232+
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
233+
)
234+
or
235+
exists(Actions::WorkflowEnv env |
236+
env.getWorkflow() = this.getJobStmt().getWorkflowStmt() and
237+
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
238+
)
239+
}
242240
}
243241

244242
/**
@@ -317,6 +315,26 @@ class RunExpr extends StepStmt, Expression {
317315
Expression getScriptExpr() { result = scriptExpr }
318316

319317
string getScript() { result = scriptExpr.getValue() }
318+
319+
/**
320+
* Gets a environment variable expression by name in the scope of the current node.
321+
*/
322+
Expression getEnvExpr(string name) {
323+
exists(Actions::StepEnv env |
324+
env.getStep() = this and
325+
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
326+
)
327+
or
328+
exists(Actions::JobEnv env |
329+
env.getJob() = this.getJobStmt() and
330+
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
331+
)
332+
or
333+
exists(Actions::WorkflowEnv env |
334+
env.getWorkflow() = this.getJobStmt().getWorkflowStmt() and
335+
env.(YamlMapping).maps(any(YamlScalar s | s.getValue() = name), result)
336+
)
337+
}
320338
}
321339

322340
/**
@@ -330,11 +348,14 @@ class ExprAccessExpr extends Expression instanceof YamlString {
330348
string getExpression() { result = expr }
331349

332350
JobStmt getJobStmt() { result.getAChildNode*() = this }
351+
352+
abstract Expression getRefExpr();
333353
}
334354

335355
/**
336-
* A ExprAccessExpr where the expression evaluated is a step output read.
337-
* eg: `${{ steps.changed-files.outputs.all_changed_files }}`
356+
* Holds for an ExprAccessExpr accesing the `steps` context.
357+
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
358+
* e.g. `${{ steps.changed-files.outputs.all_changed_files }}`
338359
*/
339360
class StepOutputAccessExpr extends ExprAccessExpr {
340361
string stepId;
@@ -347,17 +368,16 @@ class StepOutputAccessExpr extends ExprAccessExpr {
347368
this.getExpression().regexpCapture("steps\\.[A-Za-z0-9_-]+\\.outputs\\.([A-Za-z0-9_-]+)", 1)
348369
}
349370

350-
string getStepId() { result = stepId }
351-
352-
string getVarName() { result = varName }
353-
354-
StepStmt getStepStmt() { result.getId() = stepId }
371+
override Expression getRefExpr() {
372+
this.getJobStmt() = result.(StepStmt).getJobStmt() and
373+
result.(StepStmt).getId() = stepId
374+
}
355375
}
356376

357377
/**
358-
* A ExprAccessExpr where the expression evaluated is a job output read.
359-
* eg: `${{ needs.job1.outputs.foo}}`
360-
* eg: `${{ jobs.job1.outputs.foo}}` (for reusable workflows)
378+
* Holds for an ExprAccessExpr accesing the `needs` or `job` contexts.
379+
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
380+
* e.g. `${{ needs.job1.outputs.foo}}` or `${{ jobs.job1.outputs.foo}}` (for reusable workflows)
361381
*/
362382
class JobOutputAccessExpr extends ExprAccessExpr {
363383
string jobId;
@@ -372,9 +392,7 @@ class JobOutputAccessExpr extends ExprAccessExpr {
372392
.regexpCapture("(needs|jobs)\\.[A-Za-z0-9_-]+\\.outputs\\.([A-Za-z0-9_-]+)", 2)
373393
}
374394

375-
string getVarName() { result = varName }
376-
377-
Expression getOutputExpr() {
395+
override Expression getRefExpr() {
378396
exists(JobStmt job |
379397
job.getId() = jobId and
380398
job.getLocation().getFile() = this.getLocation().getFile() and
@@ -391,8 +409,9 @@ class JobOutputAccessExpr extends ExprAccessExpr {
391409
}
392410

393411
/**
394-
* A ExprAccessExpr where the expression evaluated is a reusable workflow input read.
395-
* eg: `${{ inputs.foo}}`
412+
* Holds for an ExprAccessExpr accesing the `inputs` context.
413+
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
414+
* e.g. `${{ inputs.foo }}`
396415
*/
397416
class ReusableWorkflowInputAccessExpr extends ExprAccessExpr {
398417
string paramName;
@@ -401,12 +420,29 @@ class ReusableWorkflowInputAccessExpr extends ExprAccessExpr {
401420
paramName = this.getExpression().regexpCapture("inputs\\.([A-Za-z0-9_-]+)", 1)
402421
}
403422

404-
string getParamName() { result = paramName }
405-
406-
Expression getInputExpr() {
423+
override Expression getRefExpr() {
407424
exists(ReusableWorkflowStmt w |
408425
w.getLocation().getFile() = this.getLocation().getFile() and
409426
w.getInputsStmt().getInputExpr(paramName) = result
410427
)
411428
}
412429
}
430+
431+
/**
432+
* Holds for an ExprAccessExpr accesing the `env` context.
433+
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
434+
* e.g. `${{ env.foo }}`
435+
*/
436+
class EnvAccessExpr extends ExprAccessExpr {
437+
string varName;
438+
439+
EnvAccessExpr() { varName = this.getExpression().regexpCapture("env\\.([A-Za-z0-9_-]+)", 1) }
440+
441+
override Expression getRefExpr() {
442+
exists(JobUsesExpr s | s.getEnvExpr(varName) = result)
443+
or
444+
exists(StepUsesExpr s | s.getEnvExpr(varName) = result)
445+
or
446+
exists(RunExpr s | s.getEnvExpr(varName) = result)
447+
}
448+
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ private class StepUsesTree extends StandardPreOrderTree instanceof StepUsesExpr
227227
override ControlFlowTree getChildNode(int i) {
228228
result =
229229
rank[i](Expression child, Location l |
230-
child = super.getArgumentExpr(_) and l = child.getLocation()
230+
(child = super.getArgumentExpr(_) or child = super.getEnvExpr(_)) and
231+
l = child.getLocation()
231232
|
232233
child
233234
order by
@@ -240,7 +241,8 @@ private class JobUsesTree extends StandardPreOrderTree instanceof JobUsesExpr {
240241
override ControlFlowTree getChildNode(int i) {
241242
result =
242243
rank[i](Expression child, Location l |
243-
child = super.getArgumentExpr(_) and l = child.getLocation()
244+
(child = super.getArgumentExpr(_) or child = super.getEnvExpr(_)) and
245+
l = child.getLocation()
244246
|
245247
child
246248
order by

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,10 @@ predicate runEnvToScriptstep(DataFlow::Node pred, DataFlow::Node succ) {
8181
exists(string script, string line |
8282
script = r.getScript() and
8383
line = script.splitAt("\n") and
84-
line.regexpMatch(".*::set-output\\s+name.*") and
84+
(
85+
line.regexpMatch(".*::set-output\\s+name.*") or
86+
line.regexpMatch(".*>>\\s*$GITHUB_ENV.*")
87+
) and
8588
script.indexOf("$" + ["", "{", "ENV{"] + varName) > 0
8689
) and
8790
succ.asExpr() = r

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

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -164,41 +164,52 @@ class ArgumentPosition extends string {
164164
*/
165165
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
166166

167-
predicate stepUsesOutputDefToUse(Node nodeFrom, Node nodeTo) {
168-
// nodeTo is an OutputVarAccessExpr scoped with the namespace of the nodeFrom Step output
169-
exists(StepUsesExpr uses, StepOutputAccessExpr outputRead |
170-
uses = nodeFrom.asExpr() and
171-
outputRead = nodeTo.asExpr() and
172-
outputRead.getStepId() = uses.getId() and
173-
uses.getJobStmt() = outputRead.getJobStmt()
167+
/**
168+
* Holds if there is a local flow step between a ${{}} expression accesing a step output variable and the step output itself
169+
* e.g. ${{ steps.step1.output.foo }}
170+
*/
171+
predicate stepsCtxLocalStep(Node nodeFrom, Node nodeTo) {
172+
exists(StepStmt astFrom, StepOutputAccessExpr astTo |
173+
(astFrom instanceof UsesExpr or astFrom instanceof RunExpr) and
174+
astFrom = nodeFrom.asExpr() and
175+
astTo = nodeTo.asExpr() and
176+
astTo.getRefExpr() = astFrom
174177
)
175178
}
176179

177-
predicate runOutputDefToUse(Node nodeFrom, Node nodeTo) {
178-
// nodeTo is an OutputVarAccessExpr scoped with the namespace of the nodeFrom Step output
179-
exists(RunExpr uses, StepOutputAccessExpr outputRead |
180-
uses = nodeFrom.asExpr() and
181-
outputRead = nodeTo.asExpr() and
182-
outputRead.getStepId() = uses.getId() and
183-
uses.getJobStmt() = outputRead.getJobStmt()
180+
/**
181+
* Holds if there is a local flow step between a ${{}} expression accesing a job output variable and the job output itself
182+
* e.g. ${{ needs.job1.output.foo }} or ${{ job.job1.output.foo }}
183+
*/
184+
predicate jobsCtxLocalStep(Node nodeFrom, Node nodeTo) {
185+
exists(Expression astFrom, JobOutputAccessExpr astTo |
186+
astFrom = nodeFrom.asExpr() and
187+
astTo = nodeTo.asExpr() and
188+
astTo.getRefExpr() = astFrom
184189
)
185190
}
186191

187-
predicate jobOutputDefToUse(Node nodeFrom, Node nodeTo) {
188-
// nodeTo is a JobOutputAccessExpr and nodeFrom is the Job output expression
189-
exists(Expression astFrom, JobOutputAccessExpr astTo |
192+
/**
193+
* Holds if there is a local flow step between a ${{}} expression accesing a reusable workflow input variable and the input itself
194+
* e.g. ${{ inputs.foo }}
195+
*/
196+
predicate inputsCtxLocalStep(Node nodeFrom, Node nodeTo) {
197+
exists(Expression astFrom, ReusableWorkflowInputAccessExpr astTo |
190198
astFrom = nodeFrom.asExpr() and
191199
astTo = nodeTo.asExpr() and
192-
astTo.getOutputExpr() = astFrom
200+
astTo.getRefExpr() = astFrom
193201
)
194202
}
195203

196-
predicate reusableWorkflowInputDefToUse(Node nodeFrom, Node nodeTo) {
197-
// nodeTo is a ReusableWorkflowInputAccessExpr and nodeFrom is the ReusableWorkflowStmt corresponding parameter expression
198-
exists(Expression astFrom, ReusableWorkflowInputAccessExpr astTo |
204+
/**
205+
* Holds if there is a local flow step between a ${{}} expression accesing an env var and the var definition itself
206+
* e.g. ${{ env.foo }}
207+
*/
208+
predicate envCtxLocalStep(Node nodeFrom, Node nodeTo) {
209+
exists(Expression astFrom, EnvAccessExpr astTo |
199210
astFrom = nodeFrom.asExpr() and
200211
astTo = nodeTo.asExpr() and
201-
astTo.getInputExpr() = astFrom
212+
astTo.getRefExpr() = astFrom
202213
)
203214
}
204215

@@ -209,10 +220,10 @@ predicate reusableWorkflowInputDefToUse(Node nodeFrom, Node nodeTo) {
209220
*/
210221
pragma[nomagic]
211222
predicate localFlowStep(Node nodeFrom, Node nodeTo) {
212-
stepUsesOutputDefToUse(nodeFrom, nodeTo) or
213-
runOutputDefToUse(nodeFrom, nodeTo) or
214-
jobOutputDefToUse(nodeFrom, nodeTo) or
215-
reusableWorkflowInputDefToUse(nodeFrom, nodeTo)
223+
stepsCtxLocalStep(nodeFrom, nodeTo) or
224+
jobsCtxLocalStep(nodeFrom, nodeTo) or
225+
inputsCtxLocalStep(nodeFrom, nodeTo) or
226+
envCtxLocalStep(nodeFrom, nodeTo)
216227
}
217228

218229
/**

ql/lib/test/test.ql

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ query predicate runStepChildren(RunExpr run, AstNode child) { child.getParentNod
3131

3232
query predicate varAccesses(ExprAccessExpr ea, string expr) { expr = ea.getExpression() }
3333

34-
query predicate outputAccesses(StepOutputAccessExpr va, string id, string var) {
35-
id = va.getStepId() and var = va.getVarName()
36-
}
37-
3834
query predicate orphanVarAccesses(ExprAccessExpr va, string var) {
3935
var = va.getExpression() and
4036
not exists(AstNode n | n = va.getParentNode())
@@ -53,25 +49,21 @@ query predicate cfgNodes(Cfg::Node n) {
5349
}
5450

5551
query predicate dfNodes(DataFlow::Node e) {
56-
e.getLocation().getFile().getBaseName() = "simple1.yml"
52+
e.getLocation().getFile().getBaseName() = "argus_case_study.yml"
5753
}
5854

5955
query predicate exprNodes(DataFlow::ExprNode e) { any() }
6056

6157
query predicate argumentNodes(DataFlow::ArgumentNode e) { any() }
6258

63-
query predicate localFlow(StepUsesExpr s, StepOutputAccessExpr o) { s.getId() = o.getStepId() }
64-
6559
query predicate usesIds(StepUsesExpr s, string a) { s.getId() = a }
6660

67-
query predicate varIds(StepOutputAccessExpr s, string a) { s.getStepId() = a }
68-
6961
query predicate nodeLocations(DataFlow::Node n, Location l) { n.getLocation() = l }
7062

7163
query predicate scopes(Cfg::CfgScope c) { any() }
7264

73-
query predicate sources(string action, string version, string output, string kind) {
74-
sourceModel(action, version, output, kind)
65+
query predicate sources(string action, string version, string output, string trigger, string kind) {
66+
sourceModel(action, version, output, trigger, kind)
7567
}
7668

7769
query predicate summaries(string action, string version, string input, string output, string kind) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Issue Workflow
2+
3+
on:
4+
issues:
5+
types: [opened, edited]
6+
7+
jobs:
8+
redirectIssue:
9+
runs-on: ubuntu-latest
10+
name: Check for issue transfer
11+
env:
12+
content_analysis_response: undefined
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Remove conflicting chars
16+
env:
17+
ISSUE_TITLE: ${{github.event.issue.title}}
18+
uses: frabert/[email protected]
19+
id: remove_quotations
20+
with:
21+
pattern: "\""
22+
string: ${{env.ISSUE_TITLE}}
23+
replace-with: "-"
24+
- name: Check info
25+
id: check-info
26+
run: |
27+
echo "foo $(pwsh bar ${{steps.remove_quotations.outputs.replaced}}) " >> $GITHUB_ENV
28+
29+

0 commit comments

Comments
 (0)