Defense in Depth in Practice: Prevent, Contain, Recover
Finding a hardware weakness is only the first step. The more useful question is what we can do to prevent it, contain it, or recover when prevention fails.
The previous article described a particularly unpleasant weakness on a popular embedded platform: a single physical memory transaction can lock up the whole system, including the application cores, auxiliary processor and on-chip watchdog, until an external reset or power cycle.
Identifying the weakness was only the first step. The more useful question is what we can do about it.
Defense in depth covers many different aspects of embedded cybersecurity. In an older article [Immutability: The Cornerstone of Embedded Defense], we looked at immutability as one way to prevent critical parts of a system from being modified.
The black-hole example lets us focus on another aspect: limiting exposure and privileges to what is actually necessary.
Which components can generate a dangerous transaction? Which ones really need that capability? Can we prevent the transaction, contain its effects, or at least recover when prevention fails?
A useful remediation flow is:
Discover → Prevent → Contain → Recover → Accept or redesign
Discover Before Protecting
The first black hole we found was at 0x30000000.
It would be tempting to protect that address and consider the problem solved. That would be a mistake.
We developed a tool to explore the physical address space systematically and identify accesses that cause abnormal behavior. The analysis is now complete and uncovered several additional black holes on the same platform.
The complete list is deliberately not published here. The objective of this article is not to provide a catalogue of addresses that can be used to crash a product.
The exploration tool we used is available on request for teams interested in analysing their own platform.
The important result is methodological: the first weakness you find may only be the first one you noticed.
Blacklist vs Whitelist
Finding several black holes changes the nature of the problem.
Protecting one known bad range with a dedicated exclusion is easy to imagine. Protecting many of them is less so, especially when the available protection mechanisms have a limited number of regions, alignment constraints, or apply only to selected parts of the address space.
A blacklist also depends on the assumption that every dangerous range has been identified.
That suggests reversing the question.
Instead of asking:
Which parts of the SoC address space must this component never access?
ask:
Which parts does it actually need to access?
If that required set is small and well defined, whitelisting can be both simpler and more robust: everything is inaccessible by default, and only the resources needed by the component are explicitly exposed.
Prevent at the Highest Level
The safest dangerous transaction is the one that is never generated.
At application level, this begins with ordinary software engineering: careful code review, input validation, pointer and buffer management, controlled use of drivers, and careful construction of DMA descriptors.
Admittedly, none of this is specific to this particular hardware weakness. And none of it is sufficient on its own either.
Defense in depth explicitly assumes that one layer can fail. A bug can survive code review. A corrupted pointer can escape normal checks. A legitimate external input may trigger an execution path that was never anticipated. Application correctness is therefore the first protection, not the last one.
Unfortunately, it is often treated as the only one under this common paradigm: if the application is designed never to access a dangerous region, the weakness is simply considered irrelevant. But that assumption holds only as long as the application behaves exactly as expected.
Defense in depth starts precisely where that confidence becomes insufficient. The question is not only whether the current software intends to generate the access, but whether another component, a bug, a corrupted state or a future change could generate it anyway.
Reduce What Linux Can Reach
On the Cortex-A53 side, Linux provides several opportunities to reduce exposure.
The Device Tree is one of them, but its role must not be overestimated. Reserving an area in the DTS can prevent normal kernel subsystems from treating it as available memory, but it does not create a hardware firewall around the corresponding physical addresses.
The kernel configuration also deserves careful scrutiny.
For example, CONFIG_DEVMEM determines whether the traditional /dev/mem physical-memory interface is available. If the product does not need it, disabling it removes a particularly direct path to arbitrary physical addresses.
Other kernel options can further restrict low-level hardware access and should be reviewed as part of the production hardening process. The important point is not to enumerate configuration switches here, but to avoid carrying a development-oriented kernel configuration unchanged into the deployed product.
The MMU provides another layer. User processes should only have mappings for the memory they actually need, and privileged software should be kept as small and constrained as practical.
The general principle is more important than any individual configuration switch:
Remove unnecessary ways of generating the problematic transaction globally, then restrict the remaining software to the address space it actually needs.
The Cortex-M7 Needs the Same Review
The Cortex-M7 has no MMU comparable to the Cortex-A’s, but it does have an MPU.
The obvious first idea is to blacklist each dangerous region. That turns out to be awkward.
The MPU provides only 16 regions in total. But the number of regions is not the only constraint. MPU regions also have size and alignment requirements, and subregions provide only limited additional flexibility.
That matters in this particular case because the dangerous reserved blocks we found do not line up conveniently with the regions we would like to protect. Representing them accurately can consume several MPU regions and subregions for each.
One pragmatic choice is therefore to protect the largest useful portion of a dangerous range and accept that some smaller areas remain accessible.
But, again, there is a more fundamental alternative: whitelisting.
Instead of describing every address that the M7 must not access, start with no access at all and explicitly expose only the memories and peripherals required by its firmware.
This sounds obvious, but it is not the configuration developers necessarily start from: the MPU configuration provided as part of a typical MCUXpresso project should not be assumed to implement a default-deny product security policy. If that is the policy required by the product, the MPU configuration must be explicitly reviewed and reworked.
In practice, this is easy to overlook. The firmware runs, peripherals work, and the MPU setup is often treated as infrastructure that does not need further attention.
Defense in depth requires the opposite approach: the memory map visible to a core should be a deliberate product design decision.
Can the RDC Be the Global Firewall?
The i.MX8M Plus also contains a capable Resource Domain Controller, so a natural question is whether the problem can simply be solved there.
At first glance, the RDC looks like exactly the right kind of mechanism: it controls access to the on-chip resources between the Cortex-A53 platform, Cortex-M7 and other bus masters.
But the RDC does not protect arbitrary locations across the entire physical address space. Its memory-region protection is associated with a defined list of resources such as DAP, FlexSPI, OCRAM, Cortex-M7 TCM, GIC, DRAM, DDRC, PCIe/USB, VPU and NPU address spaces.
Some of the dangerous regions uncovered during our analysis simply do not belong to the resources covered by that list.
The consequence is straightforward: they cannot be protected by the RDC.
That makes the RDC useful, but not a general-purpose global hardware firewall for this problem.
The broader lesson is worth keeping: never infer the protection scope of a security block from its name. Check which transactions and address spaces it can actually control.
Some Masters Are Harder to Constrain
Processors are relatively convenient because they provide MMU or MPU mechanisms. Other bus masters are more difficult.
DMA engines, hardware accelerators and peripheral subsystems may be capable of initiating memory transactions without going through the protection mechanism of either CPU core.
Some can be constrained through their own configuration or through SoC-level access-control mechanisms. Others offer very little useful isolation for this particular problem.
There is therefore no universal protection layer on this SoC that lets us say that no internal master can ever access the dangerous regions.
That is an important result in itself: defense in depth does not mean that every layer can benefit from perfect containment. It means finding where meaningful controls exist, using them deliberately, and understanding the gaps that remain.
Remove External Paths You Do Not Need
The same reasoning applies to debug and recovery interfaces: if JTAG is not required on deployed products, do not leave it available.
Simply removing the connector is not a security control. The signals still exist on the board, and somebody with physical access and sufficient motivation can reach test pads, vias or device pins.
On the i.MX8M Plus, JTAG can be permanently disabled through eFuse configuration. The important point is not merely to remove the connector, but to disable the capability itself during manufacturing.
For a production device that does not require JTAG, that decision belongs in the provisioning process.
JTAG is not the only path worth reviewing
The i.MX8M Plus Boot ROM contains a USB Serial Downloader, the mechanism used by tools such as uuu during provisioning and recovery. The underlying protocol includes commands that can read and write explicitly specified memory addresses, so leaving it available may expose much more than a simple firmware-update interface.
Thankfully, the Reference Manual explicitly states that USB Serial Download can be disabled by fuse. That makes the mitigation especially clean.
The same rule applies here:
Development and manufacturing paths must not automatically survive into the deployed product.
If a recovery mechanism is required in production, keep it deliberately and secure it appropriately. If it is not required, remove the capability rather than merely hiding the connector or interface.
Prevention Still Has Limits
At this stage, we have accumulated several useful protections.
Linux can be hardened. The Cortex-M7 can be constrained with its MPU. Unnecessary debug and boot paths can be removed. The RDC can be used where the relevant resources are actually covered.
But the exercise also reveals the limits of the platform.
Some bus masters remain difficult to constrain. Some dangerous address ranges do not fall behind the RDC. MPU resources are finite. Hardware accelerators and DMA engines may have access paths that are not conveniently filtered by the CPU protection mechanisms.
This matters because defense in depth is not about assembling a checklist of security features. It is about understanding where the boundaries of each protection really are.
Once those limits are known, the next question is not how to pretend they do not exist, but how to reduce their consequences.
Recover Outside the Failure Domain
The experiments shown in the first article of this series have demonstrated that the on-chip watchdog does not recover this particular black-hole failure.
That tells us something important about the architecture: the watchdog belongs to the same failure domain as the fault it is supposed to recover from.
If the SoC itself becomes unresponsive, a recovery mechanism located inside that SoC may disappear with it.
An external hardware watchdog changes that. It does not prevent the dangerous access. It does not repair the hardware weakness. But because it is outside the affected failure domain, it can still force a reset when the SoC no longer responds.
The result is a very different system-level behavior.
Without external supervision, the failure is permanent. With it, the same event can become a temporary interruption followed by automatic recovery.
That may be sufficient for some products. For others, it may not. This is where the mitigation strategy starts being a product decision.
What Residual Failure Can the Product Accept?
After all reasonable protections have been applied, the hardware weakness may still be present. The important question is therefore not whether the system has become perfect. It has not.
The question is whether the remaining failure mode is acceptable.
A short reboot after an extremely unlikely internal fault may be perfectly acceptable in one application. In another, even a few seconds of unavailability may violate a safety, availability or business requirement.
That distinction has to be made deliberately.
If the residual failure is acceptable, the architecture can keep the SoC and document the mitigation.
If it is not, the answer may be architectural rather than incremental: change the partitioning to isolate critical functions elsewhere, add independent supervision, or even select a different device.
A defense in depth approach does not make every weakness disappear. It makes the remaining failure modes explicit, so they can be accepted, mitigated further, or designed out.
A Vendor BSP Is Only a Starting Point
A vendor BSP is designed to make the device usable: it enables features, exposes development interfaces, provides reference configurations and helps engineers bring up the hardware quickly.
Those are useful goals. But they are not the same as the security requirements of a deployed product.
A development platform may legitimately expose /dev/mem, leave debug paths available, provide permissive MPU settings or preserve recovery mechanisms useful during bring-up. None of those defaults should automatically become production policy.
The product team has to review them. That means deciding which interfaces are still needed, which masters require access to which resources, which recovery paths must remain available, and which capabilities should disappear before manufacturing.
The vendor BSP tells you how the platform can be used.
The product architecture must decide how much of that capability should remain available.
From Hardware Weakness to Product Architecture
The black-hole example is useful because it is a severe weakness that is hard to fix and forces us to look beyond the immediate fault.
The interesting question is not only how to stop one access to one address.
It is how to discover what can go wrong, reduce which components can trigger it, remove unnecessary access paths, use the available hardware protections where they actually apply, and provide recovery outside the failure domain when complete prevention is impossible.
That is the practical side of defense in depth.
At Embedded Expertise, this is the kind of analysis we perform on real embedded platforms: not only checking whether the nominal software works, but examining what the system allows when something goes wrong, then turning those findings into concrete architecture and hardening decisions.
If your product depends on a complex SoC, the useful question is not only whether it works as intended.
It is also what happens when something happens that was never supposed to happen.
Enjoyed this article?
Embedded Notes is an occasional, curated selection of similar content, delivered to you by email. No strings attached, no marketing noise.
Related resources
- Defense in Depth, When the Unexpected Happens
- Immutability: The Cornerstone of Embedded Defense
- Watchdogs: One Concept, Many Meanings
- Platform Architecture service · en | fr
- Embedded Cybersecurity · en | fr
- Training · en | fr