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.
This commit is contained in:
Daniel Lublin 2022-10-06 16:00:33 +02:00 committed by Michael Cardell Widerkrantz
parent 5013338e50
commit 55c5081486
No known key found for this signature in database
GPG Key ID: D3DB3DDF57E704E5
2 changed files with 77 additions and 43 deletions

View File

@ -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

View File

@ -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();
}