Per evitare tutti questi problemi è possibile chiedere supporto all'I/O Manager riguardo l'accesso dei buffer forniti dal client. Come visto nella lezione precedente (si veda [1]) con il codice di controllo è possibile indicare il tipo di trasferimento dei dati da client a driver per una data operazione. Con METHOD_NEITHER si afferma che non si vuole alcun supporto da parte dell'I/O Manager e si vuole accedere ai buffer del client direttamente. Atri valori permettono di attivare tale supporto per DeviceIoControl e relativa dispatch routine. Per quanto riguarda ReadFile\WriteFile e relative dispatch routine il discorso è simile, cambia solo modo ti attivare il supporto da parte dell'I/O Manager. Un'altra buona notizia è che tra del codice che non implementa tale supporto e lo stesso che lo implementa la differenza si riduce a poche righe di codice. Ma prima di vedere degli esempi pratici è necessaria un po' di teoria per analizzare e chiarire come funziona il supporto dell'I/O Manager, in particolare quello che utilizza il trasferimento dei dati noto come Buffered I/O (argomento di questa lezione), sia per ReadFile\WriteFile che per DeviceIoControl.
Il client ha un buffer di input (richiesta in scrittura: WriteFile) o di output (richiesta in lettura: ReadFile) o entrambi (uno di input e uno di output oppure uno solo che sia di input e di output allo stesso tempo: DeviceIoControl).
Se il supporto dell'I/O Manager è attivo ed il tipo di trasferimento dati è Buffered I/O, l'I/O manager alloca memoria nonPaged in kernel space e salva il puntatore a tale buffer di sistema in AssociatedIrp.SystemBuffer della struttura IRP. Con ReadFile\WriteFile la dimensione di tale memoria è uguale a quella del buffer in user space. Inoltre, se il buffer è di input (WriteFile), l'I/O Manager copia il buffer in user space nello spazio allocato in kernel space.
Per DeviceIoControl la dimesione è uguale al quella massima tra il buffer di input e quello di output. Inoltre, l'I/O Manager copia il buffer di input in user space nello spazio allocato in kernel space.
A questo punto il driver può leggere o scrivere il buffer di sistema che, essendo in kernel space e condiviso dagli spazi virtuali, può essere consumato a prescindere da quale sia il thread che gestisce la richiesta. Questo risolve sia il problema del codice eseguito come DPC a IRQL $= 2$ che il problema del buffer liberato prematuramente da un altro thread del client (eventuali eccezioni per dati corrotti o robe simili verranno lanciate in usermode al momento opportuno; per quanto riguarda il driver, il suo lavoro lo fa sul buffer di sistema che è sempre valido e non può essere liberato).
Se la richiesta è stata inoltrata attraverso ReadFile il buffer del client è di output quindi l'I/O Manager copia il buffer di sistema nel buffer in user space. Per DeviceIoControl, l'I/O Manager copia il buffer di sistema nel buffer di output in user space. Il numero di byte copiati, in entrambi i casi, è uguale al valore che si trova nel campo IoStatus.Information della struttura IRP.
Infine l'I/O Manager libera la memoria di sistema allocata ed il client può continuare a leggere o scrivere il suo buffer. Fatta questa doverosa premessa teorica sul Buffered I/O, è arrivato il momento di analizzare quali sono le (poche) modifiche necessarie al codice delle lezioni precedenti.
ReadFile\WriteFile
Per attivare il Buffered I/O è necessario solo effettuare un OR tra il campo Flags dell'oggetto Device in DriverEntry ed il valore DO_BUFFERED_IO
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) { UNREFERENCED_PARAMETER(RegistryPath); // ... DeviceObject->Flags |= DO_BUFFERED_IO; // ... }
Nelle dispatch routine relative agli indici IRP_MJ_READ e IRP_MJ_WRITE le uniche due modifiche riguardano il recupero del buffer e la rimozione del blocco __try\__exception
NTSTATUS RWDriverRead(PDEVICE_OBJECT pDO, PIRP pIrp) { UNREFERENCED_PARAMETER(pDO); KeWaitForMutexObject(&mutex, Executive, KernelMode, FALSE, NULL); NTSTATUS status = STATUS_INVALID_USER_BUFFER; PIO_STACK_LOCATION stack; ULONG len = 0; __try { stack = IoGetCurrentIrpStackLocation(pIrp); len = stack->Parameters.Read.Length; if (len == 0) return CompleteIrp(pIrp, STATUS_INVALID_BUFFER_SIZE, 0); //PVOID buffer = pIrp->UserBuffer; PVOID buffer = pIrp->AssociatedIrp.SystemBuffer; if (!buffer) return CompleteIrp(pIrp, STATUS_INSUFFICIENT_RESOURCES, 0); /*__try { RtlCopyMemory(buffer, pBuffer, len); status = STATUS_SUCCESS; } __except (EXCEPTION_EXECUTE_HANDLER) { status = STATUS_ACCESS_VIOLATION; }*/ RtlCopyMemory(buffer, pBuffer, len); status = STATUS_SUCCESS; } __finally { KeReleaseMutex(&mutex, FALSE); return CompleteIrp(pIrp, status, len); } }
NTSTATUS RWDriverWrite(PDEVICE_OBJECT pDO, PIRP pIrp) { UNREFERENCED_PARAMETER(pDO); KeWaitForMutexObject(&mutex, Executive, KernelMode, FALSE, NULL); NTSTATUS status = STATUS_INVALID_USER_BUFFER; PIO_STACK_LOCATION stack; ULONG len = 0; __try { stack = IoGetCurrentIrpStackLocation(pIrp); len = stack->Parameters.Write.Length; if (len == 0) return CompleteIrp(pIrp, STATUS_INVALID_BUFFER_SIZE, 0); //PVOID buffer = pIrp->UserBuffer; PVOID buffer = pIrp->AssociatedIrp.SystemBuffer; if (!buffer) return CompleteIrp(pIrp, STATUS_INSUFFICIENT_RESOURCES, 0); /*__try { RtlCopyMemory(pBuffer, buffer, len); status = STATUS_SUCCESS; } __except (EXCEPTION_EXECUTE_HANDLER) { status = STATUS_ACCESS_VIOLATION; }*/ RtlCopyMemory(pBuffer, buffer, len); status = STATUS_SUCCESS; } __finally { KeReleaseMutex(&mutex, FALSE); return CompleteIrp(pIrp, status, len); } }
Il blocco __try/__catch non serve più perché è stato l' I/O Manager a creare lo spazio per il buffer di sistema in kernel space e quindi avrà sempre un indirizzo valido. Il resto è esattamente uguale al codice della lezione precedente (si veda [2]) che non usava il supporto dell'I/O Manager. In particolare, il codice del client resta totalmente invariato.
DeviceIoControl
Per attivare il Buffered I/O è necessario specificare il valore METHOD_BUFFERED come terzo argomento della macro CTL_CODE
#define IOCTL_DICDRIVER_INC_VALUE CTL_CODE(DICDRIVER_DEVICE, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
Nella dispatch routine relativa all'indice IRP_MJ_DEVICE_CONTROL le uniche due modifiche riguardano il recupero dei buffer di input e output e la rimozione del blocco __try\__exception. In particolare, si noti che non si lavora più con due buffer separati: il buffer di sistema è trattato come di input fino alla prima scrittura, dopodiché si considera di output.
NTSTATUS DICDriverDeviceIoControl(PDEVICE_OBJECT pDO, PIRP Irp) { UNREFERENCED_PARAMETER(pDO); PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp); NTSTATUS status = STATUS_SUCCESS; ULONG len = 0; switch (stack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_DICDRIVER_INC_VALUE: { if (stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(ULONG) || (len = stack->Parameters.DeviceIoControl.OutputBufferLength) < sizeof(ULONG)) { status = STATUS_BUFFER_TOO_SMALL; break; } //PULONG iBuffer = (PULONG)stack->Parameters.DeviceIoControl.Type3InputBuffer; PULONG iBuffer = (PULONG)Irp->AssociatedIrp.SystemBuffer; if (iBuffer == NULL) { status = STATUS_INVALID_PARAMETER; break; } /*PULONG oBuffer = (PULONG)Irp->UserBuffer; if (oBuffer == NULL) { status = STATUS_INVALID_PARAMETER; break; }*/ /*__try { if (*iBuffer < (ULONG)(-1)) { *oBuffer = ++(*iBuffer); break; } else status = STATUS_INVALID_USER_BUFFER; } __except (EXCEPTION_EXECUTE_HANDLER) { // something wrong with the buffer status = STATUS_ACCESS_VIOLATION; }*/ if (*iBuffer < (ULONG)(-1)) { ++(*iBuffer); break; } else status = STATUS_INVALID_USER_BUFFER; break; } default: { status = STATUS_INVALID_DEVICE_REQUEST; break; } } // Irp->IoStatus.Information ritornato da I/O Manager in lpBytesReturned fornito da client in DeviceIoControl return CompleteIrp(Irp, status, len); }
Il resto è esattamente uguale al codice della lezione precedente (si veda [1]) che non usava il supporto dell'I/O Manager. In particolare, il codice del client resta totalmente invariato.
Codice sorgente:
RWBIODriver.zip
DICBIODriver.zip
Riferimenti:
[1] 07 - Comunicazione Client / Driver: DeviceIoControl
[2] 06 - Comunicazione Client \ Driver: ReadFile e WriteFile
[3] 01 - Cenni preliminari
[4] Windows Kernel Programming - Pavel Yosifovich
Nessun commento:
Posta un commento