Skip to content

Commit c8d42d8

Browse files
Watson1978claude
andcommitted
Fix excessive memory allocation from crafted container headers
A container header carries an element count that the unpacker used to pre-allocate the Array or Hash. The count is capped at SHRT_MAX per container, but nested containers each pre-allocate up to that cap, so a small crafted message of deeply nested headers could still force a large allocation, and a streaming Unpacker holds it until the connection ends. An element needs at least one byte, so a container cannot hold more elements than there are bytes left to read. Cap the initial size by the buffered size as well; the container still grows as more data arrives. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 4aa8bb9 commit c8d42d8

1 file changed

Lines changed: 12 additions & 8 deletions

File tree

‎ext/msgpack/unpacker.c‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,13 @@ static inline VALUE rb_hash_new_capa_inline(long capa)
7474
#define rb_hash_new_capa rb_hash_new_capa_inline
7575
#endif
7676

77-
static inline int16_t initial_buffer_size(long size)
77+
static inline int16_t initial_buffer_size(msgpack_unpacker_t* uk, long count, size_t bytes_per_element)
7878
{
79-
return (size > INITIAL_BUFFER_CAPACITY_MAX) ? INITIAL_BUFFER_CAPACITY_MAX : size;
79+
size_t available = msgpack_buffer_all_readable_size(UNPACKER_BUFFER_(uk)) / bytes_per_element;
80+
if((size_t)count > available) {
81+
count = (long)available;
82+
}
83+
return (count > INITIAL_BUFFER_CAPACITY_MAX) ? INITIAL_BUFFER_CAPACITY_MAX : count;
8084
}
8185

8286
void msgpack_unpacker_static_init(void)
@@ -481,14 +485,14 @@ static int read_primitive(msgpack_unpacker_t* uk)
481485
if(count == 0) {
482486
return object_complete(uk, rb_ary_new());
483487
}
484-
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_ARRAY, count, rb_ary_new2(initial_buffer_size(count)));
488+
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_ARRAY, count, rb_ary_new2(initial_buffer_size(uk, count, 1)));
485489

486490
SWITCH_RANGE(b, 0x80, 0x8f) // FixMap
487491
int count = b & 0x0f;
488492
if(count == 0) {
489493
return object_complete(uk, rb_hash_new());
490494
}
491-
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_MAP_KEY, count*2, rb_hash_new_capa(initial_buffer_size(count)));
495+
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_MAP_KEY, count*2, rb_hash_new_capa(initial_buffer_size(uk, count, 2)));
492496

493497
SWITCH_RANGE(b, 0xc0, 0xdf) // Variable
494498
switch(b) {
@@ -711,7 +715,7 @@ static int read_primitive(msgpack_unpacker_t* uk)
711715
if(count == 0) {
712716
return object_complete(uk, rb_ary_new());
713717
}
714-
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_ARRAY, count, rb_ary_new2(initial_buffer_size(count)));
718+
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_ARRAY, count, rb_ary_new2(initial_buffer_size(uk, count, 1)));
715719
}
716720

717721
case 0xdd: // array 32
@@ -721,7 +725,7 @@ static int read_primitive(msgpack_unpacker_t* uk)
721725
if(count == 0) {
722726
return object_complete(uk, rb_ary_new());
723727
}
724-
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_ARRAY, count, rb_ary_new2(initial_buffer_size(count)));
728+
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_ARRAY, count, rb_ary_new2(initial_buffer_size(uk, count, 1)));
725729
}
726730

727731
case 0xde: // map 16
@@ -731,7 +735,7 @@ static int read_primitive(msgpack_unpacker_t* uk)
731735
if(count == 0) {
732736
return object_complete(uk, rb_hash_new());
733737
}
734-
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_MAP_KEY, count*2, rb_hash_new_capa(initial_buffer_size(count)));
738+
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_MAP_KEY, count*2, rb_hash_new_capa(initial_buffer_size(uk, count, 2)));
735739
}
736740

737741
case 0xdf: // map 32
@@ -741,7 +745,7 @@ static int read_primitive(msgpack_unpacker_t* uk)
741745
if(count == 0) {
742746
return object_complete(uk, rb_hash_new());
743747
}
744-
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_MAP_KEY, count*2, rb_hash_new_capa(initial_buffer_size(count)));
748+
return _msgpack_unpacker_stack_push(uk, STACK_TYPE_MAP_KEY, count*2, rb_hash_new_capa(initial_buffer_size(uk, count, 2)));
745749
}
746750

747751
default:

0 commit comments

Comments
 (0)