From 3a5cb6fcccea5e9a06ae075683641ef0c9e2daa3 Mon Sep 17 00:00:00 2001 From: Zack Deveau Date: Fri, 19 Jan 2024 15:01:46 -0500 Subject: [PATCH] Resize ary when `Array#sort!` block modifies embedded ary In cases where `rb_ary_sort_bang` is called with a block and tmp is an embedded array, we need to account for the block potentially impacting the capacity of ary. ex: ``` var_0 = (1..70).to_a var_0.sort! do |var_0_block_129, var_1_block_129| var_0.pop var_1_block_129 <=> var_0_block_129 end.shift(3) ``` The above example can put the array into a corrupted state resulting in a heap buffer overflow and possible segfault. This commit adds a conditional to determine when the capacity of ary has been modified by the provided block. If this is the case, ensure that the capacity of ary is adjusted to handle at minimum the len of tmp. --- array.c | 3 +++ test/ruby/test_array.rb | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/array.c b/array.c index bcf98fc012..56f3584e1d 100644 --- a/array.c +++ b/array.c @@ -3393,6 +3393,9 @@ rb_ary_sort_bang(VALUE ary) rb_ary_unshare(ary); FL_SET_EMBED(ary); } + if (ARY_EMBED_LEN(tmp) > ARY_CAPA(ary)) { + ary_resize_capa(ary, ARY_EMBED_LEN(tmp)); + } ary_memcpy(ary, 0, ARY_EMBED_LEN(tmp), ARY_EMBED_PTR(tmp)); ARY_SET_LEN(ary, ARY_EMBED_LEN(tmp)); } diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 6e84bdbd02..9560fca958 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -3556,6 +3556,15 @@ def test_big_array_literal_with_kwsplat assert_equal(10000, eval(lit).size) end + def test_array_safely_modified_by_sort_block + var_0 = (1..70).to_a + var_0.sort! do |var_0_block_129, var_1_block_129| + var_0.pop + var_1_block_129 <=> var_0_block_129 + end.shift(3) + assert_equal((1..67).to_a.reverse, var_0) + end + private def need_continuation unless respond_to?(:callcc, true) -- 2.39.2 (Apple Git-143)