Komplexität beim embedded Software Design

Komplexität beim embedded Software Design

‚Best Practises‘ für statische Analysetools

Embedded-Entwickler stehen unter größerem täglichen Druck als Programmierer in anderen Industrien. Während die meisten Entwicklungsteams von weniger Komplexität durch Cloud-basierter, homogener Hardware und Virtualisierung profitieren, sieht es bei embedded Entwicklern ganz anders aus: Sie müssen Anwendungen programmieren, die durchgängige und vorhersagbare Leistung über eine steigende Anzahl an heterogenen Hardware-/Prozessor-Konfigurationen bringen, die immer neue Features beinhalten, und stehen dabei vor vielen Herausforderungen.

Beispiel einer Befehls-Injektions-Schwachstelle im Code (Bild: GrammaTech Inc.)

Beispiel einer Befehls-Injektions-Schwachstelle im Code (Bild: GrammaTech Inc.)


Außer Haus produzierter Code birgt besondere Risiken aufgrund verschachtelter Supply Chains, wiederholter Unzugänglichkeit zum Source Code der Library, und der Angst vor Insidern, die heimlich Schwachstellen einschleusen. Embedded Software Entwickler müssen potenzielle Parallelitätsprobleme (Concurrency) kennen, die zu unvorhersehbarem Verhalten führen oder unbeabsichtigte Angriffsflächen bieten können. Die zunehmende Netzwerkfähigkeit von embedded Systemen bedeutet eine große Herausforderung. Sei es Code für Netzwerkrouter, medizinische Geräte oder Home-Security Systeme – jedes Gerät mit Netzwerkanschluss bietet eine Angriffsfläche für raffinierte Cyber Attacken. Leistungskritische Software in Avionik, Automobilen und Konsumgeräten unterliegt einer steigenden Anzahl an Standards für Codequalität und Sicherheit, wie DO-178B/C, MISRA und ISO. Nach Einschätzungen der Industrie wachsen die Code-Basen von embedded Anwendungen um beinahe 30% pro Jahr. Embedded Entwicklungsteams nutzen eine Mischung aus Wasserfall- und agilen Methodiken. Unabhängig von der Coding Philosophie steigt für jedes Teammitglied der Zeitdruck nach schnelleren Zyklen für neue Features, zur Reaktion auf Kundenwünsche, und um bekannte Fehler zu korrigieren. Dennoch kann der Wunsch nach Schnelligkeit nicht auf Kosten der Code-Entwicklung gehen.
 Vereinfachte Version des Codes mit dem Infinite Loop Bug (Bild: GrammaTech Inc.)

Vereinfachte Version des Codes mit dem Infinite Loop Bug (Bild: GrammaTech Inc.)

Risiken durch embedded Sprachen

C und C++, die gängigsten Sprachen für embedded Software, beinhalten besondere Risiken für die Entwickler, denn Mängel und Unklarheiten in der Sprach-Spezifizierung können unerwünschtes und unerwartetes Verhalten in der Ausführung erzielen. Aber eine embedded Anwendung muss in unterschiedlichen Hardware/Firmware-Umgebungen laufen, so dass ein Test auf unerwartetes Verhalten schwierig sein kann. Wegen all dieser Faktoren haben embedded Entwicklungsteams formalisierte Codier- und Testpraktiken übernommen. Zusätzlich zum standardmäßigen QA-Test können automatisierte Tools den Quellcode und die Ausführung einer Anwendung schon früh in der Entwicklung prüfen, wenn Fehler einfacher zu korrigieren sind, und anspruchsvolle Berichte erstellen, um die Einhaltung von Standards so effizient wie möglich zu gestalten.

 Beispiel eines Type Mismatch (Bild: GrammaTech Inc.)

Beispiel eines Type Mismatch (Bild: GrammaTech Inc.)

Der Wert einer frühen, automatisierten Fehlererkennung

Unabhängig davon, welchen Code embedded Entwicklungsteams schreiben, ist die Fehleridentifizierung zu einem frühen Zeitpunkt wertvoll für die Ausführung und Leistung des Codes. Von allen Tools und Strategien zur Verbesserung des Entwicklungsprozesses von embedded Software bietet die automatisierte Quell-/Binary-Analyse mit die höchsten ROI-Werte. Sie ist unbestritten effizienter als ein manueller Ablauf. Ihr größter Wert ist allerdings, was sie zu welchem Zeitpunkt findet. Das belegt eine Studie des NIST (National Institute of Standard Technology) von 2002: Demnach braucht die Beseitigung eines einzelnen Fehlers in der Entwicklung rund 5 Stunden, während die Entfernung eines Fehlers in einer Produktionsumgebung durchschnittlich 15 Stunden verschlingt. Der nachgewiesene Wert einer frühen Fehlererkennung im Software-Entwickungszyklus und die Belastungen für die embedded Programmierteams untermauern die automatisierte Codeanalyse als eine der kosteneffizientesten Investitionen für Unternehmen, um Zyklen schneller freizugeben und die Produktivität der Entwickler zu steigern. Denn: Wird die automatisierte Codeanalyse mit dem ALM Prozess integriert, formalisiert sie die Identifizierung von schwer auffindbaren Fehlern und/oder Schwachstellen und fügt diese dem Bug-Tracking-System hinzu, wo sie priorisiert und eliminiert werden können. Die Erfahrung von GrammaTech bei der Entwicklung von embedded Software stammt zum Großteil von CodeSonar, dem Flaggschiff-Produkt zur statischen Codeanalyse. Es hat mehr als 500 Mio. Codezeilen verarbeitet, um die Leistung von Fehler-intoleranten Geräten wie den NASA Mars Curiosity Rover zu schützen, oder des U.S. Justizministeriums, um nennenswerte Ausfälle von Automotive Systemen zu überprüfen. Durch die Zusammenarbeit mit weltweit führenden Geräte-Herstellern und U.S. Regierungsagenturen, die hochsicherheitsfähigen Code mit Null-Fehler-Toleranz entwickeln und testen, konnte sich das Entwicklungsteam von GrammaTech ein tiefes Verständnis der für moderne embedded Software maßgeblichen Qualitätsanforderungen aneignen. GrammaTech empfiehlt gewisse ‚Schlüssel-Anforderungen‘ für automatisierte Code-Analysetools, einschließlich der nachfolgendgenannten. Jede davon enthält ein Beispiel mit echtem Code, der in CodeSonar analysiert wurde:

 Unstimmige Methoden der Ansprache einer Klasse an ein Feld (Bild: GrammaTech Inc.)

Unstimmige Methoden der Ansprache einer Klasse an ein Feld (Bild: GrammaTech Inc.)

Analyse von Binärdateien

Obwohl extern geschriebener Code beinahe in jeder Anwendung zum Einsatz kommt, haben Entwickler oft keine Möglichkeit für dessen Analyse, weil sie keinen Zugriff auf seinen Quellcode haben. Ohne diesen können automatisierte Analysetools nur Vermutungen betreffend Qualität und Sicherheit des externen kommerziellen oder Open Source Codes anstellen. Nach Schätzungen von VDC Research handelt es sich tatsächlich bei etwa 30% des Codes in embedded Anwendungen um externe kommerzielle Software – darum ist Quellcode in der embedded Entwicklung oft nicht verfügbar. Ein automatisiertes Tool, das Binärdateien analysiert, kann diesen gefährlichen ‚Blind Spot‘ in Anwendungen ausschalten. Bild 2 enthält ein Beispiel einer Befehls-Injektions-Schwachstelle – sie wurde in ein Programm namens UnrealIRCd (sh. CVE-2010-2075) eingeschleust. Die Zeile 5602 ist ein Aufruf an die System() Funktion, deren Parameter von Daten aus einer Netzwerkverbindung stammen. CodeSonar fand den Fehler sowohl im Quellcode als auch im kompilierten Code.

‚Native‘ Support für Standards

Weltweit setzen sich Standards wie MISRA, DO-178B oder ISO 26262 durch. Diese Standards kommen oft in Kombinationen in embedded-intensiven Industrien wie Automotive, Aerospace, Medizingeräte, Industriesteuerungen zum Einsatz. Unternehmen in diesen Märkten müssen gerüstet sein, um nicht nur Verstöße der vordergründigen syntaktischen Regeln, sondern auch aus undefiniertem Verhalten entstandene ernste Bedrohungen zu identifizieren, wie es der MISRA C:2012 Standard vorschreibt. Während einige dieser Ereignisse durch Testläufe spezifiziert werden können, spüren nur die fortschrittlichsten statischen Analysetools die subtileren Ereignisse auf. Das Code-Beispiel in Bild 2 enthält eine vereinfachte Version des Codes mit dem Infinite Loop Bug, der im Microsoft Zune Player gefunden wurde. Am letzten Tag eines Schaltjahres wäre der Wert aus days genau 366, und der Loop würde nicht beendet. Bild 3 zeigt einen Type Mismatch: Die Variable tenths ist als vorzeichenbehaftete Integerzahl angegeben, aber der arithmetische Ausdruck führt zu einem vorzeichenlosen Wert. Solche Type Widersprüche sind durch den MISRA Standard verboten, weil sie zu stillen Trunkierungen und Overflows führen können, die u.U. unerwartetes Verhalten zur Folge haben.

Integrierte Sicherheit

Der Trend zur Vernetzung von embedded Systemen vergrößert die potenziellen Angriffsflächen für Hacker. Diese Attacken werden typischerweise ausgelöst, wenn ein unbefugter Anwender Daten über einen Eingangskanal (z.B. Netzwerkport) sendet. Programmierer können sich gegen diese Schwachstellen wehren, indem sie Eingangsdaten als potenziell gefährlich (Tainted) bewerten und sie validieren, bevor die Anwendung damit arbeiten darf. Weil der Datenfluss über die gesamte Anwendung manuell nachverfolgt werden muss, ist die Lokalisierung dieser gefährlichen Angriffe eine große Herausforderung. Ein automatisiertes Analysetool zur Untersuchung der Daten nach potenziell gefährlichem Input verkürzt die dafür aufzuwendende Zeit und steigert die Effektivität. Die Sicherstellung, dass importierte Daten nicht gefährlich sind, verringert letztendlich Risiko und gesetzliche Haftbarkeit, dass kompromittierte Software Endkunden erreicht.

Concurrency Checking

Das Programmieren von Multithreaded Anwendungen ist äußerst komplex und ermöglicht aufgrund der geteilten Daten schwer auffindbare Fehler. Der Trend zu Multicore Chip Design verlangt Multithreaded Software, um die Vorteile von moderner Hardware auszuschöpfen. Moderne statische Analyselösungen sprechen Concurrency Probleme in C und C++ Programmen an, aber speziell für Java gibt es bislang keine umfassende Lösung. Dabei wird Java als dritt-populärste Sprache für embedded Systeme heute von 28% der embedded Entwickler genutzt. Programmierer, die ihren Code nicht erfolgreich vor Fehlern wie Race Conditions und Deadlocks in C/C++ und Java schützen, werden unweigerlich mit Produktausfällen konfrontiert. Als Beispiel zeigt der Code in Bild 4 eine Unstimmigkeit bei den Methoden, wie eine Klasse ein Feld anspricht. Bei manchen Klassen sind die Zugänge synchronisiert, bei anderen nicht. Diese Fehlerart ist einfach zu übersehen, kann aber zu mysteriösen Symptomen führen, die schwer zu reproduzieren und festzustellen sind.

Tiefgreifende Analyse

C und C++, die gängigsten Sprachen für embedded Anwendungen, sind aufgrund einiger eigener Unzulänglichkeiten sehr anfällig für gefährliche Fehler. Trifft diese Eigenschaft auf eine steigende Vielfalt an Kombinationen aus Ziel-Hardware/-Firmware, kann das zu unberechenbarem Verhalten führen. Der Schlüssel für die Fehlerbehebung im Code ist die Auswahl eines Tools, das tief in die Codebasis eindringt und zugleich eine niedrige False-Positive Rate erzielt. Tools, die mit Hilfe eines einheitlichen Datenflusses und der symbolischen Ausführungsanalyse ein gesamtes Programm untersuchen, finden mehr potenzielle Fehler und Schwachstellen. Sie ermöglichen die Auslieferung von qualitativ höherwertigerer Software. Zudem sind Teams mit einem Tool mit modernen Darstellungsmöglichkeiten für Fehler wie z.B. Visualisierung besser gerüstet, um das genaue Code-Verhalten in ihren embedded Anwendungen nachzuvollziehen. Weil der Preis eines Fehlers bei embedded Geräten so hoch sein kann, sind Entwicklerteams historische ‚Early-Adopter‘ von modernen Lösungen zur Codeanalyse. Wegen der steigenden Anforderungen der embedded Industrie brauchen Entwickler diese automatisierten Analysetools dringender als zuvor, als Voraussetzung für einen erfolgreichen Job. Nur mit den fortschrittlichsten statischen Analysetools können sie die Herausforderungen durch neue regulatorische Normen erfüllen, die Auswirkungen durch sich explosionsartig vermehrenden Drittanbieter-Code verringern, und die allgegenwärtigen vernetzten Geräte und Multicore Prozessoren managen.

GrammaTech Inc.

Das könnte Sie auch Interessieren