Compare commits

..
38 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 2a41a960fe subprojects: Prepare for release 2026-11-07 15:45:32 +01:00
Ole André Vadla Ravnås 25bd49299a subprojects: Bump outdated 2026-11-07 15:44:13 +01:00
tubibibo c84323b6ac linux: Don't pass our stdin to crash logcat
Under `adb shell`, frida-server's stdin and stdout are the same socket.
The crash monitor's logcat inherited it, so when frida-server died
abnormally, logcat kept it open and `adb shell` never saw EOF.

Give logcat /dev/null for stdin instead.
2026-11-07 15:38:00 +01:00
Ole André Vadla Ravnås 45aef70f35 subprojects: Prepare for release 2026-11-06 13:15:10 +01:00
Ole André Vadla Ravnås eb8efdf40a subprojects: Bump outdated 2026-11-06 12:19:36 +01:00
Ole André Vadla Ravnås 025e5c433d submodules: Bump releng 2026-11-06 12:19:35 +01:00
Ole André Vadla Ravnås f9314073dc subprojects: Prepare for release 2026-11-05 22:45:53 +01:00
Ole André Vadla Ravnås 4011e34449 ci: Run QNX tests out of /tmp
On the emulated QNX 6.5 sabrelite board, threads that concurrently
take first-touch page faults on code mapped from the boot image's
/opt sometimes get SIGSEGV with SEGV_MAPERR, even though the pages
are mapped. A standalone dlopen() test with two or more threads
calling into a fresh library crashes on the first iteration, and
passes when the library lives in /tmp. This made the agent tests
crash intermittently once the reloaded agent spun up its threads.
2026-11-05 22:43:11 +01:00
Ole André Vadla Ravnås 2fef8d7d20 subprojects: Bump outdated 2026-11-05 21:59:57 +01:00
Ole André Vadla Ravnås 9f681e26f1 subprojects: Bump outdated 2026-11-05 21:00:52 +01:00
Ole André Vadla Ravnås 18a156817f barebone: Bump @types/frida-gum to 19.13.0 2026-11-05 20:58:58 +01:00
Ole André Vadla Ravnås 2ea8b6278e compiler: Bump @types/frida-gum to 19.13.0 2026-11-05 20:58:58 +01:00
washedbeast 40ce660b54 linux: Fix memfd check in code swap fallback
Gum.Linux.enumerate_ranges() gives paths as they appear in the maps,
like "/memfd:jit-zygote-cache (deleted)", so the "memfd:" prefix check
from 72a96226 never matched. When libc could not be found in the
target, the code swap then picked ART's read-only JIT zygote cache and
the first write failed with "unable to perform ptrace pokedata: I/O
error".
2026-11-05 11:43:12 +01:00
washedbeast 7913d880c2 linux: Parse /proc/<pid>/maps as raw bytes
Mapping names are not always valid UTF-8: file paths may hold any
bytes, and on Android kernels before 5.17 an anonymous mapping's name
is read from a user pointer whenever the maps are read, so a name whose
string got unmapped shows up as random bytes. We hit this with Magisk's
Zygisk, where apps inherit [anon:System property context nodes] from
zygote with a garbage name.

GRegex rejects a subject that is not valid UTF-8 unless the pattern is
raw, so one such line made ProcMapsIter yield no mappings at all. The
injector then could not find libc in the target, and attach failed.
2026-11-05 11:43:12 +01:00
washedbeast 34e718e4a3 linux: Make syscall-trace strings valid UTF-8
g_variant_new_string() returns NULL when given invalid UTF-8, and the
g_variant_ref_sink() that Vala adds around it then dereferences the
NULL, crashing frida-server.

Module paths in resolve-symbols, and string arguments and AF_UNIX
socket paths in read-events, come from /proc/<pid>/maps and from the
kernel, so they can hold any byte. Replace invalid sequences before
they go into a GVariant.
2026-11-05 11:39:13 +01:00
tubibibo 10d6f866b1 agent: Retry emulated load through LoadLibraryExt
Houdini v3 rejects the deprecated NativeBridgeLoadLibrary() entrypoint
and returns NULL, which surfaces as "Process is not using emulation".
When the plain call fails and the extended entrypoint is available,
retry through it with a null namespace.
2026-11-05 11:31:24 +01:00
Ole André Vadla Ravnås d5bc484072 subprojects: Bump outdated 2026-11-04 12:53:53 +01:00
Ole André Vadla Ravnås 04bd09b7b7 submodules: Bump releng 2026-11-04 12:53:52 +01:00
Ole André Vadla Ravnås ef7dd9701c agent: Mirror native cloak in emulated realm
The emulated agent sees the native agent's threads when it walks the
host's thread list, as they are only cloaked in the native agent's Gum.

Have the native agent share its cloaked threads with the emulated agent,
as a snapshot followed by changes, applied to the emulated agent's own
cloak.

Depends on the CloakObserver interface in frida-gum.
2026-11-03 23:11:43 +01:00
Ole André Vadla Ravnås 8777bbe8af agent: Feed emulated thread registry from host
On Android's native bridge, threads are created and torn down by the
host's Bionic, which the emulated agent's Interceptor cannot hook. This
broke Process.attachThreadObserver() in the emulated realm.

Have the native agent expose its own thread registry to the emulated
agent, which plugs it into Gum as a ThreadProvider: a snapshot on
activation, followed by added, removed and renamed events.

Depends on the ThreadProvider interface in frida-gum.
2026-11-03 23:11:43 +01:00
Francesco Tamagni 8734d9875a fruity: Fix kill() fallback (#1282)
LLDB errors are Efni.Error since the move to idun, so catching
Frida.Error skipped the ProcessControlService fallback.
2026-11-03 17:35:54 +01:00
Francesco Tamagni d043c66c75 channel: Keep streams alive until linked
The registry only holds a weak reference, so a stream nobody else owns,
such as one relayed by idund, was gone before link().
2026-11-03 17:29:07 +01:00
Ole André Vadla Ravnås 317363ce80 subprojects: Prepare for release 2026-11-02 22:10:32 +01:00
Ole André Vadla Ravnås f1aca3182f subprojects: Bump outdated 2026-11-02 22:08:48 +01:00
Ole André Vadla Ravnås 92bc074641 linux: Skip libraries of a foreign ABI in mapper
On x86_64 Android with a native bridge, the arm64 guest libraries may
be mapped before the host ones, and since the mapper looks up
dependencies by basename, the x86_64 agent got bound to the guest libc.

Only accept libraries whose machine and pointer size match the agent.

Fixes #1281.
2026-11-02 21:01:10 +01:00
Ole André Vadla Ravnås 8c98e9b6c6 linux: Find module bases using the ELF layout
Since 00d8ba24, the mapper picks library bases through
find_module_by_path(), whose heuristic starts a new candidate at every
mapping with file offset 0 and prefers executable ones. This breaks
libraries whose code shares file page 0 with their headers, such as
Android's libdl.so on x86_64, where the code mapping was picked as the
base, one page too high.

Instead, accept a mapping at file offset 0 as the base only if the
library's code segments are mapped executable where the linker would
have put them. This also rejects stray mappings of the file, so the
Android-specific libc workaround is no longer needed.

Fixes #1280.
2026-11-02 21:00:51 +01:00
Ole André Vadla Ravnås b61866bf84 subprojects: Prepare for release 2026-11-01 15:11:39 +01:00
Ole André Vadla Ravnås 67d80c40a3 subprojects: Bump outdated 2026-11-01 15:10:19 +01:00
Ole André Vadla Ravnås 22b43c980f agent: Shut down Gum while still able to patch
When code memory is delegated to the host, Gum's early destructors need
to run while the host connection is still up, as some of them revert
hooks. Do so as the last step of removing instrumentation, which happens
inside unload() for agents that delegate.
2026-11-01 15:06:45 +01:00
Ole André Vadla Ravnås 00d8ba24d0 linux: Ignore stray library mappings in mapper
A process may map a library file outside of the dynamic linker. When
such a mapping came before the loaded image, the mapper used it as the
library's base and bound imports into non-executable memory.

Pick each library's base through find_module_by_path(), which prefers
the mapping that the linker actually loaded.

Fixes #1278.
2026-11-01 14:42:47 +01:00
Ole André Vadla Ravnås bf5d2e0516 windows: Include winternl.h for NTSTATUS
The helper backend glue relied on NTSTATUS being pulled in indirectly,
which is no longer the case with recent SDKs.
2026-11-01 14:14:55 +01:00
Ole André Vadla Ravnås cb997b4131 windows: Keep agent mapped while target exits
When the target exits, its other threads are terminated before DLL
detach notifications run, including the agent's. We then saw the
injector thread end without the agent having asked to stay resident,
and unmapped it while hooks such as the unwind broker's were still in
place. Anything running during the remainder of the shutdown could then
call into unmapped memory, e.g. the shim engine walking the stack.

Leave the agent mapped when the process is already exiting.

Fixes frida/frida-gum#1200.
2026-11-01 14:12:34 +01:00
Ole André Vadla Ravnås e106e11a07 subprojects: Bump outdated 2026-10-31 00:48:09 +01:00
Ole André Vadla Ravnås fa87aae7e4 subprojects: Bump outdated 2026-10-30 23:12:48 +01:00
Ole André Vadla Ravnås 340cca8edc subprojects: Bump outdated 2026-10-30 21:59:02 +01:00
Ole André Vadla Ravnås 275ce5a663 submodules: Bump releng 2026-10-30 21:59:01 +01:00
Ole André Vadla Ravnås aa97282d85 Update URLs of repos moved to frida-forks 2026-10-30 21:56:44 +01:00
Leo Conforti 6f68beba34 tests: Check that Linux spawn leaves SIGCHLD alone
A spawned process used to make GLib replace the host's SIGCHLD handler
with its own, and reset it to SIG_DFL once the process was gone, which
kept Node.js from reaping its child_process children. And if the host
reset SIGCHLD in the meantime, the spawned process was never reaped.

Both rely on GLib using pidfd for child watches, as it does since
frida/glib#17, so they are skipped on kernels without pidfd.

See frida/frida-node#121.
2026-10-30 20:40:24 +01:00
38 changed files with 706 additions and 181 deletions

No files matched your search

+4 -1
View File
@@ -451,7 +451,10 @@ jobs:
cd build
cp -a tests/frida-tests tests/labrats lib/agent/frida-agent.so /tmp/pkg/
tar -C /tmp/pkg -cf /tmp/runner.tar .
/opt/sabrelite/run.sh /tmp/runner.tar /opt/frida/frida-tests
/opt/sabrelite/run.sh /tmp/runner.tar \
"cp /opt/frida/frida-tests /opt/frida/frida-agent.so /tmp/ && \
ln -sP /opt/frida/labrats /tmp/labrats && \
/tmp/frida-tests"
# This job is used to check that the Linux helpers are built correctly for all
# supported architectures. We do this because the binary artifacts are checked
+1 -1
View File
@@ -43,7 +43,7 @@ platform-dependent (e.g. `linux-x86_64`).
## Build Requirements
- **Vala compiler**: Must be the Frida-optimized fork from
https://github.com/frida/vala (checks for `-frida` suffix
https://github.com/frida-forks/vala (checks for `-frida` suffix
in version)
- **C/C++ standards**: gnu99/c99 for C, c++17 for C++
- **Go >= 1.24**: Required for compiler backend
+250
View File
@@ -103,6 +103,9 @@ namespace Frida.Agent {
private SpawnMonitor? spawn_monitor;
private ThreadSuspendMonitor? thread_suspend_monitor;
private UnwindSitter? unwind_sitter;
#if ANDROID && (ARM || ARM64)
private NativeCloakMirror? native_cloak_mirror;
#endif
private delegate void CompletionNotify ();
@@ -350,6 +353,9 @@ namespace Frida.Agent {
bool enable_unwind_broker = true;
bool enable_exit_monitor = true;
bool enable_thread_suspend_monitor = true;
#if ANDROID && (ARM || ARM64)
bool in_emulated_realm = false;
#endif
foreach (unowned string option in tokens[1:]) {
if (option == "eternal")
ensure_eternalized ();
@@ -365,6 +371,10 @@ namespace Frida.Agent {
enable_exit_monitor = false;
else if (option == "thread-suspend-monitor:off")
enable_thread_suspend_monitor = false;
#if ANDROID && (ARM || ARM64)
else if (option == "realm:emulated")
in_emulated_realm = true;
#endif
}
if (exceptor_mode != FULL)
@@ -374,6 +384,10 @@ namespace Frida.Agent {
try {
yield setup_connection_with_transport_uri (transport_uri);
#if ANDROID && (ARM || ARM64)
if (in_emulated_realm)
yield connect_to_native_realm ();
#endif
} catch (Error e) {
start_error = e;
main_loop.quit ();
@@ -406,6 +420,23 @@ namespace Frida.Agent {
padder = null;
}
#if ANDROID && (ARM || ARM64)
private async void connect_to_native_realm () throws Error {
try {
NativeCloak cloak = yield connection.get_proxy (null, ObjectPath.NATIVE_CLOAK, DO_NOT_LOAD_PROPERTIES,
null);
native_cloak_mirror = new NativeCloakMirror (cloak);
native_cloak_mirror.activate ();
NativeThreadRegistry registry = yield connection.get_proxy (null, ObjectPath.NATIVE_THREAD_REGISTRY,
DO_NOT_LOAD_PROPERTIES, null);
Gum.ThreadRegistry.set_provider (new NativeThreadProvider (registry));
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
}
#endif
private static bool can_allocate_code () {
size_t page_size = Gum.query_page_size ();
@@ -1194,6 +1225,8 @@ namespace Frida.Agent {
unwind_sitter = null;
interceptor.end_transaction ();
Gum.shutdown ();
}
private void ensure_eternalized () {
@@ -1476,6 +1509,8 @@ namespace Frida.Agent {
private Socket? emulated_socket;
private BridgeState? emulated_bridge_state;
private Thread<void>? emulated_worker;
private NativeCloakService? native_cloak;
private NativeThreadRegistryService? native_thread_registry;
private async AgentSessionProvider? try_get_emulated_provider (Cancellable? cancellable) throws IOError {
if (get_emulated_request == null)
@@ -1515,6 +1550,8 @@ namespace Frida.Agent {
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, classloader_namespace);
} else {
emulated_agent = nb_api.load_library (emulated_agent_path, RTLD_LAZY);
if (emulated_agent == null && nb_api.load_library_ext != null)
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, null);
}
if (emulated_agent == null)
throw new Error.NOT_SUPPORTED ("Process is not using emulation");
@@ -1554,6 +1591,7 @@ namespace Frida.Agent {
* code being discarded, which seems like an edge-case the translator doesn't handle.
*/
parameters.append ("|exceptor:off|exit-monitor:off");
parameters.append ("|realm:emulated");
emulated_bridge_state = new BridgeState (parameters.str);
@@ -1562,6 +1600,13 @@ namespace Frida.Agent {
var connection = yield new DBusConnection (stream, ServerGuid.HOST_SESSION_SERVICE,
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
native_cloak = new NativeCloakService ();
connection.register_object (ObjectPath.NATIVE_CLOAK, (NativeCloak) native_cloak);
native_thread_registry = new NativeThreadRegistryService ();
connection.register_object (ObjectPath.NATIVE_THREAD_REGISTRY,
(NativeThreadRegistry) native_thread_registry);
AgentSessionProvider provider = yield connection.get_proxy (null, ObjectPath.AGENT_SESSION_PROVIDER,
DO_NOT_LOAD_PROPERTIES, cancellable);
@@ -1609,6 +1654,16 @@ namespace Frida.Agent {
emulated_worker = null;
}
if (native_thread_registry != null) {
native_thread_registry.close ();
native_thread_registry = null;
}
if (native_cloak != null) {
native_cloak.close ();
native_cloak = null;
}
emulated_socket = null;
if (emulated_agent != null) {
@@ -1769,6 +1824,201 @@ namespace Frida.Agent {
}
#endif
#if ANDROID && (X86 || X86_64)
private sealed class NativeCloakService : Object, NativeCloak, Gum.Cloak.Observer {
private MainContext main_context = MainContext.ref_thread_default ();
private bool observing = false;
public uint[] activate (Cancellable? cancellable) {
Gum.Cloak.add_observer (this);
observing = true;
uint[] threads = {};
Gum.Cloak.enumerate_threads (id => {
threads += (uint) id;
return true;
});
return threads;
}
public void close () {
if (!observing)
return;
Gum.Cloak.remove_observer (this);
observing = false;
}
public void thread_added (Gum.ThreadId id) {
schedule (() => {
thread_cloaked ((uint) id);
return Source.REMOVE;
});
}
public void thread_removed (Gum.ThreadId id) {
schedule (() => {
thread_uncloaked ((uint) id);
return Source.REMOVE;
});
}
private void schedule (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (main_context);
}
}
private sealed class NativeThreadRegistryService : Object, NativeThreadRegistry {
private unowned Gum.ThreadRegistry? registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadInfo[] activate (Cancellable? cancellable) {
registry = Gum.ThreadRegistry.obtain ();
NativeThreadInfo[] threads = {};
registry.lock ();
added_handler = registry.thread_added.connect (on_thread_added);
removed_handler = registry.thread_removed.connect (on_thread_removed);
renamed_handler = registry.thread_renamed.connect (on_thread_renamed);
registry.enumerate_threads (thread => {
threads += NativeThreadInfo ((uint) thread.id, thread.name ?? "");
return true;
});
registry.unlock ();
return threads;
}
public void deactivate (Cancellable? cancellable) {
close ();
}
public void close () {
if (registry == null)
return;
registry.disconnect (added_handler);
registry.disconnect (removed_handler);
registry.disconnect (renamed_handler);
registry = null;
}
private void on_thread_added (Gum.ThreadDetails thread) {
thread_added (NativeThreadInfo ((uint) thread.id, thread.name ?? ""));
}
private void on_thread_removed (Gum.ThreadDetails thread) {
thread_removed ((uint) thread.id);
}
private void on_thread_renamed (Gum.ThreadDetails thread, string? previous_name) {
thread_renamed ((uint) thread.id, thread.name ?? "");
}
}
#endif
#if ANDROID && (ARM || ARM64)
private sealed class NativeCloakMirror : Object {
public NativeCloak native_cloak {
get;
construct;
}
public NativeCloakMirror (NativeCloak native_cloak) {
Object (native_cloak: native_cloak);
}
public void activate () throws GLib.Error {
native_cloak.thread_cloaked.connect (on_thread_cloaked);
native_cloak.thread_uncloaked.connect (on_thread_uncloaked);
foreach (uint id in native_cloak.activate (null))
on_thread_cloaked (id);
}
private void on_thread_cloaked (uint id) {
if (!Gum.Cloak.has_thread (id))
Gum.Cloak.add_thread (id);
}
private void on_thread_uncloaked (uint id) {
Gum.Cloak.remove_thread (id);
}
}
private sealed class NativeThreadProvider : Object, Gum.ThreadProvider {
public NativeThreadRegistry native_registry {
get;
construct;
}
private unowned Gum.ThreadRegistry registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadProvider (NativeThreadRegistry native_registry) {
Object (native_registry: native_registry);
}
public void activate (Gum.ThreadRegistry registry) {
this.registry = registry;
added_handler = native_registry.thread_added.connect (add_thread);
removed_handler = native_registry.thread_removed.connect (on_thread_removed);
renamed_handler = native_registry.thread_renamed.connect (on_thread_renamed);
NativeThreadInfo[] threads;
try {
threads = native_registry.activate (null);
} catch (GLib.Error e) {
return;
}
foreach (var thread in threads)
add_thread (thread);
}
public void deactivate () {
native_registry.disconnect (added_handler);
native_registry.disconnect (removed_handler);
native_registry.disconnect (renamed_handler);
try {
native_registry.deactivate (null);
} catch (GLib.Error e) {
}
}
private void add_thread (NativeThreadInfo thread) {
var details = Gum.ThreadDetails ();
details.id = thread.id;
if (thread.name != "") {
details.name = thread.name;
details.flags = NAME;
}
registry.register (details);
}
private void on_thread_removed (uint id) {
registry.unregister (id);
}
private void on_thread_renamed (uint id, string name) {
registry.rename (id, (name != "") ? name : null);
}
}
#endif
private sealed class LiveAgentSession : BaseAgentSession {
public uint registration_id {
get;
+12 -2
View File
@@ -6,6 +6,8 @@ namespace Frida {
private Gee.Map<ChannelId?, weak IOStream> channels =
new Gee.HashMap<ChannelId?, weak IOStream> (ChannelId.hash, ChannelId.equal);
private Gee.Map<ChannelId?, IOStream> unlinked_channels =
new Gee.HashMap<ChannelId?, IOStream> (ChannelId.hash, ChannelId.equal);
~ChannelRegistry () {
clear ();
@@ -18,25 +20,33 @@ namespace Frida {
channel_stream.abandon ();
}
channels.clear ();
unlinked_channels.clear ();
}
public void register (ChannelId id, IOStream stream) {
stream.set_data ("channel-registry-entry", new Entry (this, id));
lock (channels)
lock (channels) {
channels[id] = stream;
unlinked_channels[id] = stream;
}
}
public IOStream link (ChannelId id) throws Error {
IOStream? stream;
lock (channels)
IOStream? unlinked_stream = null;
lock (channels) {
stream = channels[id];
unlinked_channels.unset (id, out unlinked_stream);
}
if (stream == null)
throw new Error.INVALID_ARGUMENT ("Invalid channel ID");
return stream;
}
public void unlink (ChannelId id) {
IOStream? unlinked_stream = null;
lock (channels) {
unlinked_channels.unset (id, out unlinked_stream);
if (!channels.unset (id))
return;
}
+32
View File
@@ -133,6 +133,36 @@ namespace Frida {
Cancellable? cancellable) throws GLib.Error;
}
#if ANDROID
[DBus (name = "re.frida.NativeThreadRegistry17")]
public interface NativeThreadRegistry : Object {
public abstract NativeThreadInfo[] activate (Cancellable? cancellable) throws GLib.Error;
public abstract void deactivate (Cancellable? cancellable) throws GLib.Error;
public signal void thread_added (NativeThreadInfo thread);
public signal void thread_removed (uint id);
public signal void thread_renamed (uint id, string name);
}
[DBus (name = "re.frida.NativeCloak17")]
public interface NativeCloak : Object {
public abstract uint[] activate (Cancellable? cancellable) throws GLib.Error;
public signal void thread_cloaked (uint id);
public signal void thread_uncloaked (uint id);
}
public struct NativeThreadInfo {
public uint id;
public string name;
public NativeThreadInfo (uint id, string name) {
this.id = id;
this.name = name;
}
}
#endif
[DBus (name = "re.frida.AgentMessageSink17")]
public interface AgentMessageSink : Object {
public abstract async void post_messages (AgentMessage[] messages, uint batch_id,
@@ -2802,6 +2832,8 @@ namespace Frida {
public const string AGENT_SESSION_PROVIDER = "/re/frida/AgentSessionProvider";
public const string AGENT_SESSION = "/re/frida/AgentSession";
public const string AGENT_CONTROLLER = "/re/frida/AgentController";
public const string NATIVE_THREAD_REGISTRY = "/re/frida/NativeThreadRegistry";
public const string NATIVE_CLOAK = "/re/frida/NativeCloak";
public const string AGENT_MESSAGE_SINK = "/re/frida/AgentMessageSink";
public const string GADGET_SESSION = "/re/frida/GadgetSession";
public const string CHANNEL = "/re/frida/Channel";
+1 -1
View File
@@ -14,7 +14,7 @@ if not meson.get_compiler('vala').version().endswith('-frida')
Frida currently relies on features not yet upstream. Grab the Frida-optimized
Vala sources from:
https://github.com/frida/vala
https://github.com/frida-forks/vala
and compile them with Meson.''')
endif
+1 -1
Submodule releng updated: a3f1a54307...164a6a4117.
+4 -4
View File
@@ -8,7 +8,7 @@
"name": "frida-barebone-script-runtime",
"version": "1.0.0",
"devDependencies": {
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
}
@@ -439,9 +439,9 @@
}
},
"node_modules/@types/frida-gum": {
"version": "19.12.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.12.0.tgz",
"integrity": "sha512-fqXtwFA8AwE89tSdNHlaY+hNWI5j62bcO1xk5w0NSuHBIH6KRxYzIK6rp/JP7TXtRKU2ewBwx81FrHTfGF0q7w==",
"version": "19.13.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.13.0.tgz",
"integrity": "sha512-tbmEZj9JUt3S6FqFE4XJPi/WhQuUU0/sM1IWS1KDKqvy6077hwNjG1v4MC3wpk+FO/WzfZ8G1gAKqAKJosfj1Q==",
"dev": true,
"license": "MIT"
},
+1 -1
View File
@@ -9,7 +9,7 @@
"watch": "tsc -w"
},
"devDependencies": {
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
}
+4 -4
View File
@@ -36,7 +36,7 @@
"@frida/util": "^1.0.3",
"@frida/vm": "^2.0.0",
"@frida/web-streams": "^1.0.0",
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"@types/node": "18.19.103",
"frida-fs": "7.1.1"
}
@@ -270,9 +270,9 @@
"license": "MIT"
},
"node_modules/@types/frida-gum": {
"version": "19.12.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.12.0.tgz",
"integrity": "sha512-fqXtwFA8AwE89tSdNHlaY+hNWI5j62bcO1xk5w0NSuHBIH6KRxYzIK6rp/JP7TXtRKU2ewBwx81FrHTfGF0q7w==",
"version": "19.13.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.13.0.tgz",
"integrity": "sha512-tbmEZj9JUt3S6FqFE4XJPi/WhQuUU0/sM1IWS1KDKqvy6077hwNjG1v4MC3wpk+FO/WzfZ8G1gAKqAKJosfj1Q==",
"license": "MIT"
},
"node_modules/@types/node": {
+1 -1
View File
@@ -32,7 +32,7 @@
"@frida/util": "^1.0.3",
"@frida/vm": "^2.0.0",
"@frida/web-streams": "^1.0.0",
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"@types/node": "18.19.103",
"frida-fs": "7.1.1"
}
+3 -1
View File
@@ -1074,7 +1074,9 @@ namespace Frida {
lldb_session = new LLDBSession (lldb, process, null, ExceptionHandlingStatus.FULL, device);
yield lldb_session.kill (cancellable);
yield lldb_session.close (cancellable);
} catch (Error e) {
} catch (GLib.Error e) {
if (e is IOError.CANCELLED)
throw (IOError) e;
var process_control = yield Idun.ProcessControlService.open (device, cancellable);
yield process_control.kill (pid, cancellable);
}
+1 -1
View File
@@ -2433,7 +2433,7 @@ namespace Frida {
Gum.Linux.enumerate_ranges ((Posix.pid_t) session.pid, READ | EXECUTE, d => {
unowned Gum.FileMapping? file = d.file;
if (file != null && file.path.has_prefix ("memfd:"))
if (file != null && file.path.has_prefix ("/memfd:"))
return true;
if (d.range.size >= code.length) {
code_start = d.range.base_address + d.range.size - round_size_to_page_size (code.length);
+10 -6
View File
@@ -1791,7 +1791,11 @@ namespace Frida {
}
MatchInfo info;
if (!/^([0-9a-f]+)-([0-9a-f]+) rw-p 00000000 00:00 0 *$/m.match (raw_maps, 0, out info)) {
try {
var anon_rw_pattern = new Regex ("^([0-9a-f]+)-([0-9a-f]+) rw-p 00000000 00:00 0 *$", MULTILINE | RAW);
if (!anon_rw_pattern.match (raw_maps, 0, out info))
assert_not_reached ();
} catch (RegexError e) {
assert_not_reached ();
}
assert (info != null);
@@ -2478,7 +2482,7 @@ namespace Frida {
if (stdout_pipe == null) {
try {
var process = new Subprocess.newv ({ "logcat", "-b", "crash", "-B" },
STDIN_INHERIT | STDOUT_PIPE | STDERR_SILENCE);
STDOUT_PIPE | STDERR_SILENCE);
logcat = process;
stdout_pipe = process.get_stdout_pipe ();
@@ -2894,7 +2898,7 @@ namespace Frida {
},
module_list => {
foreach (var path in module_list)
modules.add_value (new Variant.variant (new Variant.tuple ({ path })));
modules.add_value (new Variant.variant (new Variant.tuple ({ path.make_valid () })));
});
vardict_add (reply, "modules", modules.end ());
@@ -3111,7 +3115,7 @@ namespace Frida {
switch (attachment_type) {
case SyscallTracer.AttachmentType.STRING:
return (string) data;
return ((string) data).make_valid ();
case SyscallTracer.AttachmentType.BYTES: {
if (arg_type != null) {
Variant? v = try_decode_timespec (arg_type, data, len);
@@ -3208,7 +3212,7 @@ namespace Frida {
if (name_len != 0)
Memory.copy (str, path + 1, name_len);
return new Variant.tuple ({ "unix-abstract", (string) str });
return new Variant.tuple ({ "unix-abstract", ((string) str).make_valid () });
}
size_t n = 1;
@@ -3218,7 +3222,7 @@ namespace Frida {
var str = new uint8[n + 1];
Memory.copy (str, path, n);
return new Variant.tuple ({ "unix", (string) str });
return new Variant.tuple ({ "unix", ((string) str).make_valid () });
}
default:
return null;
+27 -72
View File
@@ -21,7 +21,7 @@ namespace Frida {
}
public Bytes make_request () {
Gee.List<string> missing = new TargetLibraries (pid).find_missing (module);
Gee.List<string> missing = new TargetLibraries (pid, module).find_missing ();
var header = HelperMappedAgentRequest ();
header.size = capacity;
@@ -40,10 +40,10 @@ namespace Frida {
public Bytes map (uint64 base_address, string entrypoint) throws Error {
try {
var libraries = new TargetLibraries (pid);
var libraries = new TargetLibraries (pid, module);
var memory = new MappedAgentMemory (pid, blob, module, base_address, capacity);
var mapper = Gum.Mapper.new_take_blob ("frida-agent.so", blob, libraries.make_resolver (module));
var mapper = Gum.Mapper.new_take_blob ("frida-agent.so", blob, libraries.make_resolver ());
mapper.set_target (memory);
Gum.Address extra_address;
@@ -61,16 +61,21 @@ namespace Frida {
}
private sealed class TargetLibraries {
private uint pid;
private Gum.ElfModule agent;
private ProcMapsSnapshot maps;
private Gee.Map<string, Gum.ElfModule> modules = new Gee.HashMap<string, Gum.ElfModule> ();
private Gee.List<ProcMapsSnapshot.Mapping> library_starts = new Gee.ArrayList<ProcMapsSnapshot.Mapping> ();
public TargetLibraries (uint pid) {
this.pid = pid;
public TargetLibraries (uint pid, Gum.ElfModule agent) {
this.agent = agent;
this.maps = ProcMapsSnapshot.from_pid (pid);
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (is_library_start (m))
library_starts.add (m);
}
}
public Gee.List<string> find_missing (Gum.ElfModule agent) {
public Gee.List<string> find_missing () {
var missing = new Gee.ArrayList<string> ();
agent.enumerate_dependencies (d => {
if (find (d.name) == null)
@@ -80,7 +85,7 @@ namespace Frida {
return missing;
}
public Gum.ElfModuleResolver make_resolver (Gum.ElfModule agent) throws Error {
public Gum.ElfModuleResolver make_resolver () throws Error {
var resolver = new Gum.ElfModuleResolver ();
var pending = new Gee.ArrayQueue<string> ();
@@ -98,7 +103,7 @@ namespace Frida {
if (!seen.add (mapping.path))
continue;
Gum.ElfModule library = load (mapping);
Gum.ElfModule library = maps.load_image (mapping);
resolver.add_module (library, mapping.start);
library.enumerate_dependencies (d => {
@@ -111,16 +116,14 @@ namespace Frida {
}
private ProcMapsSnapshot.Mapping? find (string name) {
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (is_library_start (m) && library_name_matches (name, Path.get_basename (m.path)))
foreach (ProcMapsSnapshot.Mapping m in library_starts) {
if (library_name_matches (name, Path.get_basename (m.path)) && is_compatible_image (m))
return m;
}
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (!is_library_start (m))
continue;
foreach (ProcMapsSnapshot.Mapping m in library_starts) {
try {
if (load (m).soname == name)
if (maps.load_image (m).soname == name && is_compatible_image (m))
return m;
} catch (Error e) {
}
@@ -144,65 +147,17 @@ namespace Frida {
}
}
private Gum.ElfModule load (ProcMapsSnapshot.Mapping mapping) throws Error {
Gum.ElfModule? module = modules[mapping.path];
if (module == null) {
module = open_mapped_file (mapping);
if (module == null)
module = copy_from_target (mapping);
modules[mapping.path] = module;
}
return module;
}
private Gum.ElfModule? open_mapped_file (ProcMapsSnapshot.Mapping mapping) {
private bool is_compatible_image (ProcMapsSnapshot.Mapping start) {
Gum.ElfModule library;
try {
return new Gum.ElfModule.from_file (("/proc/%u/map_files/%" + uint64.FORMAT_MODIFIER + "x-%" +
uint64.FORMAT_MODIFIER + "x").printf (pid, mapping.start, mapping.end));
} catch (Gum.Error e) {
library = maps.load_image (start);
} catch (Error e) {
return false;
}
string path = "/proc/%u/root%s".printf (pid, mapping.path);
Posix.Stat st;
if (Posix.stat (path, out st) != 0 || st.st_ino != mapping.inode)
return null;
try {
return new Gum.ElfModule.from_file (path);
} catch (Gum.Error e) {
return null;
}
}
private Gum.ElfModule copy_from_target (ProcMapsSnapshot.Mapping first) throws Error {
var segments = new Gee.ArrayList<ProcMapsSnapshot.Mapping> ();
uint64 end = first.end;
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (m.path == first.path && m.inode == first.inode && m.readable) {
segments.add (m);
end = uint64.max (end, m.end);
}
}
var image = new uint8[end - first.start];
var mem = new FileDescriptor (Posix.open ("/proc/%u/mem".printf (pid), Posix.O_RDONLY));
if (mem.handle == -1)
throw new Error.PERMISSION_DENIED ("Unable to read memory of process %u", pid);
foreach (ProcMapsSnapshot.Mapping m in segments)
mem.pread (image[m.start - first.start:m.end - first.start], m.start);
var blob = new Bytes.take ((owned) image);
var module = (Gum.ElfModule) Object.new (typeof (Gum.ElfModule),
"base-address", (Gum.Address) first.start,
"source-path", first.path,
"source-blob", blob,
"source-mode", Gum.ElfSourceMode.ONLINE);
try {
module.load ();
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to parse %s: %s", first.path, e.message);
}
return module;
return library.machine == agent.machine &&
library.pointer_size == agent.pointer_size &&
maps.is_image_base (start);
}
}
+101 -64
View File
@@ -6,7 +6,9 @@ namespace Frida {
default = 1;
}
private uint32 pid;
private Gee.List<Mapping> maps = new Gee.ArrayList<Mapping> ();
private Gee.Map<string, Gum.ElfModule> images = new Gee.HashMap<string, Gum.ElfModule> ();
private const uint64 VM_READ = 1;
private const uint64 VM_WRITE = 2;
@@ -44,6 +46,7 @@ namespace Frida {
}
private void build_from_pid (uint32 pid) {
this.pid = pid;
maps.clear ();
var it = ProcMapsIter.for_pid (pid);
@@ -268,76 +271,105 @@ namespace Frida {
}
public Mapping? find_module_by_path_at_gen (string path, uint32 at_gen) {
Mapping? best_base = null;
int best_score = int.MIN;
Mapping? cur_base = null;
uint cur_total = 0;
uint cur_exec = 0;
#if ANDROID
unowned string libc_path = Gum.Process.get_libc_module ().path;
#endif
for (int i = 0; i < maps.size; i++) {
var m = maps[i];
if (!mapping_alive_at (m, at_gen))
continue;
unowned string mp = m.path;
if (mp == "[page size compat]")
continue;
if (mp != path) {
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
cur_base = null;
cur_total = 0;
cur_exec = 0;
continue;
}
#if ANDROID
if (mp == libc_path && m.shared)
continue;
#endif
if (m.file_offset == 0) {
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
cur_base = m;
cur_total = 0;
cur_exec = 0;
}
if (cur_base != null) {
cur_total++;
if (m.executable)
cur_exec++;
}
foreach (Mapping m in maps) {
if (m.path == path && m.file_offset == 0 && mapping_alive_at (m, at_gen) && is_image_base_at_gen (m, at_gen))
return m;
}
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
return best_base;
return null;
}
private static void consider_candidate (ref Mapping? best_base, ref int best_score, Mapping? candidate_base, uint total,
uint exec) {
if (candidate_base == null)
return;
public bool is_image_base (Mapping start) {
return is_image_base_at_gen (start, gen);
}
int score = score_candidate (total, exec);
if (score > best_score) {
best_score = score;
best_base = candidate_base;
private bool is_image_base_at_gen (Mapping start, uint32 at_gen) {
Gum.ElfModule image;
try {
image = load_image (start);
} catch (Error e) {
return false;
}
uint64 page_mask = ~((uint64) Gum.query_page_size () - 1);
bool mapped = true;
image.enumerate_segments (s => {
if (s.file_size == 0 || (s.protection & Gum.PageProtection.EXECUTE) == 0)
return true;
uint64 address = start.start + ((s.vm_address - image.preferred_address) & page_mask);
Mapping? m = find_mapping_at_gen (address, at_gen);
mapped = m != null &&
m.executable &&
!m.shared &&
m.inode == start.inode &&
m.path == start.path &&
m.file_offset + (address - m.start) == (s.file_offset & page_mask);
return mapped;
});
return mapped;
}
public Gum.ElfModule load_image (Mapping start) throws Error {
Gum.ElfModule? image = images[start.path];
if (image == null) {
image = open_mapped_file (start);
if (image == null)
image = copy_from_process (start);
images[start.path] = image;
}
return image;
}
private Gum.ElfModule? open_mapped_file (Mapping mapping) {
try {
return new Gum.ElfModule.from_file (("/proc/%u/map_files/%" + uint64.FORMAT_MODIFIER + "x-%" +
uint64.FORMAT_MODIFIER + "x").printf (pid, mapping.start, mapping.end));
} catch (Gum.Error e) {
}
string path = "/proc/%u/root%s".printf (pid, mapping.path);
Posix.Stat st;
if (Posix.stat (path, out st) != 0 || st.st_ino != mapping.inode)
return null;
try {
return new Gum.ElfModule.from_file (path);
} catch (Gum.Error e) {
return null;
}
}
private static int score_candidate (uint total, uint exec) {
int score = (int) total;
if (exec == 0)
score = -score;
return score;
private Gum.ElfModule copy_from_process (Mapping first) throws Error {
var segments = new Gee.ArrayList<Mapping> ();
uint64 end = first.end;
foreach (Mapping m in maps) {
if (m.path == first.path && m.inode == first.inode && m.readable && m.start >= first.start &&
mapping_alive_at (m, gen)) {
segments.add (m);
end = uint64.max (end, m.end);
}
}
var image = new uint8[end - first.start];
var mem = new FileDescriptor (Posix.open ("/proc/%u/mem".printf (pid), Posix.O_RDONLY));
if (mem.handle == -1)
throw new Error.PERMISSION_DENIED ("Unable to read memory of process %u", pid);
foreach (Mapping m in segments)
mem.pread (image[m.start - first.start:m.end - first.start], m.start);
var blob = new Bytes.take ((owned) image);
var module = (Gum.ElfModule) Object.new (typeof (Gum.ElfModule),
"base-address", (Gum.Address) first.start,
"source-path", first.path,
"source-blob", blob,
"source-mode", Gum.ElfSourceMode.ONLINE);
try {
module.load ();
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to parse %s: %s", first.path, e.message);
}
return module;
}
}
@@ -417,8 +449,13 @@ namespace Frida {
return;
}
if (!/^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,}) (\d+) *([^\n]*)$/m.match (
contents, 0, out info)) {
try {
var line_pattern = new Regex (
"""^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,}) (\d+) *([^\n]*)$""",
MULTILINE | RAW);
if (!line_pattern.match (contents, 0, out info))
assert_not_reached ();
} catch (RegexError e) {
assert_not_reached ();
}
}
+11 -1
View File
@@ -9,6 +9,7 @@
#include <windows.h>
#include <tlhelp32.h>
#include <strsafe.h>
#include <winternl.h>
#define CHECK_OS_RESULT(n1, cmp, n2, op) \
if (!((n1) cmp (n2))) \
@@ -100,6 +101,7 @@ typedef NTSTATUS (WINAPI * RtlCreateUserThreadFunc) (HANDLE process, SECURITY_DE
static void frida_propagate_open_process_error (guint32 pid, DWORD os_error, GError ** error);
static gboolean frida_enable_debug_privilege (void);
static gboolean frida_process_is_exiting (HANDLE process);
static gboolean frida_remote_worker_context_init (FridaRemoteWorkerContext * rwc, FridaInjectionDetails * details, GError ** error);
static gsize frida_remote_worker_context_emit_payload (FridaRemoteWorkerContext * rwc, gpointer code);
@@ -265,7 +267,7 @@ _frida_windows_helper_backend_free_inject_instance (void * inject_instance, gboo
if (instance->mappers != NULL)
{
if (!*is_resident)
if (!*is_resident && !frida_process_is_exiting (instance->process_handle))
g_ptr_array_foreach (instance->mappers, (GFunc) gum_mapper_unmap, NULL);
g_ptr_array_unref (instance->mappers);
}
@@ -713,6 +715,14 @@ beach:
return success;
}
static gboolean
frida_process_is_exiting (HANDLE process)
{
DWORD exit_code;
return GetExitCodeProcess (process, &exit_code) && exit_code != STILL_ACTIVE;
}
static gboolean
frida_remote_worker_context_init (FridaRemoteWorkerContext * rwc, FridaInjectionDetails * details, GError ** error)
{
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/brotli.git
url = https://github.com/frida-forks/brotli.git
revision = dd581fd4fea06096e3949ad6cb16775538d0ef47
depth = 1
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/efni.git
revision = 54525e06e27659afb6c664c18f9a10bbaac2dca9
revision = 72f90afcc1f7c663db4de934e9f7ab9d3f67d4b1
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.25.0
revision = 17.26.2
depth = 1
[provide]
+2 -2
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/glib.git
revision = c366177b9292432a26f6903fa82a71d83e4927eb
url = https://github.com/frida-forks/glib.git
revision = f11db6352345ad21deb5f5783bb988bfca2a3d85
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/idun.git
revision = eaf98270beab0ecf174e611d99036db6843a1865
revision = 59f682f4aba229ca73d175f66f3fa6d9e2f4b7a0
depth = 1
[provide]
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/libbpf.git
url = https://github.com/frida-forks/libbpf.git
revision = 9c4e176810717e27b6990a8722b252fc1967667f
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/libgee.git
url = https://github.com/frida-forks/libgee.git
revision = 69621d2c665b75c1e064abdecd540ef1749615ee
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/libnice.git
url = https://github.com/frida-forks/libnice.git
revision = 91e2d02edb2999786d7ac854f99bdfd7832df64f
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/libusb.git
url = https://github.com/frida-forks/libusb.git
revision = 23d5a2cdc55aa4f0895960d08528e5aa13e7f7e2
depth = 1
+2 -2
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/lwip.git
revision = f8424fd177ae3654fa040b8b6bc0c32de7fd6ad9
url = https://github.com/frida-forks/lwip.git
revision = 6ab74462bd4a5afb13394942ab031d7bee005b4c
depth = 1
[provide]
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/lzfse.git
url = https://github.com/frida-forks/lzfse.git
revision = 5cfb7c86919d3c2c636d0d7552b51855a611ba1c
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/nghttp2.git
url = https://github.com/frida-forks/nghttp2.git
revision = 3e516202a0df939d6ab6bab47780fa35c5c4662b
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/ngtcp2.git
url = https://github.com/frida-forks/ngtcp2.git
revision = 0009518d5ac5e4ce80343fb5f3c719991ffd47af
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/openssl.git
url = https://github.com/frida-forks/openssl.git
revision = a94348d1cd4fb009e7c590fe4e193c2ea89f5ab8
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/selinux.git
url = https://github.com/frida-forks/selinux.git
revision = 507985d931eb1f0227218d5d845634e602426c20
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/termux-elf-cleaner.git
url = https://github.com/frida-forks/termux-elf-cleaner.git
revision = 85ed0baf1ce6abc1bf2cde92c808cc853d0bf791
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/usrsctp.git
url = https://github.com/frida-forks/usrsctp.git
revision = bfd359be44adc76c55efacce9a2f71ff0a7f8cd4
depth = 1
+3
View File
@@ -12,6 +12,9 @@ test_sources = [
if host_os_family == 'darwin'
test_sources += 'test-host-session-darwin-glue.m'
endif
if host_os_family == 'linux'
test_sources += 'test-host-session-linux-glue.c'
endif
test_vala_args = []
test_depends = []
+53
View File
@@ -0,0 +1,53 @@
#include <glib.h>
#ifdef HAVE_LINUX
#include <signal.h>
static void frida_host_session_test_linux_on_sigchld (int sig);
static struct sigaction frida_host_session_test_linux_saved_sigchld_action;
void
frida_host_session_test_linux_install_sigchld_handler (void)
{
struct sigaction action = { 0, };
action.sa_handler = frida_host_session_test_linux_on_sigchld;
sigemptyset (&action.sa_mask);
action.sa_flags = SA_RESTART;
sigaction (SIGCHLD, &action, &frida_host_session_test_linux_saved_sigchld_action);
}
gboolean
frida_host_session_test_linux_sigchld_handler_is_installed (void)
{
struct sigaction action;
sigaction (SIGCHLD, NULL, &action);
return action.sa_handler == frida_host_session_test_linux_on_sigchld;
}
void
frida_host_session_test_linux_reset_sigchld_handler (void)
{
struct sigaction action = { 0, };
action.sa_handler = SIG_DFL;
sigemptyset (&action.sa_mask);
sigaction (SIGCHLD, &action, &frida_host_session_test_linux_saved_sigchld_action);
}
void
frida_host_session_test_linux_restore_sigchld_handler (void)
{
sigaction (SIGCHLD, &frida_host_session_test_linux_saved_sigchld_action, NULL);
}
static void
frida_host_session_test_linux_on_sigchld (int sig)
{
}
#endif
+119
View File
@@ -113,6 +113,16 @@ namespace Frida.HostSessionTest {
h.run ();
});
GLib.Test.add_func ("/HostSession/Linux/spawn-should-not-replace-sigchld-handler", () => {
var h = new Harness ((h) => Linux.spawn_should_not_replace_sigchld_handler.begin (h as Harness));
h.run ();
});
GLib.Test.add_func ("/HostSession/Linux/spawned-process-should-be-reaped-after-sigchld-reset", () => {
var h = new Harness ((h) => Linux.spawned_process_should_be_reaped_after_sigchld_reset.begin (h as Harness));
h.run ();
});
GLib.Test.add_func ("/HostSession/Linux/ChildGating/fork", () => {
var h = new Harness ((h) => Linux.fork.begin (h as Harness));
h.run ();
@@ -1742,6 +1752,81 @@ namespace Frida.HostSessionTest {
h.done ();
}
private async void spawn_should_not_replace_sigchld_handler (Harness h) {
if (!can_run_sigchld_scenario ()) {
h.done ();
return;
}
var backend = new LinuxHostSessionBackend ();
var prov = yield h.setup_local_backend (backend);
install_sigchld_handler ();
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (new NullHostSessionHub (), null, (status, progress) => {}, cancellable);
var pid = yield host_session.spawn (Frida.Test.Labrats.path_to_executable ("sleeper"), HostSpawnOptions (),
cancellable);
yield host_session.resume (pid, cancellable);
assert_true (sigchld_handler_is_installed ());
yield host_session.kill (pid, cancellable);
var reaped = yield wait_until_reaped (pid);
assert_true (reaped);
assert_true (sigchld_handler_is_installed ());
} catch (GLib.Error e) {
printerr ("Unexpected error: %s\n", e.message);
assert_not_reached ();
}
restore_sigchld_handler ();
yield h.teardown_backend (backend);
h.done ();
}
private async void spawned_process_should_be_reaped_after_sigchld_reset (Harness h) {
if (!can_run_sigchld_scenario ()) {
h.done ();
return;
}
var backend = new LinuxHostSessionBackend ();
var prov = yield h.setup_local_backend (backend);
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (new NullHostSessionHub (), null, (status, progress) => {}, cancellable);
var pid = yield host_session.spawn (Frida.Test.Labrats.path_to_executable ("sleeper"), HostSpawnOptions (),
cancellable);
yield host_session.resume (pid, cancellable);
/* As libuv does once its last child has exited. */
reset_sigchld_handler ();
yield host_session.kill (pid, cancellable);
var reaped = yield wait_until_reaped (pid);
assert_true (reaped);
} catch (GLib.Error e) {
printerr ("Unexpected error: %s\n", e.message);
assert_not_reached ();
}
restore_sigchld_handler ();
yield h.teardown_backend (backend);
h.done ();
}
private async void fork (Harness h) {
yield Unix.run_fork_scenario (h, Frida.Test.Labrats.path_to_executable ("forker"));
}
@@ -1758,6 +1843,40 @@ namespace Frida.HostSessionTest {
yield Unix.run_exec_scenario (h, Frida.Test.Labrats.path_to_executable ("spawner"), "spawn-bad-then-good-path", "execv");
}
private bool can_run_sigchld_scenario () {
if (!GLib.Test.slow () && (
Frida.Test.os () == Frida.Test.OS.ANDROID ||
Frida.Test.os_arch_suffix () == "-linux-armbe8" ||
Frida.Test.os_arch_suffix () == "-linux-arm64be"
)) {
stdout.printf ("<skipping, run in slow mode> ");
return false;
}
if (!PidFileDescriptor.is_supported ()) {
stdout.printf ("<skipping, kernel lacks pidfd support> ");
return false;
}
return true;
}
private async bool wait_until_reaped (uint pid) {
var timer = new Timer ();
while (FileUtils.test ("/proc/%u".printf (pid), EXISTS)) {
if (timer.elapsed () >= 5.0)
return false;
Timeout.add (50, wait_until_reaped.callback);
yield;
}
return true;
}
private extern void install_sigchld_handler ();
private extern bool sigchld_handler_is_installed ();
private extern void reset_sigchld_handler ();
private extern void restore_sigchld_handler ();
namespace Manual {
private async void spawn_android_app (Harness h) {
+48 -1
View File
@@ -22,6 +22,10 @@ namespace Frida.InjectorTest {
GLib.Test.add_func ("/Injector/resource-leaks", test_resource_leaks);
#if LINUX
GLib.Test.add_func ("/Injector/Linux/inject-with-non-utf8-mapping", test_injection_with_non_utf8_mapping);
#endif
#if DARWIN
GLib.Test.add_func ("/Injector/suspended-injection-current-arch", () => {
test_suspended_injection (Frida.Test.Arch.CURRENT);
@@ -149,6 +153,45 @@ namespace Frida.InjectorTest {
rat.close ();
}
#if LINUX
private void test_injection_with_non_utf8_mapping () {
var logfile = File.new_for_path (Frida.Test.path_to_temporary_file ("non-utf8-mapping.log"));
try {
logfile.delete ();
} catch (GLib.Error delete_error) {
}
var envp = new string[] {
"FRIDA_LABRAT_LOGFILE=" + logfile.get_path ()
};
var sleeper = File.new_for_path (Frida.Test.Labrats.path_to_executable ("sleeper"));
var sleeper_copy = File.new_for_path (Frida.Test.path_to_temporary_file ("sleeper-\xff"));
try {
sleeper.copy (sleeper_copy, OVERWRITE);
} catch (GLib.Error e) {
assert_not_reached ();
}
FileUtils.chmod (sleeper_copy.get_path (), 0700);
var rat = new Labrat.from_path (sleeper_copy.get_path (), envp);
rat.inject ("simple-agent", "");
rat.wait_for_uninject ();
assert_true (content_of (logfile) == ">m<");
try {
rat.process.kill ();
logfile.delete ();
sleeper_copy.delete ();
} catch (GLib.Error e) {
assert_not_reached ();
}
rat.close ();
}
#endif
#if DARWIN
private void test_suspended_injection (Frida.Test.Arch arch) {
var logfile = File.new_for_path (Frida.Test.path_to_temporary_file ("suspended-injection.log"));
@@ -193,8 +236,12 @@ namespace Frida.InjectorTest {
private PendingUninject? pending_uninject;
public Labrat (string name, string[] envp, Frida.Test.Arch arch = Frida.Test.Arch.CURRENT) {
this.from_path (Frida.Test.Labrats.path_to_executable (name), envp, arch);
}
public Labrat.from_path (string path, string[] envp, Frida.Test.Arch arch = Frida.Test.Arch.CURRENT) {
try {
process = Frida.Test.Process.start (Frida.Test.Labrats.path_to_executable (name), null, envp, arch);
process = Frida.Test.Process.start (path, null, envp, arch);
} catch (Error e) {
printerr ("\nFAIL: %s\n\n", e.message);
assert_not_reached ();