Skip to content

Commit

Permalink
Merge branch 'main' into gtirb-nondet-ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
l-kent committed Aug 19, 2024
2 parents a321018 + ffd54a9 commit ad3a7d7
Show file tree
Hide file tree
Showing 35 changed files with 3,828 additions and 80 deletions.
6 changes: 4 additions & 2 deletions src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ object Main {
@arg(name = "analysis-results-dot", doc = "Log analysis results in .dot form at specified path.")
analysisResultsDot: Option[String],
@arg(name = "threads", short = 't', doc = "Separates threads into multiple .bpl files with given output filename as prefix (requires --analyse flag)")
threadSplit: Flag
threadSplit: Flag,
@arg(name = "summarise-procedures", doc = "Generates summaries of procedures which are used in pre/post-conditions (requires --analyse flag)")
summariseProcedures: Flag
)

def main(args: Array[String]): Unit = {
Expand Down Expand Up @@ -80,7 +82,7 @@ object Main {
val q = BASILConfig(
loading = ILLoadingConfig(conf.inputFileName, conf.relfFileName, conf.specFileName, conf.dumpIL, conf.mainProcedureName, conf.procedureDepth),
runInterpret = conf.interpret.value,
staticAnalysis = if conf.analyse.value then Some(StaticAnalysisConfig(conf.dumpIL, conf.analysisResults, conf.analysisResultsDot, conf.threadSplit.value)) else None,
staticAnalysis = if conf.analyse.value then Some(StaticAnalysisConfig(conf.dumpIL, conf.analysisResults, conf.analysisResultsDot, conf.threadSplit.value, conf.summariseProcedures.value)) else None,
boogieTranslation = BoogieGeneratorConfig(if conf.lambdaStores.value then BoogieMemoryAccessMode.LambdaStoreSelect else BoogieMemoryAccessMode.SuccessiveStoreSelect,
true, rely, conf.threadSplit.value),
outputPrefix = conf.outFileName,
Expand Down
38 changes: 30 additions & 8 deletions src/main/scala/analysis/EdgeFunctionLattice.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,38 @@ class EdgeFunctionLattice[T, L <: Lattice[T]](val valuelattice: L) extends Latti
def composeWith(e: EdgeFunction[T]): EdgeFunction[T] = this

def joinWith(e: EdgeFunction[T]): EdgeFunction[T] =
if (e == this || c == valuelattice.top) this
else if (c == valuelattice.bottom) e
else
e match {
case IdEdge() => ??? // never reached with the currently implemented analyses
case ConstEdge(ec) => ConstEdge(valuelattice.lub(c, ec))
case _ => e.joinWith(this)
}
e match {
case IdEdge() => JoinEdge(c)
case ConstEdge(ec) => ConstEdge(valuelattice.lub(c, ec))
case _ => e.joinWith(this)
}

override def toString = s"ConstEdge($c)"
}

/**
* Edge that is the join of an IdEdge and ConstEdge.
* `\l . lub(l, c)` as a lambda expression.
*/
case class JoinEdge(c: T) extends EdgeFunction[T] {

def apply(x: T): T = valuelattice.lub(x, c)

def composeWith(e: EdgeFunction[T]): EdgeFunction[T] =
e match {
case IdEdge() => this
case ConstEdge(c) => ConstEdge(c)
case JoinEdge(d) => JoinEdge(valuelattice.lub(c, d))
}

def joinWith(e: EdgeFunction[T]): EdgeFunction[T] =
e match {
case IdEdge() => this
case ConstEdge(d) => JoinEdge(valuelattice.lub(c, d))
case JoinEdge(d) => JoinEdge(valuelattice.lub(c, d))
}

override def toString = s"JoinEdge($c)"
}

}
187 changes: 187 additions & 0 deletions src/main/scala/analysis/SummaryGenerator.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package analysis

import analysis.*
import ir.*
import boogie.*
import util.Logger
import specification.SpecGlobal

/**
* A temporary copy of RNA analysis which works on Taintables.
*/
import analysis.solvers.SimpleWorklistFixpointSolver

private trait RNATaintableAnalysis(
program: Program,
globals: Map[BigInt, String],
constProp: Map[CFGPosition, Map[Variable, FlatElement[BitVecLiteral]]],
) {
val lattice: MapLattice[CFGPosition, Set[Taintable], PowersetLattice[Taintable]] = MapLattice(PowersetLattice())

val domain: Set[CFGPosition] = Set.empty ++ program

private val stackPointer = Register("R31", 64)
private val linkRegister = Register("R30", 64)
private val framePointer = Register("R29", 64)

private val ignoreRegions: Set[Expr] = Set(linkRegister, framePointer, stackPointer)

def eval(cmd: Command, s: Set[Taintable]): Set[Taintable] = {
var m = s
val exprs = cmd match {
case assume: Assume =>
Set(assume.body)
case assert: Assert =>
Set(assert.body)
case memoryAssign: MemoryAssign =>
m = m -- getMemoryVariable(cmd, memoryAssign.mem, memoryAssign.index, memoryAssign.size, constProp, globals)
Set(memoryAssign.index, memoryAssign.value)
case indirectCall: IndirectCall =>
if (ignoreRegions.contains(indirectCall.target)) return m
Set(indirectCall.target)
case assign: Assign =>
m = m - assign.lhs
Set(assign.rhs)
case _ => return m
}

exprs.foldLeft(m) {
(m, expr) => {
val vars = expr.variables.filter(!ignoreRegions.contains(_)).map { v => v: Taintable }
val memvars: Set[Taintable] = expr.loads.flatMap {
l => getMemoryVariable(cmd, l.mem, l.index, l.size, constProp, globals)
}
m.union(vars).union(memvars)
}
}
}

def transfer(n: CFGPosition, s: Set[Taintable]): Set[Taintable] = n match {
case cmd: Command =>
eval(cmd, s)
case _ => s // ignore other kinds of nodes
}
}

private class RNATaintableSolver(
program: Program,
globals: Map[BigInt, String],
constProp: Map[CFGPosition, Map[Variable, FlatElement[BitVecLiteral]]],
) extends RNATaintableAnalysis(program, globals, constProp)
with IRIntraproceduralBackwardDependencies
with Analysis[Map[CFGPosition, Set[Taintable]]]
with SimpleWorklistFixpointSolver[CFGPosition, Set[Taintable], PowersetLattice[Taintable]]

/**
* Generates summaries (requires and ensures clauses) for procedures that necessarily must hold (assuming a correct implementation).
* This helps because the verifier cannot make any assumptions about procedures with no summaries.
*/
class SummaryGenerator(
program: Program,
specGlobals: Set[SpecGlobal],
globals: Map[BigInt, String],
constProp: Map[CFGPosition, Map[Variable, FlatElement[BitVecLiteral]]],
varDepsSummaries: Map[Procedure, Map[Taintable, Set[Taintable]]],
) {
private val rnaResults = RNATaintableSolver(program, globals, constProp).analyze()

// registers R19 to R29 and R31 must be preserved by a procedure call, so we only need summaries for R0-R18 and R30
// TODO support R30? the analysis currently ignores it?
val variables: Set[Taintable] = 0.to(18).map { n =>
Register(s"R$n", 64)
}.toSet ++ specGlobals.map { g =>
analysis.GlobalVariable(dsl.mem, BitVecLiteral(g.address, 64), g.size, g.name)
}

private def toGamma(variable: Taintable): Option[BExpr] = {
variable match {
case variable: Register => Some(variable.toGamma)
case variable: LocalVar => None
case variable: GlobalVariable => Some(variable.toGamma)
//case variable: LocalStackVariable => None
case variable: UnknownMemory => Some(FalseBLiteral)
}
}

private def toExprGamma(variable: Taintable): Option[BExpr] = {
variable match {
case variable: GlobalVariable => Some(BinaryBExpr(BoolOR, variable.toGamma, variable.L))
case _ => toGamma(variable)
}
}

/**
* Get a map of variables to variables which have tainted it in the procedure.
*/
private def getTainters(procedure: Procedure, variables: Set[Taintable]): Map[Taintable, Set[Taintable]] = {
varDepsSummaries.getOrElse(procedure, Map())
}

/**
* Generate requires clauses for a procedure. Currently this does nothing.
*/
def generateRequires(procedure: Procedure): List[BExpr] = List()

/**
* Generate ensures clauses for a procedure. Currently, all generated ensures clauses are of the form (for example)
* ensures Gamma_R2 || (Gamma_R2 == old(Gamma_y)) || (Gamma_R2 == old(Gamma_R1)) || (Gamma_R2 == old(Gamma_R2));
* whenever this can be done soundly.
*/
def generateEnsures(procedure: Procedure): List[BExpr] = {
if procedure.blocks.isEmpty then return List()

// We only need to make ensures clauses about the gammas of modified variables.
val relevantVars = variables.filter { v =>
v match {
case v: Global => procedure.modifies.contains(v)
case _ => true
}
}

// TODO further explanation of this would help
// Use rnaResults to find stack function arguments
val tainters = relevantVars.map {
v => (v, Set())
}.toMap ++ getTainters(procedure, variables ++ rnaResults(procedure.begin) + UnknownMemory()).filter { (variable, taints) =>
relevantVars.contains(variable)
}

tainters.toList.flatMap {
(variable, taints) => {
// If our variable was tainted by memory that we know nothing about, it is sound to assume that we
// know nothing about its gamma in the post state.
if taints.contains(UnknownMemory()) then None
else toGamma(variable).flatMap {
varGamma => {
if taints.isEmpty then {
// If the variable is tainted by nothing, it must have been set to some constant value, meaning
// its gamma is low. Note that if a variable was unchanged, it would be tainted by itself.
Some(varGamma)
} else {
// Else, we must consider the variables which have tainted this variable. We can soundly assert
// that this variable's gamma must be equal to one of its tainter's gammas, or it is set to
// low.
//
// In a correct procedure, we never have L(x) = true and Gamma_x = false, so we can safely
// assume L(x) || Gamma_x == Gamma_x. This means that the gamma of any expression is equal
// to the conjunction of the gammas of the variables and the loads in the expression.
val equations = taints.flatMap { tainter =>
toGamma(tainter).map { tainterGamma =>
BinaryBExpr(BoolEQ, varGamma, Old(tainterGamma))
}
}

if equations.nonEmpty then {
Some(equations.foldLeft(varGamma) { (expr, eq) =>
BinaryBExpr(BoolOR, expr, eq)
})
} else {
None
}
}
}
}
}
}
}
}
Loading

0 comments on commit ad3a7d7

Please sign in to comment.