skip to content
Back to GitHub.com
Home Bounties Research Advisories Get Involved Events

Model answers - GitHub Security Lab CTF: Go and don't return

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-golang-ctf-2021", "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-golang-ctf-2021", "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).getStmt() = 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-golang-ctf-2021", "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-golang-ctf-2021", "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