2.2 Windows driver development: kernel spin lock structure

When it comes to spin locks, we must talk about linked lists. In the previous article "Linked Lists and Structures in the Kernel", we simply used the linked list structure to store the process information list. I believe readers should have understood it. The basic use of kernel linked lists. This article will explain the simple application of spin locks. Spin locks are used to solve thread synchronization problems when reading and writing kernel linked lists. Locks must be used to solve multi-thread synchronization problems. Spin locks are usually used. A spin lock is a high IRQL lock provided in the kernel, which accesses a resource synchronously and exclusively.

Before understanding spin locks, we need to briefly introduce how to allocate memory in the kernel. Generally speaking, there are two functions to allocate memory. ExAllocatePool can allocate memory space without any labels, and ExAllocatePoolWithTag can allocate tagged memory. There is no difference in use between the two. Correspondingly, ExFreePool is used to release non-tagged memory, while ExFreePoolWithTag is used to release the corresponding memory through the passed in tag.

There are three commonly used paging attributes here. NonPagedPool is used to allocate non-paged memory, PagedPool is paged memory, and NonPagedPoolExecute has execution permissions. of non-paged memory.

  • NonPagedPool: Used to allocate non-paged memory, which will not be swapped to disk and can be directly accessed by the kernel. Suitable for memory that requires fast access, such as driver code, interrupt handlers, system calls, etc.

  • PagedPool: used to allocate paged memory, which may be swapped to disk and needs to be accessed through the paging mechanism. It is suitable for memories that occupy large space and have low access frequency, such as caches, data structures, etc.

  • NonPagedPoolExecute: It is non-paged memory with execution permissions, suitable for situations where code needs to be executed, such as some special drivers.

It should be noted that there are certain security risks in using NonPagedPoolExecute to allocate memory, because malware may use this memory to carry out attacks. Therefore, it is recommended to use this pagination property only when necessary.

#include <ntifs.h>

typedef struct _MyStruct
{<!-- -->
    ULONGx;
    ULONG y;
}MyStruct, *pMyStruct;

VOID UnDriver(PDRIVER_OBJECT driver)
{<!-- -->
    DbgPrint("The driver has been uninstalled \
");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{<!-- -->
    DbgPrint("hello lyshark \
");

    // Used to allocate and release non-tagged memory
    PVOID buffer = ExAllocatePool(NonPagedPool, 1024);

    DbgPrint("[*] allocated memory address = %p \
", buffer);

    ExFreePool(buffer);

    // Used to allocate memory with tags
    pMyStruct ptr_buffer = (pMyStruct)ExAllocatePoolWithTag(NonPagedPoolExecute, sizeof(pMyStruct),"lyshark");

    ptr_buffer->x = 100;
    ptr_buffer->y = 200;

    DbgPrint("[*] Allocate memory x = %d y = %d \
", ptr_buffer->x, ptr_buffer->y);

    ExFreePoolWithTag(ptr_buffer, "lyshark");

    UNICODE_STRING dst = {<!-- --> 0 };
    UNICODE_STRING src = RTL_CONSTANT_STRING(L"hello lyshark");

    dst.Buffer = (PWCHAR)ExAllocatePool(NonPagedPool, src.Length);
    if (dst.Buffer == NULL)
    {<!-- -->
        DbgPrint("[-] Error in allocating space \
");
    }

    dst.Length = dst.MaximumLength = src.Length;

    RtlCopyUnicodeString( & amp;dst, & amp;src);

    DbgPrint("[*] Output copy = %wZ \
", dst);

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

The code output effect is shown in the figure below;

Then let’s get to the point, taking a simple linked list as an example. Linked lists are mainly divided into one-way linked lists and two-way linked lists. There is only one linked list pointer in the linked list node of the one-way linked list, which points to the next linked list element, while there are two linked list nodes in the doubly linked list. Linked list node pointer, where Blink points to the previous linked list node and Flink points to the next node, taking a doubly linked list as an example.

#include <ntifs.h>
#include <ntstrsafe.h>

/*
// Linked list node pointer
typedef struct _LIST_ENTRY
{
  struct _LIST_ENTRY *Flink; // The node after the current node
  struct _LIST_ENTRY *Blink; // The previous node of the current node
}LIST_ENTRY, *PLIST_ENTRY;
*/

typedef struct _MyStruct
{<!-- -->
  ULONGx;
  ULONG y;
  LIST_ENTRY lpListEntry;
}MyStruct,*pMyStruct;

VOID UnDriver(PDRIVER_OBJECT driver)
{<!-- -->
  DbgPrint("Driver uninstalled successfully \
");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{<!-- -->
    DbgPrint("hello lyshark \
");

  //Initialize the head node
  LIST_ENTRY ListHeader = {<!-- --> 0 };
  InitializeListHead( & amp;ListHeader);

  //Define linked list elements
  MyStruct testA = {<!-- --> 0 };
  MyStruct testB = {<!-- --> 0 };
  MyStruct testC = {<!-- --> 0 };

  testA.x = 100;
  testA.y = 200;

  testB.x = 1000;
  testB.y = 2000;

  testC.x = 10000;
  testC.y = 20000;

  // Insert nodes into the head and tail respectively
  InsertHeadList( & amp;ListHeader, & amp;testA.lpListEntry);
  InsertTailList( & amp;ListHeader, & amp;testB.lpListEntry);
  InsertTailList( & amp;ListHeader, & amp;testC.lpListEntry);

  // If the node is not empty, remove a node
  if (IsListEmpty( & amp;ListHeader) == FALSE)
  {<!-- -->
    RemoveEntryList( & amp;testA.lpListEntry);
  }

  // Output linked list data
  PLIST_ENTRY pListEntry = NULL;
  pListEntry = ListHeader.Flink;

  while (pListEntry != & amp;ListHeader)
  {<!-- -->
    // Calculate the memory distance between members and the top of the structure
    pMyStruct ptr = CONTAINING_RECORD(pListEntry, MyStruct, lpListEntry);
    DbgPrint("Node element X = %d Node element Y = %d \
", ptr->x, ptr->y);

    // Get the address of the next element
    pListEntry = pListEntry->Flink;
  }

  Driver->DriverUnload = UnDriver;
  return STATUS_SUCCESS;
}

The output effect of the linked list is shown in the figure below;

As mentioned above, in kernel development, thread synchronization problems will occur when multiple threads access the same data structure. In order to solve this problem, a lock mechanism can be used for synchronization. Spin lock is a commonly used lock mechanism. It is a high IRQL lock used for synchronized and exclusive access to a resource.

  • The basic idea of a spin lock is: when a thread tries to acquire a lock, if the lock is already occupied, the thread keeps looping (that is, spinning) until the lock is released. Spin locks are suitable for situations where the lock holding time is short and there are few competitors, so that process context switching and scheduling overhead can be avoided.

The Windows kernel provides multiple types of spin locks, such as KSPIN_LOCK, KIRQL, etc. Among them, KSPIN_LOCK is the most commonly used type of spin lock. You can initialize a spin lock through the KeInitializeSpinLock function, and pass KeAcquireSpinLock and The KeReleaseSpinLock function performs locking and unlocking operations.

It should be noted that when using spin locks, you need to pay attention to problems such as deadlock and priority inversion, so you need to use it with caution in actual applications.

#include <ntifs.h>
#include <ntstrsafe.h>

/*
// Linked list node pointer
typedef struct _LIST_ENTRY
{
struct _LIST_ENTRY *Flink; // The node after the current node
struct _LIST_ENTRY *Blink; // The previous node of the current node
}LIST_ENTRY, *PLIST_ENTRY;
*/

typedef struct _MyStruct
{<!-- -->
    ULONGx;
    ULONG y;
    LIST_ENTRY lpListEntry;
}MyStruct, *pMyStruct;

//Define global linked list and global lock
LIST_ENTRY my_list_header;
KSPIN_LOCK my_list_lock;

// initialization
voidInit()
{<!-- -->
    InitializeListHead( & amp;my_list_header);
    KeInitializeSpinLock( & amp;my_list_lock);
}

//Use lock within function
void function_ins()
{<!-- -->
    KIRQL Irql;

    // lock
    KeAcquireSpinLock( & amp;my_list_lock, & amp;Irql);

    DbgPrint("Lock internal execution\
");

    // release lock
    KeReleaseSpinLock( & amp;my_list_lock, Irql);
}

VOID UnDriver(PDRIVER_OBJECT driver)
{<!-- -->
    DbgPrint("Driver uninstalled successfully \
");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{<!-- -->
    DbgPrint("hello lyshark \
");

    //Initialize the linked list
    Init();

    //Allocate linked list space
    pMyStruct testA = (pMyStruct)ExAllocatePool(NonPagedPoolExecute, sizeof(pMyStruct));
    pMyStruct testB = (pMyStruct)ExAllocatePool(NonPagedPoolExecute, sizeof(pMyStruct));

    //Assign value
    testA->x = 100;
    testA->y = 200;

    testB->x = 1000;
    testB->y = 2000;

    //Insert data into the global linked list
    if (NULL != testA & amp; & amp; NULL != testB)
    {<!-- -->
        ExInterlockedInsertHeadList( & amp;my_list_header, (PLIST_ENTRY) & amp;testA->lpListEntry, & amp;my_list_lock);
        ExInterlockedInsertTailList( & amp;my_list_header, (PLIST_ENTRY) & amp;testB->lpListEntry, & amp;my_list_lock);
    }

    function_ins();

    // Remove node A and put it in remove_entry
    PLIST_ENTRY remove_entry = ExInterlockedRemoveHeadList( & amp;testA->lpListEntry, & amp;my_list_lock);

    // Output linked list data
    while (remove_entry != & amp;my_list_header)
    {<!-- -->
        // Calculate the memory distance between members and the top of the structure
        pMyStruct ptr = CONTAINING_RECORD(remove_entry, MyStruct, lpListEntry);
        DbgPrint("Node element X = %d Node element Y = %d \
", ptr->x, ptr->y);

        // Get the address of the next element
        remove_entry = remove_entry->Flink;
    }

    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

The execution effect after locking is as shown in the figure below;