/*++

Copyright (c) Microsoft Corporation. All rights reserved.

Module Name:

    IoDevObj.c

Abstract:

    This module contains functions for managing device objects.

Author:

    Adrian J. Oney  - April 21, 2002

Revision History:

--*/

#include "WlDef.h"
#include "IopDevObj.h"
#pragma hdrstop

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, IoDevObjCreateDeviceSecure)
#pragma alloc_text(PAGE, IopDevObjAdjustNewDeviceParameters)
#pragma alloc_text(PAGE, IopDevObjApplyPostCreationSettings)
#endif


NTSTATUS
IoDevObjCreateDeviceSecure(
    IN  PDRIVER_OBJECT      DriverObject,
    IN  ULONG               DeviceExtensionSize,
    IN  PUNICODE_STRING     DeviceName              OPTIONAL,
    IN  DEVICE_TYPE         DeviceType,
    IN  ULONG               DeviceCharacteristics,
    IN  BOOLEAN             Exclusive,
    IN  PCUNICODE_STRING    DefaultSDDLString,
    IN  LPCGUID             DeviceClassGuid         OPTIONAL,
    OUT PDEVICE_OBJECT     *DeviceObject
    )
/*++

Routine Description:

    This routine creates a securable named device object. The security settings
    for the device object are retrieved from the registry or constructed using
    the passed in defaults if registry overrides are not available.

    It should be used
      1. To secure legacy device objects
      2. To secure raw PnP PDOs

    It should not be used to create FDOs, non-raw PDOs, or any unnamed objects.
    For those operations, IoCreateDevice should be used.

Arguments:

    DriverObject - A pointer to the driver object for this device.

    DeviceExtensionSize - Size, in bytes, of extension to device object;
        i.e., the size of the driver-specific data for this device object.

    DeviceName - Optional name that should be associated with this device.
        If the DeviceCharacteristics has the FILE_AUTOGENERATED_DEVICE_NAME
        flag set, this parameter is ignored.

    DeviceType - The type of device that the device object should represent.
        Possibly overriden by registry.

    DeviceCharacteristics - The characteristics for the device. Additional
        flags may be supplied by the registry.

    Exclusive - Indicates that the device object should be created with using
        the exclusive object attribute. Possibly overriden by registry.

        NOTE: This flag should not be used for WDM drivers.  Since only the
        PDO is named, it is the only device object in a devnode attachment
        stack that is openable.  However, since this device object is created
        by the underlying bus driver (which has no knowledge about what type
        of device this is), there is no way to know whether this flag should
        be set.  Therefore, this parameter should always be FALSE for WDM
        drivers.  Drivers attached to the PDO (e.g., the function driver) must
        enforce any exclusivity rules.

    DefaultSDDLString - In the absense of registry settings, this string
        specifies the security to supply for the device object.

        Only the subset of the SDDL format is currently supported. The format
        is:
          D:P(ACE)(ACE)(ACE), where (ACE) is (AceType;;Access;;;SID)

        Where:

          AceType - Only Allow ("A") is supported.
          Access - Rights specified in either hex format (0xnnnnnnnn), or via the
                   SDDL Generic/Standard abbreviations
          SID - Abbreviated security ID
                (WD, BA, SY, IU, RC, AU, NU, AN, BG, BU, LS, NS)
                The S-w-x-y-z form for SIDs is not supported

          The unimplemented ace fields are:

            AceFlags - Describes features such as inheritance for sub-objects
                (ie files) and containers (ie keys/folders). An example SDDL
                AceFlag string would be ("OICI"). While control over
                inheritance is crucial for registry keys and files, it's
                irrelevant for device objects. As such, this function supports
                no ACE flags.

            ObjectGuid - Used for describing rights that transcent the 32bit
                mask supplied by the OS. Typically used for Active Directory
                objects.

            InheritObjectGuid - - Used for describing rights that transcent the
                32bit mask supplied by the OS. Typically used for Active
                Directory objects.

        Example -
          "D:P(A;;GA;;;SY)" which is Allow System to have Generic All access.

    DeviceClassGuid - Supplies a device install class GUID. This class is
        looked up in the registry to see if any potential overrides exist.
        For legacy device objects, the caller may need to invent an appropriate
        class GUID (see IoCreateDeviceSecure documention on how to properly
        install a full class).

        Note that if no registry override exists, the registry will
        automatically be updated to *reflect* the default SDDL string.
        Therefore it is a very bad idea to use the same device class GUID with
        different DefaultSDDLString values (objects needing different default
        security should have different classes, or be secured via INFs where
        possible).

    DeviceObject - Pointer to the device object pointer this routine will
        return.

Return Value:

    NTSTATUS.

--*/
{
    PSECURITY_DESCRIPTOR securityDescriptor;
    STACK_CREATION_SETTINGS stackSettings, updateSettings;
    PDEVICE_OBJECT newDeviceObject;
    UNICODE_STRING classKeyName;
    DEVICE_TYPE finalDeviceType;
    ULONG finalCharacteristics;
    BOOLEAN finalExclusivity;
    ULONG disposition;
    NTSTATUS status;

    PAGED_CODE();

    //
    // Preinit for failure
    //
    *DeviceObject = NULL;
    newDeviceObject = NULL;

    //
    // The device object is securable only if it has a name. Therefore, we fail
    // the create call if the device doesn't have a name.
    //
    if (!(ARGUMENT_PRESENT(DeviceName) ||
        (DeviceCharacteristics & FILE_AUTOGENERATED_DEVICE_NAME))) {

        return STATUS_INVALID_PARAMETER;
    }

    if (ARGUMENT_PRESENT(DeviceClassGuid)) {

        //
        // Try to find the appropriate security descriptor for the device. First
        // look for an override in the registry using the class GUID. We will
        // create a section in the registry if one doesn't exist as well. This is
        // a clue to the system administrator that there is something to lock down
        // in the system.
        //
        status = PpRegStateReadCreateClassCreationSettings(
            DeviceClassGuid,
            DriverObject,
            &stackSettings
            );

        if (!NT_SUCCESS(status)) {

            return status;
        }

    } else {

        PpRegStateInitEmptyCreationSettings(&stackSettings);
    }

    //
    // If a registry setting wasn't specified, parse the default SDDL string.
    //
    if (!(stackSettings.Flags & DSIFLAG_SECURITY_DESCRIPTOR)) {

        //
        // Parse the SDDL string into a security descriptor, and mark it
        // "default" as well. SE_DACL_DEFAULT means the DACL came from a
        // "default" mechanism, typically implying parental inheritance or
        // object default security. In our case, the "default source" is the
        // library as opposed to the user or an INF.
        //
        status = SeSddlSecurityDescriptorFromSDDL(
            DefaultSDDLString,
            TRUE,
            &securityDescriptor
            );

        if (!NT_SUCCESS(status)) {

            goto Exit;
        }

        PpRegStateLoadSecurityDescriptor(
            securityDescriptor,
            &stackSettings
            );

        if (ARGUMENT_PRESENT(DeviceClassGuid)) {

            //
            // Update the registry with the default SDDL string so that the
            // admin knows what settings are being used for this class. Note
            // that we don't free updateSettings, as the security descriptor
            // is also used by stackSettings.
            //
            PpRegStateInitEmptyCreationSettings(&updateSettings);

            PpRegStateLoadSecurityDescriptor(
                securityDescriptor,
                &updateSettings
                );

            status = PpRegStateUpdateStackCreationSettings(
                DeviceClassGuid,
                &updateSettings
                );

            if (!NT_SUCCESS(status)) {

                goto Exit;
            }
        }
    }

    //
    // Fill out the default values
    //
    finalDeviceType = DeviceType;
    finalCharacteristics = DeviceCharacteristics;
    finalExclusivity = Exclusive;

    //
    // Adjust the parameters based on the registry overrides
    //
    IopDevObjAdjustNewDeviceParameters(
        &stackSettings,
        &finalDeviceType,
        &finalCharacteristics,
        &finalExclusivity
        );

    //
    // Create the device object. The newly created object should have the
    // DO_DEVICE_INITIALIZING flag on it, meaning it cannot be opened. We
    // therefore still have an opportunity to apply security.
    //
    status = IoCreateDevice(
        DriverObject,
        DeviceExtensionSize,
        DeviceName,
        finalDeviceType,
        finalCharacteristics,
        finalExclusivity,
        &newDeviceObject
        );

    if (!NT_SUCCESS(status)) {

        goto Exit;
    }

    ASSERT(newDeviceObject->Flags & DO_DEVICE_INITIALIZING);

    status = IopDevObjApplyPostCreationSettings(
        newDeviceObject,
        &stackSettings
        );

    if (!NT_SUCCESS(status)) {

        IoDeleteDevice(newDeviceObject);
        goto Exit;
    }

    *DeviceObject = newDeviceObject;

Exit:

    //
    // Clean up the security descriptor as appropriate.
    //
    PpRegStateFreeStackCreationSettings(&stackSettings);

    return status;
}


VOID
IopDevObjAdjustNewDeviceParameters(
    IN      PSTACK_CREATION_SETTINGS    StackCreationSettings,
    IN OUT  PDEVICE_TYPE                DeviceType,
    IN OUT  PULONG                      DeviceCharacteristics,
    IN OUT  PBOOLEAN                    Exclusive
    )
/*++

Routine Description:

    This routine adjusts a newly created device object to reflect the passed
    in stack creation settings.

Arguments:

    StackCreationSettings - Information reflecting the settings to apply.

    DeviceType - On input, the device type specified by the caller. This field
        is updated to reflect any changes specified in the registry.

    DeviceCharacteristics - On input, the characteristics specified by the
        caller. This field is updated to reflect any changes specified in the
        registry.

    Exclusive - On input, the exclusivity specified by the caller. This field
        is updated to reflect any changes specified in the registry.

Return Value:

    None.

--*/
{
    PAGED_CODE();

    if (StackCreationSettings->Flags & DSIFLAG_DEVICE_TYPE) {

        *DeviceType = StackCreationSettings->DeviceType;
    }

    if (StackCreationSettings->Flags & DSIFLAG_CHARACTERISTICS) {

        *DeviceCharacteristics = StackCreationSettings->Characteristics;
    }

    if (StackCreationSettings->Flags & DSIFLAG_EXCLUSIVE) {

        *Exclusive = (BOOLEAN) StackCreationSettings->Exclusivity;
    }
}


NTSTATUS
IopDevObjApplyPostCreationSettings(
    IN  PDEVICE_OBJECT              DeviceObject,
    IN  PSTACK_CREATION_SETTINGS    StackCreationSettings
    )
/*++

Routine Description:

    This routine adjusts a newly created device object to reflect the passed
    in stack creation settings.

Arguments:

    DeviceObject - Device object who's settings to adjust.

    StackCreationSettings - Information reflecting the settings to apply.

Return Value:

    NTSTATUS.

--*/
{
    SECURITY_INFORMATION securityInformation;
    ACCESS_MASK desiredAccess;
    BOOLEAN fromDefaultSource;
    NTSTATUS status;
    HANDLE handle;

    PAGED_CODE();

    if (!(StackCreationSettings->Flags & DSIFLAG_SECURITY_DESCRIPTOR)) {

        return STATUS_SUCCESS;
    }

    //
    // Get the corresponding securityInformation from the descriptor.
    //
    status = SeUtilSecurityInfoFromSecurityDescriptor(
        StackCreationSettings->SecurityDescriptor,
        &fromDefaultSource,
        &securityInformation
        );

    if (!NT_SUCCESS(status)) {

        return status;
    }

#ifdef _KERNELIMPLEMENTATION_

    status = ObSetSecurityObjectByPointer(
        DeviceObject,
        securityInformation,
        StackCreationSettings->SecurityDescriptor
        );

#else

    //
    // Since ObSetSecurityObjectByPointer isn't available on Win2K, we have to
    // use a rather sneaky trick. The device technically isn't openable yet.
    // However, ObOpenObjectByPointer doesn't bother doing any parse stuff.
    // Therefore, we can get a quick handle to the object, set the security
    // descriptor, and then dump the handle without the driver being any wiser.
    //
    SeSetSecurityAccessMask(securityInformation, &desiredAccess);

    status = ObOpenObjectByPointer(
        DeviceObject,
        OBJ_KERNEL_HANDLE,
        NULL,
        desiredAccess,
        *IoDeviceObjectType,
        KernelMode,
        &handle
        );

    if (!NT_SUCCESS(status)) {

        return status;
    }

    status = ZwSetSecurityObject(
        handle,
        securityInformation,
        StackCreationSettings->SecurityDescriptor
        );

    ZwClose(handle);

#endif // _KERNELIMPLEMENTATION_

    return status;
}