0

I have a Windows service that tries to start a driver.

I get a handle to the driver and call StartService and it returns SUCCESS. But immediatelly after that, I do a QueryServiceStatusEx and dwCurrentState is 1 (SERVICE_STOPPED).

I then try to start it again (although StartService returned true the first time) and the same thing happens: StartService returns true but dwCurrentState is still 1. What's interesting is that if I put a one second sleep between the two starts, it works as expected.

The code is too big for me to post it entirely, but it looks something like this:

SERVICE_STATUS_PROCESS get_service_info(const wchar_t* szSvcName)
{
    SERVICE_STATUS_PROCESS ssStatus{};
    SC_HANDLE   schSCManager = NULL;
    SC_HANDLE   schService = NULL;
    DWORD dwBytesNeeded;

    schSCManager = OpenSCManager(
        NULL,                    // local computer
        NULL,                    // servicesActive database 
        SC_MANAGER_ALL_ACCESS);  // full access rights 

    if (NULL != schSCManager)
    {
        // Get a handle to the service.
        schService = OpenService(
            schSCManager,         // SCM database 
            szSvcName,            // name of service 
            SERVICE_ALL_ACCESS);  // full access 

        if (schService != NULL)
        {
            // Check the status in case the service is not stopped. 

            QueryServiceStatusEx(
                schService,                     // handle to service 
                SC_STATUS_PROCESS_INFO,         // information level
                (LPBYTE)&ssStatus,             // address of structure
                sizeof(SERVICE_STATUS_PROCESS), // size of structure
                &dwBytesNeeded);              // size needed if buffer is too small
        }
    }

    if (schService != NULL)
        CloseServiceHandle(schService);

    if (schSCManager != NULL)
        CloseServiceHandle(schSCManager);

    return ssStatus;
}

void ServiceEntryPoint()
{
    if (StartStopService(name, true, 5000))
    {
        auto IsServiceStopped = [name]
        {
            SERVICE_STATUS_PROCESS status = get_service_info(name);
            return status.dwCurrentState == SERVICE_STOPPED;
        };

        // check if the service was really started
        if (IsServiceStopped())
        {
            //Sleep(1000);
            StartStopService(name, true, 1000);
        }

        bool stillStopped = IsServiceStopped();
    }
}

If I comment-out the Sleep(1000) line, get_service_info will return SERVICE_STATUS_PROCESS with the following values (for both calls):

DWORD dwServiceType = 1;
DWORD dwCurrentState = 1;
DWORD dwControlsAccepted = 0;
DWORD dwWin32ExitCode = 31;
DWORD dwServiceSpecificExitCode = 0;
DWORD dwCheckPoint = 0;
DWORD dwWaitHint = 0;
DWORD dwProcessId = 0;
DWORD dwServiceFlags = 0;

If I keep the Sleep(1000) line, get_service_info will return SERVICE_STATUS_PROCESS with the same values as above for the first call, but for the second call (after the sleep) it will have the following values:

DWORD dwServiceType = 1;
DWORD dwCurrentState = 4;
DWORD dwControlsAccepted = 1;
DWORD dwWin32ExitCode = 0;
DWORD dwServiceSpecificExitCode = 0;
DWORD dwCheckPoint = 0;
DWORD dwWaitHint = 0;
DWORD dwProcessId = 0;
DWORD dwServiceFlags = 0;

I have two questions:

  1. Why does StartService return true if the service was not, in fact started? (not even pending start) I've added a __debugbreak to my driver's DriverEntry and it's not triggered the first time. It's as if I don't call StartService at all. Same behaviour for the second call, unless I add the sleep. When the sleep is present, the debugbreak is hit.

  2. What possible explanation is there for the necessity of that sleep for this scenario to work? I've checked the Remarks section for StartService but I didn't find anything that gives any clear explanation for the this behaviour.

Note! ServiceEntryPoint is a function that is called is called at the beginning of the service that tries to start the driver (that fails to start).

conectionist
  • 2,694
  • 6
  • 28
  • 50

2 Answers2

1

StartService() returning true does not mean the service was actually started, only that the request was successfully given to the SCM and it took steps to begin starting the service. It is the service's responsibility to report its actual status back to the SCM. If StartService() returns true, you need to call QueryServiceStatus/Ex() in a loop until the service is no longer in a "pending" state, or until the reported CheckPoint times out. This is clearly explained in the StartService documentation:

When a service is started, the Service Control Manager (SCM) spawns the service process, if necessary. If the specified service shares a process with other services, the required process may already exist. The StartService function does not wait for the first status update from the new service, because it can take a while. Instead, it returns when the SCM receives notification from the service control dispatcher that the ServiceMain thread for this service was created successfully.

The SCM sets the following default status values before returning from StartService:

  • Current state of the service is set to SERVICE_START_PENDING.
  • Controls accepted is set to none (zero).
  • The CheckPoint value is set to zero.
  • The WaitHint time is set to 2 seconds.

The calling process can determine if the new service has finished its initialization by calling the QueryServiceStatus function periodically to query the service's status.

Microsoft even provides a full example of this:

Starting a Service

//
// Purpose: 
//   Starts the service if possible.
//
// Parameters:
//   None
// 
// Return value:
//   None
//
VOID __stdcall DoStartSvc()
{
    SERVICE_STATUS_PROCESS ssStatus; 
    DWORD dwOldCheckPoint; 
    DWORD dwStartTickCount;
    DWORD dwWaitTime;
    DWORD dwBytesNeeded;

    // Get a handle to the SCM database. 

    schSCManager = OpenSCManager( 
        NULL,                    // local computer
        NULL,                    // servicesActive database 
        SC_MANAGER_ALL_ACCESS);  // full access rights 

    if (NULL == schSCManager) 
    {
        printf("OpenSCManager failed (%d)\n", GetLastError());
        return;
    }

    // Get a handle to the service.

    schService = OpenService( 
        schSCManager,         // SCM database 
        szSvcName,            // name of service 
        SERVICE_ALL_ACCESS);  // full access 

    if (schService == NULL)
    { 
        printf("OpenService failed (%d)\n", GetLastError()); 
        CloseServiceHandle(schSCManager);
        return;
    }    

    // Check the status in case the service is not stopped. 

    if (!QueryServiceStatusEx( 
            schService,                     // handle to service 
            SC_STATUS_PROCESS_INFO,         // information level
            (LPBYTE) &ssStatus,             // address of structure
            sizeof(SERVICE_STATUS_PROCESS), // size of structure
            &dwBytesNeeded ) )              // size needed if buffer is too small
    {
        printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
        CloseServiceHandle(schService); 
        CloseServiceHandle(schSCManager);
        return; 
    }

    // Check if the service is already running. It would be possible 
    // to stop the service here, but for simplicity this example just returns. 

    if(ssStatus.dwCurrentState != SERVICE_STOPPED && ssStatus.dwCurrentState != SERVICE_STOP_PENDING)
    {
        printf("Cannot start the service because it is already running\n");
        CloseServiceHandle(schService); 
        CloseServiceHandle(schSCManager);
        return; 
    }

    // Save the tick count and initial checkpoint.

    dwStartTickCount = GetTickCount();
    dwOldCheckPoint = ssStatus.dwCheckPoint;

    // Wait for the service to stop before attempting to start it.

    while (ssStatus.dwCurrentState == SERVICE_STOP_PENDING)
    {
        // Do not wait longer than the wait hint. A good interval is 
        // one-tenth of the wait hint but not less than 1 second  
        // and not more than 10 seconds. 

        dwWaitTime = ssStatus.dwWaitHint / 10;

        if( dwWaitTime < 1000 )
            dwWaitTime = 1000;
        else if ( dwWaitTime > 10000 )
            dwWaitTime = 10000;

        Sleep( dwWaitTime );

        // Check the status until the service is no longer stop pending. 

        if (!QueryServiceStatusEx( 
                schService,                     // handle to service 
                SC_STATUS_PROCESS_INFO,         // information level
                (LPBYTE) &ssStatus,             // address of structure
                sizeof(SERVICE_STATUS_PROCESS), // size of structure
                &dwBytesNeeded ) )              // size needed if buffer is too small
        {
            printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
            CloseServiceHandle(schService); 
            CloseServiceHandle(schSCManager);
            return; 
        }

        if ( ssStatus.dwCheckPoint > dwOldCheckPoint )
        {
            // Continue to wait and check.

            dwStartTickCount = GetTickCount();
            dwOldCheckPoint = ssStatus.dwCheckPoint;
        }
        else
        {
            if(GetTickCount()-dwStartTickCount > ssStatus.dwWaitHint)
            {
                printf("Timeout waiting for service to stop\n");
                CloseServiceHandle(schService); 
                CloseServiceHandle(schSCManager);
                return; 
            }
        }
    }

    // Attempt to start the service.

    if (!StartService(
            schService,  // handle to service 
            0,           // number of arguments 
            NULL) )      // no arguments 
    {
        printf("StartService failed (%d)\n", GetLastError());
        CloseServiceHandle(schService); 
        CloseServiceHandle(schSCManager);
        return; 
    }
    else printf("Service start pending...\n"); 

    // Check the status until the service is no longer start pending. 

    if (!QueryServiceStatusEx( 
            schService,                     // handle to service 
            SC_STATUS_PROCESS_INFO,         // info level
            (LPBYTE) &ssStatus,             // address of structure
            sizeof(SERVICE_STATUS_PROCESS), // size of structure
            &dwBytesNeeded ) )              // if buffer too small
    {
        printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
        CloseServiceHandle(schService); 
        CloseServiceHandle(schSCManager);
        return; 
    }

    // Save the tick count and initial checkpoint.

    dwStartTickCount = GetTickCount();
    dwOldCheckPoint = ssStatus.dwCheckPoint;

    while (ssStatus.dwCurrentState == SERVICE_START_PENDING) 
    { 
        // Do not wait longer than the wait hint. A good interval is 
        // one-tenth the wait hint, but no less than 1 second and no 
        // more than 10 seconds. 

        dwWaitTime = ssStatus.dwWaitHint / 10;

        if( dwWaitTime < 1000 )
            dwWaitTime = 1000;
        else if ( dwWaitTime > 10000 )
            dwWaitTime = 10000;

        Sleep( dwWaitTime );

        // Check the status again. 

        if (!QueryServiceStatusEx( 
            schService,             // handle to service 
            SC_STATUS_PROCESS_INFO, // info level
            (LPBYTE) &ssStatus,             // address of structure
            sizeof(SERVICE_STATUS_PROCESS), // size of structure
            &dwBytesNeeded ) )              // if buffer too small
        {
            printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
            break; 
        }

        if ( ssStatus.dwCheckPoint > dwOldCheckPoint )
        {
            // Continue to wait and check.

            dwStartTickCount = GetTickCount();
            dwOldCheckPoint = ssStatus.dwCheckPoint;
        }
        else
        {
            if(GetTickCount()-dwStartTickCount > ssStatus.dwWaitHint)
            {
                // No progress made within the wait hint.
                break;
            }
        }
    } 

    // Determine whether the service is running.

    if (ssStatus.dwCurrentState == SERVICE_RUNNING) 
    {
        printf("Service started successfully.\n"); 
    }
    else 
    { 
        printf("Service not started. \n");
        printf("  Current State: %d\n", ssStatus.dwCurrentState); 
        printf("  Exit Code: %d\n", ssStatus.dwWin32ExitCode); 
        printf("  Check Point: %d\n", ssStatus.dwCheckPoint); 
        printf("  Wait Hint: %d\n", ssStatus.dwWaitHint); 
    } 

    CloseServiceHandle(schService); 
    CloseServiceHandle(schSCManager);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Ok, so like you mentioned above (from the documentation), "The SCM sets the following default status values before returning from StartService: Current state of the service is set to SERVICE_START_PENDING, ...". To me this means that if call StartService and then I immediately call QueryStatus, the status should be SERVICE_START_PENDING. But in my case it's not. It's SERVICE_STOPPED. My question is why does the service not go into the SERVICE_START_PENDING state if StartService retuns true. – conectionist Jun 12 '19 at 14:01
  • @conectionist "*if call StartService and then I immediately call QueryStatus, the status should be SERVICE_START_PENDING. But in my case it's not. It's SERVICE_STOPPED*" - that can happen if the service starts and then immediately stops before you have a chance to query it. Or, if you are querying it wrong, which you did not show that code. "*why does the service not go into the SERVICE_START_PENDING state if StartService retuns true.*" - most likely, it does start and then stops immediately. Does the SERVICE_STOPPED status include an error code? Does the service have a log somewhere? – Remy Lebeau Jun 12 '19 at 16:26
  • I've posted some code and error values. The error code I get with SERVICE_STOPPED status is 31 (ERROR_GEN_FAILURE). The service does not have a log. – conectionist Jun 14 '19 at 13:37
  • "it does start and then stops immediately" - that would be very odd because I added a __debugbreak() right at the beginning of the driver's entry point which is never triggered. I would expect for that break to be triggered and only after that for it to stop somewhere. – conectionist Jun 14 '19 at 13:40
  • @conectionist the fact that `SERVICE_STOPPED` is reporting an error code likely means the service is failing to initialize the driver before it gets that far. You need to debug the service and find it why. Did you check the System log for errors? – Remy Lebeau Jun 14 '19 at 14:57
0

One possible sequence of events that could explain what you see:

  1. You call StartService
  2. Your service starts
  3. Your service quickly crashes
  4. You call QueryServiceStatusEx, which returns SERVICE_STOPPED
  5. Windows restarts the service (because of Recovery settings)
  6. Your service starts normally (doesn't crash this time)

Adding the Sleep() delays the check until after the service started normally the second time.

Check the System area of the Event logs to see if this is happening.

CoreTech
  • 2,345
  • 2
  • 17
  • 24
  • Hmm, interesting. I will check this. But, if the service/driver crashes quickly, shouldn't it still first hit the DriverEntry? This doesn't happen at all. – conectionist Jun 07 '19 at 14:40
  • It doesn't necessarily have to *crash*. If a service cannot initialize itself properly for any reason (bad config, missing dependencies, etc), it could simply set its status to STOPPED and exit immediately, which would not trigger auto-recovery. Maybe the 1st start attempt tried to initialize something on the system that wasn't ready until the 2nd start attempt. – Remy Lebeau Jun 07 '19 at 20:16