From 55c5081486f0ffdd510c331a6d0970893303e62c Mon Sep 17 00:00:00 2001 From: Daniel Lublin Date: Thu, 6 Oct 2022 16:00:33 +0200 Subject: [PATCH] Adjust and document the firmware state-machine, including USS In particular, order of LOAD_USS and LOAD_APP_SIZE is not required, but the need to send both is documented. This is followed up with adjustment in the host programs' Go code, to try to reinforce this. LoadApp() will take the secretPhrase parameter (to be hashed as USS), and loadUSS() will be unexported. Correct CMD/RSP lengths in pseudo-code. --- doc/system_description/software.md | 61 +++++++++++++++++++------ hw/application_fpga/fw/mta1_mkdf/main.c | 59 ++++++++++++------------ 2 files changed, 77 insertions(+), 43 deletions(-) diff --git a/doc/system_description/software.md b/doc/system_description/software.md index 9430d51..fe14a9e 100644 --- a/doc/system_description/software.md +++ b/doc/system_description/software.md @@ -83,28 +83,30 @@ The purpose of the firmware is to bootstrap an application. The host will send a raw binary targeted to be loaded at `0x4001_0000` in the device. - 1. The host sends sets the size of the app by using the + 1. The host sends the User Supplied Secret (USS) by using the `FW_CMD_LOAD_APP_SIZE` command. - 2. The firmware executes `FW_CMD_LOAD_APP_SIZE` command, which + 2. The host sends the size of the app by using the + `FW_CMD_LOAD_APP_SIZE` command. + 3. The firmware executes `FW_CMD_LOAD_APP_SIZE` command, which stores the application size into `APP_SIZE`, and sets `APP_ADDR` to zero. A `FW_RSP_LOAD_APP_SIZE` reponse is sent back to the host, with the status of the action (ok/fail). - 3. If the the host receive a sucessful command, it will send + 4. If the the host receive a sucessful command, it will send multiple `FW_CMD_LOAD_APP_DATA` commands, containing the full application. - 4. For each received `FW_CMD_LOAD_APP_DATA` command the firmware + 5. For each received `FW_CMD_LOAD_APP_DATA` command the firmware places the data into `0x4001_0000` and upwards. The firmware response with `FW_RSP_LOAD_APP_DATA` response to the host for each received block. - 5. When the final block of the application image is received, we + 6. When the final block of the application image is received, we measure the application by computing a BLAKE2s digest over the entire application, - The Compound Device Identifier is computed by using the `UDS` and - the measurement of the application, and placed in the `CDI` - register. Then `0x4001_0000` is written to `APP_ADDR`. The final - `FW_RSP_LOAD_APP_DATA` response is sent to the host, completing - the loading. + The Compound Device Identifier is computed by using the `UDS`, + the measurement of the application, and the `USS`, and placed in + the `CDI` register. Then `0x4001_0000` is written to `APP_ADDR`. + The final `FW_RSP_LOAD_APP_DATA` response is sent to the host, + completing the loading. NOTE: The firmware uses SPRAM for data and stack. We need to make sure that the application image does not overwrite the firmware's running @@ -114,6 +116,16 @@ to check application image is sane. The shared firmware data area (e.g. `.data` and the stack must be cleared prior launching the application. +### Loading the User Supplied Secret (USS) + +The host program may send `FW_CMD_LOAD_USS` and `FW_CMD_LOAD_APP_SIZE` +in any order. But it *should* always send both `FW_CMD_LOAD_USS` and +`FW_CMD_LOAD_APP_SIZE` before sending the multiple +`FW_CMD_LOAD_APP_DATA`. If it does not, the USS will not be +predictable because somebody could have send `FW_CMD_LOAD_USS` before, +and the last `FW_CMD_LOAD_APP_DATA` (on whichever iteration) will +cause the currently loaded USS to be used for calculating CDI. + ### Starting an application Starting an application includes the "switch to application mode" @@ -142,6 +154,7 @@ Procedure: Available commands/reponses: +#### `FW_{CMD,RSP}_LOAD_USS` #### `FW_{CMD,RSP}_LOAD_APP_SIZE` #### `FW_{CMD,RSP}_LOAD_APP_DATA` #### `FW_{CMD,RSP}_RUN_APP` @@ -155,7 +168,7 @@ Available commands/reponses: Verification that the device is an authentic Mullvad device. Implemented using challenge/response. -#### `FW_{CMD,RSP}_GET_APPLICATION_DIGEST` +#### `FW_{CMD,RSP}_GET_APP_DIGEST` This command returns the un-keyed hash digest for the application that was loaded. It allows the host to verify that the application was @@ -174,7 +187,7 @@ host -> host <- u8 RSP[1 + 32] - RSP[0].len = 33 // command frame format + RSP[0].len = 32 // command frame format RSP[1] = 0x02 // FW_RSP_NAME_VERSION RSP[2..6] = NAME0 @@ -187,10 +200,30 @@ host <- #### Load an application ``` +host -> + u8 CMD[1 + 128]; + + CMD[0].len = 128 // command frame format + CMD[1] = 0x0a // FW_CMD_LOAD_USS + + CMD[2..6] = User Supplied Secret + + CMD[6..] = 0 + +host <- + u8 RSP[1 + 4]; + + RSP[0].len = 4 // command frame format + RSP[1] = 0x0b // FW_RSP_LOAD_USS + + RSP[2] = STATUS + + RSP[3..] = 0 + host -> u8 CMD[1 + 32]; - CMD[0].len = 4 // command frame format + CMD[0].len = 32 // command frame format CMD[1] = 0x03 // FW_CMD_LOAD_APP_SIZE CMD[2..6] = APP_SIZE @@ -200,7 +233,7 @@ host -> host <- u8 RSP[1 + 4]; - RSP[0].len = 5 // command frame format + RSP[0].len = 4 // command frame format RSP[1] = 0x04 // FW_RSP_LOAD_APP_SIZE RSP[2] = STATUS diff --git a/hw/application_fpga/fw/mta1_mkdf/main.c b/hw/application_fpga/fw/mta1_mkdf/main.c index 630f3b2..a8831d4 100644 --- a/hw/application_fpga/fw/mta1_mkdf/main.c +++ b/hw/application_fpga/fw/mta1_mkdf/main.c @@ -23,9 +23,11 @@ static volatile uint32_t *cdi = (volatile uint32_t *)MTA1_MKDF_MMIO_MTA1_ static volatile uint32_t *app_addr = (volatile uint32_t *)MTA1_MKDF_MMIO_MTA1_APP_ADDR; static volatile uint32_t *app_size = (volatile uint32_t *)MTA1_MKDF_MMIO_MTA1_APP_SIZE; -#define LED_RED (1 << MTA1_MKDF_MMIO_MTA1_LED_R_BIT) +#define LED_RED (1 << MTA1_MKDF_MMIO_MTA1_LED_R_BIT) #define LED_GREEN (1 << MTA1_MKDF_MMIO_MTA1_LED_G_BIT) -#define LED_BLUE (1 << MTA1_MKDF_MMIO_MTA1_LED_B_BIT) +#define LED_BLUE (1 << MTA1_MKDF_MMIO_MTA1_LED_B_BIT) +#define LED_WHITE (LED_RED | LED_GREEN | LED_BLUE) +// clang-format on static void print_hw_version(uint32_t name0, uint32_t name1, uint32_t ver) { @@ -46,7 +48,6 @@ static void print_hw_version(uint32_t name0, uint32_t name1, uint32_t ver) putinthex(ver); lf(); } -// clang-format on static void print_digest(uint8_t *md) { @@ -69,21 +70,15 @@ int main() uint8_t cmd[CMDLEN_MAXBYTES]; uint8_t rsp[CMDLEN_MAXBYTES]; uint8_t *loadaddr = (uint8_t *)APP_RAM_ADDR; - int left = 0; // Bytes left to read - int nbytes = 0; // Bytes to write to memory - uint8_t uss[32]; - uint32_t local_app_size = 0; - uint8_t in; - uint8_t digest[32]; + int left = 0; // Bytes left to receive + uint8_t uss[32] = {0}; + uint8_t digest[32] = {0}; print_hw_version(local_name0, local_name1, local_ver); - // If host does not load USS, we use an all zero USS - memset(uss, 0, 32); - for (;;) { // blocking; fw flashing white while waiting for cmd - in = readbyte_ledflash(LED_RED | LED_BLUE | LED_GREEN, 800000); + uint8_t in = readbyte_ledflash(LED_WHITE, 800000); if (parseframe(in, &hdr) == -1) { puts("Couldn't parse header\n"); @@ -106,7 +101,7 @@ int main() // Min length is 1 byte so this should always be here switch (cmd[0]) { case FW_CMD_NAME_VERSION: - puts("request: name-version\n"); + puts("cmd: name-version\n"); if (hdr.len != 1) { // Bad length - give them an empty response @@ -122,10 +117,10 @@ int main() break; case FW_CMD_LOAD_USS: - puts("request: load-uss\n"); + puts("cmd: load-uss\n"); - if (hdr.len != 128 || *app_size != 0) { - // Bad cmd length, or app_size already set + if (hdr.len != 128) { + // Bad cmd length rsp[0] = STATUS_BAD; fwreply(hdr, FW_RSP_LOAD_USS, rsp); break; @@ -138,7 +133,7 @@ int main() break; case FW_CMD_LOAD_APP_SIZE: - puts("request: load-app-size\n"); + puts("cmd: load-app-size\n"); if (hdr.len != 32) { // Bad length @@ -148,8 +143,9 @@ int main() } // cmd[1..4] contains the size. - local_app_size = cmd[1] + (cmd[2] << 8) + - (cmd[3] << 16) + (cmd[4] << 24); + uint32_t local_app_size = cmd[1] + (cmd[2] << 8) + + (cmd[3] << 16) + + (cmd[4] << 24); puts("app size: "); putinthex(local_app_size); @@ -163,6 +159,9 @@ int main() *app_size = local_app_size; *app_addr = 0; + // Clear digest as GET_APP_DIGEST returns it even if it + // has not been calculated + memset(digest, 0, 32); // Reset where to start loading the program loadaddr = (uint8_t *)APP_RAM_ADDR; @@ -173,16 +172,18 @@ int main() break; case FW_CMD_LOAD_APP_DATA: - puts("request: load-app-data\n"); + puts("cmd: load-app-data\n"); - if (hdr.len != 128 || *app_size == 0) { - // Bad length of this command or bad app size - - // they need to call FW_CMD_LOAD_APP_SIZE first + if (hdr.len != 128 || *app_size == 0 || + *app_addr != 0) { + // Bad length, or app_size not yet set, or + // app_addr already set (fully loaded!) rsp[0] = STATUS_BAD; fwreply(hdr, FW_RSP_LOAD_APP_DATA, rsp); break; } + int nbytes; if (left > 127) { nbytes = 127; } else { @@ -224,11 +225,11 @@ int main() break; case FW_CMD_RUN_APP: - puts("request: run-app\n"); + puts("cmd: run-app\n"); if (hdr.len != 1 || *app_size == 0 || *app_addr == 0) { - // Bad cmd length, or app_size and app_addr are - // not both set + // Bad cmd length, or app_size or app_addr are + // not yet set rsp[0] = STATUS_BAD; fwreply(hdr, FW_RSP_RUN_APP, rsp); break; @@ -262,14 +263,14 @@ int main() break; // This is never reached! case FW_CMD_GET_APP_DIGEST: - puts("request: get-app-digest\n"); + puts("cmd: get-app-digest\n"); memcpy(rsp, &digest, 32); fwreply(hdr, FW_RSP_GET_APP_DIGEST, rsp); break; default: - puts("Received unknown firmware command: 0x"); + puts("Got unknown firmware cmd: 0x"); puthex(cmd[0]); lf(); }