reactos/drivers/filesystems/ntfs/fsctl.c

1006 lines
33 KiB
C
Raw Normal View History

/*
* ReactOS kernel
* Copyright (C) 2002 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/filesystems/ntfs/fsctl.c
* PURPOSE: NTFS filesystem driver
* PROGRAMMER: Eric Kohl
* Valentin Verkhovsky
* Pierre Schweitzer
*/
/* INCLUDES *****************************************************************/
#include "ntfs.h"
#include <ntdddisk.h>
#define NDEBUG
#include <debug.h>
/* FUNCTIONS ****************************************************************/
/*
* FUNCTION: Tests if the device contains a filesystem that can be mounted
* by this fsd.
*/
static
NTSTATUS
NtfsHasFileSystem(PDEVICE_OBJECT DeviceToMount)
{
PARTITION_INFORMATION PartitionInfo;
DISK_GEOMETRY DiskGeometry;
ULONG ClusterSize, Size, k;
PBOOT_SECTOR BootSector;
NTSTATUS Status;
DPRINT("NtfsHasFileSystem() called\n");
Size = sizeof(DISK_GEOMETRY);
Status = NtfsDeviceIoControl(DeviceToMount,
IOCTL_DISK_GET_DRIVE_GEOMETRY,
NULL,
0,
&DiskGeometry,
&Size,
TRUE);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtfsDeviceIoControl() failed (Status %lx)\n", Status);
return Status;
}
if (DiskGeometry.MediaType == FixedMedia)
{
/* We have found a hard disk */
Size = sizeof(PARTITION_INFORMATION);
Status = NtfsDeviceIoControl(DeviceToMount,
IOCTL_DISK_GET_PARTITION_INFO,
NULL,
0,
&PartitionInfo,
&Size,
TRUE);
if (!NT_SUCCESS(Status))
{
DPRINT1("NtfsDeviceIoControl() failed (Status %lx)\n", Status);
return Status;
}
if (PartitionInfo.PartitionType != PARTITION_IFS)
{
DPRINT1("Invalid partition type\n");
return STATUS_UNRECOGNIZED_VOLUME;
}
}
DPRINT1("BytesPerSector: %lu\n", DiskGeometry.BytesPerSector);
BootSector = ExAllocatePoolWithTag(NonPagedPool,
DiskGeometry.BytesPerSector,
TAG_NTFS);
if (BootSector == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = NtfsReadSectors(DeviceToMount,
0,
1,
DiskGeometry.BytesPerSector,
(PVOID)BootSector,
TRUE);
if (!NT_SUCCESS(Status))
{
goto ByeBye;
}
/*
* Check values of different fields. If those fields have not expected
* values, we fail, to avoid mounting partitions that Windows won't mount.
*/
/* OEMID: this field must be NTFS */
if (RtlCompareMemory(BootSector->OEMID, "NTFS ", 8) != 8)
{
DPRINT1("Failed with NTFS-identifier: [%.8s]\n", BootSector->OEMID);
Status = STATUS_UNRECOGNIZED_VOLUME;
goto ByeBye;
}
/* Unused0: this field must be COMPLETELY null */
for (k = 0; k < 7; k++)
{
if (BootSector->BPB.Unused0[k] != 0)
{
DPRINT1("Failed in field Unused0: [%.7s]\n", BootSector->BPB.Unused0);
Status = STATUS_UNRECOGNIZED_VOLUME;
goto ByeBye;
}
}
/* Unused3: this field must be COMPLETELY null */
for (k = 0; k < 4; k++)
{
if (BootSector->BPB.Unused3[k] != 0)
{
DPRINT1("Failed in field Unused3: [%.4s]\n", BootSector->BPB.Unused3);
Status = STATUS_UNRECOGNIZED_VOLUME;
goto ByeBye;
}
}
/* Check cluster size */
ClusterSize = BootSector->BPB.BytesPerSector * BootSector->BPB.SectorsPerCluster;
if (ClusterSize != 512 && ClusterSize != 1024 &&
ClusterSize != 2048 && ClusterSize != 4096 &&
ClusterSize != 8192 && ClusterSize != 16384 &&
ClusterSize != 32768 && ClusterSize != 65536)
{
DPRINT1("Cluster size failed: %hu, %hu, %hu\n",
BootSector->BPB.BytesPerSector,
BootSector->BPB.SectorsPerCluster,
ClusterSize);
Status = STATUS_UNRECOGNIZED_VOLUME;
goto ByeBye;
}
ByeBye:
ExFreePool(BootSector);
return Status;
}
static
ULONG
NtfsQueryMftZoneReservation(VOID)
{
ULONG ZoneReservation = 1;
RTL_QUERY_REGISTRY_TABLE QueryTable[2];
RtlZeroMemory(QueryTable, sizeof(QueryTable));
QueryTable[0].Flags = RTL_QUERY_REGISTRY_DIRECT;
QueryTable[0].Name = L"NtfsMftZoneReservation";
QueryTable[0].EntryContext = &ZoneReservation;
RtlQueryRegistryValues(RTL_REGISTRY_CONTROL,
L"FileSystem",
QueryTable,
NULL,
NULL);
return ZoneReservation;
}
static
NTSTATUS
NtfsGetVolumeData(PDEVICE_OBJECT DeviceObject,
PDEVICE_EXTENSION DeviceExt)
{
DISK_GEOMETRY DiskGeometry;
PFILE_RECORD_HEADER VolumeRecord;
PVOLINFO_ATTRIBUTE VolumeInfo;
PBOOT_SECTOR BootSector;
ULONG Size;
PNTFS_INFO NtfsInfo = &DeviceExt->NtfsInfo;
NTSTATUS Status;
PNTFS_ATTR_CONTEXT AttrCtxt;
PNTFS_ATTR_RECORD Attribute;
PNTFS_FCB VolumeFcb;
PWSTR VolumeNameU;
DPRINT("NtfsGetVolumeData() called\n");
Size = sizeof(DISK_GEOMETRY);
Status = NtfsDeviceIoControl(DeviceObject,
IOCTL_DISK_GET_DRIVE_GEOMETRY,
NULL,
0,
&DiskGeometry,
&Size,
TRUE);
if (!NT_SUCCESS(Status))
{
DPRINT("NtfsDeviceIoControl() failed (Status %lx)\n", Status);
return Status;
}
DPRINT("BytesPerSector: %lu\n", DiskGeometry.BytesPerSector);
BootSector = ExAllocatePoolWithTag(NonPagedPool,
DiskGeometry.BytesPerSector,
TAG_NTFS);
if (BootSector == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = NtfsReadSectors(DeviceObject,
0, /* Partition boot sector */
1,
DiskGeometry.BytesPerSector,
(PVOID)BootSector,
TRUE);
if (!NT_SUCCESS(Status))
{
ExFreePool(BootSector);
return Status;
}
/* Read data from the bootsector */
NtfsInfo->BytesPerSector = BootSector->BPB.BytesPerSector;
NtfsInfo->SectorsPerCluster = BootSector->BPB.SectorsPerCluster;
NtfsInfo->BytesPerCluster = BootSector->BPB.BytesPerSector * BootSector->BPB.SectorsPerCluster;
NtfsInfo->SectorCount = BootSector->EBPB.SectorCount;
NtfsInfo->ClusterCount = DeviceExt->NtfsInfo.SectorCount / (ULONGLONG)DeviceExt->NtfsInfo.SectorsPerCluster;
NtfsInfo->MftStart.QuadPart = BootSector->EBPB.MftLocation;
NtfsInfo->MftMirrStart.QuadPart = BootSector->EBPB.MftMirrLocation;
NtfsInfo->SerialNumber = BootSector->EBPB.SerialNumber;
if (BootSector->EBPB.ClustersPerMftRecord > 0)
NtfsInfo->BytesPerFileRecord = BootSector->EBPB.ClustersPerMftRecord * NtfsInfo->BytesPerCluster;
else
NtfsInfo->BytesPerFileRecord = 1 << (-BootSector->EBPB.ClustersPerMftRecord);
if (BootSector->EBPB.ClustersPerIndexRecord > 0)
NtfsInfo->BytesPerIndexRecord = BootSector->EBPB.ClustersPerIndexRecord * NtfsInfo->BytesPerCluster;
else
NtfsInfo->BytesPerIndexRecord = 1 << (-BootSector->EBPB.ClustersPerIndexRecord);
DPRINT("Boot sector information:\n");
DPRINT(" BytesPerSector: %hu\n", BootSector->BPB.BytesPerSector);
DPRINT(" SectorsPerCluster: %hu\n", BootSector->BPB.SectorsPerCluster);
DPRINT(" SectorCount: %I64u\n", BootSector->EBPB.SectorCount);
DPRINT(" MftStart: %I64u\n", BootSector->EBPB.MftLocation);
DPRINT(" MftMirrStart: %I64u\n", BootSector->EBPB.MftMirrLocation);
DPRINT(" ClustersPerMftRecord: %lx\n", BootSector->EBPB.ClustersPerMftRecord);
DPRINT(" ClustersPerIndexRecord: %lx\n", BootSector->EBPB.ClustersPerIndexRecord);
DPRINT(" SerialNumber: %I64x\n", BootSector->EBPB.SerialNumber);
ExFreePool(BootSector);
ExInitializeNPagedLookasideList(&DeviceExt->FileRecLookasideList,
NULL, NULL, 0, NtfsInfo->BytesPerFileRecord, TAG_FILE_REC, 0);
DeviceExt->MasterFileTable = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
if (DeviceExt->MasterFileTable == NULL)
{
ExDeleteNPagedLookasideList(&DeviceExt->FileRecLookasideList);
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = NtfsReadSectors(DeviceObject,
NtfsInfo->MftStart.u.LowPart * NtfsInfo->SectorsPerCluster,
NtfsInfo->BytesPerFileRecord / NtfsInfo->BytesPerSector,
NtfsInfo->BytesPerSector,
(PVOID)DeviceExt->MasterFileTable,
TRUE);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed reading MFT.\n");
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, DeviceExt->MasterFileTable);
ExDeleteNPagedLookasideList(&DeviceExt->FileRecLookasideList);
return Status;
}
Status = FindAttribute(DeviceExt,
DeviceExt->MasterFileTable,
AttributeData,
L"",
0,
&DeviceExt->MFTContext,
&DeviceExt->MftDataOffset);
if (!NT_SUCCESS(Status))
{
DPRINT1("Can't find data attribute for Master File Table.\n");
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, DeviceExt->MasterFileTable);
ExDeleteNPagedLookasideList(&DeviceExt->FileRecLookasideList);
return Status;
}
VolumeRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
if (VolumeRecord == NULL)
{
DPRINT1("Allocation failed for volume record\n");
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, DeviceExt->MasterFileTable);
ExDeleteNPagedLookasideList(&DeviceExt->FileRecLookasideList);
return STATUS_INSUFFICIENT_RESOURCES;
}
/* Read Volume File (MFT index 3) */
DeviceExt->StorageDevice = DeviceObject;
Status = ReadFileRecord(DeviceExt,
NTFS_FILE_VOLUME,
VolumeRecord);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed reading volume file\n");
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, VolumeRecord);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, DeviceExt->MasterFileTable);
ExDeleteNPagedLookasideList(&DeviceExt->FileRecLookasideList);
return Status;
}
/* Enumerate attributes */
NtfsDumpFileAttributes(DeviceExt, DeviceExt->MasterFileTable);
/* Enumerate attributes */
NtfsDumpFileAttributes(DeviceExt, VolumeRecord);
/* Get volume name */
Status = FindAttribute(DeviceExt, VolumeRecord, AttributeVolumeName, L"", 0, &AttrCtxt, NULL);
if (NT_SUCCESS(Status) && AttrCtxt->pRecord->Resident.ValueLength != 0)
{
Attribute = AttrCtxt->pRecord;
DPRINT("Data length %lu\n", AttributeDataLength(Attribute));
NtfsInfo->VolumeLabelLength =
min (Attribute->Resident.ValueLength, MAXIMUM_VOLUME_LABEL_LENGTH);
RtlCopyMemory(NtfsInfo->VolumeLabel,
(PVOID)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset),
NtfsInfo->VolumeLabelLength);
VolumeNameU = NtfsInfo->VolumeLabel;
}
else
{
NtfsInfo->VolumeLabelLength = 0;
VolumeNameU = L"\0";
}
if (NT_SUCCESS(Status))
{
ReleaseAttributeContext(AttrCtxt);
}
VolumeFcb = NtfsCreateFCB(VolumeNameU, NULL, DeviceExt);
if (VolumeFcb == NULL)
{
DPRINT1("Failed allocating volume FCB\n");
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, VolumeRecord);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, DeviceExt->MasterFileTable);
ExDeleteNPagedLookasideList(&DeviceExt->FileRecLookasideList);
return STATUS_INSUFFICIENT_RESOURCES;
}
VolumeFcb->Flags = FCB_IS_VOLUME;
VolumeFcb->RFCB.FileSize.QuadPart = DeviceExt->NtfsInfo.SectorCount * DeviceExt->NtfsInfo.BytesPerSector;
VolumeFcb->RFCB.ValidDataLength = VolumeFcb->RFCB.FileSize;
VolumeFcb->RFCB.AllocationSize = VolumeFcb->RFCB.FileSize;
VolumeFcb->MFTIndex = 0;
DeviceExt->VolumeFcb = VolumeFcb;
/* Get volume information */
Status = FindAttribute(DeviceExt, VolumeRecord, AttributeVolumeInformation, L"", 0, &AttrCtxt, NULL);
if (NT_SUCCESS(Status) && AttrCtxt->pRecord->Resident.ValueLength != 0)
{
Attribute = AttrCtxt->pRecord;
DPRINT("Data length %lu\n", AttributeDataLength (Attribute));
VolumeInfo = (PVOID)((ULONG_PTR)Attribute + Attribute->Resident.ValueOffset);
NtfsInfo->MajorVersion = VolumeInfo->MajorVersion;
NtfsInfo->MinorVersion = VolumeInfo->MinorVersion;
NtfsInfo->Flags = VolumeInfo->Flags;
}
if (NT_SUCCESS(Status))
{
ReleaseAttributeContext(AttrCtxt);
}
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, VolumeRecord);
NtfsInfo->MftZoneReservation = NtfsQueryMftZoneReservation();
return Status;
}
static
NTSTATUS
NtfsMountVolume(PDEVICE_OBJECT DeviceObject,
PIRP Irp)
{
PDEVICE_OBJECT NewDeviceObject = NULL;
PDEVICE_OBJECT DeviceToMount;
PIO_STACK_LOCATION Stack;
PNTFS_FCB Fcb = NULL;
PNTFS_CCB Ccb = NULL;
PNTFS_VCB Vcb = NULL;
NTSTATUS Status;
BOOLEAN Lookaside = FALSE;
DPRINT("NtfsMountVolume() called\n");
if (DeviceObject != NtfsGlobalData->DeviceObject)
{
Status = STATUS_INVALID_DEVICE_REQUEST;
goto ByeBye;
}
Stack = IoGetCurrentIrpStackLocation(Irp);
DeviceToMount = Stack->Parameters.MountVolume.DeviceObject;
Status = NtfsHasFileSystem(DeviceToMount);
if (!NT_SUCCESS(Status))
{
goto ByeBye;
}
Status = IoCreateDevice(NtfsGlobalData->DriverObject,
sizeof(DEVICE_EXTENSION),
NULL,
FILE_DEVICE_DISK_FILE_SYSTEM,
0,
FALSE,
&NewDeviceObject);
if (!NT_SUCCESS(Status))
goto ByeBye;
NewDeviceObject->Flags |= DO_DIRECT_IO;
Vcb = (PVOID)NewDeviceObject->DeviceExtension;
RtlZeroMemory(Vcb, sizeof(NTFS_VCB));
Vcb->Identifier.Type = NTFS_TYPE_VCB;
Vcb->Identifier.Size = sizeof(NTFS_TYPE_VCB);
Status = NtfsGetVolumeData(DeviceToMount,
Vcb);
if (!NT_SUCCESS(Status))
goto ByeBye;
Lookaside = TRUE;
NewDeviceObject->Vpb = DeviceToMount->Vpb;
Vcb->StorageDevice = DeviceToMount;
Vcb->StorageDevice->Vpb->DeviceObject = NewDeviceObject;
Vcb->StorageDevice->Vpb->RealDevice = Vcb->StorageDevice;
Vcb->StorageDevice->Vpb->Flags |= VPB_MOUNTED;
NewDeviceObject->StackSize = Vcb->StorageDevice->StackSize + 1;
NewDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
Vcb->StreamFileObject = IoCreateStreamFileObject(NULL,
Vcb->StorageDevice);
InitializeListHead(&Vcb->FcbListHead);
Fcb = NtfsCreateFCB(NULL, NULL, Vcb);
if (Fcb == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ByeBye;
}
Ccb = ExAllocatePoolWithTag(NonPagedPool,
sizeof(NTFS_CCB),
TAG_CCB);
if (Ccb == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto ByeBye;
}
RtlZeroMemory(Ccb, sizeof(NTFS_CCB));
Ccb->Identifier.Type = NTFS_TYPE_CCB;
Ccb->Identifier.Size = sizeof(NTFS_TYPE_CCB);
Vcb->StreamFileObject->FsContext = Fcb;
Vcb->StreamFileObject->FsContext2 = Ccb;
Vcb->StreamFileObject->SectionObjectPointer = &Fcb->SectionObjectPointers;
Vcb->StreamFileObject->PrivateCacheMap = NULL;
Vcb->StreamFileObject->Vpb = Vcb->Vpb;
Ccb->PtrFileObject = Vcb->StreamFileObject;
Fcb->FileObject = Vcb->StreamFileObject;
Fcb->Vcb = (PDEVICE_EXTENSION)Vcb->StorageDevice;
Fcb->Flags = FCB_IS_VOLUME_STREAM;
Fcb->RFCB.FileSize.QuadPart = Vcb->NtfsInfo.SectorCount * Vcb->NtfsInfo.BytesPerSector;
Fcb->RFCB.ValidDataLength.QuadPart = Vcb->NtfsInfo.SectorCount * Vcb->NtfsInfo.BytesPerSector;
Fcb->RFCB.AllocationSize.QuadPart = Vcb->NtfsInfo.SectorCount * Vcb->NtfsInfo.BytesPerSector; /* Correct? */
// Fcb->Entry.ExtentLocationL = 0;
// Fcb->Entry.DataLengthL = DeviceExt->CdInfo.VolumeSpaceSize * BLOCKSIZE;
_SEH2_TRY
{
CcInitializeCacheMap(Vcb->StreamFileObject,
(PCC_FILE_SIZES)(&Fcb->RFCB.AllocationSize),
TRUE,
&(NtfsGlobalData->CacheMgrCallbacks),
Fcb);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
goto ByeBye;
}
_SEH2_END;
ExInitializeResourceLite(&Vcb->DirResource);
KeInitializeSpinLock(&Vcb->FcbListLock);
/* Get serial number */
NewDeviceObject->Vpb->SerialNumber = Vcb->NtfsInfo.SerialNumber;
/* Get volume label */
NewDeviceObject->Vpb->VolumeLabelLength = Vcb->NtfsInfo.VolumeLabelLength;
RtlCopyMemory(NewDeviceObject->Vpb->VolumeLabel,
Vcb->NtfsInfo.VolumeLabel,
Vcb->NtfsInfo.VolumeLabelLength);
FsRtlNotifyVolumeEvent(Vcb->StreamFileObject, FSRTL_VOLUME_MOUNT);
Status = STATUS_SUCCESS;
ByeBye:
if (!NT_SUCCESS(Status))
{
/* Cleanup */
if (Vcb && Vcb->StreamFileObject)
ObDereferenceObject(Vcb->StreamFileObject);
if (Fcb)
NtfsDestroyFCB(Fcb);
if (Ccb)
ExFreePool(Ccb);
if (Lookaside)
ExDeleteNPagedLookasideList(&Vcb->FileRecLookasideList);
if (NewDeviceObject)
IoDeleteDevice(NewDeviceObject);
}
DPRINT("NtfsMountVolume() done (Status: %lx)\n", Status);
return Status;
}
static
NTSTATUS
NtfsVerifyVolume(PDEVICE_OBJECT DeviceObject,
PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
UNREFERENCED_PARAMETER(Irp);
DPRINT1("NtfsVerifyVolume() called\n");
return STATUS_WRONG_VOLUME;
}
static
NTSTATUS
GetNfsVolumeData(PDEVICE_EXTENSION DeviceExt,
PIRP Irp)
{
PIO_STACK_LOCATION Stack;
PNTFS_VOLUME_DATA_BUFFER DataBuffer;
PNTFS_ATTR_RECORD Attribute;
[NTFS] Totally rewrite the way MFT records attributes are handled. Up to now, we were having really similar loops, only looking at the resident part of the attribute list, not really caring about how the loop was going. This was leading to some issues: - In case the attribute we were looking for was stored in the non-resident part of the attribute list, we would miss it (excepted in the case of FindAttribute() which was properly browsing the whole attribute list). - In the specific case of FindAttribute(), one would have been able to setup a broken MFT record with the resident attribute list pointing on the non resident attribute list which itself would point to the resident attribute list. In such case, the driver would loop forever caught on the loop, allocating tones of memory. It was possible to trigger this by user space, from a non-privileged user, just by browsing the right directory entry. - In the case of the other loops (non FindAttribute()), another issue (other than missing attributes) was present, one would have been able to setup a broken MFT record with an attribute of null-length. This would have caused the driver to loop forever on the attribute list. This could be triggered from usermode too. And could be triggered by a non-privileged user. This commit introduces a new set of functions for attributes browsing: FindFirstAttribute(), FindNextAttribute(), FindCloseAttribute(). It allows safely browsing attributes and handles broken cases. It also performs reading of the attribute list when present and makes sure there's only one read. This method should be the only one to use to browse the attributes. The whole NTFS code base has been converted to use this newly set of functions. This really simplifies the implementation of FindAttribute(), and prevent unsafe code duplication. CORE-10037 #resolve #comment Fixed with r68829 svn path=/trunk/; revision=68829
2015-08-26 18:20:04 +00:00
FIND_ATTR_CONTXT Context;
NTSTATUS Status;
DataBuffer = (PNTFS_VOLUME_DATA_BUFFER)Irp->AssociatedIrp.SystemBuffer;
Stack = IoGetCurrentIrpStackLocation(Irp);
if (Stack->Parameters.FileSystemControl.OutputBufferLength < sizeof(NTFS_VOLUME_DATA_BUFFER) ||
Irp->UserBuffer == NULL)
{
DPRINT1("Invalid output! %d %p\n", Stack->Parameters.FileSystemControl.OutputBufferLength, Irp->UserBuffer);
return STATUS_INVALID_PARAMETER;
}
DataBuffer->VolumeSerialNumber.QuadPart = DeviceExt->NtfsInfo.SerialNumber;
DataBuffer->NumberSectors.QuadPart = DeviceExt->NtfsInfo.SectorCount;
DataBuffer->TotalClusters.QuadPart = DeviceExt->NtfsInfo.ClusterCount;
DataBuffer->FreeClusters.QuadPart = NtfsGetFreeClusters(DeviceExt);
DataBuffer->TotalReserved.QuadPart = 0LL; // FIXME
DataBuffer->BytesPerSector = DeviceExt->NtfsInfo.BytesPerSector;
DataBuffer->BytesPerCluster = DeviceExt->NtfsInfo.BytesPerCluster;
DataBuffer->BytesPerFileRecordSegment = DeviceExt->NtfsInfo.BytesPerFileRecord;
DataBuffer->ClustersPerFileRecordSegment = DeviceExt->NtfsInfo.BytesPerFileRecord / DeviceExt->NtfsInfo.BytesPerCluster;
DataBuffer->MftStartLcn.QuadPart = DeviceExt->NtfsInfo.MftStart.QuadPart;
DataBuffer->Mft2StartLcn.QuadPart = DeviceExt->NtfsInfo.MftMirrStart.QuadPart;
DataBuffer->MftZoneStart.QuadPart = 0; // FIXME
DataBuffer->MftZoneEnd.QuadPart = 0; // FIXME
[NTFS] Totally rewrite the way MFT records attributes are handled. Up to now, we were having really similar loops, only looking at the resident part of the attribute list, not really caring about how the loop was going. This was leading to some issues: - In case the attribute we were looking for was stored in the non-resident part of the attribute list, we would miss it (excepted in the case of FindAttribute() which was properly browsing the whole attribute list). - In the specific case of FindAttribute(), one would have been able to setup a broken MFT record with the resident attribute list pointing on the non resident attribute list which itself would point to the resident attribute list. In such case, the driver would loop forever caught on the loop, allocating tones of memory. It was possible to trigger this by user space, from a non-privileged user, just by browsing the right directory entry. - In the case of the other loops (non FindAttribute()), another issue (other than missing attributes) was present, one would have been able to setup a broken MFT record with an attribute of null-length. This would have caused the driver to loop forever on the attribute list. This could be triggered from usermode too. And could be triggered by a non-privileged user. This commit introduces a new set of functions for attributes browsing: FindFirstAttribute(), FindNextAttribute(), FindCloseAttribute(). It allows safely browsing attributes and handles broken cases. It also performs reading of the attribute list when present and makes sure there's only one read. This method should be the only one to use to browse the attributes. The whole NTFS code base has been converted to use this newly set of functions. This really simplifies the implementation of FindAttribute(), and prevent unsafe code duplication. CORE-10037 #resolve #comment Fixed with r68829 svn path=/trunk/; revision=68829
2015-08-26 18:20:04 +00:00
Status = FindFirstAttribute(&Context, DeviceExt, DeviceExt->MasterFileTable, FALSE, &Attribute);
while (NT_SUCCESS(Status))
{
if (Attribute->Type == AttributeData)
{
ASSERT(Attribute->IsNonResident);
DataBuffer->MftValidDataLength.QuadPart = Attribute->NonResident.DataSize;
break;
}
[NTFS] Totally rewrite the way MFT records attributes are handled. Up to now, we were having really similar loops, only looking at the resident part of the attribute list, not really caring about how the loop was going. This was leading to some issues: - In case the attribute we were looking for was stored in the non-resident part of the attribute list, we would miss it (excepted in the case of FindAttribute() which was properly browsing the whole attribute list). - In the specific case of FindAttribute(), one would have been able to setup a broken MFT record with the resident attribute list pointing on the non resident attribute list which itself would point to the resident attribute list. In such case, the driver would loop forever caught on the loop, allocating tones of memory. It was possible to trigger this by user space, from a non-privileged user, just by browsing the right directory entry. - In the case of the other loops (non FindAttribute()), another issue (other than missing attributes) was present, one would have been able to setup a broken MFT record with an attribute of null-length. This would have caused the driver to loop forever on the attribute list. This could be triggered from usermode too. And could be triggered by a non-privileged user. This commit introduces a new set of functions for attributes browsing: FindFirstAttribute(), FindNextAttribute(), FindCloseAttribute(). It allows safely browsing attributes and handles broken cases. It also performs reading of the attribute list when present and makes sure there's only one read. This method should be the only one to use to browse the attributes. The whole NTFS code base has been converted to use this newly set of functions. This really simplifies the implementation of FindAttribute(), and prevent unsafe code duplication. CORE-10037 #resolve #comment Fixed with r68829 svn path=/trunk/; revision=68829
2015-08-26 18:20:04 +00:00
Status = FindNextAttribute(&Context, &Attribute);
}
[NTFS] Totally rewrite the way MFT records attributes are handled. Up to now, we were having really similar loops, only looking at the resident part of the attribute list, not really caring about how the loop was going. This was leading to some issues: - In case the attribute we were looking for was stored in the non-resident part of the attribute list, we would miss it (excepted in the case of FindAttribute() which was properly browsing the whole attribute list). - In the specific case of FindAttribute(), one would have been able to setup a broken MFT record with the resident attribute list pointing on the non resident attribute list which itself would point to the resident attribute list. In such case, the driver would loop forever caught on the loop, allocating tones of memory. It was possible to trigger this by user space, from a non-privileged user, just by browsing the right directory entry. - In the case of the other loops (non FindAttribute()), another issue (other than missing attributes) was present, one would have been able to setup a broken MFT record with an attribute of null-length. This would have caused the driver to loop forever on the attribute list. This could be triggered from usermode too. And could be triggered by a non-privileged user. This commit introduces a new set of functions for attributes browsing: FindFirstAttribute(), FindNextAttribute(), FindCloseAttribute(). It allows safely browsing attributes and handles broken cases. It also performs reading of the attribute list when present and makes sure there's only one read. This method should be the only one to use to browse the attributes. The whole NTFS code base has been converted to use this newly set of functions. This really simplifies the implementation of FindAttribute(), and prevent unsafe code duplication. CORE-10037 #resolve #comment Fixed with r68829 svn path=/trunk/; revision=68829
2015-08-26 18:20:04 +00:00
FindCloseAttribute(&Context);
Irp->IoStatus.Information = sizeof(NTFS_VOLUME_DATA_BUFFER);
if (Stack->Parameters.FileSystemControl.OutputBufferLength >= sizeof(NTFS_EXTENDED_VOLUME_DATA) + sizeof(NTFS_VOLUME_DATA_BUFFER))
{
PNTFS_EXTENDED_VOLUME_DATA ExtendedData = (PNTFS_EXTENDED_VOLUME_DATA)((ULONG_PTR)Irp->UserBuffer + sizeof(NTFS_VOLUME_DATA_BUFFER));
ExtendedData->ByteCount = sizeof(NTFS_EXTENDED_VOLUME_DATA);
ExtendedData->MajorVersion = DeviceExt->NtfsInfo.MajorVersion;
ExtendedData->MinorVersion = DeviceExt->NtfsInfo.MinorVersion;
Irp->IoStatus.Information += sizeof(NTFS_EXTENDED_VOLUME_DATA);
}
return STATUS_SUCCESS;
}
static
NTSTATUS
GetNtfsFileRecord(PDEVICE_EXTENSION DeviceExt,
PIRP Irp)
{
NTSTATUS Status;
PIO_STACK_LOCATION Stack;
PNTFS_FILE_RECORD_INPUT_BUFFER InputBuffer;
PFILE_RECORD_HEADER FileRecord;
PNTFS_FILE_RECORD_OUTPUT_BUFFER OutputBuffer;
ULONGLONG MFTRecord;
Stack = IoGetCurrentIrpStackLocation(Irp);
if (Stack->Parameters.FileSystemControl.InputBufferLength < sizeof(NTFS_FILE_RECORD_INPUT_BUFFER) ||
Irp->AssociatedIrp.SystemBuffer == NULL)
{
DPRINT1("Invalid input! %d %p\n", Stack->Parameters.FileSystemControl.InputBufferLength, Irp->AssociatedIrp.SystemBuffer);
return STATUS_INVALID_PARAMETER;
}
if (Stack->Parameters.FileSystemControl.OutputBufferLength < (FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer) + DeviceExt->NtfsInfo.BytesPerFileRecord) ||
Irp->AssociatedIrp.SystemBuffer == NULL)
{
DPRINT1("Invalid output! %d %p\n", Stack->Parameters.FileSystemControl.OutputBufferLength, Irp->AssociatedIrp.SystemBuffer);
return STATUS_BUFFER_TOO_SMALL;
}
FileRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
if (FileRecord == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
InputBuffer = (PNTFS_FILE_RECORD_INPUT_BUFFER)Irp->AssociatedIrp.SystemBuffer;
MFTRecord = InputBuffer->FileReferenceNumber.QuadPart;
DPRINT1("Requesting: %I64x\n", MFTRecord);
do
{
Status = ReadFileRecord(DeviceExt, MFTRecord, FileRecord);
if (NT_SUCCESS(Status))
{
if (FileRecord->Flags & FRH_IN_USE)
{
break;
}
}
--MFTRecord;
} while (TRUE);
DPRINT1("Returning: %I64x\n", MFTRecord);
OutputBuffer = (PNTFS_FILE_RECORD_OUTPUT_BUFFER)Irp->AssociatedIrp.SystemBuffer;
OutputBuffer->FileReferenceNumber.QuadPart = MFTRecord;
OutputBuffer->FileRecordLength = DeviceExt->NtfsInfo.BytesPerFileRecord;
RtlCopyMemory(OutputBuffer->FileRecordBuffer, FileRecord, DeviceExt->NtfsInfo.BytesPerFileRecord);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, FileRecord);
Irp->IoStatus.Information = FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer) + DeviceExt->NtfsInfo.BytesPerFileRecord;
return STATUS_SUCCESS;
}
static
NTSTATUS
GetVolumeBitmap(PDEVICE_EXTENSION DeviceExt,
PIRP Irp)
{
NTSTATUS Status = STATUS_SUCCESS;
PIO_STACK_LOCATION Stack;
PVOLUME_BITMAP_BUFFER BitmapBuffer;
LONGLONG StartingLcn;
PFILE_RECORD_HEADER BitmapRecord;
PNTFS_ATTR_CONTEXT DataContext;
ULONGLONG TotalClusters;
ULONGLONG ToCopy;
BOOLEAN Overflow = FALSE;
DPRINT("GetVolumeBitmap(%p, %p)\n", DeviceExt, Irp);
Stack = IoGetCurrentIrpStackLocation(Irp);
if (Stack->Parameters.FileSystemControl.InputBufferLength < sizeof(STARTING_LCN_INPUT_BUFFER))
{
DPRINT1("Invalid input! %d\n", Stack->Parameters.FileSystemControl.InputBufferLength);
return STATUS_INVALID_PARAMETER;
}
if (Stack->Parameters.FileSystemControl.OutputBufferLength < sizeof(VOLUME_BITMAP_BUFFER))
{
DPRINT1("Invalid output! %d\n", Stack->Parameters.FileSystemControl.OutputBufferLength);
return STATUS_BUFFER_TOO_SMALL;
}
BitmapBuffer = NtfsGetUserBuffer(Irp, FALSE);
if (Irp->RequestorMode == UserMode)
{
_SEH2_TRY
{
ProbeForRead(Stack->Parameters.FileSystemControl.Type3InputBuffer,
Stack->Parameters.FileSystemControl.InputBufferLength,
sizeof(CHAR));
ProbeForWrite(BitmapBuffer, Stack->Parameters.FileSystemControl.OutputBufferLength,
sizeof(CHAR));
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
else
{
if (Stack->Parameters.FileSystemControl.Type3InputBuffer == NULL ||
BitmapBuffer == NULL)
{
Status = STATUS_INVALID_PARAMETER;
}
}
if (!NT_SUCCESS(Status))
{
DPRINT1("Invalid buffer! %p %p\n", Stack->Parameters.FileSystemControl.Type3InputBuffer, BitmapBuffer);
return Status;
}
StartingLcn = ((PSTARTING_LCN_INPUT_BUFFER)Stack->Parameters.FileSystemControl.Type3InputBuffer)->StartingLcn.QuadPart;
if (StartingLcn > DeviceExt->NtfsInfo.ClusterCount)
{
DPRINT1("Requested bitmap start beyond partition end: %I64x %I64x\n", DeviceExt->NtfsInfo.ClusterCount, StartingLcn);
return STATUS_INVALID_PARAMETER;
}
/* Round down to a multiple of 8 */
StartingLcn = StartingLcn & ~7;
TotalClusters = DeviceExt->NtfsInfo.ClusterCount - StartingLcn;
ToCopy = TotalClusters / 8;
if ((ToCopy + FIELD_OFFSET(VOLUME_BITMAP_BUFFER, Buffer)) > Stack->Parameters.FileSystemControl.OutputBufferLength)
{
DPRINT1("Buffer too small: %x, needed: %x\n", Stack->Parameters.FileSystemControl.OutputBufferLength, (ToCopy + FIELD_OFFSET(VOLUME_BITMAP_BUFFER, Buffer)));
Overflow = TRUE;
ToCopy = Stack->Parameters.FileSystemControl.OutputBufferLength - FIELD_OFFSET(VOLUME_BITMAP_BUFFER, Buffer);
}
BitmapRecord = ExAllocateFromNPagedLookasideList(&DeviceExt->FileRecLookasideList);
if (BitmapRecord == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = ReadFileRecord(DeviceExt, NTFS_FILE_BITMAP, BitmapRecord);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed reading volume bitmap: %lx\n", Status);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, BitmapRecord);
return Status;
}
Status = FindAttribute(DeviceExt, BitmapRecord, AttributeData, L"", 0, &DataContext, NULL);
if (!NT_SUCCESS(Status))
{
DPRINT1("Failed find $DATA for bitmap: %lx\n", Status);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, BitmapRecord);
return Status;
}
BitmapBuffer->StartingLcn.QuadPart = StartingLcn;
BitmapBuffer->BitmapSize.QuadPart = ToCopy * 8;
Irp->IoStatus.Information = FIELD_OFFSET(VOLUME_BITMAP_BUFFER, Buffer);
_SEH2_TRY
{
Irp->IoStatus.Information += ReadAttribute(DeviceExt, DataContext, StartingLcn / 8, (PCHAR)BitmapBuffer->Buffer, ToCopy);
Status = (Overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
ReleaseAttributeContext(DataContext);
ExFreeToNPagedLookasideList(&DeviceExt->FileRecLookasideList, BitmapRecord);
return Status;
}
static
NTSTATUS
LockOrUnlockVolume(PDEVICE_EXTENSION DeviceExt,
PIRP Irp,
BOOLEAN Lock)
{
PFILE_OBJECT FileObject;
PNTFS_FCB Fcb;
PIO_STACK_LOCATION Stack;
DPRINT("LockOrUnlockVolume(%p, %p, %d)\n", DeviceExt, Irp, Lock);
Stack = IoGetCurrentIrpStackLocation(Irp);
FileObject = Stack->FileObject;
Fcb = FileObject->FsContext;
/* Only allow locking with the volume open */
if (!(Fcb->Flags & FCB_IS_VOLUME))
{
return STATUS_ACCESS_DENIED;
}
/* Bail out if it's already in the demanded state */
if (((DeviceExt->Flags & VCB_VOLUME_LOCKED) && Lock) ||
(!(DeviceExt->Flags & VCB_VOLUME_LOCKED) && !Lock))
{
return STATUS_ACCESS_DENIED;
}
/* Deny locking if we're not alone */
if (Lock && DeviceExt->OpenHandleCount != 1)
{
return STATUS_ACCESS_DENIED;
}
/* Finally, proceed */
if (Lock)
{
DeviceExt->Flags |= VCB_VOLUME_LOCKED;
}
else
{
DeviceExt->Flags &= ~VCB_VOLUME_LOCKED;
}
return STATUS_SUCCESS;
}
static
NTSTATUS
NtfsUserFsRequest(PDEVICE_OBJECT DeviceObject,
PIRP Irp)
{
NTSTATUS Status;
PIO_STACK_LOCATION Stack;
PDEVICE_EXTENSION DeviceExt;
DPRINT("NtfsUserFsRequest(%p, %p)\n", DeviceObject, Irp);
Stack = IoGetCurrentIrpStackLocation(Irp);
DeviceExt = DeviceObject->DeviceExtension;
switch (Stack->Parameters.FileSystemControl.FsControlCode)
{
case FSCTL_CREATE_USN_JOURNAL:
case FSCTL_DELETE_USN_JOURNAL:
case FSCTL_ENUM_USN_DATA:
case FSCTL_EXTEND_VOLUME:
//case FSCTL_GET_RETRIEVAL_POINTER_BASE:
case FSCTL_GET_RETRIEVAL_POINTERS:
//case FSCTL_LOOKUP_STREAM_FROM_CLUSTER:
case FSCTL_MARK_HANDLE:
case FSCTL_MOVE_FILE:
case FSCTL_QUERY_USN_JOURNAL:
case FSCTL_READ_FILE_USN_DATA:
case FSCTL_READ_USN_JOURNAL:
//case FSCTL_SHRINK_VOLUME:
case FSCTL_WRITE_USN_CLOSE_RECORD:
UNIMPLEMENTED;
DPRINT1("Unimplemented user request: %x\n", Stack->Parameters.FileSystemControl.FsControlCode);
Status = STATUS_NOT_IMPLEMENTED;
break;
case FSCTL_LOCK_VOLUME:
Status = LockOrUnlockVolume(DeviceExt, Irp, TRUE);
break;
case FSCTL_UNLOCK_VOLUME:
Status = LockOrUnlockVolume(DeviceExt, Irp, FALSE);
break;
case FSCTL_GET_NTFS_VOLUME_DATA:
Status = GetNfsVolumeData(DeviceExt, Irp);
break;
case FSCTL_GET_NTFS_FILE_RECORD:
Status = GetNtfsFileRecord(DeviceExt, Irp);
break;
case FSCTL_GET_VOLUME_BITMAP:
Status = GetVolumeBitmap(DeviceExt, Irp);
break;
default:
DPRINT("Invalid user request: %x\n", Stack->Parameters.FileSystemControl.FsControlCode);
Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
return Status;
}
NTSTATUS
NtfsFileSystemControl(PNTFS_IRP_CONTEXT IrpContext)
{
NTSTATUS Status;
PIRP Irp;
PDEVICE_OBJECT DeviceObject;
DPRINT("NtfsFileSystemControl() called\n");
DeviceObject = IrpContext->DeviceObject;
Irp = IrpContext->Irp;
Irp->IoStatus.Information = 0;
switch (IrpContext->MinorFunction)
{
case IRP_MN_KERNEL_CALL:
DPRINT1("NTFS: IRP_MN_USER_FS_REQUEST\n");
Status = STATUS_INVALID_DEVICE_REQUEST;
break;
case IRP_MN_USER_FS_REQUEST:
Status = NtfsUserFsRequest(DeviceObject, Irp);
break;
case IRP_MN_MOUNT_VOLUME:
DPRINT("NTFS: IRP_MN_MOUNT_VOLUME\n");
Status = NtfsMountVolume(DeviceObject, Irp);
break;
case IRP_MN_VERIFY_VOLUME:
DPRINT1("NTFS: IRP_MN_VERIFY_VOLUME\n");
Status = NtfsVerifyVolume(DeviceObject, Irp);
break;
default:
DPRINT1("NTFS FSC: MinorFunction %d\n", IrpContext->MinorFunction);
Status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
return Status;
}
/* EOF */