/** * @file sys/fsctl.c * * @copyright 2015-2016 Bill Zissimopoulos */ /* * This file is part of WinFsp. * * You can redistribute it and/or modify it under the terms of the * GNU Affero General Public License version 3 as published by the * Free Software Foundation. * * Licensees holding a valid commercial license may use this file in * accordance with the commercial license agreement provided with the * software. */ #include static NTSTATUS FspFsctlFileSystemControl( PDEVICE_OBJECT FsctlDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp); static NTSTATUS FspFsvolFileSystemControlReparsePoint( PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp, BOOLEAN IsWrite); static NTSTATUS FspFsvolFileSystemControlReparsePointComplete( PIRP Irp, const FSP_FSCTL_TRANSACT_RSP *Response, BOOLEAN IsWrite); static NTSTATUS FspFsvolFileSystemControl( PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp); FSP_IOCMPL_DISPATCH FspFsvolFileSystemControlComplete; static FSP_IOP_REQUEST_FINI FspFsvolFileSystemControlRequestFini; FSP_DRIVER_DISPATCH FspFileSystemControl; #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, FspFsctlFileSystemControl) #pragma alloc_text(PAGE, FspFsvolFileSystemControlReparsePoint) #pragma alloc_text(PAGE, FspFsvolFileSystemControlReparsePointComplete) #pragma alloc_text(PAGE, FspFsvolFileSystemControl) #pragma alloc_text(PAGE, FspFsvolFileSystemControlComplete) #pragma alloc_text(PAGE, FspFsvolFileSystemControlRequestFini) #pragma alloc_text(PAGE, FspFileSystemControl) #endif enum { RequestFileNode = 0, }; static NTSTATUS FspFsctlFileSystemControl( PDEVICE_OBJECT FsctlDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp) { PAGED_CODE(); NTSTATUS Result = STATUS_INVALID_DEVICE_REQUEST; switch (IrpSp->MinorFunction) { case IRP_MN_USER_FS_REQUEST: switch (IrpSp->Parameters.FileSystemControl.FsControlCode) { case FSP_FSCTL_VOLUME_NAME: if (0 != IrpSp->FileObject->FsContext2) Result = FspVolumeGetName(FsctlDeviceObject, Irp, IrpSp); break; case FSP_FSCTL_VOLUME_LIST: Result = FspVolumeGetNameList(FsctlDeviceObject, Irp, IrpSp); break; case FSP_FSCTL_TRANSACT: case FSP_FSCTL_TRANSACT_BATCH: if (0 != IrpSp->FileObject->FsContext2) Result = FspVolumeTransact(FsctlDeviceObject, Irp, IrpSp); break; case FSP_FSCTL_STOP: if (0 != IrpSp->FileObject->FsContext2) Result = FspVolumeStop(FsctlDeviceObject, Irp, IrpSp); break; } break; case IRP_MN_MOUNT_VOLUME: Result = FspVolumeMount(FsctlDeviceObject, Irp, IrpSp); break; } return Result; } static NTSTATUS FspFsvolFileSystemControlReparsePoint( PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp, BOOLEAN IsWrite) { PAGED_CODE(); FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject); PFILE_OBJECT FileObject = IrpSp->FileObject; /* do we support reparse points? */ if (!FsvolDeviceExtension->VolumeParams.ReparsePoints) return STATUS_INVALID_DEVICE_REQUEST; /* is this a valid FileObject? */ if (!FspFileNodeIsValid(FileObject->FsContext)) return STATUS_INVALID_DEVICE_REQUEST; NTSTATUS Result; FSP_FILE_NODE *FileNode = FileObject->FsContext; FSP_FILE_DESC *FileDesc = FileObject->FsContext2; ULONG FsControlCode = IrpSp->Parameters.FileSystemControl.FsControlCode; PVOID InputBuffer = Irp->AssociatedIrp.SystemBuffer; PVOID OutputBuffer = Irp->UserBuffer; ULONG InputBufferLength = IrpSp->Parameters.FileSystemControl.InputBufferLength; ULONG OutputBufferLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength; PREPARSE_DATA_BUFFER ReparseData; PWSTR ReparseTargetPath; USHORT ReparseTargetPathLength; UINT16 TargetOnFileSystem = 0; FSP_FSCTL_TRANSACT_REQ *Request; ASSERT(FileNode == FileDesc->FileNode); if (IsWrite) { if (0 == InputBuffer || 0 == InputBufferLength || FSP_FSCTL_TRANSACT_REQ_BUFFER_SIZEMAX - (FileNode->FileName.Length + sizeof(WCHAR)) < InputBufferLength) return STATUS_INVALID_PARAMETER; Result = FsRtlValidateReparsePointBuffer(InputBufferLength, InputBuffer); if (!NT_SUCCESS(Result)) return Result; ReparseData = (PREPARSE_DATA_BUFFER)InputBuffer; if (IO_REPARSE_TAG_SYMLINK == ReparseData->ReparseTag) { /* NTFS severely limits symbolic links; we will not do that unless our file system asks */ if (FsvolDeviceExtension->VolumeParams.ReparsePointsAccessCheck) { if (KernelMode != Irp->RequestorMode && !SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_CREATE_SYMBOLIC_LINK_PRIVILEGE), UserMode)) return STATUS_PRIVILEGE_NOT_HELD; } ReparseTargetPath = ReparseData->SymbolicLinkReparseBuffer.PathBuffer + ReparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR); ReparseTargetPathLength = ReparseData->SymbolicLinkReparseBuffer.SubstituteNameLength; /* is this an absolute path? determine if target resides on same device as link */ if (!FlagOn(ReparseData->SymbolicLinkReparseBuffer.Flags, SYMLINK_FLAG_RELATIVE) && ReparseTargetPathLength >= sizeof(WCHAR) && L'\\' == ReparseTargetPath[0]) { UNICODE_STRING TargetObjectName; PDEVICE_OBJECT TargetDeviceObject; PFILE_OBJECT TargetFileObject; ULONG TargetFileNameIndex; ULONG32 TargetProviderId; FSRTL_MUP_PROVIDER_INFO_LEVEL_1 ProviderInfo; ULONG ProviderInfoSize; TargetObjectName.Length = TargetObjectName.MaximumLength = ReparseTargetPathLength; TargetObjectName.Buffer = ReparseTargetPath; Result = FspGetDeviceObjectPointer(&TargetObjectName, FILE_READ_DATA, &TargetFileNameIndex, &TargetFileObject, &TargetDeviceObject); if (!NT_SUCCESS(Result)) goto target_check_exit; if (TargetFileNameIndex < ReparseTargetPathLength && IoGetRelatedDeviceObject(FileObject) == TargetDeviceObject) { if (0 == FsvolDeviceExtension->VolumePrefix.Length) TargetOnFileSystem = (UINT16)TargetFileNameIndex; else { ProviderInfoSize = sizeof ProviderInfo; Result = FsRtlMupGetProviderInfoFromFileObject(TargetFileObject, 1, &ProviderInfo, &ProviderInfoSize); if (NT_SUCCESS(Result)) { TargetProviderId = ProviderInfo.ProviderId; ProviderInfoSize = sizeof ProviderInfo; Result = FsRtlMupGetProviderInfoFromFileObject(FileObject, 1, &ProviderInfo, &ProviderInfoSize); if (!NT_SUCCESS(Result)) goto target_check_exit; if (ProviderInfo.ProviderId == TargetProviderId) TargetOnFileSystem = (UINT16)TargetFileNameIndex; } else { TargetObjectName.Length = TargetObjectName.MaximumLength = FsvolDeviceExtension->VolumePrefix.Length; TargetObjectName.Buffer = ReparseTargetPath + TargetFileNameIndex / sizeof(WCHAR); TargetFileNameIndex += FsvolDeviceExtension->VolumePrefix.Length; if (TargetFileNameIndex < ReparseTargetPathLength && RtlEqualUnicodeString(&FsvolDeviceExtension->VolumePrefix, &TargetObjectName, FSP_VOLUME_PREFIX_CASE_INS)) TargetOnFileSystem = (UINT16)TargetFileNameIndex; } } } ObDereferenceObject(TargetFileObject); target_check_exit: ; } } FspFileNodeAcquireExclusive(FileNode, Full); } else { if (0 == OutputBuffer || 0 == OutputBufferLength) return STATUS_INVALID_PARAMETER; /* * NtFsControlFile (IopXxxControlFile) will setup Irp->AssociatedIrp.SystemBuffer * with enough space for either InputBufferLength or OutputBufferLength. There is * no need to call FspBufferUserBuffer ourselves. */ FspFileNodeAcquireShared(FileNode, Full); } Result = FspIopCreateRequestEx(Irp, &FileNode->FileName, IsWrite ? InputBufferLength : 0, FspFsvolFileSystemControlRequestFini, &Request); if (!NT_SUCCESS(Result)) { FspFileNodeRelease(FileNode, Full); return Result; } Request->Kind = FspFsctlTransactFileSystemControlKind; Request->Req.FileSystemControl.UserContext = FileNode->UserContext; Request->Req.FileSystemControl.UserContext2 = FileDesc->UserContext2; Request->Req.FileSystemControl.FsControlCode = FsControlCode; if (IsWrite) { Request->Req.FileSystemControl.Buffer.Offset = Request->FileName.Size; Request->Req.FileSystemControl.Buffer.Size = (UINT16)InputBufferLength; RtlCopyMemory(Request->Buffer + Request->Req.FileSystemControl.Buffer.Offset, InputBuffer, InputBufferLength); Request->Req.FileSystemControl.TargetOnFileSystem = TargetOnFileSystem; } FspFileNodeSetOwner(FileNode, Full, Request); FspIopRequestContext(Request, RequestFileNode) = FileNode; return FSP_STATUS_IOQ_POST; } static NTSTATUS FspFsvolFileSystemControlReparsePointComplete( PIRP Irp, const FSP_FSCTL_TRANSACT_RSP *Response, BOOLEAN IsWrite) { PAGED_CODE(); if (IsWrite) return STATUS_SUCCESS; NTSTATUS Result; PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PVOID OutputBuffer = Irp->AssociatedIrp.SystemBuffer; ULONG OutputBufferLength = IrpSp->Parameters.FileSystemControl.OutputBufferLength; if (Response->Buffer + Response->Rsp.FileSystemControl.Buffer.Offset + Response->Rsp.FileSystemControl.Buffer.Size > (PUINT8)Response + Response->Size) return STATUS_IO_REPARSE_DATA_INVALID; Result = FsRtlValidateReparsePointBuffer(Response->Rsp.FileSystemControl.Buffer.Size, (PVOID)(Response->Buffer + Response->Rsp.FileSystemControl.Buffer.Offset)); if (!NT_SUCCESS(Result)) return Result; if (Response->Rsp.FileSystemControl.Buffer.Size > OutputBufferLength) return STATUS_BUFFER_TOO_SMALL; RtlCopyMemory(OutputBuffer, Response->Buffer + Response->Rsp.FileSystemControl.Buffer.Offset, Response->Rsp.FileSystemControl.Buffer.Size); Irp->IoStatus.Information = Response->Rsp.FileSystemControl.Buffer.Size; return STATUS_SUCCESS; } static NTSTATUS FspFsvolFileSystemControl( PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp) { PAGED_CODE(); NTSTATUS Result = STATUS_INVALID_DEVICE_REQUEST; switch (IrpSp->MinorFunction) { case IRP_MN_USER_FS_REQUEST: switch (IrpSp->Parameters.FileSystemControl.FsControlCode) { case FSP_FSCTL_WORK: case FSP_FSCTL_WORK_BEST_EFFORT: Result = FspVolumeWork(FsvolDeviceObject, Irp, IrpSp); break; case FSCTL_GET_REPARSE_POINT: Result = FspFsvolFileSystemControlReparsePoint(FsvolDeviceObject, Irp, IrpSp, FALSE); break; case FSCTL_SET_REPARSE_POINT: case FSCTL_DELETE_REPARSE_POINT: Result = FspFsvolFileSystemControlReparsePoint(FsvolDeviceObject, Irp, IrpSp, TRUE); break; } break; } return Result; } NTSTATUS FspFsvolFileSystemControlComplete( PIRP Irp, const FSP_FSCTL_TRANSACT_RSP *Response) { FSP_ENTER_IOC(PAGED_CODE()); /* exit now if we do not have a FileObject (FSP_FSCTL_WORK*) */ if (0 == IrpSp->FileObject) FSP_RETURN(); if (!NT_SUCCESS(Response->IoStatus.Status)) { Irp->IoStatus.Information = 0; Result = Response->IoStatus.Status; FSP_RETURN(); } PFILE_OBJECT FileObject = IrpSp->FileObject; FSP_FILE_NODE *FileNode = FileObject->FsContext; FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp); Result = STATUS_INVALID_DEVICE_REQUEST; switch (IrpSp->MinorFunction) { case IRP_MN_USER_FS_REQUEST: switch (IrpSp->Parameters.FileSystemControl.FsControlCode) { case FSCTL_GET_REPARSE_POINT: Result = FspFsvolFileSystemControlReparsePointComplete(Irp, Response, FALSE); break; case FSCTL_SET_REPARSE_POINT: case FSCTL_DELETE_REPARSE_POINT: Result = FspFsvolFileSystemControlReparsePointComplete(Irp, Response, TRUE); break; } break; } ASSERT(STATUS_INVALID_DEVICE_REQUEST != Result); FspIopRequestContext(Request, RequestFileNode) = 0; FspFileNodeReleaseOwner(FileNode, Full, Request); FSP_LEAVE_IOC( "%s%sFileObject=%p", IRP_MN_USER_FS_REQUEST == IrpSp->MinorFunction ? IoctlCodeSym(IrpSp->Parameters.FileSystemControl.FsControlCode) : "", IRP_MN_USER_FS_REQUEST == IrpSp->MinorFunction ? ", " : "", IrpSp->FileObject); } static VOID FspFsvolFileSystemControlRequestFini(FSP_FSCTL_TRANSACT_REQ *Request, PVOID Context[4]) { PAGED_CODE(); FSP_FILE_NODE *FileNode = Context[RequestFileNode]; if (0 != FileNode) FspFileNodeReleaseOwner(FileNode, Full, Request); } NTSTATUS FspFileSystemControl( PDEVICE_OBJECT DeviceObject, PIRP Irp) { FSP_ENTER_MJ(PAGED_CODE()); switch (FspDeviceExtension(DeviceObject)->Kind) { case FspFsvolDeviceExtensionKind: FSP_RETURN(Result = FspFsvolFileSystemControl(DeviceObject, Irp, IrpSp)); case FspFsctlDeviceExtensionKind: FSP_RETURN(Result = FspFsctlFileSystemControl(DeviceObject, Irp, IrpSp)); default: FSP_RETURN(Result = STATUS_INVALID_DEVICE_REQUEST); } FSP_LEAVE_MJ( "%s%sFileObject=%p", IRP_MN_USER_FS_REQUEST == IrpSp->MinorFunction ? IoctlCodeSym(IrpSp->Parameters.FileSystemControl.FsControlCode) : "", IRP_MN_USER_FS_REQUEST == IrpSp->MinorFunction ? ", " : "", IrpSp->FileObject); }