1

I am updating my question to better reflect what I was actually going after. To state a fact about my original confusion quickly, it is incorrect to say that there is a 1-to-1 relationship between "Device Interface Class GUID" and the Device Instance ID. A device can have many device interfaces. As Ben Voigt noted in the comments, see this for more information.


How can one open a handle to a child device after calling the CM_Get_Child (...) function?

Take the following code snip as an example:

#pragma comment (lib, "Setupapi.lib")
#pragma comment (lib, "Cfgmgr32.lib")

#include <iostream>
#include <Windows.h>
#include <Setupapi.h>
#include <Cfgmgr32.h> 

#define GUID_STRING_SIZE 40

int main ()
{
    CONFIGRET CMResult = CR_SUCCESS;
    WCHAR DeviceInstanceID[] = L"USB\\VID_2109&PID_0813\\8&216C1825&0&4\0"; // Parent Device Instance ID.

    DEVNODE ParentDeviceNode = (DWORD) 0; // A device instance handle. This handle is bounded to the local machine.
    CMResult = CM_Locate_DevNode ((PDEVINST) &ParentDeviceNode, DeviceInstanceID, CM_LOCATE_DEVNODE_NORMAL);

    if (CMResult != CR_SUCCESS)
    {
        std::cout << "No parent device node found." << std::endl;
        return -1;
    }
    else
    {
        DEVINST NextChildDeviceNode = (DWORD) 0;
        CMResult = CM_Get_Child ((PDEVINST) &NextChildDeviceNode, ParentDeviceNode, 0x0);    // Gets the first child of the parent node. If this returns "CR_NO_SUCH_DEVNODE," then there is no child attached.

        if (CMResult != CR_SUCCESS)
        {
            std::cout << "No child device node found." << std::endl;
            return -2;
        }
        else
        {
            ULONG ChildInstanceIDBuffLength = 0;
            CMResult = CM_Get_Device_ID_Size (&ChildInstanceIDBuffLength, NextChildDeviceNode, 0x0);

            if (CMResult != CR_SUCCESS)
            {
                std::cout << "Could not get the size of the device instance ID of child device." << std::endl;
                return -3;
            }
            else
            {
                WCHAR * ChildInstanceIDBuff = (WCHAR *) malloc (ChildInstanceIDBuffLength);
                CMResult = CM_Get_Device_IDW (NextChildDeviceNode, ChildInstanceIDBuff, ChildInstanceIDBuffLength, 0x0);

                if (CMResult != CR_SUCCESS)
                {
                    std::cout << "Could not actual device instance ID string of child device" << std::endl;
                    return -4;
                }
                else
                {
                    std::cout << "Found child device instance ID: ";
                    std::wcout << ChildInstanceIDBuff << std::endl;

                    /*
                     *  Open handle to the child device node now!
                     */
                }

                free (ChildInstanceIDBuff);
            }
        }
    }

    return 0;
}

How can I use the newly obtained child Device Instance ID to open a handle to the device? CreateFile (...) requires the complete device path, which includes the missing "Device Interface Class GUID."

More specifically, a device path has the following format:
\\?\usb#vid_2109&pid_0813#7&3981C8D6&0&2#{[DEVICE_INTERFACE_GUID]}, where:

  1. [DEVICE_INTERFACE_GUID] - This the "Device Interface Class GUID." This is NOT the same as the "Device Setup Class GUID."

There does not appear to be an easy way to get this "Device Interface Class GUID" without some level of brute force (e.g. CM_Enumerate_Classes (...) using the CM_ENUMERATE_CLASSES_INTERFACE flag). Is there a function I can call to get a handle to a device using only its "Device Instance ID," so that I can then call DeviceIoControl (...) and query information about the device?

Code Doggo
  • 2,146
  • 6
  • 33
  • 58
  • 2
    A device can implement more than one device interface... – Ben Voigt Jan 14 '20 at 21:22
  • @BenVoigt So, is there no way to get a list of these interfaces? What I am trying to get at is when I call `CM_Get_Child (...)`, I get a "_Device Instance Handle_" of the child device (i.e. a DevNode number that can be used to look up the device in the Device Tree). How can I get the complete device path, so I can actually open a file handle on the device to query for information? – Code Doggo Jan 14 '20 at 21:24
  • There is a way, I found it once, but it is pretty buried and I don't know if I kept the code. Meanwhile I suggest you write your question to not assume a 1:1 mapping of instance ID to device interface path. – Ben Voigt Jan 14 '20 at 21:26
  • 1
    Very helpful: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/device-information-sets – Ben Voigt Jan 14 '20 at 21:29
  • for what you need device path ? are `DEVPKEY_Device_PDOName` is ok for you ? i be ask - how/from where you get *pDeviceID* too ? so what you have at begin and what final goal ? – RbMm Jan 14 '20 at 21:31
  • @RbMb: The device's interface's path is what you can pass to `CreateFile`, I think this is what OP wants to do. – Ben Voigt Jan 14 '20 at 21:35
  • @BenVoigt Exactly. I want to open up a handle on the device and to do that, I need the device's interface path to pass into `CreateFile (...)`. – Code Doggo Jan 14 '20 at 21:36
  • @BenVoigt - so `DEVPKEY_Device_PDOName` is ok here . use this you can open device - this is Nt format name of device, can use in `NtOpenFile` – RbMm Jan 14 '20 at 21:43
  • @CodeDoggo - think you need change question - how by DeviceId i can try open file on device – RbMm Jan 14 '20 at 21:52
  • @RbMm: I don't think he should open the PDO path. You get different operations when you open a device interface path depending on which interface it is (and a single device can have many). – Ben Voigt Jan 14 '20 at 21:53
  • @BenVoigt - you mistake - so called device interface path - only symbolic link to `PDO` path. so we can use both in call NtOpenFile. the device even can not know which path used in open - so can not handle it different. look in winobj - interface - exactly symlink to pdo – RbMm Jan 14 '20 at 21:59
  • @RbMb: But isn't the interface PDO different from the device PDO? – Ben Voigt Jan 14 '20 at 22:03
  • @BenVoigt - this both point to the **same** device. so we can use any in open – RbMm Jan 14 '20 at 22:04
  • @BenVoigt another task that OP ask wrong question. he ask not what he really need (open file on device) but another question – RbMm Jan 14 '20 at 22:05
  • @RbMm I agree. I had some misunderstandings. I thought the "*Device Interface Class GUID*" was unique per device. I was wrong. I will be updating my question to better reflect what I was going after. – Code Doggo Jan 14 '20 at 22:07
  • @CodeDoggo - if you ask - how open file on device by Device Instance ID - i can give exactly answer – RbMm Jan 14 '20 at 22:08

2 Answers2

0

You can use the CM_Enumerate_Classes function with the CM_ENUMERATE_CLASSES_INTERFACE flag (Requires Windows 8) to get possible values to pass as that third parameter of SetupDiEnumDeviceInterfaces.

Beginning with Windows 8 and later operating systems, callers can use the ulFlags member to specify which device classes CM_Enumerate_Classes should return. Prior to Windows 8, CM_Enumerate_Classes returned only device setup classes.

Note that finding all interfaces on a device is very useful for diagnosing driver problems and/or reverse engineering random peripherals rescued from scrap. But you should know what interface class you are dealing with long before you get to the point of calling CreateFile.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • I have update my question to better reflect what I was trying to get at. Take a look at it and let me know what you think. To comment on the `CM_Enumerate_Classes (...)` function with the `CM_ENUMERATE_CLASSES_INTERFACE` flag, isn't it possible you could select the wrong interface? For example, if a parent **device A** has a child **device B** using **interface D**, but at the same time **device B** is also a child of another device, but using a different **interface C**, couldn't you get the wrong handle if you simply blindly brute force? – Code Doggo Jan 14 '20 at 23:17
  • Or would this child **device B** enumerate twice with two unique instance IDs? – Code Doggo Jan 14 '20 at 23:18
  • @CodeDoggo: A device only has one parent. You can have two interfaces which are children of the same device, but the device can't appear multiple times in the hierarchy. – Ben Voigt Jan 14 '20 at 23:24
  • So, only one device interface can be "active" at a time for a given device? – Code Doggo Jan 14 '20 at 23:25
  • I didn't say that. Enumerating all device interface classes would in fact be very useful for determining if a device supports multiple interfaces. – Ben Voigt Jan 14 '20 at 23:26
  • @CodeDoggo - you forget about your target. you not need device interface. you need device path for open handle on it – RbMm Jan 14 '20 at 23:37
0

if we need open handle to device by Device Instance ID -

  • first call CM_Locate_DevNodeW function for obtains a device instance handle to the device node that is associated with a specified device instance ID on the local machine.
  • then we need call CM_Get_DevNode_PropertyW function with DEVPKEY_Device_PDOName - this return name of the physical name object (PDO) that represents a device and we can use it in call NtOpenFile. of course if very want - can use and in call CreateFileW if add L"\\\\?\\Global\\GLOBALROOT" to name, but not view any sense do this.

volatile UCHAR guz = 0;

ULONG OpenDeviceByDeviceID(_Out_ PHANDLE FileHandle, _In_ PWSTR DeviceID)
{
    DEVINST dnDevInst;

    CONFIGRET CmReturnCode = CM_Locate_DevNodeW(&dnDevInst, DeviceID, CM_LOCATE_DEVNODE_NORMAL);

    if (CmReturnCode == CR_SUCCESS)
    {
        ULONG cb = 0, rcb = 128;

        PVOID stack = alloca(guz);

        DEVPROPTYPE PropertyType;

        union {
            PVOID pv;
            PWSTR sz;
            PBYTE pb;
        };

        do 
        {
            if (cb < rcb)
            {
                rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
            }

            CmReturnCode = CM_Get_DevNode_PropertyW(dnDevInst, 
                &DEVPKEY_Device_PDOName, &PropertyType, pb, &rcb, 0);

            if (CmReturnCode == CR_SUCCESS)
            {
                if (PropertyType == DEVPROP_TYPE_STRING)
                {
                    DbgPrint("PDOName = %S\n", sz);

#if 1
                    IO_STATUS_BLOCK iosb;
                    UNICODE_STRING ObjectName;
                    OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName };
                    RtlInitUnicodeString(&ObjectName, sz);
                    NTSTATUS status = NtOpenFile(FileHandle, 
                        FILE_GENERIC_READ, &oa, &iosb, 0, 0);

                    return 0 > status ? RtlNtStatusToDosError(status) : NOERROR;
#else
                    static WCHAR prefix[] = L"\\\\?\\Global\\GLOBALROOT";
                    alloca(sizeof(prefix) - sizeof(WCHAR));

                    PWSTR fileName = sz - _countof(prefix) + 1;
                    memcpy(fileName, prefix, sizeof(prefix) - sizeof(WCHAR));

                    HANDLE hFile = CreateFileW(fileName, FILE_GENERIC_READ, 
                        0, 0, OPEN_EXISTING, 0, 0);
                    if (hFile == INVALID_HANDLE_VALUE)
                    {
                        return GetLastError();
                    }

                    *FileHandle = hFile;
                    return NOERROR;
#endif
                }
                else
                {
                    CmReturnCode = CR_WRONG_TYPE;
                }
            }

        } while (CmReturnCode == CR_BUFFER_SMALL);
    }

    return CM_MapCrToWin32Err(CmReturnCode, 0);
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • @RbMmn I have updated my question to better reflect what I was trying to get at. Take a look at it and let me know. – Code Doggo Jan 14 '20 at 23:13
  • @CodeDoggo - i give you answer how open handle to device by it device id or device instance handle. of course you can do the same for any device and it childs. however unclear - how you get device id at begin ? you must not hardcode it anyway – RbMm Jan 14 '20 at 23:22
  • 1
    The entire use of alloca here is ridiculously fragile – Ben Voigt Jan 14 '20 at 23:22
  • 1
    @BenVoigt - think this is not main point here - where allocate memory - in stack or heap. but why you think that it is *ridiculously fragile* ? – RbMm Jan 14 '20 at 23:24
  • It appears you paste together successive buffers from alloca. Sometimes you ignore the return value. That's all against the rules, although it probably works on some systems. – Ben Voigt Jan 14 '20 at 23:25
  • @BenVoigt - yes, you right, i paste together successive buffers from alloca. but i not understand what you mean under *Sometimes you ignore the return value* - from which api and where here ? my code is correct – RbMm Jan 14 '20 at 23:27
  • That `alloca(sizeof(prefix) - sizeof(WCHAR));`, the returned buffer is never used, you seem to assume it extends another buffer. Not safe. – Ben Voigt Jan 14 '20 at 23:29
  • 1
    @BenVoigt - no - return buffer is **used** in next instruction `memcpy`. you mean that it **implicit** used, but anyway language can not drop call to *alloca* because except return value it can have another side effects. so i not view here any unsafe, but anyway think this was question not about *c++* language but about how get device path. are in this point you view something wrong ? – RbMm Jan 14 '20 at 23:33
  • 1
    @BenVoigt already not say that this is under not compile path. i be never use `CreateFile` here instead `NtOpenFile` when we already have NT-path. this is just for demo how convert NT-path for dos path – RbMm Jan 14 '20 at 23:36
  • I'm not saying that the language can drop the call to `alloca`. I'm saying it can do all sorts of other things... like put canary values in between the allocations to detect buffer overflow. Your code is full of buffer overflows, and that's undefined behavior even if you carefully planned where the overflow will go. – Ben Voigt Jan 15 '20 at 15:26
  • 1
    @BenVoigt - **put canary values** - this is runtime checks control by [*/RTC (Run-Time Error Checks)*](https://learn.microsoft.com/en-us/cpp/build/reference/rtc-run-time-error-checks?view=vs-2019). if we off it - compiler will be no *put canary values*. we have full control over compiler at this point. of course this checks must be off for use such code. really of course code ok, i many years use such code in many places without problem. but this question not about *c++*. i simply have **ready code** for this and copy-paste it. OP can use another memory allocation - not this is point – RbMm Jan 15 '20 at 15:33
  • 1
    @BenVoigt but my real point here - about difference between *Interfcace name* and *PDO name* - look for [`IoRegisterDeviceInterface`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-ioregisterdeviceinterface) in part of **ReferenceString** - if it not used (usual) - the SymbolicLinkName will be exactly PDOName. – RbMm Jan 15 '20 at 15:47
  • 1
    @BenVoigt - more exactly *put canary values* - done by */RTCs* option (this is debug only option incompatible with any optimization *O1, O2* ( if we good understand what we do - we can do many unusual things example - https://godbolt.org/z/We6w36 ) – RbMm Jan 15 '20 at 16:43
  • 1
    Yes you can do many unusual things which strictly speaking are not allowed by the language formal specification, and *ridiculously fragile* is a correct description of those things, which is why I said it. No programmer coming across this (or any other) Stack Overflow answer should be using those tricks, the few programmers who understand the internals well enough to pull this off don't need to ask Stack Overflow to solve their problems. – Ben Voigt Jan 15 '20 at 17:16
  • BTW there is also `/GS`, which unlike `/RTCs` is intended for use in production. And it applies to `alloca` buffers. https://learn.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-check – Ben Voigt Jan 15 '20 at 17:20
  • 1
    @BenVoigt - no, you mistake here `/GS` this is only security cookie for return address, it not check `alloca` allocations. so until i not overrun buffers - here all ok. i know topic of course in all details, because use this – RbMm Jan 15 '20 at 17:25
  • 1
    @BenVoigt - more exactly be say */GS* not check split of 2 alloca, unlike */RTCs* - because this we can do cumulative `alloca` with `/GS` but can not do with `/RTCs` – RbMm Jan 15 '20 at 17:27
  • 1
    @BenVoigt - [*/RTCs*](https://godbolt.org/z/Nd3Aiw) use `_RTC_AllocaHelper` and it allocate in stack [`_RTC_ALLOCA_NODE`](https://chromium.googlesource.com/chromium/deps/perl/+/master/c/i686-w64-mingw32/include/rtcapi.h#57) chain. when i do several alloca in loop - we overwrite this, if use */RTCs* option. if not use - all ok here (if not too big buffer of course). the */GS* check only in single place cookie - are stack not overrun. this not conflict with cumulative *alloca*. so here possible use it (in user mode only when we have big stack space) for small allocations – RbMm Jan 15 '20 at 17:45