Skip to main content

Embedded Expertise

Defense in Depth, When the Unexpected Happens

Cybersecurity usually starts at the periphery by blocking attack vectors. But what if the failure comes from the inside?

An attacker needs both an exploitable weakness and a way to reach it. Firewalls, authentication, secure boot, access control, restricted physical interfaces and similar measures are designed to close those paths.

That is essential. But it works only as long as the defenses hold, and as long as no relevant path has been overlooked.

In a previous article, I discussed the difference between perimetric and in-depth security. Perimetric security controls how an attacker can reach the system. Defense in depth adds internal layers so that one failed protection, one bug or one overlooked path does not automatically compromise the whole product.

This article looks at one very concrete example of why that second part matters.

The example is real, easy to reproduce on a popular platform, and starts with something that should never happen.

What Defense in Depth Adds

Defense in depth does not replace perimeter protection. It complements it.

The perimeter tries to prevent an attacker from reaching a vulnerability. Defense in depth also considers weaknesses inside the system, including weaknesses for which no known attack vector exists yet.

The reasoning is simple:

What if a condition that shouldn’t happen occurs anyway?

There are several ways this can happen.

The first is obvious: a perimeter protection fails. An attacker obtains access that the architecture assumed would remain unavailable.

The second is that a path through the perimeter was overlooked. Debug interfaces are a good example. On the i.MX8M Plus, the Debug Access Port provides a bridge between the external JTAG interface and on-chip memory-mapped resources. Transactions initiated through it are explicitly described as external debugger accesses.

The third case may not initially involve cybersecurity at all.

Software has bugs. A buffer overflow may corrupt a pointer. A driver may program the wrong physical address. Firmware on an auxiliary processor may calculate an invalid address. A DMA descriptor may be corrupted or incorrectly initialized.

At that point, this is a software reliability or safety problem.

But suppose an attacker discovers a legitimate input sequence that reliably triggers the bug. The attacker does not need direct access to the affected resource. They only need a repeatable way of causing the software to trigger the failure on their behalf. The bug has become part of an attack path.

Defense in depth is concerned with what happens next.

A Real Example: the 0x30000000 Black Hole

The popular NXP i.MX8M Plus provides a particularly simple example.

In the section detailing the memory map, the Reference Manual marks the 1 MB block based at 0x30000000 as Reserved.

Software is therefore not supposed to access it. But accessing it anyway is both remarkably easy and particularly devastating. The experiments below probably work on most stock Linux distributions, including Debian and Yocto images.

Important!Do not attempt running any of the following commands on a system that you cannot afford to crash.

From a Linux terminal, a physical read can be generated with a Python one-liner:

# python3 -c 'import mmap, os; fd = os.open("/dev/mem", os.O_RDWR | os.O_SYNC); mem = mmap.mmap(fd, 4096, offset=0x30000000); print(mem[0:4])'

Or, even simpler, when Busybox’s devmem is available:

# devmem 0x30000000

On the platforms I tested from various vendors, the result is consistently a complete and persistent system lockup.

So, is this a Linux crash?

No, since the same access can be generated before Linux is even running, directly from U-Boot:

u-boot=> md.l 30000000 1

More Than One Way To Reach It

The Linux and U-Boot commands are only convenient ways to demonstrate the failure.

The i.MX8M Plus is a heterogeneous SoC. Its architecture includes Cortex-A53 application processors, a Cortex-M7, DMA engines, debug infrastructure and multiple hardware processing blocks connected through AXI and AHB fabrics.

The Reference Manual consequently defines separate memory maps for the Cortex-A53, Cortex-M7, DMA subsystem and Debug Access Port.

In testing, the same system-wide failure can also be triggered from the Cortex-M7 (this is actually how the weakness was initially discovered) and through JTAG/DAP.

The JTAG case is particularly instructive.

JTAG is often treated primarily as a development concern. Yet the DAP is specifically intended to let an external debugger access on-chip memory-mapped resources. It is therefore part of the product attack surface whenever it remains accessible.

But even if every obvious external path were perfectly secured, the underlying weakness would still exist.

Any processing core or other bus master capable of issuing the relevant physical transaction could potentially trigger it. Depending on the actual routing and access rights configured in a system, that can include auxiliary processors, DMA-capable devices and other processing engines.

Not every DMA engine or accelerator necessarily has access to every physical address, so the precise reachable space depends on the SoC architecture.

But the architectural question remains the same:

Which components are capable of generating an operation that should never happen, and what happens if one of them does?

No operating system involved here. The Cortex-A53 side stops responding, but so does the Cortex-M7 and other logic that I tested. The failure affects the system as a whole, not just the operating system running on the application cores.

No useful kernel fault message on the console or other diagnostic log is produced before the lockup.

The Linux watchdog is already running, but it does not recover the system either. From the software point of view, the failure is permanent. Recovery requires external intervention, such as a hardware reset or power cycle.

This is why I refer to 0x30000000 as a black hole: from the externally observable behavior, a transaction goes in and normal system operation never comes back.

I deliberately do not try to diagnose the exact internal mechanism here. It could involve address decoding, interconnect behavior, error propagation or another SoC integration detail. We do not have enough evidence to say precisely what happens inside the silicon.

Nor is that the purpose of the example.

This is also not about pointing a finger at NXP. Modern SoCs integrate many processor cores, buses, memories, accelerators and IP blocks. Complex devices have limitations, errata and unexpected corner cases.

This one is useful because it is real, dramatic and easy to reproduce on a popular processor.

Why This Is a Problem

On a conventionally secured product, there may be no obvious external path to 0x30000000.

An ordinary application should not have arbitrary physical memory access. Production debug access should be controlled. Firmware should access only its intended resources. DMA engines should be programmed with valid descriptors.

Under those assumptions, the black hole should never be reached. Then reality strikes back.

A perimeter protection can fail. A physical or logical path can have been overlooked. A bug in software running on any processor with sufficient access can generate the wrong address. A DMA engine can be misconfigured. A corrupted descriptor can send a transaction somewhere it was never supposed to go.

And some of these cases can become cybersecurity issues without an attacker ever gaining direct access to physical memory.

Consider this path:

legitimate external input

latent software bug

corrupted pointer or DMA descriptor

physical access to 0x30000000

system-wide persistent lockup

The attacker does not need /dev/mem. They do not need JTAG. They do not necessarily need arbitrary code execution.

They only need to identify a legitimate input pattern that makes existing software trigger the failure predictably.

That is where a latent bug can become an attack vector. This is also why Reserved is a requirement, not a protection.

The memory map tells correctly written software not to access the region. That is useful and necessary documentation. But the word Reserved does not itself stop a faulty or compromised component from issuing the transaction.

And There May Be Many More Black Holes

0x30000000 is unlikely to be the only interesting case in a device of this complexity.

The i.MX8M Plus memory map contains many Reserved areas and gaps. There is also no reason to expect every Reserved location to behave in the same way.

Different IP blocks explicitly document different behavior for reserved accesses. For example, the eDMA documentation states that reading or writing a reserved memory location generates a bus error. The ISI documentation likewise specifies transfer errors for accesses to reserved locations.
Other holes exist at the SoC integration level rather than inside a particular peripheral, and their behavior may be different.

So the problem is not really 0x30000000.

It is the assumption that everything which should never happen can safely be ignored because the perimeter is expected to block the attack vectors.

Defense in depth challenges that assumption.

What Can We Do About It?

The 0x30000000 black hole failure is therefore not just an odd hardware corner case. It is a system-wide weakness that may be triggered through several paths, directly or indirectly.

The next article will look at what can be done to prevent, contain or mitigate the effects of such a weakness.

Enjoyed this article?
Embedded Notes is an occasional, curated selection of similar content, delivered to you by email. No strings attached, no marketing noise.