From ab371b9e6af78b7b0d0fe9021e2163d4dfb9d816 Mon Sep 17 00:00:00 2001 From: MaChInEgUn3 Date: Thu, 20 Aug 2026 07:29:41 -0300 Subject: [PATCH] Fix Windows crashes and data corruption when flashing large ROMs Found while debugging why flashing a 6.8GB recovery ROM on Windows reliably crashed or corrupted the transfer. All four issues below were confirmed with a debug build against real hardware (POCO X7 Pro). - validate_check(): post_buf is allocated just large enough for the outgoing request, but is reused to hold the server's decoded/decrypted response, which is usually bigger. Overflowed the heap buffer (STATUS_HEAP_CORRUPTION on Windows) as soon as a response didn't fit. - validate_check(): post_buf was free()'d right after the curl request succeeded, but is read through json_create()'s in-place parsing for the rest of the function - a use-after-free on every successful request. This is likely the real root cause behind the heap corruption above; the oversized buffer just changed how visibly it crashed. - validate_check() (choice 2, "ROMs that can be flashed"): tiny-json's json_getSibling()/json_getName() dereference their argument without a NULL check. A response object with 0 or 1 top-level property crashed with a NULL pointer dereference. - start_sideload(): the ROM file was opened in text mode ("r") instead of binary ("rb"). On Windows this makes the CRT treat byte 0x1A as EOF and rewrite CRLF sequences, corrupting whatever binary ROM data follows the first occurrence. - start_sideload(): file_size/block/offset/total_sent were `long`, which is 32-bit on Windows (LLP64) even in a 64-bit build. Any ROM over ~2GB - i.e. basically every recovery ROM this tool flashes - overflows these, corrupting the sideload-host size announced to the device and the per-chunk file offsets, aborting the transfer partway through (observed as the device silently rebooting mid-flash). Widened to 64-bit and switched to _fseeki64/_ftelli64 on Windows (fseeko/ftello elsewhere) to seek past the 2GB mark correctly. Tested end-to-end flashing a 6.3GB rodin_global recovery ROM on Windows 11 after these fixes; completed successfully where the unmodified build crashed or hung every time. --- miasst.c | 64 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/miasst.c b/miasst.c index 3320c11..7984a37 100755 --- a/miasst.c +++ b/miasst.c @@ -229,6 +229,12 @@ const char *validate_check(const char *md5, int flash) { } size_t post_buf_len = strlen(json_post_data) + strlen("q=&t=&s=1") + 1; + /* post_buf is reused below to hold the server's decoded/decrypted response + * (EVP_DecodeBlock + AES-CBC decrypt write back into it), which is + * typically larger than the outgoing request. Sizing it to only the + * request length caused a heap buffer overflow (observed as + * STATUS_HEAP_CORRUPTION on Windows) as soon as the response didn't fit. */ + if (post_buf_len < 8192) post_buf_len = 8192; unsigned char *post_buf = (unsigned char *)malloc(post_buf_len); if (!post_buf) { curl_free(json_post_data); @@ -262,7 +268,11 @@ const char *validate_check(const char *md5, int flash) { return NULL; } fclose(response_file); - free(post_buf); + /* post_buf is NOT freed here (unlike the error path above): it is + * reused below to hold the decoded/decrypted response and is read + * through json_create()'s in-place parsing for the rest of this + * function. Freeing it here was a use-after-free on every successful + * request - the buffer would still be reused right after being freed. */ curl_easy_cleanup(curl); FILE *response_file_read = fopen("response.tmp", "rb"); @@ -329,13 +339,17 @@ const char *validate_check(const char *md5, int flash) { } else { if (json_getType(parsed_json) == JSON_OBJ) { json_t const *child = json_getChild(parsed_json); - if (strcmp(json_getName(json_getSibling(child)), "Signup") == 0 || strcmp(json_getName(json_getSibling(child)), "VersionBoot") == 0) { + /* json_getSibling()/json_getName() dereference their argument with + * no NULL check (tiny-json), so a response object with 0 or 1 + * property crashed here with a NULL pointer dereference. */ + json_t const *firstSibling = child ? json_getSibling(child) : NULL; + if (firstSibling && (strcmp(json_getName(firstSibling), "Signup") == 0 || strcmp(json_getName(firstSibling), "VersionBoot") == 0)) { fprintf(stderr, "Error: Invalid data\n"); return NULL; } while (child) { - child = json_getSibling(child); - if (strcmp(json_getName(child), "Icon") == 0) { + child = json_getSibling(child); + if (!child || strcmp(json_getName(child), "Icon") == 0) { break; } json_t const *cA = json_getProperty(parsed_json, json_getName(child)); @@ -354,18 +368,32 @@ const char *validate_check(const char *md5, int flash) { int start_sideload(const char *sideload_file, const char *validate) { printf("\n\n"); - FILE *fp = fopen(sideload_file, "r"); + /* "rb": must be binary, not text mode. On Windows the CRT's text-mode + * translation treats byte 0x1A as EOF and rewrites CRLF sequences, + * silently truncating/corrupting the ROM data being streamed below. */ + FILE *fp = fopen(sideload_file, "rb"); if (!fp) { perror("Failed to open file"); return 1; } - fseek(fp, 0, SEEK_END); - long file_size = ftell(fp); - fseek(fp, 0, SEEK_SET); + /* file_size/offsets must be 64-bit: `long` is 32-bit on Windows (LLP64) + * even in a 64-bit build, so it silently overflows/wraps for any ROM + * over ~2GB (i.e. basically every recovery ROM this tool flashes). That + * corrupts the sideload-host size announced to the device and the + * per-chunk file offsets, aborting the transfer partway through. */ +#ifdef _WIN32 + _fseeki64(fp, 0, SEEK_END); + long long file_size = _ftelli64(fp); + _fseeki64(fp, 0, SEEK_SET); +#else + fseeko(fp, 0, SEEK_END); + long long file_size = ftello(fp); + fseeko(fp, 0, SEEK_SET); +#endif char sideload_host_command[128 + strlen(validate)]; memset(sideload_host_command, 0, sizeof(sideload_host_command)); - sprintf(sideload_host_command, "sideload-host:%ld:%d:%s:0", file_size, ADB_SIDELOAD_CHUNK_SIZE, validate); + sprintf(sideload_host_command, "sideload-host:%lld:%d:%s:0", file_size, ADB_SIDELOAD_CHUNK_SIZE, validate); send_command(ADB_OPEN, 1, 0, sideload_host_command, strlen(sideload_host_command) + 1); @@ -379,7 +407,7 @@ int start_sideload(const char *sideload_file, const char *validate) { char dummy_data[64]; int dummy_data_size; adb_usb_packet pkt; - long total_sent = 0; + long long total_sent = 0; while (1) { pkt.cmd = 0; @@ -405,13 +433,17 @@ int start_sideload(const char *sideload_file, const char *validate) { continue; } - long block = strtol(dummy_data, NULL, 10); - long offset = block * ADB_SIDELOAD_CHUNK_SIZE; + long long block = strtoll(dummy_data, NULL, 10); + long long offset = block * ADB_SIDELOAD_CHUNK_SIZE; if (offset > file_size) break; int to_write = ADB_SIDELOAD_CHUNK_SIZE; - if(offset + ADB_SIDELOAD_CHUNK_SIZE > file_size) - to_write = file_size - offset; - fseek(fp, offset, SEEK_SET); + if(offset + ADB_SIDELOAD_CHUNK_SIZE > file_size) + to_write = file_size - offset; +#ifdef _WIN32 + _fseeki64(fp, offset, SEEK_SET); +#else + fseeko(fp, offset, SEEK_SET); +#endif fread(work_buffer, 1, to_write, fp); send_command(ADB_WRTE, pkt.arg1, pkt.arg0, work_buffer, to_write); send_command(ADB_OKAY, pkt.arg1, pkt.arg0, NULL, 0); @@ -587,4 +619,4 @@ int main(int argc, char *argv[]) { break; } -} \ No newline at end of file +}