summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornagachika <nagachika@ruby-lang.org>2024-07-15 08:46:16 +0900
committernagachika <nagachika@ruby-lang.org>2024-07-15 08:50:38 +0900
commit6b73406833dd22e489114fa77c1c80c4b7af2ed0 (patch)
tree5d39ac809dcc4a60a66d6dae0af25f9ac0757c10
parent9f1e18a138e5196c04c5231c6a799dc637f2e9af (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.h1
-rw-r--r--io.c2
-rw-r--r--string.c36
-rw-r--r--version.h2
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);
diff --git a/io.c b/io.c
index 748192b881..052639cb6d 100644
--- a/io.c
+++ b/io.c
@@ -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);
diff --git a/string.c b/string.c
index ed51fa4af7..6a9eebd687 100644
--- a/string.c
+++ b/string.c
@@ -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)
{
diff --git a/version.h b/version.h
index 71e4b15dd7..e3f22dac75 100644
--- a/version.h
+++ b/version.h
@@ -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"