Skip to content

Commit

Permalink
Fix endianness issues on USB mass storage CBW and CSW signatures.
Browse files Browse the repository at this point in the history
USB mass storage works by wrapping other storage protocols in USB
packets.  There are several defined, but universally the most common
one is SCSI.  While SCSI is big-endian, USB is little-endian.

The wrappers used in the USB mass storage protocol have some
"signature" (magic number) fields that are part of the USB protocol,
and hence little-endian.  Most OSs apparently don't check these
signatures, but some (such as FreeBSD) do.

The previous code encodes these signatures as big-endian.  This patch
fixes them to be little-endian.

(Technical background follows for the curious.  This is oriented to
SCSI under USB Bulk-Only mass storage protocol, which is by far the
predominant protocol used by USB mass storage devices, including the
UF2 bootloader here.)

A CBW (Command Block Wrapper) is a SCSI command (or other protocol,
but typically SCSI) embedded in a USB mass storage packet.  A CSW
(Command Status Wrapper) is a SCSI status (reply) embedded in a USB
mass storage packet.

The CBW/CSW mechanism embeds any SCSI protocol transaction.  The first
command is typically an INQUIRY command (which retrieves metadata
about the drive), but for simplicity, we'll talk about a READ command.

A typical "read" transaction is as folllows:

1. The host sends the device a SCSI read command (SCSI CDB block),
encapsulated in a USB mass storage CBW packet (which is further
encapsulated in USB bulk data packets, addressed to the device's read
endpoint).

2. If the device is able to fulfill the request, it sends the host the
requested data, with no encapsulation at the USB mass storage level.
(These packets are still encapsulated at the protocol layer, addressed
from the device's write endpoint).

3. If the device is unable to fulfill the request, it sends a USB
STALL packet (which is at the protocol level, lower than the mass
storage level).

4. Regardless of whether the device was able to fulfill the request or
not, it sends a SCSI status packet, encapsulated in a USB mass storage
CSW packet (which is further encapsulated in USB bulk data packets,
addressed from the device's write endpoint).

If everything works well, the host now can unambiguously tell whether
the command succeeded or failed, based on the presence / absence of
the STALL packet, and interprets the next packet as a SCSI status
packet (embedded in a CSW wrapper).

If everything isn't working well, the host and device can get out of
sync in these state transitions.  To ensure that everything is working
properly, every SCSI command or status is wrapped in a packet specific
to the USB mass storage protocol: a CBW for a command, or CSW for a
status (reply).

The CBW starts with the magic number 0x43425355 (little-endian,
"USBC"), followed by a tag used to identify this command.  The CSW
starts with the magic number 0x53425355 (little-endian, "USBS"),
followed by the tag of the command being responded to.

(The endianness of the tag is irrelevant, since it's only a four-byte
sequence that needs to be the same in the CSW as in the initiating
CBW.)

Since the CBW's and CSW's magic numbers are typically irrelevant, they
can usually be ignored.  Most OSs' implementations of the USB mass
storage protocol ignore them.  However, they serve as a good check to
ensure that the host and device are in sync; hence, some OSs (such as
FreeBSD) do validate them.

Since the CBW and CSW are defined as part of the USB spec, their
signatures are little-endian, even though the values in the wrapped
packets are big-endian.

As described above, these signatures are ignored by many OSs, but are
validated by FreeBSD.

I'm surprised that try_read_cbw ever worked if USE_MSC_CHECKS was
enabled.  I patched the code within USE_MSC_CHECKS, but haven't
actually tested it: I only tested the default build.
  • Loading branch information
piquan committed Oct 28, 2019
1 parent 84c32dc commit b68f3a1
Showing 1 changed file with 4 additions and 4 deletions.
8 changes: 4 additions & 4 deletions src/msc.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ void msc_reset(void) {
//! Structure to receive a CBW packet
static struct usb_msc_cbw udi_msc_cbw;
//! Structure to send a CSW packet
static struct usb_msc_csw udi_msc_csw = {.dCSWSignature = CPU_TO_BE32(USB_CSW_SIGNATURE)};
static struct usb_msc_csw udi_msc_csw = {.dCSWSignature = cpu_to_le32(USB_CSW_SIGNATURE)};
//! Structure with current SCSI sense data
static struct scsi_request_sense_data udi_msc_sense;

Expand Down Expand Up @@ -281,7 +281,7 @@ bool try_read_cbw(struct usb_msc_cbw *cbw, uint8_t ep, PacketBuffer *handoverCac
#if USE_MSC_CHECKS
// Check CBW integrity:
// transfer status/CBW length/CBW signature
if ((sizeof(*cbw) != nb_received) || (cbw->dCBWSignature != CPU_TO_BE32(USB_CBW_SIGNATURE))) {
if ((sizeof(*cbw) != nb_received) || (cbw->dCBWSignature != cpu_to_le32(USB_CBW_SIGNATURE))) {
if (handoverCache)
resetIntoBootloader();
// (5.2.1) Devices receiving a CBW with an invalid signature should
Expand Down Expand Up @@ -783,7 +783,7 @@ static void handover_flash(UF2_HandoverArgs *handover, PacketBuffer *handoverCac
static void process_handover_initial(UF2_HandoverArgs *handover, PacketBuffer *handoverCache,
WriteState *state) {
struct usb_msc_csw csw = {.dCSWTag = handover->cbw_tag,
.dCSWSignature = CPU_TO_BE32(USB_CSW_SIGNATURE),
.dCSWSignature = cpu_to_le32(USB_CSW_SIGNATURE),
.bCSWStatus = USB_CSW_STATUS_PASS,
.dCSWDataResidue = 0};
// write out the block passed from user space
Expand All @@ -807,7 +807,7 @@ static void process_handover(UF2_HandoverArgs *handover, PacketBuffer *handoverC
}

struct usb_msc_csw csw = {.dCSWTag = cbw.dCBWTag,
.dCSWSignature = CPU_TO_BE32(USB_CSW_SIGNATURE),
.dCSWSignature = cpu_to_le32(USB_CSW_SIGNATURE),
.bCSWStatus = USB_CSW_STATUS_PASS,
.dCSWDataResidue = le32_to_cpu(cbw.dCBWDataTransferLength)};

Expand Down

0 comments on commit b68f3a1

Please sign in to comment.