Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/class/net/ncm_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,12 @@ static bool recv_validate_datagram(const recv_ntb_t *ntb, uint32_t len) {
TU_LOG_DRV("(EE) ill ndp16 length: %d\n", ndp16->wLength);
return false;
}
// the NDP block (wLength bytes from wNdpIndex) must fit within the received NTB, otherwise the
// datagram pointer array walked below (max_ndx is derived from wLength) runs past ntb->data
if ((uint32_t) nth16->wNdpIndex + ndp16->wLength > len) {
TU_LOG_DRV("(EE) ill ndp16 length: %d (%lu)\n", ndp16->wLength, len);
return false;
}
if (ndp16->dwSignature != NDP16_SIGNATURE_NCM0 && ndp16->dwSignature != NDP16_SIGNATURE_NCM1) {
TU_LOG_DRV("(EE) ill signature: 0x%08x\n", (unsigned) ndp16->dwSignature);
return false;
Expand Down
40 changes: 40 additions & 0 deletions test/fuzz/device/net_ncm/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Focused fuzz harness for the NCM receive path (recv_validate_datagram).
#
# It is self-contained: it #includes src/class/net/ncm_device.c and stubs the
# few usbd/glue symbols, so it does not link the whole device stack.
#
# make # build the libFuzzer target: ./net_ncm
# make regression # build + replay the crafted seed under ASan (no engine)
# make clean

TOP := ../../../..

CC ?= clang

INC += \
$(TOP)/src \
.

CFLAGS += $(addprefix -I,$(INC)) -g -O1 -fsanitize=address

FUZZ_FLAGS := -fsanitize=fuzzer

.PHONY: all regression clean get-deps

all: net_ncm

# No external dependencies (the harness only needs the in-tree NCM driver).
get-deps:

# libFuzzer supplies its own main(), so drop the standalone driver here.
net_ncm: fuzz.c
$(CC) $(CFLAGS) $(FUZZ_FLAGS) -DNO_MAIN -o $@ $<

# Standalone build with a plain main(): replays the crafted seed and any extra
# corpus files given as arguments. Used as a deterministic regression.
regression: fuzz.c
$(CC) $(CFLAGS) -o net_ncm_regression $<
./net_ncm_regression

clean:
$(RM) -rf net_ncm net_ncm_regression *.dSYM
140 changes: 140 additions & 0 deletions test/fuzz/device/net_ncm/fuzz.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 Ha Thach (tinyusb.org)
* SPDX-License-Identifier: MIT
*
* This file is part of the TinyUSB stack.
*/

// Focused fuzz harness for the NCM receive path. It feeds a raw NCM Transfer
// Block (NTB), i.e. host-controlled bytes off the OUT endpoint, straight into
// recv_validate_datagram() which is the function that decides whether an
// incoming NTB is well formed before the driver walks its datagram array.
//
// recv_validate_datagram() is static, so the driver is pulled in by #include so
// the harness can reach it. The referenced usbd/glue symbols are stubbed below;
// none of them are exercised by the validation path.
//
// The seed in net_ncm_seed_corpus.zip crafts an NTB whose first NDP carries a
// wLength of 0xfff0 while the transfer itself is only 64 bytes. Before the
// wNdpIndex + wLength bound was added, max_ndx was derived from that wLength and
// the ndp16_datagram[] walk ran tens of kB past ntb->data (a 3200 byte buffer);
// this reproduces that out-of-bounds read under -fsanitize=address.

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

#include "class/net/ncm_device.c"

//--------------------------------------------------------------------+
// Stubs: referenced by the NCM driver, unused by recv_validate_datagram
//--------------------------------------------------------------------+
bool usbd_edpt_xfer(uint8_t rhport, uint8_t ep_addr, uint8_t *buffer, uint16_t total_bytes, bool is_isr) {
(void) rhport; (void) ep_addr; (void) buffer; (void) total_bytes; (void) is_isr;
return true;
}
bool usbd_edpt_busy(uint8_t rhport, uint8_t ep_addr) {
(void) rhport; (void) ep_addr;
return false;
}
bool usbd_edpt_open(uint8_t rhport, tusb_desc_endpoint_t const *desc_ep) {
(void) rhport; (void) desc_ep;
return true;
}
bool usbd_open_edpt_pair(uint8_t rhport, uint8_t const *p_desc, uint8_t ep_count, uint8_t xfer_type,
uint8_t *ep_out, uint8_t *ep_in) {
(void) rhport; (void) p_desc; (void) ep_count; (void) xfer_type; (void) ep_out; (void) ep_in;
return true;
}
bool tud_control_xfer(uint8_t rhport, tusb_control_request_t const *request, void *buffer, uint16_t len) {
(void) rhport; (void) request; (void) buffer; (void) len;
return true;
}
bool tud_control_status(uint8_t rhport, tusb_control_request_t const *request) {
(void) rhport; (void) request;
return true;
}
tusb_speed_t tud_speed_get(void) {
return TUSB_SPEED_FULL;
}
bool tud_network_recv_cb(const uint8_t *src, uint16_t size) {
(void) src; (void) size;
return true;
}
uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg) {
(void) dst; (void) ref; (void) arg;
return 0;
}

//--------------------------------------------------------------------+
// Fuzz entry
//--------------------------------------------------------------------+
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Give the NTB a heap allocation of the exact receive-buffer size so any read
// past ntb->data is caught by AddressSanitizer.
recv_ntb_t *ntb = (recv_ntb_t *) malloc(sizeof(recv_ntb_t));
if (ntb == NULL) {
return 0;
}
size_t n = size < sizeof(ntb->data) ? size : sizeof(ntb->data);
memset(ntb->data, 0, sizeof(ntb->data));
memcpy(ntb->data, data, n);
recv_validate_datagram(ntb, (uint32_t) size);
free(ntb);
return 0;
}

//--------------------------------------------------------------------+
// Standalone driver (built when there is no libFuzzer engine): replays the
// crafted NTB, or any corpus files passed on the command line.
//--------------------------------------------------------------------+
#ifndef NO_MAIN
static void run_crafted_seed(void) {
uint8_t buf[64];
memset(buf, 0, sizeof(buf));

// NTH16: signature, wHeaderLength = sizeof(nth16_t), wSequence, wBlockLength, wNdpIndex
uint32_t nth_sig = NTH16_SIGNATURE;
memcpy(buf + 0, &nth_sig, 4);
uint16_t v;
v = sizeof(nth16_t); memcpy(buf + 4, &v, 2);
v = 0; memcpy(buf + 6, &v, 2);
v = sizeof(buf); memcpy(buf + 8, &v, 2);
v = sizeof(nth16_t); memcpy(buf + 10, &v, 2);

// NDP16 at wNdpIndex: signature, wLength (unbounded pre-fix), wNextNdpIndex
uint32_t ndp_sig = NDP16_SIGNATURE_NCM0;
memcpy(buf + sizeof(nth16_t) + 0, &ndp_sig, 4);
v = 0xFFF0; memcpy(buf + sizeof(nth16_t) + 4, &v, 2);
v = 0; memcpy(buf + sizeof(nth16_t) + 6, &v, 2);

LLVMFuzzerTestOneInput(buf, sizeof(buf));
}

int main(int argc, char **argv) {
if (argc < 2) {
run_crafted_seed();
return 0;
}
for (int i = 1; i < argc; i++) {
FILE *f = fopen(argv[i], "rb");
if (f == NULL) {
continue;
}
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len > 0) {
uint8_t *buf = (uint8_t *) malloc((size_t) len);
if (buf != NULL && fread(buf, 1, (size_t) len, f) == (size_t) len) {
LLVMFuzzerTestOneInput(buf, (size_t) len);
}
free(buf);
}
fclose(f);
}
return 0;
}
#endif
Binary file added test/fuzz/device/net_ncm/net_ncm_seed_corpus.zip
Binary file not shown.
24 changes: 24 additions & 0 deletions test/fuzz/device/net_ncm/tusb_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026 Ha Thach (tinyusb.org)
* SPDX-License-Identifier: MIT
*
* This file is part of the TinyUSB stack.
*/

#ifndef _TUSB_CONFIG_H_
#define _TUSB_CONFIG_H_

// Minimal configuration to build the NCM device driver for the receive-path
// fuzz harness. Only the NCM class is enabled; no root hub / DCD is needed
// because the harness drives recv_validate_datagram() directly.
#define CFG_TUSB_MCU OPT_MCU_NONE
#define CFG_TUSB_OS OPT_OS_NONE

#define CFG_TUD_ENABLED 1
#define CFG_TUD_ENDPOINT0_SIZE 64

// Network class has 2 drivers: ECM/RNDIS and NCM. Fuzz the NCM parser here.
#define CFG_TUD_ECM_RNDIS 0
#define CFG_TUD_NCM 1

#endif
Loading