kernel
Loading...
Searching...
No Matches
handle.c
Go to the documentation of this file.
1/*++
2
3Module Name:
4
5 handle.c
6
7Purpose:
8
9 This module contains the implementation of MatanelOS's Handle Table.
10
11Author:
12
13 slep (Matanel) 2025.
14
15Revision History:
16
17--*/
18
19#include "../../includes/ht.h"
20#include "../../includes/ob.h"
21#include "../../includes/ps.h"
22#include "../../includes/mt.h"
23#include "../../assert.h"
24
25// ->>>>> The handle table handles are accessed in pageable memory, we cannot be at DISPATCH_LEVEL or above.
26// Since we also use push locks.
27
30
31static
33HtpLookupEntry(
34 IN PHANDLE_TABLE Table,
35 IN HANDLE Handle
36)
37
38/*++
39
40 Routine description:
41
42 Lookups the entry in the given handle table from supplied handle.
43
44 Arguments:
45
46 [IN] PHANDLE_TABLE Table - Pointer to the handle table.
47 [IN] HANDLE Handle - The handle.
48
49 Return Values:
50
51 The entry on success, or NULL on invalid table/handle.
52
53--*/
54
55{
56 // Reject NULL Table, Handles, and handles that are 1/2/3 (must start with 4)
57 if (!Table || !Handle || ((uint64_t)Handle & 3)) return NULL;
58
59 uint64_t TableCode = Table->TableCode;
60 uint64_t Level = TableCode & TABLE_LEVEL_MASK;
61 void* TableBase = (void*)(TableCode & ~TABLE_LEVEL_MASK);
62
63 // Handles are multiples of 4, so we divide by 4 to get actual index
64 uint64_t Index = (uint64_t)(Handle) >> 2;
65
66 if (Level == 0) {
67 // Direct Array
69 return &Entries[Index];
70 }
71 else if (Level == 1) {
72 // TableBase points to an array of pointers to pages
73 // We need to find WHICH page, and then WHICH entry in that page.
74 uint64_t MaxEntriesPerLevel = LOW_LEVEL_ENTRIES;
75 uint64_t PageIndex = Index / MaxEntriesPerLevel;
76 uint64_t EntryIndex = Index % MaxEntriesPerLevel;
77
78 PHANDLE_TABLE_ENTRY* PageTable = (PHANDLE_TABLE_ENTRY*)TableBase;
79 PHANDLE_TABLE_ENTRY ActualPage = PageTable[PageIndex];
80 if (ActualPage) {
81 return &ActualPage[EntryIndex];
82 }
83 }
84
85 // We dont really support millions of handles, so. (level 2 support needed.)
86 return NULL;
87}
88
91 IN PEPROCESS Process
92)
93
94/*++
95
96 Routine description:
97
98 Create a handle table for the given process.
99
100 Arguments:
101
102 [IN] PEPROCESS Process - Pointer to process.
103
104 Return Values:
105
106 Pointer to table on success, or NULL on failure.
107
108--*/
109
110{
111 PHANDLE_TABLE Table = MmAllocatePoolWithTag(NonPagedPool, sizeof(HANDLE_TABLE), 'bTtH'); // HtTb - Handle Table.
112
113 // Allocate the first page of the entries (level 0) (switched to paged pool now)
115 if (!Level0) {
116 MmFreePool(Table);
117 return NULL;
118 }
119
120 // Initialize the free list in the new page.
121 for (uint64_t i = 1; i < LOW_LEVEL_ENTRIES - 1; i++) {
122 Level0[i].NextFreeTableEntry = (i + 1) * 4; // Store as a handle value.
123 }
124 Level0[LOW_LEVEL_ENTRIES - 1].NextFreeTableEntry = 0; // End of the list.
125 // The first index is NULL, always.
126 Level0[0].Object = NULL;
127
128 Table->TableCode = (uint64_t)Level0; // Level is 0, so bottom bits are 0
129 Table->FirstFreeHandle = 4;
130 Table->QuotaProcess = Process;
131 Table->TableLock.Value = 0;
132
133 return Table;
134}
135
136static
138HtpAllocateAndInitHandlePage(
139 IN PHANDLE_TABLE Table,
140 IN uint32_t BaseHandleIndex
141)
142
143/*++
144
145 Routine description:
146
147 Creates a HANDLE_TABLE_ENTRY for the given Table (does not insert).
148
149 Arguments:
150
151 [IN] PHANDLE_TABLE Table - The table to create the entry for.
152 [IN] uint32_t BaseHandleIndex - The base index to create the starting handle table entry for. (e.g if last index was 16, then supply it, :))
153
154 Return Values:
155
156 Pointer to allocated table entry on success, or NULL on failure.
157
158--*/
159
160{
162 if (!NewPage) return NULL;
163
164 // Link all entries in this new page together
165 uint32_t i;
166 for (i = 0; i < LOW_LEVEL_ENTRIES - 1; i++) {
167 // Calculate the actual handle value for the *next* entry
168 NewPage[i].NextFreeTableEntry = (BaseHandleIndex + i + 1) * 4;
169 }
170
171 // The last entry in this new page points to the CURRENT FirstFreeHandle.
172 NewPage[LOW_LEVEL_ENTRIES - 1].NextFreeTableEntry = Table->FirstFreeHandle;
173 NewPage[0].Object = NULL;
174
175 return NewPage;
176}
177
178static
179void
180HtpExpandTable(
181 PHANDLE_TABLE Table
182)
183
184/*++
185
186 Routine description:
187
188 Expands a HANDLE_TABLE to its next intended level.
189
190 Arguments:
191
192 [IN] PHANDLE_TABLE Table - The table to expand.
193
194 Return Values:
195
196 None.
197
198 Notes:
199
200 Any level above 1 is not supported.
201
202--*/
203
204{
205 uint64_t TableCode = Table->TableCode;
206 uint64_t CurrentLevel = TableCode & TABLE_LEVEL_MASK;
207 void* TableBase = (void*)(TableCode & ~TABLE_LEVEL_MASK);
208
209 PHANDLE_TABLE_ENTRY NewFreePage = NULL;
210 uint32_t NewBaseIndex = 0;
211
212 //
213 // Case 1: Promoting from Level 0 to Level 1
214 //
215 if (CurrentLevel == 0) {
216 // Allocate the "Directory" page (holds pointers, not entries)
218 if (!Directory) return; // OOM
219
220 // The existing Level 0 page becomes the first entry in the directory
221 Directory[0] = (PHANDLE_TABLE_ENTRY)TableBase;
222
223 // Allocate a NEW Level 0 page for the second slot
224 NewBaseIndex = LOW_LEVEL_ENTRIES; // The index starts where the first page left off
225 NewFreePage = HtpAllocateAndInitHandlePage(Table, NewBaseIndex);
226
227 if (!NewFreePage) {
228 MmFreePool(Directory);
229 return;
230 }
231
232 Directory[1] = NewFreePage;
233
234 // Update TableCode: Pointer to Directory | Level 1
235 Table->TableCode = ((uint64_t)Directory) | 1;
236
237 // Update the free list to point to the start of our new page
238 // (NewFreePage[0] corresponds to NewBaseIndex)
239 Table->FirstFreeHandle = NewBaseIndex * 4;
240 }
241 //
242 // Case 2: Already Level 1, need to add a new page
243 //
244 else if (CurrentLevel == 1) {
245 PHANDLE_TABLE_ENTRY* Directory = (PHANDLE_TABLE_ENTRY*)TableBase;
246
247 // Find the first empty slot in the directory
248 uint32_t DirectoryIndex = 0;
249 for (DirectoryIndex = 0; DirectoryIndex < LOW_LEVEL_ENTRIES; DirectoryIndex++) {
250 if (Directory[DirectoryIndex] == NULL) break;
251 }
252
253 if (DirectoryIndex >= LOW_LEVEL_ENTRIES) {
254 // Level 1 is full, no level 2 yet.
255 goto Level2Setup;
256 }
257
258 // Calculate the Handle Index base for this new page
259 NewBaseIndex = DirectoryIndex * LOW_LEVEL_ENTRIES;
260
261 // Allocate the new page
262 NewFreePage = HtpAllocateAndInitHandlePage(Table, NewBaseIndex);
263 if (!NewFreePage) return;
264
265 // Link it into the directory
266 Directory[DirectoryIndex] = NewFreePage;
267
268 // Update Free List
269 Table->FirstFreeHandle = NewBaseIndex * 4;
270 }
271
272Level2Setup:
273 return;
274}
275
276HANDLE
278 PHANDLE_TABLE Table,
279 void* Object,
280 uint32_t Access
281)
282
283/*++
284
285 Routine description:
286
287 Creates a HANDLE for specified Object. (Ob)
288
289 Arguments:
290
291 [IN] PHANDLE_TABLE Table - The table to insert the handle in.
292 [IN] void* Object - The Object to create the handle for.
293 [IN] uint32_t Access - The maximum access the handle should have.
294
295 Return Values:
296
297 The HANDLE number, or MT_INVALID_HANDLE on new handle allocation failure.
298
299--*/
300
301{
302 // Acquire exclusive push lock, we are modifying the table.
303 // Since we are in pageable memory, we can wait and access it even.
305
306 // Is there a free handle in the list?
307 if (Table->FirstFreeHandle == 0)
308 {
309
310 // Expand table, no free handles.
311 HtpExpandTable(Table);
312
313 // Check again. If it is STILL 0, expansion failed (OOM).
314 if (Table->FirstFreeHandle == 0) {
316 return MT_INVALID_HANDLE; // Return NULL/0
317 }
318 }
319
320 // Pop from Free List
321 uint32_t FreeIndex = Table->FirstFreeHandle;
322 PHANDLE_TABLE_ENTRY Entry = HtpLookupEntry(Table, (HANDLE)FreeIndex);
323
324 // Sanity check (Should never happen if FirstFreeHandle != 0)
325 if (!Entry) {
327 return MT_INVALID_HANDLE;
328 }
329
330 // Update head of free list
331 Table->FirstFreeHandle = Entry->NextFreeTableEntry;
332
333 // Setup the Entry
334 Entry->Object = Object;
335 Entry->GrantedAccess = Access;
337
338 return (HANDLE)FreeIndex;
339}
340
341void
343 PHANDLE_TABLE Table,
344 HANDLE Handle
345)
346
347/*++
348
349 Routine description:
350
351 Delets a HANDLE from the table.
352
353 Arguments:
354
355 [IN] PHANDLE_TABLE Table - The table to delete the handle from.
356 [IN] HANDLE Handle - The handle to delete.
357
358 Return Values:
359
360 None.
361
362--*/
363
364{
366
367 // Validate Handle
368 // Ensure it's not 0 (if 0 is invalid) and is a multiple of 4
369 if (!Handle || ((uint64_t)Handle & 3)) {
371 return;
372 }
373
374 // Lookup the entry
375 PHANDLE_TABLE_ENTRY Entry = HtpLookupEntry(Table, Handle);
376
377 // Check if entry is actually in use
378 if (!Entry || !Entry->Object) {
379 // Handle is already free or invalid
381 return;
382 }
383
384 // 4. Invalidate the Entry
385 Entry->Object = NULL;
386 Entry->GrantedAccess = 0;
387
388 // Push onto Free List (LIFO - Stack)
389 // The current head of the list becomes the next for this entry.
390 Entry->NextFreeTableEntry = Table->FirstFreeHandle;
391 // This entry becomes the new head.
392 Table->FirstFreeHandle = (uint32_t)Handle;
394}
395
396void*
398 IN PHANDLE_TABLE Table,
399 IN HANDLE Handle,
401)
402
403/*++
404
405 Routine description:
406
407 Retrieves the object for the specified Handle.
408
409 Arguments:
410
411 [IN] PHANDLE_TABLE Table - The table to enumerate the handle in.
412 [IN] HANDLE Handle - The Object's handle.
413 [OUT OPTIONAL] PHANDLE_TABLE_ENTRY* OutEntry - The table entry for the handle.
414
415 Return Values:
416
417 The Object found for the handle.
418
419--*/
420
421{
422 void* Object = NULL;
423
424 // We can acquire a shared push lock since we are strictly reading and not writing to the table contents.
425 MsAcquirePushLockShared(&Table->TableLock);
426
427 PHANDLE_TABLE_ENTRY Entry = HtpLookupEntry(Table, Handle);
428
429 // Check if valid and allocated
430 if (Entry && Entry->Object) {
431 Object = Entry->Object;
432 }
433
434 MsReleasePushLockShared(&Table->TableLock);
435 if (Entry) {
436 if (OutEntry) *OutEntry = Entry;
437 }
438 return Object;
439}
440
441void
443 IN PHANDLE_TABLE Table
444)
445
446/*++
447
448 Routine description:
449
450 Deletes the handle table allocated. (Dereferences any objects that are still alive, frees directory and other)
451
452 Arguments:
453
454 [IN] PHANDLE_TABLE Table - The table to delete.
455
456 Return Values:
457
458 None.
459
460--*/
461
462{
463 // We just free all of the levels and the table itself.
464 if (!Table) return;
465
466 // Grab the table lock.
467 MsAcquirePushLockExclusive(&Table->TableLock);
468
469 uint64_t TableCode = Table->TableCode;
470 uint64_t Level = TableCode & TABLE_LEVEL_MASK;
471 void* TableBase = (void*)(TableCode & ~TABLE_LEVEL_MASK);
472
473 if (Level == 0) {
474 // Single contigious page of entries
476 if (Entries) {
477 // Walk and dereference any live objects that are alive.
478 for (uint64_t i = 0; i < LOW_LEVEL_ENTRIES; i++) {
479 void* Object = Entries[i].Object;
480 if (Object) {
481 Entries[i].Object = NULL;
482 // Decrement handle count atomically
484 InterlockedDecrementIfNotZero((volatile uint64_t*)&Header->HandleCount);
485 ObDereferenceObject(Object);
486 }
487 }
488 }
489
490 // No more live handles — release lock and free the page
491 MsReleasePushLockExclusive(&Table->TableLock);
493 }
494
495 else if (Level == 1) {
496 // Directory of page pointers.
497 PHANDLE_TABLE_ENTRY* Directory = (PHANDLE_TABLE_ENTRY*)TableBase;
498 if (Directory) {
499 // Walk every allocated page.
500 for (uint64_t dir = 0; dir < LOW_LEVEL_ENTRIES; dir++) {
501 PHANDLE_TABLE_ENTRY Page = Directory[dir];
502 if (!Page) continue;
503
504 for (uint64_t i = 0; i < LOW_LEVEL_ENTRIES; i++) {
505 void* Object = Page[i].Object;
506 if (Object) {
507 Page[i].Object = NULL;
508 // Decrement handle count atomically
510 InterlockedDecrementIfNotZero((volatile uint64_t*)Header->HandleCount);
511 ObDereferenceObject(Object);
512 }
513 }
514
515 // Free this page of handles.
516 MmFreePool(Page);
517 }
518 }
519
520 // Release push lock and free the directory itself.
521 MsReleasePushLockExclusive(&Table->TableLock);
522 if (Directory) MmFreePool(Directory);
523 }
524
525 else {
526 // Unsupported level, release lock and get out.
527 assert(false, "Unsupported level encountered on handle table free.");
528 MsReleasePushLockExclusive(&Table->TableLock);
529 }
530
531 // Finally, free our table itself.
532 MmFreePool(Table);
533}
534
537 IN HANDLE Handle
538)
539
540/*++
541
542 Routine description:
543
544 Decrements handle count of object, Dereferences the object associated with the handle, then deletes the handle.
545
546 Arguments:
547
548 [IN] HANDLE Handle - The handle to close.
549
550 Return Values:
551
552 None.
553
554--*/
555
556{
557 if (Handle == MtCurrentProcess() || Handle == MtCurrentThread() || !Handle) return MT_INVALID_HANDLE;
558
560
561 // First get the object for the handle
562 void* Object = HtGetObject(Table, Handle, NULL);
563 if (!Object) return MT_INVALID_HANDLE;
564
565 // Remove the handle from the table first
566 HtDeleteHandle(Table, Handle);
567
568 // Decrement handle count atomically
570 InterlockedDecrementU64((volatile uint64_t*)&Header->HandleCount);
571
572 // Dereference the object
573 ObDereferenceObject(Object);
574
575 return MT_SUCCESS;
576}
#define _Out_Opt
Definition annotations.h:11
#define IN
Definition annotations.h:8
#define assert(...)
Definition assert.h:57
FORCEINLINE uint64_t InterlockedDecrementU64(volatile uint64_t *target)
Definition atomic.h:128
FORCEINLINE bool InterlockedDecrementIfNotZero(volatile uint64_t *value)
Definition atomic.h:229
int32_t HANDLE
Definition core.h:58
EPROCESS * PEPROCESS
Definition core.h:52
struct _DOUBLY_LINKED_LIST DOUBLY_LINKED_LIST
PUSH_LOCK HandleTableLock
Definition handle.c:29
PHANDLE_TABLE HtCreateHandleTable(IN PEPROCESS Process)
Definition handle.c:90
HANDLE HtCreateHandle(PHANDLE_TABLE Table, void *Object, uint32_t Access)
Definition handle.c:277
DOUBLY_LINKED_LIST HandleTableList
Definition handle.c:28
void HtDeleteHandle(PHANDLE_TABLE Table, HANDLE Handle)
Definition handle.c:342
MTSTATUS HtClose(IN HANDLE Handle)
Definition handle.c:536
void HtDeleteHandleTable(IN PHANDLE_TABLE Table)
Definition handle.c:442
void * HtGetObject(IN PHANDLE_TABLE Table, IN HANDLE Handle, _Out_Opt PHANDLE_TABLE_ENTRY *OutEntry)
Definition handle.c:397
struct _HANDLE_TABLE HANDLE_TABLE
struct _HANDLE_TABLE_ENTRY * PHANDLE_TABLE_ENTRY
struct _HANDLE_TABLE * PHANDLE_TABLE
#define LOW_LEVEL_ENTRIES
Definition ht.h:38
#define TABLE_LEVEL_MASK
Definition ht.h:39
uint64_t Entries[]
Definition mh.h:1
@ NonPagedPool
Definition mm.h:355
@ PagedPool
Definition mm.h:356
#define VirtualPageSize
Definition mm.h:53
struct _PUSH_LOCK PUSH_LOCK
#define MtCurrentThread()
Definition mt.h:29
#define MtCurrentProcess()
Definition mt.h:28
#define MT_SUCCESS
Definition mtstatus.h:22
int32_t MTSTATUS
Definition mtstatus.h:12
#define MT_INVALID_HANDLE
Definition mtstatus.h:36
void ObDereferenceObject(IN void *Object)
Definition ob.c:554
struct _OBJECT_HEADER * POBJECT_HEADER
Definition ob.h:64
#define OBJECT_TO_OBJECT_HEADER(o)
Definition ob.h:68
void MmFreePool(IN void *buf)
Definition pool.c:632
void * MmAllocatePoolWithTag(IN enum _POOL_TYPE PoolType, IN size_t NumberOfBytes, IN uint32_t Tag)
Definition pool.c:443
FORCEINLINE PEPROCESS PsGetCurrentProcess(void)
Definition ps.h:300
void MsAcquirePushLockExclusive(IN PUSH_LOCK *Lock)
Definition pushlock.c:80
void MsAcquirePushLockShared(IN PUSH_LOCK *Lock)
Definition pushlock.c:150
void MsReleasePushLockExclusive(IN PUSH_LOCK *Lock)
Definition pushlock.c:99
void MsReleasePushLockShared(IN PUSH_LOCK *Lock)
Definition pushlock.c:179
PHANDLE_TABLE ObjectTable
Definition ps.h:171
uint32_t NextFreeTableEntry
Definition ht.h:34
void * Object
Definition ht.h:29
uint32_t GrantedAccess
Definition ht.h:33
PUSH_LOCK TableLock
Definition ht.h:44
uint64_t TableCode
Definition ht.h:47
PEPROCESS QuotaProcess
Definition ht.h:48
uint32_t FirstFreeHandle
Definition ht.h:52
uint64_t Value
Definition ms.h:100