Skip to content

Commit

Permalink
fruity: Fix direct channel reliability
Browse files Browse the repository at this point in the history
Where we might establish the HostSession connection through usbmuxd, and
proceed to establish direct connections through the tunnel. This is
usually fine, except when an Apple service happens to bind to the same
port inside the tunnel. In that case we'd end up talking to that instead
of the broker port, which is listening on INADDR_ANY.

Kudos to @mrmacete for helping track this one down.

Co-authored-by: Håvard Sørbø <[email protected]>
  • Loading branch information
oleavr and hsorbo committed Nov 26, 2024
1 parent b32408c commit 1bd9d38
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 72 deletions.
150 changes: 88 additions & 62 deletions src/fruity/device-monitor.vala
Original file line number Diff line number Diff line change
Expand Up @@ -324,87 +324,96 @@ namespace Frida.Fruity {
unowned string location = tokens[1];

if (protocol == "tcp") {
var tunnel = yield find_tunnel (cancellable);

uint16 port;
ulong raw_port;
if (ulong.try_parse (location, out raw_port)) {
if (raw_port == 0 || raw_port > uint16.MAX)
throw new Error.INVALID_ARGUMENT ("Invalid TCP port");
port = (uint16) raw_port;
} else {
if (tunnel == null)
throw new Error.NOT_SUPPORTED ("Unable to resolve port name; tunnel not available");
var service_info = tunnel.discovery.get_service (location);
port = service_info.port;
}

Error? pending_error = null;

if (tunnel != null) {
try {
return yield tunnel.open_tcp_connection (port, cancellable);
} catch (Error e) {
if (e is Error.SERVER_NOT_RUNNING)
pending_error = e;
else
throw e;
}
}
var channel = yield open_tcp_channel (location, ALLOW_ANY_TRANSPORT, cancellable);
return channel.stream;
}

var usbmux_device = find_usbmux_device ();
if (usbmux_device != null) {
if (usbmux_device.connection_type == USB) {
UsbmuxClient client = null;
try {
client = yield UsbmuxClient.open (cancellable);
if (protocol == "lockdown")
return yield open_lockdown_service (location, cancellable);

yield client.connect_to_port (usbmux_device.id, port, cancellable);
throw new Error.NOT_SUPPORTED ("Unsupported channel address");
}

return client.connection;
} catch (GLib.Error e) {
if (client != null)
client.close.begin ();
public async TcpChannel open_tcp_channel (string location, OpenTcpChannelFlags flags, Cancellable? cancellable)
throws Error, IOError {
var usbmux_device = find_usbmux_device ();
var tunnel = yield find_tunnel (cancellable);

if (e is UsbmuxError.CONNECTION_REFUSED)
throw new Error.SERVER_NOT_RUNNING ("%s", e.message);
uint16 port;
ulong raw_port;
if (ulong.try_parse (location, out raw_port)) {
if (raw_port == 0 || raw_port > uint16.MAX)
throw new Error.INVALID_ARGUMENT ("Invalid TCP port");
port = (uint16) raw_port;
} else {
if (tunnel == null)
throw new Error.NOT_SUPPORTED ("Unable to resolve port name; tunnel not available");
if ((flags & OpenTcpChannelFlags.ALLOW_TUNNEL) == 0)
throw new Error.NOT_SUPPORTED ("Connection to tunnel service not allowed by flags");
var service_info = tunnel.discovery.get_service (location);
port = service_info.port;
}

throw new Error.TRANSPORT ("%s", e.message);
}
}
Error? pending_error = null;

InetSocketAddress device_address = usbmux_device.network_address;
var target_address = (InetSocketAddress) Object.new (typeof (InetSocketAddress),
address: device_address.address,
port: port,
flowinfo: device_address.flowinfo,
scope_id: device_address.scope_id
);
if ((flags & OpenTcpChannelFlags.ALLOW_TUNNEL) != 0 && tunnel != null) {
try {
var stream = yield tunnel.open_tcp_connection (port, cancellable);
return new TcpChannel () { stream = stream, kind = TUNNEL };
} catch (Error e) {
if (e is Error.SERVER_NOT_RUNNING)
pending_error = e;
else
throw e;
}
}

var client = new SocketClient ();
if ((flags & OpenTcpChannelFlags.ALLOW_USBMUX) != 0 && usbmux_device != null) {
if (usbmux_device.connection_type == USB) {
UsbmuxClient client = null;
try {
var connection = yield client.connect_async (target_address, cancellable);
client = yield UsbmuxClient.open (cancellable);

Tcp.enable_nodelay (connection.socket);
yield client.connect_to_port (usbmux_device.id, port, cancellable);

return connection;
return new TcpChannel () { stream = client.connection, kind = USBMUX };
} catch (GLib.Error e) {
if (e is IOError.CONNECTION_REFUSED)
if (client != null)
client.close.begin ();

if (e is UsbmuxError.CONNECTION_REFUSED)
throw new Error.SERVER_NOT_RUNNING ("%s", e.message);

throw new Error.TRANSPORT ("%s", e.message);
}
}

if (pending_error != null)
throw pending_error;
throw new Error.TRANSPORT ("No viable transport found");
}
InetSocketAddress device_address = usbmux_device.network_address;
var target_address = (InetSocketAddress) Object.new (typeof (InetSocketAddress),
address: device_address.address,
port: port,
flowinfo: device_address.flowinfo,
scope_id: device_address.scope_id
);

if (protocol == "lockdown")
return yield open_lockdown_service (location, cancellable);
var client = new SocketClient ();
try {
var connection = yield client.connect_async (target_address, cancellable);

throw new Error.NOT_SUPPORTED ("Unsupported channel address");
Tcp.enable_nodelay (connection.socket);

return new TcpChannel () { stream = connection, kind = USBMUX };
} catch (GLib.Error e) {
if (e is IOError.CONNECTION_REFUSED)
throw new Error.SERVER_NOT_RUNNING ("%s", e.message);

throw new Error.TRANSPORT ("%s", e.message);
}
}

if (pending_error != null)
throw pending_error;
throw new Error.TRANSPORT ("No viable transport found");
}

private static int compare_transports (Transport a, Transport b) {
Expand Down Expand Up @@ -432,6 +441,23 @@ namespace Frida.Fruity {
}
}

public class TcpChannel {
public IOStream stream;
public Kind kind;

public enum Kind {
USBMUX,
TUNNEL
}
}

[Flags]
public enum OpenTcpChannelFlags {
ALLOW_USBMUX,
ALLOW_TUNNEL,
ALLOW_ANY_TRANSPORT = ALLOW_USBMUX | ALLOW_TUNNEL,
}

public interface Transport : Object {
public abstract ConnectionType connection_type {
get;
Expand Down
42 changes: 32 additions & 10 deletions src/fruity/fruity-host-session.vala
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,7 @@ namespace Frida {
var transport_broker = server.transport_broker;
if (transport_broker != null) {
try {
entry.connection = yield establish_direct_connection (transport_broker, remote_session_id, device,
entry.connection = yield establish_direct_connection (transport_broker, remote_session_id, server,
cancellable);
} catch (Error e) {
if (e is Error.NOT_SUPPORTED)
Expand Down Expand Up @@ -1087,10 +1087,10 @@ namespace Frida {

DBusConnection? connection = null;
try {
var stream = yield device.open_channel (
("tcp:%" + uint16.FORMAT_MODIFIER + "u").printf (DEFAULT_CONTROL_PORT),
cancellable);
var channel =
yield device.open_tcp_channel (DEFAULT_CONTROL_PORT.to_string (), ALLOW_ANY_TRANSPORT, cancellable);

IOStream stream = channel.stream;
WebServiceTransport transport = PLAIN;
string? origin = null;

Expand Down Expand Up @@ -1118,7 +1118,7 @@ namespace Frida {
if (connection.closed)
throw new Error.SERVER_NOT_RUNNING ("Unable to connect to remote frida-server");

var server = new RemoteServer (session, connection, flavor, transport_broker);
var server = new RemoteServer (flavor, session, connection, channel, device, transport_broker);
attach_remote_server (server);
current_remote_server = server;
last_server_check_timer = null;
Expand Down Expand Up @@ -1452,7 +1452,12 @@ namespace Frida {
}
}

private class RemoteServer : Object {
private class RemoteServer : Object, HostChannelProvider {
public Flavor flavor {
get;
construct;
}

public HostSession session {
get;
construct;
Expand All @@ -1463,7 +1468,12 @@ namespace Frida {
construct;
}

public Flavor flavor {
public Fruity.TcpChannel channel {
get;
construct;
}

public Fruity.Device device {
get;
construct;
}
Expand All @@ -1478,15 +1488,27 @@ namespace Frida {
set;
}

public RemoteServer (HostSession session, DBusConnection connection, Flavor flavor,
TransportBroker? transport_broker) {
public RemoteServer (Flavor flavor, HostSession session, DBusConnection connection, Fruity.TcpChannel channel,
Fruity.Device device, TransportBroker? transport_broker) {
Object (
flavor: flavor,
session: session,
connection: connection,
flavor: flavor,
channel: channel,
device: device,
transport_broker: transport_broker
);
}

public async IOStream open_channel (string address, Cancellable? cancellable) throws Error, IOError {
if (!address.has_prefix ("tcp:"))
throw new Error.NOT_SUPPORTED ("Unsupported channel address");
var flags = (channel.kind == TUNNEL)
? Fruity.OpenTcpChannelFlags.ALLOW_TUNNEL
: Fruity.OpenTcpChannelFlags.ALLOW_USBMUX;
var channel = yield device.open_tcp_channel (address[4:], flags, cancellable);
return channel.stream;
}
}
}

Expand Down

0 comments on commit 1bd9d38

Please sign in to comment.