Vulnerability in LZMA implementation
Darek
x64x6a at gmail.com
Thu Aug 1 01:31:24 CEST 2024
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
More information about the U-Boot
mailing list