0

I want to share cdrom device over network.

On the client side I create root enumerated device (scsi bus). On the server side (where cdrom device resides) I replace device stack's FDO with my own (in other words - cdrom.sys is replaced by another driver).

Requests are redirected from client to server using windows sockets.

The format of data transferred over network (from client to server): USER_HEADER, USER_SCSI_REQUEST_BLOCK, [data to be transferred to device]

The format of data transferred over network (from server to client):

USER_HEADER, USER_SCSI_REQUEST_BLOCK, [data to be transferred from device / sense data]

The structures are defined as follows:

struct USER_HEADER
{
    ULONG Id;
    ULONG Length;
    ULONG MajorFunction;
    ULONG MinorFunction;
    ULONG IoControlCode;
    ULONG InputBufferLength;
    ULONG OutputBufferLength;
    NTSTATUS Status;
    ULONG Information;
};

struct USER_SCSI_REQUEST_BLOCK
{
    UCHAR                      Function;
    UCHAR                      SrbStatus;
    UCHAR                      ScsiStatus;
    UCHAR                      PathId;
    UCHAR                      TargetId;
    UCHAR                      Lun;
    UCHAR                      QueueTag;
    UCHAR                      QueueAction;
    UCHAR                      CdbLength;
    UCHAR                      SenseInfoBufferLength;
    ULONG                      SrbFlags;
    ULONG                      DataTransferLength;
    ULONG                      TimeOutValue;
    ULONG                      QueueSortKey;
    UCHAR                      Cdb[16];
};

Client side code to pack and unpack requests sent from cdrom.sys:

PVOID GetBuffer(MDL *pSourceMdl, MDL *pTargetMdl, PVOID pBuffer, ULONG Length, BOOLEAN *pUnmap)
{
    PVOID pBuffer2;

    if (pSourceMdl->MdlFlags & (MDL_MAPPED_TO_SYSTEM_VA | MDL_SOURCE_IS_NONPAGED_POOL))
    {
        pBuffer2 = (UCHAR*)pSourceMdl->MappedSystemVa + ((UCHAR*)pBuffer - ((UCHAR*)pSourceMdl->StartVa + pSourceMdl->ByteOffset));

        *pUnmap = FALSE;
    }
    else
    {
        IoBuildPartialMdl(pSourceMdl, pTargetMdl, pBuffer, Length);

        pBuffer2 = MmMapLockedPagesSpecifyCache(pTargetMdl, KernelMode, MmCached, NULL, FALSE, NormalPagePriority);

        *pUnmap = TRUE;
    }

    return pBuffer2;
}

void PackRequest(IRP *pIrp, USER_HEADER *pUserHeader, STORAGE_EXTENSION *pStorageExtension)
{
    BOOLEAN Unmap;
    PVOID pBuffer;
    IO_STACK_LOCATION *pStack;
    USER_SCSI_REQUEST_BLOCK *pUserSrb;
    SCSI_REQUEST_BLOCK *pSrb;

    pStack = IoGetCurrentIrpStackLocation(pIrp);

    pIrp->Tail.Overlay.DriverContext[0] = (PVOID)pStorageExtension->Id;
    pUserHeader->Id = pStorageExtension->Id;
    ++pStorageExtension->Id;

    pUserHeader->Status = 0;
    pUserHeader->Information = 0;

    pUserHeader->MajorFunction = pStack->MajorFunction;
    pUserHeader->MinorFunction = pStack->MinorFunction;

    if (pStack->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
    {
        pUserHeader->IoControlCode = 0;
        pUserHeader->InputBufferLength = 0;
        pUserHeader->OutputBufferLength = 0;
        pUserHeader->Length = sizeof(USER_HEADER) + sizeof(USER_SCSI_REQUEST_BLOCK);

        pUserSrb = (USER_SCSI_REQUEST_BLOCK*)((UCHAR*)pUserHeader + sizeof(USER_HEADER));
        pSrb = pStack->Parameters.Scsi.Srb;

        pUserSrb->Function = pSrb->Function;
        pUserSrb->SrbStatus = pSrb->SrbStatus;
        pUserSrb->ScsiStatus = pSrb->ScsiStatus;
        pUserSrb->PathId = pSrb->PathId;
        pUserSrb->TargetId = pSrb->TargetId;
        pUserSrb->Lun = pSrb->Lun;
        pUserSrb->QueueTag = pSrb->QueueTag;
        pUserSrb->QueueAction = pSrb->QueueAction;
        pUserSrb->CdbLength = pSrb->CdbLength;
        pUserSrb->SenseInfoBufferLength = pSrb->SenseInfoBufferLength;
        pUserSrb->SrbFlags = pSrb->SrbFlags;
        pUserSrb->DataTransferLength = pSrb->DataTransferLength;
        pUserSrb->TimeOutValue = pSrb->TimeOutValue;

        if ((pSrb->DataTransferLength) && (pSrb->SrbFlags & SRB_FLAGS_DATA_OUT))
        {
            pBuffer = GetBuffer(pIrp->MdlAddress, pStorageExtension->pMdl, pSrb->DataBuffer, pSrb->DataTransferLength, &Unmap);

            memcpy((UCHAR*)pUserSrb + sizeof(USER_SCSI_REQUEST_BLOCK), pBuffer, pSrb->DataTransferLength);

            if (Unmap) MmUnmapLockedPages(pBuffer, pStorageExtension->pMdl);

            pUserHeader->Length += pSrb->DataTransferLength;
        }

        pUserSrb->QueueSortKey = pSrb->QueueSortKey;
        memcpy(pUserSrb->Cdb, pSrb->Cdb, sizeof(pSrb->Cdb));
    }
    else
    {
        pUserHeader->IoControlCode = pStack->Parameters.DeviceIoControl.IoControlCode;
        pUserHeader->InputBufferLength = pStack->Parameters.DeviceIoControl.InputBufferLength;
        pUserHeader->OutputBufferLength = pStack->Parameters.DeviceIoControl.OutputBufferLength;

        pUserHeader->Length = sizeof(USER_HEADER);

        if ((pUserHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY) ||
            (pUserHeader->IoControlCode == IOCTL_STORAGE_ENABLE_IDLE_POWER))
        {
            pUserHeader->Length += pUserHeader->InputBufferLength;
            memcpy((UCHAR*)pUserHeader + sizeof(USER_HEADER), pIrp->AssociatedIrp.SystemBuffer, pUserHeader->InputBufferLength);
        }
        else if ((pUserHeader->IoControlCode != IOCTL_STORAGE_POWER_ACTIVE) &&
            (pUserHeader->IoControlCode != IOCTL_SCSI_GET_ADDRESS))
        {
            __debugbreak();
        }
    }
}

void UnpackRequest(USER_HEADER *pUserHeader, IRP *pIrp, STORAGE_EXTENSION *pStorageExtension)
{
    BOOLEAN Unmap;
    PVOID pBuffer;
    IO_STACK_LOCATION *pStack;
    USER_SCSI_REQUEST_BLOCK *pUserSrb;
    SCSI_REQUEST_BLOCK *pSrb;

    pStack = IoGetCurrentIrpStackLocation(pIrp);

    if (pUserHeader->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
    {
        pUserSrb = (USER_SCSI_REQUEST_BLOCK*)((UCHAR*)pUserHeader + sizeof(USER_HEADER));
        pSrb = pStack->Parameters.Scsi.Srb;

        pSrb->SrbStatus = pUserSrb->SrbStatus;
        pSrb->ScsiStatus = pUserSrb->ScsiStatus;

        pSrb->SenseInfoBufferLength = pUserSrb->SenseInfoBufferLength;
        pSrb->DataTransferLength = pUserSrb->DataTransferLength;

        if (NT_SUCCESS(pUserHeader->Status))
        {
            if ((pUserSrb->DataTransferLength) && (pUserSrb->SrbFlags & SRB_FLAGS_DATA_IN))
            {
                pBuffer = GetBuffer(pIrp->MdlAddress, pStorageExtension->pMdl, pSrb->DataBuffer, pUserSrb->DataTransferLength, &Unmap);

                memcpy(pBuffer, (UCHAR*)pUserSrb + sizeof(USER_SCSI_REQUEST_BLOCK), pUserSrb->DataTransferLength);

                if (Unmap) MmUnmapLockedPages(pBuffer, pStorageExtension->pMdl);
            }
            else
            {
                if (pUserSrb->Function == SRB_FUNCTION_CLAIM_DEVICE) pSrb->DataBuffer = pStack->DeviceObject;
            }
        }
        else
        {
            if ((pUserSrb->SenseInfoBufferLength) && (pUserSrb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID))
            {
                memcpy(pSrb->SenseInfoBuffer, (UCHAR*)pUserSrb + sizeof(USER_SCSI_REQUEST_BLOCK), pUserSrb->SenseInfoBufferLength);
            }
        }
    }
    else
    {
        if (NT_SUCCESS(pUserHeader->Status))
        {
            if ((pUserHeader->IoControlCode == IOCTL_SCSI_GET_ADDRESS) ||
                (pUserHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY))
            {
                memcpy(pIrp->AssociatedIrp.SystemBuffer, (UCHAR*)pUserHeader + sizeof(USER_HEADER), pUserHeader->Information);
            }
        }
    }
}

Server side code to allocate request and IO completion routine:

NTSTATUS AllocateRequest(DEVICE_EXTENSION *pDeviceExtension, IRP *pIrp, IRP **ppIrp2)
{
    IRP *pIrp2;
    PVOID pBuffer;
    NTSTATUS Status;
    IO_STACK_LOCATION *pStack;
    SCSI_REQUEST_BLOCK *pSrb;
    USER_SCSI_REQUEST_BLOCK *pUserSrb;
    DEVICE_OBJECT *pDeviceObject;
    USER_HEADER *pUserHeader;

    pUserHeader = (USER_HEADER*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);
    pDeviceObject = pDeviceExtension->pLowerDeviceObject;

    pIrp2 = IoAllocateIrp(pDeviceObject->StackSize, FALSE);

    if (pIrp2)
    {
        pStack = IoGetNextIrpStackLocation(pIrp2);

        pStack->DeviceObject = pDeviceObject;
        pIrp2->Tail.Overlay.Thread = PsGetCurrentThread();

        pStack->MajorFunction = pUserHeader->MajorFunction;
        pStack->MinorFunction = pUserHeader->MinorFunction;

        if (pUserHeader->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
        {
            pUserSrb = (USER_SCSI_REQUEST_BLOCK*)((UCHAR*)pUserHeader + sizeof(USER_HEADER));
            pSrb = (SCSI_REQUEST_BLOCK*)((UCHAR*)pUserSrb + (sizeof(USER_SCSI_REQUEST_BLOCK) + pUserSrb->DataTransferLength + pUserSrb->SenseInfoBufferLength));

            pSrb->Length = sizeof(SCSI_REQUEST_BLOCK);
            pSrb->Function = pUserSrb->Function;
            pSrb->SrbStatus = pUserSrb->SrbStatus;
            pSrb->ScsiStatus = pUserSrb->ScsiStatus;
            pSrb->PathId = pUserSrb->PathId;
            pSrb->TargetId = pUserSrb->TargetId;
            pSrb->Lun = pUserSrb->Lun;
            pSrb->QueueTag = pUserSrb->QueueTag;
            pSrb->QueueAction = pUserSrb->QueueAction;
            pSrb->CdbLength = pUserSrb->CdbLength;
            pSrb->SenseInfoBufferLength = pUserSrb->SenseInfoBufferLength;
            pSrb->SrbFlags = pUserSrb->SrbFlags;
            pSrb->DataTransferLength = pUserSrb->DataTransferLength;
            pSrb->TimeOutValue = pUserSrb->TimeOutValue;

            if (pUserSrb->DataTransferLength)
            {
                pSrb->DataBuffer = (UCHAR*)pIrp->MdlAddress->StartVa + pIrp->MdlAddress->ByteOffset + (sizeof(USER_HEADER) + sizeof(USER_SCSI_REQUEST_BLOCK));

                IoBuildPartialMdl(pIrp->MdlAddress, pDeviceExtension->pMdl, pSrb->DataBuffer, pUserSrb->DataTransferLength);

                pIrp2->MdlAddress = pDeviceExtension->pMdl;
            }
            else pSrb->DataBuffer = NULL;

            if (pUserSrb->SenseInfoBufferLength)
            {
                pSrb->SenseInfoBuffer = (UCHAR*)pUserSrb + (sizeof(USER_SCSI_REQUEST_BLOCK) + pUserSrb->DataTransferLength);
            }
            else pSrb->SenseInfoBuffer = NULL;

            pSrb->NextSrb = NULL;
            pSrb->OriginalRequest = pIrp2;
            pSrb->SrbExtension = NULL;

            pSrb->QueueSortKey = pUserSrb->QueueSortKey;
            memcpy(pSrb->Cdb, pUserSrb->Cdb, sizeof(pSrb->Cdb));

            pStack->Parameters.Scsi.Srb = pSrb;
        }
        else
        {
            pStack->Parameters.DeviceIoControl.IoControlCode = pUserHeader->IoControlCode;

            pBuffer = (UCHAR*)pUserHeader + sizeof(USER_HEADER);

            if (pUserHeader->IoControlCode == IOCTL_SCSI_GET_ADDRESS)
            {
                pStack->Parameters.DeviceIoControl.OutputBufferLength = pUserHeader->OutputBufferLength;
                pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
            }
            else if (pUserHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY)
            {
                pStack->Parameters.DeviceIoControl.InputBufferLength = pUserHeader->InputBufferLength;
                pStack->Parameters.DeviceIoControl.OutputBufferLength = pUserHeader->OutputBufferLength;
                pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
            }
            else if (pUserHeader->IoControlCode == IOCTL_STORAGE_ENABLE_IDLE_POWER)
            {
                pStack->Parameters.DeviceIoControl.InputBufferLength = pUserHeader->InputBufferLength;
                pIrp2->AssociatedIrp.SystemBuffer = pBuffer;
            }
        }

        *ppIrp2 = pIrp2;
        Status = STATUS_SUCCESS;
    }
    else Status = STATUS_INSUFFICIENT_RESOURCES;

    return Status;
}

NTSTATUS IoCompletionRoutine3(DEVICE_OBJECT *pDeviceObject, IRP *pIrp2, void *pContext)
{
    IRP *pIrp;
    USER_HEADER *pUserHeader;
    DEVICE_EXTENSION *pDeviceExtension;
    USER_SCSI_REQUEST_BLOCK *pUserSrb;
    SCSI_REQUEST_BLOCK *pSrb;

    pDeviceExtension = (DEVICE_EXTENSION*)pIrp2->Tail.Overlay.DriverContext[0];
    pIrp = (IRP*)pIrp2->Tail.Overlay.DriverContext[1];

    pUserHeader = (USER_HEADER*)MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority);

    pUserHeader->Status = pIrp2->IoStatus.Status;
    pUserHeader->Information = pIrp2->IoStatus.Information;

    if (pUserHeader->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL)
    {
        pUserSrb = (USER_SCSI_REQUEST_BLOCK*)((UCHAR*)pUserHeader + sizeof(USER_HEADER));
        pSrb = (SCSI_REQUEST_BLOCK*)((UCHAR*)pUserSrb + (sizeof(USER_SCSI_REQUEST_BLOCK) + pUserSrb->DataTransferLength + pUserSrb->SenseInfoBufferLength));

        pUserSrb->SrbStatus = pSrb->SrbStatus;
        pUserSrb->ScsiStatus = pSrb->ScsiStatus;

        pUserSrb->SenseInfoBufferLength = pSrb->SenseInfoBufferLength;
        pUserSrb->DataTransferLength = pSrb->DataTransferLength;

        pUserHeader->Length = sizeof(USER_HEADER) + sizeof(USER_SCSI_REQUEST_BLOCK);

        if (NT_SUCCESS(pUserHeader->Status))
        {
            if ((pSrb->DataTransferLength) && (pSrb->SrbFlags & SRB_FLAGS_DATA_IN))
            {
                pUserHeader->Length += pUserSrb->DataTransferLength;
            }
        }
        else
        {
            if ((pSrb->SenseInfoBufferLength) && (pSrb->SrbStatus & SRB_STATUS_AUTOSENSE_VALID))
            {
                pUserHeader->Length += pUserSrb->SenseInfoBufferLength;

                if (pSrb->DataTransferLength)
                {
                    memmove((UCHAR*)pUserSrb + sizeof(USER_SCSI_REQUEST_BLOCK), (UCHAR*)pUserSrb + (sizeof(USER_SCSI_REQUEST_BLOCK) + pUserSrb->DataTransferLength), pUserSrb->SenseInfoBufferLength);
                }
            }
        }
    }
    else
    {
        pUserHeader->Length = sizeof(USER_HEADER);

        if (pUserHeader->IoControlCode == IOCTL_SCSI_GET_ADDRESS)
        {
            pUserHeader->Length += pUserHeader->Information;
        }
        else if (pUserHeader->IoControlCode == IOCTL_STORAGE_QUERY_PROPERTY)
        {
            pUserHeader->Length += pUserHeader->Information;
        }
    }

    IoFreeIrp(pIrp2);

    CompleteRequest(pIrp, STATUS_SUCCESS, 0);
    IoReleaseRemoveLock(&pDeviceExtension->RemoveLock, pIrp);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

Everything runs fine (requests are passed between server and client, no BSOD, etc), but cdrom device just does not show up on the client side. I thought it might be something with srb data buffer access. Can you help me to figure it out? Thank you.

igntec
  • 1,006
  • 10
  • 24
  • One thorn is that the compiler is allow to add padding between fields. Because of this, you can't model a SCSI message 1:1 binary. You need to read the data into a buffer, then load your structure, field by field, from the buffer. – Thomas Matthews Mar 15 '18 at 18:39
  • Another issue: you should use the `uint_xx_t` types. Fundamental C++ types are defined by range or capacity. A UCHAR can be represented using 32 bits and still meet the C++ criterial. The capacities are *minimal* requirements. – Thomas Matthews Mar 15 '18 at 18:43
  • @Thomas Matthews I use the same system (32 bit) as client and server for test purposes. I compile two drivers on another system (64 bit) - for 32 bit target. ULONG is the same as uint_32_t and is aligned on 4 byte boundary, UCHAR is the same as uint_8_t and is aligned on 1 byte boundary. SCSI response data is defined byte-wise by SCSI standard, so there should be no problems here. What exactly do I need to change? – igntec Mar 16 '18 at 00:01
  • I recommend reading into a buffer, then loading the fields from the buffer, as in my first comment. Ultimately, you should use a debugger or for real-time analysis output to a log file. – Thomas Matthews Mar 16 '18 at 00:44
  • @Thomas Matthews Can you clarify what buffer are you talking about? Srb data buffer? I checked returned data under debugger - everything seems to be correct. – igntec Mar 16 '18 at 16:31
  • When I input data, I use block read into a `uint8_t` array (a.k.a. buffer). Next, I use a pointer into that buffer to read a structure member, and increment the pointer by the size of the item (according to the data layout specification). This allows me to load a 16-bit field into a 32-bit member structure. Research *serialization* also. – Thomas Matthews Mar 16 '18 at 16:35
  • In general, I agree. But if we take this particular case - all system-supplied structures (in wdm.h) have fixed layout, because they are used to pass information between different drivers. Which in turn means that we can safely send them over network without the need to serialize and deserialize them. Under debugger everything looks fine. – igntec Mar 16 '18 at 17:22

0 Answers0