nrf52840: critical: reset loop when updating UICR.APPROTECT

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:

  1. 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. 


  2. 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)?  
Parents
  • Hi,

    Thank you for the feedback. I see that the documetnation is unclear in this regard. For persistent registers in the UICR the reset value does not indicate the value after a normal reset as these are presistent registers. Instead, this refers to the value the register has after an erase all operation. So if the register has been written to it will keep that value, and that include the whole 32 bit register, not just the lower 8 bits.

    Our tools will always write 0x5A to the whole word (so that it reads 0x0000005A) and the register has no other uses, so this is what I recomend that you do (and update your nrf_enable_access_port_protection() function accordingly).

  • Thanks for your feedback. 

    I have already updated my nrf_enable_access_port_protection() function to check only for the last byte instead of the whole word.

    void nrf_enable_access_port_protection(void)
    {
    	if (NRF_UICR->APPROTECT & 0x000000FF != 0x00)
        {
    		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-");
    	}
    }


    Would you recommend writing 0x00000000 instead of 0xFFFFFF00 as well? 

    flash_write_word((uint32_t)&NRF_UICR->APPROTECT, 0xFFFFFF00);

Reply
  • Thanks for your feedback. 

    I have already updated my nrf_enable_access_port_protection() function to check only for the last byte instead of the whole word.

    void nrf_enable_access_port_protection(void)
    {
    	if (NRF_UICR->APPROTECT & 0x000000FF != 0x00)
        {
    		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-");
    	}
    }


    Would you recommend writing 0x00000000 instead of 0xFFFFFF00 as well? 

    flash_write_word((uint32_t)&NRF_UICR->APPROTECT, 0xFFFFFF00);

Children
  • Hi,

    This looks better, checking only the part of the register that matters. Wiht this implementation it does not matter if you write 0x00000000 or 0xFFFFFF00.

    Our tools will write 0xFFFFFF00 to lock the older revision deivices, so if you want to do the same you can do that, but the effect will be the same, so I do not recommend any particular approach in this case.

    (But of corse generally the good practice is to only change bits specified in the datasheet as in some cases there may be undocumented/hidden fields, though tnat is not the case here. And the same time our tools write 0x0000005A (not 0xFFFFFF5A) to unlock the 3. revision devices).

Related