1 /*
2 * PROJECT: ReactOS Kernel
3 * LICENSE: GPL - See COPYING in the top level directory
4 * FILE: ntoskrnl/ob/obref.c
5 * PURPOSE: Manages the referencing and de-referencing of all Objects,
6 * as well as the Object Fast Reference implementation.
7 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
8 * Eric Kohl
9 * Thomas Weidenmueller (w3seek@reactos.org)
10 */
11
12 /* INCLUDES ******************************************************************/
13
14 #include <ntoskrnl.h>
15 #define NDEBUG
16 #include <debug.h>
17
18 /* PRIVATE FUNCTIONS *********************************************************/
19
20 BOOLEAN
21 FASTCALL
ObReferenceObjectSafe(IN PVOID Object)22 ObReferenceObjectSafe(IN PVOID Object)
23 {
24 POBJECT_HEADER ObjectHeader;
25 LONG_PTR OldValue, NewValue;
26
27 /* Get the object header */
28 ObjectHeader = OBJECT_TO_OBJECT_HEADER(Object);
29
30 /* Get the current reference count and fail if it's zero */
31 OldValue = ObjectHeader->PointerCount;
32 if (!OldValue) return FALSE;
33
34 /* Start reference loop */
35 do
36 {
37 /* Increase the reference count */
38 NewValue = InterlockedCompareExchangeSizeT(&ObjectHeader->PointerCount,
39 OldValue + 1,
40 OldValue);
41 if (OldValue == NewValue) return TRUE;
42
43 /* Keep looping */
44 OldValue = NewValue;
45 } while (OldValue);
46
47 /* If we got here, then the reference count is now 0 */
48 return FALSE;
49 }
50
51 VOID
52 NTAPI
ObpDeferObjectDeletion(IN POBJECT_HEADER Header)53 ObpDeferObjectDeletion(IN POBJECT_HEADER Header)
54 {
55 PVOID Entry;
56
57 /* Loop while trying to update the list */
58 do
59 {
60 /* Get the current entry */
61 Entry = ObpReaperList;
62
63 /* Link our object to the list */
64 Header->NextToFree = Entry;
65
66 /* Update the list */
67 } while (InterlockedCompareExchangePointer(&ObpReaperList,
68 Header,
69 Entry) != Entry);
70
71 /* Queue the work item if needed */
72 if (!Entry) ExQueueWorkItem(&ObpReaperWorkItem, CriticalWorkQueue);
73 }
74
75 LONG
76 FASTCALL
ObReferenceObjectEx(IN PVOID Object,IN LONG Count)77 ObReferenceObjectEx(IN PVOID Object,
78 IN LONG Count)
79 {
80 /* Increment the reference count and return the count now */
81 return InterlockedExchangeAddSizeT(&OBJECT_TO_OBJECT_HEADER(Object)->
82 PointerCount,
83 Count) + Count;
84 }
85
86 LONG
87 FASTCALL
ObDereferenceObjectEx(IN PVOID Object,IN LONG Count)88 ObDereferenceObjectEx(IN PVOID Object,
89 IN LONG Count)
90 {
91 POBJECT_HEADER Header;
92 LONG_PTR NewCount;
93
94 /* Extract the object header */
95 Header = OBJECT_TO_OBJECT_HEADER(Object);
96
97 /* Check whether the object can now be deleted. */
98 NewCount = InterlockedExchangeAddSizeT(&Header->PointerCount, -Count) - Count;
99 if (!NewCount) ObpDeferObjectDeletion(Header);
100
101 /* Return the current count */
102 return NewCount;
103 }
104
105 VOID
106 FASTCALL
ObInitializeFastReference(IN PEX_FAST_REF FastRef,IN PVOID Object OPTIONAL)107 ObInitializeFastReference(IN PEX_FAST_REF FastRef,
108 IN PVOID Object OPTIONAL)
109 {
110 /* Check if we were given an object and reference it 7 times */
111 if (Object) ObReferenceObjectEx(Object, MAX_FAST_REFS);
112
113 /* Setup the fast reference */
114 ExInitializeFastReference(FastRef, Object);
115 }
116
117 PVOID
118 FASTCALL
ObFastReferenceObjectLocked(IN PEX_FAST_REF FastRef)119 ObFastReferenceObjectLocked(IN PEX_FAST_REF FastRef)
120 {
121 PVOID Object;
122 EX_FAST_REF OldValue = *FastRef;
123
124 /* Get the object and reference it slowly */
125 Object = ExGetObjectFastReference(OldValue);
126 if (Object) ObReferenceObject(Object);
127 return Object;
128 }
129
130 PVOID
131 FASTCALL
ObFastReferenceObject(IN PEX_FAST_REF FastRef)132 ObFastReferenceObject(IN PEX_FAST_REF FastRef)
133 {
134 EX_FAST_REF OldValue;
135 ULONG_PTR Count;
136 PVOID Object;
137
138 /* Reference the object and get it pointer */
139 OldValue = ExAcquireFastReference(FastRef);
140 Object = ExGetObjectFastReference(OldValue);
141
142 /* Check how many references are left */
143 Count = ExGetCountFastReference(OldValue);
144
145 /* Check if the reference count is over 1 */
146 if (Count > 1) return Object;
147
148 /* Check if the reference count has reached 0 */
149 if (!Count) return NULL;
150
151 /* Otherwise, reference the object 7 times */
152 ObReferenceObjectEx(Object, MAX_FAST_REFS);
153
154 /* Now update the reference count */
155 if (!ExInsertFastReference(FastRef, Object))
156 {
157 /* We failed: completely dereference the object */
158 ObDereferenceObjectEx(Object, MAX_FAST_REFS);
159 }
160
161 /* Return the Object */
162 return Object;
163 }
164
165 VOID
166 FASTCALL
ObFastDereferenceObject(IN PEX_FAST_REF FastRef,IN PVOID Object)167 ObFastDereferenceObject(IN PEX_FAST_REF FastRef,
168 IN PVOID Object)
169 {
170 /* Release a fast reference. If this failed, use the slow path */
171 if (!ExReleaseFastReference(FastRef, Object)) ObDereferenceObject(Object);
172 }
173
174 PVOID
175 FASTCALL
ObFastReplaceObject(IN PEX_FAST_REF FastRef,PVOID Object)176 ObFastReplaceObject(IN PEX_FAST_REF FastRef,
177 PVOID Object)
178 {
179 EX_FAST_REF OldValue;
180 PVOID OldObject;
181 ULONG Count;
182
183 /* Check if we were given an object and reference it 7 times */
184 if (Object) ObReferenceObjectEx(Object, MAX_FAST_REFS);
185
186 /* Do the swap */
187 OldValue = ExSwapFastReference(FastRef, Object);
188 OldObject = ExGetObjectFastReference(OldValue);
189
190 /* Check if we had an active object and dereference it */
191 Count = ExGetCountFastReference(OldValue);
192 if ((OldObject) && (Count)) ObDereferenceObjectEx(OldObject, Count);
193
194 /* Return the old object */
195 return OldObject;
196 }
197
198 NTSTATUS
199 NTAPI
ObReferenceFileObjectForWrite(IN HANDLE Handle,IN KPROCESSOR_MODE AccessMode,OUT PFILE_OBJECT * FileObject,OUT POBJECT_HANDLE_INFORMATION HandleInformation)200 ObReferenceFileObjectForWrite(IN HANDLE Handle,
201 IN KPROCESSOR_MODE AccessMode,
202 OUT PFILE_OBJECT *FileObject,
203 OUT POBJECT_HANDLE_INFORMATION HandleInformation)
204 {
205 NTSTATUS Status;
206 PHANDLE_TABLE HandleTable;
207 POBJECT_HEADER ObjectHeader;
208 PHANDLE_TABLE_ENTRY HandleEntry;
209 ACCESS_MASK GrantedAccess, DesiredAccess;
210
211 /* Assume failure */
212 *FileObject = NULL;
213
214 /* Check if this is a special handle */
215 if (HandleToLong(Handle) < 0)
216 {
217 /* Make sure we have a valid kernel handle */
218 if (AccessMode != KernelMode || Handle == NtCurrentProcess() || Handle == NtCurrentThread())
219 {
220 return STATUS_INVALID_HANDLE;
221 }
222
223 /* Use the kernel handle table and get the actual handle value */
224 Handle = ObKernelHandleToHandle(Handle);
225 HandleTable = ObpKernelHandleTable;
226 }
227 else
228 {
229 /* Otherwise use this process's handle table */
230 HandleTable = PsGetCurrentProcess()->ObjectTable;
231 }
232
233 ASSERT(HandleTable != NULL);
234 KeEnterCriticalRegion();
235
236 /* Get the handle entry */
237 HandleEntry = ExMapHandleToPointer(HandleTable, Handle);
238 if (HandleEntry)
239 {
240 /* Get the object header and validate the type*/
241 ObjectHeader = ObpGetHandleObject(HandleEntry);
242
243 /* Get the desired access from the file object */
244 if (!NT_SUCCESS(IoComputeDesiredAccessFileObject((PFILE_OBJECT)&ObjectHeader->Body,
245 &DesiredAccess)))
246 {
247 Status = STATUS_OBJECT_TYPE_MISMATCH;
248 }
249 else
250 {
251 /* Extract the granted access from the handle entry */
252 if (BooleanFlagOn(NtGlobalFlag, FLG_KERNEL_STACK_TRACE_DB))
253 {
254 /* FIXME: Translate granted access */
255 GrantedAccess = HandleEntry->GrantedAccess;
256 }
257 else
258 {
259 GrantedAccess = HandleEntry->GrantedAccess & ~ObpAccessProtectCloseBit;
260 }
261
262 /* FIXME: Get handle information for audit */
263
264 HandleInformation->GrantedAccess = GrantedAccess;
265
266 /* FIXME: Get handle attributes */
267 HandleInformation->HandleAttributes = 0;
268
269 /* Do granted and desired access match? */
270 if (GrantedAccess & DesiredAccess)
271 {
272 /* FIXME: Audit access if required */
273
274 /* Reference the object directly since we have its header */
275 InterlockedIncrementSizeT(&ObjectHeader->PointerCount);
276
277 /* Unlock the handle */
278 ExUnlockHandleTableEntry(HandleTable, HandleEntry);
279 KeLeaveCriticalRegion();
280
281 *FileObject = (PFILE_OBJECT)&ObjectHeader->Body;
282
283 /* Return success */
284 ASSERT(*FileObject != NULL);
285 return STATUS_SUCCESS;
286 }
287
288 /* No match, deny write access */
289 Status = STATUS_ACCESS_DENIED;
290
291 ExUnlockHandleTableEntry(HandleTable, HandleEntry);
292 }
293 }
294 else
295 {
296 Status = STATUS_INVALID_HANDLE;
297 }
298
299 /* Return failure status */
300 KeLeaveCriticalRegion();
301 return Status;
302 }
303
304 /* PUBLIC FUNCTIONS *********************************************************/
305
306 LONG_PTR
307 FASTCALL
ObfReferenceObject(IN PVOID Object)308 ObfReferenceObject(IN PVOID Object)
309 {
310 ASSERT(Object);
311
312 /* Get the header and increment the reference count */
313 return InterlockedIncrementSizeT(&OBJECT_TO_OBJECT_HEADER(Object)->PointerCount);
314 }
315
316 LONG_PTR
317 FASTCALL
ObfDereferenceObject(IN PVOID Object)318 ObfDereferenceObject(IN PVOID Object)
319 {
320 POBJECT_HEADER Header;
321 LONG_PTR NewCount;
322
323 /* Extract the object header */
324 Header = OBJECT_TO_OBJECT_HEADER(Object);
325
326 if (Header->PointerCount < Header->HandleCount)
327 {
328 DPRINT1("Misbehaving object: %wZ\n", &Header->Type->Name);
329 return Header->PointerCount;
330 }
331
332 /* Check whether the object can now be deleted. */
333 NewCount = InterlockedDecrementSizeT(&Header->PointerCount);
334 if (!NewCount)
335 {
336 /* Sanity check */
337 ASSERT(Header->HandleCount == 0);
338
339 /* Check if APCs are still active */
340 if (!KeAreAllApcsDisabled())
341 {
342 /* Remove the object */
343 ObpDeleteObject(Object, FALSE);
344 }
345 else
346 {
347 /* Add us to the deferred deletion list */
348 ObpDeferObjectDeletion(Header);
349 }
350 }
351
352 /* Return the new count */
353 return NewCount;
354 }
355
356 VOID
357 NTAPI
ObDereferenceObjectDeferDelete(IN PVOID Object)358 ObDereferenceObjectDeferDelete(IN PVOID Object)
359 {
360 POBJECT_HEADER Header = OBJECT_TO_OBJECT_HEADER(Object);
361
362 /* Check whether the object can now be deleted. */
363 if (!InterlockedDecrementSizeT(&Header->PointerCount))
364 {
365 /* Add us to the deferred deletion list */
366 ObpDeferObjectDeletion(Header);
367 }
368 }
369
370 #undef ObDereferenceObject
371 VOID
372 NTAPI
ObDereferenceObject(IN PVOID Object)373 ObDereferenceObject(IN PVOID Object)
374 {
375 /* Call the fastcall function */
376 ObfDereferenceObject(Object);
377 }
378
379 NTSTATUS
380 NTAPI
ObReferenceObjectByPointer(IN PVOID Object,IN ACCESS_MASK DesiredAccess,IN POBJECT_TYPE ObjectType,IN KPROCESSOR_MODE AccessMode)381 ObReferenceObjectByPointer(IN PVOID Object,
382 IN ACCESS_MASK DesiredAccess,
383 IN POBJECT_TYPE ObjectType,
384 IN KPROCESSOR_MODE AccessMode)
385 {
386 POBJECT_HEADER Header;
387
388 /* Get the header */
389 Header = OBJECT_TO_OBJECT_HEADER(Object);
390
391 /*
392 * Validate object type if the call is for UserMode.
393 * NOTE: Unless it's a symbolic link (Caz Yokoyama [MSFT])
394 */
395 if ((Header->Type != ObjectType) && ((AccessMode != KernelMode) ||
396 (ObjectType == ObpSymbolicLinkObjectType)))
397 {
398 /* Invalid type */
399 return STATUS_OBJECT_TYPE_MISMATCH;
400 }
401
402 /* Increment the reference count and return success */
403 InterlockedIncrementSizeT(&Header->PointerCount);
404 return STATUS_SUCCESS;
405 }
406
407 NTSTATUS
408 NTAPI
ObReferenceObjectByName(IN PUNICODE_STRING ObjectPath,IN ULONG Attributes,IN PACCESS_STATE PassedAccessState,IN ACCESS_MASK DesiredAccess,IN POBJECT_TYPE ObjectType,IN KPROCESSOR_MODE AccessMode,IN OUT PVOID ParseContext,OUT PVOID * ObjectPtr)409 ObReferenceObjectByName(IN PUNICODE_STRING ObjectPath,
410 IN ULONG Attributes,
411 IN PACCESS_STATE PassedAccessState,
412 IN ACCESS_MASK DesiredAccess,
413 IN POBJECT_TYPE ObjectType,
414 IN KPROCESSOR_MODE AccessMode,
415 IN OUT PVOID ParseContext,
416 OUT PVOID* ObjectPtr)
417 {
418 PVOID Object = NULL;
419 UNICODE_STRING ObjectName;
420 NTSTATUS Status;
421 OBP_LOOKUP_CONTEXT Context;
422 AUX_ACCESS_DATA AuxData;
423 ACCESS_STATE AccessState;
424 PAGED_CODE();
425
426 /* Fail quickly */
427 if (!ObjectPath) return STATUS_OBJECT_NAME_INVALID;
428
429 /* Capture the name */
430 Status = ObpCaptureObjectName(&ObjectName, ObjectPath, AccessMode, TRUE);
431 if (!NT_SUCCESS(Status)) return Status;
432
433 /* We also need a valid name after capture */
434 if (!ObjectName.Length) return STATUS_OBJECT_NAME_INVALID;
435
436 /* Check if we didn't get an access state */
437 if (!PassedAccessState)
438 {
439 /* Use our built-in access state */
440 PassedAccessState = &AccessState;
441 Status = SeCreateAccessState(&AccessState,
442 &AuxData,
443 DesiredAccess,
444 &ObjectType->TypeInfo.GenericMapping);
445 if (!NT_SUCCESS(Status)) goto Quickie;
446 }
447
448 /* Find the object */
449 *ObjectPtr = NULL;
450 Status = ObpLookupObjectName(NULL,
451 &ObjectName,
452 Attributes,
453 ObjectType,
454 AccessMode,
455 ParseContext,
456 NULL,
457 NULL,
458 PassedAccessState,
459 &Context,
460 &Object);
461
462 /* Cleanup after lookup */
463 ObpReleaseLookupContext(&Context);
464
465 /* Check if the lookup succeeded */
466 if (NT_SUCCESS(Status))
467 {
468 /* Check if access is allowed */
469 if (ObpCheckObjectReference(Object,
470 PassedAccessState,
471 FALSE,
472 AccessMode,
473 &Status))
474 {
475 /* Return the object */
476 *ObjectPtr = Object;
477 }
478 }
479
480 /* Free the access state */
481 if (PassedAccessState == &AccessState)
482 {
483 SeDeleteAccessState(PassedAccessState);
484 }
485
486 Quickie:
487 /* Free the captured name if we had one, and return status */
488 ObpFreeObjectNameBuffer(&ObjectName);
489 return Status;
490 }
491
492 NTSTATUS
493 NTAPI
ObReferenceObjectByHandle(IN HANDLE Handle,IN ACCESS_MASK DesiredAccess,IN POBJECT_TYPE ObjectType,IN KPROCESSOR_MODE AccessMode,OUT PVOID * Object,OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL)494 ObReferenceObjectByHandle(IN HANDLE Handle,
495 IN ACCESS_MASK DesiredAccess,
496 IN POBJECT_TYPE ObjectType,
497 IN KPROCESSOR_MODE AccessMode,
498 OUT PVOID* Object,
499 OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL)
500 {
501 PHANDLE_TABLE_ENTRY HandleEntry;
502 POBJECT_HEADER ObjectHeader;
503 ACCESS_MASK GrantedAccess;
504 ULONG Attributes;
505 PEPROCESS CurrentProcess;
506 PVOID HandleTable;
507 PETHREAD CurrentThread;
508 NTSTATUS Status;
509 PAGED_CODE();
510
511 /* Assume failure */
512 *Object = NULL;
513
514 /* Check if this is a special handle */
515 if (HandleToLong(Handle) < 0)
516 {
517 /* Check if this is the current process */
518 if (Handle == NtCurrentProcess())
519 {
520 /* Check if this is the right object type */
521 if ((ObjectType == PsProcessType) || !(ObjectType))
522 {
523 /* Get the current process and granted access */
524 CurrentProcess = PsGetCurrentProcess();
525 GrantedAccess = CurrentProcess->GrantedAccess;
526
527 /* Validate access */
528 /* ~GrantedAccess = RefusedAccess.*/
529 /* ~GrantedAccess & DesiredAccess = list of refused bits. */
530 /* !(~GrantedAccess & DesiredAccess) == TRUE means ALL requested rights are granted */
531 if ((AccessMode == KernelMode) ||
532 !(~GrantedAccess & DesiredAccess))
533 {
534 /* Check if the caller wanted handle information */
535 if (HandleInformation)
536 {
537 /* Return it */
538 HandleInformation->HandleAttributes = 0;
539 HandleInformation->GrantedAccess = GrantedAccess;
540 }
541
542 /* Reference ourselves */
543 ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentProcess);
544 InterlockedExchangeAddSizeT(&ObjectHeader->PointerCount, 1);
545
546 /* Return the pointer */
547 *Object = CurrentProcess;
548 ASSERT(*Object != NULL);
549 Status = STATUS_SUCCESS;
550 }
551 else
552 {
553 /* Access denied */
554 Status = STATUS_ACCESS_DENIED;
555 }
556 }
557 else
558 {
559 /* The caller used this special handle value with a non-process type */
560 Status = STATUS_OBJECT_TYPE_MISMATCH;
561 }
562
563 /* Return the status */
564 return Status;
565 }
566 else if (Handle == NtCurrentThread())
567 {
568 /* Check if this is the right object type */
569 if ((ObjectType == PsThreadType) || !(ObjectType))
570 {
571 /* Get the current process and granted access */
572 CurrentThread = PsGetCurrentThread();
573 GrantedAccess = CurrentThread->GrantedAccess;
574
575 /* Validate access */
576 /* ~GrantedAccess = RefusedAccess.*/
577 /* ~GrantedAccess & DesiredAccess = list of refused bits. */
578 /* !(~GrantedAccess & DesiredAccess) == TRUE means ALL requested rights are granted */
579 if ((AccessMode == KernelMode) ||
580 !(~GrantedAccess & DesiredAccess))
581 {
582 /* Check if the caller wanted handle information */
583 if (HandleInformation)
584 {
585 /* Return it */
586 HandleInformation->HandleAttributes = 0;
587 HandleInformation->GrantedAccess = GrantedAccess;
588 }
589
590 /* Reference ourselves */
591 ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentThread);
592 InterlockedExchangeAddSizeT(&ObjectHeader->PointerCount, 1);
593
594 /* Return the pointer */
595 *Object = CurrentThread;
596 ASSERT(*Object != NULL);
597 Status = STATUS_SUCCESS;
598 }
599 else
600 {
601 /* Access denied */
602 Status = STATUS_ACCESS_DENIED;
603 }
604 }
605 else
606 {
607 /* The caller used this special handle value with a non-process type */
608 Status = STATUS_OBJECT_TYPE_MISMATCH;
609 }
610
611 /* Return the status */
612 return Status;
613 }
614 else if (AccessMode == KernelMode)
615 {
616 /* Use the kernel handle table and get the actual handle value */
617 Handle = ObKernelHandleToHandle(Handle);
618 HandleTable = ObpKernelHandleTable;
619 }
620 else
621 {
622 /* Invalid access, fail */
623 return STATUS_INVALID_HANDLE;
624 }
625 }
626 else
627 {
628 /* Otherwise use this process's handle table */
629 HandleTable = PsGetCurrentProcess()->ObjectTable;
630 }
631
632 /* Enter a critical region while we touch the handle table */
633 ASSERT(HandleTable != NULL);
634 KeEnterCriticalRegion();
635
636 /* Get the handle entry */
637 HandleEntry = ExMapHandleToPointer(HandleTable, Handle);
638 if (HandleEntry)
639 {
640 /* Get the object header and validate the type*/
641 ObjectHeader = ObpGetHandleObject(HandleEntry);
642 if (!(ObjectType) || (ObjectType == ObjectHeader->Type))
643 {
644 /* Get the granted access and validate it */
645 GrantedAccess = HandleEntry->GrantedAccess;
646
647 /* Validate access */
648 /* ~GrantedAccess = RefusedAccess.*/
649 /* ~GrantedAccess & DesiredAccess = list of refused bits. */
650 /* !(~GrantedAccess & DesiredAccess) == TRUE means ALL requested rights are granted */
651 if ((AccessMode == KernelMode) ||
652 !(~GrantedAccess & DesiredAccess))
653 {
654 /* Reference the object directly since we have its header */
655 InterlockedIncrementSizeT(&ObjectHeader->PointerCount);
656
657 /* Mask out the internal attributes */
658 Attributes = HandleEntry->ObAttributes & OBJ_HANDLE_ATTRIBUTES;
659
660 /* Check if the caller wants handle information */
661 if (HandleInformation)
662 {
663 /* Fill out the information */
664 HandleInformation->HandleAttributes = Attributes;
665 HandleInformation->GrantedAccess = GrantedAccess;
666 }
667
668 /* Return the pointer */
669 *Object = &ObjectHeader->Body;
670
671 /* Unlock the handle */
672 ExUnlockHandleTableEntry(HandleTable, HandleEntry);
673 KeLeaveCriticalRegion();
674
675 /* Return success */
676 ASSERT(*Object != NULL);
677 return STATUS_SUCCESS;
678 }
679 else
680 {
681 /* Requested access failed */
682 DPRINT("Rights not granted: %x\n", ~GrantedAccess & DesiredAccess);
683 Status = STATUS_ACCESS_DENIED;
684 }
685 }
686 else
687 {
688 /* Invalid object type */
689 Status = STATUS_OBJECT_TYPE_MISMATCH;
690 }
691
692 /* Unlock the entry */
693 ExUnlockHandleTableEntry(HandleTable, HandleEntry);
694 }
695 else
696 {
697 /* Invalid handle */
698 Status = STATUS_INVALID_HANDLE;
699 }
700
701 /* Return failure status */
702 KeLeaveCriticalRegion();
703 *Object = NULL;
704 return Status;
705 }
706
707 /* EOF */
708