Can't use CONFIG_FPROTECT=y in MCUBOOT+APP (with TFM), compilation results in "No fprotect backend selected." CMake error

Hi all,

I wanted to enable read/write protection for MCUBOOT partition in my firmware from the application and encountered a strange problem. The build (build system - default, device nrf9160, SDK v2.7.0) for nrf9160dk_nrf9160_ns with MCUBOOT(child image) and TFM results with error:

CMake Error at C:/ncs/v2.7.0/nrf/lib/fprotect/CMakeLists.txt:15 (message):
No fprotect backend selected.

My research showed that the device tree for NS board has no SPU, ficr and uicr peripherals (why??). And if I try to add these peripherals in a device tree overlay file I get following compilation errors:

In file included from C:/ncs/v2.7.0/zephyr/include/zephyr/toolchain.h:50,
from C:/ncs/v2.7.0/zephyr/include/zephyr/kernel_includes.h:23,
from C:/ncs/v2.7.0/zephyr/include/zephyr/kernel.h:17,
from C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:7:
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:176:20: error: 'NRF_FICR' undeclared here (not in a function); did you mean 'NRF_FICR_S'?
176 | CHECK_DT_REG(ficr, NRF_FICR);
| ^~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/toolchain/gcc.h:87:51: note: in definition of macro 'BUILD_ASSERT'
87 | #define BUILD_ASSERT(EXPR, MSG...) _Static_assert(EXPR, "" MSG)
| ^~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:69:53: note: in expansion of macro '__DEBRACKET'
69 | #define __GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET val
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:64:9: note: in expansion of macro '__GET_ARG2_DEBRACKET'
64 | __GET_ARG2_DEBRACKET(one_or_two_args _if_code, _else_code)
| ^~~~~~~~~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:59:9: note: in expansion of macro '__COND_CODE'
59 | __COND_CODE(_XXXX##_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:180:9: note: in expansion of macro 'Z_COND_CODE_1'
180 | Z_COND_CODE_1(_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:378:23: note: in expansion of macro 'COND_CODE_1'
378 | #define UTIL_OR(a, b) COND_CODE_1(UTIL_BOOL(a), (a), (b))
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:101:17: note: in expansion of macro 'UTIL_OR'
101 | UTIL_OR(UTIL_NOT(DT_REG_HAS_IDX(DT_NODELABEL(lbl), 0)), \
| ^~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:176:1: note: in expansion of macro 'CHECK_DT_REG'
176 | CHECK_DT_REG(ficr, NRF_FICR);
| ^~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:102:25: error: expression in static assertion is not an integer
102 | (DT_REG_ADDR(DT_NODELABEL(lbl)) == (uint32_t)(mdk_addr))))
| ^
C:/ncs/v2.7.0/zephyr/include/zephyr/toolchain/gcc.h:87:51: note: in definition of macro 'BUILD_ASSERT'
87 | #define BUILD_ASSERT(EXPR, MSG...) _Static_assert(EXPR, "" MSG)
| ^~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:69:53: note: in expansion of macro '__DEBRACKET'
69 | #define __GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET val
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:64:9: note: in expansion of macro '__GET_ARG2_DEBRACKET'
64 | __GET_ARG2_DEBRACKET(one_or_two_args _if_code, _else_code)
| ^~~~~~~~~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:59:9: note: in expansion of macro '__COND_CODE'
59 | __COND_CODE(_XXXX##_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:180:9: note: in expansion of macro 'Z_COND_CODE_1'
180 | Z_COND_CODE_1(_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:378:23: note: in expansion of macro 'COND_CODE_1'
378 | #define UTIL_OR(a, b) COND_CODE_1(UTIL_BOOL(a), (a), (b))
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:101:17: note: in expansion of macro 'UTIL_OR'
101 | UTIL_OR(UTIL_NOT(DT_REG_HAS_IDX(DT_NODELABEL(lbl), 0)), \
| ^~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:176:1: note: in expansion of macro 'CHECK_DT_REG'
176 | CHECK_DT_REG(ficr, NRF_FICR);
| ^~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:275:19: error: 'NRF_SPU' undeclared here (not in a function); did you mean 'NRF_FPU'?
275 | CHECK_DT_REG(spu, NRF_SPU);
| ^~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/toolchain/gcc.h:87:51: note: in definition of macro 'BUILD_ASSERT'
87 | #define BUILD_ASSERT(EXPR, MSG...) _Static_assert(EXPR, "" MSG)
| ^~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:69:53: note: in expansion of macro '__DEBRACKET'
69 | #define __GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET val
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:64:9: note: in expansion of macro '__GET_ARG2_DEBRACKET'
64 | __GET_ARG2_DEBRACKET(one_or_two_args _if_code, _else_code)
| ^~~~~~~~~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:59:9: note: in expansion of macro '__COND_CODE'
59 | __COND_CODE(_XXXX##_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:180:9: note: in expansion of macro 'Z_COND_CODE_1'
180 | Z_COND_CODE_1(_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:378:23: note: in expansion of macro 'COND_CODE_1'
378 | #define UTIL_OR(a, b) COND_CODE_1(UTIL_BOOL(a), (a), (b))
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:101:17: note: in expansion of macro 'UTIL_OR'
101 | UTIL_OR(UTIL_NOT(DT_REG_HAS_IDX(DT_NODELABEL(lbl), 0)), \
| ^~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:275:1: note: in expansion of macro 'CHECK_DT_REG'
275 | CHECK_DT_REG(spu, NRF_SPU);
| ^~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:102:25: error: expression in static assertion is not an integer
102 | (DT_REG_ADDR(DT_NODELABEL(lbl)) == (uint32_t)(mdk_addr))))
| ^
C:/ncs/v2.7.0/zephyr/include/zephyr/toolchain/gcc.h:87:51: note: in definition of macro 'BUILD_ASSERT'
87 | #define BUILD_ASSERT(EXPR, MSG...) _Static_assert(EXPR, "" MSG)
| ^~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:69:53: note: in expansion of macro '__DEBRACKET'
69 | #define __GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET val
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:64:9: note: in expansion of macro '__GET_ARG2_DEBRACKET'
64 | __GET_ARG2_DEBRACKET(one_or_two_args _if_code, _else_code)
| ^~~~~~~~~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:59:9: note: in expansion of macro '__COND_CODE'
59 | __COND_CODE(_XXXX##_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:180:9: note: in expansion of macro 'Z_COND_CODE_1'
180 | Z_COND_CODE_1(_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:378:23: note: in expansion of macro 'COND_CODE_1'
378 | #define UTIL_OR(a, b) COND_CODE_1(UTIL_BOOL(a), (a), (b))
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:101:17: note: in expansion of macro 'UTIL_OR'
101 | UTIL_OR(UTIL_NOT(DT_REG_HAS_IDX(DT_NODELABEL(lbl), 0)), \
| ^~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:275:1: note: in expansion of macro 'CHECK_DT_REG'
275 | CHECK_DT_REG(spu, NRF_SPU);
| ^~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:326:20: error: 'NRF_UICR' undeclared here (not in a function); did you mean 'NRF_UICR_S'?
326 | CHECK_DT_REG(uicr, NRF_UICR);
| ^~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/toolchain/gcc.h:87:51: note: in definition of macro 'BUILD_ASSERT'
87 | #define BUILD_ASSERT(EXPR, MSG...) _Static_assert(EXPR, "" MSG)
| ^~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:69:53: note: in expansion of macro '__DEBRACKET'
69 | #define __GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET val
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:64:9: note: in expansion of macro '__GET_ARG2_DEBRACKET'
64 | __GET_ARG2_DEBRACKET(one_or_two_args _if_code, _else_code)
| ^~~~~~~~~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:59:9: note: in expansion of macro '__COND_CODE'
59 | __COND_CODE(_XXXX##_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:180:9: note: in expansion of macro 'Z_COND_CODE_1'
180 | Z_COND_CODE_1(_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:378:23: note: in expansion of macro 'COND_CODE_1'
378 | #define UTIL_OR(a, b) COND_CODE_1(UTIL_BOOL(a), (a), (b))
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:101:17: note: in expansion of macro 'UTIL_OR'
101 | UTIL_OR(UTIL_NOT(DT_REG_HAS_IDX(DT_NODELABEL(lbl), 0)), \
| ^~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:326:1: note: in expansion of macro 'CHECK_DT_REG'
326 | CHECK_DT_REG(uicr, NRF_UICR);
| ^~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:102:25: error: expression in static assertion is not an integer
102 | (DT_REG_ADDR(DT_NODELABEL(lbl)) == (uint32_t)(mdk_addr))))
| ^
C:/ncs/v2.7.0/zephyr/include/zephyr/toolchain/gcc.h:87:51: note: in definition of macro 'BUILD_ASSERT'
87 | #define BUILD_ASSERT(EXPR, MSG...) _Static_assert(EXPR, "" MSG)
| ^~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:69:53: note: in expansion of macro '__DEBRACKET'
69 | #define __GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET val
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:64:9: note: in expansion of macro '__GET_ARG2_DEBRACKET'
64 | __GET_ARG2_DEBRACKET(one_or_two_args _if_code, _else_code)
| ^~~~~~~~~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_internal.h:59:9: note: in expansion of macro '__COND_CODE'
59 | __COND_CODE(_XXXX##_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:180:9: note: in expansion of macro 'Z_COND_CODE_1'
180 | Z_COND_CODE_1(_flag, _if_1_code, _else_code)
| ^~~~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/include/zephyr/sys/util_macro.h:378:23: note: in expansion of macro 'COND_CODE_1'
378 | #define UTIL_OR(a, b) COND_CODE_1(UTIL_BOOL(a), (a), (b))
| ^~~~~~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:101:17: note: in expansion of macro 'UTIL_OR'
101 | UTIL_OR(UTIL_NOT(DT_REG_HAS_IDX(DT_NODELABEL(lbl), 0)), \
| ^~~~~~~
C:/ncs/v2.7.0/zephyr/soc/nordic/validate_base_addresses.c:326:1: note: in expansion of macro 'CHECK_DT_REG'
326 | CHECK_DT_REG(uicr, NRF_UICR);
| ^~~~~~~~~~~~

 Are there any examples of how to apply readout flash protection in nrf9160 ns builds?

Best regards, Valerii

Parents Reply
  • valerii7 said:
    Not completely, it fails with the same CMake error if I add only CONFIG_FPROTECT in child_image/mcuboot.conf But if I also add CONFIG_NORDIC_SECURITY_BACKEND it will compile ok.

    I guess that makes sense.

    Nice, then we got protection the bootloader it seems.

    My research showed that the device tree for NS board has no SPU, ficr and uicr peripherals (why??). And if I try to add these peripherals in a device tree overlay file I get following compilation errors:

    With TF-M (_ns), your application is split in two:

    NSPE and SPE. See  An Introduction to Trusted Firmware-M (TF-M) .

    Your Zephyr application is NSPE, while the SPE has access to the SPU. I guess that the same would be true for FICR and UICR as well.  This is why the application cannot access the SPU.

    To use FPROTECT from TF-M, you can for an custom secure service. See the TF-M secure peripheral partition sample for how this can be done. This does not support Zephyr, so you would have to use nrfx libraries directly to write to the SPU.

    Did this make sense?

Children
  • Ok, thank you for the answers. I have a couple more questions now:

    1) MCUBOOT with activated FPROTECT will do only write protection, I'm curious, why not both read and write? Is there any reason not to protect the bootloader from reading as well?

    2) "... This is why the application cannot access the SPU... " - that's a bit confusing, AFAIK MCUBOOT also belongs to NSPE but still has access to SPU. So why it works for mcuboot, but doesn't work for the app?

    3) "you would have to use nrfx libraries directly to write to the SPU." - do I understand this correctly, that the proposed solution is basically to use SPU registers "directly", without involving device tree and FPROTECT library?

    4) one more question - would it be possible to program protection mode over SWD, without bootloader/app code? Something like in STM32 where read/write flash protection can be enabled without any dedicated code, only through a JTAG/SWD. If yes, what would be the tool/command for that?

    Best regards, Valerii

  • valerii7 said:
    1) MCUBOOT with activated FPROTECT will do only write protection, I'm curious, why not both read and write? Is there any reason not to protect the bootloader from reading as well?

    Immutability only requires write-protection. As our bootloader is open source either way, I guess it is not any real reason to read-protect it.

    Not read-protecting MCUboot also allows for some features such as reading bootloader version or public key from the application. Not that those features are very well supported but at least they are possible.

    As you can hear, there are no really definitive reasins either for or against this as far as I know.

    valerii7 said:
    2) "... This is why the application cannot access the SPU... " - that's a bit confusing, AFAIK MCUBOOT also belongs to NSPE but still has access to SPU. So why it works for mcuboot, but doesn't work for the app?

    No, MCUboot runs before TF-M+App. So MCUboot runs in "secure-only". It does not have any TF-M, meanining it does not have the NSPE/SPE split => MCUboot has access to everything.

    valerii7 said:
    3) "you would have to use nrfx libraries directly to write to the SPU." - do I understand this correctly, that the proposed solution is basically to use SPU registers "directly", without involving device tree and FPROTECT library?

    Yes. TF-M does not use Zephyr, and therefore does not have access to devicetree or FPROTECT.

    valerii7 said:
    4) one more question - would it be possible to program protection mode over SWD, without bootloader/app code? Something like in STM32 where read/write flash protection can be enabled without any dedicated code, only through a JTAG/SWD. If yes, what would be the tool/command for that?

    You can write directly to registers using SWD, so almost yes.
    However, FPROTECT is not persistent, so this would be undone upon a power cycle.

    An alternative to writing from TF-M would be to add custom code to MCUboot to make MCUboot protect the application+TF-M before it boots. Not entirely sure how this would work with TF-M since TF-M uses the SPU to set Secure/Non-Secure to stuff when it starts though.

  • Sigurd, thank you very much for the detailed answers, now I understand the code much better and also see how to implement the desired functionality. The FPROTECT obviously doesn't fit my needs, since it's not persistent (I overlooked this).

    One last question - I think the UICR "User information configuration registers" could provide complete protection against firmware readout/copy via the debug interface. And, if coupled with an appropriate bootloader with encryption, could guarantee the complete protection of the firmware from copy/reuse. In this way, since FW update files are encrypted and there is also no access via the debug interface to the internal flash (access could be reenabled only after doing a complete erase of the flash), the unencrypted code is never exposed. Could you please confirm that my understanding is correct? The main purpose of the protection is to prevent the code from running on unauthorized hw (prevention of coping the complete device).

  • The feature you are describing is what we call APPROTECT or Access Port Protection. See Enabling access port protection mechanism:

    "

    Several Nordic Semiconductor SoCs or SiPs supported in the nRF Connect SDK offer an implementation of the access port protection mechanism (AP-Protect). When enabled, this mechanism blocks the debugger from read and write access to all CPU registers and memory-mapped addresses. Accessing these registers and addresses again requires disabling the mechanism and erasing the flash.

    "

    valerii7 said:
    Could you please confirm that my understanding is correct? The main purpose of the protection is to prevent the code from running on unauthorized hw (

    If you run Access Port Protection, noone should be able to read out your fw.

    If you then also encrypt your DFU, people should not be able to use intercepted DFU images.

    I agree with your understanding.

Related