Join our Newsletter — 33% off our NHI Course
Home› Guides› Embedded and Firmware Secure Coding: MISRA, CERT C…
Guide OT & IoT Security

Embedded and Firmware Secure Coding: MISRA, CERT C and Memory Safety for Devices

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 8 November 2026 14 min read
On this page

Firmware runs without most of the safety nets that protect desktop and server code, so a single out-of-bounds write in a radio parser or USB handler can give an attacker the whole device, often permanently. Many microcontrollers have no memory management unit, no address space layout randomisation and no separate kernel, and the same binary may ship to millions of units that are rarely updated. This guide covers how to write safer C and C++ for embedded targets: choosing and applying MISRA C and the SEI CERT C Coding Standard, the embedded-specific bug classes, the hardware and compiler protections worth turning on, where Rust fits, and how to prove it all in a pipeline. It is for firmware engineers, embedded team leads and product security teams. General C and C++ patterns, hardening flags and sanitizers are covered in the C and C++ Secure Coding Guide; device-level architecture is in the Embedded Device Security Guide.

Key takeaways

  • Pick a coding standard and enforce it in CI. MISRA C (current edition MISRA C:2025) is the established safety-and-security subset; the SEI CERT C Coding Standard is security-focused; MISRA publishes addenda mapping its guidelines to CERT C and to CWE memory safety weaknesses.
  • Treat every external byte as hostile. Radio frames, USB descriptors, sensor buses, bootloader commands and update images all cross a trust boundary; MISRA Directive 4.14 requires validating values received from external sources.
  • Remove whole bug classes by design. No dynamic allocation after start-up, no recursion, fixed-size buffers with explicit lengths and checked integer arithmetic eliminate many of the faults attackers rely on.
  • Use the hardware you have. An MPU, privileged and unprivileged thread modes, stack guards and execute-never regions turn silent corruption into a contained fault.
  • Keep secrets out of the source. Hard-coded passwords and keys in firmware are recoverable by anyone with a device; signing keys belong in an HSM and per-device credentials in secure storage.

Why firmware code needs its own rules

Desktop secure coding advice assumes an operating system that isolates processes, randomises memory layout and can be patched weekly. Firmware often has none of that, which changes both the impact of a bug and which mitigations are available.

ConstraintTypical on microcontrollersSecurity consequenceCoding response
Flat memory, no MMUCode, data, stack and peripherals share one address spaceAn overflow can reach any variable, the vector table or peripheral registersBounds checks at every boundary; configure the MPU to separate regions
No ASLRFixed load addressesExploits are reliable across every unitPrevent the bug class rather than relying on randomisation
Small, fixed stacksHundreds of bytes to a few kilobytes per taskDeep calls or large locals overflow silently into other dataNo recursion; worst-case stack analysis; stack guards
Interrupts and DMAConcurrent writers to shared memoryRace conditions and buffer overruns from peripheral transfersvolatile correctly, critical sections, DMA length checks
Long field life, slow updatesYears between updates, or noneA bug stays exploitable for the product's lifeHigher bar before release; signed update path
Physical accessDebug ports, flash readoutFirmware and any embedded secrets can be extractedNo secrets in code; lock debug in production builds

Choosing a coding standard: MISRA C, CERT C and MISRA C++

A coding standard turns "write safe code" into checkable rules, and both safety and product security assessors expect to see one.

StandardOwner and statusFocusBest fit
MISRA C:2025MISRA; published March 2025 as an incremental update to MISRA C:2023, supporting C11, C18 and earlierA restricted, analysable subset of C for critical systems; directives plus rules, many decidable by toolsAutomotive, industrial, medical and any team needing a safety case
MISRA C:2012 / C:2023MISRA; still widely referenced by projects and toolsSame model; C:2012 introduced decidability classificationExisting codebases and toolchains already certified against it
SEI CERT C Coding StandardCarnegie Mellon SEI; living wiki with periodic official releasesSecurity: rules and recommendations grouped by area (integers, memory, strings, arrays and others)Connected devices where security assessors ask for CERT compliance
MISRA C++MISRA; first published 2008, later editions update it for modern C++Safe subset of C++ for critical systemsC++ firmware and middleware

They are complementary, not rival choices. MISRA publishes addenda that map its guidelines to other standards: MISRA C:2023 ADD3 covers the second edition of CERT C, ADD2 covers ISO/IEC 17961, and MISRA C:2025 ADD5 maps CWE memory safety weaknesses (CWE-1399) to MISRA guidance. A team can adopt MISRA C as its base and use the addenda to show CERT C coverage. MISRA Compliance:2020 explains how to claim compliance, including recording justified deviations, and MISRA C:2012 Permits give pre-approved deviation patterns for hardware access and generated code, both common in firmware.

Rules that matter most for security

The Zephyr RTOS project publishes the subset of MISRA C:2012 that it requires for new code, with CERT C equivalents. It is a useful, public example of the rules that carry the most security weight:

MISRA C:2012RequirementCERT C equivalentBug class it prevents
Dir 4.14Validity of values received from external sources shall be checkedNone listedParser overflows, injection of out-of-range commands
Dir 4.12 and Rule 21.3No dynamic memory allocation; no malloc and free from stdlib.hAPI03-C, API04-C, STR01-C, MSC24-CUse after free, double free, heap exhaustion
Rule 1.3No undefined or critical unspecified behaviourNone listedCompiler optimisations that delete security checks
Rule 9.1Automatic variables shall not be read before being setNone listedInformation leaks from stale stack data
Rules 10.1 to 10.8Essential type model: no inappropriate, narrowing or mixed-type arithmeticINT02-C, INT31-C and othersInteger truncation and sign errors that become buffer overflows
Rule 17.2Functions shall not call themselves, directly or indirectlyMEM05-CUnbounded stack growth
Rule 17.7Return values of non-void functions shall be usedNone listedIgnored errors leaving buffers or states unchecked
Rule 18.1Pointer arithmetic shall stay within the same arrayARR30-C, ARR39-C, EXP08-COut-of-bounds reads and writes
Rule 21.17string.h functions shall not access beyond object boundsNone listedClassic string buffer overflows
Rule 22.1Dynamically obtained resources shall be releasedNone listedResource leaks leading to denial of service

Embedded bug classes and safe patterns

  • Length fields from the wire: the most common firmware vulnerability is trusting a length or count in a received packet (CWE-120, CWE-787). Parse into a fixed-size structure, check the declared length against both the buffer size and the bytes actually received, and reject rather than truncate.
  • Integer overflow before allocation or copy: count * size or offset + len can wrap on 16-bit and 32-bit targets (CWE-190). Use unsigned fixed-width types from stdint.h, check before the arithmetic, and keep the essential type rules on.
  • Interrupt and DMA races: data shared between an interrupt service routine and the main loop must be volatile and updated in a critical section or with atomic operations. Check DMA transfer lengths against the destination buffer, because the peripheral will happily write past it.
  • Stack exhaustion: with no recursion and no variable-length arrays, worst-case stack use becomes computable. Use the toolchain's stack usage output and size each task's stack with a margin, then confirm with a stack guard or watermark at run time.
  • Use after free in static pools: even without malloc, block pools and message queues can hand out a buffer that is still referenced. Clear ownership rules (one owner, explicit hand-off) prevent it.
  • Error paths and fault handlers: a hard fault handler that simply resets the device can be used to bypass checks or loop a device into denial of service. Log the fault cause to persistent storage and decide deliberately between safe state and reset.
  • Debug and test code in production: UART consoles, test commands and unlocked JTAG or SWD ports left enabled are a direct way in. Build them out with configuration options and verify the production image. Zephyr's guidance is that rarely used features stay off by default and must be explicitly enabled.

Hardware and compiler protections for firmware

ProtectionWhat it doesWhere availableNote
Memory protection unit (MPU)Enforces access rights per memory region, including no-execute for RAMMany Arm Cortex-M and other MCUsRTOSes such as Zephyr use it for thread separation and stack protection (Zephyr added memory protection for Arm in version 1.11.0)
Privileged and unprivileged modesRuns application threads without direct access to kernel data or critical peripheralsCortex-M and RTOS user mode featuresPair with a narrow system call interface
Stack canariesDetects stack buffer overwrites before return (D3FEND Stack Frame Canary Validation, D3-SFCV)GCC and Clang -fstack-protector-strong (GCC 4.9.0 and Clang 6.0.0 onwards)Needs a canary value and a failure handler suitable for bare metal
Automatic variable initialisationInitialises locals that lack an initialiser-ftrivial-auto-var-init=zero (GCC 12, Clang 8)Backs up MISRA Rule 9.1
Defined overflow behaviourStops the compiler treating signed overflow as impossible and removing checks-fno-strict-overflowSupports Rule 1.3 intent
Conversion warningsFlags implicit narrowing and sign changes-Wconversion, -Wsign-conversionOpenSSF advises enabling from day one on new code, and a baseline-then-ratchet approach on existing code
TrustZone-M or a TEESeparates secure services (keys, crypto, boot) from application codeArmv8-M and some other MCUsSee the Hardware Root of Trust Guide

The OpenSSF Compiler Options Hardening Guide explicitly includes embedded and IoT targets in its audience. Some flags need runtime support that bare-metal C libraries do not provide, so test each one on the target rather than copying a Linux flag set.

Where Rust fits

Rust removes most memory safety bugs in safe code, and the Embedded Rust Book, maintained by the Rust Embedded Working Group, covers bare-metal development on microcontrollers using Arm Cortex-M examples and how to mix C and Rust in one project. MISRA C:2025 ADD6 assesses how MISRA C guideline issues apply to Rust. A practical route for existing products is to write new parsers, protocol handlers and update logic in Rust, keep vendor drivers in C, and review every unsafe block like C. See the Rust Security Guide and the Memory Safety Roadmap Guide.

Where identity fits: secrets and keys in firmware

  • No shared default passwords: CISA's Secure by Design pledge names default passwords, universally shared across a product, as a goal to eliminate. Generate a unique credential per device at manufacture or require one at first use (CWE-798 Use of Hard-coded Credentials; NIST SP 800-53 IA-5(7) No Embedded Unencrypted Static Authenticators).
  • Per-device identity, not a fleet key: a key compiled into every image is extracted once and works everywhere. Provision per-device keys into secure storage. See the IoT Device Identity with TPM and DICE Guide.
  • Cloud tokens on devices: devices that call cloud APIs should use short-lived, scoped tokens obtained with the device identity, not long-lived API keys in flash.
  • Signing keys are the crown jewels: in the MSI signing keys leak, a ransomware group published firmware source code alongside image signing keys for 57 products and Intel Boot Guard keys for 116, meaning modified firmware could in principle pass verification. Keep signing keys in an HSM, separate from source repositories and build servers. See the Code Signing Security Guide.

Implementing it: a phased approach

  1. Baseline: choose the standard (MISRA C:2025 or the edition your tools support, with CERT C mapping via the addenda) and write a short project profile listing which guidelines are required, which are advisory and how deviations are approved.
  2. Tooling in audit mode: run a MISRA-capable static analyser and compiler warnings on the existing code without failing the build. Record the baseline count per rule.
  3. Fix the high-value rules first: external input validation, pointer arithmetic, string bounds, essential types and dynamic allocation. These map most directly to exploitable bugs.
  4. Gate new code: fail CI on new violations of required rules, while existing findings burn down. Each deviation needs a written justification and reviewer sign-off under MISRA Compliance:2020.
  5. Add dynamic testing: fuzz parsers on the host with sanitizers, using the same source compiled for the PC, and fuzz interfaces on hardware where possible. See the Fuzzing Guide.
  6. Enable protections on target: configure the MPU, stack guards and compiler hardening in a test build; measure code size, timing and power; then enable in a pilot product line.
  7. Lock production builds: strip debug commands, disable or lock debug ports, verify no secrets in the image, and sign the release. Roll back by shipping the previous signed image through the normal update path; never by re-enabling debug access in the field. See the IoT Secure Firmware Update Guide (SUIT, signed OTA).

Decision guidance

  • Safety-certified product: MISRA is normally expected; add CERT C coverage through ADD3 for the security case.
  • Connected consumer or industrial device without a safety case: start with the security-critical subset above and CERT C rules for integers, memory and strings, then widen.
  • Small team, one product: adopt an existing published subset (such as Zephyr's) rather than writing your own profile.
  • Large portfolio: one organisation-wide profile and shared deviation register, with per-product additions, so findings are comparable across teams.
  • New component on a capable MCU: consider Rust for parsers and protocol code; keep C where vendor SDKs require it.

Measuring it

  • Open required-rule violations per thousand lines, trending down release by release.
  • Deviation count and age: approved deviations, with any older than one release reviewed.
  • Fuzzing coverage of external input handlers, and crashes found per month.
  • Memory safety defects found after release, by CWE class.
  • Production image checks passed: debug locked, no hard-coded secrets, signature verified, for every release.

Framework mapping

PracticeNIST SP 800-53 Rev. 5NIST CSF 2.0ATT&CK or D3FEND
Coding standard and secure development processSA-15 Development Process, Standards, and Tools; SA-8 Security and Privacy Engineering PrinciplesPR.PS-06None
Static analysis against MISRA or CERT CSA-11(1) Static Code AnalysisPR.PS-06None
Fuzzing and developer testingSA-11 Developer Testing and EvaluationPR.PS-06None
MPU, stack guards, no-execute memorySI-16 Memory ProtectionPR.PS-06D3-SFCV Stack Frame Canary Validation
No hard-coded credentialsIA-5(7) No Embedded Unencrypted Static AuthenticatorsPR.AA-01None
Signed firmware and integrity checksSI-7 Software, Firmware, and Information IntegrityPR.PS-06Counters T1542.001 System Firmware and T1195.002 Compromise Software Supply Chain

Common failure patterns

  • Standard on paper only: a MISRA claim with thousands of unreviewed violations and no deviation records will not survive an assessor and does not prevent bugs.
  • Blanket deviations: suppressing a rule project-wide because vendor headers trigger it hides real violations in your own code; scope deviations to the file or line.
  • Testing only the happy path: parsers that were never given malformed input are where field exploits come from; fuzz every interface that accepts external data.
  • Debug left on: production units with an open SWD port or a hidden UART menu make every other control irrelevant.
  • Fleet-wide secrets: one hard-coded key or password extracted from a single device compromises the whole installed base.
  • Signing keys on the build server: as the MSI case shows, keys stored alongside source can leave with it.

Practitioner checklist

  • Adopt MISRA C (edition supported by your tools) or CERT C, with a written project profile and deviation process.
  • Run static analysis on every merge and fail the build on new required-rule violations.
  • Validate every externally received length, count and command against buffer sizes and allowed ranges.
  • Ban dynamic allocation after start-up and recursion; compute worst-case stack per task.
  • Enable the MPU, stack guards and suitable compiler hardening on target builds.
  • Fuzz parsers and protocol handlers on the host with sanitizers.
  • Remove debug commands and lock debug ports in production images, and verify it per release.
  • Remove hard-coded passwords and keys; provision per-device credentials into secure storage.
  • Keep firmware signing keys in an HSM, separate from source and build infrastructure.
  • Track violations, deviations and post-release memory safety defects as release metrics.

Standards and references

Related NHI Mgmt Group resources: C and C++ Secure Coding Guide · Embedded Device Security Guide · Firmware and Boot Security Guide · Memory-Safe Languages Guide · Secure Software Development Lifecycle (SSDLC) Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 8 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org