CTF: Go and don’t return
Model answers
This page includes a reference solution written by CTF reviewers during the contest. There are many correct ways to solve this challenge; this is one approach.
Part 1
Step 1.1
import go
from Ident i
where i.getName() = "ErrNone"
select i
Step 1.2
import go
from EqualityTestExpr e
where e.getAnOperand().(Ident).getName() = "ErrNone"
select e
Step 1.3
import go
from IfStmt s
where s.getCond().(EqualityTestExpr).getAnOperand().(Ident).getName() = "ErrNone"
select s
Step 1.4
import go
from ReturnStmt r
select r
Step 1.5
import go
from IfStmt i
where not i.getThen().getAStmt() instanceof ReturnStmt
select i
Step 1.6
import go
from IfStmt i
where
i.getCond().(EqualityTestExpr).getAnOperand().(Ident).getName() = "ErrNone"
and not i.getThen().getAStmt() instanceof ReturnStmt
select i
Part 2
Step 2.1
import go
class AuthTestConfig extends DataFlow::Configuration {
AuthTestConfig() { this = "auth-test-config" }
override predicate isSource(DataFlow::Node source) {
source = any(DataFlow::CallNode cn |
cn.getTarget().hasQualifiedName("github.com/minio/minio/cmd", "isReqAuthenticated")
).getResult()
}
override predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::EqualityTestNode n).getAnOperand()
}
}
from AuthTestConfig config, DataFlow::Node sink, DataFlow::EqualityTestNode comparison
where config.hasFlow(_, sink) and comparison.getAnOperand() = sink
select comparison
Step 2.2
import go
class AuthTestConfig extends DataFlow::Configuration {
AuthTestConfig() { this = "auth-test-config" }
override predicate isSource(DataFlow::Node source) {
source = any(DataFlow::CallNode cn |
cn.getTarget().hasQualifiedName("github.com/minio/minio/cmd", "isReqAuthenticated")
).getResult()
}
override predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::EqualityTestNode n).getAnOperand()
}
}
EqualityTestExpr getAnAuthCheck() {
exists(AuthTestConfig config, DataFlow::Node sink, DataFlow::EqualityTestNode comparison |
config.hasFlow(_, sink) and comparison.getAnOperand() = sink |
result = comparison.asExpr()
)
}
from IfStmt i
where
i.getCond() = getAnAuthCheck() and
i.getCond().(EqualityTestExpr).getAnOperand().(Ident).getName() = "ErrNone"
and not i.getThen().getAStmt() instanceof ReturnStmt
select i
Part 3
Step 3.1
import go
class AuthTestConfig extends DataFlow::Configuration {
AuthTestConfig() { this = "auth-test-config" }
override predicate isSource(DataFlow::Node source) {
source = any(DataFlow::CallNode cn |
cn.getTarget().hasQualifiedName("github.com/minio/minio/cmd", "isReqAuthenticated") or
// Note new source function:
cn.getTarget().hasQualifiedName("github.com/github/codeql-ctf-go-return", "errorSource")
).getResult()
}
override predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::EqualityTestNode n).getAnOperand()
}
}
EqualityTestExpr getADirectAuthCheck(boolean polarity) {
exists(AuthTestConfig config, DataFlow::Node sink, DataFlow::EqualityTestNode comparison |
config.hasFlow(_, sink) and comparison.getAnOperand() = sink |
result = comparison.asExpr() and
polarity = result.getPolarity()
)
}
/**
* Given `ifStmt`'s condition compares some `x` against `ErrNone` with `polarity` (true means checking
* equality; false checking inequality), return the block reached when `x != ErrNone`.
*/
BlockStmt getErrorBranch(IfStmt ifStmt, boolean polarity) {
polarity = [true, false] and
if polarity = true then result = ifStmt.getElse() else result = ifStmt.getThen()
}
from IfStmt i, boolean testPolarity
where
i.getCond() = getADirectAuthCheck(testPolarity) and
i.getCond().(EqualityTestExpr).getAnOperand().(Ident).getName() = "ErrNone"
and not getErrorBranch(i, testPolarity).getAStmt() instanceof ReturnStmt
select i
Step 3.2
import go
class AuthTestConfig extends DataFlow::Configuration {
AuthTestConfig() { this = "auth-test-config" }
override predicate isSource(DataFlow::Node source) {
source = any(DataFlow::CallNode cn |
cn.getTarget().hasQualifiedName("github.com/minio/minio/cmd", "isReqAuthenticated") or
// Note new source function:
cn.getTarget().hasQualifiedName("github.com/github/codeql-ctf-go-return", "errorSource")
).getResult()
}
override predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::EqualityTestNode n).getAnOperand()
}
}
EqualityTestExpr getAnAuthCheck() {
exists(AuthTestConfig config, DataFlow::Node sink, DataFlow::EqualityTestNode comparison |
config.hasFlow(_, sink) and comparison.getAnOperand() = sink |
result = comparison.asExpr()
)
}
ReturnStmt getAReturnStatementInBlock(BlockStmt b) {
result = b.getAChild*()
}
predicate mustReachReturnInBlock(ControlFlow::Node node, BlockStmt b) {
node.(IR::ReturnInstruction).getReturnStmt() = getAReturnStatementInBlock(b) or
forex(ControlFlow::Node succ | succ = node.getASuccessor() | mustReachReturnInBlock(succ, b))
}
from IfStmt i, ControlFlow::ConditionGuardNode ifSucc
where
i.getCond() = getAnAuthCheck() and
i.getCond().(EqualityTestExpr).getAnOperand().(Ident).getName() = "ErrNone" and
ifSucc.ensures(DataFlow::exprNode(i.getCond()), true) and
not mustReachReturnInBlock(ifSucc, i.getThen())
select i
Step 3.3
import go
class AuthTestConfig extends DataFlow::Configuration {
AuthTestConfig() { this = "auth-test-config" }
override predicate isSource(DataFlow::Node source) {
source = any(DataFlow::CallNode cn |
cn.getTarget().hasQualifiedName("github.com/minio/minio/cmd", "isReqAuthenticated") or
// Note new source function:
cn.getTarget().hasQualifiedName("github.com/github/codeql-ctf-go-return", "errorSource")
).getResult()
}
override predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::EqualityTestNode n).getAnOperand()
}
}
EqualityTestExpr getADirectAuthCheck(boolean polarity) {
exists(AuthTestConfig config, DataFlow::Node sink, DataFlow::EqualityTestNode comparison |
config.hasFlow(_, sink) and comparison.getAnOperand() = sink |
result = comparison.asExpr() and
result.getAnOperand().(Ident).getName() = "ErrNone" and
polarity = result.getPolarity()
)
}
CallExpr getACheckCall(boolean polarity, FuncDecl target, Expr innerCheck) {
innerCheck = getAnAuthCheck(polarity) and
target = result.getTarget().getFuncDecl() and
forex(DataFlow::ResultNode rn | rn.getRoot() = target | rn.asExpr() = innerCheck)
}
Expr getAnAuthCheck(boolean polarity) {
result = getADirectAuthCheck(polarity) or
result = getACheckCall(polarity, _, _)
}
/**
* Given `ifStmt`'s condition compares some `x` against `ErrNone` with `polarity` (true means checking
* equality; false checking inequality), return the block reached when `x != ErrNone`.
*/
BlockStmt getErrorBranch(IfStmt ifStmt, boolean polarity) {
polarity = [true, false] and
if polarity = true then result = ifStmt.getElse() else result = ifStmt.getThen()
}
from IfStmt i, boolean testPolarity
where
i.getCond() = getAnAuthCheck(testPolarity)
and not getErrorBranch(i, testPolarity).getAStmt() instanceof ReturnStmt
select i
Step 3.4
import go
class AuthTestConfig extends DataFlow::Configuration {
AuthTestConfig() { this = "auth-test-config" }
override predicate isSource(DataFlow::Node source) {
source = any(DataFlow::CallNode cn |
cn.getTarget().hasQualifiedName("github.com/minio/minio/cmd", "isReqAuthenticated") or
// Note new source function:
cn.getTarget().hasQualifiedName("github.com/github/codeql-ctf-go-return", "errorSource")
).getResult()
}
override predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::EqualityTestNode n).getAnOperand()
}
}
EqualityTestExpr getADirectAuthCheck(boolean polarity) {
exists(AuthTestConfig config, DataFlow::Node sink, DataFlow::EqualityTestNode comparison |
config.hasFlow(_, sink) and comparison.getAnOperand() = sink |
result = comparison.asExpr() and
polarity = result.getPolarity()
)
}
Expr getAnAuthCheck(Boolean noError, EqualityTestExpr test) {
result = getADirectAuthCheck(noError) and test = result
or
result.(ParenExpr).getExpr() = getAnAuthCheck(noError, test)
or
result.(NotExpr).getOperand() = getAnAuthCheck(noError.booleanNot(), test)
or
result.(LandExpr).getRightOperand() = getAnAuthCheck(noError, test)
or
result.(LandExpr).getLeftOperand() = getAnAuthCheck(true, test) and noError = true
or
result.(LandExpr).getLeftOperand() = getAnAuthCheck(false, test) and noError = [true, false]
or
result.(LorExpr).getRightOperand() = getAnAuthCheck(noError, test)
or
result.(LorExpr).getLeftOperand() = getAnAuthCheck(false, test) and noError = false
or
result.(LorExpr).getLeftOperand() = getAnAuthCheck(true, test) and noError = [true, false]
}
/**
* Given `ifStmt`'s condition compares some `x` against `ErrNone` with `polarity` (true means checking
* equality; false checking inequality), return a block reached when `x != ErrNone`.
*/
BlockStmt getErrorBranch(IfStmt ifStmt, boolean polarity) {
polarity = [true, false] and
if polarity = true then result = ifStmt.getElse() else result = ifStmt.getThen()
}
from IfStmt i, EqualityTestExpr test
where
test.getAnOperand().(Ident).getName() = "ErrNone"
and not forall(boolean testPolarity |
i.getCond() = getAnAuthCheck(testPolarity, test) |
exists(Stmt s | s = getErrorBranch(i, testPolarity).getAStmt() | s instanceof ReturnStmt))
and i.getFile().getBaseName() = "logicalOperators.go"
select i
Step 3.5
import go
class AuthTestConfig extends DataFlow::Configuration {
AuthTestConfig() { this = "auth-test-config" }
override predicate isSource(DataFlow::Node source) {
source = any(DataFlow::CallNode cn |
cn.getTarget().hasQualifiedName("github.com/minio/minio/cmd", "isReqAuthenticated") or
// Note new source function:
cn.getTarget().hasQualifiedName("github.com/github/codeql-ctf-go-return", "errorSource")
).getResult()
}
override predicate isSink(DataFlow::Node sink) {
sink = any(DataFlow::EqualityTestNode n).getAnOperand()
}
}
predicate returnsNil(FuncDecl f) {
forex(DataFlow::ResultNode r | r.getRoot() = f | r = Builtin::nil().getARead())
}
predicate isNil(Expr e) {
e = any(CallExpr c | returnsNil(c.getTarget().getFuncDecl())) or
e = Builtin::nil().getAReference()
}
EqualityTestExpr getADirectAuthCheck(boolean polarity) {
exists(AuthTestConfig config, DataFlow::Node sink, DataFlow::EqualityTestNode comparison |
config.hasFlow(_, sink) and comparison.getAnOperand() = sink |
result = comparison.asExpr() and
polarity = result.getPolarity()
)
}
/**
* Given `ifStmt`'s condition compares some `x` against `ErrNone` with `polarity` (true means checking
* equality; false checking inequality), return the block reached when `x != ErrNone`.
*/
BlockStmt getErrorBranch(IfStmt ifStmt, boolean polarity) {
polarity = [true, false] and
if polarity = true then result = ifStmt.getElse() else result = ifStmt.getThen()
}
from IfStmt i, boolean testPolarity, int resultIdx
where
i.getCond() = getADirectAuthCheck(testPolarity) and
i.getCond().(EqualityTestExpr).getAnOperand().(Ident).getName() = "ErrNone" and
i.getEnclosingFunction().getType().getResultType(resultIdx) = Builtin::error().getType() and
not exists(ReturnStmt r |
r = getErrorBranch(i, testPolarity).getAStmt() |
not isNil(r.getExpr(resultIdx))
)
select i