/* * 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 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); }