Wer sich mit der Sicherheit im IoT befasst, hat aktuell kaum Langeweile. Hier sind noch viele Fragen offen, wie die Daten und Anwendungen gegen Missbrauch hinreichend geschützt werden können. Denn klar ist, dass die in der IT bewährten Ansätze mit Firewalls und Malware-Scannern bei kleinen Embedded-Geräten nicht funktionieren. Gleichzeitig werden die IoT-Geräte für viele Unternehmen immer kritischer, sie bilden zunehmend die Basis der Geschäftsmodelle. Die Hersteller sind also gefordert, ihre Embedded-Systeme von Anfang an möglichst sicher zu entwickeln, so wenig Angriffsfläche wie möglich und ein Maximum an Zuverlässigkeit zu bieten.
Sicher durch Testing. Für sichere Embedded-Geräte ist das Testing ein wichtiger Baustein innerhalb des Entwicklungsprozesses, um Fehler möglichst frühzeitig zu erkennen. Zahlreiche Normen für die Entwicklung sicherheitskritischer Software stellen deswegen klare Anforderungen, welche Testmethoden anzuwenden und welche Testabdeckungen zu erzielen sind. Hierbei gibt es unterschiedliche Komplexitätsstufen, die je nach Kritikalität des Systems genutzt werden. Die wichtigsten sind:
Statement Coverage (Anweisungsüberdeckung): Es wird ermittelt, welche Anweisungen durch die Tests ausgeführt wurden. Toter Code kann dabei ebenso erkannt werden wie Anweisungen, für die noch kein geeigneter Test erstellt wurde.
Branch Coverage (Zweigüberdeckung): Sie erfasst, ob alle Programmzweige durchlaufen wurden. Dieses ist die Mindestanforderung, die an das Testing gestellt werden sollte. Branch Coverage ist zudem mit einem vertretbaren Aufwand realisierbar.
MC/DC (Modified Condition/Decision Coverage): MC/DC ist die höchste in den Normen geforderte Testabdeckungsstufe und sehr komplex. Um den Testaufwand zu minimieren, werden alle atomaren Bedingungen einer zusammengesetzten Bedingung herangezogen. Für jede der atomaren Bedingungen wird ein Testfallpaar getestet, das zur Veränderung des Gesamtergebnisses der zusammengesetzten Bedingung führt, wobei sich jedoch nur der Wahrheitswert der betrachteten atomaren Bedingung ändert. Hierbei muss der Wahrheitswert der anderen atomaren Bedingungen konstant bleiben.
Code wächst durch InstrumentierunG. Zur Ermittlung der Testabdeckung dienen Code Coverage Analyser. Diese arbeiten in der Regel nach dem gleichen Prinzip: Sie instrumentieren den Code vor der Übergabe an den Compiler, ergänzen Ihn also mit Zählern für die gewünschten Testebenen. Die Zähler werden meist als globale Arrays abgelegt. Wann und wie diese Zähler dann verändert werden, hängt von der geforderten Code-Coverage-Stufe ab. Der vor allem bei kleinen Targets unangenehme Nebeneffekt der Instrumentierung ist, dass der Code umfangreicher wird. Dadurch werden sowohl RAM als auch ROM zusätzlich belastet. Bereits eine kleine in C geschriebene While-Bedingung kann so deutlich wachsen. Aus der Ausgangsstruktur
while (! b == 0 )
{
r = a % b;
a = b;
b = r;
}
result = a;
wird durch die Instrumentierung – in diesem Fall mit dem Code-Coverage-Werkzeug Testwell CTC++ – folgende Struktur:
while ( (( ! b == 0 ) ? (ctc_t[23]++, 1) : (ctc_f[23]++, 0)) )
r = a % b ;
a = b ;
b = r ;
result = a ;
Bei Server- oder PC-Anwendungen kann dieser Effekt vernachlässigt werden. Bei Embedded-Geräten hingegen oft nicht, da die Hardware-Ressourcen aus Kostengründen oft sehr knapp kalkuliert sind. Hier ist darauf zu achten, einen Code Coverage Analyser mit einem vergleichsweise geringen Instrumentierungs- Overhead zu nutzen, da die Zähler sonst schnell die Grenzen des verfügbaren Speichers sprengen. Das gilt insbesondere, wenn sehr anspruchsvolle Testabdeckungsstufen wie MC/DC erforderlich sind. Spezielle, auf Embedded-Systeme optimierte Analyser wie Testwell CTC++ von Verifysoft Technology sind hierfür die richtige Wahl.
Partielle Instrumentierung. Sollte das Code-Coverage-Tool einen zu hohen Instrumentations-Overhead haben, kann diese Hürde beim RAM mit der partiellen Instrumentierung umgangen werden. Dabei werden nur kleine Ausschnitte des zu testenden Programms instrumentiert und getestet. Der Test wird nacheinander mit allen Programmteilen wiederholt, die daraus gewonnenen Daten werden zu einem Gesamtbild zusammengefügt. Dadurch kann die Testabdeckung für das vollständige Programm ermittelt werden. Ein anderer Ansatz auf kleinen Targets ist, die Größe der Zähler zu beschränken. Normalerweise arbeiten Code-Coverage-Werkzeuge mit 32-Bit-Zählern. Diese können zumindest theoretisch auf 16 oder 8Bit reduziert werden. Hierbei sollte man aber Vorsicht walten lassen, denn unter Umständen können die Zähler dann überlaufen. Die gewonnenen Daten müssen also mit großer Sorgfalt interpretiert werden. In extremen Fällen können die Zähler zudem auf einzelne Bits gesenkt werden. Diese Bit-Coverage kann z.B. dann sinnvoll sein, wenn es nicht relevant ist, wie oft ein Programmabschnitt durchlaufen wurde.
Auch die gewählte Coverage-Stufe beeinflusst die Anforderungen an den verfügbaren RAM. Der zusätzlich benötigte Platz im ROM hingegen lässt sich kaum begrenzen. Zur Erfassung der Code Coverage ist eine kleine Bibliothek erforderlich, die u.a. für die Übertragung der Zählerstände an einen Host zuständig ist. Nicht zu vergessen: Neben dem Speicher belastet die Instrumentierung auch den Prozessor im Target. Hierdurch kann es vorkommen, dass ein definiertes Timing nicht mehr eingehalten wird. Besonders wenn die CPU bereits eng am Limit arbeitet, können fehlerhafte Abläufe auftreten. Die Buskommunikation ist dafür besonders anfällig. Hier sollte der Tester aufmerksam den Ablauf überwachen und die Ergebnisse sorgfältig prüfen. Leistungsfähige Code-Coverage-Tools sind jedoch in der Lage, den Speicherbedarf für die Instrumentierung sowie die Änderungen des Laufzeitverhaltens relativ gering zu halten.