giovedì 18 luglio 2019

04 - Comunicazione Client / Driver: Device

Come detto in una lezione precedente (si veda [1]), una applicazione in usermode può comunicare con un driver attraverso il device che quel driver gestisce.

// In UserMode
 
 HANDLE hDevice = CreateFile(L"\\\\.\\MyDevice"GENERIC_WRITEFILE_SHARE_WRITEnullptrOPEN_EXISTING, 0, nullptr);
 
 // ...
 
 // Usa l'handle al device per dialogare con il driver
 
 // ...
 
 
 CloseHandle(hDevice);


La funzione CreateFile è usata in usermode per restituire un handle al device object che ci interessa. Il nome di questa funzione, comunque, non deve trarre in inganno. I file sono oggetti kernel e CreateFile si occupa in realtà di oggetti nella sua accezione più generale e non solo di file.
Come si può facilmente intuire, da usermode non è possibile fare riferimento ad oggetti del kernel direttamente quindi, come primo parametro, si passa a CreateFile un symbolic link che altro non è che un oggetto kernel che può essere usato in usermode e che punta all'oggetto kernel reale. Solitamente i symbolic link si trovano nella directory ?? dell'Object Manager e puntano ad oggetti in altre directory. Nel nostro caso i device sono, di norma, nella cartella Device, ma non è obbligatorio. Come si può notare, il primo parametro di CreateFile è una stringa che incomincia con "\\.\". Questo indica che quello che segue, in questo caso MyDevice, deve essere interpretato come un symbolic link e non come il nome di un file nella directory corrente. In realtà, quando si usa CreateFile nel modo "classico", per aprire cioè un file, il primo parametro (il nome del file) è comunque un symbolic link che punta ad un file object nella cartella FileSystem. Dato che una funzione che ritorna un handle ad un oggetto kernel sarebbe stata utilizzata, nella maggior parte dei casi, da programmatori di sistema per ottenere un handle ad un oggetto file è evidente come Microsoft abbia optato per il nome CreateFile e per un passaggio di parametri più semplificato per questo tipo di oggetto. Da notare anche come gli altri parametri di CreateFile abbiano lo stesso significato sia per i device che per i file in quanto, come già detto più volte, un file object è in realtà solo un caso particolare di oggetto kernel proprio come lo è un device object. Un device object quindi può essere aperto in lettura, scrittura o entrambi.




Una delle prime cose da fare nel driver è quindi quella di creare un device ed un symbolic link che punti ad esso.


DriverEntry(_In_ PDRIVER_OBJECT DriverObject_In_ PUNICODE_STRING RegistryPath)
{ 
 UNREFERENCED_PARAMETER(RegistryPath);
 
 // Imposta funzione chiamata quando driver verrà stoppato.
 // Si occuperà di cancellare il device ed il symbolic link creati in DriverEntry.
 DriverObject->DriverUnload = MyDriverUnload;
 
 // Invocate quando in usermode vengono chiamate CreateFile e CloseHandle
 DriverObject->MajorFunction[IRP_MJ_CREATE] = MyDriverCreateClose;
 DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyDriverCreateClose;
 
 // istruzione seguente equivale a 
 // RtlInitUnicodeString(&devName, L"\\Device\\MyDevice");
 // ma è più efficiente in quanto RtlInitUnicodeString calcola la lunghezza da inserire in UNICODE_STRING.Lenght a runtime
 // mentre la macro RTL_CONSTANT_STRING lo fa a compile-time
 UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\MyDevice");
 
 // crea device object
 PDEVICE_OBJECT DeviceObject;
 NTSTATUS status = IoCreateDevice(DriverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);
 if (!NT_SUCCESS(status)) {
  KdPrint(("Failed to create device (0x%08X)\n", status));
  return status;
 }
 
 // crea symbolic link
 UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\MyDevice");
 status = IoCreateSymbolicLink(&symLink, &devName);
 if (!NT_SUCCESS(status)) {
  KdPrint(("Failed to create symbolic link (0x%08X)\n", status));
  IoDeleteDevice(DeviceObject);
  return status; 
 }
}

NTSTATUS MyDriverCreateClose(PDEVICE_OBJECT DeviceObjectPIRP Irp)
{
 UNREFERENCED_PARAMETER(DeviceObject);
 
 Irp->IoStatus.Status = STATUS_SUCCESS;
 Irp->IoStatus.Information = 0;
 IoCompleteRequest(IrpIO_NO_INCREMENT);
 return STATUS_SUCCESS;
}

void MyDriverUnload(_In_ PDRIVER_OBJECT DriverObject)
{
 // cancella symbolic link
 UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\MyDevice");
 IoDeleteSymbolicLink(&symLink);
 
 // cancella device object
 IoDeleteDevice(DriverObject->DeviceObject);
}


La prima cosa interessante da notare è l'array DriverObject->MajorFunction.
Si tratta di un array di puntatori a funzione (chiamate Dispatch Routines) da inizializzare opportunamente per fare in modo che la comunicazione Client / Driver avvenga in modo coretto (si riprenderà questo discorso in maniera più approfondita in una prossima lezione). In questo caso l'array viene inizializzato nei suoi elementi con indici IRP_MJ_CREATE e IRP_MJ_CLOSE con l'indirizzo della funzione MyDriverCreateClose. Questa verrà invocata quando in usermode si chiameranno CreateFile o CloseHandle usando il symbolic link o l'handle, rispettivamente, al device object che questo driver gestisce. Come si può notare, MyDriverCreateClose "gestisce" sia CreateFile che CloseHandle in quanto, in realtà, non è direttamente responsabile di creare e ritornare l'handle al device object (in conseguenza alla chiamata a CreateFile) o di decrementarne il contatore dei riferimenti (in conseguenza alla chiamata a CloseHandle). Entrambe le operazioni sono gestite dall'I/O Manager che si occupa della comunicazione a basso livello tra client e driver, ed è lui, infatti, che si occupa di restituire un handle valido al client e a decrementarne il contatore quando il client chiama CloseHandle. MajorFunction[IRP_MJ_CREATE] e MajorFunction[IRP_MJ_CLOSE], piuttosto, si occupano semplicemente di segnalare all'I/O Manager che l'operazione di apertura\richiesta o chiusura\decremento dell'handle ha avuto successo o meno (i dettagli sulle istruzione in MyDriverCreateClose verranno ripresi nella prossima lezione quando si parlerà di IRP). A questo punto l'I/O Manager si occupa di rispondere in modo adeguato al client, restituendogli l'handle o decrementandone il contatore. Per questi motivi MajorFunction[IRP_MJ_CREATE] e MajorFunction[IRP_MJ_CLOSE] devono sempre essere inizializzati altrimenti il client non avrà modo di ottenere un handle al device object e di decrementarne il contatore.

Successivamente si passa alla creazione del device attraverso la funzione IoCreateDevice.
NTSTATUS IoCreateDevice(
 PDRIVER_OBJECT  DriverObject,
 ULONG           DeviceExtensionSize,
 PUNICODE_STRING DeviceName,
 DEVICE_TYPE     DeviceType,
 ULONG           DeviceCharacteristics,
 BOOLEAN         Exclusive,
 PDEVICE_OBJECT  *DeviceObject
);

DriverObject è il driver object che gestirà tale device. Si tratta dell'oggetto che rappresenta questo stesso driver e che viene passato a DriverEntry.
DeviceExtensionSize indica quanti byte di informazioni extra aggiungere allo spazio riservato per il device object che si sta creando (non importante al momento).
DeviceName è il nome (compreso di percorso) del device object.
DeviceType è quasi sempre FILE_DEVICE_UNKNOWN per i device puramente software (come in questo caso).
DeviceCharacteristics è quasi sempre 0 per i device puramente software (come in questo caso).
Exclusive indica se un solo client ha il permesso di accedere al device o se sono possibili accessi multipli allo stesso tempo.
DeviceObject è dove l'Object Manager salva il puntatore al device object appena creato.

IoCreateSymbolicLink crea il symbolic link nella directory ?? dell'Object Manager.

Da notare come device object e symbolic link vengano cancellati in MyDriverUnload, che è la funzione puntata da DriverObject->DriverUnload. Tale funzione dovrebbe occuparsi di liberare anche tutte le altre eventuali risorse allocate in DriverEntry, oltre a quelle appena citate.




Riferimenti:

[1] 01 - Concetti Preliminari
[2] Windows Kernel Programming - Pavel Yosifovich

Nessun commento:

Posta un commento