Skip to content

Commit e3180e5

Browse files
committed
Added Access Control Incompleteness Checker and related abstract domain for taint analysis
1 parent 138f151 commit e3180e5

File tree

4 files changed

+429
-5
lines changed

4 files changed

+429
-5
lines changed

src/main/java/it/unipr/cfg/EVMCFG.java

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ public class EVMCFG extends CFG {
5050
public Set<Statement> externalData;
5151
public Set<Statement> jumpI;
5252
public Set<Statement> successfullyTerminationStatements;
53+
private Set<Statement> calldataloads;
54+
private Set<Statement> calldatacopies;
55+
private Set<Statement> calldatasizes;
56+
private Set<Statement> callers;
57+
private Set<Statement> origins;
58+
private Set<Statement> callvalues;
5359

5460
/**
5561
* Builds a EVMCFG starting from its description.
@@ -72,6 +78,11 @@ public void computeHotspotNodes() {
7278
this.jumpNodes = new HashSet<Statement>();
7379
this.pushedJumps = new HashSet<Statement>();
7480
this.sstores = new HashSet<Statement>();
81+
this.calldataloads = new HashSet<Statement>();
82+
this.calldatacopies = new HashSet<Statement>();
83+
this.calldatasizes = new HashSet<Statement>();
84+
this.callers = new HashSet<Statement>();
85+
this.origins = new HashSet<Statement>();
7586

7687
NodeList<CFG, Statement, Edge> cfgNodeList = this.getNodeList();
7788

@@ -158,6 +169,126 @@ public Set<Statement> getAllSstore() {
158169
return this.sstores;
159170
}
160171

172+
/**
173+
* Returns all CALLDATALOAD statements contained in the control-flow graph.
174+
*
175+
* @return a set with every CALLDATALOAD statement in this CFG
176+
*/
177+
public Set<Statement> getAllCalldataload() {
178+
if (this.calldataloads == null) {
179+
NodeList<CFG, Statement, Edge> cfgNodeList = this.getNodeList();
180+
Set<Statement> calldataloads = new HashSet<>();
181+
182+
for (Statement statement : cfgNodeList.getNodes())
183+
if (statement instanceof Calldataload)
184+
calldataloads.add(statement);
185+
186+
return this.calldataloads = calldataloads;
187+
}
188+
189+
return this.calldataloads;
190+
}
191+
192+
/**
193+
* Returns all CALLDATACOPY statements contained in the control-flow graph.
194+
*
195+
* @return a set with every CALLDATACOPY statement in this CFG
196+
*/
197+
public Set<Statement> getAllCalldatacopy() {
198+
if (this.calldatacopies == null) {
199+
NodeList<CFG, Statement, Edge> cfgNodeList = this.getNodeList();
200+
Set<Statement> calldatacopies = new HashSet<>();
201+
202+
for (Statement statement : cfgNodeList.getNodes())
203+
if (statement instanceof Calldatacopy)
204+
calldatacopies.add(statement);
205+
206+
return this.calldatacopies = calldatacopies;
207+
}
208+
209+
return this.calldatacopies;
210+
}
211+
212+
/**
213+
* Returns all CALLDATASIZE statements contained in the control-flow graph.
214+
*
215+
* @return a set with every CALLDATASIZE statement in this CFG
216+
*/
217+
public Set<Statement> getAllCalldatasize() {
218+
if (this.calldatasizes == null) {
219+
NodeList<CFG, Statement, Edge> cfgNodeList = this.getNodeList();
220+
Set<Statement> calldatasizes = new HashSet<>();
221+
222+
for (Statement statement : cfgNodeList.getNodes())
223+
if (statement instanceof Calldatasize)
224+
calldatasizes.add(statement);
225+
226+
return this.calldatasizes = calldatasizes;
227+
}
228+
229+
return this.calldatasizes;
230+
}
231+
232+
/**
233+
* Returns all CALLER statements contained in the control-flow graph.
234+
*
235+
* @return a set with every CALLER statement in this CFG
236+
*/
237+
public Set<Statement> getAllCaller() {
238+
if (this.callers == null) {
239+
NodeList<CFG, Statement, Edge> cfgNodeList = this.getNodeList();
240+
Set<Statement> callers = new HashSet<>();
241+
242+
for (Statement statement : cfgNodeList.getNodes())
243+
if (statement instanceof Caller)
244+
callers.add(statement);
245+
246+
return this.callers = callers;
247+
}
248+
249+
return this.callers;
250+
}
251+
252+
/**
253+
* Returns all ORIGIN statements contained in the control-flow graph.
254+
*
255+
* @return a set with every ORIGIN statement in this CFG
256+
*/
257+
public Set<Statement> getAllOrigin() {
258+
if (this.origins == null) {
259+
NodeList<CFG, Statement, Edge> cfgNodeList = this.getNodeList();
260+
Set<Statement> origins = new HashSet<>();
261+
262+
for (Statement statement : cfgNodeList.getNodes())
263+
if (statement instanceof Origin)
264+
origins.add(statement);
265+
266+
return this.origins = origins;
267+
}
268+
269+
return this.origins;
270+
}
271+
272+
/**
273+
* Returns all CALLVALUE statements contained in the control-flow graph.
274+
*
275+
* @return a set with every CALLVALUE statement in this CFG
276+
*/
277+
public Set<Statement> getAllCallvalue() {
278+
if (this.callvalues == null) {
279+
NodeList<CFG, Statement, Edge> cfgNodeList = this.getNodeList();
280+
Set<Statement> callvalues = new HashSet<>();
281+
282+
for (Statement statement : cfgNodeList.getNodes())
283+
if (statement instanceof Callvalue)
284+
callvalues.add(statement);
285+
286+
return this.callvalues = callvalues;
287+
}
288+
289+
return this.callvalues;
290+
}
291+
161292
/**
162293
* Returns a set of all the STOP and RETURN statements in the CFG.
163294
*
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package it.unipr.crosschain.checker;
2+
3+
import it.unipr.analysis.contract.SmartContract;
4+
import it.unipr.analysis.taint.TaintAbstractDomain;
5+
import it.unipr.analysis.taint.TaintElement;
6+
import it.unipr.cfg.*;
7+
import it.unipr.utils.MyCache;
8+
import it.unive.lisa.analysis.AnalysisState;
9+
import it.unive.lisa.analysis.AnalyzedCFG;
10+
import it.unive.lisa.analysis.SemanticException;
11+
import it.unive.lisa.analysis.SimpleAbstractState;
12+
import it.unive.lisa.analysis.heap.MonolithicHeap;
13+
import it.unive.lisa.analysis.nonrelational.value.TypeEnvironment;
14+
import it.unive.lisa.analysis.types.InferredTypes;
15+
import it.unive.lisa.checks.semantic.CheckToolWithAnalysisResults;
16+
import it.unive.lisa.checks.semantic.SemanticCheck;
17+
import it.unive.lisa.program.cfg.CFG;
18+
import it.unive.lisa.program.cfg.statement.Statement;
19+
import java.util.HashSet;
20+
import java.util.Set;
21+
import org.apache.logging.log4j.LogManager;
22+
import org.apache.logging.log4j.Logger;
23+
24+
/**
25+
* Semantic checker that detects missing access control guards by combining a
26+
* taint analysis with control-flow reachability information. The checker looks
27+
* for sensitive sink instructions that can be reached from untrusted sources
28+
* without passing through any guarded conditional jumps.
29+
*/
30+
public class AccessControlIncompletenessChecker implements
31+
SemanticCheck<SimpleAbstractState<MonolithicHeap, TaintAbstractDomain, TypeEnvironment<InferredTypes>>> {
32+
33+
private static final Logger log = LogManager.getLogger(AccessControlIncompletenessChecker.class);
34+
35+
private Set<Statement> taintedJumpi = new HashSet<>();
36+
private SmartContract contract;
37+
38+
/**
39+
* Builds the checker for the given contract.
40+
*
41+
* @param contract the contract under analysis
42+
*/
43+
public AccessControlIncompletenessChecker(SmartContract contract) {
44+
this.contract = contract;
45+
}
46+
47+
@Override
48+
public boolean visit(
49+
CheckToolWithAnalysisResults<
50+
SimpleAbstractState<MonolithicHeap, TaintAbstractDomain, TypeEnvironment<InferredTypes>>> tool,
51+
CFG graph) {
52+
53+
EVMCFG cfg = ((EVMCFG) graph);
54+
55+
for (Statement node : cfg.getNodes())
56+
if (node instanceof Jumpi)
57+
for (AnalyzedCFG<SimpleAbstractState<MonolithicHeap, TaintAbstractDomain,
58+
TypeEnvironment<InferredTypes>>> result : tool.getResultOf(cfg)) {
59+
60+
AnalysisState<SimpleAbstractState<MonolithicHeap, TaintAbstractDomain,
61+
TypeEnvironment<InferredTypes>>> analysisResult = null;
62+
63+
try {
64+
analysisResult = result.getAnalysisStateBefore(node);
65+
} catch (SemanticException e1) {
66+
log.error("(AccessControlIncompletenessChecker): {}", e1.getMessage());
67+
}
68+
69+
TaintAbstractDomain taintedStack = analysisResult.getState().getValueState();
70+
71+
if (taintedStack.isBottom() || taintedStack.isTop())
72+
continue;
73+
74+
if (TaintElement.isAtLeastOneTainted(
75+
taintedStack.getElementAtPosition(1),
76+
taintedStack.getElementAtPosition(2)))
77+
taintedJumpi.add(node);
78+
}
79+
return true;
80+
}
81+
82+
@Override
83+
public boolean visit(
84+
CheckToolWithAnalysisResults<
85+
SimpleAbstractState<MonolithicHeap, TaintAbstractDomain, TypeEnvironment<InferredTypes>>> tool,
86+
CFG graph, Statement node) {
87+
88+
if (node instanceof Call
89+
|| node instanceof Callcode
90+
|| node instanceof Staticcall
91+
|| node instanceof Delegatecall
92+
|| node instanceof Sstore
93+
|| node instanceof Balance
94+
|| node instanceof Address) {
95+
EVMCFG cfg = ((EVMCFG) graph);
96+
97+
for (AnalyzedCFG<SimpleAbstractState<MonolithicHeap, TaintAbstractDomain,
98+
TypeEnvironment<InferredTypes>>> result : tool.getResultOf(cfg)) {
99+
AnalysisState<SimpleAbstractState<MonolithicHeap, TaintAbstractDomain,
100+
TypeEnvironment<InferredTypes>>> analysisResult = null;
101+
102+
try {
103+
analysisResult = result.getAnalysisStateBefore(node);
104+
} catch (SemanticException e1) {
105+
log.error("(AccessControlIncompletenessChecker): {}", e1.getMessage());
106+
}
107+
108+
// Retrieve the symbolic stack from the analysis result
109+
TaintAbstractDomain taintedStack = analysisResult.getState().getValueState();
110+
111+
if (taintedStack.isBottom() || taintedStack.isTop())
112+
// Nothing to do
113+
continue;
114+
115+
int numArgs = getNumberOfArgs(node);
116+
boolean isAtLeastOneTainted = false;
117+
118+
for (int argIndex = 1; argIndex <= numArgs; argIndex++)
119+
isAtLeastOneTainted |= TaintElement.isAtLeastOneTainted(
120+
taintedStack.getElementAtPosition(argIndex));
121+
122+
if (isAtLeastOneTainted)
123+
checkForAccessControlIncompleteness(tool, cfg, node);
124+
}
125+
}
126+
return true;
127+
}
128+
129+
/**
130+
* Computes the number of arguments consumed from the stack by the provided
131+
* EVM instruction.
132+
*
133+
* @param node the statement to inspect
134+
*
135+
* @return the amount of stack elements consumed by {@code node}
136+
*/
137+
private int getNumberOfArgs(Statement node) {
138+
if (node instanceof Call || node instanceof Callcode)
139+
return 7;
140+
if (node instanceof Staticcall || node instanceof Delegatecall)
141+
return 6;
142+
if (node instanceof Sstore)
143+
return 2;
144+
if (node instanceof Balance)
145+
return 1;
146+
return 0;
147+
}
148+
149+
/**
150+
* Verifies whether a tainted source can reach an unguarded sink without
151+
* being protected by a conditional jump and, if so, records the
152+
* vulnerability.
153+
*
154+
* @param tool the analysis tool to report warnings on
155+
* @param cfg the CFG under inspection
156+
* @param sink the sink statement that manipulates sensitive state
157+
*/
158+
private void checkForAccessControlIncompleteness(CheckToolWithAnalysisResults<
159+
SimpleAbstractState<MonolithicHeap, TaintAbstractDomain, TypeEnvironment<InferredTypes>>> tool, EVMCFG cfg,
160+
Statement sink) {
161+
162+
Set<Statement> sources = new HashSet<>();
163+
sources.addAll(cfg.getAllCalldataload());
164+
sources.addAll(cfg.getAllCalldatacopy());
165+
sources.addAll(cfg.getAllCalldatacopy());
166+
sources.addAll(cfg.getAllCaller());
167+
sources.addAll(cfg.getAllOrigin());
168+
sources.addAll(cfg.getAllCallvalue());
169+
170+
for (Statement source : sources) {
171+
if (cfg.reachableFromWithoutStatements(source, sink, taintedJumpi)) {
172+
String functionSignatureByStatement = contract.getFunctionSignatureByStatement(source);
173+
174+
// It means that this vulnerability is inside a private function
175+
if (functionSignatureByStatement.equals("no-function-found"))
176+
continue;
177+
178+
ProgramCounterLocation sinkLocation = (ProgramCounterLocation) sink.getLocation();
179+
180+
log.warn(
181+
"[DEFINITE] Access Control Incompleteness vulnerability at pc {} (line {}) coming from pc {} (line {}).",
182+
sinkLocation.getPc(),
183+
sinkLocation.getSourceCodeLine(),
184+
((ProgramCounterLocation) sink.getLocation()).getPc(),
185+
((ProgramCounterLocation) sink.getLocation()).getSourceCodeLine());
186+
187+
String warn = "[DEFINITE] Access Control Incompleteness vulnerability at "
188+
+ ((ProgramCounterLocation) sink.getLocation()).getSourceCodeLine();
189+
tool.warn(warn);
190+
MyCache.getInstance().addUncheckedExternalCallWarning(cfg.hashCode(), warn);
191+
192+
warn = "[DEFINITE] Access Control Incompleteness vulnerability in " + contract.getName() + " at "
193+
+ functionSignatureByStatement
194+
+ " (pc: " + ((ProgramCounterLocation) sink.getLocation()).getPc() + ", "
195+
+ "line: " + ((ProgramCounterLocation) sink.getLocation()).getSourceCodeLine() + ")";
196+
MyCache.getInstance().addVulnerabilityPerFunction(cfg.hashCode(), warn);
197+
}
198+
}
199+
}
200+
201+
}

0 commit comments

Comments
 (0)