CVE-2026-24823

Return of the Living Dead: How X-TRACK Resurrected a Critical zlib Bug

Alon Barad
Alon Barad
Software Engineer

Jan 28, 2026·6 min read·2 visits

Executive Summary (TL;DR)

FASTSHIFT's GPS bike computers are running a zombie version of zlib. They copied code vulnerable to CVE-2022-37434 (a 4-year-old bug), didn't patch it, and now have a CVSS 10.0 RCE on their hands. To make matters worse, the fix introduces a potential crash loop.

A critical heap buffer overflow in FASTSHIFT X-TRACK firmware caused by an unpatched fork of the zlib library. This vulnerability allows remote code execution via malformed GZIP headers, specifically through the 'extra field' handling logic.

The Hook: Copy, Paste, Pwn

History doesn't repeat itself, but developers certainly repeat their git clones. In the world of embedded systems, "if it ain't broke, don't fix it" is the law of the land. Unfortunately, "if it is broke but compiles, ship it" is often the amendment. FASTSHIFT, the makers of the X-TRACK GPS sports bike computer, learned this the hard way.

At its core, X-TRACK uses a module called PNGdec to handle image rendering—likely for maps or custom UI elements. Buried deep inside Software/X-Track/USER/App/Utils/lv_img_png/PNGdec/src/inflate.c lies a direct copy of the venerable zlib library. But this isn't just any copy; it's a fossil. It lacks the critical security patches from 2022 that addressed CVE-2022-37434.

This isn't a complex logic error unique to the bike computer's proprietary algorithms. This is a supply chain specter. By forking code and failing to maintain upstream parity, FASTSHIFT reintroduced a critical heap buffer overflow that the rest of the world fixed years ago. The result? A CVSS 10.0 vulnerability that turns a high-end sports tracker into a remote shell for anyone who knows how to craft a bad GZIP header.

The Flaw: Trusting the Header

To understand the break, we have to look at how GZIP works. The GZIP file format allows for an optional "extra field" in its header. This field is variable-length and can contain arbitrary data. The inflate() function—the workhorse of decompression—uses a state machine to parse these headers byte by byte.

The vulnerability exists in the state responsible for copying this extra field into memory. The decompressor keeps track of two things: extra_len (the total size of the extra field stated in the header) and length (how many bytes are left to read). It calculates the current write offset (len) by subtracting the remaining length from the total length.

Here is the logic flaw: The code assumes that if you tell it the extra field is 4096 bytes, you have allocated a 4096-byte buffer to hold it. But what if the header claims the extra field is massive, but the destination buffer is tiny? The code calculates the offset, blindly trusts it, and prepares to write. It performs a check to ensure the amount of data being copied doesn't overflow the remaining space from that offset, but it never checks if the offset itself is already out of bounds.

It is the memory corruption equivalent of standing on a cliff edge. The code checks if your step forward is too big, but fails to notice you are already hovering Wile E. Coyote-style over the abyss. Once the memcpy hits, it's writing to the heap at an arbitrary offset relative to the buffer.

The Code: The Smoking Gun

Let's look at the C code responsible for this disaster. This snippet is from inflate.c before the patch. Pay attention to how len is calculated and used immediately as an offset for the destination pointer.

// The Vulnerable Logic in inflate.c
if (state->flags & 0x0400) {
    // ... (logic to get next input bytes)
 
    // 1. Calculate the offset into the destination buffer
    len = state->head->extra_len - state->length;
 
    if (state->head != Z_NULL && state->head->extra != Z_NULL) {
        // 2. WRITE to state->head->extra + len
        // The ternary operator checks copy size, but NOT the start position (len)
        zmemcpy(state->head->extra + len, next,
                len + copy > state->head->extra_max ?
                state->head->extra_max - len : copy);
    }
}

If an attacker provides a GZIP stream where extra_len is significantly larger than extra_max, len becomes a large positive integer. The zmemcpy destination state->head->extra + len now points deep into the heap, far beyond the allocated extra buffer.

Now, look at the fix provided in Pull Request #120. They added a check, but in a rush to fix it, they introduced a new problem:

// The Patched (and slightly broken) Logic
if (copy) {
    // NEW BUG: This line runs BEFORE checking if state->head is NULL!
    len = state->head->extra_len - state->length;
 
    if (state->head != Z_NULL &&
        state->head->extra != Z_NULL &&
        len < state->head->extra_max) { // The actual security fix
        zmemcpy(state->head->extra + len, next, ...);
    }
}

The security fix is valid: len < state->head->extra_max prevents the OOB write. However, they moved the calculation of len above the NULL check for state->head. If state->head is NULL (which is valid in zlib usage if headers aren't needed), this code will dereference a NULL pointer and crash the device immediately. It stops the hacker, sure, but it also stops the bike computer.

The Exploit: Grooming the Heap

Exploiting this on an embedded device like the X-TRACK is an exercise in heap feng shui. The goal is to position a critical data structure immediately after the extra buffer in memory. Since we control the size of the allocation (via the GZIP header parameters) and the offset of the write (via the vulnerability), we have high-precision write primitives.

The Attack Chain:

  1. Preparation: The attacker sends a valid, benign PNG/GZIP to the device to initialize the memory allocator and fragment the heap in a predictable way.
  2. The Payload: The attacker sends a malformed GZIP stream. The header claims an extra field length of 0xFFFF (65535), but the internal buffer extra_max is only allocated for, say, 1024 bytes.
  3. The Trigger: The inflate loop processes the header. It calculates len as, for example, 4000. It attempts to write data to buffer_base + 4000.
  4. The Overwrite: At buffer_base + 4000 sits a function pointer for the UI event handler or a return address on the heap (if stack data is spilled there). The zmemcpy overwrites this pointer with the address of our shellcode (or a ROP gadget).
  5. Execution: The next time the device refreshes the screen or processes a button press, it jumps to our code. We now control the GPS, the display, and any paired Bluetooth connections.

The Impact: Why Should We Panic?

A CVSS 10.0 isn't handed out lightly. The "Critical" rating comes from the combination of remote accessibility and total compromise. This isn't just a Denial of Service (though it is that, too).

Confidentiality: The X-TRACK stores precise GPS location history. An attacker could exfiltrate the user's home address, daily routes, and workout schedules. Integrity: The firmware can be permanently modified. An attacker could inject a persistent backdoor that survives reboots, turning the device into a botnet node or a localized BLE sniffer. Availability: The device can be bricked permanently by corrupting the bootloader or critical non-volatile storage during the memory corruption event.

Because the attack vector is likely the processing of map data or custom images loaded onto the device, this could be wormable if users share infected map packs or themes.

Fix Analysis (1)

Technical Appendix

CVSS Score
10.0/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/S:N/AU:Y/R:U/V:C/RE:L/U:Red
EPSS Probability
0.04%
Top 87% most exploited

Affected Systems

FASTSHIFT X-TRACK Firmware <= 2.7

Affected Versions Detail

Product
Affected Versions
Fixed Version
FASTSHIFT X-TRACK
FASTSHIFT
<= 2.72.8
AttributeDetail
CVE IDCVE-2026-24823
CVSS v4.010.0 (Critical)
CWECWE-787 (OOB Write)
Attack VectorNetwork (Remote)
Affected Componentinflate.c (PNGdec module)
Root CauseUnchecked pointer offset in zmemcpy
CWE-787
Out-of-bounds Write

The software writes data past the end, or before the beginning, of the intended buffer.

Vulnerability Timeline

Original bug fixed in upstream zlib (CVE-2022-37434)
2022-08-05
CVE-2026-24823 Published for X-TRACK
2026-01-27
Patch PR #120 Merged
2026-01-27

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.