diff --git a/ext/bson/write.c b/ext/bson/write.c index c426129a1..2be127bc2 100644 --- a/ext/bson/write.c +++ b/ext/bson/write.c @@ -649,7 +649,6 @@ VALUE rb_bson_byte_buffer_put_array(VALUE self, VALUE array){ size_t new_position = 0; int32_t new_length = 0; size_t position = 0; - VALUE *array_element = NULL; TypedData_Get_Struct(self, byte_buffer_t, &rb_byte_buffer_data_type, b); Check_Type(array, T_ARRAY); @@ -657,12 +656,19 @@ VALUE rb_bson_byte_buffer_put_array(VALUE self, VALUE array){ /* insert length placeholder */ pvt_put_int32(b, 0); - array_element = RARRAY_PTR(array); + long original_len = RARRAY_LEN(array); + if (original_len > INT32_MAX) { + rb_raise(rb_eRangeError, "array too large for BSON serialization"); + } - for(int32_t index=0; index < RARRAY_LEN(array); index++, array_element++){ - pvt_put_type_byte(b, *array_element); + for (int32_t index = 0; index < (int32_t)original_len; index++) { + if (RARRAY_LEN(array) != original_len) { + rb_raise(rb_eRuntimeError, "array modified during BSON serialization"); + } + volatile VALUE element = rb_ary_entry(array, index); + pvt_put_type_byte(b, element); pvt_put_array_index(b, index); - pvt_put_field(b, self, *array_element); + pvt_put_field(b, self, element); } pvt_put_byte(b, 0); diff --git a/spec/bson/array_spec.rb b/spec/bson/array_spec.rb index 3aca70b5e..42a28269a 100644 --- a/spec/bson/array_spec.rb +++ b/spec/bson/array_spec.rb @@ -75,6 +75,30 @@ end end + context 'when an array element mutates the array size during serialization', + if: BSON::ByteBuffer.new.respond_to?(:put_array) do + let(:array) { Array.new(10, 1) } + + before do + evil = Class.new do + define_method(:initialize) { |a| @array = a; @mutated = false } + define_method(:bson_type) do + unless @mutated + @mutated = true + @array.replace(Array.new(200_000, 0)) + end + BSON::Int32::BSON_TYPE + end + define_method(:to_bson) { |buffer| 123.to_bson(buffer) } + end + array[0] = evil.new(array) + end + + it 'raises a RuntimeError' do + expect { array.to_bson }.to raise_error(RuntimeError, /array modified during BSON serialization/) + end + end + context 'when array contains value of an unserializable class' do class ArraySpecUnserializableClass end