symbolic execution had some nice properties:
but had huge practical problems:
complete (all true positives found)
sound (no false positives found)
greybox fuzzing, symbolic exec without approximations: always sound
symbolic execution without approximations: complete if all paths are solved
other design points than symbolic execution:
not tracking all the variable values
alternative: just track properties of interest
compute precisely what paths through code are possible
alternative: track sub/superset of possible paths
model for use-after-free, pointer is:
just track this logical state for each pointer
ignore everything else
assume all if statements/loop conditions can be true or false
similar to symbolic execution
form graph of states of quux
three ways to reach D (ABD, ACD, AD)
but only care if quux freed/allocated
so only two cases to consider
exercise: what should state of pointer q be at C?
| A. allocated | B. freed |
| C. allocated if+only if reached via path with A1 | |
| D. freed if+only if reached via path with A1 | |
| E. something else? | |
p and q could point to same thing
mark different versions of of p, q
track as if separate varaibles
shown above: track each path separately
but means we’ll have too many nodes in big program
alternate idea: avoid path explosion by merging possible sets
would like to analyze program function-at-a-time, but…
what about computed array indices?
what about pointers to pointers?
…
high false-positive solution:
high false-negative solution:
// pattern: connecting to database with empty password:
connection = DriverManager.getConnection(
"jdbc:hsqldb:hsql://db.example.com/xdb" /* database ID */,
"sa" /* username */, "" /* password */);
// pattern: Sql.hasResult()'s second argument isn't a constant
Sql.hasResult(c, "SELECT 1 FROM myTable WHERE code='"+code+"'");
// pattern: new FileReader's argument comes from request
HttpRequest request = ...;
String path = request.getParameter("path");
BufferedReader r = new BufferedReader(
new FileReader("data/" + path));good at finding some kinds of bugs
excellent for ‘‘bug patterns’’ like:
struct Foo* foo;
...
foo = malloc(sizeof(struct Bar));
Coverity, Fortify — commerical static analysis tools
Splint — unmaintained?
FindBugs (Java)
clang-analyzer — part of Clang compiler
Microsoft’s Static Driver Verifier — required for Windows drivers:
can try to apply same technique to array bounds
but much more complicated/more likely to have false positives/negatives
for each array or pointer track:
for each integer track:
similar analysis looking at paths?
really common pattern we want to find:
data from somewhere gets to dangerous place
we’ll talk about it specially next
so far: static analysis concerned with control flow
often, we’re really worried about how data moves
many applications:
…
can do this statically (potential dependencies)
or dynamically (actual dependencies as program runs)
def f(a, b, c):
desc = 'a={},b={}'.format(a, b)
if b > 10:
y = a
else:
y = c
w = y + a
pair = (w, c)
desc = desc + \
',pair={}'.format(pair)
print(desc)
return y
def f(a, b, c):
if b > 10:
y = a
else:
y = c
return y
Q: which is better …
# Python example
def stash(a):
global y
y = a
x = [0,1,2,3]
stash(x)
x[2] = input()
print(y[2])
// C example
int *y;
void stash(int *a) {
y = a;
}
int main() {
int x[3];
stash(x);
y[2] = GetInput();
printf("%d\n",x[2]);
}
same points-to problem with static analysis
need to realize that x[2] and y[2] are the same!
can fix this with dynamic approach: monitor running program
def retrieve(flag):
global the_default
if flag:
value = input()
else:
value = the_default
value = process(value)
if not flag:
print("base on default: ",value)
return value
retrieve(True)
retrieve(False)
x = int(input())
if x == 0:
print(0)
elif x == 1:
print(1)
elif ...
does input make it to output?
should we try to detect this?
harder to fix this issue
needed choose sources (so far: function arguments)
and sinks (so far: print, return)
choice depends on application
SQL injection:
private info leak: