From b44c02ad5a1c5c8c1c62b83eec96cf3a8a2107bc Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 29 May 2024 15:44:55 -0700 Subject: [PATCH] merge revision(s) c479492a6701dcef3d3a96de8946ecf7beb079d4: [Backport #20427] 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. --- array.c | 3 +++ test/ruby/test_array.rb | 9 +++++++++ version.h | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/array.c b/array.c index d029d44b39..e1b8553568 100644 --- a/array.c +++ b/array.c @@ -3387,6 +3387,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 e40651eb8e..9f89a54358 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) diff --git a/version.h b/version.h index 856dcb79df..6cc6423ca6 100644 --- a/version.h +++ b/version.h @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 1 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 69 +#define RUBY_PATCHLEVEL 70 #include "ruby/version.h" #include "ruby/internal/abi.h"