xref: /reactos/ntoskrnl/ps/apphelp.c (revision 09dde2cf)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * LICENSE:         BSD - See COPYING.ARM in the top level directory
4  * FILE:            ntoskrnl/ps/apphelp.c
5  * PURPOSE:         SHIM engine caching.
6  *                  This caching speeds up checks for the apphelp compatibility layer.
7  * PROGRAMMERS:     Mark Jansen
8  */
9 
10 /*
11 Useful references:
12 https://github.com/mandiant/ShimCacheParser/blob/master/ShimCacheParser.py
13 http://technet.microsoft.com/en-us/library/dd837644(v=ws.10).aspx
14 http://msdn.microsoft.com/en-us/library/bb432182(v=vs.85).aspx
15 http://www.alex-ionescu.com/?p=43
16 http://recxltd.blogspot.nl/2012/04/windows-appcompat-research-notes-part-1.html
17 http://journeyintoir.blogspot.ch/2013/12/revealing-recentfilecachebcf-file.html
18 https://dl.mandiant.com/EE/library/Whitepaper_ShimCacheParser.pdf
19 */
20 
21 /* INCLUDES ******************************************************************/
22 
23 #include <ntoskrnl.h>
24 #define NDEBUG
25 #include <debug.h>
26 
27 /* GLOBALS *******************************************************************/
28 
29 static BOOLEAN ApphelpCacheEnabled = FALSE;
30 static ERESOURCE ApphelpCacheLock;
31 static RTL_AVL_TABLE ApphelpShimCache;
32 static LIST_ENTRY ApphelpShimCacheAge;
33 
34 extern ULONG InitSafeBootMode;
35 
36 static UNICODE_STRING AppCompatCacheKey = RTL_CONSTANT_STRING(L"\\Registry\\MACHINE\\System\\CurrentControlSet\\Control\\Session Manager\\AppCompatCache");
37 static OBJECT_ATTRIBUTES AppCompatKeyAttributes = RTL_CONSTANT_OBJECT_ATTRIBUTES(&AppCompatCacheKey, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE);
38 static UNICODE_STRING AppCompatCacheValue = RTL_CONSTANT_STRING(L"AppCompatCache");
39 
40 #define EMPTY_SHIM_ENTRY    { { 0 }, { { 0 } }, 0 }
41 #define MAX_SHIM_ENTRIES    0x200
42 
43 #ifndef INVALID_HANDLE_VALUE
44 #define INVALID_HANDLE_VALUE (HANDLE)(-1)
45 #endif
46 
47 #include <pshpack1.h>
48 
49 typedef struct SHIM_PERSISTENT_CACHE_HEADER_52
50 {
51     ULONG Magic;
52     ULONG NumEntries;
53 } SHIM_PERSISTENT_CACHE_HEADER_52, *PSHIM_PERSISTENT_CACHE_HEADER_52;
54 
55 /* The data that is present in the registry (Win2k3 version) */
56 typedef struct SHIM_PERSISTENT_CACHE_ENTRY_52
57 {
58     UNICODE_STRING ImageName;
59     LARGE_INTEGER DateTime;
60     LARGE_INTEGER FileSize;
61 } SHIM_PERSISTENT_CACHE_ENTRY_52, *PSHIM_PERSISTENT_CACHE_ENTRY_52;
62 
63 #include <poppack.h>
64 
65 #define CACHE_MAGIC_NT_52 0xbadc0ffe
66 #define CACHE_HEADER_SIZE_NT_52 0x8
67 #define NT52_PERSISTENT_ENTRY_SIZE32 0x18
68 #define NT52_PERSISTENT_ENTRY_SIZE64 0x20
69 
70 //#define CACHE_MAGIC_NT_61 0xbadc0fee
71 //#define CACHE_HEADER_SIZE_NT_61 0x80
72 //#define NT61_PERSISTENT_ENTRY_SIZE32 0x20
73 //#define NT61_PERSISTENT_ENTRY_SIZE64 0x30
74 
75 #define SHIM_CACHE_MAGIC                    CACHE_MAGIC_NT_52
76 #define SHIM_CACHE_HEADER_SIZE              CACHE_HEADER_SIZE_NT_52
77 #ifdef _WIN64
78 #define SHIM_PERSISTENT_CACHE_ENTRY_SIZE    NT52_PERSISTENT_ENTRY_SIZE64
79 #else
80 #define SHIM_PERSISTENT_CACHE_ENTRY_SIZE    NT52_PERSISTENT_ENTRY_SIZE32
81 #endif
82 #define SHIM_PERSISTENT_CACHE_HEADER        SHIM_PERSISTENT_CACHE_HEADER_52
83 #define PSHIM_PERSISTENT_CACHE_HEADER      PSHIM_PERSISTENT_CACHE_HEADER_52
84 #define SHIM_PERSISTENT_CACHE_ENTRY         SHIM_PERSISTENT_CACHE_ENTRY_52
85 #define PSHIM_PERSISTENT_CACHE_ENTRY       PSHIM_PERSISTENT_CACHE_ENTRY_52
86 
87 C_ASSERT(sizeof(SHIM_PERSISTENT_CACHE_ENTRY) == SHIM_PERSISTENT_CACHE_ENTRY_SIZE);
88 C_ASSERT(sizeof(SHIM_PERSISTENT_CACHE_HEADER) == SHIM_CACHE_HEADER_SIZE);
89 
90 /* The struct we keep in memory */
91 typedef struct SHIM_CACHE_ENTRY
92 {
93     LIST_ENTRY List;
94     SHIM_PERSISTENT_CACHE_ENTRY Persistent;
95     ULONG CompatFlags;
96 } SHIM_CACHE_ENTRY, *PSHIM_CACHE_ENTRY;
97 
98 /* PRIVATE FUNCTIONS *********************************************************/
99 
100 PVOID
101 ApphelpAlloc(
102     _In_ ULONG ByteSize)
103 {
104     return ExAllocatePoolWithTag(PagedPool, ByteSize, TAG_SHIM);
105 }
106 
107 VOID
108 ApphelpFree(
109     _In_ PVOID Data)
110 {
111     ExFreePoolWithTag(Data, TAG_SHIM);
112 }
113 
114 VOID
115 ApphelpCacheAcquireLock(VOID)
116 {
117     KeEnterCriticalRegion();
118     ExAcquireResourceExclusiveLite(&ApphelpCacheLock, TRUE);
119 }
120 
121 BOOLEAN
122 ApphelpCacheTryAcquireLock(VOID)
123 {
124     KeEnterCriticalRegion();
125     if (!ExTryToAcquireResourceExclusiveLite(&ApphelpCacheLock))
126     {
127         KeLeaveCriticalRegion();
128         return FALSE;
129     }
130     return TRUE;
131 }
132 
133 VOID
134 ApphelpCacheReleaseLock(VOID)
135 {
136     ExReleaseResourceLite(&ApphelpCacheLock);
137     KeLeaveCriticalRegion();
138 }
139 
140 VOID
141 ApphelpDuplicateUnicodeString(
142     _Out_ PUNICODE_STRING Destination,
143     _In_ PCUNICODE_STRING Source)
144 {
145     Destination->Length = Source->Length;
146     if (Destination->Length)
147     {
148         Destination->MaximumLength = Destination->Length + sizeof(WCHAR);
149         Destination->Buffer = ApphelpAlloc(Destination->MaximumLength);
150         RtlCopyMemory(Destination->Buffer, Source->Buffer, Destination->Length);
151         Destination->Buffer[Destination->Length / sizeof(WCHAR)] = UNICODE_NULL;
152     }
153     else
154     {
155         Destination->MaximumLength = 0;
156         Destination->Buffer = NULL;
157     }
158 }
159 
160 VOID
161 ApphelpFreeUnicodeString(
162     _Inout_ PUNICODE_STRING String)
163 {
164     if (String->Buffer)
165     {
166         ApphelpFree(String->Buffer);
167     }
168     String->Length = 0;
169     String->MaximumLength = 0;
170     String->Buffer = NULL;
171 }
172 
173 /* Query file info from a handle, storing it in Entry */
174 NTSTATUS
175 ApphelpCacheQueryInfo(
176     _In_ HANDLE ImageHandle,
177     _Out_ PSHIM_CACHE_ENTRY Entry)
178 {
179     IO_STATUS_BLOCK IoStatusBlock;
180     FILE_BASIC_INFORMATION FileBasic;
181     FILE_STANDARD_INFORMATION FileStandard;
182     NTSTATUS Status;
183 
184     Status = ZwQueryInformationFile(ImageHandle,
185                                     &IoStatusBlock,
186                                     &FileBasic,
187                                     sizeof(FileBasic),
188                                     FileBasicInformation);
189     if (!NT_SUCCESS(Status))
190     {
191         return Status;
192     }
193 
194     Status = ZwQueryInformationFile(ImageHandle,
195                                     &IoStatusBlock,
196                                     &FileStandard,
197                                     sizeof(FileStandard),
198                                     FileStandardInformation);
199     if (NT_SUCCESS(Status))
200     {
201         Entry->Persistent.DateTime = FileBasic.LastWriteTime;
202         Entry->Persistent.FileSize = FileStandard.EndOfFile;
203     }
204     return Status;
205 }
206 
207 RTL_GENERIC_COMPARE_RESULTS
208 NTAPI
209 ApphelpShimCacheCompareRoutine(
210     _In_ struct _RTL_AVL_TABLE *Table,
211     _In_ PVOID FirstStruct,
212     _In_ PVOID SecondStruct)
213 {
214     PSHIM_CACHE_ENTRY FirstEntry = FirstStruct;
215     PSHIM_CACHE_ENTRY SecondEntry = SecondStruct;
216     LONG Result;
217 
218     Result = RtlCompareUnicodeString(&FirstEntry->Persistent.ImageName,
219                                      &SecondEntry->Persistent.ImageName,
220                                      TRUE);
221     if (Result < 0)
222     {
223         return GenericLessThan;
224     }
225     else if (Result == 0)
226     {
227         return GenericEqual;
228     }
229     return GenericGreaterThan;
230 }
231 
232 PVOID
233 NTAPI
234 ApphelpShimCacheAllocateRoutine(
235     _In_ struct _RTL_AVL_TABLE *Table,
236     _In_ CLONG ByteSize)
237 {
238     return ApphelpAlloc(ByteSize);
239 }
240 
241 VOID
242 NTAPI
243 ApphelpShimCacheFreeRoutine(
244     _In_ struct _RTL_AVL_TABLE *Table,
245     _In_ PVOID Buffer)
246 {
247     ApphelpFree(Buffer);
248 }
249 
250 NTSTATUS
251 ApphelpCacheParse(
252     _In_reads_(DataLength) PUCHAR Data,
253     _In_ ULONG DataLength)
254 {
255     PSHIM_PERSISTENT_CACHE_HEADER Header = (PSHIM_PERSISTENT_CACHE_HEADER)Data;
256     ULONG Cur;
257     ULONG NumEntries;
258     UNICODE_STRING String;
259     SHIM_CACHE_ENTRY Entry = EMPTY_SHIM_ENTRY;
260     PSHIM_CACHE_ENTRY Result;
261     PSHIM_PERSISTENT_CACHE_ENTRY Persistent;
262 
263     if (DataLength < CACHE_HEADER_SIZE_NT_52)
264     {
265         DPRINT1("SHIMS: ApphelpCacheParse not enough data for a minimal header (0x%x)\n", DataLength);
266         return STATUS_INVALID_PARAMETER;
267     }
268 
269     if (Header->Magic != SHIM_CACHE_MAGIC)
270     {
271         DPRINT1("SHIMS: ApphelpCacheParse found invalid magic (0x%x)\n", Header->Magic);
272         return STATUS_INVALID_PARAMETER;
273     }
274 
275     NumEntries = Header->NumEntries;
276     DPRINT("SHIMS: ApphelpCacheParse walking %d entries\n", NumEntries);
277     for (Cur = 0; Cur < NumEntries; ++Cur)
278     {
279         Persistent = (PSHIM_PERSISTENT_CACHE_ENTRY)(Data + SHIM_CACHE_HEADER_SIZE +
280                                                     (Cur * SHIM_PERSISTENT_CACHE_ENTRY_SIZE));
281         /* The entry in the Persistent storage is not really a UNICODE_STRING,
282             so we have to convert the offset into a real pointer before using it. */
283         String.Length = Persistent->ImageName.Length;
284         String.MaximumLength = Persistent->ImageName.MaximumLength;
285         String.Buffer = (PWCHAR)((ULONG_PTR)Persistent->ImageName.Buffer + Data);
286 
287         /* Now we copy all data to a local buffer, that can be safely duplicated by RtlInsert */
288         Entry.Persistent = *Persistent;
289         ApphelpDuplicateUnicodeString(&Entry.Persistent.ImageName, &String);
290         Result = RtlInsertElementGenericTableAvl(&ApphelpShimCache,
291                                                  &Entry,
292                                                  sizeof(Entry),
293                                                  NULL);
294         if (!Result)
295         {
296             DPRINT1("SHIMS: ApphelpCacheParse insert failed\n");
297             ApphelpFreeUnicodeString(&Entry.Persistent.ImageName);
298             return STATUS_INVALID_PARAMETER;
299         }
300         InsertTailList(&ApphelpShimCacheAge, &Result->List);
301     }
302     return STATUS_SUCCESS;
303 }
304 
305 BOOLEAN
306 ApphelpCacheRead(VOID)
307 {
308     HANDLE KeyHandle;
309     NTSTATUS Status;
310     KEY_VALUE_PARTIAL_INFORMATION KeyValueObject;
311     PKEY_VALUE_PARTIAL_INFORMATION KeyValueInformation = &KeyValueObject;
312     ULONG KeyInfoSize, ResultSize;
313 
314     Status = ZwOpenKey(&KeyHandle, KEY_QUERY_VALUE, &AppCompatKeyAttributes);
315     if (!NT_SUCCESS(Status))
316     {
317         DPRINT1("SHIMS: ApphelpCacheRead could not even open Session Manager\\AppCompatCache (0x%x)\n", Status);
318         return FALSE;
319     }
320 
321     Status = ZwQueryValueKey(KeyHandle,
322                              &AppCompatCacheValue,
323                              KeyValuePartialInformation,
324                              KeyValueInformation,
325                              sizeof(KeyValueObject),
326                              &ResultSize);
327     if (Status == STATUS_BUFFER_OVERFLOW)
328     {
329         KeyInfoSize = sizeof(KEY_VALUE_PARTIAL_INFORMATION) + KeyValueInformation->DataLength;
330         KeyValueInformation = ApphelpAlloc(KeyInfoSize);
331         if (KeyValueInformation != NULL)
332         {
333             Status = ZwQueryValueKey(KeyHandle,
334                                      &AppCompatCacheValue,
335                                      KeyValuePartialInformation,
336                                      KeyValueInformation,
337                                      KeyInfoSize,
338                                      &ResultSize);
339         }
340     }
341 
342     if (NT_SUCCESS(Status) && KeyValueInformation->Type == REG_BINARY)
343     {
344         Status = ApphelpCacheParse(KeyValueInformation->Data,
345                                    KeyValueInformation->DataLength);
346     }
347     else
348     {
349         DPRINT1("SHIMS: ApphelpCacheRead not loaded from registry (0x%x)\n", Status);
350     }
351 
352     if (KeyValueInformation != &KeyValueObject && KeyValueInformation != NULL)
353     {
354         ApphelpFree(KeyValueInformation);
355     }
356 
357     ZwClose(KeyHandle);
358     return NT_SUCCESS(Status);
359 }
360 
361 BOOLEAN
362 ApphelpCacheWrite(VOID)
363 {
364     ULONG Length = SHIM_CACHE_HEADER_SIZE;
365     ULONG NumEntries = 0;
366     PLIST_ENTRY ListEntry;
367     PUCHAR Buffer, BufferNamePos;
368     PSHIM_PERSISTENT_CACHE_HEADER Header;
369     PSHIM_PERSISTENT_CACHE_ENTRY WriteEntry;
370     HANDLE KeyHandle;
371     NTSTATUS Status;
372 
373     /* First we have to calculate the required size. */
374     ApphelpCacheAcquireLock();
375     ListEntry = ApphelpShimCacheAge.Flink;
376     while (ListEntry != &ApphelpShimCacheAge)
377     {
378         PSHIM_CACHE_ENTRY Entry = CONTAINING_RECORD(ListEntry, SHIM_CACHE_ENTRY, List);
379         Length += SHIM_PERSISTENT_CACHE_ENTRY_SIZE;
380         Length += Entry->Persistent.ImageName.MaximumLength;
381         ++NumEntries;
382         ListEntry = ListEntry->Flink;
383     }
384     DPRINT("SHIMS: ApphelpCacheWrite, %d Entries, total size: %d\n", NumEntries, Length);
385     Length = ROUND_UP(Length, sizeof(ULONGLONG));
386     DPRINT("SHIMS: ApphelpCacheWrite, Rounded to: %d\n", Length);
387 
388     /* Now we allocate and prepare some helpers */
389     Buffer = ApphelpAlloc(Length);
390     BufferNamePos = Buffer + Length;
391     Header = (PSHIM_PERSISTENT_CACHE_HEADER)Buffer;
392     WriteEntry = (PSHIM_PERSISTENT_CACHE_ENTRY)(Buffer + SHIM_CACHE_HEADER_SIZE);
393 
394     Header->Magic = SHIM_CACHE_MAGIC;
395     Header->NumEntries = NumEntries;
396 
397     ListEntry = ApphelpShimCacheAge.Flink;
398     while (ListEntry != &ApphelpShimCacheAge)
399     {
400         PSHIM_CACHE_ENTRY Entry = CONTAINING_RECORD(ListEntry, SHIM_CACHE_ENTRY, List);
401         USHORT ImageNameLen = Entry->Persistent.ImageName.MaximumLength;
402         /* Copy the Persistent structure over */
403         *WriteEntry = Entry->Persistent;
404         BufferNamePos -= ImageNameLen;
405         /* Copy the image name over */
406         RtlCopyMemory(BufferNamePos, Entry->Persistent.ImageName.Buffer, ImageNameLen);
407         /* Fix the Persistent structure, so that Buffer is once again an offset */
408         WriteEntry->ImageName.Buffer = (PWCH)(BufferNamePos - Buffer);
409 
410         ++WriteEntry;
411         ListEntry = ListEntry->Flink;
412     }
413     ApphelpCacheReleaseLock();
414 
415     Status = ZwOpenKey(&KeyHandle, KEY_SET_VALUE, &AppCompatKeyAttributes);
416     if (NT_SUCCESS(Status))
417     {
418         Status = ZwSetValueKey(KeyHandle,
419                                &AppCompatCacheValue,
420                                0,
421                                REG_BINARY,
422                                Buffer,
423                                Length);
424         ZwClose(KeyHandle);
425     }
426     else
427     {
428         DPRINT1("SHIMS: ApphelpCacheWrite could not even open Session Manager\\AppCompatCache (0x%x)\n", Status);
429     }
430 
431     ApphelpFree(Buffer);
432     return NT_SUCCESS(Status);
433 }
434 
435 
436 CODE_SEG("INIT")
437 NTSTATUS
438 NTAPI
439 ApphelpCacheInitialize(VOID)
440 {
441     DPRINT("SHIMS: ApphelpCacheInitialize\n");
442     /* If we are booting in safemode we do not want to use the apphelp cache */
443     if (InitSafeBootMode)
444     {
445         DPRINT1("SHIMS: Safe mode detected, disabling cache.\n");
446         ApphelpCacheEnabled = FALSE;
447     }
448     else
449     {
450         ExInitializeResourceLite(&ApphelpCacheLock);
451         RtlInitializeGenericTableAvl(&ApphelpShimCache,
452                                      ApphelpShimCacheCompareRoutine,
453                                      ApphelpShimCacheAllocateRoutine,
454                                      ApphelpShimCacheFreeRoutine,
455                                      NULL);
456         InitializeListHead(&ApphelpShimCacheAge);
457         ApphelpCacheEnabled = ApphelpCacheRead();
458     }
459     DPRINT("SHIMS: ApphelpCacheInitialize: %d\n", ApphelpCacheEnabled);
460     return STATUS_SUCCESS;
461 }
462 
463 VOID
464 NTAPI
465 ApphelpCacheShutdown(VOID)
466 {
467     if (ApphelpCacheEnabled)
468     {
469         ApphelpCacheWrite();
470     }
471 }
472 
473 NTSTATUS
474 ApphelpValidateData(
475     _In_opt_ PAPPHELP_CACHE_SERVICE_LOOKUP ServiceData,
476     _Out_ PUNICODE_STRING ImageName,
477     _Out_ PHANDLE ImageHandle)
478 {
479     NTSTATUS Status = STATUS_INVALID_PARAMETER;
480 
481     if (ServiceData)
482     {
483         UNICODE_STRING LocalImageName;
484         _SEH2_TRY
485         {
486             ProbeForRead(ServiceData,
487                          sizeof(APPHELP_CACHE_SERVICE_LOOKUP),
488                          sizeof(ULONG));
489             LocalImageName = ServiceData->ImageName;
490             *ImageHandle = ServiceData->ImageHandle;
491             if (LocalImageName.Length && LocalImageName.Buffer)
492             {
493                 ProbeForRead(LocalImageName.Buffer,
494                              LocalImageName.Length * sizeof(WCHAR),
495                              1);
496                 ApphelpDuplicateUnicodeString(ImageName, &LocalImageName);
497                 Status = STATUS_SUCCESS;
498             }
499         }
500         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
501         {
502             _SEH2_YIELD(return _SEH2_GetExceptionCode());
503         }
504         _SEH2_END;
505     }
506     if (!NT_SUCCESS(Status))
507     {
508         DPRINT1("SHIMS: ApphelpValidateData: invalid data passed\n");
509     }
510     return Status;
511 }
512 
513 NTSTATUS
514 ApphelpCacheRemoveEntryNolock(
515     _In_ PSHIM_CACHE_ENTRY Entry)
516 {
517     if (Entry)
518     {
519         PWSTR Buffer = Entry->Persistent.ImageName.Buffer;
520         RemoveEntryList(&Entry->List);
521         if (RtlDeleteElementGenericTableAvl(&ApphelpShimCache, Entry))
522         {
523             ApphelpFree(Buffer);
524         }
525         return STATUS_SUCCESS;
526     }
527     return STATUS_NOT_FOUND;
528 }
529 
530 NTSTATUS
531 ApphelpCacheLookupEntry(
532     _In_ PUNICODE_STRING ImageName,
533     _In_ HANDLE ImageHandle)
534 {
535     NTSTATUS Status = STATUS_NOT_FOUND;
536     SHIM_CACHE_ENTRY Lookup = EMPTY_SHIM_ENTRY;
537     PSHIM_CACHE_ENTRY Entry;
538 
539     if (!ApphelpCacheTryAcquireLock())
540     {
541         return Status;
542     }
543 
544     Lookup.Persistent.ImageName = *ImageName;
545     Entry = RtlLookupElementGenericTableAvl(&ApphelpShimCache, &Lookup);
546     if (Entry == NULL)
547     {
548         DPRINT("SHIMS: ApphelpCacheLookupEntry: could not find %wZ\n", ImageName);
549         goto Cleanup;
550     }
551 
552     DPRINT("SHIMS: ApphelpCacheLookupEntry: found %wZ\n", ImageName);
553     if (ImageHandle == INVALID_HANDLE_VALUE)
554     {
555         DPRINT("SHIMS: ApphelpCacheLookupEntry: ok\n");
556         /* just return if we know it, do not query file info */
557         Status = STATUS_SUCCESS;
558     }
559     else
560     {
561         Status = ApphelpCacheQueryInfo(ImageHandle, &Lookup);
562         if (NT_SUCCESS(Status) &&
563             Lookup.Persistent.DateTime.QuadPart == Entry->Persistent.DateTime.QuadPart &&
564             Lookup.Persistent.FileSize.QuadPart == Entry->Persistent.FileSize.QuadPart)
565         {
566             DPRINT("SHIMS: ApphelpCacheLookupEntry: found & validated\n");
567             Status = STATUS_SUCCESS;
568             /* move it to the front to keep it alive */
569             RemoveEntryList(&Entry->List);
570             InsertHeadList(&ApphelpShimCacheAge, &Entry->List);
571         }
572         else
573         {
574             DPRINT1("SHIMS: ApphelpCacheLookupEntry: file info mismatch (%lx)\n", Status);
575             Status = STATUS_NOT_FOUND;
576             /* Could not read file info, or it did not match, drop it from the cache */
577             ApphelpCacheRemoveEntryNolock(Entry);
578         }
579     }
580 
581 Cleanup:
582     ApphelpCacheReleaseLock();
583     return Status;
584 }
585 
586 NTSTATUS
587 ApphelpCacheRemoveEntry(
588     _In_ PUNICODE_STRING ImageName)
589 {
590     PSHIM_CACHE_ENTRY Entry;
591     NTSTATUS Status;
592 
593     ApphelpCacheAcquireLock();
594     Entry = RtlLookupElementGenericTableAvl(&ApphelpShimCache, ImageName);
595     Status = ApphelpCacheRemoveEntryNolock(Entry);
596     ApphelpCacheReleaseLock();
597     return Status;
598 }
599 
600 /* Validate that we are either called from r0, or from a service-like context */
601 NTSTATUS
602 ApphelpCacheAccessCheck(VOID)
603 {
604     if (ExGetPreviousMode() != KernelMode)
605     {
606         if (!SeSinglePrivilegeCheck(SeTcbPrivilege, UserMode))
607         {
608             DPRINT1("SHIMS: ApphelpCacheAccessCheck failed\n");
609             return STATUS_ACCESS_DENIED;
610         }
611     }
612     return STATUS_SUCCESS;
613 }
614 
615 NTSTATUS
616 ApphelpCacheUpdateEntry(
617     _In_ PUNICODE_STRING ImageName,
618     _In_ HANDLE ImageHandle)
619 {
620     NTSTATUS Status = STATUS_SUCCESS;
621     SHIM_CACHE_ENTRY Entry = EMPTY_SHIM_ENTRY;
622     PSHIM_CACHE_ENTRY Lookup;
623     PVOID NodeOrParent;
624     TABLE_SEARCH_RESULT SearchResult;
625 
626     ApphelpCacheAcquireLock();
627 
628     /* If we got a file handle, query it for info */
629     if (ImageHandle != INVALID_HANDLE_VALUE)
630     {
631         Status = ApphelpCacheQueryInfo(ImageHandle, &Entry);
632         if (!NT_SUCCESS(Status))
633         {
634             goto Cleanup;
635         }
636     }
637 
638     /* Use ImageName for the lookup, don't actually duplicate it */
639     Entry.Persistent.ImageName = *ImageName;
640     Lookup = RtlLookupElementGenericTableFullAvl(&ApphelpShimCache,
641                                                  &Entry,
642                                                  &NodeOrParent, &SearchResult);
643     if (Lookup)
644     {
645         DPRINT("SHIMS: ApphelpCacheUpdateEntry: Entry already exists, reusing it\n");
646         /* Unlink the found item, so we can put it back at the front,
647             and copy the earlier obtained file info*/
648         RemoveEntryList(&Lookup->List);
649         Lookup->Persistent.DateTime = Entry.Persistent.DateTime;
650         Lookup->Persistent.FileSize = Entry.Persistent.FileSize;
651     }
652     else
653     {
654         DPRINT("SHIMS: ApphelpCacheUpdateEntry: Inserting new Entry\n");
655         /* Insert a new entry, with its own copy of the ImageName */
656         ApphelpDuplicateUnicodeString(&Entry.Persistent.ImageName, ImageName);
657         Lookup = RtlInsertElementGenericTableFullAvl(&ApphelpShimCache,
658                                                      &Entry,
659                                                      sizeof(Entry),
660                                                      0,
661                                                      NodeOrParent,
662                                                      SearchResult);
663         if (!Lookup)
664         {
665             ApphelpFreeUnicodeString(&Entry.Persistent.ImageName);
666             Status = STATUS_NO_MEMORY;
667         }
668     }
669     if (Lookup)
670     {
671         /* Either we re-used an existing item, or we inserted a new one, keep it alive */
672         InsertHeadList(&ApphelpShimCacheAge, &Lookup->List);
673         if (RtlNumberGenericTableElementsAvl(&ApphelpShimCache) > MAX_SHIM_ENTRIES)
674         {
675             PSHIM_CACHE_ENTRY Remove;
676             DPRINT1("SHIMS: ApphelpCacheUpdateEntry: Cache growing too big, dropping oldest item\n");
677             Remove = CONTAINING_RECORD(ApphelpShimCacheAge.Blink, SHIM_CACHE_ENTRY, List);
678             Status = ApphelpCacheRemoveEntryNolock(Remove);
679         }
680     }
681 
682 Cleanup:
683     ApphelpCacheReleaseLock();
684     return Status;
685 }
686 
687 NTSTATUS
688 ApphelpCacheFlush(VOID)
689 {
690     PVOID p;
691 
692     DPRINT1("SHIMS: ApphelpCacheFlush\n");
693     ApphelpCacheAcquireLock();
694     while ((p = RtlEnumerateGenericTableAvl(&ApphelpShimCache, TRUE)))
695     {
696         ApphelpCacheRemoveEntryNolock((PSHIM_CACHE_ENTRY)p);
697     }
698     ApphelpCacheReleaseLock();
699     return STATUS_SUCCESS;
700 }
701 
702 NTSTATUS
703 ApphelpCacheDump(VOID)
704 {
705     PLIST_ENTRY ListEntry;
706     PSHIM_CACHE_ENTRY Entry;
707 
708     DPRINT1("SHIMS: NtApphelpCacheControl( Dumping entries, newest to oldest )\n");
709     ApphelpCacheAcquireLock();
710     ListEntry = ApphelpShimCacheAge.Flink;
711     while (ListEntry != &ApphelpShimCacheAge)
712     {
713         Entry = CONTAINING_RECORD(ListEntry, SHIM_CACHE_ENTRY, List);
714         DPRINT1("Entry: %wZ\n", &Entry->Persistent.ImageName);
715         DPRINT1("DateTime: 0x%I64x\n", Entry->Persistent.DateTime.QuadPart);
716         DPRINT1("FileSize: 0x%I64x\n", Entry->Persistent.FileSize.QuadPart);
717         DPRINT1("Flags: 0x%x\n", Entry->CompatFlags);
718         ListEntry = ListEntry->Flink;
719     }
720     ApphelpCacheReleaseLock();
721     return STATUS_SUCCESS;
722 }
723 
724 /* PUBLIC FUNCTIONS **********************************************************/
725 
726 NTSTATUS
727 NTAPI
728 NtApphelpCacheControl(
729     _In_ APPHELPCACHESERVICECLASS Service,
730     _In_opt_ PAPPHELP_CACHE_SERVICE_LOOKUP ServiceData)
731 {
732     NTSTATUS Status = STATUS_INVALID_PARAMETER;
733     UNICODE_STRING ImageName = { 0 };
734     HANDLE Handle = INVALID_HANDLE_VALUE;
735 
736     if (!ApphelpCacheEnabled)
737     {
738         DPRINT1("NtApphelpCacheControl: ApphelpCacheEnabled == 0\n");
739         return Status;
740     }
741     switch (Service)
742     {
743         case ApphelpCacheServiceLookup:
744             DPRINT("SHIMS: NtApphelpCacheControl( ApphelpCacheServiceLookup )\n");
745             Status = ApphelpValidateData(ServiceData, &ImageName, &Handle);
746             if (NT_SUCCESS(Status))
747                 Status = ApphelpCacheLookupEntry(&ImageName, Handle);
748             break;
749         case ApphelpCacheServiceRemove:
750             DPRINT("SHIMS: NtApphelpCacheControl( ApphelpCacheServiceRemove )\n");
751             Status = ApphelpValidateData(ServiceData, &ImageName, &Handle);
752             if (NT_SUCCESS(Status))
753                 Status = ApphelpCacheRemoveEntry(&ImageName);
754             break;
755         case ApphelpCacheServiceUpdate:
756             DPRINT("SHIMS: NtApphelpCacheControl( ApphelpCacheServiceUpdate )\n");
757             Status = ApphelpCacheAccessCheck();
758             if (NT_SUCCESS(Status))
759             {
760                 Status = ApphelpValidateData(ServiceData, &ImageName, &Handle);
761                 if (NT_SUCCESS(Status))
762                     Status = ApphelpCacheUpdateEntry(&ImageName, Handle);
763             }
764             break;
765         case ApphelpCacheServiceFlush:
766             /* FIXME: Check for admin or system here. */
767             Status = ApphelpCacheFlush();
768             break;
769         case ApphelpCacheServiceDump:
770             Status = ApphelpCacheDump();
771             break;
772         case ApphelpDBGReadRegistry:
773             DPRINT1("SHIMS: NtApphelpCacheControl( ApphelpDBGReadRegistry ): flushing cache.\n");
774             ApphelpCacheFlush();
775             DPRINT1("SHIMS: NtApphelpCacheControl( ApphelpDBGReadRegistry ): reading cache.\n");
776             Status = ApphelpCacheRead() ? STATUS_SUCCESS : STATUS_NOT_FOUND;
777             break;
778         case ApphelpDBGWriteRegistry:
779             DPRINT1("SHIMS: NtApphelpCacheControl( ApphelpDBGWriteRegistry ): writing cache.\n");
780             Status = ApphelpCacheWrite() ? STATUS_SUCCESS : STATUS_NOT_FOUND;
781             break;
782         default:
783             DPRINT1("SHIMS: NtApphelpCacheControl( Invalid service requested )\n");
784             break;
785     }
786     if (ImageName.Buffer)
787     {
788         ApphelpFreeUnicodeString(&ImageName);
789     }
790     return Status;
791 }
792 
793