Debug Port Disabled After Flashing TF-M Provisioning Image on nRF9151DK: Is This Intentional?

Context:

I'm testing the tfm_psa_template sample on the nRF9151DK and I got confused as to why I lose access to the debug port.

As required by the sample, I first flash the provisioning_image sample to write keys to OTP. After flashing this sample, I can still access the debug port without any issues.

However, once I flash the tfm_psa_template, I completely lose access to the debug port.

Interestingly, if I erase the flash and re-flash the tfm_psa_template, I regain access to the debug port.

This leads me to believe that something in the provisioning_image sample is causing the loss of debug port access.

From my understanding, the NRF_APPROTECT_HANDLING configuration controls access to the debug port for non-secure images, while NRF_SECURE_APPROTECT_HANDLING controls access for secure images.

By default, NRF_APPROTECT_HANDLING is set to NRF_APPROTECT_USE_UICR, assuming its dependencies have been met. Here's a reference from zephyr/soc/nordic/KConfig:

choice NRF_APPROTECT_HANDLING
	bool "APPROTECT handling"
	depends on SOC_SERIES_NRF52X || SOC_NRF5340_CPUNET || \
		   (SOC_NRF5340_CPUAPP && !TRUSTED_EXECUTION_NONSECURE) || \
		   SOC_SERIES_NRF91X
	default NRF_APPROTECT_USE_UICR
	help
	  Specifies how the SystemInit() function should handle the APPROTECT
	  mechanism.

Additional compilation definitions are created for NRF_APPROTECT_LOCK and NRF_APPROTECT_USER_HANDLING. Here's a reference from zephyr/modules/hal_nordic/nrfx/CMakeLists.txt:

zephyr_compile_definitions_ifdef(CONFIG_NRF_APPROTECT_LOCK
                                 ENABLE_APPROTECT)
zephyr_compile_definitions_ifdef(CONFIG_NRF_APPROTECT_USER_HANDLING
                                 ENABLE_APPROTECT_USER_HANDLING)
zephyr_compile_definitions_ifdef(CONFIG_NRF_SECURE_APPROTECT_LOCK
                                 ENABLE_SECURE_APPROTECT)
zephyr_compile_definitions_ifdef(CONFIG_NRF_SECURE_APPROTECT_USER_HANDLING
                                 ENABLE_SECURE_APPROTECT_USER_HANDLING)

These compilation definitions control the behavior of nrf_handle_approtect(void). Here's a reference from modules/hal/nordic/mdk/system_nrf91_approtect.h:

static inline void nrf91_handle_approtect(void)
{
    if (!nrf91_errata_36())
    {
        /* Target device does not support firmware-driven approtect. */
        return;
    }
    #if defined (NRF91_ERRATA_36_PRESENT) && NRF91_ERRATA_36_PRESENT
        #if defined (ENABLE_APPROTECT)
            /* Prevent processor from unlocking APPROTECT soft branch after this point. */
            NRF_APPROTECT_S->APPROTECT.FORCEPROTECT = (APPROTECT_APPROTECT_FORCEPROTECT_FORCEPROTECT_Force << APPROTECT_APPROTECT_FORCEPROTECT_FORCEPROTECT_Pos);

        #elif defined (ENABLE_APPROTECT_USER_HANDLING)
                /* Do nothing, allow user code to handle APPROTECT. Use this if you want to enable authenticated debug. */

        #else
            /* Load APPROTECT soft branch from UICR.
                If UICR->APPROTECT is disabled, APPROTECT->APPROTECT will be disabled. */
            NRF_APPROTECT_S->APPROTECT.DISABLE = NRF_UICR_S->APPROTECT == UICR_APPROTECT_PALL_HwUnprotected ? APPROTECT_APPROTECT_DISABLE_DISABLE_SwUnprotected : 0ul;
        #endif

        /* Secure APPROTECT is only available for Application core. */
        #if defined (ENABLE_SECURE_APPROTECT)
            /* Prevent processor from unlocking SECURE APPROTECT soft branch after this point. */
            NRF_APPROTECT_S->SECUREAPPROTECT.FORCEPROTECT = (APPROTECT_SECUREAPPROTECT_FORCEPROTECT_FORCEPROTECT_Force << APPROTECT_SECUREAPPROTECT_FORCEPROTECT_FORCEPROTECT_Pos);

        #elif defined (ENABLE_SECURE_APPROTECT_USER_HANDLING)
                /* Do nothing, allow user code to handle SECURE APPROTECT. Use this if you want to enable authenticated debug. */

        #else
            /* Load SECURE APPROTECT soft branch from UICR.
                If UICR->SECUREAPPROTECT is disabled, APPROTECT->SECUREAPPROTECT will be disabled. */
            NRF_APPROTECT_S->SECUREAPPROTECT.DISABLE = NRF_UICR_S->SECUREAPPROTECT == UICR_SECUREAPPROTECT_PALL_HwUnprotected ? APPROTECT_SECUREAPPROTECT_DISABLE_DISABLE_SwUnprotected : 0ul;
        #endif
    #endif
}

This function is called from SystemInit(void), but only for secure images. Here's a reference from modules/hal/nordic/nrfx/mdk/system_nrf91.c:

void SystemInit(void)
{
    #if !defined(NRF_TRUSTZONE_NONSECURE)
        /* [...] */
        nrf91_handle_approtect();
    #endif
    
    /* [...] */
}

For the provisioning_image sample, the NRF_SECURE_APPROTECT_HANDLING is disabled because the SOC_NRF5340_CPUAPP dependency is not set.

Therefore, since the provisioning_image is a non-secure image, it will execute:

/* Load APPROTECT soft branch from UICR.
    If UICR->APPROTECT is disabled, APPROTECT->APPROTECT will be disabled. */
NRF_APPROTECT_S->APPROTECT.DISABLE = NRF_UICR_S->APPROTECT == UICR_APPROTECT_PALL_HwUnprotected ? APPROTECT_APPROTECT_DISABLE_DISABLE_SwUnprotected : 0ul;

... causing the debug port to become disabled!

As it is written to UICR it is persistent across image flashes, unless it is flashed with the recover option.

Questions:

  1. Is this behavior intentional? I wasn't able to find any reference in the documentation indicating that the debug probe would be disabled after flashing the provisioning_image. While there is a brief mention of disabling debugging access to the nRF5340 network core, there’s nothing about restricting debug access for the non-secure image. It seems like some default configuration may have been changed without proper documentation in the README.
  2. Does the tfm_psa_template also build a secure image? Is there any sample available that demonstrates the use-case of both secure and non-secure images? Is this the intended usage pattern for real-world deployment? If it's not, why is nrf_handle_approtect(void) only called from secure images? I'd assume that disabling debug port access is a fairly standard "ask" for real-world deployments. Or is it only intended that this access be disabled during provisioning? If so, could this be added to the documentation?
  • Hi Vytautas, 
    Do you have any other DK that you can test for example the nRF9160DK ? 
    I got a tip from a coworker that this behavior can be related to this errata: 
    https://docs.nordicsemi.com/bundle/errata_nRF9151_Rev1/page/ERR/nRF9151/Rev1/latest/anomaly_151_36.html#anomaly_151_36

    You can also try the workaround in the errata to see if it help solving the issue. 

  • On the SiP it says "NRF9151 LACA A0 2422AAAC002TKFE"

    I managed to get debugging to work. I originally thought that UICR by itself isn't going to disable the debug port and was set only to indicate the debugging needs to be disabled.

    To enable debugging, apply the following patch:

    diff --git nrf/modules/trusted-firmware-m/tfm_boards/common/nrf_provisioning.c nrf/modules/trusted-firmware-m/tfm_boards/common/nrf_provisioning.c
    index 4ce7792e1..cd8c9e782 100644
    --- nrf/modules/trusted-firmware-m/tfm_boards/common/nrf_provisioning.c
    +++ nrf/modules/trusted-firmware-m/tfm_boards/common/nrf_provisioning.c
    @@ -29,7 +29,7 @@ static enum tfm_plat_err_t disable_debugging(void)
    
            if (approt_writable) {
                    nrfx_nvmc_word_write((uint32_t)&NRF_UICR_S->APPROTECT,
    -                                    UICR_APPROTECT_PALL_Protected);
    +                                    UICR_APPROTECT_PALL_HwUnprotected);
                    nrfx_nvmc_word_write((uint32_t)&NRF_UICR_S->SECUREAPPROTECT,
                                         UICR_SECUREAPPROTECT_PALL_Protected);
            } else {

    (You might also need to apply the workaround in the errata. In my case it wasn't necessary)

    The default behavior for the TF-M implementation for NRF is to disable the debugger.

    There doesn't seem to be a way to prevent this without modifying the code.

    (Perhaps something to look into for Nordic?)

    See nrf/modules/trusted-firmware-m/tfm_boards/common/nrf_provisioning.c:

    static enum tfm_plat_err_t disable_debugging(void)
    {
    	/* Configure the UICR such that upon the next reset, APPROTECT will be enabled */
    	bool approt_writable;
    
    	approt_writable = nrfx_nvmc_word_writable_check((uint32_t)&NRF_UICR_S->APPROTECT,
    							UICR_APPROTECT_PALL_Protected);
    	approt_writable &= nrfx_nvmc_word_writable_check((uint32_t)&NRF_UICR_S->SECUREAPPROTECT,
    							 UICR_SECUREAPPROTECT_PALL_Protected);
    
    	if (approt_writable) {
    		nrfx_nvmc_word_write((uint32_t)&NRF_UICR_S->APPROTECT,
    				     UICR_APPROTECT_PALL_HwUnprotected);
    		nrfx_nvmc_word_write((uint32_t)&NRF_UICR_S->SECUREAPPROTECT,
    				     UICR_SECUREAPPROTECT_PALL_Protected);
    	} else {
    		return TFM_PLAT_ERR_SYSTEM_ERR;
    	}
    
    	return TFM_PLAT_ERR_SUCCESS;
    }
    

    If NRF_APPROTECT_USER_HANDLING is set, you will likely need to modify the UICR register yourself to unlock the debugger. Though I haven't tried this out yet... though isn't UICR supposed to be locked and inaccessible? 

  • Hi Vytautas, 

    If you take a look at this: https://docs.nordicsemi.com/bundle/ps_nrf9151/page/dif.html#ariaid-title3

    You can find that the Debug/Access port is protected by both hardware and software. The UICR.APPROTECT is the Hardware protection. It's not just for indication. 

    To be honest I'm not too familiar with TFM and PSA, but my point of view is that after you run provisioning image and having identity key stored on UICR, you MUST have disable port locked. From security point of view, this is a MUST. I know that for developing you still want to be able to access debug port and want that we provide an option/configuration to disable this feature.


    But as security sample I don't think it's a good idea. This create a chance that product getting out on the market with this backdoor still open because the developer forgot to remove the DEBUG option. Trust me, this story is really not uncommon. 

    After the UICR.APPROTECT block the debug port access, the only way to change this value is to erase the UICR and the only way to erase the UICR is to do an Eraseall (if it's still not blocked).

  • The way it is now is acceptable to me.

    For debugging, we can just use a patched/modified file. 

    I was just outlying everything for anyone who stumbles across this ticket.

    Though I do think it should be added to the README.md that debug port will be disabled

  • I agree, the documentation should mention that the sample also demonstrate blocking debug port on the app core .

Related