Skip to content

Fix SIGSEGV when enumerating exports from APK-embedded libraries (ONLINE mode)#1099

Open
monkeywave wants to merge 2 commits intofrida:mainfrom
monkeywave:fix-apk-online-bangpath-clean
Open

Fix SIGSEGV when enumerating exports from APK-embedded libraries (ONLINE mode)#1099
monkeywave wants to merge 2 commits intofrida:mainfrom
monkeywave:fix-apk-online-bangpath-clean

Conversation

@monkeywave
Copy link
Copy Markdown
Contributor

Hey @oleavr,

first of all: thank you for your amazing work on Frida 🙏 — it’s hugely appreciated :-)

I tried the recent changes from #1094, but on my side the crash still happens (now consistently in a different code-path). I also left details here, in case it helps for cross-reference:
#1094 (comment)

Summary

On Android 14 + Android 16 AVD (Google APIs, arm64) with com.android.chrome, this still crashes:

var m = Process.getModuleByName("libmonochrome_64.so");
m.enumerateExports();

It only reproduces when the module is APK-embedded (path contains !), e.g.:

.../TrichromeLibrary.apk!/lib/arm64-v8a/libmonochrome_64.so

The tombstone’s top frame points at __fseeko64(), which suggests we’re crashing in the minizip APK extraction path, i.e., not the in-memory ELF parsing path.

Proposed fix in this PR

If we’re in ONLINE mode and the path contains !, we can skip gum_maybe_extract_from_apk() and use the already-mapped bytes at base_address directly:

else if (self->source_mode == GUM_ELF_SOURCE_MODE_ONLINE &&
         strstr (self->source_path, "!") != NULL)
{
  self->file_bytes = g_bytes_new_static (
      GSIZE_TO_POINTER (self->base_address),
      G_MAXSIZE - self->base_address);
}

Rationale: for ONLINE mode, the in-memory module image is already available and sufficient for export enumeration; avoiding APK I/O also avoids the minizip FILE* crash entirely.

Result

With the patch applied, enumerateExports() works reliably and returns a normal export list (example):

[Android Emulator 5554::Chrome ]-> m
{
  "base": "0x71d6807000",
  "name": "libmonochrome_64.so",
  "path": ".../TrichromeLibrary.apk!/lib/arm64-v8a/libmonochrome_64.so",
  "size": 148017152,
    "version": null
}

[Android Emulator 5554::Chrome ]-> m.enumerateExports();
[
    {
        "address": "0x71d90d4888",
        "name": "Java_J_N__1V_1IJJO",
        "type": "function"
    },
    {
        "address": "0x71d90de080",
        "name": "Java_J_N__1V_1IOOOOOOO",
        "type": "function"
    },
    {
        "address": "0x71d90bc430",
        "name": "Java_J_N__1I_1J",
        "type": "function"
    },
  ...
]
Crash excerpt (before patch)
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0000000000000060
Cause: null pointer dereference
#00 pc ... /apex/com.android.runtime/lib64/bionic/libc.so (__fseeko64(...)+36)
#01 pc ... /memfd:frida-agent-64.so (deleted)
...

Thanks again :-)

All the best

Daniel

@monkeywave
Copy link
Copy Markdown
Contributor Author

Hey @oleavr,

quick follow-up on this :-)

I initially added a workaround for enumerateExports() on APK-embedded libraries (...apk!/lib/...) by avoiding the minizip extraction path and using the in-memory image in ONLINE mode. See my above comment.

Update: I found that enumerateSymbols() can still hit the same underlying problem and crash with SIGSEGV (__fseeko64()), but through a separate fallback path. I’ve pushed an additional commit to this PR to address that as well.

To reproduce the error we follow the same pattern as before but now for enumerateSymbols():

var m = Process.getModuleByName("libmonochrome_64.so"); // could be different on other Android versions
m.enumerateSymbols();

Only reproduces when the module path is APK-embedded (contains !), e.g.:

.../base.apk!/lib/arm64-v8a/libmonochrome_64.so

Root cause (what seems to happen)

When .symtab is missing/empty, enumerateSymbols() may attempt an OFFLINE fallback by loading source_path as a file. For APK paths (.apk!/...), this triggers gum_maybe_extract_from_apk() (minizip) and crashes in __fseeko64().

Changes included in this PR (all in gumelfmodule.c)

  1. Avoid OFFLINE fallback for APK paths in ONLINE mode
    If source_path contains .apk!, skip gum_elf_module_new_from_file() so we never reach minizip for these mapped APK libraries.

  2. Fallback to .dynsym when .symtab yields 0 symbols
    After avoiding the crash, enumerateSymbols() would otherwise return [] for stripped Android release binaries. Falling back to dynamic symbols returns the maximum symbol info typically available in memory.

  3. (Optional) Skip section-header loading when we’re clearly memory-backed
    For memory-backed ONLINE modules, section headers may not be present at file offsets (loader maps PT_LOAD segments), so treating section headers as unavailable makes behavior deterministic.

Note on “symbol completeness” (why .dynsym fallback is reasonable)

On Android, release .so files inside APKs are commonly stripped; .symtab is often not shipped with the binary, while .dynsym remains for dynamic linking. In practice this means enumerateSymbols() can only ever recover dynamic symbols unless apps ship/upload native debug symbols separately. Android’s docs describe this flow here:
https://developer.android.com/build/include-native-symbols

Result

With these updates, enumerateSymbols() works on APK-embedded libraries without crashing, and returns a non-empty symbol list on Chrome in the emulator.

Thanks again for taking a look :-)

All the best

Daniel

@oleavr
Copy link
Copy Markdown
Member

oleavr commented Mar 7, 2026

Hey!

Thank you for the kind words ❤️ Glad to hear that 😊

I'm not able to reproduce the crash on an Android 16 AVD, but I'm fairly confident that this is because I'm running the latest frida-core from git, where our libc-shim's stdio bits recently had some fixes applied, such as this one. Does latest git of frida-core work any better for you?

Cheers,
Ole André

@monkeywave
Copy link
Copy Markdown
Contributor Author

Hey @oleavr ,

thanks for looking into this :-) I built frida-server with the latest frida-core (including your fseeko64/ftello64 libc-shim additions from f0fd7aef) and can confirm it resolves the SIGSEGV -- the type confusion between FridaFile* and bionic's FILE* seems to be the direct cause of the crash.

That said, I think the changes in this PR are still valuable as a complementary fix at the frida-gum layer:

  • Avoiding the APK extraction path entirely: Even with the libc-shim fix, gum_maybe_extract_from_apk() still fires for APK-embedded libraries. It works now (no crash), but it's extracting a large library from the APK via minizip just to read section headers -- when the ELF data is already mapped in process memory. Skipping this for APK paths avoids that unnecessary work.

  • .dynsym fallback for enumerateSymbols(): APK-embedded .so files are typically stripped (no .symtab). So even after a successful APK extraction, enumerateSymbols() would return []. The .dynsym fallback ensures we still return meaningful symbol data from the dynamic symbol table, which is always available in memory via PT_DYNAMIC.

Your libc-shim fix is the right solution at the stdio layer -- it benefits all code in the agent that touches shimmed file handles. This PR just makes sure frida-gum takes the optimal code path for APK-embedded ELFs in the first place.

Happy to hear your thoughts :-)

@oleavr
Copy link
Copy Markdown
Member

oleavr commented Mar 8, 2026

Thanks for testing, that's good to hear! :)

  • Avoiding the APK extraction path entirely

Is it safe to assume that this is always the case, and there aren't APKs out there with unstripped .so files? I haven't looked into this and lack data, so I'm hesitant to assume so.

  • .dynsym fallback for enumerateSymbols()

Makes sense. Let's land this part now.

@oleavr
Copy link
Copy Markdown
Member

oleavr commented Mar 8, 2026

Just landed the .dynsym fallback in 01eadbf. Thanks! 🙌

@monkeywave
Copy link
Copy Markdown
Contributor Author

Thanks for testing, that's good to hear! :)

  • Avoiding the APK extraction path entirely

Is it safe to assume that this is always the case, and there aren't APKs out there with unstripped .so files? I haven't looked into this and lack data, so I'm hesitant to assume so.

Good question — I should probably clarify the motivation behind the !apk skipping🙂

The idea is less about assuming that .so files inside APKs are always stripped, and more about avoiding the extraction path when the ELF is already mapped in memory. In the APK case, Frida already has access to the in-memory image via the loader (if I'm not mistaken), so extracting the .so with minizip just to read section seems somewhat redundant in most cases.

Regarding unstripped .so files: my understanding is that stripped libraries are generally the default. The official NDK documentation also seems to assume this and provides guidance on how to keep symbols if needed. So while it is definitely possible to ship an APK with unstripped .so files, my impression is that this would be relatively uncommon in practice.

So the main motivation here was simply to prefer the already-mapped in-memory ELF when possible and avoid the extra APK extraction step 🙂

@oleavr
Copy link
Copy Markdown
Member

oleavr commented Mar 26, 2026

The idea is less about assuming that .so files inside APKs are always stripped, and more about avoiding the extraction path when the ELF is already mapped in memory. In the APK case, Frida already has access to the in-memory image via the loader (if I'm not mistaken), so extracting the .so with minizip just to read section seems somewhat redundant in most cases.

Regarding unstripped .so files: my understanding is that stripped libraries are generally the default. The official NDK documentation also seems to assume this and provides guidance on how to keep symbols if needed. So while it is definitely possible to ship an APK with unstripped .so files, my impression is that this would be relatively uncommon in practice.

So the main motivation here was simply to prefer the already-mapped in-memory ELF when possible and avoid the extra APK extraction step 🙂

Ahh, makes sense. Yeah that sounds good to me. Feel free to rebase on latest main if you want to take a stab at it ☺️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants