This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

MSP432E401Y: Recovering a "Locked" microcontroller does not perform a full mass erase

Part Number: MSP432E401Y
Other Parts Discussed in Thread: MSP-EXP432E401Y, UNIFLASH

We want to disable the external debug access to the MSP432 in order to protect our sensitive user data stored in the flash memory. This is crucial for our application. But it looks like the unlocking procedure does not perform a full erase possibly enabling an atacker to read out sensitive user data.

In detail: We set DBG1 of the BOOTCFG register to 0 and commit the new register value to the flash memory. Up to this point everything works as expected and we can't access the MSP432 an more using the debugger. Now we follow the instructions in section 3.3.4.3 (Recovering a "Locked" Microcontroller) of the technical reference manual. The section states: "The mass erase of the Flash memory caused by the sequence below occurs prior to the nonvolatile registers being restored." So we expect a mass erase to be performed. To verify whether this is the case, we manually write data to the flash memory (pretty much at the end of the flash region). After that we perform the unlock sequence with `dbgjtag -f @xds110 -Y unlock,mode=msp432e4` (we always need to execute this twice to work). Now our firmware doesn't load anymore and it causes us to be able to flash our firmware again (with the "Do not erase Flash memory" setting set). But: After that we are able to read the previously written (test) value from Flash memory. Did we omit one step?

The source code we are using to disable the debug interface is as follows:

       HWREG(FLASH_FMA) = 0x75100000;
       HWREG(FLASH_FMD) = 0x7FFFFFFC;
       HWREG(FLASH_FMC) = FLASH_FMC_WRKEY | FLASH_FMC_COMT;
       while(HWREG(FLASH_FMC) & FLASH_FMC_COMT); //Wait for completion

We also tried:

       HWREG(FLASH_FMA) = 0x75100000;
       HWREG(FLASH_FMD) = 0x7FFFFFFC;
       HWREG(FLASH_FMC) = FLASH_FMC_WRKEY | FLASH_FMC_WRITE;
       while(HWREG(FLASH_FMC) & FLASH_FMC_WRITE); //Wait for completion
       HWREG(FLASH_FMA) = 0x75100000;
       HWREG(FLASH_FMC) = FLASH_FMC_WRKEY | FLASH_FMC_COMT;
       while(HWREG(FLASH_FMC) & FLASH_FMC_COMT); //Wait for completion

  • Hello Christian,

    I have never encountered this issue. The Unlock sequence should erased the Flash completely.

    Can you re-create this issue or is it a one time thing?

    Also did you confirm that the updated DBG1 value is committed to the BOOTCFG register? You can confirm by resetting the power to the board (it has to be Power on Reset; that is remove power to the MCU, then power on the MCU) and then trying to access the device via the JTAG.

    Thanks,
    Sai
  • Hello Sai,

    I'm also working on the same project and am answering in place of Christian.

    > Can you re-create this issue or is it a one time thing?

    We have recreated the issue multiple times with two different MSP432E401Y.

    > Also did you confirm that the updated DBG1 value is committed to the BOOTCFG register?

    Yes, we did. JTAG access does not work as expected and we also execute the following code at the beginning of "main()" to check if DBG1 is 0:

    uint32_t bootConfig = HWREG(FLASH_BOOTCFG);
    if(bootConfig & 3)
    {
    //Blink LEDs
    //This is NOT executed on reboot after setting BOOTCFG.
    }

    But regardless if BOOTCFG is correctly set or not, is it correct that a mass erase should be performed when we execute the unlock sequence?

    Best regards,

    Sathya Laufer
  • Hello Sathya,

    You are right! Irrespective of the BOOTCFG settings , unlock sequence should erase the flash. In fact that's the first step before restoring the nonvolatile registers. The following is the relevant section from the TRM:

    I will work on this mid next week. To be sure can you provide the exact steps to re-create this issue?

    Thanks,

    Sai

  • Hello Sai,

    it wasn't easy to create a minimal project, but here it is :-):

    flashtest.tar.gz

    And the source code:

    #include <msp.h>
    #include "driverlib/driverlib.h"
    #include "driverlib/inc/hw_flash.h"
    
    #include <string>
    #include <memory>
    #include <array>
    
    #include <iomanip> //<-- Causing the error
    #include <sstream> //<-- Causing the error
    
    constexpr size_t FLASH_SECTOR_SIZE = 0x4000;
    constexpr uint32_t FLASH_NAME_ADDRESS = 0xE6580;
    constexpr size_t FLASH_NAME_SIZE = 0x100;
    
    constexpr uint32_t _ledPeripheral = SYSCTL_PERIPH_GPIOK;
    constexpr uint32_t _ledPort = GPIO_PORTK_BASE;
    constexpr uint8_t _ledGreenValue = (1 << 4);
    constexpr uint8_t _ledOrangeValue = (1 << 5);
    
    #define I32_TO_U8CARRAY(a, pos, n) {a[pos] = (uint8_t)(((n) >> 24) & 0xFF);a[pos + 1] = (uint8_t)(((n) >> 16) & 0xFF);a[pos + 2] = (uint8_t)(((n) >> 8) & 0xFF);a[pos + 3] = (uint8_t)((n) & 0xFF);}
    #define U8CARRAY_TO_I32(a, size, pos) (pos + 4 > size ? 0 : (((uint32_t)a[pos]) << 24) | (((uint32_t)a[pos + 1]) << 16) | (((uint32_t)a[pos + 2]) << 8) | ((uint32_t)a[pos + 3]))
    
    uint32_t _clockFrequency = 0;
    
    bool eraseSector(uint32_t address)
    {
        if(address % FLASH_SECTOR_SIZE != 0) return false;
        return FlashErase(address) == 0;
    }
    
    bool writeSlot(uint32_t areaAddress, uint32_t slotSize, uint8_t* data, size_t dataSize)
    {
        if(dataSize == 0 || data == nullptr || dataSize > slotSize - 4) return false;
    
        //{{{ Load sector into memory
        uint32_t sectorAddress = areaAddress - (areaAddress % FLASH_SECTOR_SIZE);
        std::array<uint8_t, FLASH_SECTOR_SIZE> sectorData;
        std::copy((uint8_t*)sectorAddress, (uint8_t*)sectorAddress + FLASH_SECTOR_SIZE, sectorData.begin());
        //}}}
    
        if(!eraseSector(sectorAddress)) return false;
    
        //{{{ Overwrite old slot data
        uint32_t slotOffset = areaAddress - sectorAddress;
        std::fill(sectorData.begin() + slotOffset, sectorData.begin() + slotOffset + slotSize, 0);
        //}}}
    
        uint32_t metadata = (((uint32_t)1) << 24) | (dataSize & 0xFFFF);
        I32_TO_U8CARRAY(sectorData, slotOffset, metadata);
        std::copy(data, data + dataSize, sectorData.begin() + slotOffset + 4);
    
        return FlashProgram((uint32_t*)sectorData.data(), sectorAddress, sectorData.size()) == 0;
    }
    
    inline void lock()
    {
        // See chapter 7.5.2 and 7.2.3.12
        uint32_t bootConfig = HWREG(FLASH_BOOTCFG);
        if(bootConfig & 3)
        {
            HWREG(FLASH_FMA) = 0x75100000;
            HWREG(FLASH_FMD) = 0x7FFFFFFC;
            HWREG(FLASH_FMC) = FLASH_FMC_WRKEY | FLASH_FMC_COMT;
            while(HWREG(FLASH_FMC) & FLASH_FMC_COMT); //Wait for completion
            for(int32_t i = 0; i < 100; i++) //Blink both LEDs for 10 seconds
            {
                GPIOPinWrite(_ledPort, _ledGreenValue | _ledOrangeValue, _ledGreenValue | _ledOrangeValue);
                SysCtlDelay(_clockFrequency / (20 * 4)); //50ms
                GPIOPinWrite(_ledPort, _ledGreenValue | _ledOrangeValue, 0);
                SysCtlDelay(_clockFrequency / (20 * 4)); //50ms
            }
    
            while(true);
        }
        else
        {
            GPIOPinWrite(_ledPort, _ledGreenValue, _ledGreenValue);
            SysCtlDelay(_clockFrequency / (1 * 4)); //1s
            GPIOPinWrite(_ledPort, _ledGreenValue, 0);
            SysCtlDelay(_clockFrequency / (1 * 4)); //1s
        }
    }
    
    int main(void)
    {
        _clockFrequency = SysCtlClockFreqSet((SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480), 120000000);
        IntMasterEnable();
    
        SysCtlPeripheralEnable(_ledPeripheral);
        GPIOPinTypeGPIOOutput(_ledPort, _ledGreenValue | _ledOrangeValue);
    
        lock();
    
        //{{{ iomanip
            // Without this code block and the include of iomanip and sstream the mass erase works
            auto fromEpoch = SysTickValueGet() + 3600; //Some value
            tm* fromTime = localtime(&fromEpoch);
            std::ostringstream fromStringStream;
            fromStringStream << (fromTime->tm_year + 1900) << std::setfill('0') << std::setw(2) << (fromTime->tm_mon + 1) << std::setw(2) << fromTime->tm_mday << std::setw(2) << fromTime->tm_hour << std::setw(2) << fromTime->tm_min << "00";
            std::string fromString = fromStringStream.str();
            if(fromString.back() - 4 == '7') GPIOPinWrite(_ledPort, _ledGreenValue, _ledGreenValue);
        //}}}
    
        //{{{ Test block
            std::string name;
            { //Get name
                uint32_t address = FLASH_NAME_ADDRESS;
                uint32_t metadata = U8CARRAY_TO_I32(((uint8_t*)address), 4, 0);
                uint8_t* data = nullptr;
                size_t dataSize = 0;
                if((metadata >> 24) == 1)
                {
                    dataSize = metadata & 0xFFFF;
                    if(dataSize > 0 && dataSize <= FLASH_NAME_SIZE - 4)
                    {
                        data = (uint8_t*)(address + 4);
                        name = std::string((char*)data, dataSize);
                    }
                }
            }
    
            bool dataExists = (name == "Hello Mellon");
            name = "Hello Mellon";
            if(!writeSlot(FLASH_NAME_ADDRESS, FLASH_NAME_SIZE, (uint8_t*)name.data(), name.size()))
            {
                GPIOPinWrite(_ledPort, _ledOrangeValue, _ledOrangeValue);
                while(true);
            }
    
            if(dataExists)
            {
                for(int32_t i = 0; i < 10; i++)
                {
                    GPIOPinWrite(_ledPort, _ledGreenValue, _ledGreenValue);
                    SysCtlDelay(_clockFrequency / (2 * 4));
                    GPIOPinWrite(_ledPort, _ledGreenValue, 0);
                    SysCtlDelay(_clockFrequency / (2 * 4));
                }
    
                //Executing the mass erase commands below also erases "Hello Mellon" from flash memory.
                //FLASH_CTRL->FMC = FLASH_FMC_WRKEY | FLASH_FMC_MERASE;
                //while(FLASH_CTRL->FMC & FLASH_FMC_MERASE);
            }
            else
            {
                while(true)
                {
                    GPIOPinWrite(_ledPort, _ledOrangeValue, _ledOrangeValue);
                    SysCtlDelay(_clockFrequency / (2 * 4));
                    GPIOPinWrite(_ledPort, _ledOrangeValue, 0);
                    SysCtlDelay(_clockFrequency / (2 * 4));
                }
            }
        //}}}
    }
    

    Steps to reproduce the problem:

    1. Take a previously unflashed MSP432E401Y and flash it with the program above (with boards I was experimenting with the mass erasing sometimes starts working and then always works).
    2. Power on and power off the board until the green LED flashes.
    3. Execute the unlock procedure two times using `dbgjtag -f @xds110 -Y unlock,mode=msp432e4`
    4. Flash the program above again (the green and orange LED should blink quickly to acknowledge that the locking has completed).
    5. Power off and power on the board once.
    6. The green LED should flash indicating "Hello Mellon" is still readable from flash memory.
    7. Sometimes steps 3 to 5 have to be repeated two times.

    I now reproduced the problem on 10 different boards. Not previously flashed boards always seem to show the problem. The triggering cause seems to be the usage of <sstream> and/or <iomanip>. Removing those two include files and the associated source code makes the unlock procedure successfully erase the flash memory again.

    A few more information:

    The test system has a Intel Core i7-7820X CPU and is running Ubuntu Bionic. The CCS version is 8.3.0.00009, the TI compiler version 18.1.4.LTS. We are flashing our boards with the MSP432E401Y LaunchPad.

    Your help is very much appreciated.

    Best regards,

    Sathya Laufer

  • Hello Sai,

    were you able to reproduce the problem? We are building a product where it is really important that data is not recoverable after a mass erase. So it's very important to us to know, what caused the problem to be sure it won't occur again.

    Cheers,

    Sathya
  • Hello Sathya,

    Apologize for the delay in getting back!

    I tried to follow the steps that you described, but the LED blinking does not work as I am using a LaunchPad (MSP-EXP432E401Y).

    Any way, the following are the steps I followed to mass erase the device and when I read the flash I don't see any data in Flash, which means mass erase worked.

    • Erase the main memory of MSP-EXP432E401Y
      • Using Uniflash's Memory Browser, confirmed that the 0x000E.6580, 0x0000.0000, 0x000F.FFFF are all erased (that is 0xFFFF.FFFF) 
    • Flash the program provided by you (FlashTest2.out) using Uniflash
      • Using Uniflash's Memory Browser, confirmed that 0x0000.0000, 0x0001.83C0 (last location according to FlashTest2.map) and 0x0001.83D0 are programmed.
      • 0x000E.6580 is not programmed.
    • Reset the device multiple times >10.
      • 0x000E.6580 is never programmed
      • Using Uniflash's Memory Browser, confirmed by reading 0x000E.6580.
      • Since I am able to read the Memory, it means JTAG is not locked.
    • Execute the unlock procedure once using `dbgjtag -f @xds110 -Y unlock,mode=msp432e4`
    • Read Flash to confirm it is erased.
      • Uniflash's Memory Browser, confirmed that the 0x000E.6580, 0x0000.0000, 0x000F.FFFF are all erased (that is 0xFFFF.FFFF) 

    It looks like the application that you provided is not executing the JTAG Lock sequence and also not programming the location  0x000E.6580.  Possible that it faulted some where as this application may not have been developed for MSP-EXP432E401Y.

    However the mass erase is working as expected. You could verify by running the above mentioned steps on the hardware for which the application is built. This would also confirm if the logic implemented in the application (for identifying mass erase) is working as expected or not.

    Thanks,

    Sai

  • Hello Sai,

    as you can see in the source code, it's not written for the Launchpad. You need to change `SYSCTL_XTAL_16MHZ` to `SYSCTL_XTAL_25MHZ`. So of course it is not locked and no data is ever written to flash ;-)...
    I just tried - on my Launchpad I can't reproduce the issue. It might be, because it was used before (as I mentioned earlier the issue can not always be reproduced on non-new microcontrollers). Let me get a new Launchpad and then I will try to reproduce it before posting the updated source here.

    Cheers,

    Sathya
  • Hello Sathya,

    Any updates on this topic?

    Thanks,
    Sai
  • Hello Sathya,

    I will close this thread as there is no update for about 2 weeks. If you want further help please post below and the thread will automatically open. If the thread is locked, you can open a related thread by clicking the "Ask a related question" button towards the top of the browser.

    Thanks,
    Sai

**Attention** This is a public forum