diff options
| author | nagachika <nagachika@ruby-lang.org> | 2024-07-15 08:46:16 +0900 |
|---|---|---|
| committer | nagachika <nagachika@ruby-lang.org> | 2024-07-15 08:50:38 +0900 |
| commit | 6b73406833dd22e489114fa77c1c80c4b7af2ed0 (patch) | |
| tree | 5d39ac809dcc4a60a66d6dae0af25f9ac0757c10 | |
| parent | 9f1e18a138e5196c04c5231c6a799dc637f2e9af (diff) | |
merge revision(s) 5e0c17145131e073814c7e5b15227d0b4e73cabe: [Backport #20169]
Make io_fwrite safe for compaction
[Bug #20169]
Embedded strings are not safe for system calls without the GVL because
compaction can cause pages to be locked causing the operation to fail
with EFAULT. This commit changes io_fwrite to use rb_str_tmp_frozen_no_embed_acquire,
which guarantees that the return string is not embedded.
| -rw-r--r-- | internal/string.h | 1 | ||||
| -rw-r--r-- | io.c | 2 | ||||
| -rw-r--r-- | string.c | 36 | ||||
| -rw-r--r-- | version.h | 2 |
4 files changed, 39 insertions, 2 deletions
diff --git a/internal/string.h b/internal/string.h index 12edbff2b1..dce6dcebf0 100644 --- a/internal/string.h +++ b/internal/string.h @@ -57,6 +57,7 @@ static inline VALUE rb_str_eql_internal(const VALUE str1, const VALUE str2); RUBY_SYMBOL_EXPORT_BEGIN /* string.c (export) */ VALUE rb_str_tmp_frozen_acquire(VALUE str); +VALUE rb_str_tmp_frozen_no_embed_acquire(VALUE str); void rb_str_tmp_frozen_release(VALUE str, VALUE tmp); VALUE rb_setup_fake_str(struct RString *fake_str, const char *name, long len, rb_encoding *enc); VALUE rb_str_upto_each(VALUE, VALUE, int, int (*each)(VALUE, VALUE), VALUE); @@ -1968,7 +1968,7 @@ io_fwrite(VALUE str, rb_io_t *fptr, int nosync) if (converted) OBJ_FREEZE(str); - tmp = rb_str_tmp_frozen_acquire(str); + tmp = rb_str_tmp_frozen_no_embed_acquire(str); RSTRING_GETMEM(tmp, ptr, len); n = io_binwrite(tmp, ptr, len, fptr, nosync); rb_str_tmp_frozen_release(str, tmp); @@ -1399,6 +1399,42 @@ rb_str_tmp_frozen_acquire(VALUE orig) return str_new_frozen_buffer(0, orig, FALSE); } +VALUE +rb_str_tmp_frozen_no_embed_acquire(VALUE orig) +{ + if (OBJ_FROZEN_RAW(orig) && !STR_EMBED_P(orig) && !rb_str_reembeddable_p(orig)) return orig; + if (STR_SHARED_P(orig) && !STR_EMBED_P(RSTRING(orig)->as.heap.aux.shared)) return rb_str_tmp_frozen_acquire(orig); + + VALUE str = str_alloc_heap(0); + OBJ_FREEZE(str); + /* Always set the STR_SHARED_ROOT to ensure it does not get re-embedded. */ + FL_SET(str, STR_SHARED_ROOT); + + size_t capa = str_capacity(orig, TERM_LEN(orig)); + + /* If the string is embedded then we want to create a copy that is heap + * allocated. If the string is shared then the shared root must be + * embedded, so we want to create a copy. If the string is a shared root + * then it must be embedded, so we want to create a copy. */ + if (STR_EMBED_P(orig) || FL_TEST_RAW(orig, STR_SHARED | STR_SHARED_ROOT)) { + RSTRING(str)->as.heap.ptr = rb_xmalloc_mul_add_mul(sizeof(char), capa, sizeof(char), TERM_LEN(orig)); + memcpy(RSTRING(str)->as.heap.ptr, RSTRING_PTR(orig), capa); + } + else { + /* orig must be heap allocated and not shared, so we can safely transfer + * the pointer to str. */ + RSTRING(str)->as.heap.ptr = RSTRING(orig)->as.heap.ptr; + RBASIC(str)->flags |= RBASIC(orig)->flags & STR_NOFREE; + RBASIC(orig)->flags &= ~STR_NOFREE; + STR_SET_SHARED(orig, str); + } + + RSTRING(str)->as.heap.len = RSTRING(orig)->as.heap.len; + RSTRING(str)->as.heap.aux.capa = capa; + + return str; +} + void rb_str_tmp_frozen_release(VALUE orig, VALUE tmp) { @@ -11,7 +11,7 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 4 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 187 +#define RUBY_PATCHLEVEL 188 #include "ruby/version.h" #include "ruby/internal/abi.h" |
