Skip to content

Commit

Permalink
fruity: Avoid USB access in case of kernel NCM
Browse files Browse the repository at this point in the history
So we won't run into permission issues, and avoid unneccessary work.

Co-authored-by: Håvard Sørbø <[email protected]>
  • Loading branch information
oleavr and hsorbo committed Nov 26, 2024
1 parent ef4fa0d commit e149f5e
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 107 deletions.
122 changes: 59 additions & 63 deletions src/fruity/device-monitor.vala
Original file line number Diff line number Diff line change
Expand Up @@ -993,68 +993,66 @@ namespace Frida.Fruity {
}

private async void handle_usb_device_arrival (LibUSB.Device raw_device) {
string? udid = null;
char serial[LibUSB.DEVICE_STRING_BYTES_MAX + 1];
var res = raw_device.get_device_string (SERIAL_NUMBER, serial);
if (res >= LibUSB.Error.SUCCESS) {
serial[res] = '\0';
udid = (string) serial;
if (udid.length == 24)
udid = udid[:8] + "-" + udid[8:];

var transport = usb_transports.first_match (t => t.udid == udid);
if (transport != null) {
if (!transport.try_complete_modeswitch (raw_device))
transport = null;
}
UsbDevice usb_device;
try {
usb_device = new UsbDevice (raw_device, this);
} catch (Error e) {
return;
}

if (transport == null) {
transport = new PortableCoreDeviceUsbTransport (this, raw_device, udid, pairing_store);
usb_transports.add (transport);
unowned string udid = usb_device.udid;
var transport = usb_transports.first_match (t => t.udid == udid);
if (transport != null) {
if (!transport.try_complete_modeswitch (raw_device))
transport = null;
}

if (state != STARTING) {
uint delays[] = { 0, 50, 250 };
for (uint attempts = 0; attempts != delays.length; attempts++) {
uint delay = delays[attempts];
if (delay != 0) {
var timeout_source = new TimeoutSource (delay);
timeout_source.set_callback (handle_usb_device_arrival.callback);
timeout_source.attach (main_context);
if (transport == null) {
transport = new PortableCoreDeviceUsbTransport (this, usb_device, pairing_store);
usb_transports.add (transport);

if (state != STARTING) {
uint delays[] = { 0, 50, 250 };
for (uint attempts = 0; attempts != delays.length; attempts++) {
uint delay = delays[attempts];
if (delay != 0) {
var timeout_source = new TimeoutSource (delay);
timeout_source.set_callback (handle_usb_device_arrival.callback);
timeout_source.attach (main_context);

var cancel_source = new CancellableSource (io_cancellable);
cancel_source.set_callback (handle_usb_device_arrival.callback);
cancel_source.attach (main_context);
var cancel_source = new CancellableSource (io_cancellable);
cancel_source.set_callback (handle_usb_device_arrival.callback);
cancel_source.attach (main_context);

yield;
yield;

cancel_source.destroy ();
timeout_source.destroy ();
cancel_source.destroy ();
timeout_source.destroy ();

if (io_cancellable.is_cancelled ())
break;
}
if (io_cancellable.is_cancelled ())
break;
}

try {
yield transport.open (io_cancellable);
try {
yield transport.open (io_cancellable);
break;
} catch (GLib.Error e) {
// We might still be waiting for a udev rule to run...
if (!(e is Error.PERMISSION_DENIED))
break;
} catch (GLib.Error e) {
// We might still be waiting for a udev rule to run...
if (!(e is Error.PERMISSION_DENIED))
break;
}
}
}

transport_attached (transport);
}

transport_attached (transport);
}

if (AtomicUint.dec_and_test (ref pending_usb_device_arrivals) && state == STARTING)
usb_started.resolve (true);
}

private async void handle_usb_device_departure (LibUSB.Device raw_device) {
var transport = usb_transports.first_match (t => t.raw_device == raw_device && !t.modeswitch_in_progress);
var transport = usb_transports.first_match (t => t.usb_device.raw_device == raw_device && !t.modeswitch_in_progress);
if (transport == null)
return;

Expand Down Expand Up @@ -1185,9 +1183,9 @@ namespace Frida.Fruity {
}

private sealed class PortableCoreDeviceUsbTransport : Object, Transport {
public LibUSB.Device raw_device {
public UsbDevice usb_device {
get {
return _raw_device;
return _usb_device;
}
}

Expand All @@ -1199,7 +1197,7 @@ namespace Frida.Fruity {

public string udid {
get {
return _udid;
return _usb_device.udid;
}
}

Expand Down Expand Up @@ -1232,26 +1230,23 @@ namespace Frida.Fruity {
}
}

private weak PortableCoreDeviceBackend parent;
private LibUSB.Device _raw_device;
private string _udid;
private unowned PortableCoreDeviceBackend parent;
private UsbDevice _usb_device;
private string? _name;

private Promise<UsbDevice>? device_request;
private Promise<LibUSB.Device>? modeswitch_request;
private Promise<Tunnel?>? tunnel_request;
private NcmPeer? ncm_peer;

public PortableCoreDeviceUsbTransport (PortableCoreDeviceBackend parent, LibUSB.Device raw_device, string udid,
PairingStore store) {
public PortableCoreDeviceUsbTransport (PortableCoreDeviceBackend parent, UsbDevice device, PairingStore store) {
Object (pairing_store: store);

this.parent = parent;
_raw_device = raw_device;
_udid = udid;
_usb_device = device;

char product[LibUSB.DEVICE_STRING_BYTES_MAX + 1];
var res = raw_device.get_device_string (PRODUCT, product);
var res = device.raw_device.get_device_string (PRODUCT, product);
if (res >= LibUSB.Error.SUCCESS) {
product[res] = '\0';
_name = (string) product;
Expand All @@ -1271,11 +1266,11 @@ namespace Frida.Fruity {
device_request = new Promise<UsbDevice> ();

try {
var device = yield UsbDevice.open (_raw_device, parent, cancellable);
if (NcmPeer.detect_ncm_ifaddrs_on_system (_usb_device).is_empty && parent.modeswitch_allowed) {
_usb_device.ensure_open ();

if (parent.modeswitch_allowed) {
modeswitch_request = new Promise<LibUSB.Device> ();
if (yield device.maybe_modeswitch (cancellable)) {
if (yield _usb_device.maybe_modeswitch (cancellable)) {
var source = new TimeoutSource.seconds (2);
source.set_callback (() => {
if (modeswitch_request != null) {
Expand All @@ -1286,21 +1281,22 @@ namespace Frida.Fruity {
});
source.attach (MainContext.get_thread_default ());

LibUSB.Device raw_device = null;
try {
_raw_device = yield modeswitch_request.future.wait_async (cancellable);
raw_device = yield modeswitch_request.future.wait_async (cancellable);
} finally {
source.destroy ();
}

device = yield UsbDevice.open (_raw_device, parent, cancellable);
_usb_device = new UsbDevice (raw_device, parent);
} else {
modeswitch_request = null;
}
}

device_request.resolve (device);
device_request.resolve (_usb_device);

return device;
return _usb_device;
} catch (GLib.Error e) {
device_request.reject (e);
device_request = null;
Expand Down Expand Up @@ -1423,7 +1419,7 @@ namespace Frida.Fruity {
return yield establish_using_our_driver (usb_device, cancellable);
}

private static Gee.List<InetSocketAddress> detect_ncm_ifaddrs_on_system (UsbDevice usb_device) throws Error {
public static Gee.List<InetSocketAddress> detect_ncm_ifaddrs_on_system (UsbDevice usb_device) throws Error {
var device_ifaddrs = new Gee.ArrayList<InetSocketAddress> ();

#if LINUX
Expand Down
7 changes: 5 additions & 2 deletions src/fruity/ncm.vala
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ namespace Frida.Fruity {
}

private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
device.ensure_open ();

unowned LibUSB.Device raw_device = device.raw_device;
unowned LibUSB.DeviceHandle handle = device.handle;

Expand Down Expand Up @@ -126,9 +128,10 @@ namespace Frida.Fruity {
if (!found_cdc_header || !found_data_interface)
throw new Error.NOT_SUPPORTED ("%s", make_user_error_message ("No USB CDC-NCM interface found"));

var language_id = yield device.query_default_language_id (cancellable);

uint8 mac_address[6];
string mac_address_str = yield device.read_string_descriptor (mac_address_index, device.default_language_id,
cancellable);
string mac_address_str = yield device.read_string_descriptor (mac_address_index, language_id, cancellable);
if (mac_address_str.length != 12)
throw new Error.PROTOCOL ("Invalid MAC address");
for (uint i = 0; i != 6; i++) {
Expand Down
69 changes: 27 additions & 42 deletions src/fruity/usb.vala
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[CCode (gir_namespace = "FridaFruity", gir_version = "1.0")]
namespace Frida.Fruity {
internal sealed class UsbDevice : Object, AsyncInitable {
internal sealed class UsbDevice : Object {
public string udid {
get;
construct;
}

public LibUSB.Device? raw_device {
get {
return _raw_device;
Expand All @@ -18,22 +23,8 @@ namespace Frida.Fruity {
}
}

public string udid {
get {
return _udid;
}
}

public uint16 default_language_id {
get {
return _default_language_id;
}
}

private LibUSB.Device? _raw_device;
private LibUSB.DeviceHandle? _handle;
private string _udid;
private uint16 _default_language_id;
private uint num_pending_operations;
private Promise<bool>? pending_operations_completed;

Expand All @@ -45,38 +36,24 @@ 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, UsbDeviceBackend backend, Cancellable? cancellable = null)
throws Error, IOError {
var device = new UsbDevice (raw_device, backend);
public UsbDevice (LibUSB.Device raw_device, UsbDeviceBackend backend) throws Error {
char serial[LibUSB.DEVICE_STRING_BYTES_MAX + 1];
var res = raw_device.get_device_string (SERIAL_NUMBER, serial);
Usb.check (res, "Failed to get serial number");
serial[res] = '\0';

try {
yield device.init_async (Priority.DEFAULT, cancellable);
} catch (GLib.Error e) {
throw_api_error (e);
}
Object (
udid: udid_from_serial_number ((string) serial),
backend: backend
);

return device;
}

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

private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
public void ensure_open (Cancellable? cancellable = null) throws Error {
if (_handle != null)
return;
Usb.check (_raw_device.open (out _handle), "Failed to open USB device");

Bytes language_ids_response = yield read_string_descriptor_bytes (0, 0, cancellable);
if (language_ids_response.get_size () < sizeof (uint16))
throw new Error.PROTOCOL ("Invalid language IDs response");
Buffer language_ids = new Buffer (language_ids_response, LITTLE_ENDIAN);
_default_language_id = language_ids.read_uint16 (0);

var dev_desc = LibUSB.DeviceDescriptor (_raw_device);
string serial_number = yield read_string_descriptor (dev_desc.iSerialNumber, _default_language_id, cancellable);
_udid = udid_from_serial_number (serial_number);

return true;
}

public async void close (Cancellable? cancellable) throws IOError {
Expand Down Expand Up @@ -134,10 +111,18 @@ namespace Frida.Fruity {

private static string udid_from_serial_number (string serial) {
if (serial.length == 24)
return serial.substring (0, 8) + "-" + serial.substring (8);
return serial[:8] + "-" + serial[8:];
return serial;
}

public async uint16 query_default_language_id (Cancellable? cancellable) throws Error, IOError {
Bytes language_ids_response = yield read_string_descriptor_bytes (0, 0, cancellable);
if (language_ids_response.get_size () < sizeof (uint16))
throw new Error.PROTOCOL ("Invalid language IDs response");
Buffer language_ids = new Buffer (language_ids_response, LITTLE_ENDIAN);
return language_ids.read_uint16 (0);
}

public async string read_string_descriptor (uint8 index, uint16 language_id, Cancellable? cancellable)
throws Error, IOError {
var response = yield read_string_descriptor_bytes (index, language_id, cancellable);
Expand Down

0 comments on commit e149f5e

Please sign in to comment.