Automotive-Systeme sind ein Paradebeispiel für Embedded-Systeme, die sowohl funktionale Sicherheit als auch Cybersecurity erfordern. Die Entwicklungszeit für Kfz-Steuergeräte ist in der Regel länger als bei standardmäßigen Softwareanwendungen, aber der Markt drängt auf eine schnellere Bereitstellung, während gleichzeitig die Security-Anforderungen steigen (Bild 1).
Steigende Security-Anforderungen für Automotive-Systeme
Je später im Produkt-Entwicklungszyklus ein Fehler entdeckt wird, desto höher sind die Kosten für dessen Behebung (Bild 2). Deshalb sollen Softwareentwicklungsrichtlinien, sicherheitsspezifische Entwicklungsökosysteme und statische/dynamische Analysetools zusammen mit strengen Überprüfungsanforderungen die Zuverlässigkeit von Software verbessern und potenzielle Gefahren vermeiden. Diese Faktoren erhöhen die Komplexität und die Kosten der Softwareentwicklung. Die Programmiersprache Rust kann jedoch die meisten speicherbezogenen Fehler zur Kompilierzeit erkennen. Die Sprache verfügt über Null-Kosten-Abstraktionen; alle Speicherprüfungen erfolgen zur Kompilierzeit, sodass sich Speichersicherheit ohne Verwendung einer automatischen Speicherbereinigung oder einer Laufzeitumgebung implementieren lässt.
Rust-Ökosystem für Embedded-Systeme
Die offiziellen Entwicklungswerkzeuge von Rust unterstützen die meisten gängigen CPU-Architekturen wie ARM, Mips oder Risc-V mit Targets, die auch eine Bare-Metal-Entwicklung ermöglichen. Die Entwicklung von Embedded-Systemen erfordert aber mehr als nur einen Compiler – es braucht zusätzliche Unterstützung wie CPU-Boilerplate, Abstraktionen der Kernarchitektur, Start-up-Code, Hardware-Abstraktionsschichten (HAL), Treiber und Echtzeitbetriebssysteme.
Die Rust Embedded Working Group entstand, um die Nutzung von Rust in ressourcenbeschränkten Umgebungen und nichttraditionellen Plattformen zu erforschen. Die Beiträge der Gruppe umfassen Cortex-M Crates, Low-Level-CPU-Zugriff, minimale Laufzeit/Start-up, Semihosting-Abstraktion, Heap-Allokator und die laufende Arbeit an embedded-hal (hardware abstraction layer for embedded systems) zur Entwicklung plattformunabhängiger Treiber.
Für den Lowlevel-Hardware-Zugriff auf speicherbelegte CPU- oder Peripherieregister kommen PACs (peripheral access crates) zum Einsatz, im Allgemeinen generiert mit Tools wie svd2rust oder ChipTool aus CMSIS-SVD-Dateien (system view description). Obwohl es viele PACs auf crates.io gibt, werden die meisten von ihnen wie auch die Rust-Treiber und HALs von der Embedded Rust-Community entwickelt, gepflegt und unterstützt.
Infineon unterstützt Rust
Bei Infineon gibt es eine unternehmensinterne Community von Rust-Nutzern; offiziell setzt das Unternehmen die Programmiersprache seit 2021 ein, seit Beginn der Entwicklung von ARM-Cortex-M-basierenden PSoC-6-Bausteinen. Der Geschäftsbereich Connected Secure Systems hat sich seither an verschiedenen Initiativen beteiligt, um die Nutzung von Rust bei Infineon zu fördern, und hat erst kürzlich damit begonnen, Crates für PSoC 6 und Security Controller zu erstellen, als Unterstützung für neue Entwicklungen in Rust auf der Legacy-C-Codebasis.
Innerhalb des Geschäftsbereichs Automotive Microcontroller von Infineon begann die Rust-Entwicklung 2022 mit den ARM-Cortex-M-basierenden Traveo T2G-Automotive-Mikrocontrollern (Bild 3). Die Entwicklung für Traveo erfolgte mit der Standard-Rust-ARM-Toolchain mit Cortex-M-Targets. Die PACs für Traveo T2G-MCUs sind auf crates.io verfügbar ebenso wie ein detailliertes Anwenderhandbuch mit Codebeispielen auf GitHub. Eine Unterstützung für Automotive PSoC-4-Bausteine ist ebenfalls in Planung.
Eigener Compiler für Aurix-Mikrocontroller
Für Aurix, das keine offizielle Rust-Toolchain unterstützt, war jedoch ein eigener Compiler erforderlich – der kam schließlich von HighTec EDV-Systeme, dem Tool-Partner von Infineon. HighTec stellt einen automotive-grade Multi-Architektur-C/C++-Compiler her, der auf der Open-Source-Compilertechnologie LLVM basiert und darauf zugeschnitten ist, die Leistung der Aurix-TriCore-Architektur bestmöglich zu nutzen sowie die Sicherheitsanforderungen von Automotive- und Industrieanwendungen zu erfüllen.
Das Unternehmen hat den ersten Rust-Compiler für Aurix TC3xx und die kommenden TC4xx-MCUs vorgestellt, der erweiterte Speichersicherheit, Gleichzeitigkeit, Performance und C/C++-Interoperabilität bietet. Die vollständige Entwicklungsplattform umfasst den Compiler, das Cargo-Build-System, Rust-Bibliotheken, Hardware Abstraction Layer und das Board Support Package für TC3xx (Bild 4). Für die Entwicklung sicherheitskritischer Automotive-Steuergeräte ist eine sicherheitszertifizierte Toolchain obligatorisch, deshalb hat HighTec eine nach ISO 26262 ASIL D zertifizierte Methodik zum Qualifizieren von C/C++-Tools entwickelt. Die Methodik wird in naher Zukunft angepasst, sodass bald ein sicherheitszertifizierter Rust-Compiler verfügbar sein wird.
Infineons Geschäftsbereich Automotive Microcontroller arbeitet außerdem in verschiedenen Arbeitsgruppen mit Tool- und Middleware-Partnern an der Standardisierung von PACs für Aurix, die als Basis für die zukünftige Entwicklung von sicherheitszertifizierten Hardware-Abstraktionsschichten und Treibern dienen sollen.
Rust für Embedded-Systeme
Rust ist eine Multiparadigmen-Programmiersprache, für die Sicherheit, Gleichzeitigkeit und Performance vorrangig sind. Speichersicherheitsfunktionen machen Rust für Embedded-Systeme zu einer interessanten Alternative zu C oder C++. Hauptvorteil ist dabei die zur Kompilierzeit garantierte Speichersicherheit, die fremde oder unbeabsichtigte Speicherzugriffe praktisch unmöglich macht. Zu den wichtigsten Sicherheitsmerkmalen von Rust gehören:
Referenzen & Ausleihen
Das Referenzen- und Ausleihen-Modell (borrowing) verwaltet Ressourcen und erreicht Speichersicherheit ohne Einsatz einer automatischen Speicherbereinigung (garbage collector). Auch beugt das Modell häufigen Problemen wie Nullwert-Dereferenzierungen vor und verbessert die Zuverlässigkeit und Robustheit des Codes durch umfassende Fehlererkennung zur Kompilierzeit.
Unveränderlichkeit
Alle Variablen sind standardmäßig unveränderlich. Das verhindert unbeabsichtigte Änderungen und trägt zu den Sicherheitsgarantien der Sprache sowie deren Fähigkeit bei, potenzielle Probleme zur Kompilierzeit zu erkennen. Veränderliche Referenzen ermöglichen die temporäre Ausleihe von Daten (borrowing) innerhalb eines begrenzten Bereichs.
Lebensdauer
In Rust definiert die Lebensdauer die Dauer oder den Umfang, in dem ein Wert oder eine Referenz gültig ist. So ist sichergestellt, dass geliehene Referenzen die referenzierten Werte nicht überdauern und Fehler wie hängende Referenzen (dangling references) oder die Verwendung nach Freigabe (use after free) lassen sich verhindern. Während die Lebensdauern meist vom Compiler abgeleitet werden können, sind manchmal explizite Annotationen erforderlich, um die Referenz-Beziehung zu verstehen.
Typsicherheit
Rust ist statisch typisiert und zur Kompilierzeit werden Funktionssignaturen, Variablenzuweisungen, Parameter und Ausdrücke streng überprüft. Dies hilft dem Compiler, Typkonvertierungsfehler zur Kompilierzeit zu erkennen.
Das starke Typinferenzsystem kann Variablentypen ohne explizite Annotationen ableiten und erlaubt es, Variablentypen in der Deklaration wegzulassen. Die Typsicherheitsfunktionen ermöglichen auch die Verwendung von Type State Programming, das korrekte Programmzustände zur Kompilierzeit erzwingt und die Vorhersagbarkeit des Systems verbessert.
Was ist Unsafe Rust?
Die Speichersicherheitsfunktionen von Rust sind standardmäßig aktiviert, außer in unsafe Blöcken: Hier lassen sich bestimmte Regeln umgehen. Bei einer Programmiersprache, die für Speichersicherheit steht, mag das unlogisch erscheinen. Aber Rust unterstützt auch eine System- und Low-Level-Programmierung, bei der der Zugriff auf die Hardware über speicherabgebildete Register notwendig sein kann, was wiederum die Verwendung von unformatierten Zeigern (raw pointers) erfordert. Die statische Analyse ist jedoch konservativ und kann zuweilen korrekten Code als Fehler kennzeichnen, was zeigt, warum Unsafe Rust notwendig ist. Kleine, spezifische unsafe Blöcke erleichtern das Audit und die Überprüfung von Quellcode. Das kommt auch der Rückverfolgbarkeit zugute, da mögliche Speicherfehler nur von unsafe Blöcken stammen können.
Interoperabilität von Rust und C/C++
Die Programmiersprache unterstützt die bidirektionale Interoperabilität mit C und C++. Für C++ muss die C ABI (Application Binary Interface) verwendet werden. Das fördert die Wiederverwendung von bestehendem Code und erleichtert den schrittweisen Wechsel zu Rust, insbesondere bei der Arbeit mit bereits qualifizierten Softwarekomponenten. Allerdings lassen sich C-Funktionen aus unsafe Blöcken heraus aufrufen, da C-Code unsicheres Verhalten aufweisen kann.
Safety, Security und Robustheit
Highend-Embedded-Systeme arbeiten zunehmend mit Rust. Aber mittelgroße bis kleine oder tief eingebettete Systeme sind in der Regel ressourcenbeschränkt, stellen Echtzeitanforderungen und haben oft zusätzliche strenge Security- oder Safety-Anforderungen, etwa für IoT-Geräte, Smartcard-Anwendungen, Automobil- oder Industriesysteme. Die meisten dieser Applikationen sind in C entwickelt; für hochgradig sicherheitskritische Systeme wird Ada/Spark verwendet.
Obwohl die Sicherheit des Gesamtsystems über den Umfang eines einzelnen Aspekts des Systems hinausgeht, ist die Robustheit der Software von entscheidender Bedeutung für die Systemsicherheit. Die größte Bedrohung für die Robustheit der Software sind speicherbezogene Probleme. Deswegen sind statische Speicherprüfungen, Speicherisolierung zwischen verschiedenen Softwarekomponenten und sichere Kodierungsverfahren eine grundlegende Voraussetzung für derartige Systeme.