In eingebetteten und Echtzeitsystemen sind Softwareausfälle nicht nur technische Pannen – sie können zu körperlichen Verletzungen, katastrophalen Datenlecks oder zum vollständigen Missionsverlust führen. Da C++ Entwicklern Low-Level-Zugriff auf Hardwareregister und Zeiger gibt, bietet es auch reichlich Gelegenheit, undefiniertes Verhalten, Speicherbeschädigung und subtile Sicherheitslücken einzuführen.
Um diese Risiken zu beseitigen, verlassen sich Engineering-Leiter und sicherheitskritische Branchen auf MISRA C++.
Was ist MISRA C++?
MISRA (Motor Industry Software Reliability Association) entwickelte ursprünglich C- und C++-Codierstandards, um eine sichere, zuverlässige und vorhersehbare Softwareausführung in sicherheitskritischen Fahrzeug-ECUs zu gewährleisten. Heute ist MISRA C++ der internationale Goldstandard für die restriktive Teilmengenbildung von C++ – gefährliche Sprachmerkmale, mehrdeutige Syntax und undefiniertes Verhalten werden entfernt.
Statt als einfache Formatierungsrichtlinie zu wirken, zielen MISRA-Regeln auf die Grundursachen von Softwarefehlern:
- Speichersicherheit: Beseitigung von Pufferüberläufen, Use-after-free und Stack-Beschädigung.
- Deterministische Ausführung: Vermeidung dynamischer Heap-Fragmentierung und unbehandelter Ausnahmen.
- Typsicherheit: Stopp von stiller Kürzung und gefährlichen impliziten Casts.
Welche Branchen verlangen MISRA-Konformität?
MISRA-C++-Konformität ist in einer Vielzahl missionskritischer und stark regulierter Sektoren vorgeschrieben:
- Automotive (ISO 26262): Einsatz bei OEMs und Tier-1-Zulieferern (z. B. Stellantis, BMW, Bosch) für Motorsteuergeräte, ADAS-Wahrnehmung, elektronische Bremsen und softwaredefinierte Fahrzeugarchitekturen (SDV).
- Luft- und Raumfahrt & Avionik (DO-178C): Flugsteuerungscomputer, Satelliten-Lageregelungssysteme und Raketenlenkmodule.
- Medizinprodukte (IEC 62304): Automatisierte Insulinpumpen, Herzschrittmacher, Operationsroboter und Beatmungsgeräte.
- Bahnsysteme (EN 50128) & Verteidigung: Signalnetze, autonome Verteidigungsplattformen und schwere Industrieautomation.
Die 10 wichtigsten sicherheitskritischen MISRA-C++-Regeln
Nachfolgend finden Sie 10 wesentliche, sicherheitsbewertete MISRA-C++-Prinzipien – komplett mit nicht konformen Beispielen und sicheren, modernen C++-Fixes, um Low-Level-Schwachstellen in eingebetteten Geräten zu verhindern.
1. Zeigerarithmetik einschränken und Grenzsicherheit gewährleisten
Sicherheitsbedrohung: Pufferüberläufe, Zeiger-Manipulations-Exploits und Out-of-bounds-Speicherbeschädigung.
❌ Nicht konform:
void processBuffer(uint8_t *ptr, size_t len) {
for (size_t i = 0; i <= len; ++i) { // Bug: <= allows out-of-bounds write
*(ptr + i) = 0;
}
}
✅ Konforme Lösung:
#include <span>
void processBuffer(std::span<uint8_t> buffer) {
for (auto &val : buffer) {
val = 0; // Bounds-safe container iteration
}
}
2. Alle Variablen müssen vor der Verwendung initialisiert werden
Sicherheitsbedrohung: Informationspreisgabe, undefiniertes Verhalten und Exploits über nicht initialisierten Speicher.
❌ Nicht konform:
void authenticateUser() {
bool isAuthenticated; // Uninitialized stack memory containing garbage data
if (checkToken()) {
isAuthenticated = true;
}
if (isAuthenticated) { // Undefined behavior if checkToken() returns false
grantAccess();
}
}
✅ Konforme Lösung:
void authenticateUser() {
bool isAuthenticated = false; // Explicit default initialization
if (checkToken()) {
isAuthenticated = true;
}
if (isAuthenticated) {
grantAccess();
}
}
3. Dynamische Heap-Speicherallokation muss eingeschränkt oder vermieden werden
Sicherheitsbedrohung: Denial of Service (DoS) durch Heap-Erschöpfung, Speicherlecks und nicht deterministische Allokationsverzögerungen.
❌ Nicht konform:
void handleMessage(const char *data, size_t length) {
char *buffer = new char[length]; // Dangerous heap allocation per message
// process buffer...
delete[] buffer;
}
✅ Konforme Lösung:
#include <array>
void handleMessage(const char *data, size_t length) {
std::array<char, 256> staticBuffer; // Stack-allocated or statically pooled
if (length < staticBuffer.size()) {
// process safely without dynamic heap calls
}
}
4. Keine impliziten Typumwandlungen, die Datenverlust verursachen
Sicherheitsbedrohung: Integer-Kürzung, Wrap-around-Fehler und Pufferüberlauf-Exploits.
❌ Nicht konform:
void parseHeader(uint32_t declaredLength) {
uint8_t bufferSize = declaredLength; // Implicit narrowing: 512 truncated to 0!
uint8_t *dest = new uint8_t[bufferSize];
}
✅ Konforme Lösung:
void parseHeader(uint32_t declaredLength) {
if (declaredLength > 255) {
return; // Guard against truncation and invalid input
}
uint8_t bufferSize = static_cast<uint8_t>(declaredLength);
}
5. Volatile Datenzugriffe müssen korrekt behandelt werden
Sicherheitsbedrohung: Race Conditions, verpasste Hardware-Ereignisse und umgangene Sicherheitsprüfungen durch Register-Caching der Compiler-Optimierung.
❌ Nicht konform:
uint32_t *const AuthStatusReg = reinterpret_cast<uint32_t*>(0x40001000);
void verifySecurityGate() {
if (*AuthStatusReg == 0x1) { // Compiler may cache read value in a CPU register
unlockDevice();
}
}
✅ Konforme Lösung:
volatile uint32_t *const AuthStatusReg = reinterpret_cast<volatile uint32_t*>(0x40001000);
void verifySecurityGate() {
if (*AuthStatusReg == 0x1) { // Forces hardware re-read on every execution
unlockDevice();
}
}
6. Undefiniertes Verhalten bei bitweisen Operationen vermeiden
Sicherheitsbedrohung: Blinde Flecken der Compiler-Optimierung, Shift-Register-Überlauf und Logikbeschädigung.
❌ Nicht konform:
uint32_t shiftRegister(uint32_t val, int bits) {
return val << bits; // Undefined behavior if bits < 0 or bits >= 32
}
✅ Konforme Lösung:
uint32_t shiftRegister(uint32_t val, int bits) {
if (bits < 0 || bits >= 32) {
return 0; // Explicit bounds checking to prevent UB
}
return val << bits;
}
7. const-Korrektheit konsequent durchsetzen
Sicherheitsbedrohung: Versehentliche Speichermanipulation oder Privilegieneskalation durch unbefugte Änderung statischer Konfigurationsparameter.
❌ Nicht konform:
struct SystemConfig {
uint32_t key;
};
void updateConfig(SystemConfig &cfg) {
cfg.key = 0xDEADBEEF; // Mutable access can be exploited or mutated by mistake
}
✅ Konforme Lösung:
struct SystemConfig {
uint32_t key;
};
void inspectConfig(const SystemConfig &cfg) {
// cfg.key = ...; -> Compiler error: prevents unauthorized mutation
}
8. Variablenverschattung (Name Hiding) vermeiden
Sicherheitsbedrohung: Logikverwirrung und umgangene Autorisierungen, weil lokale Variablen statt der beabsichtigten globalen Zustände referenziert werden.
❌ Nicht konform:
int globalAccessLevel = 0;
void processLogin(int accessLevel) {
int globalAccessLevel = 1; // Shadows global variable!
// Developer assumes global state is updated, but it remains unchanged.
}
✅ Konforme Lösung:
int g_AccessLevel = 0;
void processLogin(int inputAccessLevel) {
g_AccessLevel = inputAccessLevel; // Clear, distinct variable scopes
}
9. Keine „Magic Numbers“ verwenden
Sicherheitsbedrohung: Hartcodierte Logikfehler, nicht wartbare Sicherheitsrichtlinien und Audit-Mängel.
❌ Nicht konform:
void validateUser(uint8_t role) {
if (role == 3) { // What is 3? Admin? Guest? Superuser?
grantSuperUserAccess();
}
}
✅ Konforme Lösung:
enum class UserRole : uint8_t {
Guest = 1,
Operator = 2,
Administrator = 3
};
void validateUser(UserRole role) {
if (role == UserRole::Administrator) {
grantSuperUserAccess();
}
}
10. switch-Anweisungen müssen eine default-Klausel haben
Sicherheitsbedrohung: Unbehandelte Zustandsübergänge, die Sicherheitsprüfungen bei korrupten Hardware-Enum-Werten überspringen.
❌ Nicht konform:
void handleCommand(ProtocolState state) {
switch (state) {
case ProtocolState::Idle: resetTimer(); break;
case ProtocolState::Active: processData(); break;
// Missing default: corrupted enum skips checks silently!
}
}
✅ Konforme Lösung:
void handleCommand(ProtocolState state) {
switch (state) {
case ProtocolState::Idle: resetTimer(); break;
case ProtocolState::Active: processData(); break;
default:
triggerSecurityAlarm(); // Catches corrupt or unexpected states safely
break;
}
}
Zusammenfassung
Die Anwendung von MISRA-C++-Regeln in Ihrer automatisierten CI/CD-Pipeline für statische Analyse (wie CppDepend) garantiert, dass Sicherheit bei jedem einzelnen Commit verifiziert wird – lange bevor der Code physische Hardware erreicht.

