Speichersicherheit in Embedded-Systemen Warum Embedded-Entwickler auf Rust vertrauen sollten

Von Caroline Guillaume, CEO TrustInSoft 6 min Lesedauer

Anbieter zum Thema

Rust gewinnt als Alternative zu C und C++ an Bedeutung. Die Sprache kombiniert vertraute Syntax mit strengen Regeln für Speicherzugriffe und hilft Entwicklern, typische Fehlerquellen in Embedded- und sicherheitskritischen Systemen zu vermeiden.

Trust in Rust: Gerade wenn es um Speicher- und Anwendungssicherheit geht, könnte es sich für Embedded-Entwickler lohnen, auf Trust als Programmiersprache der Wahl zu setzen.(Bild:  Rust Foundation)
Trust in Rust: Gerade wenn es um Speicher- und Anwendungssicherheit geht, könnte es sich für Embedded-Entwickler lohnen, auf Trust als Programmiersprache der Wahl zu setzen.
(Bild: Rust Foundation)

C und C++ sind nach wie vor die vorherrschenden Sprachen im Bereich Embedded-Systeme. Entwickler wissen jedoch, dass diese Sprachen Herausforderungen mit sich bringen, insbesondere aufgrund ihrer Handhabung von Pointern und ähnlichen Objekten, was zu Problemen hinsichtlich der Speichersicherheit führen kann.

Die neue Programmiersprache Rust bietet eine vergleichbare Syntax und verspricht größere Flexibilität und mehr Garantien für einen sicheren Betrieb. Rust enthält Elemente funktionaler Sprachen und anderer fortgeschrittener Konzepte, die in modernen Informatik-Lehrplänen vermittelt werden. Ein wesentlicher Grund für die zunehmende Verbreitung von Rust ist seine Fähigkeit, viele der speicherbezogenen Probleme zu lösen, mit denen C- und C++-Programmierer sowie deren Code-Anwender häufig konfrontiert sind. Diese Eigenschaften machen Rust zu einer strategischen Wahl für die Entwicklung neuer Softwaremodule in kritischen Bereichen wie Fahrzeug- und Industrieanwendungen.

Rust wird bereits breit unterstützt. Viele führende Softwareunternehmen haben sich aufgrund seiner Zuverlässigkeit und den Fokus auf Speichersicherheit zu Befürwortern von Rust erklärt. Ende 2022 erreichte Rust einen Meilenstein, indem es neben C als erste Sprache von der Linux-Community für die Entwicklung von Kernel-Modulen unterstützt wurde. Die Dynamik von Rust nahm mit der Veröffentlichung eines Berichts des US White House Office of the National Cyber Director weiter zu, in dem speichersichere Sprachen wie Rust die Cybersicherheit verbessern und zum Schutz vor Cyberangriffen beitragen.

Speicherprobleme

Ein wesentlicher Unterschied zwischen C/C++ und Rust liegt in der Behandlung von Pointern. Sie ermöglichen die ressourcenschonende Manipulation von Daten im Speicher. Das einfache Erstellen und Ändern von Pointern durch C- oder C++-Programme birgt jedoch Risiken.

So kann eine Funktion in einem Programm einen Pointer definieren, um auf Speicher in einem vom Betriebssystem zugewiesenen temporären Puffer zuzugreifen. Versuche, diesen Pointer zu verwenden, wenn ein anderer Teil des Codes den Speicher bereits freigegeben hat, führen wahrscheinlich zu einer Beschädigung der Daten. Kurz darauf kann es zu einem Programmfehler kommen. Ebenso sollte ein Null-Pointer, der definiert, aber nicht korrekt initialisiert wurde, bei seiner Verwendung einen Speicherzugriffsfehler erzeugen, was häufig zu einem Programmfehler führt.

Programme können auch Speicher nicht freigeben, wenn dieser mehr benötigt wird. In einem lang laufenden Programm führen solche Speicherlecks zu Systeminstabilität, wenn das Betriebssystem keinen freien Speicher für weitere Objekte findet.

Es gibt noch weitere Risiken: Liegt die Adresse eines Pointers außerhalb des für eine Datenstruktur oder einen Puffer zugewiesenen Speicherbereichs, führt dies häufig zu einer gefährlichen Datenbeschädigung. Hacker können diese Eigenschaft für Pufferüberlauf-Angriffe ausnutzen.

Eine sicherere Wahl

Rust-Nutzer können viele Sicherheitsprobleme vermeiden, indem sie die strengen Regeln und die integrierte Unterstützung für die Speicherzuweisung nutzen. Kompilierzeitprüfungen tragen dazu bei, das korrekte Verhalten von Referenzen zu gewährleisten. Rust bietet Datentypen, die pointerähnliche Funktionen bieten, aber durch Kompilierzeitprüfungen unterstützt werden. Diese Prüfungen helfen, die bei C-ähnlichen Pointern auftretenden Probleme zu vermeiden. Das von Rust unterstützte Speichermodell stellt auch sicher, dass temporäre Speicherstrukturen sicher gelöscht werden, sobald sie vom Programm nicht mehr benötigt werden. Wichtig für Echtzeitsysteme ist, dass kein Garbage-Collection-Prozess im Hintergrund ausgeführt werden muss.

Durch die Bereitstellung speichersicherer Strukturen und Manipulationstechniken beschleunigt Rust die Entwicklung und das Testen von Software. Außerdem nutzt es die Fähigkeiten, die Hochschulabsolventen heute erwerben. Diese beiden Faktoren sind in Branchen wie der Automobilindustrie wichtig, in denen der Softwareanteil in Fahrzeugen rapide zunimmt.

Legacy-Nutzung weiterhin notwendig

Die Wiederverwendung bestehender Code-Module ist für Unternehmen, die sicherheitskritische Systeme entwickeln, ebenso wichtig. Die Entwicklung dieser Systeme muss konservativ erfolgen. Änderungen an bestehenden Systemen sollten nur vorgenommen werden, wenn dies unbedingt notwendig ist. Es ist unpraktisch und sogar unerwünscht, Module in einer neuen Sprache neu zu schreiben, selbst wenn deren Schutzmechanismen gegenüber Legacy-C/C++ erhebliche Vorteile bieten. Diese bestehenden Module müssen überprüft werden, sobald sie in ein Zielsystem integriert sind, das große Teile in Rust oder einer ähnlich speichersicheren Sprache enthält.

Jetzt Newsletter abonnieren

Verpassen Sie nicht unsere besten Inhalte

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung. Die Einwilligungserklärung bezieht sich u. a. auf die Zusendung von redaktionellen Newslettern per E-Mail und auf den Datenabgleich zu Marketingzwecken mit ausgewählten Werbepartnern (z. B. LinkedIn, Google, Meta).

Aufklappen für Details zu Ihrer Einwilligung

Es gibt auch Situationen, in denen zusätzliche Überprüfungen erforderlich sind, um die Sicherheit zu gewährleisten, selbst wenn eine Sprache starke Verhaltensgarantien bietet. Dies gilt vor allem für Embedded-Steuerungen. Viele Low-Level-Interaktionen, wie der Zugriff auf speicherabgebildete Hardware-Register oder Datenpuffer, lassen sich mit Rust-Referenzen und ähnlichen speichersicheren Elementen nicht ohne Weiteres durchführen. Programmierer können diese Interaktionen in Rust mithilfe von Raw-Pointern verwalten. Kurz gesagt: Obwohl sich diese Pointer ähnlich wie die von C und C++ verhalten, benötigen sie ohne die Sicherheitsvorkehrungen von Rust zusätzliche Überprüfungen.

Erweiterte statische Analyse

Es gibt andere Situationen, in denen Entwickler „unsafe{}“-Blöcke verwenden müssen. Ohne diese Blöcke würde der Compiler alle Aufrufe von unsicheren Funktionen oder Methoden sowie Code, der versucht, auf die Felder von Unions zuzugreifen, verbieten. Obwohl Unions in Rust meist nicht empfohlen werden, können sie zur Kompatibilität mit C und C++ beitragen. Der Compiler kann die Sicherheit von Operationen auf keinem der Felder garantieren, da er nicht feststellen kann, ob Schreibvorgänge in einem Feld andere Felder innerhalb derselben Speicherstruktur beschädigen.

Es gibt jedoch formale Verifizierungs- und mathematische Analyseverfahren, die eine Code-Analyse vor der Ausführung ermöglichen. Diese können feststellen, ob der Code Speichersicherheitsprobleme wie Pufferüberläufe, Null-Pointer-Zugriffe und andere Fehler aufweist. Statische Analysetechniken sind in gewisser Weise umfassend und bieten Garantien, die selbst umfangreiche dynamische Tests nicht bieten können. Entwickler arbeiten an Tools für Rust-basierte Anwendungen, die erkannte Probleme hervorheben und anzeigen, wo Programmierer zusätzliche Sicherheitsvorkehrungen einfügen müssen, z. B. Überprüfungen des Adressbereichs einer Pointer-Referenz. Da Rust häufig mit C- und C++-Modulen aus früheren Entwicklungen koexistieren muss, werden Tools wie TrustInSoft Analyzer eingesetzt, um sicherzustellen, dass die kombinierte Codebasis frei von Speicherproblemen ist.

Innovate Your Software – for a Smarter Future

Deutschlands Leitkongress der Embedded-Softwarebranche

Embedded Software Engineering Kongress

Das Programm des ESE Kongress umfasst 96 Vorträge, 21 Seminare und 3 Keynotes. Seien Sie dabei, wenn sich die Embedded-Software-Community trifft, und nutzen Sie Diskussionen und Expertengespräche für einen ergiebigen Wissenstransfer und erfolgreiches Networken. Während der vier Kongresstage erwartet Sie zudem eine große Fachausstellung mit den führenden Firmen der Branche. Erfahren Sie alles über die neuesten Trends, Herausforderungen und Lösungen im Embedded Software Engineering, von KI, Safety und Security bis hin zu Management und Innovation.

Es gibt auch definierte, aber unerwünschte Verhaltensweisen: So können eine Division durch Null oder bestimmte Fehler einen „Panic“-Zustand auslösen und das Programm zum Absturz bringen. Das Erkennen all dieser unerwünschten Verhaltensweisen ist daher ein wichtiger Aspekt beim Einsatz eines fortschrittlichen statischen Analyzers.

Das Überprüfen des korrekten Verhaltens wird durch die automatische Generierung von Assertions weiter verbessert. So lässt sich testen, ob Code in beliebiger Sprache unerwartete oder außerhalb des zulässigen Bereichs liegende Eingaben sicher verarbeitet. Diese Art von Tests simuliert das „Fuzzing“, das Hacker häufig nutzen, um Schwachstellen zu identifizieren.

Diese Tools bieten einen formalen, überprüfbaren Nachweis für das Nichtvorhandensein von Schwachstellen in der Speichersicherheit, die sonst zu unsicherem und unvorhersehbarem Verhalten in sicherheitskritischen Fahrzeugen führen könnten. Um zu verhindern, dass Entwickler mit potenziellen Fehlern überfordert werden, stehen Tools bereit, die die Anzahl der Fehlalarme minimieren und sicherstellen, dass nur Code hervorgehoben wird, bei dem wahrscheinlich Probleme mit der Speichersicherheit auftreten.

Da Rust bei der Entwicklung hochkritischer Systeme weiter an Bedeutung gewinnt, muss sichergestellt sein, dass externe Code-Module und Low-Level-Funktionen frei von latenten Problemen sind, die den Betrieb vor Ort beeinträchtigen könnten. Durch zusätzliche statische Tests und Verifizierungen können Entwickler undefiniertes Verhalten frühzeitig im Integrationszyklus erkennen und beheben. So wird sichergestellt, dass diese Probleme rechtzeitig vor der Bereitstellung beseitigt sind.

 (sg)

(ID:50634723)