Strings allocated in mapped memory are broken
We found that when creating an object space dump via ObjectSpace.dump_all
and a String
is encountered that had been created through mm_str
prior to unmapping or remapping the metrics file, MRI will crash: https://bugs.ruby-lang.org/issues/19156
We narrowed this down to any interaction that inspects string data, such as valid_encoding?
As per the referenced issue, we found several small, executable test cases to reproduce this crash. The first test case is what is most likely to happen in a production app, whereas the second shows more immediately what causes the crash:
- Creating an
MmapedValue
with a label string that outsizes the default file size set viainitial_mmap_file_size
(or alternatively creating enough samples to outgrow the initial size), then inspecting all Strings on the Ruby heap. This will result in the initial memory mapping being freed and a new memory of twice the size being created, thus invalidating all pointers in String objects created in the previous mapping:
Prometheus::Client::MmapedValue.new(:counter, :counter, 'ordered_counter', { label_1: 'x' * (1024 * 4) })
# This will crash
ObjectSpace.each_object(String, &:valid_encoding?)
- It is more directly visible when directly unmapping
FastMmapedFile
after it allocated string data to hold the sample entries:
require 'prometheus'
require 'prometheus/client'
File.write("/tmp/mmap.txt", "a" * 100)
file = FastMmapedFile.new("/tmp/mmap.txt")
str = file.slice(0, 100)
# This works
p str
file.munmap
# This will crash
p str
The root cause for this appears to be that mm_str
asks MRI to allocate a zero-length string and then overwrites the underlying pointer to the string data as well as its length:
ret = rb_obj_alloc(rb_cString);
RSTRING(ret)->as.heap.ptr = i_mm->t->addr;
RSTRING(ret)->as.heap.aux.capa = i_mm->t->len;
RSTRING(ret)->as.heap.len = i_mm->t->real;
This will most certainly crash when releasing the mapped memory before the string gets GC'ed, because it will point to an invalid memory region and MRI will not know.