Safetica > Resources > Como mexemos com drivers de arranque

Como mexemos com drivers de arranque

Um dos nossos clientes reportou recentemente um problema ao executar o nosso software em conjunto com o VMware vShield. Embora a solução se tenha revelado bastante simples, o processo de investigação merece um artigo de blogue, uma vez que revelou alguns factos interessantes relacionados com o comportamento dos drivers no momento do arranque.

A ideia principal por detrás do VMware vShield é fornecer serviços de antivírus a máquinas virtuais sem instalar uma solução completa de antivírus nos sistemas operativos convidados. Os dados de entrada das máquinas virtuais são obtidos a partir de um pequeno driver chamado vsepflt.sys e designado vShield Endpoint Thin Agent. O driver regista-se como um minifilter de sistema de ficheiros e depois comunica com a solução de antivírus no sistema anfitrião. Infelizmente, quando o nosso software estava a ser executado dentro de uma máquina virtual, o driver vsepflt.sys recusava-se misteriosamente a iniciar no momento do arranque, embora funcionasse bem quando carregado manualmente.

Achámos que a solução de carregamento manual não era suficientemente boa, porque, embora provavelmente funcionasse bem na prática, podia indicar algo desagradável dentro do nosso software. Tudo o que sabíamos era que a inicialização do driver vsepflt.sys falhava com o código de erro 0x80000011 (STATUS_DEVICE_BUSY).

Como não havia indicação direta nem indireta de onde poderia estar o problema no nosso software e não era facilmente possível examinar as condições exatas na máquina do cliente durante o arranque do sistema, decidi analisar um pouco o driver vsepflt.sys. O driver (pelo menos as suas rotinas de inicialização) revelou-se bastante bem escrito — todos os caminhos de erro na sua função DriverEntry (a rotina responsável pela inicialização do driver) pareciam produzir uma impressão de debug a indicar o que provavelmente correu mal. Os nomes das rotinas eram normalmente incluídos nas mensagens de debug, o que tornava o código mais legível. Além disso, as rotinas responsáveis pelas impressões de debug sugeriam que as mensagens não são apenas enviadas para o depurador, mas também para outro local fora da máquina virtual onde o cliente as pode recolher e fornecer-nos ( ativar o registo de debug para o driver thin agent VMware Tools vShield Endpoint ). Passado algum tempo isto realmente aconteceu e, depois de instruir o VMware a produzir registos mais detalhados, foi descoberta a seguinte mensagem de debug:

Listagem 1: Mensagens de debug do vsepflt.sys

DEBUG: VFileUmcReadParams : umcMsgTimeoutMs: 2000
DEBUG: VFileUmcReadParams : umcMsgTimeout100Ns: -20000000
INFO: DriverEntry : Created EPSec control device: 0xfffffa8003e1a6a0.
DEBUG: VSockLayer_Init : Initializing VSockLayer module
ERROR: VSockLayer_Init : VMCISock_WskCaptureProviderNPI: 0x80000011
ERROR: VFileSocketMgr_Init : Failed to initialize vsock layer: 0x80000011
ERROR: DriverEntry : VFileSocketMgr_Init() failed: 0x80000011
AUDIT: DriverEntry : vfileFilter build-2530600 load failed: 0x80000011

A mensagem indicava que uma chamada à rotina WskCaptureProviderNPI falhou com um código de erro STATUS_DEVICE_BUSY. Aqueles de vós familiarizados com a interface tipo socket Winsock Kernel , introduzida no Windows Vista, que permite a componentes do kernel comunicar convenientemente em rede, sabem que a rotina com o mesmo nome precisa de ser chamada quando se inicializa o lado cliente da interface (registando uma aplicação Winsock Kernel). Contudo, não se revelou assim tão simples. O driver vsepflt.sys não usa Winsock Kernel (isso causaria problemas de compatibilidade com versões anteriores ao Vista) e nenhuma das rotinas de inicialização WSK (WskRegister, WskCaptureProviderNPI) contém um caminho de código que obviamente contivesse o código de erro que tínhamos visto. Por isso, foi necessária uma análise mais profunda.

Verificou-se que o driver vsepflt.sys usa a interface VMware vSockets para comunicar com software no sistema operativo anfitrião. VMware vSockets é uma interface tipo socket feita especificamente para esse propósito. Consiste num driver de kernel e numa DLL que se regista como Winsock Service Provider (WSP), para que uma aplicação possa usar funções socket padrão para utilizar a interface; basta especificar uma constante de família de endereços especial ao criar um socket. A boa notícia era que a interface vSockets está documentada. Infelizmente, havia também más notícias: a documentação afirma claramente que a interface se destina a ser usada apenas por aplicações e não por componentes do kernel de terceiros.

Isso causou um problema considerável, uma vez que tínhamos especulado sobre criar um driver minifilter de sistema de ficheiros simples que agisse como o vsepflt.sys, pelo menos durante a fase de inicialização. Felizmente, sabíamos com bastante precisão onde as coisas corriam mal dentro do vsepflt.sys (as mensagens de debug ajudaram aqui). Mais reverse engineering revelou que a falha é causada pelo envio de um pedido IOCTL para o objeto de dispositivo chamado \Device\vmci, servido por \Driver\vsock, o driver que implementa a parte kernel do VMware vSockets. A descoberta permitiu-nos criar um driver de teste que tentasse enviar o mesmo IOCTL durante a sua inicialização. O código que envia o IOCTL é mostrado na Listagem 2. Como pode ver, o IOCTL não é muito difícil de construir, uma vez que basta zerar todo o buffer de entrada exceto o primeiro DWORD, que precisa de ser definido como 1. As coisas são um pouco mais complicadas dentro do vsepflt.sys; provavelmente existe uma biblioteca estática que trata das operações vSockets, mas isso pouco interessa se só estivermos interessados em enviar o IOCTL.

Listagem 2: Envio do IOCTL tipo WskCaptureProviderNPI (para versões 32 bits do Windows)

NTSTATUS _ConnectVSocks(void)
{
  LARGE_INTEGER timeout;
  PIRP irp = NULL;
  PIO_STACK_LOCATION irpStack = NULL;
  KEVENT event;
  IO_STATUS_BLOCK iosb;
  PDEVICE_OBJECT deviceObject = NULL;
  PFILE_OBJECT fileObject = NULL;
  UNICODE_STRING uDeviceName;
  NTSTATUS status = STATUS_UNSUCCESSFUL;

  timeout.QuadPart = -10000000;
  RtlInitUnicodeString(&uDeviceName, L"\\DosDevices\\vmci");
  for (SIZE_T i = 0; i < 100; ++i) {
    status = IoGetDeviceObjectPointer(&uDeviceName, 0x1, &fileObject, &deviceObject);
    if (NT_SUCCESS(status)) {
      ULONG inputBuffer[0x5C / sizeof(ULONG)];

      memset(inputBuffer, 0, 0x5C);
      inputBuffer[0] = 1;
      KeInitializeEvent(&event, NotificationEvent, FALSE);

      irp = IoBuildDeviceIoControlRequest(0x810320C0, deviceObject, inputBuffer, sizeof(inputBuffer), inputBuffer, sizeof(inputBuffer), FALSE, &event, &iosb);
      if (irp != NULL) {
        irpStack = IoGetNextIrpStackLocation(irp);
        irpStack->FileObject = fileObject;
        status = IoCallDriver(deviceObject, irp);
        if (status == STATUS_PENDING) {
          (VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
          status = iosb.Status;
        }

        if (NT_SUCCESS(status))
          break;
      } else {
        status = STATUS_INSUFFICIENT_RESOURCES;
        ObDereferenceObject(fileObject);
      }
    }

    if (!NT_SUCCESS(status))
      KeDelayExecutionThread(KernelMode, FALSE, &timeout);
  }

  return status;
}

 

Depois do driver de teste estar construído, conseguimos reproduzir com sucesso o problema no nosso ambiente. Quando o nosso software estava em execução, o IOCTL falhava sempre com STATUS_DEVICE_BUSY, independentemente de quantas tentativas fizéssemos (a Listagem 2 indica que o driver de teste tenta enviar o IOCTL 100 vezes antes de desistir). A melhor coisa do driver de teste, contudo, era que conseguíamos examinar o estado do sistema operativo. A coisa mais interessante que encontrámos foi a lista de drivers de boot-start inicializados (ou em processo de inicialização) mostrada na Listagem 3. Para comparar a lista com uma máquina que apenas contém o VMware Tools, veja a Listagem 4. Decidi listar objetos de driver, e não ficheiros de driver mapeados, para ter uma melhor ideia de quais já estavam inicializados.

Listagem 3: Drivers inicializados cedo no momento do arranque numa máquina a executar o nosso software

0: kd> !object \Driver
Object: 8c458b08  Type: (85547b50) Directory
   ObjectHeader: 8c458af0 (new version)
   HandleCount: 0  PointerCount: 23
   Directory Object: 8c408ed0  Name: Driver

   Hash Address  Type          Name
   ---- -------  ----          ----
    01  855d2a70 Driver        LSI_SAS
    04  8556bf38 Driver        vmbus
        8555c798 Driver        Compbatt
        85628f38 Driver        msisadrv
    05  855665c8 Driver        mountmgr
    08  8556b788 Driver        atapi
    09  8555d7d8 Driver        volmgrx
    11  8556da20 Driver        amdxata
    16  8556b670 Driver        vsock
    17  85566e40 Driver        vmci
    18  856481b8 Driver        00000142
        855f18a8 Driver        WMIxWDM
        855f92d8 Driver        ACPI_HAL
    25  8555c670 Driver        volmgr
    29  85628978 Driver        pci
    30  8555ce10 Driver        partmgr
    32  8554c908 Driver        ACPI
        8556d030 Driver        msahci
        8554d320 Driver        Wdf01000
    33  85619668 Driver        PnpManager
    34  856287d0 Driver        vdrvroot
    36  85566f38 Driver        intelide
0: kd> !drvobj \Driver\vmci
Driver object (85566e40) is for:
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for vmci.sys -
 \Driver\vmci
Driver Extension List: (id , addr)

Device Object list:

0: kd> !object \Global??\VMCIDev
Object Global??\VMCIDev not found

 

Listagem 4: Lista de drivers de arranque inicializados numa máquina quase limpa

0: kd> !object \Driver
Object: 8c067dd8  Type: (851ce040) Directory
   ObjectHeader: 8c067dc0 (new version)
   HandleCount: 0  PointerCount: 23
   Directory Object: 8c004e18  Name: Driver

   Hash Address  Type          Name
   ---- -------  ----          ----
    01  85825460 Driver        LSI_SAS
    04  8539f1e8 Driver        vmbus
        85b60730 Driver        Compbatt
        85ca3330 Driver        msisadrv
    05  8539b1e8 Driver        mountmgr
    08  8537f1e8 Driver        atapi
    09  85cf1998 Driver        volmgrx
    11  85213568 Driver        amdxata
    16  8539e1e8 Driver        vsock
    17  853291e8 Driver        vmci
    18  852471b8 Driver        00000142
        85230030 Driver        WMIxWDM
        8522f908 Driver        ACPI_HAL
    25  85b17030 Driver        volmgr
    29  858f3088 Driver        pci
    30  85a4a728 Driver        partmgr
    32  853971e8 Driver        msahci
        8514c8c0 Driver        ACPI
        85235db8 Driver        Wdf01000
    33  85222ce0 Driver        PnpManager
    34  85be4e88 Driver        vdrvroot
    36  853411e8 Driver        intelide
0: kd> !drvobj \Driver\vmci
Driver object (853291e8) is for:
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for vmci.sys -
 \Driver\vmci
Driver Extension List: (id , addr)

Device Object list:
8584c6e8  8584c890
0: kd> !object \Global??\VMCIDev
Object: 8c1cb978  Type: (851cef78) SymbolicLink
   ObjectHeader: 8c1cb960 (new version)
   HandleCount: 0  PointerCount: 1
   Directory Object: 8c008d20  Name: VMCIDev
   Target String is '\Device\VMCIHostDev'
0: kd> !devobj \Device\VMCIHostDev
Device object (8584c890) is for:
 VMCIHostDev \Driver\vmci DriverObject 853291e8
Current Irp 00000000 RefCount 0 Type 00000004 Flags 00002040
Dacl 8c1fbca8 DevExt 8584c948 DevObjExt 8584c9c8
ExtensionFlags (0000000000)
Characteristics (0x00000100)  FILE_DEVICE_SECURE_OPEN
AttachedTo (Lower) 8522f030 \Driver\PnpManager
Device queue is not busy.

 

O estranho na Listagem 3 era que nenhum dos nossos drivers estava efetivamente carregado, embora estivessem registados como drivers de arranque. O driver vsock.sys estava carregado e provavelmente inicializado o suficiente para receber pedidos IOCTL. Decidi dar uma vista de olhos rápida também a esse driver, apenas para verificar se havia dependências óbvias com outros drivers. E havia uma.

O driver vsock.sys comunica com outro driver da VMware, vmci.sys (\Driver\vmci, não confundir com \Device\vmci pertencente a \Driver\vsock) que também é iniciado no momento do arranque. A ligação simbólica \DosDevices\VMCIDev é usada para estabelecer um canal de comunicação com o driver. Como se vê na Listagem 3, essa ligação simbólica não existe, enquanto está presente na Listagem 4. Por isso, deduzi que o nosso software de alguma forma impede o driver vmci.sys de criar os seus objetos de dispositivo suficientemente cedo.

O driver vmci.sys serve um dispositivo virtual PnP da classe System setup que provavelmente representa um dispositivo de bus raiz para toda a atividade relacionada com o VMware Tools. Como um dos nossos drivers se regista como upper filter para esta classe de setup, a inicialização do driver vmci.sys foi adiada até que todos os upper filters estivessem prontos ( especificar a Driver Load Order ). É exatamente por isso que o driver não estava pronto quando era necessário ao vsock.sys. E foi isso que causou o problema de STATUS_DEVICE_BUSY. Lembre-se de que a Listagem 3 mostra que nenhum dos nossos drivers é carregado tão cedo durante o arranque.

Toda a história teve afinal o seu final feliz. Propusemos duas soluções para o problema: remover o nosso upper filter para a classe System device setup ou mover os nossos drivers para um grupo de ordem de carregamento, como o System Boot Extender, que carrega mais cedo no arranque. Aprendemos novamente que nunca se é demasiado cuidadoso quando se trata da inicialização de drivers de arranque. Mexer com a ordem de carregamento no momento do arranque pode não só pôr o sistema num estado em que não consegue arrancar, como também levar a problemas com causas muito menos óbvias.

Por fim, gostaríamos de agradecer ao nosso cliente por ter sido realmente prestável quando precisámos de informação adicional relacionada com o problema. Nem sempre é o caso.

 


 

Escrito por Martin Drab, Developer @Safetica Technologies

O Martin escava sempre fundo para saber como as coisas funcionam por dentro. Especialmente interessado em programação de baixo nível e de kernel em Windows, mantém partes essenciais do Device Control para si. Código a funcionar sem falhas, um artigo técnico interessante e um bom tempo a nadar são as coisas que mais lhe agradam.

Similar posts