Blog 10 min read

Mastering MISRA C++: Top 10 Security-Critical Rules for Embedded Systems

Share this article
Mastering MISRA C++: Top 10 Security-Critical Rules for Embedded Systems

In embedded and real-time systems, software failures aren't just technical glitches—they can result in physical injury, catastrophic data breaches, or complete mission loss. Because C++ gives developers low-level access to hardware registers and pointers, it also provides ample opportunity to introduce undefined behavior, memory corruption, and subtle security vulnerabilities.

To eliminate these risks, engineering leaders and safety-critical industries rely on MISRA C++.

What is MISRA C++?

MISRA (Motor Industry Software Reliability Association) originally developed C and C++ coding standards to ensure safe, reliable, and predictable software execution in safety-critical vehicle ECUs. Today, MISRA C++ serves as the international gold standard for restrictively subsetting C++—pruning away dangerous language features, ambiguous syntax, and undefined behavior.

Rather than acting as a simple formatting guide, MISRA rules target root-cause software bugs:

  • Memory Safety: Eliminating buffer overflows, use-after-free, and stack corruption.
  • Deterministic Execution: Preventing dynamic heap fragmentation and unhandled exceptions.
  • Type Safety: Stopping silent truncation and dangerous implicit casts.

Which Industries Require MISRA Compliance?

MISRA C++ compliance is mandated across a wide range of mission-critical and highly regulated sectors:

  • Automotive (ISO 26262): Used by OEMs and Tier-1 suppliers (e.g., Stellantis, BMW, Bosch) for engine control units, ADAS perception, electronic braking, and software-defined vehicle (SDV) architectures.
  • Aerospace & Avionics (DO-178C): Flight control computers, satellite attitude determination systems, and rocket guidance modules.
  • Medical Devices (IEC 62304): Automated insulin pumps, pacemakers, surgical robotics, and ventilators.
  • Rail Systems (EN 50128) & Defense: Signaling networks, autonomous defense platforms, and heavy industrial automation.

Top 10 Security-Critical MISRA C++ Rules

Below are 10 essential security-ranked MISRA C++ principles, complete with non-compliant examples and safe, modern C++ fixes to prevent low-level vulnerabilities in embedded devices.

1. Restrict Pointer Arithmetic and Ensure Bounds Safety

Security Threat: Buffer overflows, pointer manipulation exploits, and out-of-bounds memory corruption.

❌ Non-Compliant:

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;
    }
}

✅ Compliant Fix:

#include <span>
void processBuffer(std::span<uint8_t> buffer) {
    for (auto &val : buffer) {
        val = 0; // Bounds-safe container iteration
    }
}

2. All Variables Shall Be Initialized Before Use

Security Threat: Information disclosure, undefined behavior, and uninitialized memory exploits.

❌ Non-Compliant:

void authenticateUser() {
    bool isAuthenticated; // Uninitialized stack memory containing garbage data
    if (checkToken()) {
        isAuthenticated = true;
    }
    if (isAuthenticated) { // Undefined behavior if checkToken() returns false
        grantAccess();
    }
}

✅ Compliant Fix:

void authenticateUser() {
    bool isAuthenticated = false; // Explicit default initialization
    if (checkToken()) {
        isAuthenticated = true;
    }
    if (isAuthenticated) {
        grantAccess();
    }
}

3. Dynamic Heap Memory Allocation Shall Be Restricted or Avoided

Security Threat: Denial of Service (DoS) via heap exhaustion, memory leaks, and non-deterministic allocation delays.

❌ Non-Compliant:

void handleMessage(const char *data, size_t length) {
    char *buffer = new char[length]; // Dangerous heap allocation per message
    // process buffer...
    delete[] buffer;
}

✅ Compliant Fix:

#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. No Implicit Type Conversions That Cause Data Loss

Security Threat: Integer truncation, wrap-around errors, and buffer overflow exploits.

❌ Non-Compliant:

void parseHeader(uint32_t declaredLength) {
    uint8_t bufferSize = declaredLength; // Implicit narrowing: 512 truncated to 0!
    uint8_t *dest = new uint8_t[bufferSize];
}

✅ Compliant Fix:

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 Data Access Must Be Handled Correctly

Security Threat: Race conditions, missed hardware events, and security check bypasses due to register caching by compiler optimizations.

❌ Non-Compliant:

uint32_t *const AuthStatusReg = reinterpret_cast<uint32_t*>(0x40001000);
void verifySecurityGate() {
    if (*AuthStatusReg == 0x1) { // Compiler may cache read value in a CPU register
        unlockDevice();
    }
}

✅ Compliant Fix:

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. Avoid Undefined Behavior in Bitwise Operations

Security Threat: Compiler optimization blind spots, shift register overflow, and logic corruption.

❌ Non-Compliant:

uint32_t shiftRegister(uint32_t val, int bits) {
    return val << bits; // Undefined behavior if bits < 0 or bits >= 32
}

✅ Compliant Fix:

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. Enforce const Correctness Rigorously

Security Threat: Accidental memory tampering or privilege escalation through unauthorized mutation of static configuration parameters.

❌ Non-Compliant:

struct SystemConfig {
    uint32_t key;
};
void updateConfig(SystemConfig &cfg) {
    cfg.key = 0xDEADBEEF; // Mutable access can be exploited or mutated by mistake
}

✅ Compliant Fix:

struct SystemConfig {
    uint32_t key;
};
void inspectConfig(const SystemConfig &cfg) {
    // cfg.key = ...; -> Compiler error: prevents unauthorized mutation
}

8. Avoid Variable Shadowing (Name Hiding)

Security Threat: Logic confusion and authorization bypasses caused by referencing local scope variables instead of intended global states.

❌ Non-Compliant:

int globalAccessLevel = 0;
void processLogin(int accessLevel) {
    int globalAccessLevel = 1; // Shadows global variable!
    // Developer assumes global state is updated, but it remains unchanged.
}

✅ Compliant Fix:

int g_AccessLevel = 0;
void processLogin(int inputAccessLevel) {
    g_AccessLevel = inputAccessLevel; // Clear, distinct variable scopes
}

9. Avoid Using "Magic Numbers"

Security Threat: Hardcoded logic flaws, unmaintainable security policies, and audit failures.

❌ Non-Compliant:

void validateUser(uint8_t role) {
    if (role == 3) { // What is 3? Admin? Guest? Superuser?
        grantSuperUserAccess();
    }
}

✅ Compliant Fix:

enum class UserRole : uint8_t {
    Guest = 1,
    Operator = 2,
    Administrator = 3
};
void validateUser(UserRole role) {
    if (role == UserRole::Administrator) {
        grantSuperUserAccess();
    }
}

10. Switch Statements Must Have a default Clause

Security Threat: Unhandled state machine transitions skipping safety checks when processing corrupted hardware enum values.

❌ Non-Compliant:

void handleCommand(ProtocolState state) {
    switch (state) {
        case ProtocolState::Idle:   resetTimer(); break;
        case ProtocolState::Active: processData(); break;
        // Missing default: corrupted enum skips checks silently!
    }
}

✅ Compliant Fix:

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;
    }
}

Summary

Adopting MISRA C++ rules in your automated CI/CD static analysis pipeline (like CppDepend) guarantees that safety and security are verified on every single commit—long before code ever reaches physical hardware.

Share this article