Skip to content

Commit

Permalink
bootloader: disallow firmware downgrades
Browse files Browse the repository at this point in the history
  • Loading branch information
benma committed Mar 5, 2018
1 parent 47b7079 commit 350c7a8
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 32 deletions.
13 changes: 11 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,16 @@ if(GIT_FOUND)
list(GET VERSION_LIST 1 MINOR)
list(GET VERSION_LIST 2 PATCH)
string(REPLACE "v" "" MAJOR ${vMAJOR})

execute_process(COMMAND git "tag" "--list" OUTPUT_VARIABLE GIT_TAG_LIST WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_STRIP_TRAILING_WHITESPACE)
string(REPLACE "\n" ";" GIT_TAG_LIST ${GIT_TAG_LIST})
list(LENGTH GIT_TAG_LIST VERSION_MONOTONIC)
else()
set(GIT_COMMIT_HASH "Git not found.")
set(VERSION_MONOTONIC $ENV{MONOTONIC_VERSION})
endif()
if(NOT VERSION_MONOTONIC)
message(FATAL_ERROR "firmware needs a monotonic version")
endif()
STRING(REGEX REPLACE "-.*$" "" PATCH ${PATCH})
set(VERSION_STRING "${GIT_COMMIT_HASH}")
Expand Down Expand Up @@ -127,6 +135,7 @@ message(STATUS "Linker: ${CMAKE_LINKER}")
message(STATUS "Archiver: ${CMAKE_AR}")
message(STATUS " - Options -")
message(STATUS "Build type: ${BUILD_TYPE}")
message(STATUS "Monotonic fw version: ${VERSION_MONOTONIC}")
message(STATUS "Verbose: ${CMAKE_VERBOSE_MAKEFILE}")
message(STATUS "Documentation: ${BUILD_DOCUMENTATION} (make doc)")
message(STATUS "Coverage flags: ${BUILD_COVERAGE}")
Expand Down Expand Up @@ -266,9 +275,9 @@ else()
elseif(BUILD_TYPE STREQUAL "firmware")
add_custom_command(
OUTPUT ${MYPROJECT}.pad.bin
COMMAND python ../../py/pad_firmware_binary.py ${MYPROJECT}.bin ${MYPROJECT}.pad.bin
COMMAND python ../../py/pad_firmware_binary.py ${MYPROJECT}.bin ${MYPROJECT}.pad.bin "${VERSION_MONOTONIC}"
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin
COMMENT "\nPadding binary"
COMMENT "\nPadding binary; adding monotonic version: ${VERSION_MONOTONIC}"
)
add_custom_target(padded_file ALL
DEPENDS ${MYPROJECT}.pad.bin
Expand Down
19 changes: 9 additions & 10 deletions py/dbb_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def hid_send_frame(data):
# INIT frame
write = data[idx : idx + min(data_len, usb_report_size - 7)]
dbb_hid.write(b'\0' + struct.pack(">IBH",HWW_CID, HWW_CMD, data_len & 0xFFFF) + write + b'\xEE' * (usb_report_size - 7 - len(write)))
else:
else:
# CONT frame
write = data[idx : idx + min(data_len, usb_report_size - 5)]
dbb_hid.write(b'\0' + struct.pack(">IB", HWW_CID, seq) + write + b'\xEE' * (usb_report_size - 5 - len(write)))
Expand Down Expand Up @@ -180,37 +180,36 @@ def sendPlainBoot(msg):
print("\nSending: {}".format(msg))
if type(msg) == str:
msg = msg.encode()
dbb_hid.write(b'\0' + bytearray(msg) + b'\0' * (boot_buf_size_send - len(msg)))
dbb_hid.write(b'\0' + bytearray(msg) + b'\0' * (boot_buf_size_send - len(msg)))
reply = []
while len(reply) < boot_buf_size_reply:
while len(reply) < boot_buf_size_reply:
reply = reply + dbb_hid.read(boot_buf_size_reply)

reply = bytearray(reply).rstrip(b' \t\r\n\0')
reply = ''.join(chr(e) for e in reply)
print("Reply: {} {}\n\n".format(reply[:2], reply[2:]))
return reply[1]
return reply


def sendChunk(chunknum, data):
b = bytearray(b"\x77\x00")
b[1] = chunknum % 0xFF
b.extend(data)
dbb_hid.write(b'\0' + b + b'\xFF'*(boot_buf_size_send-len(b)))
dbb_hid.write(b'\0' + b + b'\xFF'*(boot_buf_size_send-len(b)))
reply = []
while len(reply) < boot_buf_size_reply:
while len(reply) < boot_buf_size_reply:
reply = reply + dbb_hid.read(boot_buf_size_reply)
reply = bytearray(reply).rstrip(b' \t\r\n\0')
reply = ''.join(chr(e) for e in reply)
print("Loaded: {} Code: {}".format(chunknum, reply))


def sendBin(filename):
def sendBin(filename):
with open(filename, "rb") as f:
cnt = 0
while True:
while True:
data = f.read(chunksize)
if len(data) == 0:
break
sendChunk(cnt, data)
cnt += 1

20 changes: 12 additions & 8 deletions py/load_firware.py → py/load_firmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
# Load firmware onto the Digital Bitbox.
#
# The Digital Bitbox must be in bootloader mode to use this script:
# 1- Unlock the bootloader using send_command.py to send '{"bootloader":"unlock"}'
# 1- Unlock the bootloader using send_command.py to send '{"bootloader":"unlock"}'
# 2- Hold the touch button 3 seconds to permit unlocking.
# 3- Replug the device, and briefly touch the touch button within 3 seconds.
# 3- Replug the device, and briefly touch the touch button within 3 seconds.
# The LED will flash a few times quickly when entering bootloader mode.
#
#
# Firmware signatures are valid for deterministically built firware releases (refer to the github readme for building).
# Invalid firmware cannot be run.
#
# After loading new firmware, re-lock the bootloader using send_command.py to send '{"bootloader":"lock"}'
# After loading new firmware, re-lock the bootloader using send_command.py to send '{"bootloader":"lock"}'


import sys
Expand Down Expand Up @@ -94,7 +94,7 @@ def printFirmwareHash(filename):
if len(d) == 0:
break
data = data + bytearray(d)
data = data + b'\xFF' * (applen - len(data))
data = data + b'\xFF' * (applen - len(data))
print('\nHashed firmware', binascii.hexlify(Hash((data))))


Expand All @@ -109,7 +109,13 @@ def printFirmwareHash(filename):
sendPlainBoot("e") # erase existing firmware (required)
sendBin(fn) # send new firmware

if sendPlainBoot("s" + "0" + sig) != '0': # verify new firmware
# upload sigs and verify new firmware
load_result = sendPlainBoot("s" + "0" + sig)
if load_result[1] == 'V':
latest_version, = struct.unpack('>I', binascii.unhexlify(load_result[2+64:][:8]))
app_version, = struct.unpack('>I', binascii.unhexlify(load_result[2+64+8:][:8]))
print('ERROR: firmware downgrade not allowed. Got version %d, but must be equal or higher to %d' % (app_version, latest_version))
elif load_result[1] != '0':
print('ERROR: invalid firmware signature\n\n')
else:
print('SUCCESS: valid firmware signature\n\n')
Expand All @@ -120,5 +126,3 @@ def printFirmwareHash(filename):
print(ex)
except (KeyboardInterrupt, SystemExit):
print('Exiting code')


13 changes: 12 additions & 1 deletion py/pad_firmware_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@
import sys
import os
import shutil
import struct

binfile = sys.argv[1]
padfile = sys.argv[2]
try:
version_monotonic = int(sys.argv[3])
if version_monotonic == 0xffffffff or version_monotonic <= 0:
raise Exception()
except:
print "\nERROR: version needs to be between 1 and 0xffffffff-1"
sys.exit(1)
binsize = os.stat(binfile).st_size

max_binsize = 225280 # 220kB
Expand All @@ -16,6 +24,9 @@
sys.exit(1)
else:
shutil.copyfile(binfile, padfile)
# firmware monotonic version is a 4 byte big endian unsigned integer.
version_bytes = struct.pack('>I', version_monotonic)
with open(padfile, 'ab') as f:
f.write(b'\xff' * (max_binsize - binsize))
f.write(b'\xff' * (max_binsize - binsize - len(version_bytes)))
f.write(version_bytes)
f.close()
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
with file(binfile, 'r') as original:
data = original.read()

if len(data) != 32768:
print '\n\nError: the binfile must be padded to 32768 bytes'
if len(data) != 225280:
print '\n\nError: the binfile must be padded to 220kB'
sys.exit()

with file(padfile, 'w') as modified:
modified.write(sig + data)

except:
print '\n\nUsage:\n ./append_signatures_to_binary.py <binary_file> <output_file> <signature_blob>\n\n\n'
print '\n\nUsage:\n ./prepend_signatures_firmware_binary.py <firmware_binary> <output_file> <signature_blob>\n\n\n'
52 changes: 44 additions & 8 deletions src/bootloader.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,15 @@ static void bootloader_firmware_erase(void)
bootloader_report_status(OP_STATUS_OK);
}

static inline uint32_t bootloader_parse_app_version(uint8_t *start)
{
uint32_t version = (*start << 24) | (*(start + 1) << 16) | (*(start + 2) << 8) | (*
(start + 3));
if (version == 0xffffffff) {
version = 0;
}
return version;
}

static uint8_t bootloader_firmware_verified(void)
{
Expand All @@ -174,15 +183,28 @@ static uint8_t bootloader_firmware_verified(void)
pubkey++;
cnt++;
}
memcpy(report + 2, utils_uint8_to_hex(hash, 32), 64); // return double hash of app binary

if (valid < BOOT_SIG_M) {
bootloader_report_status(OP_STATUS_ERR);
} else {
bootloader_report_status(OP_STATUS_OK);
return 0;
}

memcpy(report + 2, utils_uint8_to_hex(hash, 32), 64); // return double hash of app binary
return (valid < BOOT_SIG_M) ? 0 : 1;
uint32_t app_version = bootloader_parse_app_version((uint8_t *)FLASH_APP_VERSION_START);
uint32_t app_latest_version = bootloader_parse_app_version((uint8_t *)(
FLASH_SIG_START + FLASH_BOOT_LATEST_APP_VERSION_BYTES));
memcpy(report + 2 + 64, utils_uint8_to_hex((uint8_t *)(FLASH_SIG_START +
FLASH_BOOT_LATEST_APP_VERSION_BYTES), FLASH_APP_VERSION_LEN), 2 * FLASH_APP_VERSION_LEN);
memcpy(report + 2 + 64 + 2 * FLASH_APP_VERSION_LEN,
utils_uint8_to_hex((uint8_t *)FLASH_APP_VERSION_START, FLASH_APP_VERSION_LEN),
2 * FLASH_APP_VERSION_LEN);
if (app_version < app_latest_version) {
bootloader_report_status(OP_STATUS_ERR_VERSION);
return 0;
}

bootloader_report_status(OP_STATUS_OK);
return 1;
}


Expand All @@ -208,7 +230,6 @@ static void bootloader_reboot(void)
NVIC_SystemReset();
}


void bootloader_command(const char *command)
{
memset(report, 0, sizeof(report));
Expand Down Expand Up @@ -250,7 +271,7 @@ void bootloader_command(const char *command)
pubkey++;
cnt++;
}
memset(sig, 0xFF, FLASH_SIG_LEN);
memcpy(sig, (uint8_t *)FLASH_SIG_START, FLASH_SIG_LEN);
memcpy(sig, utils_hex_to_uint8(command + FLASH_BOOT_OP_LEN), cnt * 64);

flash_unlock(FLASH_SIG_START, FLASH_SIG_START + FLASH_SIG_LEN, NULL, NULL);
Expand All @@ -264,10 +285,25 @@ void bootloader_command(const char *command)
break;
}

bootloader_firmware_verified();
if (bootloader_firmware_verified()) {

memcpy((uint8_t *)sig + FLASH_BOOT_LATEST_APP_VERSION_BYTES,
(uint8_t *)FLASH_APP_VERSION_START,
FLASH_APP_VERSION_LEN);

if (flash_erase_page(FLASH_SIG_START, IFLASH_ERASE_PAGES_8) != FLASH_RC_OK) {
bootloader_report_status(OP_STATUS_ERR_ERASE);
break;
}

if (flash_write(FLASH_SIG_START, sig, FLASH_SIG_LEN, 0) != FLASH_RC_OK) {
bootloader_report_status(OP_STATUS_ERR_WRITE);
break;
}
}

break;
}

default:
bootloader_report_status(OP_STATUS_ERR_INVALID_CMD);
bootloader_loading_ready = 0;
Expand Down
1 change: 1 addition & 0 deletions src/bootloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ typedef enum BOOT_OP_CODES {

typedef enum BOOT_STATUS {
OP_STATUS_ERR = 'Z',
OP_STATUS_ERR_VERSION = 'V',
OP_STATUS_ERR_LEN = 'N',
OP_STATUS_ERR_MACRO = 'M',
OP_STATUS_ERR_WRITE = 'W',
Expand Down
3 changes: 3 additions & 0 deletions src/flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,14 @@
#define FLASH_APP_START (IFLASH0_ADDR + FLASH_BOOT_LEN + FLASH_SIG_LEN)
#define FLASH_APP_LEN (IFLASH0_SIZE - FLASH_BOOT_LEN - FLASH_SIG_LEN)
#define FLASH_APP_PAGE_NUM (FLASH_APP_LEN / IFLASH0_PAGE_SIZE)
#define FLASH_APP_VERSION_LEN (4)// 4 byte big endian unsigned int
#define FLASH_APP_VERSION_START (FLASH_APP_START + FLASH_APP_LEN - FLASH_APP_VERSION_LEN)
#define FLASH_BOOT_OP_LEN (2)// 1 byte op code and 1 byte parameter
#define FLASH_BOOT_PAGES_PER_CHUNK (8)
#define FLASH_BOOT_CHUNK_LEN (IFLASH0_PAGE_SIZE * FLASH_BOOT_PAGES_PER_CHUNK)
#define FLASH_BOOT_CHUNK_NUM (FLASH_APP_LEN / FLASH_BOOT_CHUNK_LEN)// app len should be a multiple of chunk len
#define FLASH_BOOT_LOCK_BYTE (FLASH_SIG_LEN - 1)
#define FLASH_BOOT_LATEST_APP_VERSION_BYTES (FLASH_BOOT_LOCK_BYTE - FLASH_APP_VERSION_LEN)


#define COMMANDER_REPORT_SIZE 3584
Expand Down

0 comments on commit 350c7a8

Please sign in to comment.