NetWkstaUserEnum is bugged, it is impossible to use resumeHandle

Stefano Balzarotti 0 Reputation points
2024-12-13T19:57:09.1533333+00:00

Referring documentation: https://learn.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstauserenum

Steps to reproduce the bug:

  1. you need a server with many simultaneously concurrent logged users
  2. call NetWkstaUserEnum, with prefmaxlen set to a size in bytes able to contain a partial set of users. For example if you have 100 users, and a user structure occupy 100 bytes, set to 2000 bytes to return 20 user structures.
  3. NetWkstaUserEnum returns ERROR_MORE_DATA, and sets resumeHandle to a value to be passed again to retrieve the rest of users.
  4. resumeHandle value is wrong and NetWkstaUserEnum returns the same set of users
  5. Loop would be blocked in an infinte loop without ever be able to retrieve the complete list of users.

Simple explanation:

The internal implementation of NetWkstaUserEnum relies on

MSV1_0_ENUMUSERS_RESPONSE::EnumHandles that retrieves users in descending order, but then WsEnumUserInfo assumes users in ascending order.

You can find a detailed explanation of this bug here: https://stackoverflow.com/a/79271924/5081328

If you don't have a server with many users, you can try by setting a small buffer just be able hold one user, for example 100 bytes, assuming is enough to hold at least one user.

The code to reproduce the bug is the same as Microsoft example:

#ifndef UNICODE
#define UNICODE
#endif
#pragma comment(lib, "netapi32.lib")

#include <stdio.h>
#include <assert.h>
#include <windows.h> 
#include <lm.h>

int wmain(int argc, wchar_t *argv[])
{
   LPWKSTA_USER_INFO_0 pBuf = NULL;
   LPWKSTA_USER_INFO_0 pTmpBuf;
   DWORD dwLevel = 0;
   DWORD dwPrefMaxLen = 100;
   DWORD dwEntriesRead = 0;
   DWORD dwTotalEntries = 0;
   DWORD dwResumeHandle = 0;
   DWORD i;
   DWORD dwTotalCount = 0;
   NET_API_STATUS nStatus;
   LPWSTR pszServerName = NULL;

   if (argc > 2)
   {
      fwprintf(stderr, L"Usage: %s [\\\\ServerName]\n", argv[0]);
      exit(1);
   }
   // The server is not the default local computer.
   //
   if (argc == 2)
      pszServerName = argv[1];
   fwprintf(stderr, L"\nUsers currently logged on %s:\n", pszServerName);
   //
   // Call the NetWkstaUserEnum function, specifying level 0.
   //
   do // begin do
   {
      nStatus = NetWkstaUserEnum( pszServerName,
                                  dwLevel,
                                  (LPBYTE*)&pBuf,
                                  dwPrefMaxLen,
                                  &dwEntriesRead,
                                  &dwTotalEntries,
                                  &dwResumeHandle);
      //
      // If the call succeeds,
      //
      if ((nStatus == NERR_Success) || (nStatus == ERROR_MORE_DATA))
      {
         if ((pTmpBuf = pBuf) != NULL)
         {
            //
            // Loop through the entries.
            //
            for (i = 0; (i < dwEntriesRead); i++)
            {
               assert(pTmpBuf != NULL);

               if (pTmpBuf == NULL)
               {
                  //
                  // Only members of the Administrators local group
                  //  can successfully execute NetWkstaUserEnum
                  //  locally and on a remote server.
                  //
                  fprintf(stderr, "An access violation has occurred\n");
                  break;
               }
               //
               // Print the user logged on to the workstation. 
               //
               wprintf(L"\t-- %s\n", pTmpBuf->wkui0_username);

               pTmpBuf++;
               dwTotalCount++;
            }
         }
      }
      //
      // Otherwise, indicate a system error.
      //
      else
         fprintf(stderr, "A system error has occurred: %d\n", nStatus);
      //
      // Free the allocated memory.
      //
      if (pBuf != NULL)
      {
         NetApiBufferFree(pBuf);
         pBuf = NULL;
      }
   }
   // 
   // Continue to call NetWkstaUserEnum while 
   //  there are more entries. 
   // 
   while (nStatus == ERROR_MORE_DATA); // end do
   //
   // Check again for allocated memory.
   //
   if (pBuf != NULL)
      NetApiBufferFree(pBuf);
   //
   // Print the final count of workstation users.
   //
   fprintf(stderr, "\nTotal of %d entries enumerated\n", dwTotalCount);

   return 0;
}

This issue has been around for more than 20 years affecting windows since windows server 2003.

Now I don't expect a quick solution by Microsoft, even if they will solve in next windows version I will still need to rely on other ways to retrieve currently logged users in my application, because I will still need to support old windows versions for years.

But I still post here hoping it will get the deserved attention by Microsoft for a serious issue that has been around by so many years.

The bug has already been reported as feedback on Microsoft documentation page, on Microsoft Feedback hub and here : https://github.com/microsoft/WindowsAppSDK/issues/4955

Let me know if there is a better channel to report bugs in windows OS API,

Windows API - Win32
Windows API - Win32
A core set of Windows application programming interfaces (APIs) for desktop and server applications. Previously known as Win32 API.
2,700 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Darran Rowe 1,236 Reputation points
    2024-12-13T21:10:48.7633333+00:00

    The two documented ways of reporting bugs are:

    1. Paid support. This is the only way to guarantee a timely response from a Microsoft engineer.
    2. The feedback hub. While there are people who don't feel that this is the place to report bugs in Windows, this is really the place. You will want to report it under the Developer Platform -> API Feedback category.

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.