Nenhuma das operações de E/S
Um sistema de arquivos deve lidar com operações que normalmente envolvem a manipulação direta de buffers de usuário. Essas operações são inerentemente arriscadas porque o endereço do usuário pode não ser válido. Os sistemas de arquivos devem estar particularmente conscientes dessas operações e garantir que eles as protejam adequadamente. As operações a seguir dependem do membro Flags do objeto de dispositivo do sistema de arquivos para especificar como o gerenciador de E/S deve transferir dados entre o espaço de endereço do usuário e do kernel:
Normalmente, um sistema de arquivos não escolhe nem E/S implicitamente definindo nem DO_DIRECT_IO nem DO_BUFFERED_IO no membro Flags do objeto de dispositivo de volume que ele cria.
A operação a seguir ignora o membro Flags do objeto de dispositivo do sistema de arquivos e não usa nenhuma E/S para transferir dados entre o espaço de endereço do usuário e do kernel:
Usando nem E/S, o sistema de arquivos é responsável por lidar com suas próprias operações de transferência de dados. Isso permite que um sistema de arquivos atenda a uma operação colocando diretamente os dados no buffer de espaço do usuário de um aplicativo. Assim, o sistema de arquivos deve garantir que o buffer do usuário seja válido quando a operação começar e manipule normalmente o buffer se tornando inválido enquanto a operação está em andamento. E/S rápida também passa ponteiros brutos. Os desenvolvedores devem estar cientes de que verificar a validade do buffer no início da operação não é suficiente para garantir que ele permaneça válido durante toda a operação. Por exemplo, um aplicativo mal-intencionado pode mapear um bloco de memória (por meio de uma seção, por exemplo), emitir uma operação de E/S e desmapear o bloco de memória enquanto a operação de E/S está em andamento.
Há várias maneiras de um sistema de arquivos lidar com essa situação. Um mecanismo é bloquear a memória física que corresponde ao endereço do usuário e criar um segundo mapeamento no espaço de endereço do sistema operacional. Isso garante que o sistema de arquivos use um endereço virtual que ele controla. Portanto, mesmo que o endereço do usuário se torne inválido, o endereço criado pelo sistema de arquivos permanecerá válido. O código do sistema de arquivos FASTFAT usa duas funções diferentes para conseguir isso. A primeira função bloqueia o buffer do usuário:
VOID
FatLockUserBuffer (
IN PIRP_CONTEXT IrpContext,
IN OUT PIRP Irp,
IN LOCK_OPERATION Operation,
IN ULONG BufferLength
)
/*++
Routine Description:
This routine locks the specified buffer for the specified type of
access. The file system requires this routine because it does not
ask the I/O system to lock its buffers for direct I/O. This routine
can only be called from the file system driver (FSD) while still in the user context.
Note that this is the *input/output* buffer.
Arguments:
Irp - Pointer to the Irp for which the buffer will be locked.
Operation - IoWriteAccess for read operations, or IoReadAccess for
write operations.
BufferLength - Length of user buffer.
Return Value:
None
--*/
{
PMDL Mdl = NULL;
if (Irp->MdlAddress == NULL) {
//
// Allocate the Mdl and Raise if the allocation fails.
//
Mdl = IoAllocateMdl( Irp->UserBuffer, BufferLength, FALSE, FALSE, Irp );
if (Mdl == NULL) {
FatRaiseStatus( IrpContext, STATUS_INSUFFICIENT_RESOURCES );
}
//
// now probe the buffer described by the Irp. If there is an exception,
// deallocate the Mdl and return the appropriate "expected" status.
//
try {
MmProbeAndLockPages( Mdl,
Irp->RequestorMode,
Operation );
} except(EXCEPTION_EXECUTE_HANDLER) {
NTSTATUS Status;
Status = GetExceptionCode();
IoFreeMdl( Mdl );
Irp->MdlAddress = NULL;
FatRaiseStatus( IrpContext,
FsRtlIsNtstatusExpected(Status) ? Status : STATUS_INVALID_USER_BUFFER );
}
}
UNREFERENCED_PARAMETER( IrpContext );
}
Essa rotina garante que a memória física que faz backup do endereço de um usuário não será reutilizado para nenhuma outra finalidade enquanto a operação estiver em andamento. Um sistema de arquivos pode fazer isso para enviar a operação de E/S para a camada de classe de disco ou gerenciamento de volume subjacente para atender a uma E/S de usuário não armazenada em cache. Nesse caso, o sistema de arquivos não precisa de seu próprio endereço virtual para o buffer. Uma segunda função cria o mapeamento do sistema de arquivos para o espaço de endereço do kernel:
PVOID
FatMapUserBuffer (
IN PIRP_CONTEXT IrpContext,
IN OUT PIRP Irp
)
/*++
Routine Description:
This routine conditionally maps the user buffer for the current I/O
request in the specified mode. If the buffer is already mapped, it
just returns its address.
Note that this is the *input/output* buffer.
Arguments:
Irp - Pointer to the Irp for the request.
Return Value:
Mapped address
--*/
{
UNREFERENCED_PARAMETER( IrpContext );
//
// If there is no Mdl, then we must be in the FSD, and can simply
// return the UserBuffer field from the Irp.
//
if (Irp->MdlAddress == NULL) {
return Irp->UserBuffer;
} else {
PVOID Address = MmGetSystemAddressForMdlSafe( Irp->MdlAddress, NormalPagePriority );
if (Address == NULL) {
ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
}
return Address;
}
}
A implementação fastfat permite que a segunda rotina retorne o endereço no nível do usuário também, o que exige que o sistema de arquivos FAT garanta que o endereço retornado (usuário ou kernel) deve ser válido. Ele faz isso usando as palavras-chave __try e __except para criar um bloco protegido.
Essas rotinas estão no arquivo de origem deviosup.c das amostras de fastfat que o WDK contém.
Outro problema crítico ocorre quando a solicitação não é atendida no contexto do chamador. Se um sistema de arquivos postar a solicitação em um thread de trabalho, o driver deverá bloquear o buffer com um MDL para não perder o controle dele. A função FatPrePostIrp no arquivo de origem workque.c dos exemplos de fastfat fornece um exemplo de como esse problema é tratado pelo sistema de arquivos FASTFAT.
O sistema de arquivos FASTFAT protege contra uma ampla gama de falhas, não apenas buffers de usuário inválidos, usando essas rotinas. Embora essa seja uma técnica muito poderosa, ela também envolve garantir que todos os blocos de código protegidos liberem corretamente todos os recursos que possam estar mantendo. Os recursos a serem liberados incluem memória, objetos de sincronização ou algum outro recurso do próprio sistema de arquivos. Uma falha ao fazer isso daria a um suposto invasor a capacidade de causar fome de recursos fazendo muitas chamadas repetitivas para o sistema operacional para esgotar o recurso.