-1

I am implementing a custom port monitor based off of the LocalMon sample, but when I return from my implementation of LcmEnumPorts, I receive the error "The data is invalid", and the list of ports installed on my machine is empty. Removing the monitor eliminates the error, and all ports reappear.

Operation could not be completed (error 0x0000000d).  The data is invalid.

Why? I've checked that the structure I am returning is coherent and fits within the buffer allocated.

Example implementation of LcmEnumPorts:

_Success_(return != FALSE)
BOOL WINAPI LcmEnumPorts(
    _In_                        HANDLE  hMonitor,
    _In_opt_                    LPWSTR  pName,
                                DWORD   Level,
    _Out_writes_bytes_opt_(cbBuf)
                                LPBYTE  pPorts,
                                DWORD   cbBuf,
    _Out_                       LPDWORD pcbNeeded,
    _Out_                       LPDWORD pcReturned
    )
{
    UNREFERENCED_PARAMETER(pName);
    UNREFERENCED_PARAMETER(hMonitor);

    if (!pcbNeeded || !pcReturned || (!pPorts && (cbBuf > 0)))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    else if ((1 != Level) && (2 != Level))
    {
        SetLastError(ERROR_INVALID_LEVEL);
        return FALSE;
    }

    const wchar_t szMonitorName[] = L"WDK Sample Port";
    const wchar_t szPortName[] = L"NUL:";

    size_t cbPortName = wcslen(szPortName) * sizeof(wchar_t) + sizeof(wchar_t);
    size_t cbPortDesc = wcslen(szMonitorName) * sizeof(wchar_t) + sizeof(wchar_t);
    size_t cbText = cbPortName + cbPortDesc;
    size_t cbStruct = Level == 1 ? sizeof(PORT_INFO_1) : sizeof(PORT_INFO_2);

    *pcbNeeded = (DWORD)(cbText + cbStruct);
    *pcReturned = 0;

    if (*pcbNeeded > cbBuf)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    if (Level == 1)
    {
        PPORT_INFO_1 pPort1 = (PPORT_INFO_1)pPorts;
        LPWSTR pPortName = (LPWSTR)(pPorts + cbStruct);
        StringCbCopy(pPortName, cbPortName, szPortName);
        pPort1->pName = pPortName;
    }
    else if (Level == 2)
    {
        PPORT_INFO_2 pPort2 = (PPORT_INFO_2)pPorts;

        LPWSTR pPortName = (LPWSTR)(pPorts + cbStruct);
        StringCbCopy(pPortName, cbPortName, szPortName);
        pPort2->pPortName = pPortName;

        LPWSTR pPortDesc = (LPWSTR)(pPorts + cbStruct + cbPortName);
        StringCbCopy(pPortDesc, cbPortDesc, szMonitorName);
        pPort2->pMonitorName = pPortDesc;
        pPort2->pDescription = pPortDesc;

        pPort2->fPortType = PORT_TYPE_READ | PORT_TYPE_WRITE;
        pPort2->Reserved = 0;
    }
    *pcReturned = 1;

    return TRUE;
}
Mitch
  • 21,223
  • 6
  • 63
  • 86
  • Would the downvoter care to comment? I'm not sure what is objectionable about this question? – Mitch Oct 29 '17 at 21:23

1 Answers1

1

Though it does not appear to be documented anywhere, the spooler seems to reuse the same buffer for multiple driver calls.

The docs say:

The EnumPorts function should fill the buffer pointed to by pPort with an array of PORT_INFO_1 or PORT_INFO_2 structures. Then starting in a memory location following the last array element, the function must load all the strings pointed to by the array's structure members. Refer to localmon.dll, a sample port monitor, for an example of how to do this. The function must also return the number of structures supplied (that is, the number of supported ports) by placing the number in the location pointed to by pcReturned.

But in reality, it seems that the same buffer is reused for all of the print monitors. Each time, the top pointer is moved down for each PORT_INFO_1/2 structure, and the bottom pointer is moved up for each allocated string.

Diagram showing top and bottom pointers

Reviewing localmon shows it exclusively allocates strings from the bottom of the buffer. Changing the sample to do the same allows it to execute without error.

_Success_(return != FALSE)
BOOL
LcmEnumPorts(
    _In_                        HANDLE  hMonitor,
    _In_opt_                    LPWSTR  pName,
                                DWORD   Level,
    _Out_writes_bytes_opt_(cbBuf)
                                LPBYTE  pPorts,
                                DWORD   cbBuf,
    _Out_                       LPDWORD pcbNeeded,
    _Out_                       LPDWORD pcReturned
    )
{
    UNREFERENCED_PARAMETER(pName);
    UNREFERENCED_PARAMETER(hMonitor);

    if (!pcbNeeded || !pcReturned || (!pPorts && (cbBuf > 0)))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }
    else if ((1 != Level) && (2 != Level))
    {
        SetLastError(ERROR_INVALID_LEVEL);
        return FALSE;
    }

    const wchar_t szMonitorName[] = L"WDK Sample Port";
    const wchar_t szPortName[] = L"NUL:";

    size_t cbPortName = wcslen(szPortName) * sizeof(wchar_t) + sizeof(wchar_t);
    size_t cbPortDesc = wcslen(szMonitorName) * sizeof(wchar_t) + sizeof(wchar_t);
    size_t cbText = cbPortName + cbPortDesc;
    size_t cbStruct = Level == 1 ? sizeof(PORT_INFO_1) : sizeof(PORT_INFO_2);

    *pcbNeeded = (DWORD)(cbText + cbStruct);
    *pcReturned = 0;

    if (*pcbNeeded > cbBuf)
    {
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    if (Level == 1)
    {
        PPORT_INFO_1 pPort1 = (PPORT_INFO_1)pPorts;
        // Changed                  vvvvvvvvvvvvvvvvvvvvvvvvvvv
        LPWSTR pPortName = (LPWSTR)(pPorts + cbBuf - cbPortName);
        StringCbCopy(pPortName, cbPortName, szPortName);
        pPort1->pName = pPortName;
    }
    else if (Level == 2)
    {
        PPORT_INFO_2 pPort2 = (PPORT_INFO_2)pPorts;

        // Changed                  vvvvvvvvvvvvvvvvvvvvvvvvvvv
        LPWSTR pPortName = (LPWSTR)(pPorts + cbBuf - cbPortName);
        StringCbCopy(pPortName, cbPortName, szPortName);
        pPort2->pPortName = pPortName;

        // Changed                  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
        LPWSTR pPortDesc = (LPWSTR)(pPorts + cbBuf - cbPortName - cbPortDesc);
        StringCbCopy(pPortDesc, cbPortDesc, szMonitorName);
        pPort2->pMonitorName = pPortDesc;
        pPort2->pDescription = pPortDesc;

        pPort2->fPortType = PORT_TYPE_READ | PORT_TYPE_WRITE;
        pPort2->Reserved = 0;
    }
    *pcReturned = 1;

    return TRUE;
}
Mitch
  • 21,223
  • 6
  • 63
  • 86