Skip to content

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:

  1. Creating an MmapedValue with a label string that outsizes the default file size set via initial_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?)
  1. 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;

https://gitlab.com/gitlab-org/prometheus-client-mmap/-/blob/6e6d470356a2a34b9ce4ceb133f85f216ea753bd/ext/fast_mmaped_file/mmap.c#L46

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.

Edited by Matthias Käppler