I have attached a bad LZMA file generated by modifying a valid LZMA's size to 0 as described. The attached `create_bad_lzma.py` was used for this.
I have also attached a dummy proof-of-concept that mimic's U-Boots calls to LzmaDecode(). When ran on linux, this segfaults off the page within the memcpy(): ``` root@ubuntu:~/7z/7-Zip/C# gcc -I`pwd` LzmaDec.c test.c -o test root@ubuntu:~/7z/7-Zip/C# ./test uncompressed size: 0x0 compressed size: 0xfffffffffffffffa 7z Release: 19.00 Segmentation fault (core dumped) ``` I can get another PoC working in U-Boot to show that it can be used to gain code execution, but I would need some time to get another environment setup. Please let me know if have questions. Thank you, Darek On Wed, Jul 31, 2024 at 4:31 PM Darek <x64...@gmail.com> wrote: > Hello! I hope this is all the correct contacts for reporting this issue. > > I have discovered that there is a vulnerability in the version of 7z LZMA > that U-Boot is using (9.20). I've found this to lead to code execution > when used with `lzmadec`. > I have found no issued CVEs or public disclosures announcing this > vulnerability, but it appears to have been patched in 7z LZMA version 21.02 > alpha. I've patched locally with 24.07 and U-Boot appeared to LZMA > decompress successfully, but I'm unaware of other use cases within the > project. I would also be curious if another LZMA implementation would be > considered. > > I'm separately coordinating disclosure with the 7z maintainers and > planning on requesting a CVE. Since your project uses an older version > that is unlikely to get a backported patch, and the issue is already fixed > in newer versions, I am disclosing this to U-Boot maintainers at the same > time. > > > The bug triggers when an LZMA has the 8-byte length field in the header > set to 0. This field is located at offset 5 from the start of an LZMA file. > Due to how the logic of `LzmaDec_DecodeToDic()` works, this eventually > reaches a `memcpy(p->tempBuf, src, inSize)` on this line - > https://github.com/u-boot/u-boot/blob/8877bc51a8a4d921ba2f163208b8b1a57ba47c18/lib/lzma/LzmaDec.c#L793 > > The `inSize` value here is allowed to be greater than the buffer size due > to the previous if-statement ignoring this value if `checkEndMarkNow` is > set. > The return value from `LzmaDec_TryDummy()` also needs to return > `DUMMY_ERROR` to reach the `memcpy()`. > > The value of `dicLimit` in this function is initially the value read from > the LZMA header, which for this case is 0. Under my testing, when this was > 0, it hits the memcpy call, using the passed `srcLen` value (`inSize`). > > Within `lmzadec`, this value is originally set to max size_t as the > default here - > https://github.com/u-boot/u-boot/blob/8877bc51a8a4d921ba2f163208b8b1a57ba47c18/cmd/lzmadec.c#L24 > > And passed to the `LzmaDecode()` function here - > https://github.com/u-boot/u-boot/blob/8877bc51a8a4d921ba2f163208b8b1a57ba47c18/lib/lzma/LzmaTools.c#L110 > > > Under my testing, when `lzmadec` is passed an LZMA file with a 0-length > field, this ends up calling: > ``` > memcpy(p->tempBuf, src, 0xFFFFFFFFFFFFFFF5) > ``` > This is no longer max size_t due to previous subtractions in this loop and > within a previous function: > > https://github.com/u-boot/u-boot/blob/8877bc51a8a4d921ba2f163208b8b1a57ba47c18/lib/lzma/LzmaDec.c#L746 > and > > https://github.com/u-boot/u-boot/blob/8877bc51a8a4d921ba2f163208b8b1a57ba47c18/lib/lzma/LzmaTools.c#L51 > > This leads to code execution when the corrupted LZMA file is large enough > to overwrite `memcpy()`'s instructions, or if the memory past the LZMA file > is also controlled previously. > > In my testing, I created a small LZMA file and modified the header's size > to 0. I then appended assembly instructions that performed a loop to this > file, so that when code execution would occur, I could easily verify by > seeing the loop. I repeated this until the file was over 1GB. > In U-Boot CLI, I then used TFTP to load the LZMA file from my server and > then ran `lzmadec`with my selected source and destination address far away > from U-Boot's code. When I attached a debugger at this point, the program > was stuck in my infinite loop code. > > > I believe this is especially a concern for secure boot implementations > that utilize U-Boot's LZMA decompression, as it could bypass their secure > boot implementation. > > Please let me know if any additional details are needed or if a > proof-of-concept video/script would be required for further evidence. > > Thank you, > Darek >
/* gcc -I`pwd` LzmaDec.c test.c -o test ./test LzmaDecode() calling logic borrowed from https://github.com/u-boot/u-boot/blob/master/lib/lzma/LzmaTools.c#L108 */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/mman.h> #include "sys/stat.h" #include <fcntl.h> #include "LzmaDec.h" #include "7zVersion.h" #define LZMA_SIZE_OFFSET LZMA_PROPS_SIZE #define LZMA_DATA_OFFSET LZMA_SIZE_OFFSET+sizeof(uint64_t) static void *SzAlloc(const ISzAlloc *p, size_t size) { return malloc(size); } static void SzFree(const ISzAlloc *p, void *address) { free(address); } int get_file(const char * filename, void **addr){ int fd, ret; size_t len_file, len; struct stat st; ELzmaStatus state; memset(&state, 0, sizeof(state)); if ((fd = open(filename, O_RDWR | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH)) < 0){ perror("Error in file opening"); return -1; } if ((ret = fstat(fd, &st)) < 0){ perror("Error in fstat"); return -1; } len_file = st.st_size; if ((*addr = mmap(NULL, len_file, PROT_READ, MAP_SHARED,fd,0)) == MAP_FAILED){ perror("Error in mmap"); return -1; } return 0; } void main(){ char *src, *dst; size_t uncompressedSize, compressedSize; size_t outProcessed; ELzmaStatus state; ISzAlloc g_Alloc; memset(&state, 0, sizeof(state)); g_Alloc.Alloc = SzAlloc; g_Alloc.Free = SzFree; if (get_file("/tmp/bad_test.lzma", (void**)&src) == -1) { return; } // read value from LZMA header uncompressedSize = *(uint64_t *)(&src[LZMA_SIZE_OFFSET]); printf("uncompressed size: 0x%lx\n", uncompressedSize); // set to max size_t, as it was done in U-Boot here with `dst_len` - https://github.com/u-boot/u-boot/blob/master/cmd/lzmadec.c#L24 compressedSize = ~0UL; // this is followed by a subtract of props size - https://github.com/u-boot/u-boot/blob/master/lib/lzma/LzmaTools.c#L51 compressedSize = (compressedSize - LZMA_PROPS_SIZE); printf("compressed size: 0x%lx\n", compressedSize); printf("7z Release: %s\n", MY_VERSION); LzmaDecode( dst, &uncompressedSize, src + LZMA_DATA_OFFSET, &compressedSize, src, LZMA_PROPS_SIZE, LZMA_FINISH_END, &state, &g_Alloc); }
bad_test.lzma
Description: Binary data
create_bad_lzma.py
Description: Binary data