---
title: ENSIIE - Analyse Statique de Programmes 2012/2013
layout: clean_page
---
# Analyse Statique de programmes - TP Frama-C

ENSIIE 3ème année - année 2012/2013

Enseignants : Virgile Prevosto et Julien Signoles

22 janvier 2013

## Introduction

Le but de ce TP est d'utiliser l'outil [Frama-C](http://frama-c.com) et
en particulier son greffon d'[analyse de
valeur](http://frama-c.com/download/value-analysis-Oxygen-20120901.pdf).
La version installée à l'ENSIIE est
[Oxygen-20120901](http://frama-c.com/download.html).

### Prise en main

Frama-C est fondé sur un ensemble de greffons proposant chacun un type
particulier d'analyse. La liste des greffons disponibles peut être
obtenue par `frama-c -help`. De même, les options permettant de piloter
le noyau s'obtiennent avec `frama-c -kernel-help`. Nous nous
intéresserons en particulier aux options `-main` et `-ocode`.

De plus, nous utiliserons avant tout le greffon `Value`, fondé sur
l'interprétation abstraite (cf. cours précédent). Ses options sont
accessibles par `frama-c -value-help`. Les options les plus importantes
pour ce TP sont `-val`, `-slevel` et `-wlevel`.

Enfin, il existe une interface graphique, `frama-c-gui`, qui accepte les
mêmes options que `frama-c`.

### PGCD

On considère la fonction `pgcd` ci-dessous (fichier
[ppcm.c](/assets/dokuwiki/ppcm.c)), qui calcule `n` élevé à la puissance
`m`

``` c
int pgcd(int x, int y) {
     int a = x, b = y;
     while(b!=0) {
          int tmp = mod(a,b);
          a = b; b = tmp;
     }
     return a;
}
```

Afin de contourner une imprécision de Frama-C, on n'utilise pas
l'opérateur `%` habituel, mais une opération `mod` spécifiée de la
manière suivante:

``` c
/*@ requires y != 0;
    assigns \result \from x,y;
    ensures -(y) < \result < y || y<\result< -y;
    ensures \result == x%y;
*/
int mod(int x, int y);
```

#### Une première analyse

On définit une petite fonction de test:

``` c
int P;

void test() {
     P=pgcd(49,28);
}
```

1.  On utilise l'option `-main test` pour indiquer à Frama-C que le
    point d'entrée est `test` et `-val` pour lancer l'analyse de valeur.
    Quelles est la valeur obtenue pour `P`?
2.  Pour voir ce qui se passe à l'intérieur de la boucle, on peut
    utiliser une fonction `Frama_C_show_each_XXX(...)`, où `XXX` est un
    suffixe quelconque. Cette fonction provoquera l'affichage des
    intervalles trouvés pour chacun de ses arguments chaque fois que
    l'analyse passe par le statement correspondant. Ainsi,
    `Frama_C_show_each_a(a)` en début de boucle permettra de voir
    comment évoluent les valeurs possibles pour `a`. Que constate-t-on?
3.  Le problème observé vient du fait que les valeurs pour `a`
    superposent tous les pas de boucle, et qu'on effectue du widening.
    Une première approche permet de retarder le moment où on a recours à
    l'élargissement, en augmentant la valeur de l'option `-wlevel` (`3`
    par défaut). Tester différentes valeurs. Qu'obtient comme résultats
    pour `P`?
4.  Une meilleure option consiste à augmenter la valeur de l'option
    `-slevel` (`1` par défaut), qui permet de propager jusqu'à `n` états
    séparés pour chaque statement. Tester différentes valeurs.
    Qu'obtient-on comme résultats pour `P`?

#### Analyse générique

1.  On choisit maintenant comme point d'entrée `pgcd` elle-même. Quels
    sont les problèmes recensés par Frama-C?
2.  Pour montrer que la pré-condition de `mod` est toujours vérifiée, on
    va avoir recours à une annotation ACSL: quand l'analyse de valeur
    rencontre une disjonction, et si la valeur du `slevel` le permet,
    elle utilise un état pour chaque branche de la disjonction. Écrire
    une annotation permettant de ne pas émettre d'alarme. La disjonction
    doit respecter deux critères:
    1.  elle doit couvrir tous les cas possible: l'analyse de valeur
        doit pouvoir la valider
    2.  chaque cas doit permettre à l'analyse de valeur de réduire
        l'état correspondant: il faut utiliser des conjonctions
        d'inégalités impliquant une variable.

### PPCM

On considère maintenant la fonction `ppcm` ci-dessous (et donnée
également dans [ppcm.c](/assets/dokuwiki/ppcm.c)), avec une fonction
`test2` permettant de faire un test particulier.

``` c
int ppcm(int x, int y) {
     int a = x, b = y;
     while(b!=0) {
          int tmp = mod(a,b);
          a = b; b = tmp;
     }
     return x * y / a;
}

void test2() {
     P=ppcm(49,28);
}
```

1.  étudier la valeur trouvée pour `P` après l'exécution de `test2`
2.  Dans un contexte générique, la multiplication à la fin de `ppcm`
    peut mener à un overflow. On va limiter le cas d'étude à des valeurs
    comprises entre `-10000` et `10000` pour `x` et `y`. En se servant
    de la fonction `interval` donnée ci-dessous, fournir une fonction
    `main` qui appelle `ppcm` dans ce contexte.
3.  Peut-on avoir une division par 0 lors de l'exécution de `ppcm` avec
    des paramètres quelconques? Corriger éventuellement la fonction.

<!-- end list -->

``` c
/*@ requires a<=b;
  ensures a<=\result<=b;
*/
int interval(int a, int b);
```

### SHA-3

On considère à présent [l'implémentation en
C](http://keccak.noekeon.org/KeccakReferenceAndOptimized-3.2.zip) de la
fonction de hachage cryptographique **Keccak** librement disponible sur
[cette page](http://keccak.noekeon.org/files.html). Cette fonction a été
sélectionnée fin 2012 à l'issue de la compétition **SHA-3** pour
remplacer [SHA-2](https://fr.wikipedia.org/wiki/SHA-2). On cherche dans
cet exercice à montrer que l'implantation de référence est exempt
d'erreur à l'exécution pour des messages inférieurs à une certaine
taille.

#### Récupération des fichiers

L'archive Keccak contient en fait plusieurs implantations: une en C pur,
et d'autres optimisées et comportant certaines portions en assembleur.
Seule la première nous intéresse. On va commencer par utiliser Frama-C
pour générer un fichier qui regroupe les fonctions de cette
implantation, et uniquement celles-ci. À l'aide du fichier `makefile`,
et plus particulièrement de sa variable `SOURCE_REFERENCE`, trouver la
liste des fichiers `.c` à considérer (NB: on ne s'intéresse pas aux
fichiers `main*`, puisque nous utiliserons nos propres fonctions
`main`). Générer un fichier C faisant le link de ceux-ci à l'aide de la
commande `frama-c -print -ocode resultat.i entree1.c entree2.c ...` On
travaillera désormais avec le fichier `resultat.i`, qui alternativement
est disponible [ici](/assets/dokuwiki/keccak_reference.c)

#### Analyse

Il manque à ce résultat quelques fonctions de la stdlib dont Frama-C
aura besoin, et qui sont données [ici](/assets/dokuwiki/keccak_lib.c).
On s'intéresse à la fonction `main` suivante
([fichier](/assets/dokuwiki/keccak_main.c)):

``` c
#include <stdio.h>
#include "KeccakNISTInterface.h"

#define HASHLEN 64

BitSequence msg[80] = "hello, world";

int main(void) {
  BitSequence hash[HASHLEN/8];
  unsigned long i;
  hashState context;
  Init(&context, HASHLEN);
  Update(&context,msg,80);
  Final(&context,hash);
  for (i=0; i<HASHLEN/(8*sizeof(unsigned long)); i++)
       printf("%lu\n",((unsigned long *)hash)[i]);
  return 0;
}
```

``` 
  - compiler cette application (''resultat.i'' et ''main'') avec ''gcc'', et exécuter le résultat obtenu. Qu'observe-t-on?
  - Lancer l'analyse de valeurs de Frama-C sur ''resultat.i'' et ''main''. Qu'observe-t-on comme résultats ?
  - Le cas échéant, corriger le code en vous basant sur la documentation de ''KeccakNISTInterface.h'', et relancer compilation et analyse. Qu'observe-t-on
  - À l'aide de la fonction ''interval'', modifier ''main'' pour que ''msg'' contienne une suite aléatoire de caractères, et relancer l'analyse. Y'a-t-il des erreurs à l'exécution possibles?
```