diff --git a/actions/ql/lib/codeql/actions/security/ArgumentInjectionQuery.qll b/actions/ql/lib/codeql/actions/security/ArgumentInjectionQuery.qll index 1d461cca3df2..6ac385c78d31 100644 --- a/actions/ql/lib/codeql/actions/security/ArgumentInjectionQuery.qll +++ b/actions/ql/lib/codeql/actions/security/ArgumentInjectionQuery.qll @@ -1,6 +1,7 @@ private import actions private import codeql.actions.TaintTracking private import codeql.actions.dataflow.ExternalFlow +private import codeql.actions.security.ControlChecks import codeql.actions.dataflow.FlowSources import codeql.actions.DataFlow @@ -88,6 +89,19 @@ private module ArgumentInjectionConfig implements DataFlow::ConfigSig { run.getScript().getAnEnvReachingArgumentInjectionSink(var, _, _) ) } + + predicate observeDiffInformedIncrementalMode() { any() } + + Location getASelectedSourceLocation(DataFlow::Node source) { none() } + + Location getASelectedSinkLocation(DataFlow::Node sink) { + result = sink.getLocation() + or + exists(Event event | result = event.getLocation() | + inPrivilegedContext(sink.asExpr(), event) and + not exists(ControlCheck check | check.protects(sink.asExpr(), event, "argument-injection")) + ) + } } /** Tracks flow of unsafe user input that is used to construct and evaluate a code script. */ diff --git a/actions/ql/lib/codeql/actions/security/ArtifactPoisoningQuery.qll b/actions/ql/lib/codeql/actions/security/ArtifactPoisoningQuery.qll index f0649bb8174e..b6480a53fc9a 100644 --- a/actions/ql/lib/codeql/actions/security/ArtifactPoisoningQuery.qll +++ b/actions/ql/lib/codeql/actions/security/ArtifactPoisoningQuery.qll @@ -4,6 +4,7 @@ import codeql.actions.DataFlow import codeql.actions.dataflow.FlowSources import codeql.actions.security.PoisonableSteps import codeql.actions.security.UntrustedCheckoutQuery +import codeql.actions.security.ControlChecks string unzipRegexp() { result = "(unzip|tar)\\s+.*" } @@ -318,6 +319,19 @@ private module ArtifactPoisoningConfig implements DataFlow::ConfigSig { exists(run.getScript().getAFileReadCommand()) ) } + + predicate observeDiffInformedIncrementalMode() { any() } + + Location getASelectedSourceLocation(DataFlow::Node source) { none() } + + Location getASelectedSinkLocation(DataFlow::Node sink) { + result = sink.getLocation() + or + exists(Event event | result = event.getLocation() | + inPrivilegedContext(sink.asExpr(), event) and + not exists(ControlCheck check | check.protects(sink.asExpr(), event, "artifact-poisoning")) + ) + } } /** Tracks flow of unsafe artifacts that is used in an insecure way. */ diff --git a/actions/ql/lib/codeql/actions/security/CodeInjectionQuery.qll b/actions/ql/lib/codeql/actions/security/CodeInjectionQuery.qll index fac498f72dab..a75ac84ef772 100644 --- a/actions/ql/lib/codeql/actions/security/CodeInjectionQuery.qll +++ b/actions/ql/lib/codeql/actions/security/CodeInjectionQuery.qll @@ -3,6 +3,8 @@ private import codeql.actions.TaintTracking private import codeql.actions.dataflow.ExternalFlow import codeql.actions.dataflow.FlowSources import codeql.actions.DataFlow +import codeql.actions.security.ControlChecks +import codeql.actions.security.CachePoisoningQuery class CodeInjectionSink extends DataFlow::Node { CodeInjectionSink() { @@ -35,6 +37,53 @@ private module CodeInjectionConfig implements DataFlow::ConfigSig { exists(run.getScript().getAFileReadCommand()) ) } + + predicate observeDiffInformedIncrementalMode() { any() } + + Location getASelectedSourceLocation(DataFlow::Node source) { none() } + + Location getASelectedSinkLocation(DataFlow::Node sink) { + result = sink.getLocation() + or + // where clause from CodeInjectionCritical.ql + exists(Event event, RemoteFlowSource source | result = event.getLocation() | + inPrivilegedContext(sink.asExpr(), event) and + isSource(source) and + source.getEventName() = event.getName() and + not exists(ControlCheck check | check.protects(sink.asExpr(), event, "code-injection")) and + // exclude cases where the sink is a JS script and the expression uses toJson + not exists(UsesStep script | + script.getCallee() = "actions/github-script" and + script.getArgumentExpr("script") = sink.asExpr() and + exists(getAToJsonReferenceExpression(sink.asExpr().(Expression).getExpression(), _)) + ) + ) + or + // where clause from CachePoisoningViaCodeInjection.ql + exists(Event event, LocalJob job, DataFlow::Node source | result = event.getLocation() | + job = sink.asExpr().getEnclosingJob() and + job.getATriggerEvent() = event and + // job can be triggered by an external user + event.isExternallyTriggerable() and + // the checkout is not controlled by an access check + isSource(source) and + not exists(ControlCheck check | check.protects(source.asExpr(), event, "code-injection")) and + // excluding privileged workflows since they can be exploited in easier circumstances + // which is covered by `actions/code-injection/critical` + not job.isPrivilegedExternallyTriggerable(event) and + ( + // the workflow runs in the context of the default branch + runsOnDefaultBranch(event) + or + // the workflow caller runs in the context of the default branch + event.getName() = "workflow_call" and + exists(ExternalJob caller | + caller.getCallee() = job.getLocation().getFile().getRelativePath() and + runsOnDefaultBranch(caller.getATriggerEvent()) + ) + ) + ) + } } /** Tracks flow of unsafe user input that is used to construct and evaluate a code script. */ diff --git a/actions/ql/lib/codeql/actions/security/CommandInjectionQuery.qll b/actions/ql/lib/codeql/actions/security/CommandInjectionQuery.qll index 59d523cd5827..878b7a598056 100644 --- a/actions/ql/lib/codeql/actions/security/CommandInjectionQuery.qll +++ b/actions/ql/lib/codeql/actions/security/CommandInjectionQuery.qll @@ -3,6 +3,7 @@ private import codeql.actions.TaintTracking private import codeql.actions.dataflow.ExternalFlow import codeql.actions.dataflow.FlowSources import codeql.actions.DataFlow +import codeql.actions.security.ControlChecks private class CommandInjectionSink extends DataFlow::Node { CommandInjectionSink() { madSink(this, "command-injection") } @@ -16,6 +17,22 @@ private module CommandInjectionConfig implements DataFlow::ConfigSig { predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } predicate isSink(DataFlow::Node sink) { sink instanceof CommandInjectionSink } + + predicate observeDiffInformedIncrementalMode() { any() } + + Location getASelectedSourceLocation(DataFlow::Node source) { none() } + + Location getASelectedSinkLocation(DataFlow::Node sink) { + result = sink.getLocation() + or + // where clause from CommandInjectionCritical.ql + exists(Event event | result = event.getLocation() | + inPrivilegedContext(sink.asExpr(), event) and + not exists(ControlCheck check | + check.protects(sink.asExpr(), event, ["command-injection", "code-injection"]) + ) + ) + } } /** Tracks flow of unsafe user input that is used to construct and evaluate a system command. */ diff --git a/actions/ql/lib/codeql/actions/security/EnvPathInjectionQuery.qll b/actions/ql/lib/codeql/actions/security/EnvPathInjectionQuery.qll index 33efc9b1bc8f..0ed0d2a877e9 100644 --- a/actions/ql/lib/codeql/actions/security/EnvPathInjectionQuery.qll +++ b/actions/ql/lib/codeql/actions/security/EnvPathInjectionQuery.qll @@ -108,6 +108,30 @@ private module EnvPathInjectionConfig implements DataFlow::ConfigSig { exists(run.getScript().getAFileReadCommand()) ) } + + predicate observeDiffInformedIncrementalMode() { any() } + + Location getASelectedSourceLocation(DataFlow::Node source) { none() } + + Location getASelectedSinkLocation(DataFlow::Node sink) { + result = sink.getLocation() + or + // where clause from EnvPathInjectionCritical.ql + exists(Event event, RemoteFlowSource source | result = event.getLocation() | + inPrivilegedContext(sink.asExpr(), event) and + isSource(source) and + ( + not source.getSourceType() = "artifact" and + not exists(ControlCheck check | check.protects(sink.asExpr(), event, "code-injection")) + or + source.getSourceType() = "artifact" and + not exists(ControlCheck check | + check.protects(sink.asExpr(), event, ["untrusted-checkout", "artifact-poisoning"]) + ) and + sink instanceof EnvPathInjectionFromFileReadSink + ) + ) + } } /** Tracks flow of unsafe user input that is used to construct and evaluate the PATH environment variable. */ diff --git a/actions/ql/lib/codeql/actions/security/EnvVarInjectionQuery.qll b/actions/ql/lib/codeql/actions/security/EnvVarInjectionQuery.qll index 656ea1207b51..a99c346e16d3 100644 --- a/actions/ql/lib/codeql/actions/security/EnvVarInjectionQuery.qll +++ b/actions/ql/lib/codeql/actions/security/EnvVarInjectionQuery.qll @@ -163,6 +163,40 @@ private module EnvVarInjectionConfig implements DataFlow::ConfigSig { exists(run.getScript().getAFileReadCommand()) ) } + + predicate observeDiffInformedIncrementalMode() { any() } + + Location getASelectedSourceLocation(DataFlow::Node source) { none() } + + Location getASelectedSinkLocation(DataFlow::Node sink) { + result = sink.getLocation() + or + // where clause from EnvVarInjectionCritical.ql + exists(Event event, RemoteFlowSource source | result = event.getLocation() | + inPrivilegedContext(sink.asExpr(), event) and + isSource(source) and + // exclude paths to file read sinks from non-artifact sources + ( + // source is text + not source.getSourceType() = "artifact" and + not exists(ControlCheck check | + check.protects(sink.asExpr(), event, ["envvar-injection", "code-injection"]) + ) + or + // source is an artifact or a file from an untrusted checkout + source.getSourceType() = "artifact" and + not exists(ControlCheck check | + check + .protects(sink.asExpr(), event, + ["envvar-injection", "untrusted-checkout", "artifact-poisoning"]) + ) and + ( + sink instanceof EnvVarInjectionFromFileReadSink or + madSink(sink, "envvar-injection") + ) + ) + ) + } } /** Tracks flow of unsafe user input that is used to construct and evaluate an environment variable. */