Skip to content

MSP_DATAFLASH_READ can write four bytes beyond the calculated response capacity #11776

Description

@jFriedli

Current Behavior

serializeDataflashReadReply() calculates the maximum flash read length from the current remaining destination-buffer space:

const int bytesRemainingInBuf = sbufBytesRemaining(dst);
uint16_t readLen = (size > bytesRemainingInBuf) ? bytesRemainingInBuf : size;

It then writes the four-byte address into the same destination buffer:

sbufWriteU32(dst, address);

and afterwards reads readLen bytes directly into the new destination pointer:

const int bytesRead = flashfsReadAbs(address, sbufPtr(dst), readLen);
sbufAdvance(dst, bytesRead);

The four bytes consumed by sbufWriteU32() are not subtracted when readLen is calculated.

For example, if 512 bytes remain and the request asks for 512 bytes:

calculated readLen: 512
address written:       4 bytes
flash data written:  512 bytes
total required:       516 bytes
available:            512 bytes

Affected source in the tested commit:

  • https://github.com/iNavFlight/inav/blob/c5c593d71d33c8e284bf9cd34381588fda7a98c8/src/main/fc/fc_msp.c#L2793-L2822

MSP-over-telemetry response storage can be 512 bytes when CRSF support is compiled:

  • https://github.com/iNavFlight/inav/blob/c5c593d71d33c8e284bf9cd34381588fda7a98c8/src/main/telemetry/msp_shared.h#L282-L295
  • https://github.com/iNavFlight/inav/blob/c5c593d71d33c8e284bf9cd34381588fda7a98c8/src/main/telemetry/crsf.h#L301-L305

Security impact

This is a memory-corruption vulnerability in an MSP command handler.

An MSP-capable peer controls the requested DATAFLASH read size. serializeDataflashReadReply() calculates readLen using the buffer capacity before serializing the four-byte address into that same buffer. As a result, a maximum-size response can write four bytes beyond the actual writable region.

The immediate impact is an out-of-bounds write in flight-controller memory. Depending on the surrounding memory layout, this can cause:

  • flight-controller crash or reset,
  • corruption of adjacent state,
  • denial of service / loss of flight control,
  • and potentially stronger memory-corruption effects.

I have not demonstrated controlled code execution, so the report should not be interpreted as an RCE claim.

Attacker preconditions

The attacker or peer must be able to send an accepted MSP_DATAFLASH_READ command. INAV supports MSP through more than direct USB/UART in some deployments, including telemetry integrations, but the actual authentication/trust boundary depends on the configured transport.

What is proven

  • The vulnerable calculation/order is present in the tested INAV 9.1 source.
  • A host-side ASan harness reproducing the same ordering writes past the end of the response allocation.

What is not proven

  • A physical FC crash.
  • Reliable exploitation beyond denial of service/memory corruption.
  • Unauthenticated RF reachability on all deployments.

Steps to Reproduce

I reproduced the buffer-accounting error in a host-side AddressSanitizer harness.

This is not an ASan trace from an INAV firmware image or a physical flight controller. The harness reproduces the ordering and buffer-size calculation used by serializeDataflashReadReply().

Save as poc_inav_dataflash_offby4.c:

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    uint8_t *ptr;
    uint8_t *end;
} sbuf_t;

static int remain(sbuf_t *b)
{
    return (int)(b->end - b->ptr);
}

static void w8(sbuf_t *b, uint8_t v)
{
    *b->ptr++ = v;
}

static void w32(sbuf_t *b, uint32_t v)
{
    for (int i = 0; i < 4; i++) {
        w8(b, v >> (8 * i));
    }
}

static uint32_t flashSize = 1024;

static int readAbs(uint32_t address, uint8_t *buffer, unsigned len)
{
    if (address + len > flashSize) {
        len = flashSize - address;
    }

    memset(buffer, 0x41, len);
    return (int)len;
}

static void vulnerable(sbuf_t *dst, uint32_t address, uint16_t size)
{
    int bytesRemainingInBuf = remain(dst);
    uint16_t readLen =
        (size > bytesRemainingInBuf) ? bytesRemainingInBuf : size;

    if (readLen > flashSize - address) {
        readLen = flashSize - address;
    }

    w32(dst, address);

    int bytesRead = readAbs(address, dst->ptr, readLen);
    dst->ptr += bytesRead;
}

int main(void)
{
    uint8_t *p = malloc(512);
    sbuf_t b = {p, p + 512};

    vulnerable(&b, 0, 512);

    free(p);
    return 0;
}

Compile and run:

gcc -O0 -g -fsanitize=address,undefined     poc_inav_dataflash_offby4.c     -o poc_inav_dataflash_offby4

ASAN_OPTIONS=abort_on_error=1 ./poc_inav_dataflash_offby4

Observed locally:

ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 512
0 bytes after 512-byte region

The tested INAV tree was:

commit: c5c593d71d33c8e284bf9cd34381588fda7a98c8
date:   2026-07-18 22:18:56 -0500
subject: Merge pull request #11728 from iNavFlight/release/9.1

I also verified directly in that clone that the affected calculation/order remains present.

Expected behavior

The amount read from flash should be limited to the response space remaining after accounting for the four-byte address field.

No flash backend should receive a destination length larger than the actual writable region at sbufPtr(dst).

Suggested solution(s)

Write or reserve the address field before calculating the remaining read capacity:

sbufWriteU32(dst, address);

const int bytesRemainingInBuf = sbufBytesRemaining(dst);
uint16_t readLen =
    (size > bytesRemainingInBuf) ? bytesRemainingInBuf : size;

Alternatively, explicitly subtract the serialized address size before selecting readLen.

The normal FlashFS end-of-volume bounds check should remain in addition to the destination-buffer check.

Additional context

This report establishes a production buffer-accounting bug and demonstrates the resulting overwrite with a host-side sanitizer harness.

I have not reproduced a crash on a physical flight controller and am not claiming a specific exploit primitive such as code execution.

Reachability depends on an attacker or peer being able to submit an accepted MSP_DATAFLASH_READ command through an MSP-capable transport. The report does not assume that every radio/telemetry deployment is unauthenticated.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions