diff options
author | Alan Wu <XrXr@users.noreply.github.com> | 2019-05-08 09:44:41 -0400 |
---|---|---|
committer | Nobuyoshi Nakada <nobu@ruby-lang.org> | 2019-05-09 10:04:19 +0900 |
commit | c06ddfee878524168e4af07443217ed2f8d0954b (patch) | |
tree | 46ebccbc5e21a929eb5fd644543975cce4cf115e /string.c | |
parent | d802698d3e27a3cf091a56556df3562cc6ff996c (diff) | |
download | ruby-c06ddfee878524168e4af07443217ed2f8d0954b.tar.gz |
str_duplicate: Don't share with a frozen shared string
This is a follow up for 3f9562015e651735bfc2fdd14e8f6963b673e22a.
Before this commit, it was possible to create a shared string which
shares with another shared string by passing a frozen shared string
to `str_duplicate`.
Such string looks like:
```
-------- -----------------
| root | ------ owns -----> | root's buffer |
-------- -----------------
^ ^ ^
----------- | |
| shared1 | ------ references ----- |
----------- |
^ |
----------- |
| shared2 | ------ references ---------
-----------
```
This is bad news because `rb_fstring(shared2)` can make `shared1`
independent, which severs the reference from `shared1` to `root`:
```c
/* from fstr_update_callback() */
str = str_new_frozen(rb_cString, shared2); /* can return shared1 */
if (STR_SHARED_P(str)) { /* shared1 is also a shared string */
str_make_independent(str); /* no frozen check */
}
```
If `shared1` was the only reference to `root`, then `root` can be
reclaimed by the GC, leaving `shared2` in a corrupted state:
```
----------- --------------------
| shared1 | -------- owns --------> | shared1's buffer |
----------- --------------------
^
|
----------- -------------------------
| shared2 | ------ references ----> | root's buffer (freed) |
----------- -------------------------
```
Here is a reproduction script for the situation this commit fixes.
```ruby
a = ('a' * 24).strip.freeze.strip
-a
p a
4.times { GC.start }
p a
```
- string.c (str_duplicate): always share with the root string when
the original is a shared string.
- test_rb_str_dup.rb: specifically test `rb_str_dup` to make
sure it does not try to share with a shared string.
[Bug #15792]
Closes: https://github.com/ruby/ruby/pull/2159
Diffstat (limited to 'string.c')
-rw-r--r-- | string.c | 16 |
1 files changed, 7 insertions, 9 deletions
@@ -1504,15 +1504,13 @@ str_duplicate(VALUE klass, VALUE str) MEMCPY(RSTRING(dup)->as.ary, RSTRING(str)->as.ary, char, embed_size); if (flags & STR_NOEMBED) { - if (UNLIKELY(!(flags & FL_FREEZE))) { - if (FL_TEST_RAW(str, STR_SHARED)) { - str = RSTRING(str)->as.heap.aux.shared; - } - else { - str = str_new_frozen(klass, str); - FL_SET_RAW(str, flags & FL_TAINT); - flags = FL_TEST_RAW(str, flag_mask); - } + if (FL_TEST_RAW(str, STR_SHARED)) { + str = RSTRING(str)->as.heap.aux.shared; + } + else if (UNLIKELY(!(flags & FL_FREEZE))) { + str = str_new_frozen(klass, str); + FL_SET_RAW(str, flags & FL_TAINT); + flags = FL_TEST_RAW(str, flag_mask); } if (flags & STR_NOEMBED) { RB_OBJ_WRITE(dup, &RSTRING(dup)->as.heap.aux.shared, str); |