Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect SP_DRVINFO_DETAIL_DATA.HardwareId marshalling #374

Closed
X9VoiD opened this issue Feb 9, 2023 · 1 comment
Closed

Incorrect SP_DRVINFO_DETAIL_DATA.HardwareId marshalling #374

X9VoiD opened this issue Feb 9, 2023 · 1 comment

Comments

@X9VoiD
Copy link

X9VoiD commented Feb 9, 2023

Describe the bug and how to reproduce

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Vanara.PInvoke;
using static Vanara.PInvoke.SetupAPI;

namespace Repro;

public class Repro
{
    public static int Main()
    {
        using SafeHDEVINFO deviceInfoSet = SetupDiGetClassDevs((nint)null, null, HWND.NULL, DIGCF.DIGCF_ALLCLASSES | DIGCF.DIGCF_PRESENT);
        if (deviceInfoSet.IsInvalid)
            throw new Win32Exception();

        SP_DEVINFO_DATA deviceInfo = new()
        {
            cbSize = (uint)Unsafe.SizeOf<SP_DEVINFO_DATA>()
        };

        for (uint i = 0; ; i++)
        {
            if (!SetupDiEnumDeviceInfo(deviceInfoSet, i, ref deviceInfo))
            {
                if (Marshal.GetLastWin32Error() != Win32Error.ERROR_NO_MORE_ITEMS)
                    throw new Win32Exception();

                break;
            }

            if (!SetupDiBuildDriverInfoList(deviceInfoSet, ref deviceInfo, SPDIT.SPDIT_COMPATDRIVER))
                throw new Win32Exception();

            for (int j = 0; ; j++)
            {
                SP_DRVINFO_DATA_V2 driverInfo = new()
                {
                    cbSize = (uint)Marshal.SizeOf<SP_DRVINFO_DATA_V2>()
                };

                if (!SetupDiEnumDriverInfo(deviceInfoSet, in deviceInfo, SPDIT.SPDIT_COMPATDRIVER, (uint)j, ref driverInfo))
                {
                    if (Marshal.GetLastWin32Error() != Win32Error.ERROR_NO_MORE_ITEMS)
                        throw new Win32Exception();

                    break;
                }

                SP_DRVINFO_DETAIL_DATA driverDetail = new()
                {
                    cbSize = (uint)Marshal.SizeOf<SP_DRVINFO_DETAIL_DATA>()
                };

                if (!SetupDiGetDriverInfoDetail(deviceInfoSet, in deviceInfo, in driverInfo, ref driverDetail, (uint)driverDetail.cbSize, out uint neededSize))
                    throw new Win32Exception();
            }
        }

        return 0;
    }
}

This code will eventually throw the following exception in my machine:

An unhandled exception of type 'System.ComponentModel.Win32Exception' occurred in Repro.dll: 'The data area passed to a system call is too small.'

For context, SP_DRVINFO_DETAIL_DATA.cbSize ends up being 1584, however neededSize is 1594.

I tried solving it by allocating my own buffer and then creating a ref SP_DRVINFO_DETAIL_DATA out of it, which kind of works. It doesn't throw anymore, however the SizeConst = 1 makes it so I can only grab one letter (the ref goes through marshaling).

                SP_DRVINFO_DETAIL_DATA driverDetailTemp = new()
                {
                    cbSize = (uint)Marshal.SizeOf<SP_DRVINFO_DETAIL_DATA>()
                };

                SetupDiGetDriverInfoDetail(deviceInfoSet, in deviceInfo, in driverInfo, ref driverDetailTemp, driverDetailTemp.cbSize, out uint neededSize);

                unsafe
                {
                    byte[] buffer = ArrayPool<byte>.Shared.Rent((int)neededSize);
                    Span<byte> bufferSpan = buffer.AsSpan(0, (int)neededSize);
                    ref SP_DRVINFO_DETAIL_DATA driverDetailRef = ref Unsafe.As<byte, SP_DRVINFO_DETAIL_DATA>(ref MemoryMarshal.GetReference(bufferSpan));
                    driverDetailRef.cbSize = driverDetailTemp.cbSize;

                    if (!SetupDiGetDriverInfoDetail(deviceInfoSet, in deviceInfo, in driverInfo, ref driverDetailRef, neededSize, out _))
                        throw new Win32Exception();

                    ArrayPool<byte>.Shared.Return(buffer);
                }

The CompatIDs that is supposed to be after HardwareID is also missing using this method, since marshaller did not know of its existence.

What code is involved

https://github.com/dahall/Vanara/blob/master/PInvoke/SetupAPI/SetupAPI.cs#L3489-L3490
https://github.com/dahall/Vanara/blob/master/PInvoke/SetupAPI/SetupAPI.DiFuncs2.cs#L1774-L1878

Expected behavior

A possible solution is to provide an overload of SetupDiGetDriverInfoDetail() that uses nint instead of ref SP_DRVINFO_DETAIL_DATA.

dahall added a commit that referenced this issue Feb 11, 2023
…VINFO_DETAIL_DATA and extract the correct values. (#374)
@dahall
Copy link
Owner

dahall commented Feb 11, 2023

Big bug. Thanks. I've just pushed a commit with some big changes to fix this. You can get the packages on the project's AppVeyor feed. New code to do what you've done above:

public void Main()
{
   using SafeHDEVINFO deviceInfoSet = Win32Error.ThrowLastErrorIfInvalid(SetupDiGetClassDevs(Flags: DIGCF.DIGCF_ALLCLASSES | DIGCF.DIGCF_PRESENT));

   foreach (SP_DEVINFO_DATA deviceInfo in SetupDiEnumDeviceInfo(deviceInfoSet))
   {
      SP_DEVINFO_DATA diDup = deviceInfo; // Need copy for ref param below
      Win32Error.ThrowLastErrorIfFalse(SetupDiBuildDriverInfoList(deviceInfoSet, ref diDup, SPDIT.SPDIT_COMPATDRIVER));

      foreach (SP_DRVINFO_DATA_V2 driverInfo in SetupDiEnumDriverInfo(deviceInfoSet, deviceInfo, SPDIT.SPDIT_COMPATDRIVER))
      {
         SP_DRVINFO_DETAIL_DATA_MGD mgd = SetupDiGetDriverInfoDetail(deviceInfoSet, deviceInfo, driverInfo);
         // Manipulate as needed
      }
   }
}

@dahall dahall closed this as completed Feb 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants