Skip to content

Commit 7bd862e

Browse files
Use zero-init pools (#5855)
## Description Following up on #4871 to introduce a broader fix to address #4870. Makes pool allocs zero-init by default in user mode. ## Testing Added an alloc test to test zero-inits. ## Documentation N/A
1 parent d9a2cf5 commit 7bd862e

File tree

8 files changed

+280
-3
lines changed

8 files changed

+280
-3
lines changed

src/inc/quic_platform_posix.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,13 @@ CxPlatAlloc(
367367
_In_ uint32_t Tag
368368
);
369369

370+
_Ret_maybenull_
371+
void*
372+
CxPlatAllocUninitialized(
373+
_In_ size_t ByteCount,
374+
_In_ uint32_t Tag
375+
);
376+
370377
void
371378
CxPlatFree(
372379
__drv_freesMem(Mem) _Frees_ptr_ void* Mem,
@@ -375,6 +382,8 @@ CxPlatFree(
375382

376383
#define CXPLAT_ALLOC_PAGED(Size, Tag) CxPlatAlloc(Size, Tag)
377384
#define CXPLAT_ALLOC_NONPAGED(Size, Tag) CxPlatAlloc(Size, Tag)
385+
#define CXPLAT_ALLOC_PAGED_UNINITIALIZED(Size, Tag) CxPlatAllocUninitialized(Size, Tag)
386+
#define CXPLAT_ALLOC_NONPAGED_UNINITIALIZED(Size, Tag) CxPlatAllocUninitialized(Size, Tag)
378387
#define CXPLAT_FREE(Mem, Tag) CxPlatFree((void*)Mem, Tag)
379388

380389
#define CxPlatZeroMemory(Destination, Length) memset((Destination), 0, (Length))
@@ -605,6 +614,39 @@ CxPlatPoolAlloc(
605614
}
606615
#if DEBUG
607616
Header->SpecialFlag = CXPLAT_POOL_ALLOC_FLAG;
617+
#endif
618+
Header->Owner = Pool;
619+
void* Result = (void*)((uint8_t*)Header + sizeof(CXPLAT_POOL_HEADER));
620+
CxPlatZeroMemory(Result, Pool->Size - sizeof(CXPLAT_POOL_HEADER));
621+
return Result;
622+
}
623+
624+
QUIC_INLINE
625+
void*
626+
CxPlatPoolAllocUninitialized(
627+
_Inout_ CXPLAT_POOL* Pool
628+
)
629+
{
630+
CxPlatLockAcquire(&Pool->Lock);
631+
CXPLAT_POOL_HEADER* Header =
632+
#if DEBUG
633+
CxPlatGetAllocFailDenominator() ? NULL : // No pool when using simulated alloc failures
634+
#endif
635+
(CXPLAT_POOL_HEADER*)CxPlatListPopEntry(&Pool->ListHead);
636+
if (Header != NULL) {
637+
CXPLAT_DBG_ASSERT(Pool->ListDepth > 0);
638+
CXPLAT_DBG_ASSERT(Header->SpecialFlag == CXPLAT_POOL_FREE_FLAG);
639+
Pool->ListDepth--;
640+
}
641+
CxPlatLockRelease(&Pool->Lock);
642+
if (Header == NULL) {
643+
Header = (CXPLAT_POOL_HEADER*)CxPlatAlloc(Pool->Size, Pool->Tag);
644+
if (Header == NULL) {
645+
return NULL;
646+
}
647+
}
648+
#if DEBUG
649+
Header->SpecialFlag = CXPLAT_POOL_ALLOC_FLAG;
608650
#endif
609651
Header->Owner = Pool;
610652
return (void*)((uint8_t*)Header + sizeof(CXPLAT_POOL_HEADER));

src/inc/quic_platform_winkernel.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,10 @@ CxPlatLogAssert(
253253

254254
extern uint64_t CxPlatTotalMemory;
255255

256-
#define CXPLAT_ALLOC_PAGED(Size, Tag) ExAllocatePool2(POOL_FLAG_PAGED | POOL_FLAG_UNINITIALIZED, Size, Tag)
257-
#define CXPLAT_ALLOC_NONPAGED(Size, Tag) ExAllocatePool2(POOL_FLAG_NON_PAGED | POOL_FLAG_UNINITIALIZED, Size, Tag)
256+
#define CXPLAT_ALLOC_PAGED(Size, Tag) ExAllocatePool2(POOL_FLAG_PAGED, Size, Tag)
257+
#define CXPLAT_ALLOC_NONPAGED(Size, Tag) ExAllocatePool2(POOL_FLAG_NON_PAGED, Size, Tag)
258+
#define CXPLAT_ALLOC_PAGED_UNINITIALIZED(Size, Tag) ExAllocatePool2(POOL_FLAG_PAGED | POOL_FLAG_UNINITIALIZED, Size, Tag)
259+
#define CXPLAT_ALLOC_NONPAGED_UNINITIALIZED(Size, Tag) ExAllocatePool2(POOL_FLAG_NON_PAGED | POOL_FLAG_UNINITIALIZED, Size, Tag)
258260
#define CXPLAT_FREE(Mem, Tag) ExFreePoolWithTag((void*)Mem, Tag)
259261

260262
//
@@ -292,6 +294,23 @@ void*
292294
CxPlatPoolAlloc(
293295
_Inout_ CXPLAT_POOL* Pool
294296
)
297+
{
298+
CXPLAT_POOL_HEADER* Header =
299+
(CXPLAT_POOL_HEADER*)ExAllocateFromLookasideListEx(Pool);
300+
if (Header == NULL) {
301+
return NULL;
302+
}
303+
Header->Owner = Pool;
304+
void* Result = (void*)(Header + 1);
305+
RtlZeroMemory(Result, Pool->L.Size - sizeof(CXPLAT_POOL_HEADER));
306+
return Result;
307+
}
308+
309+
QUIC_INLINE
310+
void*
311+
CxPlatPoolAllocUninitialized(
312+
_Inout_ CXPLAT_POOL* Pool
313+
)
295314
{
296315
CXPLAT_POOL_HEADER* Header =
297316
(CXPLAT_POOL_HEADER*)ExAllocateFromLookasideListEx(Pool);

src/inc/quic_platform_winuser.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,12 @@ CxPlatAlloc(
278278
_In_ uint32_t Tag
279279
);
280280

281+
void*
282+
CxPlatAllocUninitialized(
283+
_In_ size_t ByteCount,
284+
_In_ uint32_t Tag
285+
);
286+
281287
void
282288
CxPlatFree(
283289
__drv_freesMem(Mem) _Frees_ptr_ void* Mem,
@@ -286,6 +292,8 @@ CxPlatFree(
286292

287293
#define CXPLAT_ALLOC_PAGED(Size, Tag) CxPlatAlloc(Size, Tag)
288294
#define CXPLAT_ALLOC_NONPAGED(Size, Tag) CxPlatAlloc(Size, Tag)
295+
#define CXPLAT_ALLOC_PAGED_UNINITIALIZED(Size, Tag) CxPlatAllocUninitialized(Size, Tag)
296+
#define CXPLAT_ALLOC_NONPAGED_UNINITIALIZED(Size, Tag) CxPlatAllocUninitialized(Size, Tag)
289297
#define CXPLAT_FREE(Mem, Tag) CxPlatFree((void*)Mem, Tag)
290298

291299
typedef struct CXPLAT_POOL CXPLAT_POOL;
@@ -446,6 +454,35 @@ CxPlatPoolAlloc(
446454
CXPLAT_DBG_ASSERT(Header->SpecialFlag == CXPLAT_POOL_FREE_FLAG);
447455
}
448456
Header->SpecialFlag = CXPLAT_POOL_ALLOC_FLAG;
457+
#endif
458+
Header->Owner = Pool;
459+
void* Result = (void*)(Header + 1);
460+
RtlZeroMemory(Result, Pool->Size - sizeof(CXPLAT_POOL_HEADER));
461+
return Result;
462+
}
463+
464+
QUIC_INLINE
465+
void*
466+
CxPlatPoolAllocUninitialized(
467+
_Inout_ CXPLAT_POOL* Pool
468+
)
469+
{
470+
CXPLAT_POOL_HEADER* Header =
471+
#if DEBUG
472+
CxPlatGetAllocFailDenominator() ? NULL : // No pool when using simulated alloc failures
473+
#endif
474+
(CXPLAT_POOL_HEADER*)InterlockedPopEntrySList(&Pool->ListHead);
475+
if (Header == NULL) {
476+
Header = Pool->Allocate(Pool->Size, Pool->Tag, Pool);
477+
if (Header == NULL) {
478+
return NULL;
479+
}
480+
}
481+
#if DEBUG
482+
else {
483+
CXPLAT_DBG_ASSERT(Header->SpecialFlag == CXPLAT_POOL_FREE_FLAG);
484+
}
485+
Header->SpecialFlag = CXPLAT_POOL_ALLOC_FLAG;
449486
#endif
450487
Header->Owner = Pool;
451488
return (void*)(Header + 1);

src/platform/datapath_winkernel.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2606,7 +2606,11 @@ CxPlatSendDataAllocDataBuffer(
26062606
_In_ CXPLAT_POOL* BufferPool
26072607
)
26082608
{
2609-
CXPLAT_DATAPATH_SEND_BUFFER* SendBuffer = CxPlatPoolAlloc(BufferPool);
2609+
//
2610+
// Use the *PoolAllocUninitialized method here to preserve the MDL
2611+
// pre-initializations (the default *PoolAlloc method zero-inits).
2612+
//
2613+
CXPLAT_DATAPATH_SEND_BUFFER* SendBuffer = CxPlatPoolAllocUninitialized(BufferPool);
26102614
if (SendBuffer == NULL) {
26112615
return NULL;
26122616
}

src/platform/platform_posix.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,24 @@ CxPlatAlloc(
281281
(CxPlatform.AllocFailDenominator < 0 && InterlockedIncrement(&CxPlatform.AllocCounter) % CxPlatform.AllocFailDenominator == 0)) {
282282
return NULL;
283283
}
284+
#endif
285+
return calloc(1, ByteCount);
286+
}
287+
288+
void*
289+
CxPlatAllocUninitialized(
290+
_In_ size_t ByteCount,
291+
_In_ uint32_t Tag
292+
)
293+
{
294+
UNREFERENCED_PARAMETER(Tag);
295+
#ifdef DEBUG
296+
CXPLAT_DBG_ASSERT(ByteCount != 0);
297+
uint32_t Rand;
298+
if ((CxPlatform.AllocFailDenominator > 0 && (CxPlatRandom(sizeof(Rand), &Rand), Rand % CxPlatform.AllocFailDenominator) == 1) ||
299+
(CxPlatform.AllocFailDenominator < 0 && InterlockedIncrement(&CxPlatform.AllocCounter) % CxPlatform.AllocFailDenominator == 0)) {
300+
return NULL;
301+
}
284302
#endif
285303
return malloc(ByteCount);
286304
}

src/platform/platform_winuser.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,33 @@ CxPlatAlloc(
459459
return NULL;
460460
}
461461

462+
void* Alloc = HeapAlloc(CxPlatform.Heap, HEAP_ZERO_MEMORY, ByteCount + AllocOffset);
463+
if (Alloc == NULL) {
464+
return NULL;
465+
}
466+
*((uint32_t*)Alloc) = Tag;
467+
return (void*)((uint8_t*)Alloc + AllocOffset);
468+
#else
469+
UNREFERENCED_PARAMETER(Tag);
470+
return HeapAlloc(CxPlatform.Heap, HEAP_ZERO_MEMORY, ByteCount);
471+
#endif
472+
}
473+
474+
void*
475+
CxPlatAllocUninitialized(
476+
_In_ size_t ByteCount,
477+
_In_ uint32_t Tag
478+
)
479+
{
480+
#ifdef DEBUG
481+
CXPLAT_DBG_ASSERT(CxPlatform.Heap);
482+
CXPLAT_DBG_ASSERT(ByteCount != 0);
483+
uint32_t Rand;
484+
if ((CxPlatform.AllocFailDenominator > 0 && (CxPlatRandom(sizeof(Rand), &Rand), Rand % CxPlatform.AllocFailDenominator) == 1) ||
485+
(CxPlatform.AllocFailDenominator < 0 && InterlockedIncrement(&CxPlatform.AllocCounter) % CxPlatform.AllocFailDenominator == 0)) {
486+
return NULL;
487+
}
488+
462489
void* Alloc = HeapAlloc(CxPlatform.Heap, 0, ByteCount + AllocOffset);
463490
if (Alloc == NULL) {
464491
return NULL;
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*++
2+
3+
Copyright (c) Microsoft Corporation.
4+
Licensed under the MIT License.
5+
6+
Abstract:
7+
8+
Tests to verify that memory allocations are zero-initialized by default.
9+
10+
--*/
11+
12+
#include "main.h"
13+
#include <algorithm>
14+
15+
//
16+
// Size large enough to avoid small-allocation optimizations that might
17+
// coincidentally return zeroed memory.
18+
//
19+
#define TEST_ALLOC_SIZE 256
20+
21+
static bool
22+
IsZeroMemory(
23+
_In_ const uint8_t* Buffer,
24+
_In_ size_t Size
25+
)
26+
{
27+
return std::all_of(Buffer, Buffer + Size, [](uint8_t b) { return b == 0; });
28+
}
29+
30+
//
31+
// Allocates memory, fills it with non-zero data, frees it, then reallocates
32+
// the same size and checks whether the new allocation is zero-initialized.
33+
//
34+
// If the allocator does NOT zero-initialize, the recycled block will likely
35+
// still contain the 0xDE pattern, causing the test to fail.
36+
//
37+
TEST(AllocTest, NonPagedAllocIsZeroInitialized)
38+
{
39+
//
40+
// Phase 1: Allocate and poison memory so the heap has a dirty free block.
41+
//
42+
uint8_t* First =
43+
(uint8_t*)CXPLAT_ALLOC_NONPAGED(TEST_ALLOC_SIZE, QUIC_POOL_TEST);
44+
ASSERT_NE(nullptr, First);
45+
46+
memset(First, 0xDE, TEST_ALLOC_SIZE);
47+
CXPLAT_FREE(First, QUIC_POOL_TEST);
48+
49+
//
50+
// Phase 2: Reallocate the same size. A non-zeroing allocator will likely
51+
// hand back the same (still dirty) block.
52+
//
53+
uint8_t* Second =
54+
(uint8_t*)CXPLAT_ALLOC_NONPAGED(TEST_ALLOC_SIZE, QUIC_POOL_TEST);
55+
ASSERT_NE(nullptr, Second);
56+
57+
EXPECT_TRUE(IsZeroMemory(Second, TEST_ALLOC_SIZE))
58+
<< "CXPLAT_ALLOC_NONPAGED returned non-zero-initialized memory. ";
59+
60+
CXPLAT_FREE(Second, QUIC_POOL_TEST);
61+
}
62+
63+
TEST(AllocTest, PagedAllocIsZeroInitialized)
64+
{
65+
uint8_t* First =
66+
(uint8_t*)CXPLAT_ALLOC_PAGED(TEST_ALLOC_SIZE, QUIC_POOL_TEST);
67+
ASSERT_NE(nullptr, First);
68+
69+
memset(First, 0xDE, TEST_ALLOC_SIZE);
70+
CXPLAT_FREE(First, QUIC_POOL_TEST);
71+
72+
uint8_t* Second =
73+
(uint8_t*)CXPLAT_ALLOC_PAGED(TEST_ALLOC_SIZE, QUIC_POOL_TEST);
74+
ASSERT_NE(nullptr, Second);
75+
76+
EXPECT_TRUE(IsZeroMemory(Second, TEST_ALLOC_SIZE))
77+
<< "CXPLAT_ALLOC_PAGED returned non-zero-initialized memory. ";
78+
79+
CXPLAT_FREE(Second, QUIC_POOL_TEST);
80+
}
81+
82+
//
83+
// CxPlatPoolAlloc can return a previously-used
84+
// object from its free list, and that object will contain stale data unless
85+
// the allocator explicitly zeroes it.
86+
//
87+
TEST(AllocTest, PoolAllocIsZeroInitializedOnReuse)
88+
{
89+
CXPLAT_POOL Pool;
90+
CxPlatPoolInitialize(FALSE, TEST_ALLOC_SIZE, QUIC_POOL_TEST, &Pool);
91+
92+
//
93+
// Phase 1: Allocate from pool, poison, return to pool.
94+
//
95+
uint8_t* First = (uint8_t*)CxPlatPoolAlloc(&Pool);
96+
ASSERT_NE(nullptr, First);
97+
98+
memset(First, 0xDE, TEST_ALLOC_SIZE);
99+
CxPlatPoolFree(First);
100+
101+
//
102+
// Phase 2: Re-allocate from pool. The pool will return the same object
103+
// from its free list. If pool alloc doesn't zero-init, it will still
104+
// contain the 0xDE poison bytes.
105+
//
106+
uint8_t* Second = (uint8_t*)CxPlatPoolAlloc(&Pool);
107+
ASSERT_NE(nullptr, Second);
108+
109+
EXPECT_TRUE(IsZeroMemory(Second, TEST_ALLOC_SIZE))
110+
<< "CxPlatPoolAlloc returned non-zero-initialized memory on reuse. ";
111+
112+
CxPlatPoolFree(Second);
113+
CxPlatPoolUninitialize(&Pool);
114+
}
115+
116+
TEST(AllocTest, PoolAllocIsZeroInitializedFresh)
117+
{
118+
CXPLAT_POOL Pool;
119+
CxPlatPoolInitialize(FALSE, TEST_ALLOC_SIZE, QUIC_POOL_TEST, &Pool);
120+
121+
uint8_t* Mem = (uint8_t*)CxPlatPoolAlloc(&Pool);
122+
ASSERT_NE(nullptr, Mem);
123+
124+
EXPECT_TRUE(IsZeroMemory(Mem, TEST_ALLOC_SIZE))
125+
<< "CxPlatPoolAlloc returned non-zero-initialized memory on fresh alloc. ";
126+
127+
CxPlatPoolFree(Mem);
128+
CxPlatPoolUninitialize(&Pool);
129+
}

src/platform/unittest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
set(SOURCES
55
main.cpp
6+
AllocTest.cpp
67
CryptTest.cpp
78
DataPathTest.cpp
89
PlatformTest.cpp

0 commit comments

Comments
 (0)