Animated donut verification

Pascal Cuoq - 22nd Jul 2011

Here's a cool obfuscated C program by Andy Sloane that draws a revolving donut.

You know where this is heading... I am going to suggest that someone should verify it. I will get us started.

1. Download the code

2. Determine what library functions it needs:

$ frama-c -metrics donut.c 
[kernel] preprocessing with "gcc -C -E -I.  donut.c" 
donut.c:1:[kernel] user error: syntax error 

Oops. The donut is a little bit too obfuscated for our front-end it seems. The problem here is the idiom k; to declare an int variable k. Let's change this:

int          k;double sin() 
          cos();main(){float A= 
       0 B=0 i j z[1760];char b[ 
     1760];printf("\x1b[2J");for(;; 
  ){memset(b 32 1760);memset(z 0 7040) 
  ;for(j=0;6.28>j;j+=0.07)for(i=0;6.28 
 >i;i+=0.02){float c=sin(i) d=cos(j) e= 
 sin(A) f=sin(j) g=cos(A) h=d+2 D=1/(c* 
 h*e+f*g+5) l=cos      (i) m=cos(B) n=s\ 
in(B) t=c*h*g-f*        e;int x=40+30*D* 
(l*h*m-t*n) y=            12+15*D*(l*h*n 
+t*m) o=x+80*y           N=8*((f*e-c*d*g 
 )*m-c*d*e-f*g-l        *d*n);if(22>y&& 
 y>0&&x>0&&80>x&&D>z[o]){z[o]=D;;;b[o]= 
 ". -~:;=!*#$@"[N>0?N:0];}}/*#****!!-*/ 
  printf("\x1b[H");for(k=0;1761>k;k++) 
   putchar(k%80?b[k]:10);A+=0.04;B+= 
     0.02;}}/*****####*******!!=;:~ 
       ~::==!!!**********!!!==::- 
         . ~~;;;========;;;:~-. 
             .. -------- */ 
$ frama-c -metrics donut.c 
[kernel] preprocessing with "gcc -C -E -I.  donut.c" 
[metrics] Syntactic metrics 
           Defined function (1): 
             main  (0 call);  
           Undefined functions (5): 
             putchar  (1 call); sin  (4 calls); cos  (4 calls); printf  (2 calls); 
             memset  (2 calls);  

Functions putchar() and printf() do not influence the rest of the execution so we can postpone finding a good modelization for them until the very end. We have a memset() implementation leftover from the Skein tutorial. Math functions cos() and sin() can in a first step be specified as returning [-1.0 .. 1.0]. The value analysis has for a long time had a Frama_C_cos() built-in that does a little better than that and the next release will have a Frama_C_sin() but these are not necessary yet.

Let's use a file sincos.c such as:

#include "share/builtin.h" 
double sin(double x) 
{ 
  return Frama_C_double_interval(-1.0  1.0); 
} 
double cos(double x) 
{ 
  return Frama_C_double_interval(-1.0  1.0); 
} 

3. Launch an imprecise analysis:

$ bin/toplevel.opt -val share/builtin.c sincos.c donut.c share/libc.c | grep assert 

We use the command above and get the results below. There are 9 alarms and 2 of them are redundant with the other 7 (these two would be removed in the GUI).

share/libc.c:51:[kernel] warning: out of bounds write. assert \valid(tmp); 
donut.c:14:[kernel] warning: accessing out of bounds index [-7610..9610]. assert 0 ≤ o ∧ o < 1760; 
donut.c:14:[kernel] warning: accessing uninitialized left-value: assert \initialized(z[o]); 
donut.c:14:[kernel] warning: accessing out of bounds index [-7610..9610]. assert 0 ≤ o ∧ o < 1760; 
donut.c:15:[kernel] warning: out of bounds read. assert \valid(". -~:;=!*#$@"+tmp_7); 
donut.c:15:[kernel] warning: accessing out of bounds index [-7610..9610]. assert 0 ≤ o ∧ o < 1760; 
donut.c:17:[kernel] warning: accessing uninitialized left-value: assert \initialized(b[k]); 
donut.c:17:[kernel] warning: accessing out of bounds index [0..1760]. assert 0 ≤ k ∧ k < 1760; 
donut.c:14:[value] warning: overflow in float: [--..--] -> [-3.40282346639e+38 .. 3.40282346639e+38]. assert(Ook) 
[scope] [rm_asserts] removing 2 assertion(s) 

4. The imprecise analysis was fast. Let us launch a more precise analysis:

$ bin/toplevel.opt -val share/builtin.c sincos.c donut.c share/libc.c -slevel 200 | grep assert 
share/libc.c:51:[kernel] warning: out of bounds write. assert \valid(tmp); 
donut.c:14:[kernel] warning: accessing out of bounds index [-7610..9610]. assert 0 ≤ o ∧ o < 1760; 
donut.c:14:[kernel] warning: accessing uninitialized left-value: assert \initialized(z[o]); 
donut.c:14:[kernel] warning: accessing out of bounds index [-7610..9610]. assert 0 ≤ o ∧ o < 1760; 
donut.c:15:[kernel] warning: accessing out of bounds index [-7610..9610]. assert 0 ≤ o ∧ o < 1760; 
donut.c:15:[kernel] warning: out of bounds read. assert \valid(". -~:;=!*#$@"+tmp_7); 
donut.c:17:[kernel] warning: accessing uninitialized left-value: assert \initialized(b[k]); 
donut.c:17:[kernel] warning: accessing out of bounds index [200..1760]. assert 0 ≤ k ∧ k < 1760; 
donut.c:14:[value] warning: overflow in float: [--..--] -> [-3.40282346639e+38 .. 3.40282346639e+38]. assert(Ook) 
[scope] [rm_asserts] removing 2 assertion(s) 

It's still the same assertions. The development version of Frama-C shows the range of the index k (that is [200..1760]) when it prints the assertion assert 0 ≤ k ∧ k < 1760;. That suggests that pushing the value of -slevel to more than 1760 may eliminate this alarm.

I will let someone else continue from here if they want. This program could be very rewarding to verify (a little birdie tells me that there is at least one true alarm to be found using the value analysis or any other techniquesI have completely lost faith in that little birdie. There may or may not be a bug we will know for sure when we are done but don't stay awake all night looking for one) so I recommended as a fun exercise for those who are bored from sandy holiday beaches.

Pascal Cuoq
22nd Jul 2011