Schwerpunkte

Software-Test

Wie robust ist mein Code?

16. Juli 2018, 17:00 Uhr   |  Frank Büchner, Hitex


Fortsetzung des Artikels von Teil 1 .

Gezielte Fehlerinjektion

Hitex
© Hitex

Bild 2: Übergabe eines NULL-Zeigers an das Testobjekt mit dem Werkzeug TESSY.

Beim Software-Unit-Test werden, anders als beim Fuzzing, gezielt Fehler injiziert. Ein Vorteil des Software-Unit-Tests ist es, dass es technisch sehr einfach ist, beliebige Eingabewerte – also insbesondere ungültige und abnormale – für den Test zu verwenden, wenn diese Eingabe per Parameter oder globale Variable erfolgt. Beispielsweise ist es technisch sehr einfach, für einen Pointer den Wert NULL oder für eine Variable einen ungültigen Wert zu übergeben (Bild 2). Technisch ebenso einfach ist es, einen ungültigen Wert als Eingabe zu verwenden, wenn es sich beispielsweise um den Rückgabewert einer durch das Testobjekt aufgerufenen Funktion handelt, und diese durch eine Platzhalterfunktion (Stub) ersetzt ist. 

Technisch schwieriger wird es jedoch, wenn die Fehlerinjektion innerhalb der Software-Unit bzw. Funktion erfolgen muss, beispielsweise weil sich eine Speicherzelle ändern soll, was im Nicht-Fehlerfall nicht eintritt. Eine solche Situation zeigt dieses Listing:

char volatile memory
int WriteRead(void)
{
        memory = 0x5a;
        if (memory == 0x5a)
               return 1; /*memory o.k. */
        else
               return 0; /*memory defect */

Dies ist ein minimalistisches Beispiel für eine technisch anspruchsvolle Fehlerinjektion, die erforderlich ist, um den else-Zweig der if-Anweisung auszuführen. Im Beispiel wird der Variablen memory ein Wert (in unserem Fall 0x5a) zugewiesen. Direkt danach wird der Wert wieder gelesen, um zu prüfen, ob er korrekt gespeichert wurde. Falls die Speicherhardware nicht defekt ist, sind der gelesene Wert und der geschriebene Wert gleich und der then-Zweig der if-Anweisung wird ausgeführt; somit ist der Rückgabewert der Funktion WriteRead() gleich 1. Man kann die Funktion WriteRead() als (sehr primitiven) Kern eines Speicher-Testalgorithmus betrachten.

Um zu prüfen, ob dieser Algorithmus Fehler in der Speicherhardware richtig erkennt, muss man dafür sorgen, dass zwischen dem Schreiben und dem Lesen der Inhalt des Speichers verändert wird. Für den Test aussagekräftiger Speicher-Testalgorithmen, die auch die genaue Position des verfälschten Bits angeben, kann es notwendig sein, genau ein bestimmtes Bit zu ändern; in unserem primitiven Fall ist eine beliebige Änderung ausreichend. Natürlich testen Speicher-Testalgorithmen nicht nur eine einzelne Speicherzelle, sondern mehrere; hiervon ist in diesem Beispiel ebenfalls abstrahiert.

Unser primitives Beispiel beschreibt im Prinzip auch die Problematik beim Test der redundanten Speicherung von Daten, wie sie beispielsweise sicherheitskritische Systeme nutzen. Hierbei wird der Wert einer Variablen nicht nur an einer Stelle im Speicher abgelegt, sondern (üblicherweise der invertierte Wert) auch an einer zusätzlichen Stelle. Vor der Verwendung wird geprüft, ob beide Werte unverändert sind; wenn nicht, deutet dies auf einen Defekt im Speicher hin. Möchte man testen, ob richtig erkannt wird, wenn einer der beiden Werte verfälscht wurde, ist wie beim Speichertest-Beispiel einer der Werte im Speicher vor dem Lesen zu ändern.

Eine eingeschobene Programmanweisung zwischen dem Schreiben und dem Lesen kann den gespeicherten Wert verändern. Dies injiziert den Fehler. Die herkömmliche Methode für die Programmiersprache C/C++ benutzt den Präprozessor um die Anweisung einzuschieben, wie folgendes Listing zeigt:

#ifdef UNIT_TEST
int inject_fault;
#edinf  

char volatile memory;
int WriteRead(void)
{
     memory = 0x5a:
#ifdef UNIT_TEST
     if (inject_fault) memory = ~memory;
#endif
     if (memory == 0x5a)
          return 1; /*memory o.k. */
     else
          return 0; /*memory defect */
}

Ist die Präprozessorkonstante UNIT_TEST aus obigem Beispiel definiert, so wird eine zusätzliche globale Variable inject_fault definiert. Diese globale Variable lässt sich, wie schon oben erwähnt, beim Software-Unit-Test ohne Weiteres auf beliebige Werte setzen. Diese Variable wird benutzt, um für jeden Testfall festzulegen, ob Fehlerinjektion stattfinden soll (inject_fault ungleich 0) oder nicht (inject_fault gleich 0). Ist inject_fault ungleich 0, wird der Inhalt der Variablen memory invertiert.

Im Prinzip ließe sich an dieser Stelle eine beliebige Aktion als Fehlerinjektion ausführen, beispielsweise ein bestimmtes Bit invertieren, setzen oder rücksetzen. Das gewünschte Bit und die gewünschte Aktion könnte dabei durch den Wert von inject_fault festgelegt werden. Wird ein Testfall mit Fehlerinjektion und ein Testfall ohne Fehlerinjektion ausgeführt, so wird einmal der Wert 1 und einmal der Wert 0 von WriteRead() zurückgegeben. Zudem werden alle Zweige des Testobjekts durchlaufen, d. h. man erhält durch diese zwei Testfälle hundert Prozent Zweigüberdeckung (Branch Coverage).

Der Hauptnachteil der herkömmlichen Methode ist, dass der ursprüngliche Quellcode zum Zweck der Fehlerinjektion erweitert werden muss. Das bedeutet insbesondere, dass man sicherstellen muss, dass die Präprozessorkonstante UNIT_TEST beim Erzeugen der Software für das Endprodukt nicht definiert ist, denn sonst wäre die Fehlerinjektion auch im ausgelieferten Produkt enthalten, was fatal wäre. Ferner ist der Aufwand zur Programmierung der Fehlerinjektion zu bedenken, insbesondere wenn kompliziertere Injektionen erfolgen sollen.

Die Erweiterung des Quelltextes für die Fehlerinjektion erhöht natürlich auch den Aufwand für Reviews. Nicht zu unterschätzen ist auch der Verwaltungsaufwand für möglicherweise zahlreiche Fehlerinjektionen an unterschiedlichen Stellen im Programm, jede aktiviert und gesteuert durch möglicherweise unterschiedliche Präprozessorkonstanten und zusätzliche Variablen.

Seite 2 von 3

1. Wie robust ist mein Code?
2. Gezielte Fehlerinjektion
3. Fehlerinjektion ohne Änderung am Quellcode

Auf Facebook teilenAuf Twitter teilenAuf Linkedin teilenVia Mail teilen

Das könnte Sie auch interessieren

DER Branchentreff der Safety & Security-Experten
Safety trifft auf Security
Reinigung für den Code
Statische Code-Analyse mit Security-Berichten
Statische Tests automatisieren

Verwandte Artikel

Hitex GmbH