Skip to content

Commit

Permalink
fruity: Fix teardown when using our NCM driver
Browse files Browse the repository at this point in the history
Co-authored-by: Håvard Sørbø <[email protected]>
  • Loading branch information
oleavr and hsorbo committed Sep 17, 2024
1 parent 0fd255d commit 2d99088
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 19 deletions.
94 changes: 82 additions & 12 deletions src/fruity/device-monitor.vala
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ namespace Frida.Fruity {
}
}

private sealed class PortableCoreDeviceBackend : Object, Backend {
private sealed class PortableCoreDeviceBackend : Object, Backend, UsbDeviceBackend {
public bool supports_modeswitch {
get {
return LibUSB.has_capability (HAS_HOTPLUG) != 0;
Expand All @@ -719,6 +719,7 @@ namespace Frida.Fruity {

private Gee.Set<PortableCoreDeviceUsbTransport> usb_transports = new Gee.HashSet<PortableCoreDeviceUsbTransport> ();
private Promise<bool> usb_started = new Promise<bool> ();
private Promise<bool> usb_stopped = new Promise<bool> ();
private bool modeswitch_allowed = false;
private Promise<bool>? modeswitch_activated;
private Gee.Set<string>? modeswitch_udids_pending;
Expand All @@ -731,6 +732,7 @@ namespace Frida.Fruity {
private Gee.Map<uint32, LibUSB.Device> polled_usb_devices = new Gee.HashMap<uint32, LibUSB.Device> ();
private Source? polled_usb_timer;
private uint polled_usb_outdated = 0;
private Gee.Set<unowned PendingUsbOperation> pending_usb_ops = new Gee.HashSet<unowned PendingUsbOperation> ();

private PairingBrowser network_browser = PairingBrowser.make_default ();
private Gee.Map<string, PortableCoreDeviceNetworkTransport> network_transports =
Expand All @@ -746,6 +748,7 @@ namespace Frida.Fruity {
CREATED,
STARTING,
STARTED,
FLUSHING,
STOPPING,
STOPPED,
}
Expand Down Expand Up @@ -777,7 +780,7 @@ namespace Frida.Fruity {
}

public async void stop (Cancellable? cancellable) throws IOError {
state = STOPPING;
state = FLUSHING;

io_cancellable.cancel ();

Expand All @@ -790,18 +793,19 @@ namespace Frida.Fruity {
yield transport.close (cancellable);
network_transports.clear ();

usb_worker.join ();
usb_worker = null;

if (polled_usb_timer != null) {
polled_usb_timer.destroy ();
polled_usb_timer = null;
}

foreach (var transport in usb_transports.to_array ())
yield transport.close (cancellable);
usb_transports.clear ();

try {
yield usb_stopped.future.wait_async (cancellable);
} catch (Error e) {
assert_not_reached ();
}

usb_worker.join ();
usb_worker = null;

usb_context = null;

state = STOPPED;
Expand Down Expand Up @@ -833,13 +837,15 @@ namespace Frida.Fruity {
if (LibUSB.Context.init (out usb_context) != SUCCESS) {
schedule_on_frida_thread (() => {
usb_started.resolve (true);
usb_stopped.resolve (true);
return Source.REMOVE;
});
return;
}

AtomicUint.inc (ref pending_usb_device_arrivals);

bool callbacks_registered = true;
if (LibUSB.has_capability (HAS_HOTPLUG) != 0) {
usb_context.hotplug_register_callback (DEVICE_ARRIVED | DEVICE_LEFT, ENUMERATE, VENDOR_ID_APPLE,
PRODUCT_ID_IPHONE, LibUSB.HotPlugEvent.MATCH_ANY, on_usb_hotplug_event, out iphone_callback);
Expand Down Expand Up @@ -871,7 +877,30 @@ namespace Frida.Fruity {

if (AtomicUint.compare_and_exchange (ref polled_usb_outdated, 1, 0))
refresh_polled_usb_devices ();

if (state == FLUSHING) {
if (callbacks_registered) {
usb_context.hotplug_deregister_callback (iphone_callback);
usb_context.hotplug_deregister_callback (ipad_callback);
callbacks_registered = false;
}

if (polled_usb_timer != null) {
polled_usb_timer.destroy ();
polled_usb_timer = null;
}

lock (pending_usb_ops) {
if (pending_usb_ops.is_empty)
state = STOPPING;
}
}
}

schedule_on_frida_thread (() => {
usb_stopped.resolve (true);
return Source.REMOVE;
});
}

private int on_usb_hotplug_event (LibUSB.Context ctx, LibUSB.Device device, LibUSB.HotPlugEvent event) {
Expand Down Expand Up @@ -922,7 +951,7 @@ namespace Frida.Fruity {
}

try {
device = yield UsbDevice.open (raw_device, io_cancellable);
device = yield UsbDevice.open (raw_device, this, io_cancellable);

if (modeswitch_allowed && yield device.maybe_modeswitch (io_cancellable))
break;
Expand Down Expand Up @@ -989,6 +1018,19 @@ namespace Frida.Fruity {
polled_usb_devices = current_devices;
}

private UsbOperation allocate_usb_operation () {
var op = new PendingUsbOperation (this);
lock (pending_usb_ops)
pending_usb_ops.add (op);
return op;
}

private void on_usb_operation_complete (PendingUsbOperation op) {
lock (pending_usb_ops)
pending_usb_ops.remove (op);
usb_context.interrupt_event_handler ();
}

private static uint32 make_usb_device_id (LibUSB.Device device, LibUSB.DeviceDescriptor desc) {
return ((uint32) device.get_port_number () << 24) |
((uint32) device.get_device_address () << 16) |
Expand Down Expand Up @@ -1017,6 +1059,34 @@ namespace Frida.Fruity {
source.set_callback ((owned) function);
source.attach (main_context);
}

private class PendingUsbOperation : Object, UsbOperation {
public LibUSB.Transfer transfer {
get {
return _transfer;
}
}

private weak PortableCoreDeviceBackend backend;
private LibUSB.Transfer _transfer;

public PendingUsbOperation (PortableCoreDeviceBackend backend) {
this.backend = backend;
}

construct {
_transfer = new LibUSB.Transfer ();
}

public override void dispose () {
if (_transfer != null) {
_transfer = null;
backend.on_usb_operation_complete (this);
}

base.dispose ();
}
}
}

private sealed class PortableCoreDeviceUsbTransport : Object, Transport {
Expand Down Expand Up @@ -1081,7 +1151,7 @@ namespace Frida.Fruity {

ncm_peer = null;

usb_device.close ();
yield usb_device.close (cancellable);
}

public async Tunnel? find_tunnel (UsbmuxDevice? device, Cancellable? cancellable) throws Error, IOError {
Expand Down
58 changes: 51 additions & 7 deletions src/fruity/usb.vala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ namespace Frida.Fruity {
}
}

public UsbDeviceBackend backend {
get;
construct;
}

public LibUSB.DeviceHandle? handle {
get {
return _handle;
Expand All @@ -29,6 +34,8 @@ namespace Frida.Fruity {
private LibUSB.DeviceHandle? _handle;
private string _udid;
private uint16 _default_language_id;
private uint num_pending_operations;
private Promise<bool>? pending_operations_completed;

private enum AppleSpecificRequest {
GET_MODE = 0x45,
Expand All @@ -38,8 +45,9 @@ namespace Frida.Fruity {
private const string MODE_INITIAL_UNTETHERED = "3:3:3:0"; // => 5:3:3:0
private const string MODE_INITIAL_TETHERED = "4:4:3:4"; // => 5:4:3:4

public static async UsbDevice open (LibUSB.Device raw_device, Cancellable? cancellable = null) throws Error, IOError {
var device = new UsbDevice (raw_device);
public static async UsbDevice open (LibUSB.Device raw_device, UsbDeviceBackend backend, Cancellable? cancellable = null)
throws Error, IOError {
var device = new UsbDevice (raw_device, backend);

try {
yield device.init_async (Priority.DEFAULT, cancellable);
Expand All @@ -50,8 +58,8 @@ namespace Frida.Fruity {
return device;
}

private UsbDevice (LibUSB.Device raw_device) {
Object ();
private UsbDevice (LibUSB.Device raw_device, UsbDeviceBackend backend) {
Object (backend: backend);
_raw_device = raw_device;
}

Expand All @@ -71,7 +79,17 @@ namespace Frida.Fruity {
return true;
}

public void close () {
public async void close (Cancellable? cancellable) throws IOError {
if (num_pending_operations != 0) {
pending_operations_completed = new Promise<bool> ();
try {
yield pending_operations_completed.future.wait_async (cancellable);
} catch (Error e) {
assert_not_reached ();
}
pending_operations_completed = null;
}

_handle = null;
_raw_device = null;
}
Expand Down Expand Up @@ -174,7 +192,8 @@ namespace Frida.Fruity {

public async Bytes control_transfer (uint8 request_type, uint8 request, uint16 val, uint16 index, uint16 length,
uint timeout, Cancellable? cancellable) throws Error, IOError {
var transfer = new LibUSB.Transfer ();
var op = backend.allocate_usb_operation ();
unowned LibUSB.Transfer transfer = op.transfer;
var ready_closure = new TransferReadyClosure (control_transfer.callback);

var buffer = new uint8[sizeof (LibUSB.ControlSetup) + length];
Expand All @@ -190,7 +209,9 @@ namespace Frida.Fruity {

try {
Usb.check (transfer.submit (), "Failed to submit control transfer");
on_operation_started ();
yield;
on_operation_ended ();
} finally {
cancel_source.destroy ();
}
Expand All @@ -202,7 +223,8 @@ namespace Frida.Fruity {

public async size_t bulk_transfer (uint8 endpoint, uint8[] buffer, uint timeout, Cancellable? cancellable)
throws Error, IOError {
var transfer = new LibUSB.Transfer ();
var op = backend.allocate_usb_operation ();
unowned LibUSB.Transfer transfer = op.transfer;
var ready_closure = new TransferReadyClosure (bulk_transfer.callback);

transfer.fill_bulk_transfer (_handle, endpoint, buffer, on_transfer_ready, ready_closure, timeout);
Expand All @@ -216,7 +238,9 @@ namespace Frida.Fruity {

try {
Usb.check (transfer.submit (), "Failed to submit bulk transfer");
on_operation_started ();
yield;
on_operation_ended ();
} finally {
cancel_source.destroy ();
}
Expand All @@ -226,6 +250,16 @@ namespace Frida.Fruity {
return transfer.actual_length;
}

private void on_operation_started () {
num_pending_operations++;
}

private void on_operation_ended () {
num_pending_operations--;
if (num_pending_operations == 0 && pending_operations_completed != null)
pending_operations_completed.resolve (true);
}

private static void on_transfer_ready (LibUSB.Transfer transfer) {
TransferReadyClosure * closure = transfer.user_data;
closure->schedule ();
Expand All @@ -252,6 +286,16 @@ namespace Frida.Fruity {
}
}

internal interface UsbDeviceBackend : Object {
public abstract UsbOperation allocate_usb_operation ();
}

internal interface UsbOperation : Object {
public abstract LibUSB.Transfer transfer {
get;
}
}

namespace Usb {
internal static void check (LibUSB.Error error, string prefix) throws Error {
if (error >= LibUSB.Error.SUCCESS)
Expand Down

0 comments on commit 2d99088

Please sign in to comment.