/*
 * KProcessHacker
 *
 * Copyright (C) 2010-2016 wj32
 *
 * This file is part of Process Hacker.
 *
 * Process Hacker 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 3 of the License, or
 * (at your option) any later version.
 *
 * Process Hacker 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 Process Hacker.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <kph.h>

NTSTATUS KphDispatchDeviceControl(
    __in PDEVICE_OBJECT DeviceObject,
    __in PIRP Irp
    )
{
    NTSTATUS status;
    PIO_STACK_LOCATION stackLocation;
    PFILE_OBJECT fileObject;
    PKPH_CLIENT client;
    PVOID originalInput;
    ULONG inputLength;
    ULONG ioControlCode;
    KPROCESSOR_MODE accessMode;
    UCHAR capturedInput[16 * sizeof(ULONG_PTR)];
    PVOID capturedInputPointer;

#define VERIFY_INPUT_LENGTH \
    do { \
        /* Ensure at compile time that our local buffer fits this particular call. */ \
        C_ASSERT(sizeof(*input) <= sizeof(capturedInput)); \
        \
        if (inputLength != sizeof(*input)) \
        { \
            status = STATUS_INFO_LENGTH_MISMATCH; \
            goto ControlEnd; \
        } \
    } while (0)

    stackLocation = IoGetCurrentIrpStackLocation(Irp);
    fileObject = stackLocation->FileObject;
    client = fileObject->FsContext;

    originalInput = stackLocation->Parameters.DeviceIoControl.Type3InputBuffer;
    inputLength = stackLocation->Parameters.DeviceIoControl.InputBufferLength;
    ioControlCode = stackLocation->Parameters.DeviceIoControl.IoControlCode;
    accessMode = Irp->RequestorMode;

    // Make sure we have a client object.
    if (!client)
    {
        status = STATUS_INTERNAL_ERROR;
        goto ControlEnd;
    }

    // Enforce signature requirement if necessary.
    if ((ioControlCode != KPH_GETFEATURES && ioControlCode != KPH_VERIFYCLIENT) &&
        (KphParameters.SecurityLevel == KphSecuritySignatureCheck ||
            KphParameters.SecurityLevel == KphSecuritySignatureAndPrivilegeCheck) &&
        !client->VerificationSucceeded)
    {
        status = STATUS_ACCESS_DENIED;
        goto ControlEnd;
    }

    // Make sure we actually have input if the input length is non-zero.
    if (inputLength != 0 && !originalInput)
    {
        status = STATUS_INVALID_BUFFER_SIZE;
        goto ControlEnd;
    }

    // Make sure the caller isn't giving us a huge buffer. If they are, it can't be correct because
    // we have a compile-time check that makes sure our buffer can store the arguments for all the
    // calls.
    if (inputLength > sizeof(capturedInput))
    {
        status = STATUS_INVALID_BUFFER_SIZE;
        goto ControlEnd;
    }

    // Probe and capture the input buffer.
    if (accessMode != KernelMode)
    {
        __try
        {
            ProbeForRead(originalInput, inputLength, sizeof(UCHAR));
            memcpy(capturedInput, originalInput, inputLength);
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            status = GetExceptionCode();
            goto ControlEnd;
        }
    }
    else
    {
        memcpy(capturedInput, originalInput, inputLength);
    }

    capturedInputPointer = capturedInput; // avoid casting below

    switch (ioControlCode)
    {
    case KPH_GETFEATURES:
        {
            struct
            {
                PULONG Features;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiGetFeatures(
                input->Features,
                accessMode
                );
        }
        break;
    case KPH_VERIFYCLIENT:
        {
            struct
            {
                PVOID CodeAddress;
                PVOID Signature;
                ULONG SignatureSize;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;
            
            if (accessMode == UserMode)
            {
                status = KpiVerifyClient(
                    input->CodeAddress,
                    input->Signature,
                    input->SignatureSize,
                    client
                    );
            }
            else
            {
                status = STATUS_UNSUCCESSFUL;
            }
        }
        break;
    case KPH_RETRIEVEKEY:
        {
            struct
            {
                KPH_KEY_LEVEL KeyLevel;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            if (accessMode == UserMode)
            {
                status = KphRetrieveKeyViaApc(
                    client,
                    input->KeyLevel,
                    Irp
                    );
            }
            else
            {
                status = STATUS_UNSUCCESSFUL;
            }
        }
        break;
    case KPH_OPENPROCESS:
        {
            struct
            {
                PHANDLE ProcessHandle;
                ACCESS_MASK DesiredAccess;
                PCLIENT_ID ClientId;
                KPH_KEY Key;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiOpenProcess(
                input->ProcessHandle,
                input->DesiredAccess,
                input->ClientId,
                input->Key,
                client,
                accessMode
                );
        }
        break;
    case KPH_OPENPROCESSTOKEN:
        {
            struct
            {
                HANDLE ProcessHandle;
                ACCESS_MASK DesiredAccess;
                PHANDLE TokenHandle;
                KPH_KEY Key;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiOpenProcessToken(
                input->ProcessHandle,
                input->DesiredAccess,
                input->TokenHandle,
                input->Key,
                client,
                accessMode
                );
        }
        break;
    case KPH_OPENPROCESSJOB:
        {
            struct
            {
                HANDLE ProcessHandle;
                ACCESS_MASK DesiredAccess;
                PHANDLE JobHandle;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiOpenProcessJob(
                input->ProcessHandle,
                input->DesiredAccess,
                input->JobHandle,
                accessMode
                );
        }
        break;
    case KPH_TERMINATEPROCESS:
        {
            struct
            {
                HANDLE ProcessHandle;
                NTSTATUS ExitStatus;
                KPH_KEY Key;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiTerminateProcess(
                input->ProcessHandle,
                input->ExitStatus,
                input->Key,
                client,
                accessMode
                );
        }
        break;
    case KPH_READVIRTUALMEMORYUNSAFE:
        {
            struct
            {
                HANDLE ProcessHandle;
                PVOID BaseAddress;
                PVOID Buffer;
                SIZE_T BufferSize;
                PSIZE_T NumberOfBytesRead;
                KPH_KEY Key;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiReadVirtualMemoryUnsafe(
                input->ProcessHandle,
                input->BaseAddress,
                input->Buffer,
                input->BufferSize,
                input->NumberOfBytesRead,
                input->Key,
                client,
                accessMode
                );
        }
        break;
    case KPH_QUERYINFORMATIONPROCESS:
        {
            struct
            {
                HANDLE ProcessHandle;
                KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass;
                PVOID ProcessInformation;
                ULONG ProcessInformationLength;
                PULONG ReturnLength;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiQueryInformationProcess(
                input->ProcessHandle,
                input->ProcessInformationClass,
                input->ProcessInformation,
                input->ProcessInformationLength,
                input->ReturnLength,
                accessMode
                );
        }
        break;
    case KPH_SETINFORMATIONPROCESS:
        {
            struct
            {
                HANDLE ProcessHandle;
                KPH_PROCESS_INFORMATION_CLASS ProcessInformationClass;
                PVOID ProcessInformation;
                ULONG ProcessInformationLength;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiSetInformationProcess(
                input->ProcessHandle,
                input->ProcessInformationClass,
                input->ProcessInformation,
                input->ProcessInformationLength,
                accessMode
                );
        }
        break;
    case KPH_OPENTHREAD:
        {
            struct
            {
                PHANDLE ThreadHandle;
                ACCESS_MASK DesiredAccess;
                PCLIENT_ID ClientId;
                KPH_KEY Key;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiOpenThread(
                input->ThreadHandle,
                input->DesiredAccess,
                input->ClientId,
                input->Key,
                client,
                accessMode
                );
        }
        break;
    case KPH_OPENTHREADPROCESS:
        {
            struct
            {
                HANDLE ThreadHandle;
                ACCESS_MASK DesiredAccess;
                PHANDLE ProcessHandle;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiOpenThreadProcess(
                input->ThreadHandle,
                input->DesiredAccess,
                input->ProcessHandle,
                accessMode
                );
        }
        break;
    case KPH_CAPTURESTACKBACKTRACETHREAD:
        {
            struct
            {
                HANDLE ThreadHandle;
                ULONG FramesToSkip;
                ULONG FramesToCapture;
                PVOID *BackTrace;
                PULONG CapturedFrames;
                PULONG BackTraceHash;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiCaptureStackBackTraceThread(
                input->ThreadHandle,
                input->FramesToSkip,
                input->FramesToCapture,
                input->BackTrace,
                input->CapturedFrames,
                input->BackTraceHash,
                accessMode
                );
        }
        break;
    case KPH_QUERYINFORMATIONTHREAD:
        {
            struct
            {
                HANDLE ThreadHandle;
                KPH_THREAD_INFORMATION_CLASS ThreadInformationClass;
                PVOID ThreadInformation;
                ULONG ThreadInformationLength;
                PULONG ReturnLength;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiQueryInformationThread(
                input->ThreadHandle,
                input->ThreadInformationClass,
                input->ThreadInformation,
                input->ThreadInformationLength,
                input->ReturnLength,
                accessMode
                );
        }
        break;
    case KPH_SETINFORMATIONTHREAD:
        {
            struct
            {
                HANDLE ThreadHandle;
                KPH_THREAD_INFORMATION_CLASS ThreadInformationClass;
                PVOID ThreadInformation;
                ULONG ThreadInformationLength;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiSetInformationThread(
                input->ThreadHandle,
                input->ThreadInformationClass,
                input->ThreadInformation,
                input->ThreadInformationLength,
                accessMode
                );
        }
        break;
    case KPH_ENUMERATEPROCESSHANDLES:
        {
            struct
            {
                HANDLE ProcessHandle;
                PVOID Buffer;
                ULONG BufferLength;
                PULONG ReturnLength;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiEnumerateProcessHandles(
                input->ProcessHandle,
                input->Buffer,
                input->BufferLength,
                input->ReturnLength,
                accessMode
                );
        }
        break;
    case KPH_QUERYINFORMATIONOBJECT:
        {
            struct
            {
                HANDLE ProcessHandle;
                HANDLE Handle;
                KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass;
                PVOID ObjectInformation;
                ULONG ObjectInformationLength;
                PULONG ReturnLength;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiQueryInformationObject(
                input->ProcessHandle,
                input->Handle,
                input->ObjectInformationClass,
                input->ObjectInformation,
                input->ObjectInformationLength,
                input->ReturnLength,
                accessMode
                );
        }
        break;
    case KPH_SETINFORMATIONOBJECT:
        {
            struct
            {
                HANDLE ProcessHandle;
                HANDLE Handle;
                KPH_OBJECT_INFORMATION_CLASS ObjectInformationClass;
                PVOID ObjectInformation;
                ULONG ObjectInformationLength;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiSetInformationObject(
                input->ProcessHandle,
                input->Handle,
                input->ObjectInformationClass,
                input->ObjectInformation,
                input->ObjectInformationLength,
                accessMode
                );
        }
        break;
    case KPH_OPENDRIVER:
        {
            struct
            {
                PHANDLE DriverHandle;
                ACCESS_MASK DesiredAccess;
                POBJECT_ATTRIBUTES ObjectAttributes;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiOpenDriver(
                input->DriverHandle,
                input->DesiredAccess,
                input->ObjectAttributes,
                accessMode
                );
        }
        break;
    case KPH_QUERYINFORMATIONDRIVER:
        {
            struct
            {
                HANDLE DriverHandle;
                DRIVER_INFORMATION_CLASS DriverInformationClass;
                PVOID DriverInformation;
                ULONG DriverInformationLength;
                PULONG ReturnLength;
            } *input = capturedInputPointer;

            VERIFY_INPUT_LENGTH;

            status = KpiQueryInformationDriver(
                input->DriverHandle,
                input->DriverInformationClass,
                input->DriverInformation,
                input->DriverInformationLength,
                input->ReturnLength,
                accessMode
                );
        }
        break;
    default:
        status = STATUS_INVALID_DEVICE_REQUEST;
        break;
    }

ControlEnd:
    Irp->IoStatus.Status = status;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return status;
}
