Skip to content

UBSan "Type mismatch in operation" trap (SIGILL) on unaligned pointer-cast reads (DNS get16, ndpi_serializer) #3213

Description

@1820893135-pixel

Describe the bug

nDPI performs 16/32-bit field reads by casting a byte pointer directly and dereferencing it (*(u_int16_t*)&payload[*i] / ntohs(*((u_int16_t *)&buf[offset]))). When the byte offset is odd the pointer is not 2-byte aligned, which is undefined behavior (CWE-843). A build with -fsanitize=address,undefined -fno-sanitize-recover=all traps on it (UBSan reports Type mismatch in operation) and the process dies with SIGILL.

Two concrete sites are reachable from untrusted input and were confirmed to crash:

  1. DNS dissector get16() (src/lib/protocols/dns.c) — an attacker-controlled DNS message whose query/answer/additional walk leaves an odd offset before a 16-bit field is read.
  2. ndpi_serializer.c deserializer helpers — ndpi_init_serializer_ll reads *((u_int16_t*)&deserializer->buffer.data[offset]) on an unaligned offset (reached via get_ndpi_flow_info in example/reader_util.c).

The same pointer-cast pattern appears library-wide (dns.c, ndpi_serializer.c, capwap.c, hpvirtgrp.c, tls.c).

Expected behavior

nDPI should read multi-byte fields from a byte buffer without relying on host alignment: unaligned loads must be performed with memcpy or byte assembly, so the library is portable to strict-alignment CPUs and never triggers UBSan.

Obtained behavior

UBSan aborts the process (SIGILL / exit 134) on a single 45-byte DNS packet, both through the DNS dissector (fuzz_dns_parse) and through the full packet-processing path (fuzz_ndpi_reader). Under ASan/UBSan builds this is a remote DoS for any nDPI consumer that parses untrusted traffic.

nDPI Environment

  • OS: Ubuntu (OSS-Fuzz base-builder image, x86-64)
  • OS version: Debian 12 bookworm based
  • Architecture: x86-64 (the crash is the UBSan trap; on strict-alignment CPUs ARM/MIPS/RISC-V the same misaligned load faults natively)
  • nDPI version / commit: 252e2a5548a1ea4eb54d3089af836207b3ef32e6 (2026-07-28, "Add Nebula protocol dissector (Add Nebula protocol dissector #3210)"), version 5.1.0
  • Compilation flags: -fsanitize=address,undefined -fno-sanitize-recover=all (equivalent to ./configure --with-sanitizer)

How to reproduce the reported bug

Used ndpiReader options

# feed the attached pcap (or any of the 3 trigger packets wrapped in Ethernet/IP/UDP)
ndpiReader -i trigger1.pcap

Steps to reproduce

  1. Build nDPI with UBSan: ./autogen.sh && CFLAGS="-fsanitize=address,undefined -fno-sanitize-recover=all" ./configure --with-only-libndpi && make
  2. Generate the trigger packet (45-byte DNS payload, below) and wrap it as a UDP pcap.
  3. Replay:
    • DNS dissector path: ./fuzz/fuzz_dns_parse < trigger1.bin
    • Full path: ./ndpiReader -i trigger1.pcap
  4. Observe UBSan Type mismatch in operation trap → SIGILL / exit 134.

Trigger packets (45 bytes each, raw DNS payload, UDP)

Trigger 1 — QDCOUNT=12 malformed query:

echo -n '4d150120000c00000000000103777777046e746f70036f72670000010001000029100000000000000400030000' | xxd -r -p > trigger1.bin

Structure: Header: ID=0x4d15 Flags=0x0120 (query) QDCOUNT=0x000c (12!) ANCOUNT=0 NSCOUNT=0 ARCOUNT=1; QNAME: \x03www \x04ntop \x03org \x00; QTYPE=0x0001; QCLASS=0x0001; trailing EDNS OPT.

Why: process_queries() loops QDCOUNT=12 but only one real query exists. On the 2nd iteration getNameLength(28) returns 1 (payload[28]=0x00), so x=29 (odd) and get16() performs *(u_int16_t*)&payload[29] — unaligned 16-bit load.

Trigger 2 — DNS response with EDNS OPT additional record:

echo -n '4d15ab20000100000000000103777777046e746f70036f72670000010001000029100000000000000400030000' | xxd -r -p > trigger2.bin

Why: process_additionals walks the OPT record; at off=28 the name terminator makes getNameLength return 1, so x=29 (odd).

Trigger 3 — QNAME containing compression-pointer byte 0xC2:

echo -n '4d150120000100000000000103777777046e746f70c26f72670000010001000029100000000000000400030000' | xxd -r -p > trigger3.bin

Why: getNameLength returns 2 for the 0xC2 pointer and the following walk lands on an odd offset.

Trigger result

DNS dissector path (fuzz_dns_parse, all 3 triggers):

==806276==ERROR: AddressSanitizer: ILL on unknown address 0x5619571c2925 (pc 0x5619571c2925 ...)
    #0 0x5619571c2925 in __clang_trap_msg$Undefined Behavior Sanitizer$Type mismatch in operation /src/ndpi/src/lib/./protocols/dns.c
    #1 0x5619571c2925 in get16 /src/ndpi/src/lib/./protocols/dns.c
    #2 0x5619571c2925 in process_queries /src/ndpi/src/lib/./protocols/dns.c:340:16
    #3 0x5619571be13f in search_dns /src/ndpi/src/lib/./protocols/dns.c:1062:10
    #4 0x5619571b812f in LLVMFuzzerTestOneInput /src/ndpi/fuzz/fuzz_dns_parse.c:94:3
    ...
SUMMARY: AddressSanitizer: ILL /src/ndpi/src/lib/./protocols/dns.c in __clang_trap_msg$Undefined Behavior Sanitizer$Type mismatch in operation

Full packet-processing path (fuzz_ndpi_reader, same pcap) — same root cause, second site:

==807234==ERROR: AddressSanitizer: ILL on unknown address ...
    #0 ... in __clang_trap_msg$Undefined Behavior Sanitizer$Type mismatch in operation /src/ndpi/src/lib/ndpi_serializer.c
    #1 ... in ndpi_init_serializer_ll /src/ndpi/src/lib/ndpi_serializer.c
    #2 ... in get_ndpi_flow_info /src/ndpi/fuzz/../example/reader_util.c:944:11
SUMMARY: AddressSanitizer: ILL ... in __clang_trap_msg$Undefined Behavior Sanitizer$Type mismatch in operation
  • Replay exit code: 134 (UBSan trap aborts)
  • Deterministic: yes — all 3 triggers reproduce; nDPI's own dns_resp / dns_query_opt seeds crash too
  • Note: on x86-64 the misaligned load does not fault natively (the crash is the UBSan trap), but on strict-alignment architectures (ARM, MIPS, RISC-V) the same code faults in production.

Additional context

Affected code (DNS get16):

/* src/lib/protocols/dns.c:162-168 */
static u_int16_t get16(u_int *i, const u_int8_t *payload) {
  u_int16_t v = *(u_int16_t*)&payload[*i];   /* <-- unaligned 16-bit load when *i is odd */
  (*i) += 2;
  return(ntohs(v));
}

Affected code (serializer):

/* src/lib/ndpi_serializer.c:548 (and 555,569,576,583) */
*s = ntohs(*((u_int16_t *) &deserializer->buffer.data[offset]));

Suggested fix:

  1. Replace pointer-cast reads with byte assembly or memcpy, e.g.:
static u_int16_t get16(u_int *i, const u_int8_t *payload) {
  u_int16_t v = (u_int16_t)(((u_int16_t)payload[*i] << 8) | payload[*i + 1]);
  (*i) += 2;
  return v;
}
  1. In the DNS walkers (process_queries / process_answers / process_additionals), treat an odd walk offset as malformed (NDPI_MALFORMED_PACKET) and bail out before calling get16().

  2. Audit the whole library for *(u_int16_t*)& / *(u_int32_t*)& pointer-cast dereferences (present in dns.c, ndpi_serializer.c, capwap.c, hpvirtgrp.c, tls.c) and replace with portable safe reads.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions