#include "sentry_modulefinder_linux.h"
#include "sentry_core.h"
#include "sentry_path.h"
#include "sentry_string.h"
#include "sentry_sync.h"
#include "sentry_value.h"
#include <arpa/inet.h>
#include <elf.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define ENSURE(Ptr) \
if (!Ptr) \
goto fail
static bool g_initialized = false;
static sentry_mutex_t g_mutex = SENTRY__MUTEX_INIT;
static sentry_value_t g_modules = { 0 };
static sentry_slice_t LINUX_GATE = { "linux-gate.so", 13 };
void *
sentry__module_get_addr(
const sentry_module_t *module, uint64_t start_offset, uint64_t size)
{
for (size_t i = 0; i < module->num_mappings; i++) {
const sentry_mapped_region_t *mapping = &module->mappings[i];
uint64_t mapping_offset = mapping->offset - module->offset_in_inode;
if (start_offset >= mapping_offset
&& start_offset < mapping_offset + mapping->size) {
uint64_t addr = start_offset - mapping_offset + mapping->addr;
if (addr + size <= mapping->addr + mapping->size) {
return (void *)(uintptr_t)(addr);
}
}
}
return NULL;
}
static void
sentry__module_mapping_push(
sentry_module_t *module, const sentry_parsed_module_t *parsed)
{
if (module->num_mappings && module->mappings_inode != parsed->inode) {
return;
}
size_t size = parsed->end - parsed->start;
if (module->num_mappings) {
sentry_mapped_region_t *last_mapping
= &module->mappings[module->num_mappings - 1];
if (last_mapping->addr + last_mapping->size == parsed->start
&& last_mapping->offset + last_mapping->size == parsed->offset) {
last_mapping->size += size;
return;
}
}
if (module->num_mappings < SENTRY_MAX_MAPPINGS) {
sentry_mapped_region_t *mapping
= &module->mappings[module->num_mappings];
module->num_mappings += 1;
mapping->offset = parsed->offset;
mapping->size = size;
mapping->addr = parsed->start;
if (module->num_mappings == 1) {
module->mappings_inode = parsed->inode;
module->offset_in_inode = parsed->offset;
}
}
}
static bool
is_duplicated_mapping(
const sentry_module_t *module, const sentry_parsed_module_t *parsed)
{
if (!module->num_mappings) {
return false;
}
const sentry_mapped_region_t *mapping = &module->mappings[0];
return (mapping->offset == parsed->offset
&& module->mappings_inode == parsed->inode);
}
int
sentry__procmaps_parse_module_line(
const char *line, sentry_parsed_module_t *module)
{
uint8_t major_device;
uint8_t minor_device;
int consumed = 0;
if (sscanf(line,
"%" SCNx64 "-%" SCNx64 " %4c %" SCNx64 " %hhx:%hhx %" SCNu64 " %n",
&module->start, &module->end, &module->permissions[0],
&module->offset, &major_device, &minor_device, &module->inode,
&consumed)
< 7) {
return 0;
}
line += consumed;
module->file.ptr = line;
module->file.len = 0;
char *nl = strchr(line, '\n');
if (consumed && (line - 1)[0] == '\n') {
module->file.ptr = NULL;
} else if (nl) {
module->file.len = nl - line;
consumed += nl - line + 1;
} else {
module->file.len = strlen(line);
consumed += module->file.len;
}
return consumed;
}
void
align(size_t alignment, void **offset)
{
size_t diff = (size_t)*offset % alignment;
if (diff != 0) {
*(size_t *)offset += alignment - diff;
}
}
static const uint8_t *
get_code_id_from_notes(
size_t alignment, void *start, void *end, size_t *size_out)
{
*size_out = 0;
if (alignment < 4) {
alignment = 4;
} else if (alignment != 4 && alignment != 8) {
return NULL;
}
const uint8_t *offset = start;
while (offset < (const uint8_t *)end) {
const Elf64_Nhdr *note = (const Elf64_Nhdr *)offset;
offset += sizeof(Elf64_Nhdr);
offset += note->n_namesz;
align(alignment, (void **)&offset);
if (note->n_type == NT_GNU_BUILD_ID) {
*size_out = note->n_descsz;
return offset;
}
offset += note->n_descsz;
align(alignment, (void **)&offset);
}
return NULL;
}
static const uint8_t *
get_code_id_from_elf(const sentry_module_t *module, size_t *size_out)
{
*size_out = 0;
const unsigned char *e_ident
= sentry__module_get_addr(module, 0, EI_NIDENT);
ENSURE(e_ident);
if (e_ident[EI_CLASS] == ELFCLASS64) {
const Elf64_Ehdr *elf
= sentry__module_get_addr(module, 0, sizeof(Elf64_Ehdr));
ENSURE(elf);
for (int i = 0; i < elf->e_phnum; i++) {
const Elf64_Phdr *header = sentry__module_get_addr(
module, elf->e_phoff + elf->e_phentsize * i, elf->e_phentsize);
ENSURE(header);
if (header->p_type != PT_NOTE) {
continue;
}
void *segment_addr = sentry__module_get_addr(
module, header->p_offset, header->p_filesz);
ENSURE(segment_addr);
const uint8_t *code_id = get_code_id_from_notes(header->p_align,
segment_addr,
(void *)((uintptr_t)segment_addr + header->p_filesz), size_out);
if (code_id) {
return code_id;
}
}
} else {
const Elf32_Ehdr *elf
= sentry__module_get_addr(module, 0, sizeof(Elf32_Ehdr));
ENSURE(elf);
for (int i = 0; i < elf->e_phnum; i++) {
const Elf32_Phdr *header = sentry__module_get_addr(
module, elf->e_phoff + elf->e_phentsize * i, elf->e_phentsize);
ENSURE(header);
if (header->p_type != PT_NOTE) {
continue;
}
void *segment_addr = sentry__module_get_addr(
module, header->p_offset, header->p_filesz);
ENSURE(segment_addr);
const uint8_t *code_id = get_code_id_from_notes(header->p_align,
segment_addr,
(void *)((uintptr_t)segment_addr + header->p_filesz), size_out);
if (code_id) {
return code_id;
}
}
}
fail:
return NULL;
}
static sentry_uuid_t
get_code_id_from_text_fallback(const sentry_module_t *module)
{
const uint8_t *text = NULL;
size_t text_size = 0;
const unsigned char *e_ident
= sentry__module_get_addr(module, 0, EI_NIDENT);
ENSURE(e_ident);
if (e_ident[EI_CLASS] == ELFCLASS64) {
const Elf64_Ehdr *elf
= sentry__module_get_addr(module, 0, sizeof(Elf64_Ehdr));
ENSURE(elf);
const Elf64_Shdr *strheader = sentry__module_get_addr(module,
elf->e_shoff + elf->e_shentsize * elf->e_shstrndx,
elf->e_shentsize);
ENSURE(strheader);
const char *names = sentry__module_get_addr(
module, strheader->sh_offset, strheader->sh_entsize);
ENSURE(names);
for (int i = 0; i < elf->e_shnum; i++) {
const Elf64_Shdr *header = sentry__module_get_addr(
module, elf->e_shoff + elf->e_shentsize * i, elf->e_shentsize);
ENSURE(header);
const char *name = names + header->sh_name;
if (header->sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) {
text = sentry__module_get_addr(
module, header->sh_offset, header->sh_size);
ENSURE(text);
text_size = header->sh_size;
break;
}
}
} else {
const Elf32_Ehdr *elf
= sentry__module_get_addr(module, 0, sizeof(Elf64_Ehdr));
ENSURE(elf);
const Elf32_Shdr *strheader = sentry__module_get_addr(module,
elf->e_shoff + elf->e_shentsize * elf->e_shstrndx,
elf->e_shentsize);
ENSURE(strheader);
const char *names = sentry__module_get_addr(
module, strheader->sh_offset, strheader->sh_entsize);
ENSURE(names);
for (int i = 0; i < elf->e_shnum; i++) {
const Elf32_Shdr *header = sentry__module_get_addr(
module, elf->e_shoff + elf->e_shentsize * i, elf->e_shentsize);
ENSURE(header);
const char *name = names + header->sh_name;
if (header->sh_type == SHT_PROGBITS && strcmp(name, ".text") == 0) {
text = sentry__module_get_addr(
module, header->sh_offset, header->sh_size);
ENSURE(text);
text_size = header->sh_size;
break;
}
}
}
sentry_uuid_t uuid = sentry_uuid_nil();
size_t max = MIN(text_size, 4096);
for (size_t i = 0; i < max; i++) {
uuid.bytes[i % 16] ^= text[i];
}
return uuid;
fail:
return sentry_uuid_nil();
}
bool
sentry__procmaps_read_ids_from_elf(
sentry_value_t value, const sentry_module_t *module)
{
size_t code_id_size;
const uint8_t *code_id = get_code_id_from_elf(module, &code_id_size);
sentry_uuid_t uuid = sentry_uuid_nil();
if (code_id) {
sentry_value_set_by_key(value, "code_id",
sentry__value_new_hexstring(code_id, code_id_size));
memcpy(uuid.bytes, code_id, MIN(code_id_size, 16));
} else {
uuid = get_code_id_from_text_fallback(module);
}
char *uuid_bytes = uuid.bytes;
uint32_t *a = (uint32_t *)uuid_bytes;
*a = htonl(*a);
uint16_t *b = (uint16_t *)(uuid_bytes + 4);
*b = htons(*b);
uint16_t *c = (uint16_t *)(uuid_bytes + 6);
*c = htons(*c);
sentry_value_set_by_key(value, "debug_id", sentry__value_new_uuid(&uuid));
return true;
}
sentry_value_t
sentry__procmaps_module_to_value(const sentry_module_t *module)
{
sentry_value_t mod_val = sentry_value_new_object();
sentry_value_set_by_key(mod_val, "type", sentry_value_new_string("elf"));
sentry_value_set_by_key(mod_val, "code_file",
sentry__value_new_string_owned(sentry__slice_to_owned(module->file)));
const sentry_mapped_region_t *first_mapping = &module->mappings[0];
const sentry_mapped_region_t *last_mapping
= &module->mappings[module->num_mappings - 1];
sentry_value_set_by_key(
mod_val, "image_addr", sentry__value_new_addr(first_mapping->addr));
sentry_value_set_by_key(mod_val, "image_size",
sentry_value_new_int32(
last_mapping->addr + last_mapping->size - first_mapping->addr));
sentry__procmaps_read_ids_from_elf(mod_val, module);
return mod_val;
}
static void
try_append_module(sentry_value_t modules, const sentry_module_t *module)
{
if (!module->file.ptr || !module->num_mappings) {
return;
}
sentry_value_t mod_val = sentry__procmaps_module_to_value(module);
if (!sentry_value_is_null(mod_val)) {
sentry_value_append(modules, mod_val);
}
}
#if defined(__i386) || defined(__ARM_EABI__) \
|| (defined(__mips__) && _MIPS_SIM == _ABIO32)
typedef Elf32_auxv_t elf_aux_entry;
#elif defined(__x86_64) || defined(__aarch64__) || defined(__powerpc64__) \
|| (defined(__mips__) && _MIPS_SIM != _ABIO32)
typedef Elf64_auxv_t elf_aux_entry;
#endif
static uint64_t
get_linux_vdso(void)
{
int fd = open("/proc/self/auxv", O_RDONLY);
if (fd < 0) {
return false;
}
elf_aux_entry one_aux_entry;
while (
read(fd, &one_aux_entry, sizeof(elf_aux_entry)) == sizeof(elf_aux_entry)
&& one_aux_entry.a_type != AT_NULL) {
if (one_aux_entry.a_type == AT_SYSINFO_EHDR) {
close(fd);
return (uint64_t)one_aux_entry.a_un.a_val;
}
}
close(fd);
return 0;
}
static bool
is_valid_elf_header(void *start)
{
const unsigned char *e_ident = start;
return e_ident[EI_MAG0] == ELFMAG0 && e_ident[EI_MAG1] == ELFMAG1
&& e_ident[EI_MAG2] == ELFMAG2 && e_ident[EI_MAG3] == ELFMAG3;
}
static void
load_modules(sentry_value_t modules)
{
int fd = open("/proc/self/maps", O_RDONLY);
if (fd < 0) {
return;
}
char buf[4096];
sentry_stringbuilder_t sb;
sentry__stringbuilder_init(&sb);
while (true) {
ssize_t n = read(fd, buf, 4096);
if (n < 0 && (errno == EAGAIN || errno == EINTR)) {
continue;
} else if (n <= 0) {
break;
}
if (sentry__stringbuilder_append_buf(&sb, buf, n)) {
sentry__stringbuilder_cleanup(&sb);
close(fd);
return;
}
}
close(fd);
char *contents = sentry__stringbuilder_into_string(&sb);
if (!contents) {
return;
}
char *current_line = contents;
uint64_t linux_vdso = get_linux_vdso();
sentry_module_t last_module = { 0 };
while (true) {
sentry_parsed_module_t module = { 0 };
int read = sentry__procmaps_parse_module_line(current_line, &module);
current_line += read;
if (!read) {
break;
}
if (!module.start || module.permissions[0] != 'r') {
continue;
}
if (!module.file.len
|| (module.file.len >= 5
&& memcmp("/dev/", module.file.ptr, 5) == 0)) {
continue;
}
if (module.start && module.start == linux_vdso) {
module.file = LINUX_GATE;
} else if (module.file.ptr[0] != '/') {
continue;
}
if (is_valid_elf_header((void *)(size_t)module.start)) {
if (!is_duplicated_mapping(&last_module, &module)) {
try_append_module(modules, &last_module);
memset(&last_module, 0, sizeof(sentry_module_t));
last_module.file = module.file;
}
}
sentry__module_mapping_push(&last_module, &module);
}
try_append_module(modules, &last_module);
sentry_free(contents);
}
sentry_value_t
sentry_get_modules_list(void)
{
sentry__mutex_lock(&g_mutex);
if (!g_initialized) {
g_modules = sentry_value_new_list();
SENTRY_TRACE("trying to read modules from /proc/self/maps");
load_modules(g_modules);
SENTRY_TRACEF("read %zu modules from /proc/self/maps",
sentry_value_get_length(g_modules));
sentry_value_freeze(g_modules);
g_initialized = true;
}
sentry_value_t modules = g_modules;
sentry_value_incref(modules);
sentry__mutex_unlock(&g_mutex);
return modules;
}
void
sentry_clear_modulecache(void)
{
sentry__mutex_lock(&g_mutex);
sentry_value_decref(g_modules);
g_modules = sentry_value_new_null();
g_initialized = false;
sentry__mutex_unlock(&g_mutex);
}