mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 18:15:11 +00:00
5f26356079
This "NoAutoMount" member was not consistently used. Sometimes it was used correctly, some other times it was used as "not NoAutoMount" i.e. "AutoMount" enabled. Fix this consistently throughout the source, and fix also some comments.
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 the worker thread isn't started yet, automatic drive letter is
|
|
* enabled but automount disabled, manually set mounted volumes online.
|
|
* Otherwise, they will be set online during database reconciliation. */
|
|
if (DeviceExtension->WorkerThreadStatus == 0 &&
|
|
DeviceExtension->AutomaticDriveLetter &&
|
|
DeviceExtension->NoAutoMount)
|
|
{
|
|
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);
|
|
}
|