diff --git a/tools/uart-flash-server/Makefile b/tools/uart-flash-server/Makefile index fe0b5b1f..03ccb855 100644 --- a/tools/uart-flash-server/Makefile +++ b/tools/uart-flash-server/Makefile @@ -16,6 +16,11 @@ $(EXE): $(EXE).o libwolfboot.o libwolfboot.o: ../../src/libwolfboot.c $(Q)$(CC) $(CFLAGS) -c -o $(@) $(^) +test_overflow: test_overflow.c + $(Q)$(CC) -Wall -g -o $@ $^ -lutil + +test: $(EXE) test_overflow + $(Q)./test_overflow ./$(EXE) clean: - $(Q)rm -f *.o $(EXE) + $(Q)rm -f *.o $(EXE) test_overflow diff --git a/tools/uart-flash-server/test_overflow.c b/tools/uart-flash-server/test_overflow.c new file mode 100644 index 00000000..c0c9bdf1 --- /dev/null +++ b/tools/uart-flash-server/test_overflow.c @@ -0,0 +1,112 @@ +/* Regression test for the uint32_t wraparound bounds-check bypass in + * uart_flash_erase()/read()/write() (finding F-4339). + * + * Drives the real ufserver binary over a pseudo-terminal and sends an ERASE + * command with address=0x1000 and len=0xFFFFFFFF. With the buggy uint32_t + * guard, address+len wraps to 0xFFF (< FIRMWARE_PARTITION_SIZE+SWAP_SIZE), the + * guard is bypassed and the erase loop walks far past the 0x21000-byte mmap, + * crashing the server with SIGSEGV. With the 64-bit guard the command is + * rejected and the server stays alive. + * + * Usage: test_overflow ./ufserver + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CMD_HDR_WOLF 'W' +#define CMD_HDR_ERASE 0x03 + +int main(int argc, char *argv[]) +{ + int master, slave; + char slavename[256]; + pid_t pid; + char tmpl[] = "/tmp/ufserver_fw_XXXXXX"; + int fd; + uint8_t buf[64]; + int i; + + if (argc != 2) { + fprintf(stderr, "usage: %s ./ufserver\n", argv[0]); + return 2; + } + + /* Create a small (non-WOLF) firmware file; mmap_firmware() pads it to + * FIRMWARE_PARTITION_SIZE + SWAP_SIZE (0x21000). */ + fd = mkstemp(tmpl); + if (fd < 0) { perror("mkstemp"); return 2; } + memset(buf, 0xA5, sizeof(buf)); + if (write(fd, buf, sizeof(buf)) != (ssize_t)sizeof(buf)) { perror("write"); return 2; } + close(fd); + + if (openpty(&master, &slave, slavename, NULL, NULL) != 0) { + perror("openpty"); + return 2; + } + + signal(SIGPIPE, SIG_IGN); + + pid = fork(); + if (pid < 0) { perror("fork"); return 2; } + if (pid == 0) { + /* Child: run the server attached to the slave pty. */ + close(master); + close(slave); + fd = open("/dev/null", O_WRONLY); + if (fd >= 0) { dup2(fd, STDOUT_FILENO); } + execl(argv[1], argv[1], tmpl, slavename, (char *)NULL); + perror("execl"); + _exit(127); + } + + /* Parent: drive the server. */ + close(slave); + usleep(200000); /* let the child open and configure the port */ + + /* STX selecting a flash command, then the ERASE opcode. */ + buf[0] = CMD_HDR_WOLF; + buf[1] = CMD_HDR_ERASE; + /* address = 0x00001000 (little-endian raw bytes) */ + buf[2] = 0x00; buf[3] = 0x10; buf[4] = 0x00; buf[5] = 0x00; + /* len = 0xFFFFFFFF -> address+len wraps to 0xFFF in uint32_t */ + buf[6] = 0xFF; buf[7] = 0xFF; buf[8] = 0xFF; buf[9] = 0xFF; + if (write(master, buf, 10) != 10) { perror("write master"); } + + /* Drain ack bytes the server sends back so it never blocks. */ + fcntl(master, F_SETFL, O_NONBLOCK); + + /* Give the server time to process (and crash, if vulnerable). */ + for (i = 0; i < 20; i++) { + uint8_t drain[64]; + (void)read(master, drain, sizeof(drain)); + usleep(50000); + int status; + pid_t r = waitpid(pid, &status, WNOHANG); + if (r == pid) { + unlink(tmpl); + if (WIFSIGNALED(status)) { + fprintf(stderr, "FAIL: server died from signal %d " + "(OOB access from wrapped bounds check)\n", + WTERMSIG(status)); + return 1; + } + fprintf(stderr, "FAIL: server exited unexpectedly (status %d)\n", + status); + return 1; + } + } + + /* Still alive after processing the malicious command: guard held. */ + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); + unlink(tmpl); + printf("PASS: server rejected wrapped address+len and stayed alive\n"); + return 0; +} diff --git a/tools/uart-flash-server/ufserver.c b/tools/uart-flash-server/ufserver.c index b6156ef5..bd159992 100644 --- a/tools/uart-flash-server/ufserver.c +++ b/tools/uart-flash-server/ufserver.c @@ -280,7 +280,7 @@ static void uart_flash_erase(uint8_t *base, int ud) return; if (read_word(ud,&len) != 4) return; - if (address + len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE)) + if ((uint64_t)address + (uint64_t)len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE)) return; if (address < FIRMWARE_PARTITION_SIZE) { printmsg(msgEraseUpdate); @@ -319,7 +319,7 @@ static void uart_flash_read(uint8_t *base, int ud) #if LOG_FLASH_ADDRESS printf("Read @%x\n", address); #endif - if (address + len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE)) + if ((uint64_t)address + (uint64_t)len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE)) return; for (i = 0; i < len; i++) { write(ud, base + address + i, 1); @@ -344,7 +344,7 @@ static void uart_flash_write(uint8_t *base, int ud) #if LOG_FLASH_ADDRESS printf("Write @%x\n", address); #endif - if (address + len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE)) + if ((uint64_t)address + (uint64_t)len > (FIRMWARE_PARTITION_SIZE + SWAP_SIZE)) return; for (i = 0; i < len; i++) { read(ud, base + address + i, 1);