logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·30.10.26
← Back to news

When the Debugger Lies

Original article
danielmangum.com|56 points|17 comments|by hasheddan|Sep 22, 2026

When the Debugger Lies

By Daniel Mangum | September 18, 2026 | 11-minute read

Recently, I joined the team at Nordic Semiconductor, and I've spent the last several weeks diving deep into the security architecture of the nRF54L series.

To get up to speed, I've been employing my preferred method of low-level exploration: writing actual firmware manually manipulating registers via a debugger. During this process, I encountered some baffling values in memory while poking around the Key Management Unit (KMU).

As it happened, I ran into a classic problem. However, diagnosing it required a granular understanding of the System on Chip (SoC) internals and the specific way the debugger interfaces with those components.

The Security Landscape of nRF54L

The nRF54L series boasts a sophisticated security suite. The primary pillars include:

  • Arm TrustZone: Integrated into the Cortex-M33 core.
  • CRACEN: A dedicated cryptographic accelerator.
  • KMU (Key Management Unit): The heart of sensitive data storage.

The KMU's primary role is to manage the Secure Information Configuration Region (SICR), where it stores metadata and key seeds. Interaction with these slots is handled by issuing specific tasks to the KMU, a process that is strictly restricted to secure mode.

The Software Abstraction Layer

In a production environment, firmware rarely talks to the KMU directly. Instead, PSA (Platform Security Architecture) drivers provide an abstraction layer for managing keys.

For instance, a call to psa_generate_key() eventually triggers import_key_for_kmu() within the CRACEN PSA driver. The logic looks roughly like this:

static psa_status_t import_key_for_kmu( 
    const psa_key_attributes_t *attributes, 
    const uint8_t *data, 
    size_t data_length, 
    uint8_t *key_buffer, 
    size_t key_buffer_size, 
    size_t *key_buffer_length, 
    size_t *key_bits) 
{ 
    size_t opaque_key_size; 
    psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED; 
    int slot_id = CRACEN_PSA_GET_KMU_SLOT(MBEDTLS_SVC_KEY_ID_GET_KEY_ID(psa_get_key_id(attributes))); 
    psa_key_attributes_t stored_attributes; 

    status = cracen_get_opaque_size(attributes, opaque_key_size); 
    if (status != PSA_SUCCESS) { return status; } 

    if (key_buffer_size < opaque_key_size) { 
        return PSA_ERROR_BUFFER_TOO_SMALL; 
    } 

    status = cracen_kmu_provision(attributes, slot_id, data, data_length); 
    if (status != PSA_SUCCESS) { return status; } 

    status = cracen_kmu_get_builtin_key(slot_id, stored_attributes, key_buffer, key_buffer_size, key_buffer_length); 
    if (status != PSA_SUCCESS) { return status; } 

    *key_bits = psa_get_key_bits(stored_attributes); 
    return status; 
}

Understanding KMU Slots

Each KMU slot is identified by an ID ranging from 000 to 249249249. A slot can hold the following data:

  1. Metadata: 32-bit32\text{-bit}32-bit value.
  2. Destination Address: 32-bit32\text{-bit}32-bit value.
  3. Value: 128-bit128\text{-bit}128-bit value.
  4. Revocation Policy: 2-bit2\text{-bit}2-bit value.

Slot State Machine

The lifecycle of a slot is governed by a state machine. I've visualized the transitions below:

The ROTATING policy is particularly useful for experimentation, as it allows a slot to return to the Erased state after a revoke task is issued. Additionally, the PUSH task allows the KMU to write the stored value directly to the pre-defined destination address.

Provisioning Logic

The provisioning process is handled by cracen_kmu_key_slot_provision(), which wraps the lower-level HAL call:

static int cracen_kmu_key_slot_provision( 
    const nrfx_kmu_key_slot_data_t *key_slot_data, 
    uint32_t slot_id) 
{ 
    int kmu_status; 
    uint8_t orig_write_buf_size; 
    cracen_kmu_key_slot_provision_write_enable_set(true, orig_write_buf_size); 
    kmu_status = nrfx_kmu_key_slot_provision(key_slot_data, slot_id); 
    cracen_kmu_key_slot_provision_write_enable_set(false, orig_write_buf_size); 
    return kmu_status; 
}

The underlying data structure in the Nordic Zephyr HAL is defined as:

typedef struct __PACKED { 
    uint32_t keyslot_value[KEY_SLOT_WORDS_COUNT]; // Key data
#if NRF_KMU_HAS_REVOKE_POLICY || defined(__NRFX_DOXYGEN__) 
    uint32_t revoke_policy; // Revocation policy
#endif 
    uint32_t keyslot_dest; // Destination for PUSH
#if NRF_KMU_HAS_METADATA || defined(__NRFX_DOXYGEN__) 
    nrfx_kmu_key_slot_metadata_t metadata; // Slot metadata
#endif 
} nrfx_kmu_key_slot_data_t;

Driver Constraints vs. Raw Access

While the KMU hardware is flexible, the PSA drivers impose strict rules. For example, while the hardware accepts any 32-bit32\text{-bit}32-bit metadata value, the PSA driver interprets those bits as a specific structure:

FieldBit WidthDescription
metadata_version4 bitsVersion of the metadata
key_usage_scheme2 bitsHow the key is used
reserved8 bitsReserved for future use
algorithm6 bitsCryptographic algorithm
size3 bitsKey size
rpolicy2 bitsRevocation policy
usage_flags7 bitsUsage permissions

Warning: If you manually provision data that doesn't conform to these PSA driver expectations, you are going to have a bad time when attempting to use those keys via the official drivers.

The Debugging Setup

To test this, I used an nRF54LM20 DK. I knew that if I corrupted the SICR, I could recover using an ERASEALL operation via the Control Access Port (CTRL-AP).

Debugger Setup Placeholder

My setup process was as follows:

  • Power up the development board.
  • Establish a connection via GDB.
  • Ensure the CPU is halted.
  • Verify secure access.

Because access port protection was disabled, the Secure Privileged Invasive Debug Enable (SPIDEN) signal was high. This allowed my on-board J-Link OB to operate with secure privileges, granting me access to the KMU. I began by reading the STATUS register to ensure the unit was ready for operations...