diff --git a/ntoskrnl/CMakeLists.txt b/ntoskrnl/CMakeLists.txt
index 2efd6f12833..75d5ee4f64a 100644
--- a/ntoskrnl/CMakeLists.txt
+++ b/ntoskrnl/CMakeLists.txt
@@ -1,6 +1,15 @@
 
 PROJECT(NTOS)
 
+option(NTOS_THREAD_SANITIZE "Compile the kernel with GCC's thread sanitizer" OFF)
+if (NTOS_THREAD_SANITIZE AND NOT (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang"))
+    message(FATAL_ERROR "You can't specify NTOS_THREAD_SANITIZE without using GCC or CLang")
+endif()
+
+if (NTOS_THREAD_SANITIZE)
+    add_compile_options(-fsanitize=thread)
+endif()
+
 include(ntos.cmake)
 
 set(NTOSKRNL_SOURCE ${SOURCE})
@@ -43,6 +52,10 @@ if(GDB AND NOT CLANG)
     target_compile_options(ntoskrnl PRIVATE -O0)
 endif()
 
+if (NTOS_THREAD_SANITIZE)
+    target_link_libraries(ntoskrnl sanitize_tsan)
+endif()
+
 target_link_libraries(ntoskrnl cportlib csq ${PSEH_LIB} arbiter cmlib ntlsalib rtl ${ROSSYM_LIB} libcntpr wdmguid ioevent)
 
 if(STACK_PROTECTOR)
diff --git a/ntoskrnl/ex/rundown.c b/ntoskrnl/ex/rundown.c
index be9e6fc6f2b..fe7810c1d9e 100644
--- a/ntoskrnl/ex/rundown.c
+++ b/ntoskrnl/ex/rundown.c
@@ -379,6 +379,7 @@ ExfWaitForRundownProtectionRelease(IN PEX_RUNDOWN_REF RunRef)
 /*
  * @implemented NT5.2
  */
+ATTRIBUTE_NO_SANITIZE("thread")
 BOOLEAN
 FASTCALL
 ExfAcquireRundownProtectionCacheAware(IN PEX_RUNDOWN_REF_CACHE_AWARE RunRefCacheAware)
diff --git a/ntoskrnl/include/internal/ex.h b/ntoskrnl/include/internal/ex.h
index 2b8b0026794..1fb673494e8 100644
--- a/ntoskrnl/include/internal/ex.h
+++ b/ntoskrnl/include/internal/ex.h
@@ -1525,6 +1525,9 @@ XIPInit(
 #define InterlockedIncrementUL(Addend) \
    (ULONG)InterlockedIncrement((PLONG)(Addend))
 
+#define InterlockedIncrementNoFenceUL(Addend)           \
+   (ULONG)InterlockedIncrementNoFence((PLONG)Addend)
+
 #define InterlockedExchangeUL(Target, Value) \
    (ULONG)InterlockedExchange((PLONG)(Target), (LONG)(Value))
 
diff --git a/ntoskrnl/include/internal/io_x.h b/ntoskrnl/include/internal/io_x.h
index 7cf42e9dc02..3a429b836a9 100644
--- a/ntoskrnl/include/internal/io_x.h
+++ b/ntoskrnl/include/internal/io_x.h
@@ -84,19 +84,19 @@ IopUpdateOperationCount(IN IOP_TRANSFER_TYPE Type)
         if (Type == IopReadTransfer)
         {
             /* Increase read count */
-            IoReadOperationCount++;
+            InterlockedIncrementNoFenceUL(&IoReadOperationCount);
             CountToChange = &PsGetCurrentProcess()->ReadOperationCount;
         }
         else if (Type == IopWriteTransfer)
         {
             /* Increase write count */
-            IoWriteOperationCount++;
+            InterlockedIncrementNoFenceUL(&IoWriteOperationCount);
             CountToChange = &PsGetCurrentProcess()->WriteOperationCount;
         }
         else
         {
             /* Increase other count */
-            IoOtherOperationCount++;
+            InterlockedIncrementNoFenceUL(&IoOtherOperationCount);
             CountToChange = &PsGetCurrentProcess()->OtherOperationCount;
         }
 
diff --git a/ntoskrnl/include/internal/ob_x.h b/ntoskrnl/include/internal/ob_x.h
index 587b82ebc61..647f7e6d24e 100644
--- a/ntoskrnl/include/internal/ob_x.h
+++ b/ntoskrnl/include/internal/ob_x.h
@@ -318,21 +318,21 @@ ObpAllocateObjectCreateInfoBuffer(IN PP_NPAGED_LOOKASIDE_NUMBER Type)
     List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[Type].P;
 
     /* Attempt allocation */
-    List->L.TotalAllocates++;
+    InterlockedIncrementNoFenceUL(&List->L.TotalAllocates);
     Buffer = (PVOID)InterlockedPopEntrySList(&List->L.ListHead);
     if (!Buffer)
     {
         /* Let the balancer know that the P list failed */
-        List->L.AllocateMisses++;
+        InterlockedIncrementNoFenceUL(&List->L.AllocateMisses);
 
         /* Try the L List */
         List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[Type].L;
-        List->L.TotalAllocates++;
+        InterlockedIncrementNoFenceUL(&List->L.TotalAllocates);
         Buffer = (PVOID)InterlockedPopEntrySList(&List->L.ListHead);
         if (!Buffer)
         {
             /* Let the balancer know the L list failed too */
-            List->L.AllocateMisses++;
+            InterlockedIncrementNoFenceUL(&List->L.AllocateMisses);
 
             /* Allocate it */
             Buffer = List->L.Allocate(List->L.Type, List->L.Size, List->L.Tag);
@@ -353,23 +353,23 @@ ObpFreeCapturedAttributes(IN PVOID Buffer,
 
     /* Use the P List */
     List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[Type].P;
-    List->L.TotalFrees++;
+    InterlockedIncrementNoFenceUL(&List->L.TotalFrees);
 
     /* Check if the Free was within the Depth or not */
     if (ExQueryDepthSList(&List->L.ListHead) >= List->L.Depth)
     {
         /* Let the balancer know */
-        List->L.FreeMisses++;
+        InterlockedIncrementNoFenceUL(&List->L.FreeMisses);
 
         /* Use the L List */
         List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[Type].L;
-        List->L.TotalFrees++;
+        InterlockedIncrementNoFenceUL(&List->L.TotalFrees);
 
         /* Check if the Free was within the Depth or not */
         if (ExQueryDepthSList(&List->L.ListHead) >= List->L.Depth)
         {
             /* All lists failed, use the pool */
-            List->L.FreeMisses++;
+            InterlockedIncrementNoFenceUL(&List->L.FreeMisses);
             List->L.Free(Buffer);
         }
         else
diff --git a/ntoskrnl/io/iomgr/iocomp.c b/ntoskrnl/io/iomgr/iocomp.c
index 41457a66574..54d607563ef 100644
--- a/ntoskrnl/io/iomgr/iocomp.c
+++ b/ntoskrnl/io/iomgr/iocomp.c
@@ -161,21 +161,21 @@ IoSetIoCompletion(IN PVOID IoCompletion,
             PPLookasideList[LookasideCompletionList].P;
 
     /* Try to allocate the Packet */
-    List->L.TotalAllocates++;
+    InterlockedIncrementNoFenceUL(&List->L.TotalAllocates);
     Packet = (PVOID)InterlockedPopEntrySList(&List->L.ListHead);
 
     /* Check if that failed, use the L list if it did */
     if (!Packet)
     {
         /* Let the balancer know */
-        List->L.AllocateMisses++;
+        InterlockedIncrementNoFenceUL(&List->L.AllocateMisses);
 
         /* Get L List */
         List = (PNPAGED_LOOKASIDE_LIST)Prcb->
                 PPLookasideList[LookasideCompletionList].L;
 
         /* Try to allocate the Packet */
-        List->L.TotalAllocates++;
+        InterlockedIncrementNoFenceUL(&List->L.TotalAllocates);
         Packet = (PVOID)InterlockedPopEntrySList(&List->L.ListHead);
     }
 
@@ -183,7 +183,7 @@ IoSetIoCompletion(IN PVOID IoCompletion,
     if (!Packet)
     {
         /* Let the balancer know */
-        List->L.AllocateMisses++;
+        InterlockedIncrementNoFenceUL(&List->L.AllocateMisses);
 
         /* Allocate from Nonpaged Pool */
         Packet = ExAllocatePoolWithTag(NonPagedPool, sizeof(*Packet), IOC_TAG);
diff --git a/ntoskrnl/io/iomgr/irp.c b/ntoskrnl/io/iomgr/irp.c
index 2de843975e0..fa28b6e4504 100644
--- a/ntoskrnl/io/iomgr/irp.c
+++ b/ntoskrnl/io/iomgr/irp.c
@@ -645,18 +645,18 @@ IoAllocateIrp(IN CCHAR StackSize,
         List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].P;
 
         /* Attempt allocation */
-        List->L.TotalAllocates++;
+        InterlockedIncrementNoFenceUL(&List->L.TotalAllocates);
         Irp = (PIRP)InterlockedPopEntrySList(&List->L.ListHead);
 
         /* Check if the P List failed */
         if (!Irp)
         {
             /* Let the balancer know */
-            List->L.AllocateMisses++;
+            InterlockedIncrementNoFenceUL(&List->L.AllocateMisses);
 
             /* Try the L List */
             List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].L;
-            List->L.TotalAllocates++;
+            InterlockedIncrementNoFenceUL(&List->L.TotalAllocates);
             Irp = (PIRP)InterlockedPopEntrySList(&List->L.ListHead);
         }
     }
@@ -665,7 +665,8 @@ IoAllocateIrp(IN CCHAR StackSize,
     if (!Irp)
     {
         /* Did we try lookaside and fail? */
-        if (Flags & IRP_ALLOCATED_FIXED_SIZE) List->L.AllocateMisses++;
+        if (Flags & IRP_ALLOCATED_FIXED_SIZE)
+            InterlockedIncrementNoFenceUL(&List->L.AllocateMisses);
 
         /* Check if we should charge quota */
         if (ChargeQuota)
@@ -1701,23 +1702,23 @@ IoFreeIrp(IN PIRP Irp)
 
         /* Use the P List */
         List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].P;
-        List->L.TotalFrees++;
+        InterlockedIncrementUL(&List->L.TotalFrees);
 
         /* Check if the Free was within the Depth or not */
         if (ExQueryDepthSList(&List->L.ListHead) >= List->L.Depth)
         {
             /* Let the balancer know */
-            List->L.FreeMisses++;
+            InterlockedIncrementUL(&List->L.FreeMisses);
 
             /* Use the L List */
             List = (PNPAGED_LOOKASIDE_LIST)Prcb->PPLookasideList[ListType].L;
-            List->L.TotalFrees++;
+            InterlockedIncrementUL(&List->L.TotalFrees);
 
             /* Check if the Free was within the Depth or not */
             if (ExQueryDepthSList(&List->L.ListHead) >= List->L.Depth)
             {
                 /* All lists failed, use the pool */
-                List->L.FreeMisses++;
+                InterlockedIncrementUL(&List->L.FreeMisses);
                 ExFreePoolWithTag(Irp, TAG_IRP);
                 Irp = NULL;
             }
diff --git a/ntoskrnl/mm/ARM3/expool.c b/ntoskrnl/mm/ARM3/expool.c
index 74393137162..0fbeaf2f461 100644
--- a/ntoskrnl/mm/ARM3/expool.c
+++ b/ntoskrnl/mm/ARM3/expool.c
@@ -2088,7 +2088,7 @@ ExAllocatePoolWithTag(IN POOL_TYPE PoolType,
         LookasideList = (PoolType == PagedPool) ?
                          Prcb->PPPagedLookasideList[i - 1].P :
                          Prcb->PPNPagedLookasideList[i - 1].P;
-        LookasideList->TotalAllocates++;
+        InterlockedIncrementNoFenceUL(&LookasideList->TotalAllocates);
         Entry = (PPOOL_HEADER)InterlockedPopEntrySList(&LookasideList->ListHead);
         if (!Entry)
         {
@@ -2098,7 +2098,7 @@ ExAllocatePoolWithTag(IN POOL_TYPE PoolType,
             LookasideList = (PoolType == PagedPool) ?
                              Prcb->PPPagedLookasideList[i - 1].L :
                              Prcb->PPNPagedLookasideList[i - 1].L;
-            LookasideList->TotalAllocates++;
+            InterlockedIncrementNoFenceUL(&LookasideList->TotalAllocates);
             Entry = (PPOOL_HEADER)InterlockedPopEntrySList(&LookasideList->ListHead);
         }
 
@@ -2107,7 +2107,7 @@ ExAllocatePoolWithTag(IN POOL_TYPE PoolType,
         //
         if (Entry)
         {
-            LookasideList->AllocateHits++;
+            InterlockedIncrementNoFenceUL(&LookasideList->AllocateHits);
 
             //
             // Get the real entry, write down its pool type, and track it
@@ -2133,188 +2133,171 @@ ExAllocatePoolWithTag(IN POOL_TYPE PoolType,
     // list optimized for this kind of size lookup
     //
     ListHead = &PoolDesc->ListHeads[i];
-    do
+
+    OldIrql = ExLockPool(PoolDesc);
+    while (ListHead != &PoolDesc->ListHeads[POOL_LISTS_PER_PAGE])
     {
+        if (ExpIsPoolListEmpty(ListHead))
+        {
+            ListHead++;
+            continue;
+        }
+
         //
-        // Are there any free entries available on this list?
+        // Remove a free entry from the list
+        // Note that due to the way we insert free blocks into multiple lists
+        // there is a guarantee that any block on this list will either be
+        // of the correct size, or perhaps larger.
         //
-        if (!ExpIsPoolListEmpty(ListHead))
-        {
-            //
-            // Acquire the pool lock now
-            //
-            OldIrql = ExLockPool(PoolDesc);
+        ExpCheckPoolLinks(ListHead);
+        Entry = POOL_ENTRY(ExpRemovePoolHeadList(ListHead));
+        ExpCheckPoolLinks(ListHead);
+        ExpCheckPoolBlocks(Entry);
+        ASSERT(Entry->BlockSize >= i);
+        ASSERT(Entry->PoolType == 0);
 
+        //
+        // Check if this block is larger that what we need. The block could
+        // not possibly be smaller, due to the reason explained above (and
+        // we would've asserted on a checked build if this was the case).
+        //
+        if (Entry->BlockSize != i)
+        {
             //
-            // And make sure the list still has entries
+            // Is there an entry before this one?
             //
-            if (ExpIsPoolListEmpty(ListHead))
+            if (Entry->PreviousSize == 0)
             {
                 //
-                // Someone raced us (and won) before we had a chance to acquire
-                // the lock.
-                //
-                // Try again!
+                // There isn't anyone before us, so take the next block and
+                // turn it into a fragment that contains the leftover data
+                // that we don't need to satisfy the caller's request
                 //
-                ExUnlockPool(PoolDesc, OldIrql);
-                continue;
-            }
+                FragmentEntry = POOL_BLOCK(Entry, i);
+                FragmentEntry->BlockSize = Entry->BlockSize - i;
 
-            //
-            // Remove a free entry from the list
-            // Note that due to the way we insert free blocks into multiple lists
-            // there is a guarantee that any block on this list will either be
-            // of the correct size, or perhaps larger.
-            //
-            ExpCheckPoolLinks(ListHead);
-            Entry = POOL_ENTRY(ExpRemovePoolHeadList(ListHead));
-            ExpCheckPoolLinks(ListHead);
-            ExpCheckPoolBlocks(Entry);
-            ASSERT(Entry->BlockSize >= i);
-            ASSERT(Entry->PoolType == 0);
+                //
+                // And make it point back to us
+                //
+                FragmentEntry->PreviousSize = i;
 
-            //
-            // Check if this block is larger that what we need. The block could
-            // not possibly be smaller, due to the reason explained above (and
-            // we would've asserted on a checked build if this was the case).
-            //
-            if (Entry->BlockSize != i)
-            {
                 //
-                // Is there an entry before this one?
+                // Now get the block that follows the new fragment and check
+                // if it's still on the same page as us (and not at the end)
                 //
-                if (Entry->PreviousSize == 0)
+                NextEntry = POOL_NEXT_BLOCK(FragmentEntry);
+                if (PAGE_ALIGN(NextEntry) != NextEntry)
                 {
                     //
-                    // There isn't anyone before us, so take the next block and
-                    // turn it into a fragment that contains the leftover data
-                    // that we don't need to satisfy the caller's request
-                    //
-                    FragmentEntry = POOL_BLOCK(Entry, i);
-                    FragmentEntry->BlockSize = Entry->BlockSize - i;
-
-                    //
-                    // And make it point back to us
-                    //
-                    FragmentEntry->PreviousSize = i;
-
+                    // Adjust this next block to point to our newly created
+                    // fragment block
                     //
-                    // Now get the block that follows the new fragment and check
-                    // if it's still on the same page as us (and not at the end)
-                    //
-                    NextEntry = POOL_NEXT_BLOCK(FragmentEntry);
-                    if (PAGE_ALIGN(NextEntry) != NextEntry)
-                    {
-                        //
-                        // Adjust this next block to point to our newly created
-                        // fragment block
-                        //
-                        NextEntry->PreviousSize = FragmentEntry->BlockSize;
-                    }
-                }
-                else
-                {
-                    //
-                    // There is a free entry before us, which we know is smaller
-                    // so we'll make this entry the fragment instead
-                    //
-                    FragmentEntry = Entry;
-
-                    //
-                    // And then we'll remove from it the actual size required.
-                    // Now the entry is a leftover free fragment
-                    //
-                    Entry->BlockSize -= i;
-
-                    //
-                    // Now let's go to the next entry after the fragment (which
-                    // used to point to our original free entry) and make it
-                    // reference the new fragment entry instead.
-                    //
-                    // This is the entry that will actually end up holding the
-                    // allocation!
-                    //
-                    Entry = POOL_NEXT_BLOCK(Entry);
-                    Entry->PreviousSize = FragmentEntry->BlockSize;
-
-                    //
-                    // And now let's go to the entry after that one and check if
-                    // it's still on the same page, and not at the end
-                    //
-                    NextEntry = POOL_BLOCK(Entry, i);
-                    if (PAGE_ALIGN(NextEntry) != NextEntry)
-                    {
-                        //
-                        // Make it reference the allocation entry
-                        //
-                        NextEntry->PreviousSize = i;
-                    }
+                    NextEntry->PreviousSize = FragmentEntry->BlockSize;
                 }
+            }
+            else
+            {
+                //
+                // There is a free entry before us, which we know is smaller
+                // so we'll make this entry the fragment instead
+                //
+                FragmentEntry = Entry;
 
                 //
-                // Now our (allocation) entry is the right size
+                // And then we'll remove from it the actual size required.
+                // Now the entry is a leftover free fragment
                 //
-                Entry->BlockSize = i;
+                Entry->BlockSize -= i;
 
                 //
-                // And the next entry is now the free fragment which contains
-                // the remaining difference between how big the original entry
-                // was, and the actual size the caller needs/requested.
+                // Now let's go to the next entry after the fragment (which
+                // used to point to our original free entry) and make it
+                // reference the new fragment entry instead.
                 //
-                FragmentEntry->PoolType = 0;
-                BlockSize = FragmentEntry->BlockSize;
+                // This is the entry that will actually end up holding the
+                // allocation!
+                //
+                Entry = POOL_NEXT_BLOCK(Entry);
+                Entry->PreviousSize = FragmentEntry->BlockSize;
 
                 //
-                // Now check if enough free bytes remained for us to have a
-                // "full" entry, which contains enough bytes for a linked list
-                // and thus can be used for allocations (up to 8 bytes...)
+                // And now let's go to the entry after that one and check if
+                // it's still on the same page, and not at the end
                 //
-                ExpCheckPoolLinks(&PoolDesc->ListHeads[BlockSize - 1]);
-                if (BlockSize != 1)
+                NextEntry = POOL_BLOCK(Entry, i);
+                if (PAGE_ALIGN(NextEntry) != NextEntry)
                 {
                     //
-                    // Insert the free entry into the free list for this size
+                    // Make it reference the allocation entry
                     //
-                    ExpInsertPoolTailList(&PoolDesc->ListHeads[BlockSize - 1],
-                                          POOL_FREE_BLOCK(FragmentEntry));
-                    ExpCheckPoolLinks(POOL_FREE_BLOCK(FragmentEntry));
+                    NextEntry->PreviousSize = i;
                 }
             }
 
             //
-            // We have found an entry for this allocation, so set the pool type
-            // and release the lock since we're done
-            //
-            Entry->PoolType = OriginalType + 1;
-            ExpCheckPoolBlocks(Entry);
-            ExUnlockPool(PoolDesc, OldIrql);
-
-            //
-            // Increment required counters
+            // Now our (allocation) entry is the right size
             //
-            InterlockedExchangeAddSizeT(&PoolDesc->TotalBytes, Entry->BlockSize * POOL_BLOCK_SIZE);
-            InterlockedIncrement((PLONG)&PoolDesc->RunningAllocs);
+            Entry->BlockSize = i;
 
             //
-            // Track this allocation
+            // And the next entry is now the free fragment which contains
+            // the remaining difference between how big the original entry
+            // was, and the actual size the caller needs/requested.
             //
-            ExpInsertPoolTracker(Tag,
-                                 Entry->BlockSize * POOL_BLOCK_SIZE,
-                                 OriginalType);
+            FragmentEntry->PoolType = 0;
+            BlockSize = FragmentEntry->BlockSize;
 
             //
-            // Return the pool allocation
+            // Now check if enough free bytes remained for us to have a
+            // "full" entry, which contains enough bytes for a linked list
+            // and thus can be used for allocations (up to 8 bytes...)
             //
-            Entry->PoolTag = Tag;
-            (POOL_FREE_BLOCK(Entry))->Flink = NULL;
-            (POOL_FREE_BLOCK(Entry))->Blink = NULL;
-            return POOL_FREE_BLOCK(Entry);
+            ExpCheckPoolLinks(&PoolDesc->ListHeads[BlockSize - 1]);
+            if (BlockSize != 1)
+            {
+                //
+                // Insert the free entry into the free list for this size
+                //
+                ExpInsertPoolTailList(&PoolDesc->ListHeads[BlockSize - 1],
+                                        POOL_FREE_BLOCK(FragmentEntry));
+                ExpCheckPoolLinks(POOL_FREE_BLOCK(FragmentEntry));
+            }
         }
-    } while (++ListHead != &PoolDesc->ListHeads[POOL_LISTS_PER_PAGE]);
+
+        //
+        // We have found an entry for this allocation, so set the pool type
+        // and release the lock since we're done
+        //
+        Entry->PoolType = OriginalType + 1;
+        ExpCheckPoolBlocks(Entry);
+        ExUnlockPool(PoolDesc, OldIrql);
+
+        //
+        // Increment required counters
+        //
+        InterlockedExchangeAddSizeT(&PoolDesc->TotalBytes, Entry->BlockSize * POOL_BLOCK_SIZE);
+        InterlockedIncrement((PLONG)&PoolDesc->RunningAllocs);
+
+        //
+        // Track this allocation
+        //
+        ExpInsertPoolTracker(Tag,
+                                Entry->BlockSize * POOL_BLOCK_SIZE,
+                                OriginalType);
+
+        //
+        // Return the pool allocation
+        //
+        Entry->PoolTag = Tag;
+        (POOL_FREE_BLOCK(Entry))->Flink = NULL;
+        (POOL_FREE_BLOCK(Entry))->Blink = NULL;
+        return POOL_FREE_BLOCK(Entry);
+    }
 
     //
     // There were no free entries left, so we have to allocate a new fresh page
     //
+    ExUnlockPool(PoolDesc, OldIrql);
     Entry = MiAllocatePoolPages(OriginalType, PAGE_SIZE);
     if (!Entry)
     {
@@ -2733,10 +2716,10 @@ ExFreePoolWithTag(IN PVOID P,
         LookasideList = (PoolType == PagedPool) ?
                          Prcb->PPPagedLookasideList[BlockSize - 1].P :
                          Prcb->PPNPagedLookasideList[BlockSize - 1].P;
-        LookasideList->TotalFrees++;
+        InterlockedIncrementNoFenceUL(&LookasideList->TotalFrees);
         if (ExQueryDepthSList(&LookasideList->ListHead) < LookasideList->Depth)
         {
-            LookasideList->FreeHits++;
+            InterlockedIncrementNoFenceUL(&LookasideList->FreeHits);
             InterlockedPushEntrySList(&LookasideList->ListHead, P);
             return;
         }
@@ -2747,10 +2730,10 @@ ExFreePoolWithTag(IN PVOID P,
         LookasideList = (PoolType == PagedPool) ?
                          Prcb->PPPagedLookasideList[BlockSize - 1].L :
                          Prcb->PPNPagedLookasideList[BlockSize - 1].L;
-        LookasideList->TotalFrees++;
+        InterlockedIncrementNoFenceUL(&LookasideList->TotalFrees);
         if (ExQueryDepthSList(&LookasideList->ListHead) < LookasideList->Depth)
         {
-            LookasideList->FreeHits++;
+            InterlockedIncrementNoFenceUL(&LookasideList->FreeHits);
             InterlockedPushEntrySList(&LookasideList->ListHead, P);
             return;
         }
diff --git a/ntoskrnl/mm/ARM3/miarm.h b/ntoskrnl/mm/ARM3/miarm.h
index 721c66c54bb..7fc1f51cef6 100644
--- a/ntoskrnl/mm/ARM3/miarm.h
+++ b/ntoskrnl/mm/ARM3/miarm.h
@@ -340,13 +340,19 @@ typedef struct _POOL_HEADER
         struct
         {
 #ifdef _WIN64
-            USHORT PreviousSize:8;
-            USHORT PoolIndex:8;
-            USHORT BlockSize:8;
-            USHORT PoolType:8;
+            BYTE PreviousSize;
+            BYTE PoolIndex;
+            BYTE BlockSize;
+            BYTE PoolType;
 #else
             USHORT PreviousSize:9;
             USHORT PoolIndex:7;
+            /*
+             * Force starting a new USHORT.
+             * See https://en.cppreference.com/w/cpp/language/bit_field
+             * Otherwise GCC takes the whole thing as a 32 bits value.
+             */
+            USHORT :0;
             USHORT BlockSize:9;
             USHORT PoolType:7;
 #endif
diff --git a/ntoskrnl/mm/i386/page.c b/ntoskrnl/mm/i386/page.c
index 7210d769df8..17384c35999 100644
--- a/ntoskrnl/mm/i386/page.c
+++ b/ntoskrnl/mm/i386/page.c
@@ -814,14 +814,24 @@ MmSetDirtyBit(PEPROCESS Process, PVOID Address, BOOLEAN Bit)
     DPRINT("MmSetDirtyBit(Process %p  Address %p  Bit %x)\n",
            Process, Address, Bit);
 
-    ASSERT(Process != NULL);
-    ASSERT(Address < MmSystemRangeStart);
-
-    ASSERT(Process == PsGetCurrentProcess());
+    if (Address >= MmSystemRangeStart)
+    {
+        ASSERT(Process == NULL);
+#if _MI_PAGING_LEVELS == 2
+        NT_VERIFY(MiSynchronizeSystemPde(MiAddressToPde(Address)));
+#else
+        ASSERT(MiIsPdeForAddressValid(Address));
+#endif
+    }
+    else
+    {
+        ASSERT(Process == PsGetCurrentProcess());
+        ASSERT(MiIsPageTablePresent(Address));
 
-    MiLockProcessWorkingSetUnsafe(Process, PsGetCurrentThread());
+        MiLockProcessWorkingSetUnsafe(Process, PsGetCurrentThread());
 
-    MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL);
+        MiMakePdeExistAndMakeValid(MiAddressToPde(Address), Process, MM_NOIRQL);
+    }
 
     PointerPte = MiAddressToPte(Address);
     // We shouldnl't set dirty bit on non-mapped adresses
@@ -836,7 +846,8 @@ MmSetDirtyBit(PEPROCESS Process, PVOID Address, BOOLEAN Bit)
     if (!Bit)
         KeInvalidateTlbEntry(Address);
 
-    MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread());
+    if (Address < MmSystemRangeStart)
+        MiUnlockProcessWorkingSetUnsafe(Process, PsGetCurrentThread());
 }
 
 CODE_SEG("INIT")
diff --git a/ntoskrnl/mm/section.c b/ntoskrnl/mm/section.c
index c6b618cdbf8..5a04c4aaeb5 100644
--- a/ntoskrnl/mm/section.c
+++ b/ntoskrnl/mm/section.c
@@ -1605,8 +1605,8 @@ MmNotPresentFaultSectionView(PMMSUPPORT AddressSpace,
          */
 
         /*
-        * Sanity check
-        */
+         * Sanity check
+         */
         MmDeletePageFileMapping(Process, Address, &DummyEntry);
         ASSERT(DummyEntry == SwapEntry);
 
@@ -1918,6 +1918,10 @@ MmAccessFaultSectionView(PMMSUPPORT AddressSpace,
     if (MmGetPageProtect(Process, Address) & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE))
     {
         DPRINT("Address 0x%p\n", Address);
+
+        /* Set the dirty bit */
+        MmSetDirtyPage(Process, Address);
+
         return STATUS_SUCCESS;
     }
 
diff --git a/ntoskrnl/ntkrnlmp/CMakeLists.txt b/ntoskrnl/ntkrnlmp/CMakeLists.txt
index a56ec6ce8e3..fb8a1ccb1e5 100644
--- a/ntoskrnl/ntkrnlmp/CMakeLists.txt
+++ b/ntoskrnl/ntkrnlmp/CMakeLists.txt
@@ -27,7 +27,12 @@ if(STACK_PROTECTOR)
     target_sources(ntkrnlmp PRIVATE $<TARGET_OBJECTS:gcc_ssp_nt>)
 endif()
 
+if (NTOS_THREAD_SANITIZE)
+    target_link_libraries(ntkrnlmp sanitize_tsan)
+endif()
+
 target_link_libraries(ntkrnlmp cportlib csq ${PSEH_LIB} arbiter cmlib ntlsalib rtl ${ROSSYM_LIB} libcntpr wdmguid ioevent)
+
 add_importlibs(ntkrnlmp hal kdcom bootvid)
 add_pch(ntkrnlmp ${REACTOS_SOURCE_DIR}/ntoskrnl/include/ntoskrnl.h "${NTKRNLMP_PCH_SKIP_SOURCE}")
 add_dependencies(ntkrnlmp psdk asm)
diff --git a/sdk/include/crt/crtdefs.h b/sdk/include/crt/crtdefs.h
index 8ea3f85bdf5..64abd213bf5 100644
--- a/sdk/include/crt/crtdefs.h
+++ b/sdk/include/crt/crtdefs.h
@@ -465,4 +465,11 @@ typedef struct localeinfo_struct {
 # endif
 #endif
 
+/* -fsanitize control */
+#if defined(__GNUC__) || defined(__clang__)
+# define ATTRIBUTE_NO_SANITIZE(__x) __attribute__((__no_sanitize__(__x)))
+#else
+# define ATTRIBUTE_NO_SANITIZE(__x)
+#endif
+
 #endif /* !_INC_CRTDEFS */
diff --git a/sdk/include/ndk/mmtypes.h b/sdk/include/ndk/mmtypes.h
index f33d5cc8b14..a65e7a68735 100644
--- a/sdk/include/ndk/mmtypes.h
+++ b/sdk/include/ndk/mmtypes.h
@@ -899,19 +899,21 @@ typedef struct _MMWSL
 //
 typedef struct _MMSUPPORT_FLAGS
 {
-    ULONG SessionSpace:1;
-    ULONG BeingTrimmed:1;
-    ULONG SessionLeader:1;
-    ULONG TrimHard:1;
-    ULONG MaximumWorkingSetHard:1;
-    ULONG ForceTrim:1;
-    ULONG MinimumWorkingSetHard:1;
-    ULONG Available0:1;
-    ULONG MemoryPriority:8;
-    ULONG GrowWsleHash:1;
-    ULONG AcquiredUnsafe:1;
-    ULONG Available:14;
+    UCHAR SessionSpace:1;
+    UCHAR BeingTrimmed:1;
+    UCHAR SessionLeader:1;
+    UCHAR TrimHard:1;
+    UCHAR MaximumWorkingSetHard:1;
+    UCHAR ForceTrim:1;
+    UCHAR MinimumWorkingSetHard:1;
+    UCHAR Available0:1;
+    UCHAR MemoryPriority;
+    USHORT GrowWsleHash:1;
+    USHORT AcquiredUnsafe:1;
+    USHORT Available:14;
 } MMSUPPORT_FLAGS, *PMMSUPPORT_FLAGS;
+C_ASSERT(sizeof(MMSUPPORT_FLAGS) == sizeof(ULONG));
+C_ASSERT(FIELD_OFFSET(MMSUPPORT_FLAGS, MemoryPriority) == 1);
 
 //
 // Per-Process Memory Manager Data
diff --git a/sdk/include/xdk/exfuncs.h b/sdk/include/xdk/exfuncs.h
index 2d0c2332f2b..daa7c5dfbe8 100644
--- a/sdk/include/xdk/exfuncs.h
+++ b/sdk/include/xdk/exfuncs.h
@@ -199,11 +199,11 @@ ExAllocateFromPagedLookasideList(
 {
   PVOID Entry;
 
-  Lookaside->L.TotalAllocates++;
+  InterlockedIncrementNoFence((PLONG)&Lookaside->L.TotalAllocates);
 #ifdef NONAMELESSUNION
   Entry = InterlockedPopEntrySList(&Lookaside->L.u.ListHead);
   if (Entry == NULL) {
-    Lookaside->L.u2.AllocateMisses++;
+    InterlockedIncrementNoFence((PLONG)Lookaside->L.u2.AllocateMisses);
     Entry = (Lookaside->L.u4.Allocate)(Lookaside->L.Type,
                                        Lookaside->L.Size,
                                        Lookaside->L.Tag);
@@ -211,7 +211,7 @@ ExAllocateFromPagedLookasideList(
 #else /* NONAMELESSUNION */
   Entry = InterlockedPopEntrySList(&Lookaside->L.ListHead);
   if (Entry == NULL) {
-    Lookaside->L.AllocateMisses++;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.AllocateMisses);
     Entry = (Lookaside->L.Allocate)(Lookaside->L.Type,
                                     Lookaside->L.Size,
                                     Lookaside->L.Tag);
@@ -227,17 +227,17 @@ ExFreeToPagedLookasideList(
   _Inout_ PPAGED_LOOKASIDE_LIST Lookaside,
   _In_ PVOID Entry)
 {
-  Lookaside->L.TotalFrees++;
+  InterlockedIncrementNoFence((PLONG)&Lookaside->L.TotalFrees);
 #ifdef NONAMELESSUNION
   if (ExQueryDepthSList(&Lookaside->L.u.ListHead) >= Lookaside->L.Depth) {
-    Lookaside->L.u3.FreeMisses++;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.u3.FreeMisses);
     (Lookaside->L.u5.Free)(Entry);
   } else {
     InterlockedPushEntrySList(&Lookaside->L.u.ListHead, (PSLIST_ENTRY)Entry);
   }
 #else /* NONAMELESSUNION */
   if (ExQueryDepthSList(&Lookaside->L.ListHead) >= Lookaside->L.Depth) {
-    Lookaside->L.FreeMisses++;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.FreeMisses);
     (Lookaside->L.Free)(Entry);
   } else {
     InterlockedPushEntrySList(&Lookaside->L.ListHead, (PSLIST_ENTRY)Entry);
@@ -1412,11 +1412,11 @@ ExAllocateFromLookasideListEx(
 {
   PVOID Entry;
 
-  Lookaside->L.TotalAllocates += 1;
+  InterlockedIncrementNoFence((PLONG)&Lookaside->L.TotalAllocates);
 #ifdef NONAMELESSUNION
   Entry = InterlockedPopEntrySList(&Lookaside->L.u.ListHead);
   if (Entry == NULL) {
-    Lookaside->L.u2.AllocateMisses += 1;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.u2.AllocateMisses);
     Entry = (Lookaside->L.u4.AllocateEx)(Lookaside->L.Type,
                                          Lookaside->L.Size,
                                          Lookaside->L.Tag,
@@ -1425,7 +1425,7 @@ ExAllocateFromLookasideListEx(
 #else /* NONAMELESSUNION */
   Entry = InterlockedPopEntrySList(&Lookaside->L.ListHead);
   if (Entry == NULL) {
-    Lookaside->L.AllocateMisses += 1;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.AllocateMisses);
     Entry = (Lookaside->L.AllocateEx)(Lookaside->L.Type,
                                       Lookaside->L.Size,
                                       Lookaside->L.Tag,
@@ -1446,9 +1446,9 @@ ExFreeToLookasideListEx(
   _Inout_ PLOOKASIDE_LIST_EX Lookaside,
   _In_ __drv_freesMem(Entry) PVOID Entry)
 {
-  Lookaside->L.TotalFrees += 1;
+  InterlockedIncrementNoFence((PLONG)&Lookaside->L.TotalFrees);
   if (ExQueryDepthSList(&Lookaside->L.ListHead) >= Lookaside->L.Depth) {
-    Lookaside->L.FreeMisses += 1;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.FreeMisses);
     (Lookaside->L.FreeEx)(Entry, Lookaside);
   } else {
     InterlockedPushEntrySList(&Lookaside->L.ListHead, (PSLIST_ENTRY)Entry);
@@ -1484,7 +1484,7 @@ ExAllocateFromNPagedLookasideList(
 {
   PVOID Entry;
 
-  Lookaside->L.TotalAllocates++;
+  InterlockedIncrementNoFence((PLONG)&Lookaside->L.TotalAllocates);
 #ifdef NONAMELESSUNION
 #if defined(_WIN2K_COMPAT_SLIST_USAGE) && defined(_X86_)
   Entry = ExInterlockedPopEntrySList(&Lookaside->L.u.ListHead,
@@ -1493,7 +1493,7 @@ ExAllocateFromNPagedLookasideList(
   Entry = InterlockedPopEntrySList(&Lookaside->L.u.ListHead);
 #endif
   if (Entry == NULL) {
-    Lookaside->L.u2.AllocateMisses++;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.u2.AllocateMisses);
     Entry = (Lookaside->L.u4.Allocate)(Lookaside->L.Type,
                                        Lookaside->L.Size,
                                        Lookaside->L.Tag);
@@ -1506,7 +1506,7 @@ ExAllocateFromNPagedLookasideList(
   Entry = InterlockedPopEntrySList(&Lookaside->L.ListHead);
 #endif
   if (Entry == NULL) {
-    Lookaside->L.AllocateMisses++;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.AllocateMisses);
     Entry = (Lookaside->L.Allocate)(Lookaside->L.Type,
                                     Lookaside->L.Size,
                                     Lookaside->L.Tag);
@@ -1522,10 +1522,10 @@ ExFreeToNPagedLookasideList(
   _Inout_ PNPAGED_LOOKASIDE_LIST Lookaside,
   _In_ __drv_freesMem(Mem) PVOID Entry)
 {
-  Lookaside->L.TotalFrees++;
+  InterlockedIncrementNoFence((PLONG)&Lookaside->L.TotalFrees);
 #ifdef NONAMELESSUNION
   if (ExQueryDepthSList(&Lookaside->L.u.ListHead) >= Lookaside->L.Depth) {
-    Lookaside->L.u3.FreeMisses++;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.u3.FreeMisses);
     (Lookaside->L.u5.Free)(Entry);
   } else {
 #if defined(_WIN2K_COMPAT_SLIST_USAGE) && defined(_X86_)
@@ -1538,7 +1538,7 @@ ExFreeToNPagedLookasideList(
    }
 #else /* NONAMELESSUNION */
   if (ExQueryDepthSList(&Lookaside->L.ListHead) >= Lookaside->L.Depth) {
-    Lookaside->L.FreeMisses++;
+    InterlockedIncrementNoFence((PLONG)&Lookaside->L.FreeMisses);
     (Lookaside->L.Free)(Entry);
   } else {
 #if defined(_WIN2K_COMPAT_SLIST_USAGE) && defined(_X86_)
diff --git a/sdk/include/xdk/rtlfuncs.h b/sdk/include/xdk/rtlfuncs.h
index cba07f72aa5..b6686b1c7be 100644
--- a/sdk/include/xdk/rtlfuncs.h
+++ b/sdk/include/xdk/rtlfuncs.h
@@ -3218,11 +3218,19 @@ RtlCheckBit(
 # define NT_ANALYSIS_ASSUME(_exp) __noop(_exp)
 #endif
 
+#if __SANITIZE_THREAD__
+extern void __tsan_disable(void);
+extern void __tsan_enable(void);
+#else
+#define __tsan_disable() (void)0
+#define __tsan_enable() (void)0
+#endif
+
 #define NT_ASSERT_ACTION(exp) \
    (NT_ANALYSIS_ASSUME(exp), \
-   ((!(exp)) ? \
+   ((__tsan_disable(), !(exp)) ? \
       (__assert_annotationA(#exp), \
-       DbgRaiseAssertionFailure(), FALSE) : TRUE))
+       DbgRaiseAssertionFailure(), __tsan_enable(), FALSE) : (__tsan_enable(), TRUE)))
 
 #define NT_ASSERTMSG_ACTION(msg, exp) \
    (NT_ANALYSIS_ASSUME(exp), \
@@ -3239,8 +3247,8 @@ RtlCheckBit(
 #if DBG
 
 #define RTL_VERIFY(exp) \
-  ((!(exp)) ? \
-    RtlAssert( (PVOID)#exp, (PVOID)__FILE__, __LINE__, NULL ), FALSE : TRUE)
+  ((__tsan_disable(), (!(exp))) ? \
+    (RtlAssert( (PVOID)#exp, (PVOID)__FILE__, __LINE__, NULL ), __tsan_enable(), FALSE) : (__tsan_enable(), TRUE))
 
 #define RTL_VERIFYMSG(msg, exp) \
   ((!(exp)) ? \
diff --git a/sdk/lib/CMakeLists.txt b/sdk/lib/CMakeLists.txt
index 0e75364c440..f5e21fe979f 100644
--- a/sdk/lib/CMakeLists.txt
+++ b/sdk/lib/CMakeLists.txt
@@ -17,6 +17,10 @@ if(MSVC)
     add_subdirectory(RunTmChk)
 endif()
 
+if (CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
+    add_subdirectory(sanitize)
+endif()
+
 add_subdirectory(delayimp)
 add_subdirectory(dmilib)
 #add_subdirectory(dnslib) Nothing links to this lib.
diff --git a/sdk/lib/sanitize/CMakeLists.txt b/sdk/lib/sanitize/CMakeLists.txt
new file mode 100644
index 00000000000..82af16f1107
--- /dev/null
+++ b/sdk/lib/sanitize/CMakeLists.txt
@@ -0,0 +1,6 @@
+
+add_compile_options(-O0)
+
+add_library(sanitize_tsan STATIC tsan.c)
+# For the headers
+target_link_libraries(sanitize_tsan PRIVATE pseh)
diff --git a/sdk/lib/sanitize/tsan.c b/sdk/lib/sanitize/tsan.c
new file mode 100644
index 00000000000..569e8e91c40
--- /dev/null
+++ b/sdk/lib/sanitize/tsan.c
@@ -0,0 +1,589 @@
+
+#define _NTSYSTEM_
+#include <ntddk.h>
+#include <ntifs.h>
+#include <ketypes.h>
+
+#include <stdlib.h>
+
+/* Do not use this. If it is instrumented, you're going to recurse */
+#pragma GCC poison KeGetCurrentThread
+
+/* Taken from ntoskrnl includes */
+FORCEINLINE
+KTHREAD*
+_KeGetCurrentThread(VOID)
+{
+    /* Return the current thread */
+    return ((PKIPCR)KeGetPcr())->PrcbData.CurrentThread;
+}
+
+FORCEINLINE
+BOOLEAN
+KeDisableInterrupts(VOID)
+{
+    ULONG Flags;
+    BOOLEAN Return;
+
+    /* Get EFLAGS and check if the interrupt bit is set */
+    Flags = __readeflags();
+    Return = (Flags & EFLAGS_INTERRUPT_MASK) ? TRUE: FALSE;
+
+    /* Disable interrupts */
+    _disable();
+    return Return;
+}
+
+static
+BOOLEAN
+is_whitelist(void* address, size_t length)
+{
+    /* Some variables just can't be checked. */
+    PKIPCR Pcr = (PKIPCR)KeGetPcr();
+    ULONG_PTR range_base = (ULONG_PTR)Pcr;
+    ULONG_PTR range_end = (ULONG_PTR)(Pcr + 1);
+    if (((ULONG_PTR)address >= (ULONG_PTR)range_base) && (((ULONG_PTR)address + length) <= range_end))
+        return TRUE;
+
+    /* Let the current thread alone */
+    PKTHREAD Thread = _KeGetCurrentThread();
+    range_base = (ULONG_PTR)Thread;
+    range_end = (ULONG_PTR)(Thread + 1);
+    if (((ULONG_PTR)address >= (ULONG_PTR)range_base) && (((ULONG_PTR)address + length) <= range_end))
+        return TRUE;
+    /* As well as its stack base */
+    range_end = (ULONG_PTR)Thread->InitialStack;
+    range_base = (ULONG_PTR)Thread->InitialStack - PAGE_SIZE;
+    if (((ULONG_PTR)address >= (ULONG_PTR)range_base) && (((ULONG_PTR)address + length) <= range_end))
+        return TRUE;
+
+    volatile KSYSTEM_TIME* Time = &SharedUserData->InterruptTime;
+    range_base = (ULONG_PTR)Time;
+    range_end = (ULONG_PTR)(Time + 1);
+    if (((ULONG_PTR)address >= (ULONG_PTR)range_base) && (((ULONG_PTR)address + length) <= range_end))
+        return TRUE;
+
+    Time = &SharedUserData->TickCount;
+    range_base = (ULONG_PTR)Time;
+    range_end = (ULONG_PTR)(Time + 1);
+    if (((ULONG_PTR)address >= (ULONG_PTR)range_base) && (((ULONG_PTR)address + length) <= range_end))
+        return TRUE;
+
+    Time = &KeTickCount;
+    range_base = (ULONG_PTR)Time;
+    range_end = (ULONG_PTR)(Time + 1);
+    if (((ULONG_PTR)address >= (ULONG_PTR)range_base) && (((ULONG_PTR)address + length) <= range_end))
+        return TRUE;
+
+    extern KTIMER_TABLE_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE];
+    range_base = (ULONG_PTR)KiTimerTableListHead;
+    range_end = (ULONG_PTR)(&KiTimerTableListHead[TIMER_TABLE_SIZE]);
+    if (((ULONG_PTR)address >= (ULONG_PTR)range_base) && (((ULONG_PTR)address + length) <= range_end))
+        return TRUE;
+
+    return FALSE;
+}
+
+/* Restore previous interrupt state */
+FORCEINLINE
+VOID
+KeRestoreInterrupts(BOOLEAN WereEnabled)
+{
+    if (WereEnabled) _enable();
+}
+
+// Part of ABI, do not change.
+// https://github.com/llvm/llvm-project/blob/main/libcxx/include/atomic
+typedef enum {
+    mo_relaxed,
+    mo_consume,
+    mo_acquire,
+    mo_release,
+    mo_acq_rel,
+    mo_seq_cst
+} morder;
+
+static LONG read_table_lock = -1;
+static LONG write_table_lock = -1;
+
+struct data_san_entry
+{
+    PKTHREAD Thread;
+    ULONG_PTR data_start;
+    ULONG_PTR data_end;
+    void* pc;
+};
+
+/* Hopefully this will be enough */
+static struct data_san_entry read_entries[1024];
+static struct data_san_entry write_entries[1024];
+static LONG read_entry_count;
+static LONG write_entry_count;
+
+static void __lock(PLONG lock)
+{
+    BOOLEAN Enabled = KeDisableInterrupts();
+
+    while (InterlockedIncrement(lock) > 0)
+    {
+        YieldProcessor();
+        InterlockedDecrement(lock);
+    }
+
+    *lock |= (!!Enabled) << 30;
+}
+
+static void __unlock(PLONG lock)
+{
+    BOOLEAN Enabled = !!(*lock & (1 << 30));
+    *lock &= ~(1 << 30);
+    InterlockedDecrement((PLONG)lock);
+    KeRestoreInterrupts(Enabled);
+}
+
+static void lock_read_table(void)
+{
+    __lock(&read_table_lock);
+}
+
+static void unlock_read_table(void)
+{
+    __unlock(&read_table_lock);
+}
+
+static void lock_write_table(void)
+{
+    __lock(&write_table_lock);
+}
+
+static void unlock_write_table(void)
+{
+    __unlock(&write_table_lock);
+}
+
+static
+int
+compare_read(
+    _In_ const void* FirstStruct,
+    _In_ const void* SecondStruct)
+{
+    const struct data_san_entry* EntryA = FirstStruct;
+    const struct data_san_entry* EntryB = SecondStruct;
+
+    /* Order first by start */
+    if (EntryA->data_start != EntryB->data_start)
+        return EntryA->data_start < EntryB->data_start ? -1 : 1;
+
+    /* Then by end. */
+    if (EntryA->data_end != EntryB->data_end)
+        return EntryA->data_end < EntryB->data_end ? -1 : 1;
+
+    /* Use thread as arbiter */
+    if (EntryA->Thread == EntryB->Thread)
+        return 0;
+    return EntryA->Thread < EntryB->Thread ? -1 : 1;
+}
+
+static
+int
+compare_intersect(
+    _In_ const void* FirstStruct,
+    _In_ const void* SecondStruct)
+{
+    const struct data_san_entry* EntryA = FirstStruct;
+    const struct data_san_entry* EntryB = SecondStruct;
+
+    /* In case of writes, the data must be exclusive to each other */
+    if (EntryA->data_end <= EntryB->data_start)
+        return -1;
+
+    if (EntryA->data_start >= EntryB->data_end)
+        return 1;
+
+    return 0;
+}
+
+void __tsan_init(void)
+{
+}
+
+#undef __tsan_disable
+void __tsan_disable(void)
+{
+    PKTHREAD Thread = _KeGetCurrentThread();
+    if (!Thread)
+        return;
+    Thread->ReservedFlags = 1;
+}
+
+#undef __tsan_enable
+void
+__tsan_enable(void)
+{
+    PKTHREAD Thread = _KeGetCurrentThread();
+    if (!Thread)
+        return;
+    Thread->ReservedFlags = 0;
+}
+
+static BOOLEAN __tsan_enabled(void)
+{
+    PKTHREAD Thread = _KeGetCurrentThread();
+    if (!Thread)
+        return FALSE;
+    /* TODO */
+    if (KeGetCurrentIrql() > DISPATCH_LEVEL)
+        return FALSE;
+    return !_KeGetCurrentThread()->ReservedFlags;
+}
+
+static
+void
+__read(void* address, size_t size, void* pc)
+{
+    BOOLEAN inserted = FALSE;
+    struct data_san_entry to_find;
+
+    if (!__tsan_enabled())
+        return;
+
+    /* Check if we must check */
+    if (is_whitelist(address, size))
+        return;
+
+    // BOOLEAN restore = KeDisableInterrupts();
+
+    to_find.data_start = (ULONG_PTR)address;
+    to_find.data_end = (ULONG_PTR)address + size;
+    to_find.Thread = _KeGetCurrentThread();
+    to_find.pc = pc;
+
+    BOOLEAN write = FALSE;
+    struct data_san_entry write_entry;
+
+    /* Insert ourself */
+    lock_read_table();
+    if (read_entry_count < ARRAYSIZE(read_entries))
+    {
+        read_entries[read_entry_count++] = to_find;
+        qsort(read_entries, read_entry_count, sizeof(read_entries[0]), compare_read);
+        inserted = TRUE;
+    }
+    unlock_read_table();
+
+    /* Check if we have a concurrent write */
+    lock_write_table();
+    struct data_san_entry* found = bsearch(&to_find, write_entries, write_entry_count, sizeof(write_entries[0]), compare_intersect);
+    if (found)
+    {
+        write = TRUE;
+        write_entry = *found;
+    }
+    unlock_write_table();
+
+    /* Remove us. */
+    if (inserted)
+    {
+        lock_read_table();
+        found = bsearch(&to_find, read_entries, read_entry_count, sizeof(read_entries[0]), compare_read);
+        const struct data_san_entry* last = &read_entries[--read_entry_count];
+        if (found < last)
+            memmove(found, found + 1, (last - found) * sizeof(read_entries[0]));
+        unlock_read_table();
+    }
+
+    // KeRestoreInterrupts(restore);
+
+    if (write)
+    {
+        DbgPrint("Found concurrent write (%lx -> %lx) from thread %p at %p while reading data (%lx -> %lx) in thread %p at %p\n",
+                 write_entry.data_start, write_entry.data_end, write_entry.Thread, write_entry.pc,
+                 to_find.data_start, to_find.data_end, to_find.Thread, to_find.pc);
+        ASSERT(FALSE);
+    }
+}
+
+static
+void
+__write(void* address, size_t size, void* pc)
+{
+    BOOLEAN inserted = FALSE;
+    struct data_san_entry to_find;
+
+    if (!__tsan_enabled())
+        return;
+
+    /* Check if we must check */
+    if (is_whitelist(address, size))
+        return;
+
+    // BOOLEAN restore = KeDisableInterrupts();
+
+    to_find.data_start = (ULONG_PTR)address;
+    to_find.data_end = (ULONG_PTR)address + size;
+    to_find.Thread = _KeGetCurrentThread();
+    to_find.pc = pc;
+
+    BOOLEAN conflict_read = FALSE;
+    struct data_san_entry conflict_entry_read;
+
+    BOOLEAN conflict_write = FALSE;
+    struct data_san_entry conflict_entry_write;
+
+    /* Insert us */
+    lock_write_table();
+    /* Before this, check for concurrent write. */
+    struct data_san_entry* found = bsearch(&to_find, write_entries, write_entry_count, sizeof(write_entries[0]), compare_intersect);
+    if (found)
+    {
+        conflict_write = TRUE;
+        conflict_entry_write = *found;
+    }
+    if (write_entry_count < ARRAYSIZE(write_entries))
+    {
+        write_entries[write_entry_count] = to_find;
+        write_entry_count++;
+        qsort(write_entries, write_entry_count, sizeof(write_entries[0]), compare_read);
+        inserted = TRUE;
+    }
+    unlock_write_table();
+
+    /* Check if we have a concurrent read */
+    lock_read_table();
+    found = bsearch(&to_find, read_entries, read_entry_count, sizeof(read_entries[0]), compare_intersect);
+    if (found)
+    {
+        conflict_read = TRUE;
+        conflict_entry_read = *found;
+    }
+    unlock_read_table();
+
+    /* Give other threads a chance to actually access the data. */
+    YieldProcessor();
+
+    /* Remove us. */
+    if (inserted)
+    {
+        lock_write_table();
+        found = bsearch(&to_find, write_entries, write_entry_count, sizeof(write_entries[0]), compare_read);
+        // NT_ASSERT(found != NULL);
+        if (found)
+        {
+            write_entry_count--;
+            const struct data_san_entry* last = &write_entries[write_entry_count];
+            if (found < last)
+                memmove(found, found + 1, (last - found) * sizeof(write_entries[0]));
+        }
+
+        unlock_write_table();
+    }
+    // KeRestoreInterrupts(restore);
+
+    if (conflict_write)
+    {
+        DbgPrint("Found concurrent write (%lx -> %lx) from thread %p at %p while writing data (%lx -> %lx) in thread %p at %p\n",
+                 conflict_entry_write.data_start, conflict_entry_write.data_end, conflict_entry_write.Thread, conflict_entry_write.pc,
+                 to_find.data_start, to_find.data_end, to_find.Thread, to_find.pc);
+        ASSERT(FALSE);
+    }
+
+    if (conflict_read)
+    {
+        DbgPrint("Found concurrent read (%lx -> %lx) from thread %p at %p while writing data (%lx -> %lx) in thread %p at %p\n",
+                 conflict_entry_read.data_start, conflict_entry_read.data_end, conflict_entry_read.Thread, conflict_entry_read.pc,
+                 to_find.data_start, to_find.data_end, to_find.Thread, to_find.pc);
+        ASSERT(FALSE);
+    }
+}
+
+static
+void
+__atomic(void* address, size_t size, void* pc)
+{
+    /* In case of atomic operations, we take care that nobody reads or write us in a non-atomic way */
+    struct data_san_entry to_find;
+    struct data_san_entry* found;
+
+    if (!__tsan_enabled())
+        return;
+
+    /* Check if we must check */
+    if (is_whitelist(address, size))
+        return;
+
+    // BOOLEAN restore = KeDisableInterrupts();
+
+    to_find.data_start = (ULONG_PTR)address;
+    to_find.data_end = (ULONG_PTR)address + size;
+    to_find.Thread = _KeGetCurrentThread();
+    to_find.pc = pc;
+
+    BOOLEAN conflict_read = FALSE;
+    struct data_san_entry conflict_entry_read;
+
+    BOOLEAN conflict_write = FALSE;
+    struct data_san_entry conflict_entry_write;
+
+    lock_read_table();
+    found = bsearch(&to_find, read_entries, read_entry_count, sizeof(read_entries[0]), compare_intersect);
+    if (found)
+    {
+        conflict_read = TRUE;
+        conflict_entry_read = *found;
+    }
+    unlock_read_table();
+
+    lock_write_table();
+    found = bsearch(&to_find, write_entries, write_entry_count, sizeof(write_entries[0]), compare_intersect);
+    if (found)
+    {
+        conflict_write = TRUE;
+        conflict_entry_write = *found;
+    }
+    unlock_write_table();
+
+    // KeRestoreInterrupts(restore);
+
+    if (conflict_read)
+    {
+        DbgPrint("Found non-atomic read (%lx -> %lx) from thread %p at %p while performing atomic op on data (%lx -> %lx) in thread %p at %p\n",
+                 conflict_entry_read.data_start, conflict_entry_read.data_end, conflict_entry_read.Thread, conflict_entry_read.pc,
+                 to_find.data_start, to_find.data_end, to_find.Thread, to_find.pc);
+        ASSERT(FALSE);
+    }
+
+    if (conflict_write)
+    {
+        DbgPrint("Found non-atomic write (%lx -> %lx) from thread %p at %p while performing atomic op on data (%lx -> %lx) in thread %p at %p\n",
+                 conflict_entry_write.data_start, conflict_entry_write.data_end, conflict_entry_write.Thread, conflict_entry_write.pc,
+                 to_find.data_start, to_find.data_end, to_find.Thread, to_find.pc);
+        ASSERT(FALSE);
+    }
+}
+
+void
+__tsan_func_entry(void* pc)
+{
+    /* Nothing for now */
+    UNREFERENCED_PARAMETER(pc);
+}
+
+void
+__tsan_func_exit()
+{
+    /* Nothing for now */
+}
+
+/* read functions */
+#define DECLARE_READ(size) void __tsan_read##size(void* address) { __read(address, size, _ReturnAddress()); }
+DECLARE_READ(1)
+DECLARE_READ(2)
+DECLARE_READ(4)
+DECLARE_READ(8)
+DECLARE_READ(16)
+void __tsan_read_range(void* address, size_t size)
+{
+    __read(address, size, _ReturnAddress());
+}
+
+/* write functions */
+#define DECLARE_WRITE(size) void __tsan_write##size(void* address) { __write(address, size, _ReturnAddress()); }
+DECLARE_WRITE(1)
+DECLARE_WRITE(2)
+DECLARE_WRITE(4)
+DECLARE_WRITE(8)
+DECLARE_WRITE(16)
+void __tsan_write_range(void* address, size_t size)
+{
+    __write(address, size, _ReturnAddress());
+}
+
+int
+morder_to___ATOMIC(morder mo)
+{
+    switch(mo)
+    {
+        case mo_relaxed: return __ATOMIC_RELAXED;
+        case mo_consume: return __ATOMIC_CONSUME;
+        case mo_acquire: return __ATOMIC_ACQUIRE;
+        case mo_release: return __ATOMIC_RELEASE;
+        case mo_acq_rel: return __ATOMIC_ACQ_REL;
+        case mo_seq_cst: return __ATOMIC_SEQ_CST;
+        default:
+            ASSERT(FALSE);
+            return -1;
+    }
+}
+
+#define ATOMIC_OP(op, __a, ...)                                 \
+    __atomic((void*)(__a), sizeof(*(__a)), _ReturnAddress());   \
+    return __atomic_##op(__a, __VA_ARGS__);
+
+
+/* Atomic 32 bit functions */
+int __tsan_atomic32_compare_exchange_strong(volatile INT32* a, INT32 *c, INT32 v, morder mo, morder fmo)
+{
+    ATOMIC_OP(compare_exchange_n, a, c, v, 0, morder_to___ATOMIC(mo), morder_to___ATOMIC(fmo));
+}
+
+INT32 __tsan_atomic32_fetch_or(volatile INT32 *a, INT32 v, morder mo)
+{
+    ATOMIC_OP(fetch_or, a, v, morder_to___ATOMIC(mo));
+}
+
+INT32 __tsan_atomic32_fetch_xor(volatile INT32 *a, INT32 v, morder mo)
+{
+    ATOMIC_OP(fetch_xor, a, v, morder_to___ATOMIC(mo));
+}
+
+INT32 __tsan_atomic32_fetch_and(volatile INT32 *a, INT32 v, morder mo)
+{
+    ATOMIC_OP(fetch_and, a, v, morder_to___ATOMIC(mo));
+}
+
+INT32 __tsan_atomic32_fetch_add(volatile INT32 *a, INT32 v, morder mo)
+{
+    ATOMIC_OP(fetch_add, a, v, morder_to___ATOMIC(mo));
+}
+
+INT32 __tsan_atomic32_fetch_sub(volatile INT32 *a, INT32 v, morder mo)
+{
+    ATOMIC_OP(fetch_sub, a, v, morder_to___ATOMIC(mo));
+}
+
+INT32 __tsan_atomic32_exchange(volatile INT32 *a, INT32 v, morder mo)
+{
+    ATOMIC_OP(exchange_n, a, v, morder_to___ATOMIC(mo));
+}
+
+/* Atomic 16 bits functions */
+int __tsan_atomic16_compare_exchange_strong(volatile INT16* a, INT16 *c, INT16 v, morder mo, morder fmo)
+{
+    ATOMIC_OP(compare_exchange_n, a, c, v, 0, morder_to___ATOMIC(mo), morder_to___ATOMIC(fmo));
+}
+
+INT16 __tsan_atomic16_fetch_or(volatile INT16 *a, INT16 v, morder mo)
+{
+    ATOMIC_OP(fetch_or, a, v, morder_to___ATOMIC(mo));
+}
+
+INT16 __tsan_atomic16_fetch_and(volatile INT16 *a, INT16 v, morder mo)
+{
+    ATOMIC_OP(fetch_and, a, v, morder_to___ATOMIC(mo));
+}
+
+INT16 __tsan_atomic16_fetch_add(volatile INT16 *a, INT16 v, morder mo)
+{
+    ATOMIC_OP(fetch_add, a, v, morder_to___ATOMIC(mo));
+}
+
+INT16 __tsan_atomic16_fetch_sub(volatile INT16 *a, INT16 v, morder mo)
+{
+    ATOMIC_OP(fetch_sub, a, v, morder_to___ATOMIC(mo));
+}
+
+void __tsan_atomic_thread_fence(morder mo)
+{
+    /* Nothing to sanitize ? */
+    __atomic_thread_fence(morder_to___ATOMIC(mo));
+}
