Introduction à Klee, dynamic symbolic execution engine

Cet article présente Klee ainsi que ses tutoriels de prises en main.

Il s’agit principalement d’une traduction anglais -> français complétée par mes prises des notes de la documentation proposée par l’éditeur de Klee.

Présentation

Klee est un outil permettant de réaliser une analyse concolique. On peut exécuter le programme avec des valeurs concrètes. Il permet notamment de détecter les erreurs suivantes :

  • ptr: Stores ou loads à des emplacements mémoires invalides.
  • free: Double or invalid free().
  • abort: Appel de la fonction abort().
  • assert: Echec d’une insertion
  • div: Division ou modulo par 0
  • user: Problème avec l’input ou dont la façon dont est appelé klee (ndlr : pas forcément lié à votre programme)
  • exec: instruction inconnue, appelle à une fonction invalide ou avec de assembleur inline.

Sauf erreur de ma part, klee n’est pas capable de détecter des fuites de mémoire.

Sources : https://klee.github.io/tutorials/testing-regex/

Installation

Docker

Il est possible d’installer Klee dans un docker :

$ docker pull klee/klee:2.1
$ docker run --rm -it --ulimit='stack=-1:-1' klee/klee:2.1

La 1ère commande tire l’image du conteneur

La 2ème commande lance le conteneur avec un shell(option -ti) à partir de l’image précédente

Lien : https://klee.github.io/docker/

Il est possible également d’installer des outils dans le conteneur car nous y avons les droits sudo.

Par exemple pour installer nano :

sudo apt update
sudo apt-get install nano

Vous pouvez copier des fichiers de vote hôte dans le conteneur avec la commande docker cp

docker cp  dossier <nom conteneur | id>:/home/klee/klee_src/examples

Utilisation dans un programme C

Il faut d’abord inclure le header de klee :

#include<klee/klee.h>

Puis faire appel à la fonction klee_make_symbolic pour que klee génère des inputs pour la fonction donnée. Exemple avec le fichier get_sign.c du tutoriel 1 de klee.

#include "klee/klee.h"

int get_sign(int x) {
} 

int main() {
  int a;
  klee_make_symbolic(&a, sizeof(a), "a");
  return get_sign(a);
} 

Pour repérer les input menant à un chemin particulier, un if par exemple. On peut faire un appel à la fonction klee_abort() dans le if afin que klee génère une erreur :

klee_abort();

Klee générera alors des tests contenant ces erreurs qu’on pourra rejouer avec la commande suivante :

for f in *.abort.err;do ktest-tool ${f%.abort.err}.ktest ; done

Tutoriel 1

Klee propose un tutoriel pour prendre en main l’outil : https://klee.github.io/tutorials/testing-function/.

Il est également possible de faire des tests en ligne : http://klee.doc.ic.ac.uk/#

Cette partie est une version abrégée du contenu de ce tutoriel

1. Compilation

Au sein du conteneur, la commande suivante permet de compiler en LLVM bitcode

clang -I ../../include -emit-llvm -c -g -O0 -Xclang -disable-O0-optnone get_sign.c
  • Le paramètre -I permet de retrouver la dépendance klee.h

  • -g ajoute les informations de debug
  • -c pour que le code soit compilé comme un fichier objet
  • -O0 -Xclang -disable-O0-optnon permet de compiler sans optimisation

klee-tutorial1

2. Run Klee

Ensuite on peut lancer klee sur le fichier bytecode

$ klee get_sign.bc

Dans le code initial, il y avait 2 if et un else, ce qui donne 3 chemins(=path) possibles.

klee-tutorial2

Pour chaque chemin exploré, klee génère un test cases

3. Test Cases

Les tests générées ont le format .ktest

On peut les lire avec l’outil ktest-tool :

 ktest-tool klee-last/test000001.ktest

klee-tutorial3

Dans les résultats, le paramètre name fait référence au “a” passé lors de l’appel à klee_make_symbolic

tutoriel-klee_code

4. Rejouer un Test Case

La replay library, remplace les appels à klee_make_symbolic par un appel à une fonction qui remplace l’input par la valeur contenue dans le fichier .ktest

export LD_LIBRARY_PATH=/home/klee/klee_build/lib/:$LD_LIBRARY_PATH
gcc -I  ../../include -L /home/klee/klee_build/lib/ get_sign.c -lkleeRuntest
KTEST_FILE=klee-last/test000001.ktest ./a.out
echo $?

klee-tutorial4

La valeur 255 renvoyée par le dernier test case correspond au return -1

Tutoriel 2

Ce tutoriel permet de test les expressions régulières

Code : https://klee.github.io/resources/Regexp.c.html

1) Compiler le code source afin de générer un fichier object en bitecode LLVM

clang -I ../../include -emit-llvm -c -g -O0 -Xclang -disable-O0-optnone Regexp.c

klee_tutorial5

2) Vérifier l’étape 1 :

llvm-nm Regexp.bc

3) Lancer klee :

klee --only-output-states-covering-new Regexp.bc

L’option –only-output-states-couvrant-new permet de limiter le nombre de tests effectués

klee-tutorial6

Erreurs

Les fichiers d’erreurs se trouvent dans le dossier klee-last. On peut afficher les erreurs rencontrés en faisant :

cat test000010.ptr.err

klee-tutorial7

Pour afficher tous les tests ayant générés un assert abort, on peut lancer sur bash la commande suivante :

for f in *.abort.err;do ktest-tool ${f%.abort.err}.ktest ; done

Erreur générée par klee

Pour éviter que Klee génère des erreurs, il faut soit ajouter ‘\0’ à la fin du buffer soit utiliser klee_assume comme ceci :

klee_make_symbolic(re, sizeof re, "re");
klee_assume(re[SIZE - 1] == '\0');

Astuces diverses

Ajouter cette option au lancement de klee pour obtenir tous les chemins faisant cracher un programme. Par défaut, klee ne conserve qu’un seul chemin.

–emit-all-errors 

Sources

Vous aimerez aussi