/*
 * hid/shared_mem.c
 *
 * Named section lifecycle. See shared_mem.h for the interface.
 *
 * Design notes:
 *
 *   - Object name: \BaseNamedObjects\Global\VhidkmShared. The
 *     \Global\ prefix places it in the global namespace so any
 *     session can open it; individual-session isolation is not a
 *     goal of this driver (the whole point of shared memory here is
 *     to be reachable from any admin-privileged process).
 *
 *   - DACL: D:P(A;;GA;;;SY)(A;;GA;;;BA). SYSTEM + BUILTIN\Administrators.
 *     The "P" flag makes the ACL protected (no inheritance). Built by
 *     hand from static SIDs because wdmsec's SDDL parser is not
 *     available in every target WDK revision.
 *
 *   - Handle lifetime: the section handle is kept open for the entire
 *     life of the device. A plain kernel handle pins the object and
 *     keeps its name resolvable by user-mode OpenFileMappingW. On
 *     destroy we close the handle; the object is released once no
 *     user mappings remain.
 *
 *   - System-space mapping: MmMapViewInSystemSpace gives us a pointer
 *     that is valid in any process context and stays paged in by
 *     virtue of being in the system PTEs. This is required because
 *     the DISPATCH_LEVEL timer callback has no guarantee about which
 *     process' VA space is current when it fires.
 *
 *   - The section is SEC_COMMIT so every byte is backed by pagefile
 *     commitment at create time. A SEC_RESERVE section would leave
 *     the pages in a trap-on-first-touch state that isn't safe at
 *     DISPATCH_LEVEL.
 */

#include "driver.h"
#include "shared_mem.h"
#include "trace.h"
#include "shared_mem.tmh"

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, VhidkmSharedSectionCreate)
#pragma alloc_text(PAGE, VhidkmSharedSectionDestroy)
#endif

DECLARE_CONST_UNICODE_STRING(g_SharedSectionName, VHID_SHARED_KERNEL_NAME_W);

/*
 * Construct the DACL granting SECTION_ALL_ACCESS to SYSTEM and
 * BUILTIN\Administrators, nothing to anyone else. The SIDs are
 * well-known and built statically into fixed-size UCHAR buffers so
 * we do not allocate pool for them. The DACL itself *is* allocated
 * (its size depends on the two SIDs concatenated into an ACL
 * structure) and released by the caller.
 */
static
_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
BuildSharedDacl(
    _Out_ PACL* OutDacl
    )
{
    SID_IDENTIFIER_AUTHORITY ntAuthority = SECURITY_NT_AUTHORITY;
    UCHAR       systemSidBuf[SECURITY_SID_SIZE(1)];
    UCHAR       adminSidBuf[SECURITY_SID_SIZE(2)];
    PSID        systemSid = (PSID)systemSidBuf;
    PSID        adminSid  = (PSID)adminSidBuf;
    PACL        dacl      = NULL;
    ULONG       daclSize;
    NTSTATUS    status;

    PAGED_CODE();

    *OutDacl = NULL;

    status = RtlInitializeSid(systemSid, &ntAuthority, 1);
    if (!NT_SUCCESS(status)) {
        return status;
    }
    *RtlSubAuthoritySid(systemSid, 0) = SECURITY_LOCAL_SYSTEM_RID;

    status = RtlInitializeSid(adminSid, &ntAuthority, 2);
    if (!NT_SUCCESS(status)) {
        return status;
    }
    *RtlSubAuthoritySid(adminSid, 0) = SECURITY_BUILTIN_DOMAIN_RID;
    *RtlSubAuthoritySid(adminSid, 1) = DOMAIN_ALIAS_RID_ADMINS;

    /*
     * Size formula: ACL header + 2 * (ACE header + SID bytes).
     * ACCESS_ALLOWED_ACE already embeds a one-ULONG SidStart, so
     * subtract sizeof(ULONG) before adding RtlLengthSid.
     */
    daclSize = sizeof(ACL)
             + 2 * (FIELD_OFFSET(ACCESS_ALLOWED_ACE, SidStart))
             + RtlLengthSid(systemSid)
             + RtlLengthSid(adminSid);

    dacl = (PACL)ExAllocatePool2(POOL_FLAG_PAGED, daclSize, VHID_POOL_TAG_HID);
    if (dacl == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    status = RtlCreateAcl(dacl, daclSize, ACL_REVISION);
    if (!NT_SUCCESS(status)) {
        ExFreePoolWithTag(dacl, VHID_POOL_TAG_HID);
        return status;
    }

    status = RtlAddAccessAllowedAce(dacl, ACL_REVISION,
                                    SECTION_ALL_ACCESS, systemSid);
    if (!NT_SUCCESS(status)) {
        ExFreePoolWithTag(dacl, VHID_POOL_TAG_HID);
        return status;
    }

    status = RtlAddAccessAllowedAce(dacl, ACL_REVISION,
                                    SECTION_ALL_ACCESS, adminSid);
    if (!NT_SUCCESS(status)) {
        ExFreePoolWithTag(dacl, VHID_POOL_TAG_HID);
        return status;
    }

    *OutDacl = dacl;
    return STATUS_SUCCESS;
}

_Use_decl_annotations_
NTSTATUS
VhidkmSharedSectionCreate(
    PVHIDKM_SHARED_SECTION Shared
    )
{
    SECURITY_DESCRIPTOR sd;
    OBJECT_ATTRIBUTES   oa;
    LARGE_INTEGER       maxSize;
    PACL                dacl   = NULL;
    HANDLE              handle = NULL;
    PVOID               object = NULL;
    PVOID               view   = NULL;
    SIZE_T              viewSize = 0;
    NTSTATUS            status;

    PAGED_CODE();

    RtlZeroMemory(Shared, sizeof(*Shared));

    status = BuildSharedDacl(&dacl);
    if (!NT_SUCCESS(status)) {
        TraceError("BuildSharedDacl failed %!STATUS!", status);
        return status;
    }

    status = RtlCreateSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
    if (!NT_SUCCESS(status)) {
        goto fail;
    }

    status = RtlSetDaclSecurityDescriptor(&sd, TRUE, dacl, FALSE);
    if (!NT_SUCCESS(status)) {
        goto fail;
    }

    InitializeObjectAttributes(&oa,
        (PUNICODE_STRING)&g_SharedSectionName,
        OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE,
        NULL,
        &sd);

    maxSize.QuadPart = VHID_SHARED_SECTION_BYTES;

    status = ZwCreateSection(&handle,
                             SECTION_ALL_ACCESS,
                             &oa,
                             &maxSize,
                             PAGE_READWRITE,
                             SEC_COMMIT,
                             NULL);
    if (!NT_SUCCESS(status)) {
        TraceError("ZwCreateSection failed %!STATUS!", status);
        goto fail;
    }

    /*
     * Resolve the handle to a referenced object pointer. Pass NULL
     * for the object type so we do not depend on the (undocumented)
     * MmSectionObjectType export; Mm* will reject non-section objects
     * when we hand it the pointer.
     */
    status = ObReferenceObjectByHandle(handle,
                                       SECTION_MAP_READ | SECTION_MAP_WRITE,
                                       NULL,
                                       KernelMode,
                                       &object,
                                       NULL);
    if (!NT_SUCCESS(status)) {
        TraceError("ObReferenceObjectByHandle(section) failed %!STATUS!", status);
        goto fail;
    }

    viewSize = VHID_SHARED_SECTION_BYTES;
    status = MmMapViewInSystemSpace(object, &view, &viewSize);
    if (!NT_SUCCESS(status)) {
        TraceError("MmMapViewInSystemSpace failed %!STATUS!", status);
        goto fail;
    }

    RtlZeroMemory(view, viewSize);

    {
        PVHID_SHARED_STATE s = (PVHID_SHARED_STATE)view;
        s->HeaderBytes = sizeof(VHID_SHARED_STATE);
        s->HeartbeatMs = VHID_SHARED_HEARTBEAT_MS;
        s->Version     = VHID_API_LEVEL;
        /*
         * Magic is stamped *last* with a release fence so a user-mode
         * opener that races driver start observes either zero or the
         * fully initialized header, never a half-written header with
         * stale fields. OpenFileMapping in user-mode will see the
         * named object immediately once ZwCreateSection returns, but
         * the commit zero-fill pre-fills everything to 0; users must
         * poll Magic to know the header is valid.
         */
        MemoryBarrier();
        s->Magic = VHID_SHARED_MAGIC;
    }

    Shared->SectionHandle = handle;
    Shared->SectionObject = object;
    Shared->View          = (PVHID_SHARED_STATE)view;
    Shared->ViewSize      = viewSize;

    ExFreePoolWithTag(dacl, VHID_POOL_TAG_HID);
    TracePnp("shared section created, view=%p size=%Iu", view, viewSize);
    return STATUS_SUCCESS;

fail:
    if (view != NULL) {
        (VOID)MmUnmapViewInSystemSpace(view);
    }
    if (object != NULL) {
        ObDereferenceObject(object);
    }
    if (handle != NULL) {
        ZwClose(handle);
    }
    if (dacl != NULL) {
        ExFreePoolWithTag(dacl, VHID_POOL_TAG_HID);
    }
    RtlZeroMemory(Shared, sizeof(*Shared));
    return status;
}

_Use_decl_annotations_
VOID
VhidkmSharedSectionDestroy(
    PVHIDKM_SHARED_SECTION Shared
    )
{
    PAGED_CODE();

    if (Shared->View != NULL) {
        (VOID)MmUnmapViewInSystemSpace(Shared->View);
        Shared->View = NULL;
    }
    if (Shared->SectionObject != NULL) {
        ObDereferenceObject(Shared->SectionObject);
        Shared->SectionObject = NULL;
    }
    if (Shared->SectionHandle != NULL) {
        (VOID)ZwClose(Shared->SectionHandle);
        Shared->SectionHandle = NULL;
    }
    Shared->ViewSize = 0;
    TracePnp("shared section destroyed");
}

_Use_decl_annotations_
VOID
VhidkmSharedSectionPublishLeds(
    PVHIDKM_SHARED_SECTION Shared,
    UCHAR LedBits
    )
{
    PVHID_SHARED_STATE s;

    if (Shared == NULL || Shared->View == NULL) {
        return;
    }
    s = Shared->View;
    s->LedBits = LedBits;
    /*
     * Release fence: the LED-bit write must be visible before the
     * seq bump so a user-mode reader that observes the new seq also
     * observes the new bits.
     */
    MemoryBarrier();
    (VOID)InterlockedIncrement(&s->LedSeq);
}
