Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modifications in Frama-C framework for handling...

Denis
June 04, 2019

Modifications in Frama-C framework for handling Linux kernel code

The talk presents an experience of the AstraVer team with deductive verification of Linux kernel code. The implemented modifications in Frama-C/AstraVer/Why3 toolchain allows to handle it more effectively. All the modifications and ACSL extensions are practically motivated and are oriented either on the support of different code patterns or on a more elegant style of specifications. Among other things, things like limited support for functions pointers, per-operation switch between integer models in arithmetic terms and expressions, simple context management and support for an arbitrary order of logic definitions, automatic relevant code extraction in the deductive verification plugin, improvements in the reinterpretation of memory for pointers to integral types will be discussed. Where it is appropriate the same example will be given in the Frama-C/AstraVer and Frama-C/WP versions to reveal the original motivation for the modification. The presentation requires prior knowledge of ACSL and the Frama-C framework.

Denis

June 04, 2019
Tweet

More Decks by Denis

Other Decks in Research

Transcript

  1. Modifications in the Frama-C framework for handling Linux kernel code

    Denis Efremov, Mikhail Mandrykin, Alexey Khoroshilov CEA, Paris, France
  2. Toolset Frama-C/AstraVer(Jessie)/Why3 • Official page https://forge.ispras.ru/projects/astraver/wiki $ opam switch create

    astraver 4.07.1 $ opam repo add ispras https://forge.ispras.ru/git/astraver.opam-repository.git $ opam update $ opam install frama-c astraver why3 altgr-ergo coq coqide • Travis-CI https://github.com/evdenis/verker/blob/master/.travis.yml
  3. Automatic relevant code extraction in the deductive verification plugin (1)

    • Motivation • Reduce and simplify the code for the deductive analysis • Implementation • Take into the analysis scope only code with ACSL specification • Replace the irrelevant structure fields with stubs • Inline enum constants • …
  4. Automatic relevant code extraction in the deductive verification plugin (2)

    struct test { int a; struct unused { int d; int e; } b; } t1; //@ requires \true; void test1(int a) { t1.a = a; } void test2(int d) { t1.b.d = d; } struct test { int a; int __padding[2]; } t1; //@ requires \true; void test1(int a) { t1.a = a; }
  5. File dependent VC generation test2.c #include "test2.h" /*@ ensures \result

    == 0; */ int test2(void) { return test1(); } test2.h /*@ ensures \result == 0; */ int test1(void) { return 0; } $ frama-c –av test2.c $ frama-c –av test2.c test2.h * Mainly for lemma functions. Usually you don’t want to prove them every time you include a file.
  6. Support for an arbitrary order of logic definitions • There

    is no forward declaration in ACSL • Definitions should be written before the first use • This limitation was removed in AstraVer plugin • Direct recursion in a definition is allowed • Why3 doesn’t allow indirect recursion in the predicates struct a; struct b {struct a *pa;}; struct a {struct b *pb;struct a *pa}; //@ requires valid_a(a); void order(struct a *a) {} /*@ predicate valid_a(struct a *pa) = \valid(pa) && (pa->pa == \null || valid_a(pa->pa)) && valid_b(pa->pb); */ /*@ predicate valid_b(struct b *pb) = \valid(pb) && pb->pa == \null; */
  7. Simple context management for logic definitions • Only global_lemma1 is

    in the global scope • lemma1, lemma2 are not imported to the VCs by default • To “import” a lemma in the verification task a predicate or a logic function from the block should be used • Allows one to keep global context clean /*@ axiomatic lemma1 { lemma lemma1: \true != \false; predicate import_lemma1 = \true; }*/ /*@ axiomatic axs { logic boolean prd; axiom ax1: prd == \true; lemma lemma2: prd == \true ; }*/ //@ lemma global_lemma1: 1/0 == 1/0; /*@ requires import_lemma1; requires prd;*/ int main(void) { return 0; }
  8. Logic Import • $ frama-c –av import.c b.c c.c •

    Sometimes it’s not allowed to modify the file structure of the project • Even add “#include” in the sources • “Import” allows one to import logic declarations from other files • Or if you don’t want to “#include” all definitions but to import a single import.c //@ import "b.c" (valid_b); //@ import "c.c" (*); /*@ requires valid_b(b); requires valid_c(c); */ int test(int b, int c) { return b + c; } c.c /*@ predicate valid_c(int c) = -100 <= c <= 100; */ b.c /*@ predicate valid_b(int b) = -10 <= b <= 10; */
  9. Limited support for function pointers /*@ requires \valid(s) && flag

    == 0; ensures \result == 0; */ int dummy_callback1(int flag, char *s); /*@ requires \valid(s); ensures \result == 1; */ int dummy_callback2(int flag, char *s); //@ ensures \result == 0; int dummy_callback3(); struct callbacks { int (*cb1) (int, char *); int (*cb2) (void); }; struct outer { struct callbacks *cbs; }; /*@ requires \valid(p) && \valid(p->cbs); requires p->cbs->cb1 == &dummy_callback1 || p->cbs->cb1 == &dummy_callback2; requires p->cbs->cb2 == (int (*)(void))&dummy_callback3; ensures \result == 0 || \result == 1; */ int caller(struct outer *p) { return p->cbs->cb1(0, 0, "const char") || p->cbs->cb2(); } • All possible values for a pointer should be specified • VCs will be generated for every function
  10. Improvements in the reinterpretation of memory for pointers to integral

    types (1) • Works for integral types only • Respects endianness • Switch between different memory representations • Not possible to “view” the memory in two different representations at the same time /*@ requires n >= 0; requires \valid(p + (0..n-1)); */ void int_access(int *p, int n) { if (n > 0) p[n-1] = 0; } void example(void) { int p[1000]; //@ av pragma p :> char *; char *q = (char *) p; //@ assert \base_addr(q) == q; //@ assert \valid(q + (0..3999)); //@ av pragma q :> int *; int_access(p, 1); }
  11. Special predicates uint\d\d_as_uint\d\d (1) // To import /*@ axiomatic casts

    { // true if “a” consists from “a1” and “a2”, respects endianness predicate uint16_as_uint8(unsigned short a, // import unsigned char a1, unsigned char a2); } */ /*@ requires \typeof(p) <: \type(unsigned short *) && \valid((unsigned short *)p); allocates \nothing; assigns *((unsigned short *)p); ensures uint16_as_uint8(*(unsigned short *)p, (unsigned char) (v & (unsigned short)0xff), (unsigned char) ((v >> (unsigned short)8) & (unsigned short)0xff)); */ void set_w16(void *p, unsigned short v);
  12. Special predicates uint\d\d_as_uint\d\d (2) void set_w16(void *p, unsigned short v)

    { unsigned short *tmp = p; //@ av pragma tmp :> unsigned char *; unsigned char *ptr = (unsigned char *) tmp; ptr[0] = v & 0xff; ptr[1] = (v >> 8) & 0xff; //@ av pragma ptr :> unsigned short *; /*@ assert \forall unsigned short *q; \base_addr(q) == \base_addr(tmp) ==> (0 <= tmp - q <= 0 <==> tmp == q); */ }
  13. Template specifications for standard mem* functions (1) /*@ requires n

    >= 0 && n % (sizeof (_type)) == 0 && \let _n = n / sizeof (_type); \valid(((_type *) dest)+(0 .. _n - 1)) && \valid(((_type *) src)+(0 .. _n - 1)) && separated__type{Pre, Pre}((_type *) dest,(_type *) src, n); assigns ((_type *) dest)[0 .. (n / sizeof (_type)) - 1] \from ((_type *) src)[0 .. (n / sizeof (_type)) - 1]; allocates \nothing; ensures memcmp__type{Here, Here}((_type *) dest, (_type *) src, n); ensures \result == dest; */ extern _type *memcpy__type(_type *restrict dest, const _type *restrict src, size_t n);
  14. Template specifications for standard mem* functions (2) // To import

    /*@ axiomatic memcmps { predicate memcmp_int{L1, L2}(int *a, int *b, size_t n); predicate separated_int(int *a, int *b, unsigned int n); } */ int test(int *a, int *b) { memcpy(a, b, sizeof (int)); //@ assert memcmp_int{Here, Here}(a, b, (unsigned long long) sizeof (int)); return 0; }
  15. Allocates and \null • Malloc function could return \null, zero-sized

    pointer in case requested size == 0, or a valid pointer • allocates a && a == \null ==> allocates \nothing; • Useful in case of a multiple allocations in a function /*@ assigns \nothing; allocates \result; ensures \valid(\result) || \result == \null; ensures a <= 0 ==> \result == \null; */ int *atest(int a) { if (a > 0) return malloc(sizeof(int)); return NULL; } /*@ requires \valid(a); assigns *a; allocates \nothing; */ void test(int **a) { *a = atest(0); }
  16. Per-operation switch between integer models in arithmetic terms and expressions

    char *strnchr(const char *s, size_t count, int c) { for (; count-- && *s != '\0'; ++s) if (*s == (char)c) return (char *)s; return NULL; } • The underflow of an unsigned loop iterator at the last iteration step due to the postfix decrement;
  17. Per-operation switch between integer models in arithmetic terms and expressions

    char *strnchr(const char *s, size_t count, int c) { for (; count-- /*@%*/ && *s != '\0'; ++s) if (*s == (char)c) return (char *)s; return NULL; } • The underflow of an unsigned loop iterator at the last iteration step due to the postfix decrement;
  18. Per-operation switch between integer models in arithmetic terms and expressions

    char *strnchr(const char *s, size_t count, int c) { for (; count-- /*@%*/ && *s != '\0'; ++s) if (*s == (char)c) return (char *)s; return NULL; } • The underflow of an unsigned loop iterator at the last iteration step due to the postfix decrement; • The intended cast to a smaller integer type;
  19. Per-operation switch between integer models in arithmetic terms and expressions

    char *strnchr(const char *s, size_t count, int c) { for (; count-- /*@%*/ && *s != '\0'; ++s) if (*s == (char) /*@%*/ c) return (char *)s; return NULL; } • The underflow of an unsigned loop iterator at the last iteration step due to the postfix decrement; • The intended cast to a smaller integer type;
  20. Why3 Sprove tool Differences between why3prove and why3sprove: • Sprove

    works with existing sessions • Sprove creates session if it doesn’t exists • Sprove saves its results to a session • Sprove supports strategies Motivation: sprove automatically proves most of the VCs with an appropriate strategy without requiring manual interaction with the ide