From e4ad17cf515a74b25921fc6267dbf347e8c69dde Mon Sep 17 00:00:00 2001 From: Thorsten von Eicken Date: Fri, 15 May 2015 00:15:23 -0700 Subject: [PATCH] add firmware validity sanity check --- user/cgiflash.c | 110 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 81 insertions(+), 29 deletions(-) diff --git a/user/cgiflash.c b/user/cgiflash.c index f5917a3..80f71e8 100644 --- a/user/cgiflash.c +++ b/user/cgiflash.c @@ -13,11 +13,21 @@ Some flash handling cgi routines. Used for reading the existing flash and updati #include +#include #include "cgiflash.h" #include "espfs.h" +// Check that the header of the firmware blob looks like actual firmware... +static char* ICACHE_FLASH_ATTR check_header(void *buf) { + uint8_t *cd = (uint8_t *)buf; + if (cd[0] != 0xEA) return "IROM magic missing"; + if (cd[1] != 4 || cd[2] > 3 || cd[3] > 0x40) return "bad flash header"; + if (((uint16_t *)buf)[3] != 0x4010) return "Invalid entry addr"; + if (((uint32_t *)buf)[2] != 0) return "Invalid start offset"; + return NULL; +} -//Cgi that reads the SPI flash. Assumes 512KByte flash. +//===== Cgi that reads the SPI flash. Assumes 512KByte flash. int ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) { int *pos=(int *)&connData->cgiData; if (connData->conn==NULL) { @@ -39,7 +49,7 @@ int ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) { if (*pos>=0x40200000+(512*1024)) return HTTPD_CGI_DONE; else return HTTPD_CGI_MORE; } -// Return which firmware needs to be uploaded next +//===== Cgi to query which firmware needs to be uploaded next int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) { if (connData->conn==NULL) { //Connection aborted. Clean up. @@ -49,43 +59,67 @@ int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) { uint8 id = system_upgrade_userbin_check(); httpdStartResponse(connData, 200); httpdHeader(connData, "Content-Type", "text/plain"); + httpdHeader(connData, "Content-Length", "9"); httpdEndHeaders(connData); - httpdSend(connData, (id == 1 ? "user1.bin" : "user2.bin"), -1); - os_printf("Next firmware: user%d.bin\n", 1-id); + char *next = id == 1 ? "user1.bin" : "user2.bin"; + httpdSend(connData, next, -1); + os_printf("Next firmware: %s (got %d)\n", next, id); return HTTPD_CGI_DONE; } -//Cgi that allows the firmware to be replaced via http POST +//===== Cgi that allows the firmware to be replaced via http POST int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) { if (connData->conn==NULL) { //Connection aborted. Clean up. return HTTPD_CGI_DONE; } - if(connData->post->len > FIRMWARE_SIZE){ - // The uploaded file is too large - os_printf("Firmware image too large\n"); - httpdStartResponse(connData, 400); + int offset = connData->post->received - connData->post->buffLen; + if (offset == 0) { + connData->cgiPrivData = NULL; + } else if (connData->cgiPrivData != NULL) { + // we have an error condition, do nothing + return HTTPD_CGI_DONE; + } + + // assume no error yet... + char *err = NULL; + int code = 400; + + // check overall size + if (connData->post->len > FIRMWARE_SIZE) err = "Firmware image too large"; + + // check that data starts with an appropriate header + if (err == NULL && offset == 0) err = check_header(connData->post->buff); + + // make sure we're buffering in 1024 byte chunks + if (err == NULL && offset % 1024 != 0) { + err = "Buffering problem"; + code = 500; + } + + // return an error if there is one + if (err != NULL) { + os_printf("Error %d: %s\n", code, err); + httpdStartResponse(connData, code); httpdHeader(connData, "Content-Type", "text/plain"); + //httpdHeader(connData, "Content-Length", strlen(err)+2); httpdEndHeaders(connData); - httpdSend(connData, "Firmware image loo large.\r\n", -1); + httpdSend(connData, err, -1); + httpdSend(connData, "\r\n", -1); + connData->cgiPrivData = (void *)1; return HTTPD_CGI_DONE; } + // let's see which partition we need to flash and what flash address that puts us at uint8 id = system_upgrade_userbin_check(); + int address = id == 1 ? 4*1024 // either start after 4KB boot partition + : 4*1024 + FIRMWARE_SIZE + 16*1024 + 4*1024; // 4KB boot, fw1, 16KB user param, 4KB reserved + address += offset; - int address; - if (id == 1) { - address = 4*1024; // start after 4KB boot partition - } else { - // 4KB boot, firmware1, 16KB user param, 4KB reserved - address = 4*1024 + FIRMWARE_SIZE + 16*1024 + 4*1024; - } - address += connData->post->received - connData->post->buffLen; - - if(address % SPI_FLASH_SEC_SIZE == 0){ - // We need to erase this block + // erase next flash block if necessary + if (address % SPI_FLASH_SEC_SIZE == 0){ os_printf("Erasing flash at 0x%05x (id=%d)\n", address, 2-id); spi_flash_erase_sector(address/SPI_FLASH_SEC_SIZE); } @@ -96,7 +130,6 @@ int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) { spi_flash_write(address, (uint32 *)connData->post->buff, connData->post->buffLen); if (connData->post->received == connData->post->len){ - // TODO: verify the firmware (is there a checksum or something???) httpdStartResponse(connData, 200); httpdEndHeaders(connData); return HTTPD_CGI_DONE; @@ -105,6 +138,8 @@ int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) { } } +static ETSTimer flash_reboot_timer; + // Handle request to reboot into the new firmware int ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) { if (connData->conn==NULL) { @@ -112,16 +147,33 @@ int ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) { return HTTPD_CGI_DONE; } - // TODO: sanity-check that the 'next' partition actually contains something that looks like + // sanity-check that the 'next' partition actually contains something that looks like // valid firmware + uint8 id = system_upgrade_userbin_check(); + int address = id == 1 ? 4*1024 // either start after 4KB boot partition + : 4*1024 + FIRMWARE_SIZE + 16*1024 + 4*1024; // 4KB boot, fw1, 16KB user param, 4KB reserved + uint32 buf[8]; + spi_flash_read(address, buf, sizeof(buf)); + char *err = check_header(buf); + if (err != NULL) { + os_printf("Error %d: %s\n", 400, err); + httpdStartResponse(connData, 400); + httpdHeader(connData, "Content-Type", "text/plain"); + //httpdHeader(connData, "Content-Length", strlen(err)+2); + httpdEndHeaders(connData); + httpdSend(connData, err, -1); + httpdSend(connData, "\r\n", -1); + return HTTPD_CGI_DONE; + } - // This hsould probably be forked into a separate task that waits a second to let the - // current HTTP request finish... - system_upgrade_flag_set(UPGRADE_FLAG_FINISH); - system_upgrade_reboot(); httpdStartResponse(connData, 200); + httpdHeader(connData, "Content-Length", "0"); httpdEndHeaders(connData); + + // Schedule a reboot + system_upgrade_flag_set(UPGRADE_FLAG_FINISH); + os_timer_disarm(&flash_reboot_timer); + os_timer_setfn(&flash_reboot_timer, (os_timer_func_t *)system_upgrade_reboot, NULL); + os_timer_arm(&flash_reboot_timer, 2000, 1); return HTTPD_CGI_DONE; } - -