Skip to content

Commit ac3e38e

Browse files
authored
Merge branch 'main' into bazookamusic/cwe-1427
2 parents b15a1af + 929870d commit ac3e38e

21 files changed

Lines changed: 333 additions & 179 deletions

File tree

CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @github/code-scanning-alert-coverage
33

44
# CodeQL language libraries
5-
/actions/ @github/codeql-dynamic
5+
/actions/ @github/code-scanning-alert-coverage
66
/cpp/ @github/codeql-c-analysis
77
/csharp/ @github/codeql-csharp
88
/csharp/autobuilder/Semmle.Autobuild.Cpp @github/codeql-c-extractor @github/code-scanning-language-coverage

actions/ql/src/Security/CWE-829/UntrustedCheckoutMedium.ql

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
2-
* @name Checkout of untrusted code in a trusted context
3-
* @description Privileged workflows have read/write access to the base repository and access to secrets.
4-
* By explicitly checking out and running the build script from a fork the untrusted code is running in an environment
5-
* that is able to push to the base repository and to access secrets.
2+
* @name Checkout of untrusted code in a non-privileged context
3+
* @description Checking out and running the build script from a fork executes untrusted code. Even in a
4+
* non-privileged workflow, this can be abused, for example to compromise self-hosted runners
5+
* or to poison caches and artifacts that are later consumed by privileged workflows.
66
* @kind problem
77
* @problem.severity warning
88
* @precision medium
@@ -20,4 +20,4 @@ from PRHeadCheckoutStep checkout
2020
where
2121
// the checkout occurs in a non-privileged context
2222
inNonPrivilegedContext(checkout)
23-
select checkout, "Potential unsafe checkout of untrusted pull request on privileged workflow."
23+
select checkout, "Potential unsafe checkout of untrusted pull request on non-privileged workflow."
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: queryMetadata
3+
---
4+
* The name, description, and alert message of `actions/untrusted-checkout/medium` have been corrected to describe a non-privileged context.
Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
| .github/workflows/artifactpoisoning81.yml:11:9:14:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
2-
| .github/workflows/dependabot2.yml:33:9:38:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
3-
| .github/workflows/mend.yml:22:9:29:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
4-
| .github/workflows/poc3.yml:18:7:25:4 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
5-
| .github/workflows/poc.yml:30:9:36:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
6-
| .github/workflows/priv_pull_request_checkout.yml:14:9:20:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
7-
| .github/workflows/test3.yml:28:9:33:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
8-
| .github/workflows/test4.yml:18:7:25:4 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
9-
| .github/workflows/test8.yml:20:9:26:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
10-
| .github/workflows/test9.yml:11:9:16:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
1+
| .github/workflows/artifactpoisoning81.yml:11:9:14:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
2+
| .github/workflows/dependabot2.yml:33:9:38:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
3+
| .github/workflows/mend.yml:22:9:29:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
4+
| .github/workflows/poc3.yml:18:7:25:4 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
5+
| .github/workflows/poc.yml:30:9:36:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
6+
| .github/workflows/priv_pull_request_checkout.yml:14:9:20:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
7+
| .github/workflows/test3.yml:28:9:33:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
8+
| .github/workflows/test4.yml:18:7:25:4 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
9+
| .github/workflows/test8.yml:20:9:26:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
10+
| .github/workflows/test9.yml:11:9:16:6 | Uses Step | Potential unsafe checkout of untrusted pull request on non-privileged workflow. |
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: deprecated
3+
---
4+
* `FuncTypeExpr.getResultDecl()` has been deprecated. Use `FuncTypeExpr.getResultDecl(int i)` instead.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* `DataFlow::ResultNode`s are no longer created for returned expressions in functions with named result parameters. In this case there are already result nodes corresponding to `IR::ReadResultInstruction`s at the end of the function body.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* `FuncTypeExpr.getNumResult()` now gets the number of result parameters. It previously got the number of result declarations, which is different when one result declaration declares more than one variable, as in `x, y int`. All uses of it expected the number of result parameters. Its QLDoc has been updated.

go/ql/lib/semmle/go/Expr.qll

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,17 +1049,29 @@ class FuncTypeExpr extends @functypeexpr, TypeExpr, ScopeNode, FieldParent {
10491049
*/
10501050
int getNumParameter() { result = count(this.getAParameterDecl().getANameExpr()) }
10511051

1052-
/** Gets the `i`th result of this function type (0-based). */
1052+
/**
1053+
* Gets the `i`th result declaration of this function type (0-based).
1054+
*
1055+
* Note: `x, y int` is a single `ResultVariableDecl`.
1056+
*/
10531057
ResultVariableDecl getResultDecl(int i) { result = this.getField(-(i + 1)) }
10541058

1055-
/** Gets a result of this function type. */
1059+
/**
1060+
* Gets a result declaration of this function type.
1061+
*
1062+
* Note: `x, y int` is a single `ResultVariableDecl`.
1063+
*/
10561064
ResultVariableDecl getAResultDecl() { result = this.getResultDecl(_) }
10571065

1058-
/** Gets the number of results of this function type. */
1059-
int getNumResult() { result = count(this.getAResultDecl()) }
1066+
/** Gets the number of result parameters of this function type. */
1067+
int getNumResult() { result = count(this.getAResultDecl().getANameExpr()) }
10601068

1061-
/** Gets the result of this function type, if there is only one. */
1062-
ResultVariableDecl getResultDecl() { this.getNumResult() = 1 and result = this.getAResultDecl() }
1069+
/**
1070+
* DEPRECATED: Use `getResultDecl(int i)` instead.
1071+
*/
1072+
deprecated ResultVariableDecl getResultDecl() {
1073+
this.getNumResult() = 1 and result = this.getAResultDecl()
1074+
}
10631075

10641076
override string toString() { result = "function type" }
10651077

go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -923,15 +923,20 @@ module Public {
923923
/**
924924
* A node whose value is returned as a result from a function.
925925
*
926-
* This can either be a node corresponding to an expression in a return statement,
927-
* or a node representing the current value of a named result variable at the exit
928-
* of the function.
926+
* If the function declares named result variables, this is a node representing
927+
* the current value of one of those variables at function exit. Otherwise, this
928+
* is a node corresponding to an expression in a return statement.
929929
*/
930930
class ResultNode extends InstructionNode {
931931
int i;
932932

933933
ResultNode() {
934934
exists(FuncDef fd |
935+
// If the function has named result variables, then the
936+
// `IR::ReadResultInstruction` nodes at the end of the function are
937+
// the correct result nodes. Otherwise, the returned expressions are
938+
// the result nodes.
939+
not exists(fd.getAResultVar()) and
935940
exists(IR::ReturnInstruction ret | ret.getRoot() = fd | insn = ret.getResult(i))
936941
or
937942
insn.(IR::ReadResultInstruction).reads(fd.getResultVar(i))

go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class SyncFileFun extends Method {
5555

5656
/**
5757
* Holds if a `call` to a function is "unhandled". That is, it is either
58-
* deferred or its result is not assigned to anything.
58+
* deferred or used as an expression statement, so that its result is discarded.
5959
*
6060
* TODO: maybe we should check that something is actually done with the result
6161
*/
@@ -77,7 +77,6 @@ predicate isWritableFileHandle(DataFlow::Node source, DataFlow::CallNode call) {
7777
// get the flags expression used for opening the file
7878
call.getArgument(1) = flags and
7979
// extract individual flags from the argument
80-
// flag = flag.getAChild*() and
8180
flag = getConstants(flags.asExpr()) and
8281
// check for one which signals that the handle will be writable
8382
// note that we are underestimating here, since the flags may be
@@ -87,27 +86,18 @@ predicate isWritableFileHandle(DataFlow::Node source, DataFlow::CallNode call) {
8786
}
8887

8988
/**
90-
* Holds if `os.File.Close` is called on `sink`.
89+
* Holds if `postDominator` post-dominates `node` in the control-flow graph. That is,
90+
* every path from `node` to the exit of the enclosing function passes through
91+
* `postDominator`.
9192
*/
92-
predicate isCloseSink(DataFlow::Node sink, DataFlow::CallNode closeCall) {
93-
// find calls to the os.File.Close function
94-
closeCall = any(CloseFileFun f).getACall() and
95-
// that are unhandled
96-
unhandledCall(closeCall) and
97-
// where the function is called on the sink
98-
closeCall.getReceiver() = sink and
99-
// and check that it is not dominated by a call to `os.File.Sync`.
100-
// TODO: fix this logic when `closeCall` is in a defer statement.
101-
not exists(IR::Instruction syncInstr, DataFlow::Node syncReceiver, DataFlow::CallNode syncCall |
102-
// match the instruction corresponding to an `os.File.Sync` call with the predecessor
103-
syncCall.asInstruction() = syncInstr and
104-
// check that the call to `os.File.Sync` is handled
105-
isHandledSync(syncReceiver, syncCall) and
106-
// find a predecessor to `closeCall` in the control flow graph which dominates the call to
107-
// `os.File.Close`
108-
syncInstr.dominatesNode(closeCall.asInstruction()) and
109-
// check that `os.File.Sync` is called on the same object as `os.File.Close`
110-
exists(DataFlow::SsaNode ssa | ssa.getAUse() = sink and ssa.getAUse() = syncReceiver)
93+
pragma[inline]
94+
predicate postDominatesNode(ControlFlow::Node postDominator, ControlFlow::Node node) {
95+
exists(ReachableBasicBlock pdbb, ReachableBasicBlock nbb, int i, int j |
96+
postDominator = pdbb.getNode(i) and node = nbb.getNode(j)
97+
|
98+
pdbb.strictlyPostDominates(nbb)
99+
or
100+
pdbb = nbb and i >= j
111101
)
112102
}
113103

@@ -127,7 +117,39 @@ predicate isHandledSync(DataFlow::Node sink, DataFlow::CallNode syncCall) {
127117
module UnhandledFileCloseConfig implements DataFlow::ConfigSig {
128118
predicate isSource(DataFlow::Node source) { isWritableFileHandle(source, _) }
129119

130-
predicate isSink(DataFlow::Node sink) { isCloseSink(sink, _) }
120+
predicate isSink(DataFlow::Node sink) {
121+
exists(DataFlow::CallNode closeCall |
122+
// `closeCall` is an unhandled call to `os.File.Close` on `sink`
123+
closeCall = any(CloseFileFun f).getACall() and
124+
unhandledCall(closeCall) and
125+
closeCall.getReceiver() = sink
126+
|
127+
// `closeCall` is not guaranteed to be preceded during
128+
// execution by a handled call to `os.File.Sync` on the same file handle.
129+
not exists(DataFlow::Node syncReceiver, DataFlow::CallNode syncCall |
130+
// check that the call to `os.File.Sync` is handled
131+
isHandledSync(syncReceiver, syncCall) and
132+
// check that `os.File.Sync` is called on the same object as `os.File.Close`
133+
exists(DataFlow::SsaNode ssa | ssa.getAUse() = sink and ssa.getAUse() = syncReceiver)
134+
|
135+
if exists(DeferStmt defer | defer.getCall() = closeCall.asExpr())
136+
then
137+
// When the call to `os.File.Close` is deferred it runs when the enclosing function
138+
// returns, but the receiver of the deferred call is evaluated where the `defer`
139+
// statement appears. It is therefore enough for the handled call to `os.File.Sync`
140+
// to post-dominate that point, since that guarantees `os.File.Sync` runs before the
141+
// deferred `os.File.Close` on every path on which the `os.File.Close` is registered.
142+
// We cannot reuse the domination check below because the control-flow graph splices
143+
// the deferred call in at the function exit, where it may be reachable along paths
144+
// that do not pass through the call to `os.File.Sync`.
145+
postDominatesNode(syncCall.asInstruction(), sink.asInstruction())
146+
else
147+
// Otherwise the call to `os.File.Close` is executed where it appears, so we require
148+
// the handled call to `os.File.Sync` to dominate it.
149+
syncCall.asInstruction().dominatesNode(closeCall.asInstruction())
150+
)
151+
)
152+
}
131153

132154
predicate observeDiffInformedIncrementalMode() { any() }
133155

@@ -148,14 +170,12 @@ import UnhandledFileCloseFlow::PathGraph
148170

149171
from
150172
UnhandledFileCloseFlow::PathNode source, DataFlow::CallNode openCall,
151-
UnhandledFileCloseFlow::PathNode sink, DataFlow::CallNode closeCall
173+
UnhandledFileCloseFlow::PathNode sink
152174
where
153175
// find data flow from an `os.OpenFile` call to an `os.File.Close` call
154176
// where the handle is writable
155177
UnhandledFileCloseFlow::flowPath(source, sink) and
156-
isWritableFileHandle(source.getNode(), openCall) and
157-
// get the `CallNode` corresponding to the sink
158-
isCloseSink(sink.getNode(), closeCall)
178+
isWritableFileHandle(source.getNode(), openCall)
159179
select sink, source, sink,
160180
"File handle may be writable as a result of data flow from a $@ and closing it may result in data loss upon failure, which is not handled explicitly.",
161181
openCall, openCall.toString()

0 commit comments

Comments
 (0)