mirror of
https://github.com/reactos/reactos.git
synced 2024-12-29 10:35:28 +00:00
b8525ce7a5
- ReparseFile was concatenated with itself, instead of ReparseIndex - Meanwhile, use RtlAppendUnicodeStringToString for concatenating strings instead of raw memory operations
2112 lines
67 KiB
C
2112 lines
67 KiB
C
/*
|
|
* ReactOS kernel
|
|
* Copyright (C) 2011-2012 ReactOS Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* COPYRIGHT: See COPYING in the top level directory
|
|
* PROJECT: ReactOS kernel
|
|
* FILE: drivers/filesystem/mountmgr/mountmgr.c
|
|
* PURPOSE: Mount Manager - remote/local database handler
|
|
* PROGRAMMER: Pierre Schweitzer (pierre.schweitzer@reactos.org)
|
|
*/
|
|
|
|
#include "mntmgr.h"
|
|
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
PWSTR DatabasePath = L"\\Registry\\Machine\\System\\MountedDevices";
|
|
PWSTR OfflinePath = L"\\Registry\\Machine\\System\\MountedDevices\\Offline";
|
|
|
|
UNICODE_STRING RemoteDatabase = RTL_CONSTANT_STRING(L"\\System Volume Information\\MountPointManagerRemoteDatabase");
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
LONG
|
|
GetRemoteDatabaseSize(IN HANDLE Database)
|
|
{
|
|
NTSTATUS Status;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
FILE_STANDARD_INFORMATION StandardInfo;
|
|
|
|
/* Just query the size */
|
|
Status = ZwQueryInformationFile(Database,
|
|
&IoStatusBlock,
|
|
&StandardInfo,
|
|
sizeof(FILE_STANDARD_INFORMATION),
|
|
FileStandardInformation);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
return StandardInfo.EndOfFile.LowPart;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
AddRemoteDatabaseEntry(IN HANDLE Database,
|
|
IN PDATABASE_ENTRY Entry)
|
|
{
|
|
LARGE_INTEGER Size;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
|
|
/* Get size to append data */
|
|
Size.QuadPart = GetRemoteDatabaseSize(Database);
|
|
|
|
return ZwWriteFile(Database, NULL, NULL, NULL,
|
|
&IoStatusBlock, Entry,
|
|
Entry->EntrySize, &Size, NULL);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
CloseRemoteDatabase(IN HANDLE Database)
|
|
{
|
|
return ZwClose(Database);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
TruncateRemoteDatabase(IN HANDLE Database,
|
|
IN LONG NewSize)
|
|
{
|
|
NTSTATUS Status;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
FILE_END_OF_FILE_INFORMATION EndOfFile;
|
|
FILE_ALLOCATION_INFORMATION Allocation;
|
|
|
|
EndOfFile.EndOfFile.QuadPart = NewSize;
|
|
Allocation.AllocationSize.QuadPart = NewSize;
|
|
|
|
/* First set EOF */
|
|
Status = ZwSetInformationFile(Database,
|
|
&IoStatusBlock,
|
|
&EndOfFile,
|
|
sizeof(FILE_END_OF_FILE_INFORMATION),
|
|
FileEndOfFileInformation);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* And then, properly set allocation information */
|
|
Status = ZwSetInformationFile(Database,
|
|
&IoStatusBlock,
|
|
&Allocation,
|
|
sizeof(FILE_ALLOCATION_INFORMATION),
|
|
FileAllocationInformation);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PDATABASE_ENTRY
|
|
GetRemoteDatabaseEntry(IN HANDLE Database,
|
|
IN LONG StartingOffset)
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG EntrySize;
|
|
PDATABASE_ENTRY Entry;
|
|
LARGE_INTEGER ByteOffset;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
|
|
/* Get the entry at the given position */
|
|
ByteOffset.QuadPart = StartingOffset;
|
|
Status = ZwReadFile(Database,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
&EntrySize,
|
|
sizeof(EntrySize),
|
|
&ByteOffset,
|
|
NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* If entry doesn't exist, truncate database */
|
|
if (!EntrySize)
|
|
{
|
|
TruncateRemoteDatabase(Database, StartingOffset);
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate the entry */
|
|
Entry = AllocatePool(EntrySize);
|
|
if (!Entry)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Effectively read the entry */
|
|
Status = ZwReadFile(Database,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
Entry,
|
|
EntrySize,
|
|
&ByteOffset,
|
|
NULL);
|
|
/* If it fails or returns inconsistent data, drop it (= truncate) */
|
|
if (!NT_SUCCESS(Status) ||
|
|
(IoStatusBlock.Information != EntrySize) ||
|
|
(EntrySize < sizeof(DATABASE_ENTRY)) )
|
|
{
|
|
TruncateRemoteDatabase(Database, StartingOffset);
|
|
FreePool(Entry);
|
|
return NULL;
|
|
}
|
|
|
|
/* Validate entry */
|
|
if (MAX(Entry->SymbolicNameOffset + Entry->SymbolicNameLength,
|
|
Entry->UniqueIdOffset + Entry->UniqueIdLength) > (LONG)EntrySize)
|
|
{
|
|
TruncateRemoteDatabase(Database, StartingOffset);
|
|
FreePool(Entry);
|
|
return NULL;
|
|
}
|
|
|
|
return Entry;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
WriteRemoteDatabaseEntry(IN HANDLE Database,
|
|
IN LONG Offset,
|
|
IN PDATABASE_ENTRY Entry)
|
|
{
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER ByteOffset;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
|
|
ByteOffset.QuadPart = Offset;
|
|
Status = ZwWriteFile(Database,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
Entry,
|
|
Entry->EntrySize,
|
|
&ByteOffset,
|
|
NULL);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if (IoStatusBlock.Information < Entry->EntrySize)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
DeleteRemoteDatabaseEntry(IN HANDLE Database,
|
|
IN LONG StartingOffset)
|
|
{
|
|
ULONG EndSize;
|
|
PVOID TmpBuffer;
|
|
NTSTATUS Status;
|
|
ULONG DatabaseSize;
|
|
PDATABASE_ENTRY Entry;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
LARGE_INTEGER EndEntriesOffset;
|
|
|
|
/* First, get database size */
|
|
DatabaseSize = GetRemoteDatabaseSize(Database);
|
|
if (!DatabaseSize)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Then, get the entry to remove */
|
|
Entry = GetRemoteDatabaseEntry(Database, StartingOffset);
|
|
if (!Entry)
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Validate parameters: ensure we won't get negative size */
|
|
if (Entry->EntrySize + StartingOffset > DatabaseSize)
|
|
{
|
|
/* If we get invalid parameters, truncate the whole database
|
|
* starting the wrong entry. We can't rely on the rest
|
|
*/
|
|
FreePool(Entry);
|
|
return TruncateRemoteDatabase(Database, StartingOffset);
|
|
}
|
|
|
|
/* Now, get the size of the remaining entries (those after the one to remove) */
|
|
EndSize = DatabaseSize - Entry->EntrySize - StartingOffset;
|
|
/* Allocate a buffer big enough to hold them */
|
|
TmpBuffer = AllocatePool(EndSize);
|
|
if (!TmpBuffer)
|
|
{
|
|
FreePool(Entry);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* Get the offset of the entry right after the one to delete */
|
|
EndEntriesOffset.QuadPart = Entry->EntrySize + StartingOffset;
|
|
/* We don't need the entry any more */
|
|
FreePool(Entry);
|
|
|
|
/* Read the ending entries */
|
|
Status = ZwReadFile(Database, NULL, NULL, NULL, &IoStatusBlock,
|
|
TmpBuffer, EndSize, &EndEntriesOffset, NULL);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
FreePool(TmpBuffer);
|
|
return Status;
|
|
}
|
|
|
|
/* Ensure nothing went wrong - we don't want to corrupt the DB */
|
|
if (IoStatusBlock.Information != EndSize)
|
|
{
|
|
FreePool(TmpBuffer);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Remove the entry */
|
|
Status = TruncateRemoteDatabase(Database, StartingOffset + EndSize);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
FreePool(TmpBuffer);
|
|
return Status;
|
|
}
|
|
|
|
/* Now, shift the ending entries to erase the entry */
|
|
EndEntriesOffset.QuadPart = StartingOffset;
|
|
Status = ZwWriteFile(Database, NULL, NULL, NULL, &IoStatusBlock,
|
|
TmpBuffer, EndSize, &EndEntriesOffset, NULL);
|
|
|
|
FreePool(TmpBuffer);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
DeleteFromLocalDatabaseRoutine(IN PWSTR ValueName,
|
|
IN ULONG ValueType,
|
|
IN PVOID ValueData,
|
|
IN ULONG ValueLength,
|
|
IN PVOID Context,
|
|
IN PVOID EntryContext)
|
|
{
|
|
PMOUNTDEV_UNIQUE_ID UniqueId = Context;
|
|
|
|
UNREFERENCED_PARAMETER(ValueType);
|
|
UNREFERENCED_PARAMETER(EntryContext);
|
|
|
|
/* Ensure it matches, and delete */
|
|
if ((UniqueId->UniqueIdLength == ValueLength) &&
|
|
(RtlCompareMemory(UniqueId->UniqueId, ValueData, ValueLength) ==
|
|
ValueLength))
|
|
{
|
|
RtlDeleteRegistryValue(RTL_REGISTRY_ABSOLUTE,
|
|
DatabasePath,
|
|
ValueName);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
DeleteFromLocalDatabase(IN PUNICODE_STRING SymbolicLink,
|
|
IN PMOUNTDEV_UNIQUE_ID UniqueId)
|
|
{
|
|
RTL_QUERY_REGISTRY_TABLE QueryTable[2];
|
|
|
|
RtlZeroMemory(QueryTable, sizeof(QueryTable));
|
|
QueryTable[0].QueryRoutine = DeleteFromLocalDatabaseRoutine;
|
|
QueryTable[0].Name = SymbolicLink->Buffer;
|
|
|
|
RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
|
|
DatabasePath,
|
|
QueryTable,
|
|
UniqueId,
|
|
NULL);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
WaitForRemoteDatabaseSemaphore(IN PDEVICE_EXTENSION DeviceExtension)
|
|
{
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER Timeout;
|
|
|
|
/* Wait for 7 minutes */
|
|
Timeout.QuadPart = 0xFA0A1F00;
|
|
Status = KeWaitForSingleObject(&(DeviceExtension->RemoteDatabaseLock), Executive, KernelMode, FALSE, &Timeout);
|
|
if (Status != STATUS_TIMEOUT)
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
return STATUS_IO_TIMEOUT;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
ReleaseRemoteDatabaseSemaphore(IN PDEVICE_EXTENSION DeviceExtension)
|
|
{
|
|
KeReleaseSemaphore(&(DeviceExtension->RemoteDatabaseLock), IO_NO_INCREMENT, 1, FALSE);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
QueryUniqueIdQueryRoutine(IN PWSTR ValueName,
|
|
IN ULONG ValueType,
|
|
IN PVOID ValueData,
|
|
IN ULONG ValueLength,
|
|
IN PVOID Context,
|
|
IN PVOID EntryContext)
|
|
{
|
|
PMOUNTDEV_UNIQUE_ID IntUniqueId;
|
|
PMOUNTDEV_UNIQUE_ID * UniqueId;
|
|
|
|
UNREFERENCED_PARAMETER(ValueName);
|
|
UNREFERENCED_PARAMETER(ValueType);
|
|
UNREFERENCED_PARAMETER(EntryContext);
|
|
|
|
/* Sanity check */
|
|
if (ValueLength >= 0x10000)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Allocate the Unique ID */
|
|
IntUniqueId = AllocatePool(sizeof(UniqueId) + ValueLength);
|
|
if (IntUniqueId)
|
|
{
|
|
/* Copy data & return */
|
|
IntUniqueId->UniqueIdLength = (USHORT)ValueLength;
|
|
RtlCopyMemory(&(IntUniqueId->UniqueId), ValueData, ValueLength);
|
|
|
|
UniqueId = Context;
|
|
*UniqueId = IntUniqueId;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
QueryUniqueIdFromMaster(IN PDEVICE_EXTENSION DeviceExtension,
|
|
IN PUNICODE_STRING SymbolicName,
|
|
OUT PMOUNTDEV_UNIQUE_ID * UniqueId)
|
|
{
|
|
NTSTATUS Status;
|
|
PDEVICE_INFORMATION DeviceInformation;
|
|
RTL_QUERY_REGISTRY_TABLE QueryTable[2];
|
|
|
|
/* Query the unique ID */
|
|
RtlZeroMemory(QueryTable, sizeof(QueryTable));
|
|
QueryTable[0].QueryRoutine = QueryUniqueIdQueryRoutine;
|
|
QueryTable[0].Name = SymbolicName->Buffer;
|
|
|
|
*UniqueId = NULL;
|
|
RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
|
|
DatabasePath,
|
|
QueryTable,
|
|
UniqueId,
|
|
NULL);
|
|
/* Unique ID found, no need to go farther */
|
|
if (*UniqueId)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Otherwise, find associate device information */
|
|
Status = FindDeviceInfo(DeviceExtension, SymbolicName, FALSE, &DeviceInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
*UniqueId = AllocatePool(DeviceInformation->UniqueId->UniqueIdLength + sizeof(MOUNTDEV_UNIQUE_ID));
|
|
if (!*UniqueId)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* Return this unique ID (better than nothing) */
|
|
(*UniqueId)->UniqueIdLength = DeviceInformation->UniqueId->UniqueIdLength;
|
|
RtlCopyMemory(&((*UniqueId)->UniqueId), &(DeviceInformation->UniqueId->UniqueId), (*UniqueId)->UniqueIdLength);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
WriteUniqueIdToMaster(IN PDEVICE_EXTENSION DeviceExtension,
|
|
IN PDATABASE_ENTRY DatabaseEntry)
|
|
{
|
|
NTSTATUS Status;
|
|
PWCHAR SymbolicName;
|
|
PLIST_ENTRY NextEntry;
|
|
UNICODE_STRING SymbolicString;
|
|
PDEVICE_INFORMATION DeviceInformation;
|
|
|
|
/* Create symbolic name from database entry */
|
|
SymbolicName = AllocatePool(DatabaseEntry->SymbolicNameLength + sizeof(WCHAR));
|
|
if (!SymbolicName)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(SymbolicName,
|
|
(PVOID)((ULONG_PTR)DatabaseEntry + DatabaseEntry->SymbolicNameOffset),
|
|
DatabaseEntry->SymbolicNameLength);
|
|
SymbolicName[DatabaseEntry->SymbolicNameLength / sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
/* Associate the unique ID with the name from remote database */
|
|
Status = RtlWriteRegistryValue(RTL_REGISTRY_ABSOLUTE,
|
|
DatabasePath,
|
|
SymbolicName,
|
|
REG_BINARY,
|
|
(PVOID)((ULONG_PTR)DatabaseEntry + DatabaseEntry->UniqueIdOffset),
|
|
DatabaseEntry->UniqueIdLength);
|
|
FreePool(SymbolicName);
|
|
|
|
/* Reget symbolic name */
|
|
SymbolicString.Length = DatabaseEntry->SymbolicNameLength;
|
|
SymbolicString.MaximumLength = DatabaseEntry->SymbolicNameLength;
|
|
SymbolicString.Buffer = (PVOID)((ULONG_PTR)DatabaseEntry + DatabaseEntry->SymbolicNameOffset);
|
|
|
|
/* Find the device using this unique ID */
|
|
for (NextEntry = DeviceExtension->DeviceListHead.Flink;
|
|
NextEntry != &(DeviceExtension->DeviceListHead);
|
|
NextEntry = NextEntry->Flink)
|
|
{
|
|
DeviceInformation = CONTAINING_RECORD(NextEntry,
|
|
DEVICE_INFORMATION,
|
|
DeviceListEntry);
|
|
|
|
if (DeviceInformation->UniqueId->UniqueIdLength != DatabaseEntry->UniqueIdLength)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (RtlCompareMemory((PVOID)((ULONG_PTR)DatabaseEntry + DatabaseEntry->UniqueIdOffset),
|
|
DeviceInformation->UniqueId->UniqueId,
|
|
DatabaseEntry->UniqueIdLength) == DatabaseEntry->UniqueIdLength)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If found, create a mount point */
|
|
if (NextEntry != &(DeviceExtension->DeviceListHead))
|
|
{
|
|
MountMgrCreatePointWorker(DeviceExtension, &SymbolicString, &(DeviceInformation->DeviceName));
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
ReconcileThisDatabaseWithMasterWorker(IN PVOID Parameter)
|
|
{
|
|
ULONG Offset;
|
|
NTSTATUS Status;
|
|
PFILE_OBJECT FileObject;
|
|
PDEVICE_OBJECT DeviceObject;
|
|
PMOUNTDEV_UNIQUE_ID UniqueId;
|
|
PDATABASE_ENTRY DatabaseEntry;
|
|
HANDLE DatabaseHandle, Handle;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
PDEVICE_INFORMATION ListDeviceInfo;
|
|
PLIST_ENTRY Entry, EntryInfo, NextEntry;
|
|
PASSOCIATED_DEVICE_ENTRY AssociatedDevice;
|
|
BOOLEAN HardwareErrors, Restart, FailedFinding;
|
|
WCHAR FileNameBuffer[0x8], SymbolicNameBuffer[100];
|
|
UNICODE_STRING ReparseFile, FileName, SymbolicName, VolumeName;
|
|
FILE_REPARSE_POINT_INFORMATION ReparsePointInformation, SavedReparsePointInformation;
|
|
PDEVICE_EXTENSION DeviceExtension = ((PRECONCILE_WORK_ITEM_CONTEXT)Parameter)->DeviceExtension;
|
|
PDEVICE_INFORMATION DeviceInformation = ((PRECONCILE_WORK_ITEM_CONTEXT)Parameter)->DeviceInformation;
|
|
|
|
/* We're unloading, do nothing */
|
|
if (Unloading)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Lock remote DB */
|
|
if (!NT_SUCCESS(WaitForRemoteDatabaseSemaphore(DeviceExtension)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Recheck for unloading */
|
|
if (Unloading)
|
|
{
|
|
goto ReleaseRDS;
|
|
}
|
|
|
|
/* Find the DB to reconcile */
|
|
KeWaitForSingleObject(&DeviceExtension->DeviceLock, Executive, KernelMode, FALSE, NULL);
|
|
for (Entry = DeviceExtension->DeviceListHead.Flink;
|
|
Entry != &DeviceExtension->DeviceListHead;
|
|
Entry = Entry->Flink)
|
|
{
|
|
ListDeviceInfo = CONTAINING_RECORD(Entry, DEVICE_INFORMATION, DeviceListEntry);
|
|
if (ListDeviceInfo == DeviceInformation)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If not found, or if removable, bail out */
|
|
if (Entry == &DeviceExtension->DeviceListHead || DeviceInformation->Removable)
|
|
{
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
goto ReleaseRDS;
|
|
}
|
|
|
|
/* Get our device object */
|
|
Status = IoGetDeviceObjectPointer(&ListDeviceInfo->DeviceName, FILE_READ_ATTRIBUTES, &FileObject, &DeviceObject);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
goto ReleaseRDS;
|
|
}
|
|
|
|
/* Mark mounted only if not unloading */
|
|
if (!(DeviceObject->Flags & DO_UNLOAD_PENDING))
|
|
{
|
|
InterlockedExchangeAdd(&ListDeviceInfo->MountState, 1);
|
|
}
|
|
|
|
ObDereferenceObject(FileObject);
|
|
|
|
/* Force default: no DB, and need for reconcile */
|
|
DeviceInformation->NeedsReconcile = TRUE;
|
|
DeviceInformation->NoDatabase = TRUE;
|
|
FailedFinding = FALSE;
|
|
|
|
/* Remove any associated device that refers to the DB to reconcile */
|
|
for (Entry = DeviceExtension->DeviceListHead.Flink;
|
|
Entry != &DeviceExtension->DeviceListHead;
|
|
Entry = Entry->Flink)
|
|
{
|
|
ListDeviceInfo = CONTAINING_RECORD(Entry, DEVICE_INFORMATION, DeviceListEntry);
|
|
|
|
EntryInfo = ListDeviceInfo->AssociatedDevicesHead.Flink;
|
|
while (EntryInfo != &ListDeviceInfo->AssociatedDevicesHead)
|
|
{
|
|
AssociatedDevice = CONTAINING_RECORD(EntryInfo, ASSOCIATED_DEVICE_ENTRY, AssociatedDevicesEntry);
|
|
NextEntry = EntryInfo->Flink;
|
|
|
|
if (AssociatedDevice->DeviceInformation == DeviceInformation)
|
|
{
|
|
RemoveEntryList(&AssociatedDevice->AssociatedDevicesEntry);
|
|
FreePool(AssociatedDevice->String.Buffer);
|
|
FreePool(AssociatedDevice);
|
|
}
|
|
|
|
EntryInfo = NextEntry;
|
|
}
|
|
}
|
|
|
|
/* Open the remote database */
|
|
DatabaseHandle = OpenRemoteDatabase(DeviceInformation, FALSE);
|
|
|
|
/* Prepare a string with reparse point index */
|
|
ReparseFile.Length = 0;
|
|
ReparseFile.MaximumLength = DeviceInformation->DeviceName.Length
|
|
+ ReparseIndex.Length
|
|
+ sizeof(UNICODE_NULL);
|
|
ReparseFile.Buffer = AllocatePool(ReparseFile.MaximumLength);
|
|
if (!ReparseFile.Buffer)
|
|
{
|
|
if (DatabaseHandle != 0)
|
|
{
|
|
CloseRemoteDatabase(DatabaseHandle);
|
|
}
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
goto ReleaseRDS;
|
|
}
|
|
|
|
RtlAppendUnicodeStringToString(&ReparseFile, &DeviceInformation->DeviceName);
|
|
RtlAppendUnicodeStringToString(&ReparseFile, &ReparseIndex);
|
|
ReparseFile.Buffer[ReparseFile.Length / sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&ReparseFile,
|
|
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
/* Open reparse point directory */
|
|
HardwareErrors = IoSetThreadHardErrorMode(FALSE);
|
|
Status = ZwOpenFile(&Handle,
|
|
FILE_GENERIC_READ,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_SYNCHRONOUS_IO_ALERT);
|
|
IoSetThreadHardErrorMode(HardwareErrors);
|
|
|
|
FreePool(ReparseFile.Buffer);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (DatabaseHandle != 0)
|
|
{
|
|
TruncateRemoteDatabase(DatabaseHandle, 0);
|
|
CloseRemoteDatabase(DatabaseHandle);
|
|
}
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
goto ReleaseRDS;
|
|
}
|
|
|
|
/* Query reparse point information
|
|
* We only pay attention to mout point
|
|
*/
|
|
RtlZeroMemory(FileNameBuffer, sizeof(FileNameBuffer));
|
|
FileName.Buffer = FileNameBuffer;
|
|
FileName.Length = sizeof(FileNameBuffer);
|
|
FileName.MaximumLength = sizeof(FileNameBuffer);
|
|
((PULONG)FileNameBuffer)[0] = IO_REPARSE_TAG_MOUNT_POINT;
|
|
Status = ZwQueryDirectoryFile(Handle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
&ReparsePointInformation,
|
|
sizeof(FILE_REPARSE_POINT_INFORMATION),
|
|
FileReparsePointInformation,
|
|
TRUE,
|
|
&FileName,
|
|
FALSE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
ZwClose(Handle);
|
|
if (DatabaseHandle != 0)
|
|
{
|
|
TruncateRemoteDatabase(DatabaseHandle, 0);
|
|
CloseRemoteDatabase(DatabaseHandle);
|
|
}
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
goto ReleaseRDS;
|
|
}
|
|
|
|
/* If we failed to open the remote DB previously,
|
|
* retry this time allowing migration (and thus, creation if required)
|
|
*/
|
|
if (DatabaseHandle == 0)
|
|
{
|
|
DatabaseHandle = OpenRemoteDatabase(DeviceInformation, TRUE);
|
|
if (DatabaseHandle == 0)
|
|
{
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
goto ReleaseRDS;
|
|
}
|
|
}
|
|
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
|
|
/* Reset all the references to our DB entries */
|
|
Offset = 0;
|
|
for (;;)
|
|
{
|
|
DatabaseEntry = GetRemoteDatabaseEntry(DatabaseHandle, Offset);
|
|
if (DatabaseEntry == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
DatabaseEntry->EntryReferences = 0;
|
|
Status = WriteRemoteDatabaseEntry(DatabaseHandle, Offset, DatabaseEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
FreePool(DatabaseEntry);
|
|
goto CloseReparse;
|
|
}
|
|
|
|
Offset += DatabaseEntry->EntrySize;
|
|
FreePool(DatabaseEntry);
|
|
}
|
|
|
|
/* Init string for QueryVolumeName call */
|
|
SymbolicName.MaximumLength = sizeof(SymbolicNameBuffer);
|
|
SymbolicName.Length = 0;
|
|
SymbolicName.Buffer = SymbolicNameBuffer;
|
|
Restart = TRUE;
|
|
|
|
/* Start looping on reparse points */
|
|
for (;;)
|
|
{
|
|
RtlCopyMemory(&SavedReparsePointInformation, &ReparsePointInformation, sizeof(FILE_REPARSE_POINT_INFORMATION));
|
|
Status = ZwQueryDirectoryFile(Handle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
&ReparsePointInformation,
|
|
sizeof(FILE_REPARSE_POINT_INFORMATION),
|
|
FileReparsePointInformation,
|
|
TRUE,
|
|
Restart ? &FileName : NULL,
|
|
Restart);
|
|
/* Restart only once */
|
|
if (Restart)
|
|
{
|
|
Restart = FALSE;
|
|
}
|
|
else
|
|
{
|
|
/* If we get the same one, we're done, bail out */
|
|
if (ReparsePointInformation.FileReference == SavedReparsePointInformation.FileReference &&
|
|
ReparsePointInformation.Tag == SavedReparsePointInformation.Tag)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If querying failed, or if onloading, or if not returning mount points, bail out */
|
|
if (!NT_SUCCESS(Status) || Unloading || ReparsePointInformation.Tag != IO_REPARSE_TAG_MOUNT_POINT)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Get the volume name associated to the mount point */
|
|
Status = QueryVolumeName(Handle, &ReparsePointInformation, 0, &SymbolicName, &VolumeName);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/* Browse the DB to find the name */
|
|
Offset = 0;
|
|
for (;;)
|
|
{
|
|
UNICODE_STRING DbName;
|
|
|
|
DatabaseEntry = GetRemoteDatabaseEntry(DatabaseHandle, Offset);
|
|
if (DatabaseEntry == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
DbName.MaximumLength = DatabaseEntry->SymbolicNameLength;
|
|
DbName.Length = DbName.MaximumLength;
|
|
DbName.Buffer = (PWSTR)((ULONG_PTR)DatabaseEntry + DatabaseEntry->SymbolicNameOffset);
|
|
/* Found, we're done! */
|
|
if (RtlEqualUnicodeString(&DbName, &SymbolicName, TRUE))
|
|
{
|
|
break;
|
|
}
|
|
|
|
Offset += DatabaseEntry->EntrySize;
|
|
FreePool(DatabaseEntry);
|
|
}
|
|
|
|
/* If we found the mount point.... */
|
|
if (DatabaseEntry != NULL)
|
|
{
|
|
/* If it was referenced, reference it once more and update to remote */
|
|
if (DatabaseEntry->EntryReferences)
|
|
{
|
|
++DatabaseEntry->EntryReferences;
|
|
Status = WriteRemoteDatabaseEntry(DatabaseHandle, Offset, DatabaseEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto FreeDBEntry;
|
|
}
|
|
|
|
FreePool(DatabaseEntry);
|
|
}
|
|
else
|
|
{
|
|
/* Query the Unique ID associated to that mount point in case it changed */
|
|
KeWaitForSingleObject(&DeviceExtension->DeviceLock, Executive, KernelMode, FALSE, NULL);
|
|
Status = QueryUniqueIdFromMaster(DeviceExtension, &SymbolicName, &UniqueId);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* If we failed doing so, reuse the old Unique ID and push it to master */
|
|
Status = WriteUniqueIdToMaster(DeviceExtension, DatabaseEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto ReleaseDeviceLock;
|
|
}
|
|
|
|
/* And then, reference & write the entry */
|
|
++DatabaseEntry->EntryReferences;
|
|
Status = WriteRemoteDatabaseEntry(DatabaseHandle, Offset, DatabaseEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto ReleaseDeviceLock;
|
|
}
|
|
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
FreePool(DatabaseEntry);
|
|
}
|
|
/* If the Unique ID didn't change */
|
|
else if (UniqueId->UniqueIdLength == DatabaseEntry->UniqueIdLength &&
|
|
RtlCompareMemory(UniqueId->UniqueId,
|
|
(PVOID)((ULONG_PTR)DatabaseEntry + DatabaseEntry->UniqueIdOffset),
|
|
UniqueId->UniqueIdLength) == UniqueId->UniqueIdLength)
|
|
{
|
|
/* Reference the entry, and update to remote */
|
|
++DatabaseEntry->EntryReferences;
|
|
Status = WriteRemoteDatabaseEntry(DatabaseHandle, Offset, DatabaseEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto FreeUniqueId;
|
|
}
|
|
|
|
FreePool(UniqueId);
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
FreePool(DatabaseEntry);
|
|
}
|
|
/* Would, by chance, the Unique ID be present elsewhere? */
|
|
else if (IsUniqueIdPresent(DeviceExtension, DatabaseEntry))
|
|
{
|
|
/* Push the ID to master */
|
|
Status = WriteUniqueIdToMaster(DeviceExtension, DatabaseEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto FreeUniqueId;
|
|
}
|
|
|
|
/* And then, reference & write the entry */
|
|
++DatabaseEntry->EntryReferences;
|
|
Status = WriteRemoteDatabaseEntry(DatabaseHandle, Offset, DatabaseEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto FreeUniqueId;
|
|
}
|
|
|
|
FreePool(UniqueId);
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
FreePool(DatabaseEntry);
|
|
}
|
|
else
|
|
{
|
|
/* OK, at that point, we're facing a totally unknown unique ID
|
|
* So, get rid of the old entry, and recreate a new one with
|
|
* the know unique ID
|
|
*/
|
|
Status = DeleteRemoteDatabaseEntry(DatabaseHandle, Offset);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto FreeUniqueId;
|
|
}
|
|
|
|
FreePool(DatabaseEntry);
|
|
/* Allocate a new entry big enough */
|
|
DatabaseEntry = AllocatePool(UniqueId->UniqueIdLength + SymbolicName.Length + sizeof(DATABASE_ENTRY));
|
|
if (DatabaseEntry == NULL)
|
|
{
|
|
goto FreeUniqueId;
|
|
}
|
|
|
|
/* Configure it */
|
|
DatabaseEntry->EntrySize = UniqueId->UniqueIdLength + SymbolicName.Length + sizeof(DATABASE_ENTRY);
|
|
DatabaseEntry->EntryReferences = 1;
|
|
DatabaseEntry->SymbolicNameOffset = sizeof(DATABASE_ENTRY);
|
|
DatabaseEntry->SymbolicNameLength = SymbolicName.Length;
|
|
DatabaseEntry->UniqueIdOffset = SymbolicName.Length + sizeof(DATABASE_ENTRY);
|
|
DatabaseEntry->UniqueIdLength = UniqueId->UniqueIdLength;
|
|
RtlCopyMemory((PVOID)((ULONG_PTR)DatabaseEntry + DatabaseEntry->SymbolicNameOffset), SymbolicName.Buffer, DatabaseEntry->SymbolicNameLength);
|
|
RtlCopyMemory((PVOID)((ULONG_PTR)DatabaseEntry + DatabaseEntry->UniqueIdOffset), UniqueId->UniqueId, UniqueId->UniqueIdLength);
|
|
|
|
/* And write it remotely */
|
|
Status = AddRemoteDatabaseEntry(DatabaseHandle, DatabaseEntry);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
FreePool(DatabaseEntry);
|
|
goto FreeUniqueId;
|
|
}
|
|
|
|
FreePool(UniqueId);
|
|
FreePool(DatabaseEntry);
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We failed finding it remotely
|
|
* So, let's allocate a new remote DB entry
|
|
*/
|
|
KeWaitForSingleObject(&DeviceExtension->DeviceLock, Executive, KernelMode, FALSE, NULL);
|
|
/* To be able to do so, we need the device Unique ID, ask master */
|
|
Status = QueryUniqueIdFromMaster(DeviceExtension, &SymbolicName, &UniqueId);
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Allocate a new entry big enough */
|
|
DatabaseEntry = AllocatePool(UniqueId->UniqueIdLength + SymbolicName.Length + sizeof(DATABASE_ENTRY));
|
|
if (DatabaseEntry != NULL)
|
|
{
|
|
/* Configure it */
|
|
DatabaseEntry->EntrySize = UniqueId->UniqueIdLength + SymbolicName.Length + sizeof(DATABASE_ENTRY);
|
|
DatabaseEntry->EntryReferences = 1;
|
|
DatabaseEntry->SymbolicNameOffset = sizeof(DATABASE_ENTRY);
|
|
DatabaseEntry->SymbolicNameLength = SymbolicName.Length;
|
|
DatabaseEntry->UniqueIdOffset = SymbolicName.Length + sizeof(DATABASE_ENTRY);
|
|
DatabaseEntry->UniqueIdLength = UniqueId->UniqueIdLength;
|
|
RtlCopyMemory((PVOID)((ULONG_PTR)DatabaseEntry + DatabaseEntry->SymbolicNameOffset), SymbolicName.Buffer, DatabaseEntry->SymbolicNameLength);
|
|
RtlCopyMemory((PVOID)((ULONG_PTR)DatabaseEntry + DatabaseEntry->UniqueIdOffset), UniqueId->UniqueId, UniqueId->UniqueIdLength);
|
|
|
|
/* And write it remotely */
|
|
Status = AddRemoteDatabaseEntry(DatabaseHandle, DatabaseEntry);
|
|
FreePool(DatabaseEntry);
|
|
FreePool(UniqueId);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto FreeVolume;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FreePool(UniqueId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Find info about the device associated associated with the mount point */
|
|
KeWaitForSingleObject(&DeviceExtension->DeviceLock, Executive, KernelMode, FALSE, NULL);
|
|
Status = FindDeviceInfo(DeviceExtension, &SymbolicName, FALSE, &ListDeviceInfo);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
FailedFinding = TRUE;
|
|
FreePool(VolumeName.Buffer);
|
|
}
|
|
else
|
|
{
|
|
/* Associate the device with the currrent DB */
|
|
AssociatedDevice = AllocatePool(sizeof(ASSOCIATED_DEVICE_ENTRY));
|
|
if (AssociatedDevice == NULL)
|
|
{
|
|
FreePool(VolumeName.Buffer);
|
|
}
|
|
else
|
|
{
|
|
AssociatedDevice->DeviceInformation = DeviceInformation;
|
|
AssociatedDevice->String.Length = VolumeName.Length;
|
|
AssociatedDevice->String.MaximumLength = VolumeName.MaximumLength;
|
|
AssociatedDevice->String.Buffer = VolumeName.Buffer;
|
|
InsertTailList(&ListDeviceInfo->AssociatedDevicesHead, &AssociatedDevice->AssociatedDevicesEntry);
|
|
}
|
|
|
|
/* If we don't have to skip notifications, notify */
|
|
if (!ListDeviceInfo->SkipNotifications)
|
|
{
|
|
PostOnlineNotification(DeviceExtension, &ListDeviceInfo->SymbolicName);
|
|
}
|
|
}
|
|
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
}
|
|
|
|
/* We don't need mount points any longer */
|
|
ZwClose(Handle);
|
|
|
|
/* Look for the DB again */
|
|
KeWaitForSingleObject(&DeviceExtension->DeviceLock, Executive, KernelMode, FALSE, NULL);
|
|
for (Entry = DeviceExtension->DeviceListHead.Flink;
|
|
Entry != &DeviceExtension->DeviceListHead;
|
|
Entry = Entry->Flink)
|
|
{
|
|
ListDeviceInfo = CONTAINING_RECORD(Entry, DEVICE_INFORMATION, DeviceListEntry);
|
|
if (ListDeviceInfo == DeviceInformation)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Entry == &DeviceExtension->DeviceListHead)
|
|
{
|
|
ListDeviceInfo = NULL;
|
|
}
|
|
|
|
/* Start the pruning loop */
|
|
Offset = 0;
|
|
for (;;)
|
|
{
|
|
/* Get the entry */
|
|
DatabaseEntry = GetRemoteDatabaseEntry(DatabaseHandle, Offset);
|
|
if (DatabaseEntry == NULL)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* It's not referenced anylonger? Prune it */
|
|
if (DatabaseEntry->EntryReferences == 0)
|
|
{
|
|
Status = DeleteRemoteDatabaseEntry(DatabaseHandle, Offset);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
FreePool(DatabaseEntry);
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
goto CloseRDB;
|
|
}
|
|
}
|
|
/* Update the Unique IDs to reflect the changes we might have done previously */
|
|
else
|
|
{
|
|
if (ListDeviceInfo != NULL)
|
|
{
|
|
UpdateReplicatedUniqueIds(ListDeviceInfo, DatabaseEntry);
|
|
}
|
|
|
|
Offset += DatabaseEntry->EntrySize;
|
|
}
|
|
|
|
FreePool(DatabaseEntry);
|
|
}
|
|
|
|
/* We do have a DB now :-) */
|
|
if (ListDeviceInfo != NULL && !FailedFinding)
|
|
{
|
|
DeviceInformation->NoDatabase = FALSE;
|
|
}
|
|
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
|
|
goto CloseRDB;
|
|
|
|
FreeUniqueId:
|
|
FreePool(UniqueId);
|
|
ReleaseDeviceLock:
|
|
KeReleaseSemaphore(&DeviceExtension->DeviceLock, IO_NO_INCREMENT, 1, FALSE);
|
|
FreeDBEntry:
|
|
FreePool(DatabaseEntry);
|
|
FreeVolume:
|
|
FreePool(VolumeName.Buffer);
|
|
CloseReparse:
|
|
ZwClose(Handle);
|
|
CloseRDB:
|
|
CloseRemoteDatabase(DatabaseHandle);
|
|
ReleaseRDS:
|
|
ReleaseRemoteDatabaseSemaphore(DeviceExtension);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
WorkerThread(IN PDEVICE_OBJECT DeviceObject,
|
|
IN PVOID Context)
|
|
{
|
|
ULONG i;
|
|
KEVENT Event;
|
|
KIRQL OldIrql;
|
|
NTSTATUS Status;
|
|
HANDLE SafeEvent;
|
|
PLIST_ENTRY Entry;
|
|
LARGE_INTEGER Timeout;
|
|
PRECONCILE_WORK_ITEM WorkItem;
|
|
PDEVICE_EXTENSION DeviceExtension;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&SafeVolumes,
|
|
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
|
NULL,
|
|
NULL);
|
|
KeInitializeEvent(&Event, NotificationEvent, FALSE);
|
|
Timeout.QuadPart = -10000000LL; /* Wait for 1 second */
|
|
|
|
/* Wait as long as possible for clearance from autochk
|
|
* We will write remote databases only if it is safe
|
|
* to access volumes.
|
|
* First, given we start before SMSS, wait for the
|
|
* event creation.
|
|
*/
|
|
i = 0;
|
|
do
|
|
{
|
|
/* If we started to shutdown, stop waiting forever and jump to last attempt */
|
|
if (Unloading)
|
|
{
|
|
i = 999;
|
|
}
|
|
else
|
|
{
|
|
/* Attempt to open the event */
|
|
Status = ZwOpenEvent(&SafeEvent, EVENT_ALL_ACCESS, &ObjectAttributes);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Wait a bit to give SMSS a chance to create the event */
|
|
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &Timeout);
|
|
}
|
|
|
|
++i;
|
|
}
|
|
while (i < 1000);
|
|
|
|
/* We managed to open the event, wait until autochk signals it */
|
|
if (i < 1000)
|
|
{
|
|
do
|
|
{
|
|
Status = ZwWaitForSingleObject(SafeEvent, FALSE, &Timeout);
|
|
}
|
|
while (Status == STATUS_TIMEOUT && !Unloading);
|
|
|
|
ZwClose(SafeEvent);
|
|
}
|
|
|
|
DeviceExtension = Context;
|
|
|
|
InterlockedExchange(&(DeviceExtension->WorkerThreadStatus), 1);
|
|
|
|
/* Acquire workers lock */
|
|
KeWaitForSingleObject(&(DeviceExtension->WorkerSemaphore), Executive, KernelMode, FALSE, NULL);
|
|
|
|
KeAcquireSpinLock(&(DeviceExtension->WorkerLock), &OldIrql);
|
|
|
|
/* Ensure there are workers */
|
|
while (!IsListEmpty(&(DeviceExtension->WorkerQueueListHead)))
|
|
{
|
|
/* Unqueue a worker */
|
|
Entry = RemoveHeadList(&(DeviceExtension->WorkerQueueListHead));
|
|
WorkItem = CONTAINING_RECORD(Entry,
|
|
RECONCILE_WORK_ITEM,
|
|
WorkerQueueListEntry);
|
|
|
|
KeReleaseSpinLock(&(DeviceExtension->WorkerLock), OldIrql);
|
|
|
|
/* Call it */
|
|
WorkItem->WorkerRoutine(WorkItem->Context);
|
|
|
|
IoFreeWorkItem(WorkItem->WorkItem);
|
|
FreePool(WorkItem);
|
|
|
|
if (InterlockedDecrement(&(DeviceExtension->WorkerReferences)) < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
KeWaitForSingleObject(&(DeviceExtension->WorkerSemaphore), Executive, KernelMode, FALSE, NULL);
|
|
KeAcquireSpinLock(&(DeviceExtension->WorkerLock), &OldIrql);
|
|
}
|
|
KeReleaseSpinLock(&(DeviceExtension->WorkerLock), OldIrql);
|
|
|
|
InterlockedDecrement(&(DeviceExtension->WorkerReferences));
|
|
|
|
/* Reset event */
|
|
KeSetEvent(&UnloadEvent, IO_NO_INCREMENT, FALSE);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
QueueWorkItem(IN PDEVICE_EXTENSION DeviceExtension,
|
|
IN PRECONCILE_WORK_ITEM WorkItem,
|
|
IN PVOID Context)
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
WorkItem->Context = Context;
|
|
|
|
/* When called, lock is already acquired */
|
|
|
|
/* If noone (-1 as references), start to work */
|
|
if (InterlockedIncrement(&(DeviceExtension->WorkerReferences)) == 0)
|
|
{
|
|
IoQueueWorkItem(WorkItem->WorkItem, WorkerThread, DelayedWorkQueue, DeviceExtension);
|
|
}
|
|
|
|
/* Otherwise queue worker for delayed execution */
|
|
KeAcquireSpinLock(&(DeviceExtension->WorkerLock), &OldIrql);
|
|
InsertTailList(&(DeviceExtension->WorkerQueueListHead),
|
|
&(WorkItem->WorkerQueueListEntry));
|
|
KeReleaseSpinLock(&(DeviceExtension->WorkerLock), OldIrql);
|
|
|
|
KeReleaseSemaphore(&(DeviceExtension->WorkerSemaphore), IO_NO_INCREMENT, 1, FALSE);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
QueryVolumeName(IN HANDLE RootDirectory,
|
|
IN PFILE_REPARSE_POINT_INFORMATION ReparsePointInformation,
|
|
IN PUNICODE_STRING FileName OPTIONAL,
|
|
OUT PUNICODE_STRING SymbolicName,
|
|
OUT PUNICODE_STRING VolumeName)
|
|
{
|
|
HANDLE Handle;
|
|
NTSTATUS Status;
|
|
ULONG NeededLength;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
PFILE_NAME_INFORMATION FileNameInfo;
|
|
PREPARSE_DATA_BUFFER ReparseDataBuffer;
|
|
|
|
UNREFERENCED_PARAMETER(ReparsePointInformation);
|
|
|
|
if (!FileName)
|
|
{
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
NULL,
|
|
OBJ_KERNEL_HANDLE,
|
|
RootDirectory,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
FileName,
|
|
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
/* Open volume */
|
|
Status = ZwOpenFile(&Handle,
|
|
SYNCHRONIZE | FILE_READ_ATTRIBUTES,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
(FileName) ? FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT :
|
|
FILE_OPEN_BY_FILE_ID | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
/* Get the reparse point data */
|
|
ReparseDataBuffer = AllocatePool(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
if (!ReparseDataBuffer)
|
|
{
|
|
ZwClose(Handle);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
Status = ZwFsControlFile(Handle,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FSCTL_GET_REPARSE_POINT,
|
|
NULL,
|
|
0,
|
|
ReparseDataBuffer,
|
|
MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
FreePool(ReparseDataBuffer);
|
|
ZwClose(Handle);
|
|
return Status;
|
|
}
|
|
|
|
/* Check that name can fit in buffer */
|
|
if (ReparseDataBuffer->MountPointReparseBuffer.SubstituteNameLength + sizeof(UNICODE_NULL) > SymbolicName->MaximumLength)
|
|
{
|
|
FreePool(ReparseDataBuffer);
|
|
ZwClose(Handle);
|
|
return STATUS_BUFFER_TOO_SMALL;
|
|
}
|
|
|
|
/* Copy symbolic name */
|
|
SymbolicName->Length = ReparseDataBuffer->MountPointReparseBuffer.SubstituteNameLength;
|
|
RtlCopyMemory(SymbolicName->Buffer,
|
|
(PWSTR)((ULONG_PTR)ReparseDataBuffer->MountPointReparseBuffer.PathBuffer +
|
|
ReparseDataBuffer->MountPointReparseBuffer.SubstituteNameOffset),
|
|
ReparseDataBuffer->MountPointReparseBuffer.SubstituteNameLength);
|
|
|
|
FreePool(ReparseDataBuffer);
|
|
|
|
/* Name has to \ terminated */
|
|
if (SymbolicName->Buffer[SymbolicName->Length / sizeof(WCHAR) - 1] != L'\\')
|
|
{
|
|
ZwClose(Handle);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* So that we can delete it, and match mountmgr requirements */
|
|
SymbolicName->Length -= sizeof(WCHAR);
|
|
SymbolicName->Buffer[SymbolicName->Length / sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
/* Also ensure it's really a volume name... */
|
|
if (!MOUNTMGR_IS_VOLUME_NAME(SymbolicName))
|
|
{
|
|
ZwClose(Handle);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Now prepare to really get the name */
|
|
FileNameInfo = AllocatePool(sizeof(FILE_NAME_INFORMATION) + 2 * sizeof(WCHAR));
|
|
if (!FileNameInfo)
|
|
{
|
|
ZwClose(Handle);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
Status = ZwQueryInformationFile(Handle,
|
|
&IoStatusBlock,
|
|
FileNameInfo,
|
|
sizeof(FILE_NAME_INFORMATION) + 2 * sizeof(WCHAR),
|
|
FileNameInformation);
|
|
if (Status == STATUS_BUFFER_OVERFLOW)
|
|
{
|
|
/* As expected... Reallocate with proper size */
|
|
NeededLength = FileNameInfo->FileNameLength;
|
|
FreePool(FileNameInfo);
|
|
|
|
FileNameInfo = AllocatePool(sizeof(FILE_NAME_INFORMATION) + NeededLength);
|
|
if (!FileNameInfo)
|
|
{
|
|
ZwClose(Handle);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* And query name */
|
|
Status = ZwQueryInformationFile(Handle,
|
|
&IoStatusBlock,
|
|
FileNameInfo,
|
|
sizeof(FILE_NAME_INFORMATION) + NeededLength,
|
|
FileNameInformation);
|
|
}
|
|
|
|
ZwClose(Handle);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
return Status;
|
|
}
|
|
|
|
/* Return the volume name */
|
|
VolumeName->Length = (USHORT)FileNameInfo->FileNameLength;
|
|
VolumeName->MaximumLength = (USHORT)FileNameInfo->FileNameLength + sizeof(WCHAR);
|
|
VolumeName->Buffer = AllocatePool(VolumeName->MaximumLength);
|
|
if (!VolumeName->Buffer)
|
|
{
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(VolumeName->Buffer, FileNameInfo->FileName, FileNameInfo->FileNameLength);
|
|
VolumeName->Buffer[FileNameInfo->FileNameLength / sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
FreePool(FileNameInfo);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
OnlineMountedVolumes(IN PDEVICE_EXTENSION DeviceExtension,
|
|
IN PDEVICE_INFORMATION DeviceInformation)
|
|
{
|
|
HANDLE Handle;
|
|
NTSTATUS Status;
|
|
BOOLEAN RestartScan;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
PDEVICE_INFORMATION VolumeDeviceInformation;
|
|
WCHAR FileNameBuffer[0x8], SymbolicNameBuffer[0x64];
|
|
UNICODE_STRING ReparseFile, FileName, SymbolicName, VolumeName;
|
|
FILE_REPARSE_POINT_INFORMATION ReparsePointInformation, SavedReparsePointInformation;
|
|
|
|
/* Removable devices don't have remote database on them */
|
|
if (DeviceInformation->Removable)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Prepare a string with reparse point index */
|
|
ReparseFile.Length = 0;
|
|
ReparseFile.MaximumLength = DeviceInformation->DeviceName.Length
|
|
+ ReparseIndex.Length
|
|
+ sizeof(UNICODE_NULL);
|
|
ReparseFile.Buffer = AllocatePool(ReparseFile.MaximumLength);
|
|
if (!ReparseFile.Buffer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RtlAppendUnicodeStringToString(&ReparseFile, &DeviceInformation->DeviceName);
|
|
RtlAppendUnicodeStringToString(&ReparseFile, &ReparseIndex);
|
|
ReparseFile.Buffer[ReparseFile.Length / sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&ReparseFile,
|
|
OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL);
|
|
|
|
/* Open reparse point */
|
|
Status = ZwOpenFile(&Handle,
|
|
FILE_GENERIC_READ,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
FILE_SYNCHRONOUS_IO_ALERT | FILE_OPEN_REPARSE_POINT);
|
|
FreePool(ReparseFile.Buffer);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DeviceInformation->NoDatabase = FALSE;
|
|
return;
|
|
}
|
|
|
|
/* Query reparse point information
|
|
* We only pay attention to mout point
|
|
*/
|
|
RtlZeroMemory(FileNameBuffer, sizeof(FileNameBuffer));
|
|
FileName.Buffer = FileNameBuffer;
|
|
FileName.Length = sizeof(FileNameBuffer);
|
|
FileName.MaximumLength = sizeof(FileNameBuffer);
|
|
((PULONG)FileNameBuffer)[0] = IO_REPARSE_TAG_MOUNT_POINT;
|
|
Status = ZwQueryDirectoryFile(Handle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
&ReparsePointInformation,
|
|
sizeof(FILE_REPARSE_POINT_INFORMATION),
|
|
FileReparsePointInformation,
|
|
TRUE,
|
|
&FileName,
|
|
FALSE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
ZwClose(Handle);
|
|
return;
|
|
}
|
|
|
|
RestartScan = TRUE;
|
|
|
|
/* Query mount points */
|
|
while (TRUE)
|
|
{
|
|
SymbolicName.Length = 0;
|
|
SymbolicName.MaximumLength = sizeof(SymbolicNameBuffer);
|
|
SymbolicName.Buffer = SymbolicNameBuffer;
|
|
RtlCopyMemory(&SavedReparsePointInformation, &ReparsePointInformation, sizeof(FILE_REPARSE_POINT_INFORMATION));
|
|
|
|
Status = ZwQueryDirectoryFile(Handle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
&ReparsePointInformation,
|
|
sizeof(FILE_REPARSE_POINT_INFORMATION),
|
|
FileReparsePointInformation,
|
|
TRUE,
|
|
(RestartScan) ? &FileName : NULL,
|
|
RestartScan);
|
|
if (!RestartScan)
|
|
{
|
|
if (ReparsePointInformation.FileReference == SavedReparsePointInformation.FileReference &&
|
|
ReparsePointInformation.Tag == SavedReparsePointInformation.Tag)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RestartScan = FALSE;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status) || ReparsePointInformation.Tag != IO_REPARSE_TAG_MOUNT_POINT)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Get the volume name associated to the mount point */
|
|
Status = QueryVolumeName(Handle,
|
|
&ReparsePointInformation,
|
|
NULL, &SymbolicName,
|
|
&VolumeName);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FreePool(VolumeName.Buffer);
|
|
|
|
/* Get its information */
|
|
Status = FindDeviceInfo(DeviceExtension, &SymbolicName,
|
|
FALSE, &VolumeDeviceInformation);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DeviceInformation->NoDatabase = TRUE;
|
|
continue;
|
|
}
|
|
|
|
/* If notification are enabled, mark it online */
|
|
if (!DeviceInformation->SkipNotifications)
|
|
{
|
|
PostOnlineNotification(DeviceExtension, &VolumeDeviceInformation->SymbolicName);
|
|
}
|
|
}
|
|
|
|
ZwClose(Handle);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
ReconcileThisDatabaseWithMaster(IN PDEVICE_EXTENSION DeviceExtension,
|
|
IN PDEVICE_INFORMATION DeviceInformation)
|
|
{
|
|
PRECONCILE_WORK_ITEM WorkItem;
|
|
|
|
/* Removable devices don't have remote database */
|
|
if (DeviceInformation->Removable)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Allocate a work item */
|
|
WorkItem = AllocatePool(sizeof(RECONCILE_WORK_ITEM));
|
|
if (!WorkItem)
|
|
{
|
|
return;
|
|
}
|
|
|
|
WorkItem->WorkItem = IoAllocateWorkItem(DeviceExtension->DeviceObject);
|
|
if (!WorkItem->WorkItem)
|
|
{
|
|
FreePool(WorkItem);
|
|
return;
|
|
}
|
|
|
|
/* And queue it */
|
|
WorkItem->WorkerRoutine = ReconcileThisDatabaseWithMasterWorker;
|
|
WorkItem->DeviceExtension = DeviceExtension;
|
|
WorkItem->DeviceInformation = DeviceInformation;
|
|
QueueWorkItem(DeviceExtension, WorkItem, &(WorkItem->DeviceExtension));
|
|
|
|
/* If there's no automount, and automatic letters
|
|
* all volumes to find those online and notify there presence
|
|
*/
|
|
if (DeviceExtension->WorkerThreadStatus == 0 &&
|
|
DeviceExtension->AutomaticDriveLetter == 1 &&
|
|
DeviceExtension->NoAutoMount == FALSE)
|
|
{
|
|
OnlineMountedVolumes(DeviceExtension, DeviceInformation);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
ReconcileAllDatabasesWithMaster(IN PDEVICE_EXTENSION DeviceExtension)
|
|
{
|
|
PLIST_ENTRY NextEntry;
|
|
PDEVICE_INFORMATION DeviceInformation;
|
|
|
|
/* Browse all the devices */
|
|
for (NextEntry = DeviceExtension->DeviceListHead.Flink;
|
|
NextEntry != &(DeviceExtension->DeviceListHead);
|
|
NextEntry = NextEntry->Flink)
|
|
{
|
|
DeviceInformation = CONTAINING_RECORD(NextEntry,
|
|
DEVICE_INFORMATION,
|
|
DeviceListEntry);
|
|
/* If it's not removable, then, it might have a database to sync */
|
|
if (!DeviceInformation->Removable)
|
|
{
|
|
ReconcileThisDatabaseWithMaster(DeviceExtension, DeviceInformation);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
CreateRemoteDatabaseWorker(IN PDEVICE_OBJECT DeviceObject,
|
|
IN PVOID Context)
|
|
{
|
|
NTSTATUS Status;
|
|
HANDLE Database = 0;
|
|
UNICODE_STRING DatabaseName;
|
|
PMIGRATE_WORK_ITEM WorkItem;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
PDEVICE_INFORMATION DeviceInformation;
|
|
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
|
|
/* Extract context */
|
|
WorkItem = Context;
|
|
DeviceInformation = WorkItem->DeviceInformation;
|
|
|
|
/* Reconstruct appropriate string */
|
|
DatabaseName.Length = 0;
|
|
DatabaseName.MaximumLength = DeviceInformation->DeviceName.Length
|
|
+ RemoteDatabase.Length
|
|
+ sizeof(UNICODE_NULL);
|
|
DatabaseName.Buffer = AllocatePool(DatabaseName.MaximumLength);
|
|
if (DatabaseName.Buffer == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Create the required folder (in which the database will be stored
|
|
* \System Volume Information at root of the volume
|
|
*/
|
|
Status = RtlCreateSystemVolumeInformationFolder(&(DeviceInformation->DeviceName));
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Finish initiating strings */
|
|
RtlAppendUnicodeStringToString(&DatabaseName, &DeviceInformation->DeviceName);
|
|
RtlAppendUnicodeStringToString(&DatabaseName, &RemoteDatabase);
|
|
DatabaseName.Buffer[DatabaseName.Length / sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
/* Create database */
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&DatabaseName,
|
|
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
|
NULL,
|
|
NULL);
|
|
|
|
Status = IoCreateFile(&Database,
|
|
SYNCHRONIZE | READ_CONTROL | FILE_WRITE_ATTRIBUTES |
|
|
FILE_READ_ATTRIBUTES | FILE_WRITE_EA | FILE_READ_EA |
|
|
FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
|
|
0,
|
|
FILE_CREATE,
|
|
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_ALERT,
|
|
NULL,
|
|
0,
|
|
CreateFileTypeNone,
|
|
NULL,
|
|
IO_STOP_ON_SYMLINK | IO_NO_PARAMETER_CHECKING);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (Status == STATUS_STOPPED_ON_SYMLINK)
|
|
{
|
|
DPRINT1("Attempt to exploit CVE-2015-1769. See CORE-10216\n");
|
|
}
|
|
|
|
Database = 0;
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
if (DatabaseName.Buffer)
|
|
{
|
|
FreePool(DatabaseName.Buffer);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
DeviceInformation->Migrated = 1;
|
|
}
|
|
else if (Database != 0)
|
|
{
|
|
ZwClose(Database);
|
|
}
|
|
|
|
IoFreeWorkItem(WorkItem->WorkItem);
|
|
|
|
WorkItem->WorkItem = NULL;
|
|
WorkItem->Status = Status;
|
|
WorkItem->Database = Database;
|
|
|
|
KeSetEvent(WorkItem->Event, 0, FALSE);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
CreateRemoteDatabase(IN PDEVICE_INFORMATION DeviceInformation,
|
|
IN OUT PHANDLE Database)
|
|
{
|
|
KEVENT Event;
|
|
NTSTATUS Status;
|
|
PMIGRATE_WORK_ITEM WorkItem;
|
|
|
|
KeInitializeEvent(&Event, NotificationEvent, FALSE);
|
|
|
|
/* Allocate a work item dedicated to migration */
|
|
WorkItem = AllocatePool(sizeof(MIGRATE_WORK_ITEM));
|
|
if (!WorkItem)
|
|
{
|
|
*Database = 0;
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory(WorkItem, sizeof(MIGRATE_WORK_ITEM));
|
|
WorkItem->Event = &Event;
|
|
WorkItem->DeviceInformation = DeviceInformation;
|
|
WorkItem->WorkItem = IoAllocateWorkItem(DeviceInformation->DeviceExtension->DeviceObject);
|
|
if (!WorkItem->WorkItem)
|
|
{
|
|
FreePool(WorkItem);
|
|
*Database = 0;
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* And queue it */
|
|
IoQueueWorkItem(WorkItem->WorkItem,
|
|
CreateRemoteDatabaseWorker,
|
|
DelayedWorkQueue,
|
|
WorkItem);
|
|
|
|
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
|
|
Status = WorkItem->Status;
|
|
|
|
*Database = (NT_SUCCESS(Status) ? WorkItem->Database : 0);
|
|
|
|
FreePool(WorkItem);
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
HANDLE
|
|
OpenRemoteDatabase(IN PDEVICE_INFORMATION DeviceInformation,
|
|
IN BOOLEAN MigrateDatabase)
|
|
{
|
|
HANDLE Database;
|
|
NTSTATUS Status;
|
|
BOOLEAN PreviousMode;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
UNICODE_STRING DeviceRemoteDatabase;
|
|
|
|
Database = 0;
|
|
|
|
/* Get database name */
|
|
DeviceRemoteDatabase.Length = 0;
|
|
DeviceRemoteDatabase.MaximumLength = DeviceInformation->DeviceName.Length
|
|
+ RemoteDatabase.Length
|
|
+ sizeof(UNICODE_NULL);
|
|
DeviceRemoteDatabase.Buffer = AllocatePool(DeviceRemoteDatabase.MaximumLength);
|
|
if (!DeviceRemoteDatabase.Buffer)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
RtlAppendUnicodeStringToString(&DeviceRemoteDatabase, &DeviceInformation->DeviceName);
|
|
RtlAppendUnicodeStringToString(&DeviceRemoteDatabase, &RemoteDatabase);
|
|
DeviceRemoteDatabase.Buffer[DeviceRemoteDatabase.Length / sizeof(WCHAR)] = UNICODE_NULL;
|
|
|
|
/* Open database */
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&DeviceRemoteDatabase,
|
|
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
|
|
NULL,
|
|
NULL);
|
|
|
|
/* Disable hard errors */
|
|
PreviousMode = IoSetThreadHardErrorMode(FALSE);
|
|
|
|
Status = IoCreateFile(&Database,
|
|
SYNCHRONIZE | READ_CONTROL | FILE_WRITE_ATTRIBUTES |
|
|
FILE_READ_ATTRIBUTES | FILE_WRITE_EA | FILE_READ_EA |
|
|
FILE_APPEND_DATA | FILE_WRITE_DATA | FILE_READ_DATA,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
|
|
0,
|
|
(!MigrateDatabase || DeviceInformation->Migrated == 0) ? FILE_OPEN_IF : FILE_OPEN,
|
|
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_ALERT,
|
|
NULL,
|
|
0,
|
|
CreateFileTypeNone,
|
|
NULL,
|
|
IO_STOP_ON_SYMLINK | IO_NO_PARAMETER_CHECKING);
|
|
if (Status == STATUS_STOPPED_ON_SYMLINK)
|
|
{
|
|
DPRINT1("Attempt to exploit CVE-2015-1769. See CORE-10216\n");
|
|
}
|
|
|
|
/* If base it to be migrated and was opened successfully, go ahead */
|
|
if (MigrateDatabase && NT_SUCCESS(Status))
|
|
{
|
|
CreateRemoteDatabase(DeviceInformation, &Database);
|
|
}
|
|
|
|
IoSetThreadHardErrorMode(PreviousMode);
|
|
FreePool(DeviceRemoteDatabase.Buffer);
|
|
|
|
return Database;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
ChangeRemoteDatabaseUniqueId(IN PDEVICE_INFORMATION DeviceInformation,
|
|
IN PMOUNTDEV_UNIQUE_ID OldUniqueId,
|
|
IN PMOUNTDEV_UNIQUE_ID NewUniqueId)
|
|
{
|
|
LONG Offset = 0;
|
|
HANDLE Database;
|
|
PDATABASE_ENTRY Entry, NewEntry;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
/* Open the remote database */
|
|
Database = OpenRemoteDatabase(DeviceInformation, FALSE);
|
|
if (!Database)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Get all the entries */
|
|
do
|
|
{
|
|
Entry = GetRemoteDatabaseEntry(Database, Offset);
|
|
if (!Entry)
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Not the correct entry, skip it */
|
|
if (Entry->UniqueIdLength != OldUniqueId->UniqueIdLength)
|
|
{
|
|
Offset += Entry->EntrySize;
|
|
FreePool(Entry);
|
|
continue;
|
|
}
|
|
|
|
/* Not the correct entry, skip it */
|
|
if (RtlCompareMemory(OldUniqueId->UniqueId,
|
|
(PVOID)((ULONG_PTR)Entry + Entry->UniqueIdOffset),
|
|
Entry->UniqueIdLength) != Entry->UniqueIdLength)
|
|
{
|
|
Offset += Entry->EntrySize;
|
|
FreePool(Entry);
|
|
continue;
|
|
}
|
|
|
|
/* Here, we have the correct entry */
|
|
NewEntry = AllocatePool(Entry->EntrySize + NewUniqueId->UniqueIdLength - OldUniqueId->UniqueIdLength);
|
|
if (!NewEntry)
|
|
{
|
|
Offset += Entry->EntrySize;
|
|
FreePool(Entry);
|
|
continue;
|
|
}
|
|
|
|
/* Recreate the entry from the previous one */
|
|
NewEntry->EntrySize = Entry->EntrySize + NewUniqueId->UniqueIdLength - OldUniqueId->UniqueIdLength;
|
|
NewEntry->EntryReferences = Entry->EntryReferences;
|
|
NewEntry->SymbolicNameOffset = sizeof(DATABASE_ENTRY);
|
|
NewEntry->SymbolicNameLength = Entry->SymbolicNameLength;
|
|
NewEntry->UniqueIdOffset = Entry->SymbolicNameLength + sizeof(DATABASE_ENTRY);
|
|
NewEntry->UniqueIdLength = NewUniqueId->UniqueIdLength;
|
|
RtlCopyMemory((PVOID)((ULONG_PTR)NewEntry + NewEntry->SymbolicNameOffset),
|
|
(PVOID)((ULONG_PTR)Entry + Entry->SymbolicNameOffset),
|
|
NewEntry->SymbolicNameLength);
|
|
RtlCopyMemory((PVOID)((ULONG_PTR)NewEntry + NewEntry->UniqueIdOffset),
|
|
NewUniqueId->UniqueId, NewEntry->UniqueIdLength);
|
|
|
|
/* Delete old entry */
|
|
Status = DeleteRemoteDatabaseEntry(Database, Offset);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
FreePool(Entry);
|
|
FreePool(NewEntry);
|
|
break;
|
|
}
|
|
|
|
/* And replace with new one */
|
|
Status = AddRemoteDatabaseEntry(Database, NewEntry);
|
|
FreePool(Entry);
|
|
FreePool(NewEntry);
|
|
} while (NT_SUCCESS(Status));
|
|
|
|
CloseRemoteDatabase(Database);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
DeleteDriveLetterRoutine(IN PWSTR ValueName,
|
|
IN ULONG ValueType,
|
|
IN PVOID ValueData,
|
|
IN ULONG ValueLength,
|
|
IN PVOID Context,
|
|
IN PVOID EntryContext)
|
|
{
|
|
PMOUNTDEV_UNIQUE_ID UniqueId;
|
|
UNICODE_STRING RegistryEntry;
|
|
|
|
UNREFERENCED_PARAMETER(EntryContext);
|
|
|
|
if (ValueType != REG_BINARY)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
UniqueId = Context;
|
|
|
|
/* First ensure we have the correct data */
|
|
if (UniqueId->UniqueIdLength != ValueLength)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (RtlCompareMemory(UniqueId->UniqueId, ValueData, ValueLength) != ValueLength)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
RtlInitUnicodeString(&RegistryEntry, ValueName);
|
|
|
|
/* Then, it's a drive letter, erase it */
|
|
if (IsDriveLetter(&RegistryEntry))
|
|
{
|
|
RtlDeleteRegistryValue(RTL_REGISTRY_ABSOLUTE,
|
|
DatabasePath,
|
|
ValueName);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
DeleteRegistryDriveLetter(IN PMOUNTDEV_UNIQUE_ID UniqueId)
|
|
{
|
|
RTL_QUERY_REGISTRY_TABLE QueryTable[2];
|
|
|
|
RtlZeroMemory(QueryTable, sizeof(QueryTable));
|
|
QueryTable[0].QueryRoutine = DeleteDriveLetterRoutine;
|
|
|
|
RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
|
|
DatabasePath,
|
|
QueryTable,
|
|
UniqueId,
|
|
NULL);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
DeleteNoDriveLetterEntryRoutine(IN PWSTR ValueName,
|
|
IN ULONG ValueType,
|
|
IN PVOID ValueData,
|
|
IN ULONG ValueLength,
|
|
IN PVOID Context,
|
|
IN PVOID EntryContext)
|
|
{
|
|
PMOUNTDEV_UNIQUE_ID UniqueId = Context;
|
|
|
|
UNREFERENCED_PARAMETER(EntryContext);
|
|
|
|
/* Ensure we have correct input */
|
|
if (ValueName[0] != L'#' || ValueType != REG_BINARY ||
|
|
UniqueId->UniqueIdLength != ValueLength)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* And then, if unique ID matching, delete entry */
|
|
if (RtlCompareMemory(UniqueId->UniqueId, ValueData, ValueLength) != ValueLength)
|
|
{
|
|
RtlDeleteRegistryValue(RTL_REGISTRY_ABSOLUTE,
|
|
DatabasePath,
|
|
ValueName);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
DeleteNoDriveLetterEntry(IN PMOUNTDEV_UNIQUE_ID UniqueId)
|
|
{
|
|
RTL_QUERY_REGISTRY_TABLE QueryTable[2];
|
|
|
|
RtlZeroMemory(QueryTable, sizeof(QueryTable));
|
|
QueryTable[0].QueryRoutine = DeleteNoDriveLetterEntryRoutine;
|
|
|
|
RtlQueryRegistryValues(RTL_REGISTRY_ABSOLUTE,
|
|
DatabasePath,
|
|
QueryTable,
|
|
UniqueId,
|
|
NULL);
|
|
}
|