[C-safe-secure-studygroup] more on dynamic+static analysis

Kostya Serebryany kcc at google.com
Thu Jan 26 21:04:11 UTC 2017


On Thu, Jan 26, 2017 at 2:11 AM, Clive Pygott <clivepygott at gmail.com> wrote:

> Your example:
>
> // again, not statically checkable, but we can insert a precise dynamic
> check
> void foo(int n, int m) {
>    int *p = (int*)malloc(n * sizeof(int));
>   ...
>   p[m] = 0;
>  }
>
> goes to what I was saying yesterday about whole program analysis.
>

Yes.


> It may be statically checkable, if at each point foo is called you can
> show   (n > 0) && (m >= 0) && (m < n)      If n and m are derived from
> expressions, then can you push back into those expression to check this
> property.
>


Correct. But
a) 'n' or 'm' do not necessary come from expressions  -- they could be
derived from un-trusted inputs,
b) not every expression is possible to analyze in theory, let alone in
practice,
c) whole program analysis may not be available



> How deep a tool can go become a quality of implementation issue
>


> What do you mean by  "we can insert a precise dynamic check", I'm guessing
> void foo(int n, int m) {
>    int *p = (int*)malloc(n * sizeof(int));
>    if ((m >= 0) && (m < n))
>

More like  if ((m < 0) || (m >= n)) abort();


>      p[m] = 0;
>  }
>
> Whilst this is a dynamic check in the sense that you are adding code that
> will perform the check at runtime, the fact that there won't be an index
> out of bounds error here is statically checkable
>

Precisely.
But this check does not fix the problem, it only changes it from
easy-to-exploit hard-to-reproduce buffer overflow into a harder-to-exploit
and easy-to-reproduce crash (DoS attack is still possible).
This is why we actually need the next layer of tools to find these cases
(fuzzing, symbolic execution, etc)

--kcc



>
>     Clive
>
>
>
>
>
>
>
>
> On Wed, Jan 25, 2017 at 11:25 PM, Kostya Serebryany <kcc at google.com>
> wrote:
>
>> There are lots of problems in C that *can not* be solved by static nor
>> by dynamic analyses alone, but *can* be solved with a combination of
>> those.
>>
>> Let's take buffer overflows as an example (probably, the most well
>> understood problem in C).
>> I think that every memory access can be classified to be one of:
>> * statically checkable
>> * not statically checkable, but precisely checkable at run-time
>> * not statically checkable, and only imprecisely checkable at run-time
>> * not statically checkable, and existing dynamic analysis tools are
>> likely useless
>>
>> // This is fully analyzable statically
>> int a[10];
>> void foo() {  for (int i = 0; i < 20; i++) a[i] = 0; }
>>
>> // This can't be analyzed statically, but we can insert  checks that
>> // will perform precise checks dynamically
>> int a[10];
>> extern int get_idx();  // No source available during analysis
>> void foo() { a[get_idx()] = 0; }
>>
>> // again, not statically checkable, but we can insert a precise dynamic
>> check
>> void foo(int n, int m) {
>>    int *p = (int*)malloc(n * sizeof(int));
>>   ...
>>   p[m] = 0;
>>  }
>>
>> // W/o the context of the calls we can't do anything statically,
>> // so the only thing that remains is imprecise dynamic check
>> // (using e.g. shadow memory, like valgrind or AddressSanitizer do)
>> void foo(int m, int *p) {
>>    p[m] = 0;
>> }
>>
>> // In this case, static analysis is most likely infeasible
>> // and adding a precise dynamic check is too expensive,
>> // but an imprecise check would work "ok" because all objects are in heap
>> // and so we can rely on the redzones (like in valgrind, asan)
>> int *p1 = (int*)malloc(N1);
>> int *p2 = (int*)malloc(N1);
>> ...
>> int *pK = (int*)malloc(NK);
>>
>> int *p = .. ? p1 : .. p2 ? : ... pK;  // one of the above pointers
>> ...
>> p[m] = 0;
>>
>>
>> // Now, this is much worse: if S::buffer overflows by one, redzones will
>> not find it,
>> // and there is no usable dynamic tool on the market today that will find
>> this reliably.
>> // (Well, there is Intel MPX, but I can't call it usable today)
>> // For this case we should be able to warn users that due to expression
>> (**)
>> // * static analysis gave up
>> // * dynamic analysis is potentially inefficient
>> struct S {
>>   int buffer[10];
>>   int more_fields;
>> };
>>
>> struct S s;
>> int *p1 = ...
>> ...
>> int *pK = &s.buffer[0];  // (**)
>> ...
>> int *p = .. ? ... :  pK;
>> p[m] = 0;
>>
>>
>> I think it would be a great outcome of this study group if we can prepare
>> a comprehensive
>> set of tests like the above.
>> Not just for buffer overflows, but for everything else as well (at least,
>> for use-after-free of all kinds)
>>
>> Thoughts?
>>
>> --kcc
>>
>>
>> _______________________________________________
>> C-safe-secure-studygroup mailing list
>> C-safe-secure-studygroup at lists.trustable.io
>> https://lists.trustable.io/cgi-bin/mailman/listinfo/c-safe-
>> secure-studygroup
>>
>>
>
> _______________________________________________
> C-safe-secure-studygroup mailing list
> C-safe-secure-studygroup at lists.trustable.io
> https://lists.trustable.io/cgi-bin/mailman/listinfo/c-
> safe-secure-studygroup
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.trustable.io/pipermail/c-safe-secure-studygroup/attachments/20170126/4fadbe07/attachment.html>


More information about the C-safe-secure-studygroup mailing list