Skip to content

Commit e75b600

Browse files
authored
Make module base address parsing robust (#376)
Resolves a `SIGSEGV` crash that occurs when co-instrumenting with recent versions of Frida. The root cause was that the previous parsing logic would select the first memory mapping matching the library name. When Frida is active, it can temporarily create a transient, read-only mapping at a lower address than the real library. This would cause our parser to select the wrong base address. This commit refactors the `findModuleBase` function to be structurally aware. It now filters all mappings for the target library and specifically searches for the pattern of a read-only (`r--p`) segment immediately followed by an executable (`r-xp`) segment. This allows it to correctly identify the real library mapping and ignore transient artifacts from other instrumentation frameworks.
1 parent 1e5c3f0 commit e75b600

File tree

1 file changed

+80
-29
lines changed

1 file changed

+80
-29
lines changed

core/src/main/jni/src/elf_util.cpp

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727

2828
#include <cassert>
2929
#include <cstring>
30+
#include <string>
31+
#include <string_view>
32+
#include <vector>
3033

3134
#include "linux/xz.h"
3235
#include "logging.h"
@@ -373,47 +376,95 @@ constexpr inline bool contains(std::string_view a, std::string_view b) {
373376
return a.find(b) != std::string_view::npos;
374377
}
375378

379+
// A clean and simple struct to hold parsed map entry data.
380+
struct MapEntry {
381+
uintptr_t start_addr;
382+
char perms[5] = {0}; // Assured null-termination
383+
std::string pathname;
384+
};
385+
376386
bool ElfImg::findModuleBase() {
377-
off_t load_addr;
378-
bool found = false;
387+
// Open the maps file using standard C file I/O.
379388
FILE *maps = fopen("/proc/self/maps", "r");
389+
if (!maps) {
390+
LOGE("failed to open /proc/self/maps");
391+
return false;
392+
}
380393

381-
char *buff = nullptr;
382-
size_t len = 0;
383-
ssize_t nread;
384-
385-
while ((nread = getline(&buff, &len, maps)) != -1) {
386-
std::string_view line{buff, static_cast<size_t>(nread)};
387-
388-
if ((contains(line, "r-xp") || contains(line, "r--p")) && contains(line, elf)) {
389-
LOGD("found: {}", line);
390-
if (auto begin = line.find_last_of(' ');
391-
begin != std::string_view::npos && line[++begin] == '/') {
392-
found = true;
393-
elf = line.substr(begin);
394-
if (elf.back() == '\n') elf.pop_back();
395-
LOGD("update path: {}", elf);
396-
break;
397-
}
394+
char line_buffer[512]; // A reasonable fixed-size buffer for map lines.
395+
std::vector<MapEntry> filtered_list;
396+
397+
// Step 1: Filter all entries containing `elf` in its path.
398+
while (fgets(line_buffer, sizeof(line_buffer), maps)) {
399+
// Use an intermediate variable of a known, large type to avoid format warnings.
400+
// `unsigned long long` and `%llx` are standard and portable.
401+
unsigned long long temp_start;
402+
char path_buffer[256] = {0};
403+
char p[5] = {0};
404+
405+
// Use the portable `%llx` specifier.
406+
int items_parsed =
407+
sscanf(line_buffer, "%llx-%*x %4s %*x %*s %*d %255s", &temp_start, p, path_buffer);
408+
409+
// The filter condition: must parse the path, and it must contain the elf name.
410+
if (items_parsed == 3 && strstr(path_buffer, elf.c_str()) != nullptr) {
411+
MapEntry entry;
412+
// Safely assign the parsed value to the uintptr_t.
413+
entry.start_addr = static_cast<uintptr_t>(temp_start);
414+
strncpy(entry.perms, p, 4);
415+
entry.pathname = path_buffer;
416+
filtered_list.push_back(std::move(entry));
398417
}
399418
}
400-
if (!found) {
401-
if (buff) free(buff);
402-
LOGE("failed to read load address for {}", elf);
403-
fclose(maps);
419+
fclose(maps);
420+
421+
if (filtered_list.empty()) {
422+
LOGE("Could not find any mappings for {}", elf.data());
404423
return false;
405424
}
406425

407-
if (char *next = buff; load_addr = strtoul(buff, &next, 16), next == buff) {
408-
LOGE("failed to read load address for {}", elf);
426+
// Also part of Step 1: Print the filtered list for debugging.
427+
LOGD("Found {} filtered map entries for {}:", filtered_list.size(), elf.data());
428+
for (const auto &entry : filtered_list) {
429+
LOGD(" {:#x} {} {}", entry.start_addr, entry.perms, entry.pathname);
409430
}
410431

411-
if (buff) free(buff);
432+
const MapEntry *found_block = nullptr;
412433

413-
fclose(maps);
434+
// Step 2: In the filtered list, search for the first `r--p` whose next entry is `r-xp`.
435+
for (size_t i = 0; i < filtered_list.size() - 1; ++i) {
436+
if (strcmp(filtered_list[i].perms, "r--p") == 0 &&
437+
strcmp(filtered_list[i + 1].perms, "r-xp") == 0) {
438+
found_block = &filtered_list[i];
439+
LOGD("Found `r--p` -> `r-xp` pattern. Choosing base from `r--p` block at {:#x}",
440+
found_block->start_addr);
441+
break; // Pattern found, exit loop.
442+
}
443+
}
444+
445+
// Step 2 (Fallback): If the pattern was not found, find the first `r-xp` entry.
446+
if (!found_block) {
447+
LOGD("`r--p` -> `r-xp` pattern not found. Falling back to first `r-xp` entry.");
448+
for (const auto &entry : filtered_list) {
449+
if (strcmp(entry.perms, "r-xp") == 0) {
450+
found_block = &entry;
451+
LOGD("Found first `r-xp` block at {:#x}", found_block->start_addr);
452+
break; // Fallback found, exit loop.
453+
}
454+
}
455+
}
456+
457+
if (!found_block) {
458+
LOGE("Fatal: Could not determine a base address for {}", elf.data());
459+
return false;
460+
}
461+
462+
// Step 3: Use the starting address of the found block as the base address.
463+
base = reinterpret_cast<void *>(found_block->start_addr);
464+
elf = found_block->pathname; // Update elf path to the canonical one.
414465

415-
LOGD("get module base {}: {:#x}", elf, load_addr);
466+
LOGD("get module base {}: {:#x}", elf, found_block->start_addr);
467+
LOGD("update path: {}", elf);
416468

417-
base = reinterpret_cast<void *>(load_addr);
418469
return true;
419470
}

0 commit comments

Comments
 (0)