Authenticated Debug Access (APPROTECT)

We are preparing a product for production and have enabled authenticated debug access on the NRF52840. Because authenticated debug access is not described in any Nordic documentation or security notices, we are seeking review of our implementation to ensure that we have not defeated the security model of the NRF52840. 

Assumptions

Clarifications

Let's start with some statements made in the Rev 3 Errata listed above (4413_679 v1.3 / 2024-03-04)

Section 4.1 [249] DIF: Access port protection needs software interface configuration describes the need for SW intervention to enable the debugging access. We have taken this to mean that, with authentication, the application can securely enable/disable debugging via:

NRF_APPROTECT->DISABLE = APPROTECT_DISABLE_DISABLE_SwDisable; // enable debug
NRF_APPROTECT->DISABLE = ~APPROTECT_DISABLE_DISABLE_SwDisable; // disable debug

Thereby preventing unauthorized access to the integrated flash. Is this correct?


"Working with the nRF52 Series' improved APPROTECT" ( Working with the nRF52 Series' improved APPROTECT) does not describe a process for securely enabling debug access. Instead, the section "Unlocking: nrfjprog or CTRL-AP ERASEALL" describes a requirement to erase all flash in order to unlock debug access. Is this simply an incomplete statement and it is in fact possible to safely unlock the debug access using the NRF_APPROTECT register described above?


The KConfig option NRF_APPROTECT_USER_HANDLING is not available to NRF52: https://github.com/zephyrproject-rtos/zephyr/blob/c98f6bccc4b83fd801ba7a7f8ece320987ea19cf/soc/nordic/Kconfig#L147-L153

config NRF_APPROTECT_USER_HANDLING
	bool "Allow user handling"
	depends on !SOC_SERIES_NRF52X
	help
	  When this option is selected, the SystemInit() function does not
	  touch the APPROTECT mechanism, allowing the user code to handle it
	  at later stages, for example, to implement authenticated debug.

The help mentions that this option would be used to implement authenticated debug. Does this mean that authenticated debug is not possible on NRF52 or simply unimplemented and/or undocumented by Nordic? As stated, we feel that we have implemented authenticated debug by protecting access to alteration of NRF_APPROTECT, but statements like this lead to questions about whether we may inadvertently introduce some vulnerability.

Implementation

Next, I will describe our implementation. We are seeking feedback, keeping the assumptions listed above in mind.

We have followed the guidance provided by the NRF52 Reference Manual(4413_417 v1.11) p.68 "Access port protection controlled by hardware and software - This information refers to build codes Fxx and later.", but it does not provide guidance for enabling authenticated debug access, it only repeats that the protection can be disabled by ERASEALL, which does not meet our requirements for authenticated debug access with erasing flash.

Enable NRF_APPROTECT_USER_HANDLING

Patch the KConfig mentioned above:

diff --git a/soc/arm/nordic_nrf/Kconfig b/soc/arm/nordic_nrf/Kconfig
index c2129db64b3..e680f2e08e5 100644
--- a/soc/arm/nordic_nrf/Kconfig
+++ b/soc/arm/nordic_nrf/Kconfig
@@ -94,7 +94,6 @@ config NRF_APPROTECT_LOCK

 config NRF_APPROTECT_USER_HANDLING
 	bool "Allow user handling"
-	depends on !SOC_SERIES_NRF52X
 	help
 	  When this option is selected, the SystemInit() function does not
 	  touch the APPROTECT mechanism, allowing the user code to handle it

And patch mdk/system_nrf52_approtect.h:

diff --git a/nrfx/mdk/system_nrf52_approtect.h b/nrfx/mdk/system_nrf52_approtect.h
index 6b31402..267cb08 100644
--- a/nrfx/mdk/system_nrf52_approtect.h
+++ b/nrfx/mdk/system_nrf52_approtect.h
@@ -47,6 +47,10 @@ static inline void nrf52_handle_approtect(void)
                 /* Prevent processor from unlocking APPROTECT soft branch after this point. */
                 NRF_APPROTECT->FORCEPROTECT = APPROTECT_FORCEPROTECT_FORCEPROTECT_Force;
             }
+        #elif  defined (ENABLE_APPROTECT_USER_HANDLING)
+                //Implement USER_HANDLING mode as found in system_nrf53_appprotect.
+                //This mode leaves the APPROTECT configuration as is, and does not change it.
+
         #else
             if (nrf52_configuration_249())
             {

Full reference to these lines: https://github.com/NordicSemiconductor/nrfx/blob/11f57e578c7feea13f21c79ea0efab2630ac68c7/mdk/system_nrf52_approtect.h#L34-L59

Bootloader

CONFIG_NRF_APPROTECT_USER_HANDLING=y. With the patch, nrf52_handle_approtect() is a nop.

Application

CONFIG_NRF_APPROTECT_USER_HANDLING=y. With the patch, nrf52_handle_approtect() is a nop.

Our application implements authenticated access. Behind that, we allow an authenticated user to unlock the flash:

NRF_APPROTECT->DISABLE = APPROTECT_DISABLE_DISABLE_SwDisable; // enable debug
NRF_APPROTECT->DISABLE = ~APPROTECT_DISABLE_DISABLE_SwDisable; // disable debug

Testing has shown that this does enable/disable access, e.g. allowing nrfutil, nrfjprog, jlink, to connect after the NRF_APPROTECT->DISABLE is set to SwDisable.

Image

In order to ensure that UICR is written correctly at the factory, the data is written to a hex that is merged with our factory hex, with the intention of setting UICR to prevent unauthorized debug access to the product.

import sys
from intelhex import IntelHex
from enum import IntEnum, unique


@unique
class UICR_Addresses(IntEnum):
    APPROTECT = 0x10001208


@unique
class APPROTECT_Values(IntEnum):
    Disabled = 0xFF  # Hardware disable of access port protection for devices where access port protection is controlled by hardware.
    HwDisabled = 0x5A  # Hardware disable of access port protection for devices where access port protection is controlled by hardware and software.
    Enabled = 0x00


ih = IntelHex()
# NOTE: all writes must be 4-bytes and aligned
ih.puts(
    int(UICR_Addresses.APPROTECT), APPROTECT_Values.HwDisabled.to_bytes(4, "little")
)
ih.write_hex_file(sys.stdout)

Looking at this now, I am concerned that it's DISABLING the APPROTECT on startup rather than ENABLING it. I think this is important because it is what mitigates the vulnerability in previous revisions? Specifically, this value is fetched in hardware at startup, mitigating the vulnerability. If I am understanding correctly, then this should be "Disabled", not "Enabled" - or "HwDisabled"?

"Step 2: Setting UICR.APPROTECT" from  Working with the nRF52 Series' improved APPROTECT  suggests writing this in the app (and bootloader?) and I suppose we should do that to make sure UICR is always "correct" - though, the app does not touch UICR, presently.

Conclusion

Thanks for your review! I hope that this analysis of authenticated debug access on the NRF52 Rev 3+ will help other companies that require the same feature.

Cheers,

JP

  • Corrections:

    "which does not meet our requirements for authenticated debug access with erasing flash." should be "which does not meet our requirements for authenticated debug access without erasing flash."

    "If I am understanding correctly, then this should be "Disabled", not "Enabled" - or "HwDisabled"?" should be "If I am understanding correctly, then this should be "Enabled", not "Disabled" or "HwDisabled"?"

  • Hi,

    Thank you for sharing. I would like to start by a disclaimer, as I cannot guarantee the security of you design. But I can comment if I see any issues. Fundamentally the approach looks good. Revision 3 of the nRF52840 with the updated AP Protech mechanism has the requiered hardware features to implement authenticated debuging, as you have done.

    Thereby preventing unauthorized access to the integrated flash. Is this correct?

    Yes, this is a correct understanding. When debug is allowed in the UICR, it is up to software to enable/disable and potentially lock the debug interface which can be used to implement authenticated debugging.

    Is this simply an incomplete statement and it is in fact possible to safely unlock the debug access using the NRF_APPROTECT register described above?

    Yes, you are right. That block post covers the most typical use case of AP protection, and not does not cover or authenticated debugging in to account. The relevat features is documented in the product specification, though, under Access port protection controlled by hardware and software.

    Does this mean that authenticated debug is not possible on NRF52 or simply unimplemented and/or undocumented by Nordic?

    It is not supported in the sense that we do not provide any official guidance or tools for it, other than the description of the hardware features itself in the product specification. So implementing it will be up to you and it is your responsibility to ensure that it meets your requierments with regards to functionality and security.

    As you have seen, CONFIG_NRF_APPROTECT_USER_HANDLING is not implemented for the nRF52 series devices (this goes all the way down to the MDK implementation, which does not support it, unlike for newer devices. However, your implementation of this looks good, and I do not have any comments regarding that.

    Looking at this now, I am concerned that it's DISABLING the APPROTECT on startup rather than ENABLING it. I think this is important because it is what mitigates the vulnerability in previous revisions?

    The debug interface will be locked by default after reset even if allowed in the UICR, and will not be open until NRF_APPROTECT->DISABLE. So until tha tis writte to, debug is not possible.

    "Step 2: Setting UICR.APPROTECT" from  Working with the nRF52 Series' improved APPROTECT  suggests writing this in the app (and bootloader?) and I suppose we should do that to make sure UICR is always "correct" - though, the app does not touch UICR, presently.

    UICR.APPROTECT need to be written to to open the debug interface. You can either do that in firmware, or via a debugger. For instance if you use "nrfutil device recover" that will among other things write to the UICR to open the debug interface (it will also write a small pice of firmware tha twrites to NRF_APPROTECT->DISABLE so that the device can subsequently be programmed via the debug interface without needing a new recover operation, but that is less relevant here).

    The remaining point which I assume you have implemened but not share he details for is how you authenticate before opening the debug interface from the nRF. That needs to be doen in a secure manner, typically using some asyncroneous cryptography so that the firmware on the nRF can authenticate the debugger before opening up for debugging. I do not have specific advice there, though.

  • HI  ,

    Thank you for confirming the approach! Just a few more clarifications:

    UICR.APPROTECT need to be written to to open the debug interface.

    Our present approach of unconditionally writing "Disabled" to the UICR, via the factory HEX, is OK because it is only one half of the lock, the other half being NRF_APPROTECT->DISABLE? Would it be more secure to write "Enabled" via the factory HEX and then write "Disabled"/"Enabled" along with the authenticated application code that alters the NRF_APPROTECT->DISABLE?

    Re: UICR, we are not setting APPPROTECT USE UICR, and because of our patch, we would never hit this:

            #else
                if (nrf52_configuration_249())
                {
                    /* Load APPROTECT soft branch from UICR.
                       If UICR->APPROTECT is disabled, POWER->APPROTECT will be disabled. */
                    NRF_APPROTECT->DISABLE = NRF_UICR->APPROTECT;
                }
            #endif

    https://github.com/NordicSemiconductor/nrfx/blob/11f57e578c7feea13f21c79ea0efab2630ac68c7/mdk/system_nrf52_approtect.h#L50-L57

    Which seems to write NRF_APPROTECT->DISABLE using the UICR. It's confusing, because if UICR is "doing something on its own", why is there this (disabled) runtime code to write the NRF_APPROTECT->DISABLE with it?

    The debug interface will be locked by default after reset even if allowed in the UICR, and will not be open until NRF_APPROTECT->DISABLE. So until tha tis writte to, debug is not possible.

    That is what we've been assuming, but it seems to contradict this article:  Working with the nRF52 Series' improved APPROTECT (emphasis mine)

    """

    The new access port protection mechanism is configured in two steps: in the hardware and in the firmware. At reset, the UICR.APPROTECT value is fetched in hardware. Then a function in MDK version 8.45.0 (or newer) is executed during startup to complete the firmware step. If the firmware step is skipped then the device may appear locked but it will not be completely protected.

    """

    If this quote is correct, then it indicates some problems for our approach, I think.

    • We have set UICR.APPROTECT to "Disabled" (0x5A), instead of "Enabled" (0x00) as suggested in the article. It says that UICR is fetched by the hardware at startup. Does this mean that there would be a brief window at reset where the DAP is unlocked, unless UICR.APPROTECT is set to "Enabled"?
    • It says "Then a function in MDK version 8.45.0 (or newer) is executed during startup to complete the firmware step." I think that we are OK as long as that firmware step is not the function that we have patched into a NOP. So we need to know exactly what firmware function this is referring to to avoid the "device may appear locked but it will not be completely protected" state that the article warns of.

    Thanks again for your time!

    JP

  • Hi,

    J.P. Hutchins said:
    Our present approach of unconditionally writing "Disabled" to the UICR, via the factory HEX, is OK

    Yes, that is OK and a requierment for implementing authenticated debug. (If UICR.APPROTECT does nto have the magic word to unlock it, there is no way software can open the debug interface).

    J.P. Hutchins said:
    Which seems to write NRF_APPROTECT->DISABLE using the UICR. It's confusing, because if UICR is "doing something on its own", why is there this (disabled) runtime code to write the NRF_APPROTECT->DISABLE with it?

    This is mostly just a way of implementing it, the point is to write the magic word 0x5A. When doe like this, the correct magic word is only writen to NRF_APPROTECT->DISABLE if it is also in UICR.APPROTECT. I do not see a funcional difference if you had written a hard coded magic word not read from UICR (particularily as you know the magic word will always be present in UICR in your case as it is requiered when implementing authenticated debugging. 

    J.P. Hutchins said:
    Does this mean that there would be a brief window at reset where the DAP is unlocked, unless UICR.APPROTECT is set to "Enabled"?

    No, the debug interface is locked down unless both the HW and SW path opens it up. However, we state in the product specification that you must lock both the UICR and SW path and recommend also writing to APPROTECT.FORCEPROTECT. However, this will make authenticated debugging impossible as there will be no way to unlock the debug interface.

    J.P. Hutchins said:
    So we need to know exactly what firmware function this is referring to to avoid the "device may appear locked but it will not be completely protected" state that the article warns of.

    This goes back to tha the article and the product specification specifies that you shall lock both the HW and FW path and this is to be on the safe side, in case one of the paths is compromized by a hypotetical attack. But by design, locking one path is enough. And again, it is the only way you can implement authenticated debuggign.

    PS: There is a twist here. You can write to the UICR and flip bits from 1 to 0 during the lifetime of the product (but not the other way around as that would requier a full chip erase). That should make it possible to leave UICR.APPROTECT 0xFF and then when you have doen your authentication procedure and want to open the debug interface first write the magic word 0x5A to UICR.APPROTECT and perform a reset (which is requiered for UICR changes to take effect). After that authenticate again and proceee with opening the debug interface via NRF_APPROTECT->DISABLE. This would then keep UICR.APPROTECT alo locked until the first authenticate debug session (at which poit tthe device is probably no longer out in the field).

  • Hi  ,   I'm working with  on this project.   

    Firstly, thank you for the detailed responses and providing clarification! 

    Regarding your comment about "PS: There's a twist here"  extra level of protection:  It seems like that may be incorrect.  The UICR.APPROTECT register is set to "disabled"  when 0xFF, meaning the device is unsecured.  So when left in this state, the firmware would only be able to set the device to "HwDisabled"  or "Enabled" (secured) by clearing bits.   

Related