Security #7393
closedtcp: segfault on StreamingBufferSlideToOffsetWithRegions
282509f70c4ce805098e59535af445362e3e9ebd
8900041405dbb5f9584edae994af2100733fb4be
9a53ec43b13f0039a083950511a18bf6f408e432
Description
Triggers on the same callstack
On SMB traffic (app-layer.smb.stream-depth == 200mb)
On a compile of the 7.0.6 tag
(gdb) bt #0 0x00007f78baad7aa0 in __memset_sse2 () from /lib64/libc.so.6 #1 0x0000559bde7c244d in memset (__len=<optimized out>, __ch=0, __dest=<optimized out>) at /usr/include/bits/string3.h:84 #2 GrowRegionToSize (size=<optimized out>, region=0x7f77f41cee40, cfg=0x559bdf104598 <stream_config+56>, sb=0x7f77f41cee40) at util-streaming-buffer.c:736 #3 StreamingBufferSlideToOffsetWithRegions (slide_offset=37755546, cfg=0x559bdf104598 <stream_config+56>, sb=0x7f77f41cee40) at util-streaming-buffer.c:946 #4 StreamingBufferSlideToOffset (sb=sb@entry=0x7f77f41cee40, cfg=cfg@entry=0x559bdf104598 <stream_config+56>, offset=offset@entry=37755546) at util-streaming-buffer.c:1016 #5 0x0000559bde7a61ff in StreamTcpPruneSession (f=0x7f7786a7a090, flags=<optimized out>) at stream-tcp-list.c:940 #6 0x0000559bde768c89 in FlowWorker (tv=0x559be8a787f0, p=0x7f780117ff70, data=0x7f78011eabf0) at flow-worker.c:657 #7 0x0000559bde6c06bd in TmThreadsSlotVarRun (tv=tv@entry=0x559be8a787f0, p=p@entry=0x7f780117ff70, slot=<optimized out>) at tm-threads.c:135 #8 0x0000559bde793015 in TmThreadsSlotProcessPkt (p=0x7f780117ff70, s=<optimized out>, tv=0x559be8a787f0) at tm-threads.h:200 #9 AFPParsePacketV3 (pbd=<optimized out>, ppd=0x7f77286e1ee0, ptv=0x7f78011809a0) at source-af-packet.c:1013 #10 AFPWalkBlock (pbd=<optimized out>, ptv=0x7f78011809a0) at source-af-packet.c:1032 #11 AFPReadFromRingV3 (ptv=0x7f78011809a0) at source-af-packet.c:1079 #12 0x0000559bde79331b in ReceiveAFPLoop (tv=0x559be8a787f0, data=<optimized out>, slot=<optimized out>) at source-af-packet.c:1431 #13 0x0000559bde6c1eca in TmThreadsSlotPktAcqLoop (td=0x559be8a787f0) at tm-threads.c:318 #14 0x00007f78bc00eea5 in start_thread () from /lib64/libpthread.so.0 #15 0x00007f78bab46b2d in clone () from /lib64/libc.so.6
# The result of ToNextMultipleOf from line 723 (gdb) print grow grow = 1327104 # The offset in the memory region for the start of the new data, as per line 735 (gdb) print region->buf_size region->buf_size = 1329152 # The value of diff, as per line 734 (gdb) print grow - region->buf_size diff = 4294965248
Updated by Philippe Antoine 8 months ago
GrowRegionToSize
does not seem to check that size
is bigger than current region->buf_size
From the caller point of view start->buf_size
may be bigger than mem_size
even if if (start->buf_size < next->buf_size)
as we have @mem_size = ToNextMultipleOf(next_re - slide_offset, cfg->buf_size);@norg
But looks painful to produce a pcap that will get to this part of the code with the right set of conditions/state...
Updated by Philippe Antoine 8 months ago
Side question: how to fuzz efficiently this part of code ?
Updated by Victor Julien 8 months ago
Philippe Antoine wrote in #note-2:
Side question: how to fuzz efficiently this part of code ?
A dedicated fuzz target?
Updated by Victor Julien 8 months ago
This wouldn't need the tcp engine at all I think, just the streaming buffer API.
Updated by Philippe Antoine 8 months ago
StreamingBufferConfig cfg = { 8+0xb, 0xff, 8+0x3f, NULL, NULL, NULL }; StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x2b, 0x74); StreamingBufferAppend(sb, &cfg, &seg1, segd, 0xfe); StreamingBufferSlideToOffset(sb, &cfg, 0x6e);
=================================================================
6386ERROR: AddressSanitizer: heap-buffer-overflow on address 0x606000001cea at pc 0x0001057f7072 bp 0x7ff7bc956ac0 sp 0x7ff7bc956290
WRITE of size 4294967087 at 0x606000001cea thread T0
#0 0x1057f7071 in __asan_memset+0x781 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0xdb071)
#1 0x103a56596 in GrowRegionToSize util-streaming-buffer.c:735
#2 0x103a49c31 in StreamingBufferSlideToOffset util-streaming-buffer.c:1025
#3 0x1035a9282 in LLVMFuzzerTestOneInput fuzz_streambuf.c:44
What do you think about it Victor ?
Updated by Philippe Antoine 8 months ago
StreamingBufferConfig cfg = { 8+0x2f, 0x9, 8+1, NULL, NULL, NULL };
StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 1, 0x77);
StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x72, 0x6b);
StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x72, 0x1);
==10459==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x611000001a1c at pc 0x00010b3f7eee bp 0x7ff7b6cb2ae0 sp 0x7ff7b6cb22a0 WRITE of size 114 at 0x611000001a1c thread T0 #0 0x10b3f7eed in __asan_memmove+0xe4d (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0xdbeed) #1 0x1096f4ec0 in StreamingBufferInsertAt util-streaming-buffer.c:1535
Updated by Philippe Antoine 8 months ago
With STREAMING_BUFFER_CONFIG_INITIALIZER
values:
StreamingBufferConfig cfg1 = { 2048, 8, 262144, NULL, NULL, NULL };
StreamingBuffer *sb = StreamingBufferInit(&cfg1);
StreamingBufferInsertAt(sb, &cfg1, &seg1, segd, 15832832, 825239);
StreamingBufferAppend(sb, &cfg1, &seg1, segd, 9934743);
StreamingBufferSlideToOffset(sb, &cfg1, 9934743);
getting grow 6723584 <= 15832832 bs
end thus the overflow
Updated by Philippe Antoine 8 months ago
Better reproducer
StreamingBufferConfig cfg = { 2048, 8, 262144, NULL, NULL, NULL };
StreamingBufferSegment seg1;
StreamingBuffer *sb = StreamingBufferInit(&cfg);
uint8_t segd[0x100000];
memset(segd, 'A', 256);
if (size < 15)
return 0;
int r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x2000, 0);
if (r != 0)
return 0;
r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x100, 0x221200);
if (r != 0)
return 0;
r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0xFFFF0, 0x1e0000);
if (r != 0)
return 0;
StreamingBufferSlideToOffset(sb, &cfg, 0x221212);
Updated by Philippe Antoine 8 months ago
Ok, I am getting to the root cause of this : RegionsIntersect
is missing a case : the one where the new insertion contains strictly the existing region (regularized with max gap)
int r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x2000, 0);
if (r != 0)
return 0;
r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x100, 0xc0000);
if (r != 0)
return 0;
r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x80802, 0x7FFFF);
if (r != 0)
return 0;
StreamingBufferSlideToOffset(sb, &cfg, 0xc0000);
First we create a main region, ok
Then, we create a next small region at 0xc0000 = 3 times STREAMING_BUFFER_REGION_GAP_DEFAULT (262144)
We have [ main offset:0 size:2000 offset:2000 ][ gap:be000 ][ aux offset:c0000 size:800 offset:100 ](max 2, cnt 2, sb->regions 2)
StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x80802, 0x7FFFF);
. makes things go wrong
It is not merged with [ aux offset:c0000 size:800 offset:100 ]
. as it should
Because we ar looking for first region matching 7ffff/100801
And we have @7ffff < 80000==reg_o==r->stream_offset - cfg->region_gap==c0000-40000 < reg_re==r->stream_offset + r->buf_size + cfg->region_gap==c0000+800+40000==100800 < 100801@norg
As we can see, this requires calling StreamingBufferInsertAt
with data length >= 2* STREAMING_BUFFER_REGION_GAP_DEFAULT + bufsize=2048 + 2, so 526 338 bytes
Updated by Philippe Antoine 8 months ago
If we want to see if region [C:D] intersects with [A:B] (given c<d and a<b)
There are 6 cases- a b c d : no match
- a c b d : match ok
- a c d b : should match but does not in current code
- c a b d : match ok
- c a d b : match ok
- c d a b : no match
Updated by Philippe Antoine 8 months ago
But only caller of StreamingBufferInsertAt
is using uint16_t as data length, so always smaller than 512 kb !
By the way, it should be reflected in function prototype...
Updated by Philippe Antoine 8 months ago
So TCP reproducer without exploiting bug in ListRegions
int r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x8000, 0);
if (r != 0)
return 0;
r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x100, 0xc0000);
if (r != 0)
return 0;
for (size_t i =1; i<14; i++) {
r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0x8000, 0+0x8000*i);
if (r != 0)
return 0;
}
for (size_t i =1; 0x7FF*i<0x50000; i++) {
StreamingBufferSlideToOffset(sb, &cfg, 0x7FF*i);
}
r = StreamingBufferInsertAt(sb, &cfg, &seg1, segd, 0xFFFF, 0x70000);
if (r != 0)
return 0;
StreamingBufferSlideToOffset(sb, &cfg, 0x60000);
Updated by Philippe Antoine 8 months ago
Analysis : we aim to have
start->buf_size > mem_size==next_re - slide_offset
So, we want to maximize start->buf_size while keeping a next region which must keep more than 256kbytes apart
We grow buf_size by multiple appending StreamingBufferInsertAt
Then, we StreamingBufferSlideToOffset while keeping buf_size by sliding just one byte less than cfg->buf_size and enjoying the rounding
But we stop before slide offset + buf_size reach next offset
We then append maximum data without getting the regions merged
We do a big slide, but not so big compared to buf_size, so that we reach next
Updated by Philippe Antoine 8 months ago
- Status changed from New to In Review
- Severity changed from MODERATE to CRITICAL
- Label Needs backport to 7.0 added
Gitlab MR
I assess this critical : default config and crash
I do not think we can turn into RCE as it will memset 4Gbytes with 0...
On the fuzzing side, I am not sure it is worth to add a target which did not find the tricky bug in a few minutes on my laptop...
Also I hit plenty of debug assertions (which I disabled) and I guess caller respects them...
Updated by Philippe Antoine 8 months ago
- Assignee changed from OISF Dev to Philippe Antoine
Updated by Philippe Antoine 8 months ago
Wondering if we can turn the StreamingBufferr API reproducer into a pcap by
- have a 3 way handshake
- Replace StreamingBufferInsertAt(length, offset) by a TCP packet with length and sequence number as offset
- Replace StreamingBufferSlideToOffset(offset) with TCP ack packet in the other direction with ack number as offset
Updated by Juliana Fajardini Reichow 7 months ago
- CVE set to 2024-55627
Updated by Victor Julien 7 months ago
- Status changed from In Review to Resolved
- Git IDs updated (diff)