In der letzten Dekade haben sich die Rückrufe von fehlerhaften Medizingeräten beinahe verdoppelt. Laut FDA (Federal Drug Administration, USA) beruhen vier von zehn Ausfällen auf einem Softwareproblem. Grund genug, der Softwarequalität mehr Aufmerksamkeit zu widmen. Dazu kommt, dass Medizingeräte zunehmend vernetzt werden, also muss sich sicherheitskritische Software auch gegen gezielte Angriffe wehren. Oft basiert die Ausrüstung auf veralteten und damit verwundbaren Windows-Betriebssystemen und weil Hersteller und Betreiber die regulatorischen Anforderungen scheuen, aktualisieren sie nicht einmal die Antivirus-Software.

Bewährte Methoden wie Codierungsstandards sind ein guter Ansatz, um das Risiko für  lebensbedrohende Softwarefehler in Medizingeräten zu verringern.

Bewährte Methoden wie Codierungsstandards sind ein guter Ansatz, um das Risiko für lebensbedrohende Softwarefehler in Medizingeräten zu verringern.Gramma-Tech

Eine weitere Risikoquelle ist Code von Drittlieferanten. Weil dieser nicht vom Gerätehersteller stammt, unterliegt er auch nicht dessen direkter Kontrolle. Ähnliches gilt für Bibliotheken und Betriebsystemfunktionen, in denen Fehler schlummern könnten.

Programmierstandards und statische Code-Analyse

Zu den Best Practices der Branche gehört, sich an Codierungsstandards für sicherheitskritische Software zu orientieren. FDA und EC (European Community) verlangen einen dokumentierten Software-Entwicklungsprozess (IEC 62304). Obwohl die FDA nicht ausdrücklich auf Programmierstandards besteht, übernehmen sie viele Medizingerätehersteller freiwillig als Teil ihrer Software-Qualitätsinitative. Manche Firma betrachtet Software aber als Add-on und überlässt es den Entwicklungsteams, sie nach ihrer eigenen Methodik zu implementieren. Dabei wären viele Best Practices mit nur minimalem Aufwand und Kosten verbunden. Ein solcher Standard sind die „Power of 10“-Regeln für sicherheitskritisches Programmieren. Entwickelt vom NASA Jet Propulsion Laboratory für Raumfahrt-Software, sind diese zehn einfachen, leicht zu merkenden Programmierregeln so konzipiert, dass statische Analysetools einen Verstoß automatisch entdecken.

Auf einen Blick

Der kleinste Fehler beim Schreiben von Software kann ein Safety- oder Security-Risiko darstellen. Programmierstandards senken hier das Risiko deutlich. Moderne Tools unterstützen die Entwickler dabei, sich an die Standards zu halten. Am Beispiel eines sehr simplen Buffer-Overflows erklärt der Artikel, warum Taint-Analyse bei der statischen Codeanalyse hilfreich ist.

Moderne statische Quellcodeanalyse kann sehr effektiv sicherstellen, dass Code konform zu einem Programmierstandard ist. Zusätzlich findet sie viele ernste Fehler wie Buffer-Overruns, Null-Pointer-Dereferenzierungen, Race-Conditions und Ressourcen-Lecks. Diese Bugs treten auf, wenn Entwickler gegen fundamentale Regeln der Sprache verstoßen oder APIs falsch verwenden. Statische Analyse findet auch Widersprüche im Code, die andeuten, dass der Programmierer wichtige Teile des Codes nicht verstanden hat.

Herausforderung Third-Party-Code

Fast 30 % des Codes in Embedded-Anwendungen wie Medizingeräten besteht aus kommerzieller Fremdsoftware im Binärformat. Da der Entwickler keinen Zugriff auf den Quellcode hat, entzieht sich dieser Teil der statischen Analyse. Besonders gefährdet sind Systeme, die sich aus Code von mehreren Anbietern zusammensetzen. Sicherheitslücken an den Schnittstellen zwischen Modulen haben ihren Ursprung oft in Verständigungsproblemen, zum Beispiel wenn zwei Entwickler eine Aussage in der Spezifikation unterschiedlich interpretieren. Sind Teile des Quellcodes nicht verfügbar, eignen sich Mischformen der statischen Analysetools, die Quellcodeanalyse mit Binärcodeanalyse kombinieren.

Zu Best Practices bei sicherheitskritischer Software gehört, Daten und Eingabewerte aus potenziell gefährlichen Kanälen immer als risikoreich einzustufen, bis deren Gültigkeit geprüft wurde. Im Sprachgebrauch des sicheren Programmierens heißen ungeprüfte Eingabewerte „tainted“. Ein Check, ob ein Programm solche Daten korrekt verarbeitet, kann sehr schwierig sein. Man muss den Verlauf der Daten durch die Code-Struktur verfolgen. Sogar bei relativ kleinen Programmen ist das sehr mühsam, und für die meisten Echtzeitanwendungen manuell nicht mehr durchführbar.

Buffer-Overrun et al

Wenn Angreifer die Eingabewerte eines Geräts manipulieren können, denn besteht die Gefahr, dass sie per speziell konstruierter Daten eine Sicherheitsschwachstelle ausnutzen. Zu den typischen Fehlerarten aufgrund von Tainted-Daten zählen Buffer-Overruns, SQL-Injektion, Befehl-Injektion, Cross-Site Scripting, Zahlenüberläufe und Path-Traversal. Gerade der berüchtigte Buffer-Overrun verdient eine ausführlichere Beschreibung: Ihn gibt es in verschiedenen Ausprägungen. Im nachfolgenden Code liegt der angegriffene Buffer auf dem Stack:

void config(void)
{
   char buf[100];
   int count;
   …
   strcpy(buf, getenv(„CONFIG“));
   …
}

Die Eingabe kommt von außen über einen Aufruf der Funktion „getenv“, die den Wert der Umgebungsvariablen „CONFIG“ abruft. Der Programmierer erwartet, dass der Wert der Umgebungsvariablen in den Buffer passt, stellt das aber nirgendwo sicher. Erhält der Angreifer die Kontrolle über den Wert dieser Umgebungsvariablen, dann kann er auch mehr als 100 Zeichen angeben und damit einen Buffer-Overrun auslösen. Bei „buf“ handelt es sich um eine automatische Variable, die der Compiler auf dem Stack hinterlegt. Darum wird jedes Zeichen nach den ersten 100 auf die Teile des Programm-Stacks außerhalb der Grenze von „buf“ geschrieben. Dort liegt, je nach dem wie der Compiler den Stack organisiert, zum Beispiel die Variable namens „count“. Der Angreifer kann also indirekt den Wert dieser Variablen verändern – je nach Bedeutung dieser Variable kann das schon enormen Schaden anrichten.

Beispiel einer Buffer-Overrun-Schwachstelle. Das Tool markiert das Resultat seiner Taint-Analyse durch Unterstreichungen.

Beispiel einer Buffer-Overrun-Schwachstelle. Das Tool markiert das Resultat seiner Taint-Analyse durch Unterstreichungen.Gramma-Tech

Zusätzlich liegt auf dem Stack aber auch die Rücksprungadresse der aktuellen Funktion. Wenn der Angreifer diese überschreibt, dann kann er den Ablauf der Software verändern und zum Beispiel direkt an die Adresse springen, an der „buf“ liegt. Wenn dann der Inhalt von „buf“ vom Angreifer so gewählt ist, dass es sich um ausführbaren Binärcode handelt, hat er die volle Kontrolle über den aktuellen Prozess erlangt.

Taint-Analyse

Die gefährlichsten Input-Kanäle sind die, die ein Angreifer kontrolliert. In einer Umgebung, in der Angreifer keinerlei Kontrolle über die Umgebungsvariablen haben, ist diese Schwachstelle vielleicht nicht ausnutzbar. Dennoch ist der Code risikoreich und bleibt ein Haftungsproblem. Außerdem könnte ein Entwickler versucht sein, diesen Code in einem anderen Programm wiederzuverwenden, wo er nicht mehr sicher wäre. In unserem Beispiel kommt der Input aus der Umgebung, aber der Code wäre genauso gefährlich, käme der String aus einer anderen Input-Quelle wie dem Dateisystem oder einem Netzwerkkanal. Die manuelle Suche nach Programmierfehlern, die für Tainted-Werte anfällig sind, gestaltet sich extrem zeitaufwändig – das macht die Automatisierung zum besten Ansatz.

Die Taint-Analyse zeigt Entwicklern, wie der Code riskante Daten von einem Programmteil zum nächsten weiterreicht. Ein modernes statisches Analysetool zur Taint-Analyse erklärt die Abläufe, damit der Entwickler mögliche Angriffsflächen in seinem Code erkennen und verstehen sowie Fehler aufspüren und korrigieren kann. Manche Tools führen die Taint-Analyse dynamisch aus. Ein statischer Ablauf hat aber wichtige Vorteile – nicht zuletzt um sicherzugehen, dass alle Ausführungspfade sicher sind, und nicht nur diejenigen, für die Testfälle vorliegen.

Gute Routine

Entwickler von Medizingeräte-Software verantworten Code, von dem die Gesundheit und Sicherheit der Patienten abhängen. Programmierstandards für sicherheitskritische Software haben sich in anderen Branchen bewährt und sollten auch für Medizingeräte zwingend sein. Einen guten Einstieg bieten die NASA/JPL „Power of Ten“, die bewährte Verfahren aus mehreren Standards bündeln. Moderne statische Analysetools finden auch Fehler, die nur unter ungewöhnlichen Umständen auftreten, und zwar sehr früh in der Entwicklung. Das spart Zeit und Kosten und erhöht die Qualität.