mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 08:23:05 -05:00
458 lines
17 KiB
C
458 lines
17 KiB
C
/**
|
|
* @file sys/mup.c
|
|
*
|
|
* @copyright 2015-2022 Bill Zissimopoulos
|
|
*/
|
|
/*
|
|
* This file is part of WinFsp.
|
|
*
|
|
* You can redistribute it and/or modify it under the terms of the GNU
|
|
* General Public License version 3 as published by the Free Software
|
|
* Foundation.
|
|
*
|
|
* Licensees holding a valid commercial license may use this software
|
|
* in accordance with the commercial license agreement provided in
|
|
* conjunction with the software. The terms and conditions of any such
|
|
* commercial license agreement shall govern, supersede, and render
|
|
* ineffective any application of the GPLv3 license to this software,
|
|
* notwithstanding of any reference thereto in the software or
|
|
* associated repository.
|
|
*/
|
|
|
|
#include <sys/driver.h>
|
|
|
|
/*
|
|
* FSP_MUP_PREFIX_CLASS
|
|
*
|
|
* Define the following macro to claim "class" prefixes during prefix
|
|
* resolution. A "class" prefix is of the form \ClassName. The alternative
|
|
* is a "full" prefix, which is of the form \ClassName\InstanceName.
|
|
*
|
|
* Claiming a class prefix has advantages and disadvantages. The main
|
|
* advantage is that by claiming a \ClassName prefix, paths such as
|
|
* \ClassName\IPC$ will be handled by WinFsp, thus speeding up prefix
|
|
* resolution for all \ClassName prefixed names. The disadvantage is
|
|
* it is no longer possible for WinFsp and another redirector to handle
|
|
* instances ("shares") under the same \ClassName prefix.
|
|
*/
|
|
#define FSP_MUP_PREFIX_CLASS
|
|
|
|
static NTSTATUS FspMupGetClassName(
|
|
PUNICODE_STRING Prefix, PUNICODE_STRING ClassName);
|
|
NTSTATUS FspMupRegister(
|
|
PDEVICE_OBJECT FsmupDeviceObject, PDEVICE_OBJECT FsvolDeviceObject);
|
|
VOID FspMupUnregister(
|
|
PDEVICE_OBJECT FsmupDeviceObject, PDEVICE_OBJECT FsvolDeviceObject);
|
|
PDEVICE_OBJECT FspMupGetFsvolDeviceObject(
|
|
PFILE_OBJECT FileObject);
|
|
NTSTATUS FspMupHandleIrp(
|
|
PDEVICE_OBJECT FsmupDeviceObject, PIRP Irp);
|
|
static NTSTATUS FspMupRedirQueryPathEx(
|
|
PDEVICE_OBJECT FsmupDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp);
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, FspMupGetClassName)
|
|
#pragma alloc_text(PAGE, FspMupRegister)
|
|
#pragma alloc_text(PAGE, FspMupUnregister)
|
|
#pragma alloc_text(PAGE, FspMupGetFsvolDeviceObject)
|
|
#pragma alloc_text(PAGE, FspMupHandleIrp)
|
|
#pragma alloc_text(PAGE, FspMupRedirQueryPathEx)
|
|
#endif
|
|
|
|
typedef struct _FSP_MUP_CLASS
|
|
{
|
|
LONG RefCount;
|
|
UNICODE_STRING Name;
|
|
UNICODE_PREFIX_TABLE_ENTRY Entry;
|
|
WCHAR Buffer[];
|
|
} FSP_MUP_CLASS;
|
|
|
|
static NTSTATUS FspMupGetClassName(
|
|
PUNICODE_STRING VolumePrefix, PUNICODE_STRING ClassName)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
RtlZeroMemory(ClassName, sizeof *ClassName);
|
|
|
|
if (L'\\' == VolumePrefix->Buffer[0])
|
|
for (PWSTR P = VolumePrefix->Buffer + 1,
|
|
EndP = VolumePrefix->Buffer + VolumePrefix->Length / sizeof(WCHAR);
|
|
EndP > P; P++)
|
|
{
|
|
if (L'\\' == *P)
|
|
{
|
|
ClassName->Buffer = VolumePrefix->Buffer;
|
|
ClassName->Length = (USHORT)((P - ClassName->Buffer) * sizeof(WCHAR));
|
|
ClassName->MaximumLength = ClassName->Length;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
NTSTATUS FspMupRegister(
|
|
PDEVICE_OBJECT FsmupDeviceObject, PDEVICE_OBJECT FsvolDeviceObject)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
NTSTATUS Result;
|
|
BOOLEAN Success;
|
|
FSP_FSMUP_DEVICE_EXTENSION *FsmupDeviceExtension = FspFsmupDeviceExtension(FsmupDeviceObject);
|
|
FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject);
|
|
PUNICODE_PREFIX_TABLE_ENTRY ClassEntry;
|
|
UNICODE_STRING ClassName;
|
|
FSP_MUP_CLASS *Class = 0;
|
|
|
|
Result = FspMupGetClassName(&FsvolDeviceExtension->VolumePrefix, &ClassName);
|
|
ASSERT(NT_SUCCESS(Result));
|
|
|
|
Class = FspAlloc(sizeof *Class + ClassName.Length);
|
|
if (0 == Class)
|
|
{
|
|
Result = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto exit;
|
|
}
|
|
|
|
RtlZeroMemory(Class, sizeof *Class);
|
|
Class->RefCount = 1;
|
|
Class->Name.Length = ClassName.Length;
|
|
Class->Name.MaximumLength = ClassName.MaximumLength;
|
|
Class->Name.Buffer = Class->Buffer;
|
|
RtlCopyMemory(Class->Buffer, ClassName.Buffer, ClassName.Length);
|
|
|
|
FspFsmupDeviceLockPrefixTable(FsmupDeviceObject);
|
|
Success = RtlInsertUnicodePrefix(&FsmupDeviceExtension->PrefixTable,
|
|
&FsvolDeviceExtension->VolumePrefix, &FsvolDeviceExtension->VolumePrefixEntry);
|
|
if (Success)
|
|
{
|
|
FspDeviceReference(FsvolDeviceObject);
|
|
|
|
ClassEntry = RtlFindUnicodePrefix(&FsmupDeviceExtension->ClassTable,
|
|
&Class->Name, 0);
|
|
if (0 == ClassEntry)
|
|
{
|
|
Success = RtlInsertUnicodePrefix(&FsmupDeviceExtension->ClassTable,
|
|
&Class->Name, &Class->Entry);
|
|
ASSERT(Success);
|
|
Class = 0;
|
|
}
|
|
else
|
|
CONTAINING_RECORD(ClassEntry, FSP_MUP_CLASS, Entry)->RefCount++;
|
|
|
|
Result = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
Result = STATUS_OBJECT_NAME_COLLISION;
|
|
FspFsmupDeviceUnlockPrefixTable(FsmupDeviceObject);
|
|
|
|
exit:
|
|
if (0 != Class)
|
|
FspFree(Class);
|
|
|
|
return Result;
|
|
}
|
|
|
|
VOID FspMupUnregister(
|
|
PDEVICE_OBJECT FsmupDeviceObject, PDEVICE_OBJECT FsvolDeviceObject)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
NTSTATUS Result;
|
|
FSP_FSMUP_DEVICE_EXTENSION *FsmupDeviceExtension = FspFsmupDeviceExtension(FsmupDeviceObject);
|
|
FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject);
|
|
PUNICODE_PREFIX_TABLE_ENTRY PrefixEntry;
|
|
PUNICODE_PREFIX_TABLE_ENTRY ClassEntry;
|
|
UNICODE_STRING ClassName;
|
|
FSP_MUP_CLASS *Class;
|
|
|
|
Result = FspMupGetClassName(&FsvolDeviceExtension->VolumePrefix, &ClassName);
|
|
ASSERT(NT_SUCCESS(Result));
|
|
|
|
FspFsmupDeviceLockPrefixTable(FsmupDeviceObject);
|
|
PrefixEntry = RtlFindUnicodePrefix(&FsmupDeviceExtension->PrefixTable,
|
|
&FsvolDeviceExtension->VolumePrefix, 0);
|
|
if (0 != PrefixEntry)
|
|
{
|
|
RtlRemoveUnicodePrefix(&FsmupDeviceExtension->PrefixTable,
|
|
&FsvolDeviceExtension->VolumePrefixEntry);
|
|
FspDeviceDereference(FsvolDeviceObject);
|
|
|
|
ClassEntry = RtlFindUnicodePrefix(&FsmupDeviceExtension->ClassTable,
|
|
&ClassName, 0);
|
|
if (0 != ClassEntry)
|
|
{
|
|
Class = CONTAINING_RECORD(ClassEntry, FSP_MUP_CLASS, Entry);
|
|
if (0 == --Class->RefCount)
|
|
{
|
|
RtlRemoveUnicodePrefix(&FsmupDeviceExtension->ClassTable,
|
|
ClassEntry);
|
|
FspFree(Class);
|
|
}
|
|
}
|
|
}
|
|
FspFsmupDeviceUnlockPrefixTable(FsmupDeviceObject);
|
|
}
|
|
|
|
PDEVICE_OBJECT FspMupGetFsvolDeviceObject(
|
|
PFILE_OBJECT FileObject)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
PDEVICE_OBJECT FsvolDeviceObject = 0;
|
|
|
|
ASSERT(0 != FileObject);
|
|
|
|
if (FspFileNodeIsValid(FileObject->FsContext))
|
|
FsvolDeviceObject = ((FSP_FILE_NODE*)FileObject->FsContext)->FsvolDeviceObject;
|
|
else if (0 != FileObject->FsContext2 &&
|
|
#pragma prefast(disable:28175, "We are a filesystem: ok to access DeviceObject->Type")
|
|
IO_TYPE_DEVICE == ((PDEVICE_OBJECT)FileObject->FsContext2)->Type &&
|
|
0 != ((PDEVICE_OBJECT)FileObject->FsContext2)->DeviceExtension &&
|
|
FspFsvolDeviceExtensionKind == FspDeviceExtension((PDEVICE_OBJECT)FileObject->FsContext2)->Kind)
|
|
FsvolDeviceObject = (PDEVICE_OBJECT)FileObject->FsContext2;
|
|
|
|
return FsvolDeviceObject;
|
|
}
|
|
|
|
NTSTATUS FspMupHandleIrp(
|
|
PDEVICE_OBJECT FsmupDeviceObject, PIRP Irp)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
FSP_FSMUP_DEVICE_EXTENSION *FsmupDeviceExtension = FspFsmupDeviceExtension(FsmupDeviceObject);
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
PFILE_OBJECT FileObject = IrpSp->FileObject;
|
|
PDEVICE_OBJECT FsvolDeviceObject = 0;
|
|
PUNICODE_PREFIX_TABLE_ENTRY PrefixEntry;
|
|
BOOLEAN DeviceDeref = FALSE;
|
|
NTSTATUS Result;
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
switch (IrpSp->MajorFunction)
|
|
{
|
|
case IRP_MJ_CREATE:
|
|
/*
|
|
* A CREATE request with an empty file name indicates that the fsmup device
|
|
* is being opened. Check for this case and handle it.
|
|
*/
|
|
if (0 == FileObject->FileName.Length)
|
|
{
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = FILE_OPENED;
|
|
IoCompleteRequest(Irp, FSP_IO_INCREMENT);
|
|
Result = Irp->IoStatus.Status;
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* Every other CREATE request must be forwarded to the appropriate fsvol device.
|
|
*/
|
|
|
|
if (0 != FileObject->RelatedFileObject)
|
|
FileObject = FileObject->RelatedFileObject;
|
|
|
|
FspFsmupDeviceLockPrefixTable(FsmupDeviceObject);
|
|
PrefixEntry = RtlFindUnicodePrefix(&FsmupDeviceExtension->PrefixTable,
|
|
&FileObject->FileName, 0);
|
|
if (0 != PrefixEntry)
|
|
{
|
|
FsvolDeviceObject = CONTAINING_RECORD(PrefixEntry,
|
|
FSP_FSVOL_DEVICE_EXTENSION, VolumePrefixEntry)->FsvolDeviceObject;
|
|
FspDeviceReference(FsvolDeviceObject);
|
|
DeviceDeref = TRUE;
|
|
}
|
|
FspFsmupDeviceUnlockPrefixTable(FsmupDeviceObject);
|
|
break;
|
|
|
|
case IRP_MJ_DEVICE_CONTROL:
|
|
/*
|
|
* A DEVICE_CONTROL request with IOCTL_REDIR_QUERY_PATH_EX must be handled
|
|
* by the fsmup device. Check for this case and handle it.
|
|
*/
|
|
if (IOCTL_REDIR_QUERY_PATH_EX == IrpSp->Parameters.DeviceIoControl.IoControlCode)
|
|
{
|
|
Irp->IoStatus.Status = FspMupRedirQueryPathEx(FsmupDeviceObject, Irp, IrpSp);
|
|
IoCompleteRequest(Irp, FSP_IO_INCREMENT);
|
|
Result = Irp->IoStatus.Status;
|
|
goto exit;
|
|
}
|
|
|
|
/*
|
|
* Every other DEVICE_CONTROL request must be forwarded to the appropriate fsvol device.
|
|
*/
|
|
|
|
/* fall through! */
|
|
|
|
default:
|
|
/*
|
|
* Every other request must be forwarded to the appropriate fsvol device. If there is no
|
|
* fsvol device, then we must return the appropriate status code (see below).
|
|
*
|
|
* Please note that since we allow the fsmup device to be opened, we must also handle
|
|
* CLEANUP and CLOSE requests for it.
|
|
*/
|
|
|
|
if (0 != FileObject)
|
|
{
|
|
if (FspFileNodeIsValid(FileObject->FsContext))
|
|
FsvolDeviceObject = ((FSP_FILE_NODE *)FileObject->FsContext)->FsvolDeviceObject;
|
|
else if (0 != FileObject->FsContext2 &&
|
|
#pragma prefast(disable:28175, "We are a filesystem: ok to access DeviceObject->Type")
|
|
IO_TYPE_DEVICE == ((PDEVICE_OBJECT)FileObject->FsContext2)->Type &&
|
|
0 != ((PDEVICE_OBJECT)FileObject->FsContext2)->DeviceExtension &&
|
|
FspFsvolDeviceExtensionKind == FspDeviceExtension((PDEVICE_OBJECT)FileObject->FsContext2)->Kind)
|
|
FsvolDeviceObject = (PDEVICE_OBJECT)FileObject->FsContext2;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (0 == FsvolDeviceObject)
|
|
{
|
|
/*
|
|
* We were not able to find an fsvol device to forward this IRP to. We will complete
|
|
* the IRP with an appropriate status code.
|
|
*/
|
|
|
|
switch (IrpSp->MajorFunction)
|
|
{
|
|
case IRP_MJ_CREATE:
|
|
/*
|
|
* For CREATE requests we return STATUS_BAD_NETWORK_PATH. Here is why.
|
|
*
|
|
* When a file \ClassName\InstanceName\Path is opened by an application, this request
|
|
* first goes to MUP. The MUP gives DFS a first chance to handle the request and if
|
|
* that fails the MUP proceeds with prefix resolution. The DFS attempts to open the
|
|
* file \ClassName\IPC$, this results in a prefix resolution for \ClassName\IPC$
|
|
* through a recursive MUP call! If this resolution fails the DFS returns to the MUP,
|
|
* which now attempts prefix resolution for \ClassName\InstanceName\Path.
|
|
*
|
|
* Under the new fsmup design we respond to IOCTL_REDIR_QUERY_PATH_EX by handling all
|
|
* paths with a \ClassName prefix (that we know). This way we ensure that we will get
|
|
* all opens for paths with a \ClassName prefix and avoid delays for requests of
|
|
* \ClassName\IPC$, which if left unhandled will be forwarded to all network
|
|
* redirectors.
|
|
*
|
|
* In order to successfully short-circuit requests for \ClassName\IPC$ we must also
|
|
* return STATUS_BAD_NETWORK_PATH in CREATE. This makes DFS think that prefix
|
|
* resolution failed and does not complain if it cannot open \ClassName\IPC$. Other
|
|
* error codes cause DFS to completely fail the open issued by the application.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_BAD_NETWORK_PATH;
|
|
break;
|
|
case IRP_MJ_CLEANUP:
|
|
case IRP_MJ_CLOSE:
|
|
/*
|
|
* CLEANUP and CLOSE requests ignore their status code (except for STATUS_PENDING).
|
|
* So return STATUS_SUCCESS. This works regardless of whether this is a legitimate
|
|
* fsmup request or an erroneous CLOSE request that we should not have seen.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
break;
|
|
case IRP_MJ_QUERY_INFORMATION:
|
|
case IRP_MJ_SET_INFORMATION:
|
|
Irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
default:
|
|
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
|
|
break;
|
|
}
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
IoCompleteRequest(Irp, FSP_IO_INCREMENT);
|
|
Result = Irp->IoStatus.Status;
|
|
goto exit;
|
|
}
|
|
|
|
ASSERT(FspFsvolDeviceExtensionKind == FspDeviceExtension(FsvolDeviceObject)->Kind);
|
|
|
|
/*
|
|
* Forward the IRP to the appropriate fsvol device. The fsvol device will take care
|
|
* to complete the IRP, etc.
|
|
*/
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
Result = IoCallDriver(FsvolDeviceObject, Irp);
|
|
|
|
if (DeviceDeref)
|
|
FspDeviceDereference(FsvolDeviceObject);
|
|
|
|
exit:
|
|
FsRtlExitFileSystem();
|
|
|
|
return Result;
|
|
}
|
|
|
|
static NTSTATUS FspMupRedirQueryPathEx(
|
|
PDEVICE_OBJECT FsmupDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ASSERT(IRP_MJ_DEVICE_CONTROL == IrpSp->MajorFunction);
|
|
ASSERT(IOCTL_REDIR_QUERY_PATH_EX == IrpSp->Parameters.DeviceIoControl.IoControlCode);
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
if (KernelMode != Irp->RequestorMode)
|
|
return STATUS_INVALID_DEVICE_REQUEST;
|
|
|
|
/* check parameters */
|
|
ULONG InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
|
|
ULONG OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;
|
|
QUERY_PATH_REQUEST_EX *QueryPathRequest = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
|
|
QUERY_PATH_RESPONSE *QueryPathResponse = Irp->UserBuffer;
|
|
if (sizeof(QUERY_PATH_REQUEST_EX) > InputBufferLength ||
|
|
0 == QueryPathRequest || 0 == QueryPathResponse)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (sizeof(QUERY_PATH_RESPONSE) > OutputBufferLength)
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
|
|
NTSTATUS Result;
|
|
FSP_FSMUP_DEVICE_EXTENSION *FsmupDeviceExtension = FspFsmupDeviceExtension(FsmupDeviceObject);
|
|
PUNICODE_PREFIX_TABLE_ENTRY Entry;
|
|
|
|
#if defined(FSP_MUP_PREFIX_CLASS)
|
|
UNICODE_STRING ClassName;
|
|
|
|
Result = FspMupGetClassName(&QueryPathRequest->PathName, &ClassName);
|
|
if (!NT_SUCCESS(Result))
|
|
return STATUS_BAD_NETWORK_PATH;
|
|
|
|
Result = STATUS_BAD_NETWORK_PATH;
|
|
FspFsmupDeviceLockPrefixTable(FsmupDeviceObject);
|
|
Entry = RtlFindUnicodePrefix(&FsmupDeviceExtension->ClassTable, &ClassName, 0);
|
|
if (0 != Entry)
|
|
{
|
|
QueryPathResponse->LengthAccepted = ClassName.Length;
|
|
Result = STATUS_SUCCESS;
|
|
}
|
|
FspFsmupDeviceUnlockPrefixTable(FsmupDeviceObject);
|
|
#else
|
|
FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension;
|
|
|
|
Result = STATUS_BAD_NETWORK_PATH;
|
|
FspFsmupDeviceLockPrefixTable(FsmupDeviceObject);
|
|
Entry = RtlFindUnicodePrefix(&FsmupDeviceExtension->PrefixTable,
|
|
&QueryPathRequest->PathName, 0);
|
|
if (0 != Entry)
|
|
{
|
|
FsvolDeviceExtension = CONTAINING_RECORD(Entry, FSP_FSVOL_DEVICE_EXTENSION, VolumePrefixEntry);
|
|
if (!FspIoqStopped(FsvolDeviceExtension->Ioq))
|
|
{
|
|
if (0 < FsvolDeviceExtension->VolumePrefix.Length &&
|
|
FspFsvolDeviceVolumePrefixInString(
|
|
FsvolDeviceExtension->FsvolDeviceObject, &QueryPathRequest->PathName) &&
|
|
(QueryPathRequest->PathName.Length == FsvolDeviceExtension->VolumePrefix.Length ||
|
|
'\\' == QueryPathRequest->PathName.Buffer[FsvolDeviceExtension->VolumePrefix.Length / sizeof(WCHAR)]))
|
|
{
|
|
QueryPathResponse->LengthAccepted = FsvolDeviceExtension->VolumePrefix.Length;
|
|
Result = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
FspFsmupDeviceUnlockPrefixTable(FsmupDeviceObject);
|
|
#endif
|
|
|
|
return Result;
|
|
}
|