Skip to content

Commit 5b5fd3c

Browse files
kpumukJens-G
authored andcommitted
Fix crashes caused by unchecked memory allocation in Ruby library
1 parent 0564434 commit 5b5fd3c

File tree

3 files changed

+59
-4
lines changed

3 files changed

+59
-4
lines changed

lib/rb/ext/struct.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ static ID sorted_field_ids_method_id;
3535
#define IS_CONTAINER(ttype) ((ttype) == TTYPE_MAP || (ttype) == TTYPE_LIST || (ttype) == TTYPE_SET)
3636
#define STRUCT_FIELDS(obj) rb_const_get(CLASS_OF(obj), fields_const_id)
3737

38+
static VALUE new_container_array(int size) {
39+
if (size < 0) {
40+
rb_exc_raise(
41+
get_protocol_exception(
42+
INT2FIX(PROTOERR_NEGATIVE_SIZE),
43+
rb_str_new2("Negative container size")
44+
)
45+
);
46+
}
47+
48+
return rb_ary_new2(size > 1024 ? 1024 : size);
49+
}
50+
3851
//-------------------------------------------
3952
// Writing section
4053
//-------------------------------------------
@@ -483,6 +496,10 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
483496
int value_ttype = FIX2INT(rb_ary_entry(map_header, 1));
484497
int num_entries = FIX2INT(rb_ary_entry(map_header, 2));
485498

499+
if (num_entries < 0) {
500+
rb_exc_raise(get_protocol_exception(INT2FIX(PROTOERR_NEGATIVE_SIZE), rb_str_new2("Negative container size")));
501+
}
502+
486503
// Check the declared key and value types against the expected ones and skip the map contents
487504
// if the types don't match.
488505
VALUE key_info = rb_hash_aref(field_info, key_sym);
@@ -523,7 +540,7 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
523540
if (!NIL_P(element_info)) {
524541
int specified_element_type = FIX2INT(rb_hash_aref(element_info, type_sym));
525542
if (specified_element_type == element_ttype) {
526-
result = rb_ary_new2(num_elements);
543+
result = new_container_array(num_elements);
527544

528545
for (i = 0; i < num_elements; ++i) {
529546
rb_ary_push(result, read_anything(protocol, element_ttype, rb_hash_aref(field_info, element_sym)));
@@ -550,7 +567,7 @@ static VALUE read_anything(VALUE protocol, int ttype, VALUE field_info) {
550567
if (!NIL_P(element_info)) {
551568
int specified_element_type = FIX2INT(rb_hash_aref(element_info, type_sym));
552569
if (specified_element_type == element_ttype) {
553-
items = rb_ary_new2(num_elements);
570+
items = new_container_array(num_elements);
554571

555572
for (i = 0; i < num_elements; ++i) {
556573
rb_ary_push(items, read_anything(protocol, element_ttype, rb_hash_aref(field_info, element_sym)));

lib/rb/lib/thrift/struct_union.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@
2020

2121
module Thrift
2222
module Struct_Union
23+
def validate_container_size(size)
24+
return size unless size < 0
25+
26+
raise ProtocolException.new(ProtocolException::NEGATIVE_SIZE, 'Negative size')
27+
end
28+
2329
def name_to_id(name)
2430
names_to_ids = self.class.instance_variable_get(:@names_to_ids)
2531
unless names_to_ids
@@ -55,6 +61,7 @@ def read_field(iprot, field = {})
5561
value.read(iprot)
5662
when Types::MAP
5763
key_type, val_type, size = iprot.read_map_begin
64+
validate_container_size(size)
5865
# Skip the map contents if the declared key or value types don't match the expected ones.
5966
if (size != 0 && (key_type != field[:key][:type] || val_type != field[:value][:type]))
6067
size.times do
@@ -73,20 +80,23 @@ def read_field(iprot, field = {})
7380
iprot.read_map_end
7481
when Types::LIST
7582
e_type, size = iprot.read_list_begin
83+
validate_container_size(size)
7684
# Skip the list contents if the declared element type doesn't match the expected one.
7785
if (e_type != field[:element][:type])
7886
size.times do
7987
iprot.skip(e_type)
8088
end
8189
value = nil
8290
else
83-
value = Array.new(size) do |n|
84-
read_field(iprot, field_info(field[:element]))
91+
value = []
92+
size.times do
93+
value << read_field(iprot, field_info(field[:element]))
8594
end
8695
end
8796
iprot.read_list_end
8897
when Types::SET
8998
e_type, size = iprot.read_set_begin
99+
validate_container_size(size)
90100
# Skip the set contents if the declared element type doesn't match the expected one.
91101
if (e_type != field[:element][:type])
92102
size.times do

lib/rb/spec/struct_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,34 @@ def validate_default_arguments(object)
142142
expect(struct.shorts).to eq(Set.new([3, 2]))
143143
end
144144

145+
it "rejects negative container sizes while reading" do
146+
struct = SpecNamespace::Foo.new
147+
prot = Thrift::BaseProtocol.new(double("transport"))
148+
149+
expect(prot).to receive(:read_list_begin).and_return([Thrift::Types::I32, -1])
150+
151+
expect {
152+
struct.send(:read_field, prot, SpecNamespace::Foo::FIELDS[4])
153+
}.to raise_error(Thrift::ProtocolException, "Negative size") { |error|
154+
expect(error.type).to eq(Thrift::ProtocolException::NEGATIVE_SIZE)
155+
}
156+
end
157+
158+
it "does not preallocate arrays from declared list sizes" do
159+
struct = SpecNamespace::Foo.new
160+
prot = Thrift::BaseProtocol.new(double("transport"))
161+
declared_size = 1 << 30
162+
sentinel = RuntimeError.new("stop after first element")
163+
164+
expect(prot).to receive(:read_list_begin).and_return([Thrift::Types::I32, declared_size])
165+
expect(prot).to receive(:read_i32).and_raise(sentinel)
166+
expect(Array).not_to receive(:new).with(declared_size)
167+
168+
expect {
169+
struct.send(:read_field, prot, SpecNamespace::Foo::FIELDS[4])
170+
}.to raise_error(sentinel)
171+
end
172+
145173
it "should serialize false boolean fields correctly" do
146174
b = SpecNamespace::BoolStruct.new(:yesno => false)
147175
prot = Thrift::BinaryProtocol.new(Thrift::MemoryBufferTransport.new)

0 commit comments

Comments
 (0)