// HACKER NEWS — CYBERSECURITY
When the Debugger Lies
I’ve spent the last few weeks working with the security architecture of the
nRF54L series from Nordic
Semiconductor (in case you missed it, I recently
joined Nordic!).
While doing so, I have engaged my typical low-level learning technique of
eschewing writing firmware for manually poking at registers using the debugger.
A few nights ago I found myself observing unexpected values in memory when
working with the key management unit. It turned out to be a familiar issue, but
one that requires an understanding of the internal system on chip (SoC)
components, and how the debugger interacts with them, to diagnose.
For a bit of background, the nRF54L series has a fairly advanced set of security
capabilities, headlined by Arm
TrustZone support in
the Cortex-M33
core, a
CRACEN cryptographic
accelerator,
and a Key Management Unit
(KMU). The
KMU is used for storing sensitive data, such as key seeds and associated
metadata, in the Secure Information Configuration Region
(SICR).
The SICR is divided into slots. These slots are targeted by issuing tasks to the
KMU, which can only be accessed in secure mode. Typically, application firmware
doesn’t interact with the KMU directly. Instead, PSA
drivers are implemented
to abstract the generation, storage, and usage of keys. For example, if
invoking
psa_generate_key(),
the operation eventually results in a call to
import_key_for_kmu()
in the CRACEN PSA
driver.
A slot can either be erased, provisioned, or revoked. The
datasheet
includes a helpful diagram of the state machine.
Slots have an ID (0 - 249) and can store metadata (32 bits), a destination
address (32 bits), a value (128 bits), and a revocation policy (2 bits). The
latter determines how state changes when a key is in the provisioned state and
various tasks that reference its slot ID are issued to the KMU. When
experimenting with the KMU, it is easiest to use the ROTATING revocation
policy, which dictates that the slot transitions back to the erased state when a
revoke task is issued.
When the PUSH task is issued, the value in the slot is written to the
destination address that was specified when the key was provisioned. The
provisioning process is documented in the
datasheet,
but it can also be seen in the cracen_kmu_key_slot_provision()
implementation:
The nrfx_kmu_key_slot_data_t definition can be found in the Nordic Zephyr
Hardware Abstraction Layer
(HAL).
You could write some fairly straightforward firmware, or even use the CRACEN
KMU
sample,
build it, then flash it onto a development kit to easily provision a key to the
KMU. However, if using the supported drivers (which you absolutely should),
additional restrictions are placed on the values that you can use when
provisioning a key slot. For example, while the KMU supports any 32 bit value
for metadata, the PSA driver assigns
meaning
to each of the bits.
Similarly, there are restrictions on the values that you can write and the
destination to which a given type of key is pushed. If manually interacting with
the KMU, the metadata, value, and destination are much more flexible. However,
if you provision non-conformant data into slots in the KMU, then attempt to
interact with it using the supported drivers, you are going to have a bad time.
Knowing the risks, and that I could restore the SICR on nRF54LM20
DK with
an
ERASEALL
operation on the control access port
(CTRL-AP),
I had powered up the board and connected with
GDB. As previously mentioned, the
KMU can only be accessed in secure mode. However, when access port protection
is not
enabled,
the Secure Privileged Invasive Debug Enable
(SPIDEN)
signal is driven high, and the on-board J-Link debugger (J-Link
OB) can
operate with secure privileges.