Blog 10 Min. Lesezeit

MISRA C++ meistern: Die 10 wichtigsten sicherheitskritischen Regeln für eingebettete Systeme

Diesen Artikel teilen
MISRA C++ meistern: Die 10 wichtigsten sicherheitskritischen Regeln für eingebettete Systeme

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.

Diesen Artikel teilen