summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZack Deveau <[email protected]>2024-01-19 15:01:46 -0500
committerNobuyoshi Nakada <[email protected]>2024-04-14 08:41:47 +0900
commitc479492a6701dcef3d3a96de8946ecf7beb079d4 (patch)
tree504f80f1b5abf0fc1c70d4d321101c0301fd2a2b
parent43a6690d2142a2f07a04c0bb75a0834a23c88fec (diff)
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: ``` ERROR: AddressSanitizer: heap-buffer-overflow on address [...] WRITE of size 560 at 0x60b0000034f0 thread T0 [...] ``` 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.
-rw-r--r--array.c3
-rw-r--r--test/ruby/test_array.rb9
2 files changed, 12 insertions, 0 deletions
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 @@ class TestArray < Test::Unit::TestCase
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)