Note: This is an exclusive, easy-to-understand deep dive into CVE-2023-40238, also known as “LogoFAIL”, targeting the InsydeH2O BIOS used in many Lenovo laptops. If you want to know how a simple but nasty bug in logo file parsing can compromise your system at boot — read on!
1. What Is CVE-2023-40238?
CVE-2023-40238 is an important vulnerability found in the BmpDecoderDxe driver of the InsydeH2O UEFI BIOS firmware, which many Lenovo devices use. It was discovered as part of a group of issues called LogoFAIL, named because they revolve around how custom OEM/branding logos (usually shown at boot) are loaded from BMP image files.
When a specially crafted BMP image file is processed, an attacker can use it to compromise the entire boot process. The scary part? This happens at the firmware (BIOS) level, long before your operating system even starts.
References:
- CVE-2023-40238 at NVD (NIST)
- Lenovo Security Advisory – LEN-160093
- Binarly Research – LogoFAIL page
Kernel 5.6 (before 05.60.47)
If you have an older BIOS version on a Lenovo device, you *might* be at risk!
3. The LogoFAIL Vulnerability: In Simple Terms
When your computer boots, the BIOS can display a logo (like the Lenovo logo). This image is usually stored as a BMP file, which is a simple bitmap file format. Now, to save space, the BMP can be compressed using RLE4 or RLE8 (run-length encoding).
Here’s the catch:
When BmpDecoderDxe tries to decode (parse) a specially crafted BMP, it uses two fields: PixelHeight and PixelWidth. Due to a signedness bug (treating a number as signed when it should be unsigned, or vice versa), a hacker can make these numbers negative or huge, causing the BIOS to copy image data into the wrong place in memory — or even outside the buffer!
Why does this matter?
Because this all happens *before* the operating system boots, attackers can gain *firmware-level* code execution, potentially bypassing *all* your security.
a) The Vulnerable BMP Structure
A BMP file has a header, and then, among other fields, it defines width and height of the image. These should *never* be negative.
If the code looks like this
// Fake snippet similar to vulnerable code logic
UINT32 RowSize = PixelWidth * BytesPerPixel;
UINT8 *DstPtr = AllocateBuffer(RowSize * PixelHeight);
// ... fill buffer during decompression loop
for (INT32 y = ; y < PixelHeight; y++) {
// copy/compress row data
memcpy(DstPtr + y * RowSize, ...);
}
If PixelHeight is negative (say, -10), this could cause the loop to go haywire, maybe wrapping around to a huge number if treated as unsigned, and overwrite memory it shouldn’t touch.
b) Crafting a Malicious BMP
Below is a simple Python script to create a BMP with an *evil* PixelHeight.
import struct
def evil_bmp(height, width):
# BMP Header
header = b'BM'
filesize = 54 + width * height * 3
header += struct.pack('<I', filesize)
header += b'\x00\x00' # Reserved1
header += b'\x00\x00' # Reserved2
header += b'\x36\x00\x00\x00' # Pixel data offset (54 bytes)
# DIB Header
header += b'\x28\x00\x00\x00' # DIB header size (40 bytes)
header += struct.pack('<i', width) # Width (signed!)
header += struct.pack('<i', height) # Height (signed!)
header += b'\x01\x00' # planes
header += b'\x18\x00' # bits per pixel (24)
header += b'\x00\x00\x00\x00' # compression (none)
header += struct.pack('<I', width * height * 3) # Image size
header += b'\x13\xB\x00\x00' # x pixels per meter (default)
header += b'\x13\xB\x00\x00' # y pixels per meter (default)
header += b'\x00\x00\x00\x00' # colors used
header += b'\x00\x00\x00\x00' # important colors
# Pixel Data (zeroes)
pixel_data = b'\x00' * (width * abs(height) * 3)
return header + pixel_data
# To create a BMP with negative height (-10)
with open('evil.bmp', 'wb') as f:
f.write(evil_bmp(-10, 100))
c) How Would An Exploit Work?
- The attacker puts this BMP file on disk where the BIOS expects to load the logo (the file path is often well-known for PXE or OEM updates).
The malcrafted negative height causes code to overwrite memory outside the expected buffer.
- An attacker can then use that overwrite to direct code execution (for instance: overwriting return addresses or function pointers in memory).
- All protections (including OS-level Secure Boot!) are side-stepped because the attack happens at firmware time.
5. How Does It Work?
The root of the bug:
BMP headers define height and width as 32-bit *signed integers*. But code often assumes only *positive* numbers, so fails to sanitize negative values. In InsydeH2O’s BmpDecoderDxe, this means the length calculations go sideways when decompressing RLE4 or RLE8 images.
When the bug triggers during decompression, the code writes past allocated memory, allowing control over memory used by other DXE modules. In advanced attacks, this is usable for persistent malware that survives reinstalls, wipes, and OS security features.
Persistence: Implants can survive drive formatting and OS reinstall.
- Supply chain attacks: If attackers can replace logo files at manufacturing, they can backdoor systems before users ever receive them.
- Remote attacks (theoretical): If the logo file is updatable by the OS or user, malware could do this remotely via software update mechanisms.
7. References For Further Reading
- CVE-2023-40238 NVD Entry
- Binarly Research: LogoFAIL
- Lenovo Advisory: LEN-160093
- GitHub: UEFI LogoFAIL detection tools
8. How Can You Protect Yourself?
1. Check your BIOS version. Update if you’re running a vulnerable version. (See Lenovo/Supplier site for correct files.)
2. Minimize logo changes. Don’t update/replace the OEM logo unless it’s from a trusted source.
Monitor vendor advisories for new UEFI and firmware vulnerabilities.
In summary:
CVE-2023-40238 (LogoFAIL) in InsydeH2O firmware reminds us that even the tiniest bug in the earliest code our laptops run can have *massive* impact—thanks to how little validation is done for “harmless” things like images.
Stay patched, don’t treat logo images as harmless, and keep an eye on your firmware updates!
Timeline
Published on: 12/07/2023 04:15:06 UTC
Last modified on: 12/16/2023 01:15:07 UTC