Hi all,
I am aware of the different access port protection mechanisms (listed in my previous ticket here: Link). I did also test both revisions successfully:
- For older revisions <= 2: it is enough to write 0xFFFFFF00 to the UICR.APPROTECT register and perform any kind of reset, even a software reset by sys_reboot(SYS_REBOOT_COLD) does the job. After the reset it is no longer possible to read/write flash (without exploiting the known vulnerability), which is the desired behavior.
- For newer revisions >=3: the APPROTECT is enabled per default. To flash the device you would need to do recover/erase-all. Then after flashing, it is recommended for more security, but not necessary to:
- write 0xFFFFFF00 to the UICR.APPROTECT (as in older versions)
- write 0x00 to APPROTECT.FORCEPROTECT
This worked fine on my two nRF52840-dk development boards: one old (Dx revision) and a new one (Fx revision) using the following nrfjprog version:
nrfjprog version: 10.6.0
JLinkARM.dll version: 6.86d
The shocking thing was when I repeated the tests on the new hardware revision (Fx) with the following nrfjprog version:
nrfjprog version: 10.24.2 external
JLinkARM.dll version: 7.98
I then noticed that after writing 0xFFFFFF00 to UICR.APPROTECT, the board kept going into a reset loop. This turned out because the check "if (NRF_UICR->APPROTECT != 0xFFFFFF00)" was always returning true which leads to the first path of the code where I write the register and do a reboot. (s. Code snippet below).
--> Further debugging on the new Fx nRF52840-dk: the UICR.APPROTECT register has the value 0x0000005A after recover. After writing (0xFFFFFF00) in the first iteration, the register gets the value 0x00000000, which is != 0xFFFFFF00. Therefore, a reset takes place over and over again.
void flash_write_word(uint32_t address, uint32_t value)
{
const struct device *flash_device;
flash_device = DEVICE_DT_GET(DT_CHOSEN(zephyr_flash_controller));
if (flash_device == NULL)
{
LOG_ERR("Unable to get flash device binding");
return;
}
if(flash_write(flash_device, address, &value, sizeof(uint32_t)) != 0)
{
LOG_ERR("Word Flash write failed");
}
}
void nrf_enable_access_port_protection(void)
{
if (NRF_UICR->APPROTECT != 0xFFFFFF00)
{
LOG_WRN("Access Port Protection is not enabled --> Enable Access Port Protection now");
/* Enable Access Port Protection
* Access through debugger to CPU registers, mapped-memory and RAM will be disabled
* To disable protection ERASEALL command must be applied. */
flash_write_word((uint32_t)&NRF_UICR->APPROTECT, 0xFFFFFF00);
LOG_INF("Access Port Protection is now enabled --> Reboot to apply the config...");
// Sleep is only necessary to show the logs before reboot for debug purposes
k_sleep(K_MSEC(2000));
sys_reboot(SYS_REBOOT_COLD);
}
else
{
LOG_INF("Access Port Protection is already enabled");
monitoring_update_text("system.approtect", nrf52_configuration_249() ? "enabled/rev 3+" : "enabled/rev 2-");
}
}
This is however not the expected behavior: as mentioned in the documentation of the UICR.APPROTECT register below, the most significant bits of the register (24 bits) should have the value 1 after reset. Only the least significant byte might have a different value.
I understand, that the change of UICR.APPROTECT initial value after recover is caused by different nrfjprog versions. It is also clear for me now that I definitely have to check only for the least significant byte and not for the whole register value. However, this triggers other thoughts:
- why didn't I find any information about this in any approtect related ticket, although I went through almost all of them. Most of them do exactly the same if conditional to know whether the register value should be written and then do a reset. This is very critical, as it might lead to devices in field being kept in reset loop forever and are therefore of no use anymore.
- Shouldn't nrfjprog not only write to the least significant byte? Why write the most significant 3 bytes to 0?
- Did anything else interesting changed with the new revision (such register default values after reset)?