Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cli/cmd/command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type AnalyzerBuilder struct {
semgrepCompatibility bool
partialFingerprints bool
ifdsAnalysisTimeout int64
iterationLimit int64
severities []string
ruleSetPaths []string
ruleLoadTracePath string
Expand Down Expand Up @@ -116,6 +117,11 @@ func (a *AnalyzerBuilder) SetIfdsAnalysisTimeout(timeout int64) *AnalyzerBuilder
return a
}

func (a *AnalyzerBuilder) SetIterationLimit(limit int64) *AnalyzerBuilder {
a.iterationLimit = limit
return a
}

func (a *AnalyzerBuilder) AddSeverity(severity string) *AnalyzerBuilder {
a.severities = append(a.severities, severity)
return a
Expand Down Expand Up @@ -237,6 +243,10 @@ func (a *AnalyzerBuilder) BuildNativeCommand() []string {
flags = append(flags, fmt.Sprintf("--ifds-analysis-timeout=%d", a.ifdsAnalysisTimeout))
}

if a.iterationLimit > 0 {
flags = append(flags, fmt.Sprintf("--iteration-limit=%d", a.iterationLimit))
}

for _, ruleSetPath := range a.ruleSetPaths {
flags = append(flags, "--semgrep-rule-set", ruleSetPath)
}
Expand Down
9 changes: 8 additions & 1 deletion cli/cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type ScanConfig struct {
PassthroughApproximations []string
DataflowApproximations []string
TrackExternalMethods bool
IterationLimit int

DebugFactReachabilitySarif bool
DebugRunAnalysisOnSelectedEntryPoints string
Expand Down Expand Up @@ -146,6 +147,8 @@ func addScanFlags(cmd *cobra.Command) {
cmd.Flags().StringArrayVar(&scanFlags.DataflowApproximations, "dataflow-approximations", nil, "Dataflow approximation class directory or Java source directory (repeatable)")

cmd.Flags().BoolVar(&scanFlags.TrackExternalMethods, "track-external-methods", false, "Write external-method coverage files next to the SARIF report")

cmd.Flags().IntVar(&scanFlags.IterationLimit, "iteration-limit", 0, "Cap the iterative-deepening fact depth at N field accessors and drop facts over it (0 = unlimited)")
}

// currentScanBuilder returns a builder pre-populated with the user's current scan flags.
Expand All @@ -158,7 +161,8 @@ func currentScanBuilder(cfg ScanConfig, sourcePath string) *utils.OpentaintComma
WithRuleID(cfg.RuleID).
WithPassthroughApproximations(cfg.PassthroughApproximations).
WithDataflowApproximations(cfg.DataflowApproximations).
WithTrackExternalMethods(cfg.TrackExternalMethods)
WithTrackExternalMethods(cfg.TrackExternalMethods).
WithIterationLimit(cfg.IterationLimit)
if !isDefaultSeverity(cfg.Severity) {
b.WithSeverity(cfg.Severity)
}
Expand Down Expand Up @@ -388,6 +392,9 @@ func runScan(cmd *cobra.Command, cfg ScanConfig) {
if cfg.TrackExternalMethods {
nativeBuilder.SetTrackExternalMethods(true)
}
if cfg.IterationLimit > 0 {
nativeBuilder.SetIterationLimit(int64(cfg.IterationLimit))
}
if cfg.DebugFactReachabilitySarif {
nativeBuilder.EnableDebugFactReachabilitySarif()
}
Expand Down
7 changes: 7 additions & 0 deletions cli/internal/utils/opentaint_command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ func (cb *OpentaintCommandBuilder) WithTrackExternalMethods(enabled bool) *Opent
return cb
}

func (cb *OpentaintCommandBuilder) WithIterationLimit(limit int) *OpentaintCommandBuilder {
if limit > 0 {
cb.flags["iteration-limit"] = strconv.Itoa(limit)
}
return cb
}

// WithPartialFingerprint adds repeatable --partial-fingerprint filters.
func (cb *OpentaintCommandBuilder) WithPartialFingerprint(fingerprints []string) *OpentaintCommandBuilder {
for _, f := range fingerprints {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ object ValueAccessor : Accessor(), AbstractionAlwaysUnrollNextAccessor {
override val accessorClassId: Int = 6
}

fun Accessor.isFieldDepthAccessor(): Boolean =
this is FieldAccessor || this is ElementAccessor || this is AnyAccessor

inline fun <T : Any> ApManager.tryAnyAccessorOrNull(accessor: Accessor, body: () -> T?): T? {
if (!AnyAccessor.containsAccessor(accessor)) return null
if (!anyAccessorUnrollStrategy.unrollAccessor(accessor)) return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class NormalMethodAnalyzer(
override val methodEntryPoint: MethodEntryPoint,
private val taintRulesStatsSamplingPeriod: Int?,
emptyContextAnalyzer: MethodAnalyzer?,
private val iterationLimit: Int?,
) : MethodAnalyzer {
private val apManager: ApManager get() = runner.apManager
private val analysisManager get() = runner.analysisManager
Expand Down Expand Up @@ -201,12 +202,13 @@ class NormalMethodAnalyzer(
private var summaryEdgesHandled: Long = 0
private val traceResolverStats = TraceResolverStats()

private var factDepthLimit = INITIAL_ALLOWED_FACT_DEPTH
private var factDepthLimit = capAtIterationLimit(INITIAL_ALLOWED_FACT_DEPTH)
private var delayedF2FInitialEdges = EdgeCollection.EdgeList(apManager, methodEntryPoint)
private var delayedF2FSummaries = EdgeCollection.EdgeList(apManager, methodEntryPoint)
private var delayedSequentialEdges = EdgeCollection.EdgeList(apManager, methodEntryPoint)

override val containsDelayedEdges: Boolean
get() = !delayedF2FInitialEdges.isEmpty || !delayedF2FSummaries.isEmpty
get() = !delayedF2FInitialEdges.isEmpty || !delayedF2FSummaries.isEmpty || !delayedSequentialEdges.isEmpty

init {
loadSummariesFromRunner()
Expand Down Expand Up @@ -553,16 +555,8 @@ class NormalMethodAnalyzer(
}

private fun addInitialF2FEdge(edge: FactToFact) {
if (delayInitialEdge(edge)) return
addSequentialEdge(edge)
}

private fun delayInitialEdge(edge: FactToFact): Boolean {
if (!edgeExceedLimit(edge)) return false

registerDelayed()
delayedF2FInitialEdges.add(edge)
return true
val admittedEdge = admitEdgeWithinLimit(edge) { delayedF2FInitialEdges.add(it) } ?: return
addSequentialEdge(admittedEdge)
}

private fun registerDelayed() {
Expand All @@ -571,31 +565,77 @@ class NormalMethodAnalyzer(
}

override fun updateFactDepthLimit(newLimit: Int) {
factDepthLimit = newLimit
factDepthLimit = capAtIterationLimit(newLimit)

val currentDelayedInitialF2F = delayedF2FInitialEdges
delayedF2FInitialEdges = EdgeCollection.EdgeList(apManager, methodEntryPoint)

val currentDelayedSummaries = delayedF2FSummaries
delayedF2FSummaries = EdgeCollection.EdgeList(apManager, methodEntryPoint)

val currentDelayedSequentialEdges = delayedSequentialEdges
delayedSequentialEdges = EdgeCollection.EdgeList(apManager, methodEntryPoint)

currentDelayedInitialF2F.toList().forEach {
addInitialF2FEdge(it as FactToFact)
}

currentDelayedSequentialEdges.toList().forEach {
addSequentialEdge(it)
}

currentDelayedSummaries.toList().forEach {
newSummaryEdge(it)
}
}

private fun edgeExceedLimit(edge: FactToFact): Boolean {
if (edge.initialFactAp.depth > factDepthLimit) return true
if (edge.factAp.depth > factDepthLimit + 2) return true
return false
private inline fun admitEdgeWithinLimit(edge: Edge, delay: (Edge) -> Unit): Edge? {
val finalFact = when (edge) {
is ZeroToZero -> return edge
is ZeroToFact -> edge.factAp
is FactToFact -> edge.factAp.takeUnless { edge.initialFactAp.exceedsFieldLimit(factDepthLimit) }
is NDFactToFact -> edge.factAp.takeUnless { edge.initialFacts.any { it.exceedsFieldLimit(factDepthLimit) } }
}

if (finalFact == null) {
delayEdgeOverLimit(edge, delay)
return null
}

if (!finalFact.exceedsFieldLimit(factDepthLimit)) return edge

val (within, rest) = finalFact.splitFieldLimit(factDepthLimit)
if (rest != null) {
delayEdgeOverLimit(edge.replaceFinalFact(rest), delay)
}

return within?.let { edge.replaceFinalFact(it) }
}

private inline fun delayEdgeOverLimit(edge: Edge, delay: (Edge) -> Unit) {
if (iterationLimitReached()) return

registerDelayed()
delay(edge)
}

private fun Edge.replaceFinalFact(factAp: FinalFactAp): Edge = when (this) {
is ZeroToZero -> this
is ZeroToFact -> ZeroToFact(methodEntryPoint, statement, factAp)
is FactToFact -> FactToFact(methodEntryPoint, initialFactAp, statement, factAp)
is NDFactToFact -> NDFactToFact(methodEntryPoint, initialFacts, statement, factAp)
}

private fun capAtIterationLimit(limit: Int): Int =
if (iterationLimit == null) limit else minOf(limit, iterationLimit)

private fun iterationLimitReached(): Boolean =
iterationLimit != null && factDepthLimit >= iterationLimit

private fun addSequentialEdge(edge: Edge) {
edges.add(edge).forEach { newEdge ->
val admittedEdge = admitEdgeWithinLimit(edge) { delayedSequentialEdges.add(it) } ?: return

edges.add(admittedEdge).forEach { newEdge ->
enqueueNewEdge(newEdge)
}
}
Expand Down Expand Up @@ -692,20 +732,11 @@ class NormalMethodAnalyzer(
if (edge is ZeroToZero) {
runner.addNewSummaryEdges(methodEntryPoint, listOf(edge))
} else {
if (delaySummaryEdge(edge)) return
addNewSummaryEdge(edge)
val admittedEdge = admitEdgeWithinLimit(edge) { delayedF2FSummaries.add(it) } ?: return
addNewSummaryEdge(admittedEdge)
}
}

private fun delaySummaryEdge(edge: Edge): Boolean {
if (edge !is FactToFact) return false
if (!edgeExceedLimit(edge)) return false

registerDelayed()
delayedF2FSummaries.add(edge)
return true
}

private fun addNewSummaryEdge(edge: Edge) {
pendingSummaryEdges.add(edge)

Expand Down Expand Up @@ -1324,7 +1355,7 @@ class NormalMethodAnalyzer(
}

override fun methodTraceResolver(): MethodTraceResolver =
MethodTraceResolver(runner, traceResolverStats, analysisContext, edges, methodInstGraph)
MethodTraceResolver(runner, traceResolverStats, analysisContext, edges, methodInstGraph, factDepthLimit)

override fun resolveIntraProceduralForwardFullTrace(
statement: CommonInst,
Expand Down Expand Up @@ -1367,7 +1398,7 @@ class NormalMethodAnalyzer(

override fun resetApManager(apManager: ApManager) {
zeroInitialFactProcessed = false
factDepthLimit = INITIAL_ALLOWED_FACT_DEPTH
factDepthLimit = capAtIterationLimit(INITIAL_ALLOWED_FACT_DEPTH)
edges = MethodAnalyzerEdges(apManager, methodEntryPoint, analysisManager)

resetEdgeProcessingStorage(apManager)
Expand All @@ -1384,10 +1415,11 @@ class NormalMethodAnalyzer(

initialFacts = apManager.initialFactAbstraction(methodEntryPoint.statement)
delayedF2FInitialEdges = EdgeCollection.EdgeList(apManager, methodEntryPoint)
delayedSequentialEdges = EdgeCollection.EdgeList(apManager, methodEntryPoint)
}

companion object {
const val INITIAL_ALLOWED_FACT_DEPTH = 3
const val INITIAL_ALLOWED_FACT_DEPTH = 1
const val DEBUG_ANALYSIS_TIME = false
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import org.opentaint.dataflow.util.object2IntMap

class MethodAnalyzerStorage(
private val languageManager: LanguageManager,
private val taintRulesStatsSamplingPeriod: Int?
private val taintRulesStatsSamplingPeriod: Int?,
private val iterationLimit: Int?,
) {
private val entryPoints = object2IntMap<MethodEntryPoint>()
private val analyzers = arrayListOf<MethodAnalyzer?>()
Expand All @@ -18,7 +19,7 @@ class MethodAnalyzerStorage(

val analyzer = if (!languageManager.isEmpty(methodEntryPoint.method)) {
val emptyContextAnalyzer = getOrCreateMethodAnalyzerWithEmptyContext(runner, methodEntryPoint)
NormalMethodAnalyzer(runner, methodEntryPoint, taintRulesStatsSamplingPeriod, emptyContextAnalyzer)
NormalMethodAnalyzer(runner, methodEntryPoint, taintRulesStatsSamplingPeriod, emptyContextAnalyzer, iterationLimit)
.let { if (NormalMethodAnalyzer.DEBUG_ANALYSIS_TIME) TimedMethodAnalyzer(it) else it }
} else {
val methodExitPoints = runner.graph.methodGraph(methodEntryPoint.method).exitPoints().toList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class TaintAnalysisUnitRunner(
override val graph: ApplicationGraph<CommonMethod, CommonInst>,
private val unitResolver: UnitResolver<CommonMethod>,
private val summarySerializationContext: SummarySerializationContext,
private val taintRulesStatsSamplingPeriod: Int?
private val taintRulesStatsSamplingPeriod: Int?,
private val iterationLimit: Int?,
) : AnalysisRunner, SummaryEdgeSubscriptionManager.SummaryEdgeProcessingCtx {
override val apManager: ApManager
get() = manager.apManager
Expand Down Expand Up @@ -331,7 +332,7 @@ class TaintAnalysisUnitRunner(

private fun methodAnalyzers(method: CommonMethod): MethodAnalyzerStorage =
methodAnalyzers.computeIfAbsent(method) {
MethodAnalyzerStorage(analysisManager, taintRulesStatsSamplingPeriod).also {
MethodAnalyzerStorage(analysisManager, taintRulesStatsSamplingPeriod, iterationLimit).also {
analyzers.add(it)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class TaintAnalysisUnitRunnerManager(
override val unitResolver: UnitResolver<CommonMethod>,
private val summarySerializationContext: SummarySerializationContext,
private val taintRulesStatsSamplingPeriod: Int?,
private val iterationLimit: Int?,
): AnalysisUnitRunnerManager, AutoCloseable {
enum class Status {
OK, EXCEPTION, TIMEOUT, OOM
Expand Down Expand Up @@ -469,7 +470,8 @@ class TaintAnalysisUnitRunnerManager(
graph = graph,
unitResolver = unitResolver,
summarySerializationContext = summarySerializationContext,
taintRulesStatsSamplingPeriod = taintRulesStatsSamplingPeriod
taintRulesStatsSamplingPeriod = taintRulesStatsSamplingPeriod,
iterationLimit = iterationLimit,
)

startRunner(unit, runner)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ interface FactAp: AccessorList {

val size: Int
val depth: Int

fun exceedsFieldLimit(limit: Int): Boolean
}

interface InitialFactAp : FactAp, ReadableAccessorList<InitialFactAp> {
Expand Down Expand Up @@ -76,4 +78,8 @@ interface FinalFactAp : FactAp, ReadableAccessorList<FinalFactAp> {
delta(other).any { it.isEmpty }

fun clearAllAccessorOccurrences(accessor: Accessor, keepStartAccessor: Boolean): FinalFactAp?

data class FieldLimitSplit(val within: FinalFactAp?, val rest: FinalFactAp?)

fun splitFieldLimit(limit: Int): FieldLimitSplit
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.opentaint.dataflow.ap.ifds.access.DeepAccessorExclusion
import org.opentaint.dataflow.ap.ifds.access.forExclusions
import org.opentaint.dataflow.ap.ifds.FactTypeChecker.CompatibilityFilterResult
import org.opentaint.dataflow.ap.ifds.access.util.AccessorIdx
import org.opentaint.dataflow.ap.ifds.access.util.AccessorInterner.Companion.isFieldDepthAccessor
import org.opentaint.dataflow.ap.ifds.serialization.SummarySerializationContext
import org.opentaint.dataflow.ap.ifds.tryAnyAccessorOrNull
import org.opentaint.dataflow.util.PersistentArrayBuilder
Expand Down Expand Up @@ -142,6 +143,17 @@ class AccessGraph(
return hash
}

fun exceedsFieldLimit(limit: Int): Boolean = fieldDepthAccessorCount() > limit

private fun fieldDepthAccessorCount(): Int {
var count = 0
val accessors = edges.keys.iterator()
while (accessors.hasNext()) {
if (accessors.nextInt().isFieldDepthAccessor()) count++
}
return count
}

override fun hashCode(): Int = hash

override fun equals(other: Any?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ data class AccessGraphFinalFactAp(
override val size: Int get() = access.size
override val depth: Int get() = size

override fun exceedsFieldLimit(limit: Int): Boolean = access.exceedsFieldLimit(limit)

// todo: no precise split for the access graph yet
override fun splitFieldLimit(limit: Int): FinalFactAp.FieldLimitSplit =
if (!exceedsFieldLimit(limit)) FinalFactAp.FieldLimitSplit(within = this, rest = null)
else FinalFactAp.FieldLimitSplit(within = null, rest = this)

override fun rebase(newBase: AccessPathBase): FinalFactAp =
AccessGraphFinalFactAp(newBase, access, exclusions)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ data class AccessGraphInitialFactAp(
override val size: Int get() = access.size
override val depth: Int get() = size

override fun exceedsFieldLimit(limit: Int): Boolean = access.exceedsFieldLimit(limit)

override fun rebase(newBase: AccessPathBase): InitialFactAp =
AccessGraphInitialFactAp(newBase, access, exclusions)

Expand Down
Loading
Loading