Bitdefender Hypervisor Memory Introspection
integrity.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "integrity.h"
6 #include "crc32.h"
7 #include "gpacache.h"
8 #include "guests.h"
9 
34 
35 
39 static LIST_HEAD gIntegrityRegions = LIST_HEAD_INIT(gIntegrityRegions);
40 
44 #define for_each_region(_var_name) list_for_each(gIntegrityRegions, INTEGRITY_REGION, _var_name)
45 
46 
47 static BOOLEAN
49  _In_ INTEGRITY_REGION *Descriptor
50  )
68 {
69  for_each_region(pIntRegion)
70  {
71  if ((Descriptor->Gva >= pIntRegion->Gva &&
72  Descriptor->Gva < pIntRegion->Gva + pIntRegion->Length) ||
73  (Descriptor->Gva + Descriptor->Length > pIntRegion->Gva &&
74  Descriptor->Gva + Descriptor->Length <= pIntRegion->Gva + pIntRegion->Length) ||
75  (pIntRegion->Gva >= Descriptor->Gva &&
76  pIntRegion->Gva < Descriptor->Gva + Descriptor->Length) ||
77  (pIntRegion->Gva + pIntRegion->Length > Descriptor->Gva &&
78  pIntRegion->Gva + pIntRegion->Length < Descriptor->Gva + Descriptor->Length))
79  {
80  WARNING("[WARNING] Found integrity regions overlapped @ %llx (0x%08X) - @ %llx (0x%08X)",
81  pIntRegion->Gva, pIntRegion->Length, Descriptor->Gva, Descriptor->Length);
82  return TRUE;
83  }
84  }
85 
86  return FALSE;
87 }
88 
89 
92  _In_ QWORD VirtualAddress,
93  _In_ DWORD Length,
95  _In_opt_ void *Context,
97  _In_ BOOLEAN CopyContent,
98  _Out_ void **Descriptor
99  )
134 {
135  INTSTATUS status;
136  INTEGRITY_REGION *pIntegrityRegion;
137  BYTE *pOriginalContent = NULL;
138  DWORD left = Length;
139  DWORD crc32 = INITIAL_CRC_VALUE;
140  QWORD gva = VirtualAddress;
141 
142  if (0 == Length)
143  {
145  }
146 
148  !IS_KERNEL_POINTER_WIN(gGuest.Guest64, VirtualAddress))
149  {
151  }
152 
153  if (gGuest.OSType == introGuestLinux &&
154  !IS_KERNEL_POINTER_LIX(VirtualAddress))
155  {
157  }
158 
159  pIntegrityRegion = HpAllocWithTag(sizeof(*pIntegrityRegion), IC_TAG_ITGR);
160  if (NULL == pIntegrityRegion)
161  {
163  }
164 
165  if (CopyContent)
166  {
167  pIntegrityRegion->OriginalContent = HpAllocWithTag(Length, IC_TAG_ITGR);
168  if (NULL == pIntegrityRegion->OriginalContent)
169  {
170  HpFreeAndNullWithTag(&pIntegrityRegion, IC_TAG_ITGR);
171 
173  }
174 
175  pOriginalContent = pIntegrityRegion->OriginalContent;
176  }
177 
178  do
179  {
180  void *p;
181  DWORD size = MIN(left, PAGE_REMAINING(gva));
182 
183  // SystemCr3 is OK, since we only support integrity regions for kernel memory only.
184  status = IntVirtMemMap(gva, size, gGuest.Mm.SystemCr3, 0, &p);
185  if (!INT_SUCCESS(status))
186  {
187  ERROR("[ERROR] Failed mapping GVA 0x%016llx to host: 0x%08x\n", VirtualAddress, status);
188  goto cleanup_and_leave;
189  }
190 
191  crc32 = Crc32ComputeFast(p, size, crc32);
192 
193  if (pOriginalContent)
194  {
195  memcpy(pOriginalContent, p, size);
196  pOriginalContent += size;
197  }
198 
199  IntVirtMemUnmap(&p);
200 
201  // The next one will be page-aligned
202  gva = gva + size;
203 
204  left -= size;
205  } while (gva < VirtualAddress + Length);
206 
207  pIntegrityRegion->Gva = VirtualAddress;
208  pIntegrityRegion->Length = Length;
209  pIntegrityRegion->Type = Type;
210  pIntegrityRegion->OriginalHash = crc32;
211  pIntegrityRegion->ViolationCount = 0;
212  pIntegrityRegion->Callback = Callback;
213  pIntegrityRegion->Context = Context;
214  pIntegrityRegion->Deleted = FALSE;
215 
216  IntIntegrityIsOverlappedRegions(pIntegrityRegion);
217 
218  InsertTailList(&gIntegrityRegions, &pIntegrityRegion->Link);
219 
220  TRACE("[INFO] Add integrity region @ %llx (%d).\n", pIntegrityRegion->Gva, pIntegrityRegion->Length);
221 
222  *Descriptor = pIntegrityRegion;
223 
224  status = INT_STATUS_SUCCESS;
225 
226 cleanup_and_leave:
227  if (!INT_SUCCESS(status))
228  {
229  if (NULL != pIntegrityRegion->OriginalContent)
230  {
231  HpFreeAndNullWithTag(&pIntegrityRegion->OriginalContent, IC_TAG_ITGR);
232  }
233 
234  HpFreeAndNullWithTag(&pIntegrityRegion, IC_TAG_ITGR);
235  }
236 
237  return status;
238 }
239 
240 
241 INTSTATUS
243  _In_ INTEGRITY_REGION *IntegrityRegion
244  )
267 {
268  QWORD gva = IntegrityRegion->Gva;
269  DWORD left = IntegrityRegion->Length;
270  DWORD crc32 = INITIAL_CRC_VALUE;
271  BYTE *pOriginalContent = IntegrityRegion->OriginalContent;
272  INTSTATUS status = INT_STATUS_SUCCESS;
273 
274  do
275  {
276  DWORD size = MIN(left, PAGE_REMAINING(gva));
277  void *p;
278 
279  // SystemCr3 is ok, since we support integrity regions for kernel memory only.
280  status = IntVirtMemMap(gva, size, gGuest.Mm.SystemCr3, 0, &p);
281  if (!INT_SUCCESS(status))
282  {
283  ERROR("[ERROR] Failed mapping GVA 0x%016llx to host: 0x%08x\n", IntegrityRegion->Gva, status);
284  goto cleanup_and_exit;
285  }
286 
287  crc32 = Crc32ComputeFast(p, size, crc32);
288 
289  if (NULL != pOriginalContent)
290  {
291  memcpy(pOriginalContent, p, size);
292  pOriginalContent += size;
293  }
294 
295  IntVirtMemUnmap(&p);
296 
297  // the next one will be page-aligned
298  gva = gva + size;
299  left -= size;
300  } while (gva < IntegrityRegion->Gva + IntegrityRegion->Length);
301 
302  IntegrityRegion->OriginalHash = crc32;
303 
304  status = INT_STATUS_SUCCESS;
305 
306 cleanup_and_exit:
307 
308  return status;
309 }
310 
311 
312 INTSTATUS
314  _In_ void *Descriptor
315  )
329 {
330  INTEGRITY_REGION *pIntegrityRegion = Descriptor;
331 
332  TRACE("[INFO] Remove integrity region @ %llx (%d).", pIntegrityRegion->Gva, pIntegrityRegion->Length);
333 
334  RemoveEntryList(&pIntegrityRegion->Link);
335 
336  if (NULL != pIntegrityRegion->OriginalContent)
337  {
338  HpFreeAndNullWithTag(&pIntegrityRegion->OriginalContent, IC_TAG_ITGR);
339  }
340 
341  HpFreeAndNullWithTag(&pIntegrityRegion, IC_TAG_ITGR);
342 
343  return INT_STATUS_SUCCESS;
344 }
345 
346 
347 INTSTATUS
349  _In_ void *Descriptor
350  )
364 {
365  INTEGRITY_REGION *pIntegrityRegion = Descriptor;
366 
367  TRACE("[INFO] Deleting integrity region @ %llx (%d) after calling callbacks.", pIntegrityRegion->Gva,
368  pIntegrityRegion->Length);
369 
370  pIntegrityRegion->Deleted = TRUE;
371 
372  return INT_STATUS_SUCCESS;
373 }
374 
375 
378  void
379  )
393 {
394  // If introspection is disabled we skip all checks
396  {
397  return INT_STATUS_SUCCESS;
398  }
399 
400  for_each_region(pIntRegion)
401  {
402  QWORD gva = pIntRegion->Gva;
403  DWORD left = pIntRegion->Length;
404  BOOLEAN skipThis = FALSE;
405  DWORD crc32 = INITIAL_CRC_VALUE;
406  INTSTATUS status;
407 
408  if (pIntRegion->Deleted)
409  {
410  continue;
411  }
412 
413  do
414  {
415  BYTE *p;
416  QWORD gpa;
417  DWORD size = MIN(left, PAGE_REMAINING(gva));
418 
419  status = IntTranslateVirtualAddress(gva & PAGE_MASK, gGuest.Mm.SystemCr3, &gpa);
420  if (!INT_SUCCESS(status))
421  {
422  skipThis = TRUE;
423  break;
424  }
425 
426  status = IntGpaCacheFindAndAdd(gGuest.GpaCache, gpa, &p);
427  if (!INT_SUCCESS(status))
428  {
429  // Do not show error messages since at sleep/hibernate the pages may not be present!
430  // We could hook the pt and see when the pages swap-out but the performance impact is too visible
431  skipThis = TRUE;
432  break;
433  }
434 
435  p += gva & PAGE_OFFSET;
436  crc32 = Crc32ComputeFast(p, size, crc32);
437  p -= gva & PAGE_OFFSET;
438 
440 
441  // the next one will be page-aligned
442  gva += size;
443 
444  left -= size;
445  } while (gva < pIntRegion->Gva + pIntRegion->Length);
446 
447  if (skipThis)
448  {
449  continue;
450  }
451 
452  if (crc32 != pIntRegion->OriginalHash)
453  {
454  pIntRegion->ViolationCount++;
455 
456  if (NULL != pIntRegion->Callback)
457  {
458  // From here on, we can safely use any CPU state/virtual mem map functions/etc.
459  status = pIntRegion->Callback(pIntRegion);
460  if (!INT_SUCCESS(status))
461  {
462  ERROR("[ERROR] Integrity violation callback failed with status: 0x%08x\n", status);
463  }
464  }
465  else if (pIntRegion->ModifiedHash != crc32)
466  {
467  pIntRegion->ModifiedHash = crc32;
468  }
469  }
470  }
471 
472  // Finally remove the regions that were deleted during calling the integrity callbacks
473  for_each_region(pIntRegion)
474  {
475  if (pIntRegion->Deleted)
476  {
477  IntIntegrityRemoveRegion(pIntRegion);
478  }
479  }
480 
481  return INT_STATUS_SUCCESS;
482 }
483 
484 
485 void
487  void
488  )
492 {
493  for_each_region(pIntRegion)
494  {
495  LOG("Gva: 0x%016llx, Length: %d, OriginalContent: %p, Type: %d, OriginalHash: 0x%08x\n",
496  pIntRegion->Gva, pIntRegion->Length, pIntRegion->OriginalContent, pIntRegion->Type,
497  pIntRegion->OriginalHash);
498  }
499 }
500 
501 
502 INTSTATUS
504  void
505  )
517 {
518  for_each_region(pIntRegion)
519  {
520  LOG("[ERROR] There should be no regions remaining... Got one on %llx!\n", pIntRegion->Gva);
521 
522  IntIntegrityRemoveRegion(pIntRegion);
523  }
524 
525  return INT_STATUS_SUCCESS;
526 }
#define _In_opt_
Definition: intro_sal.h:16
#define _Out_
Definition: intro_sal.h:22
_Bool BOOLEAN
Definition: intro_types.h:58
INTSTATUS IntVirtMemUnmap(void **HostPtr)
Unmaps a memory range previously mapped with IntVirtMemMap.
Definition: introcore.c:2234
INTSTATUS IntIntegrityDeleteRegion(void *Descriptor)
Marks the given integrity region for deletion. It will be removed after calling all the integrity cal...
Definition: integrity.c:348
uint8_t BYTE
Definition: intro_types.h:47
#define for_each_region(_var_name)
Useful macro for iterating through the list of INTEGRITY_REGION structures.
Definition: integrity.c:44
#define _In_
Definition: intro_sal.h:21
INTSTATUS IntIntegrityAddRegion(QWORD VirtualAddress, DWORD Length, INTRO_OBJECT_TYPE Type, void *Context, PFUNC_IntegrityViolationCallback Callback, BOOLEAN CopyContent, void **Descriptor)
Creates an INTEGRITY_REGION object and adds it to the gIntegrityRegions list.
Definition: integrity.c:91
QWORD SystemCr3
The Cr3 used to map the kernel.
Definition: guests.h:211
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
#define PAGE_REMAINING(addr)
Definition: pgtable.h:163
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
void * Context
User supplied context, see IntIntegrityAddRegion for an example.
Definition: integrity.h:41
BOOLEAN ProtectionActivated
Definition: guests.h:297
#define PAGE_OFFSET
Definition: pgtable.h:32
PFUNC_IntegrityViolationCallback Callback
The callback to be called when a violation occurs.
Definition: integrity.h:45
TIMER_FRIENDLY INTSTATUS IntIntegrityCheckAll(void)
The function which is called once every second and checks all the integrity regions.
Definition: integrity.c:377
#define ERROR(fmt,...)
Definition: glue.h:62
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
int INTSTATUS
The status data type.
Definition: introstatus.h:24
INTSTATUS(* PFUNC_IntegrityViolationCallback)(void *IntegrityRegion)
Integrity violation callback.
Definition: integrity.h:20
INTRO_GUEST_TYPE OSType
The type of the guest.
Definition: guests.h:278
#define MIN(a, b)
Definition: introdefs.h:146
LIST_ENTRY Link
Link to the next integrity region.
Definition: integrity.h:30
DWORD ViolationCount
The number of detected modifications on the given region.
Definition: integrity.h:37
#define LOG(fmt,...)
Definition: glue.h:61
static BOOLEAN IntIntegrityIsOverlappedRegions(INTEGRITY_REGION *Descriptor)
Checks if an integrity region is overlapped with any of the integrity regions already in the list...
Definition: integrity.c:48
INTRO_OBJECT_TYPE Type
The associated INTRO_OBJECT_TYPE with the protected region.
Definition: integrity.h:39
INTSTATUS IntGpaCacheRelease(PGPA_CACHE Cache, QWORD Gpa)
Release a previously used cached entry.
Definition: gpacache.c:678
enum _INTRO_OBJECT_TYPE INTRO_OBJECT_TYPE
The type of the object protected by an EPT hook.
#define INITIAL_CRC_VALUE
Definition: introdefs.h:221
#define IS_KERNEL_POINTER_LIX(p)
Definition: lixguest.h:11
DWORD OriginalHash
Definition: integrity.h:33
BOOLEAN Deleted
Set TRUE for postpone deleting of integrity regions (e.g. deleting from callback) ...
Definition: integrity.h:47
static BOOLEAN RemoveEntryList(LIST_ENTRY *Entry)
Definition: introlists.h:87
BOOLEAN Guest64
True if this is a 64-bit guest, False if it is a 32-bit guest.
Definition: guests.h:290
unsigned long long QWORD
Definition: intro_types.h:53
INTSTATUS IntIntegrityUninit(void)
Uninits the integrity mechanism by removing every integrity region from the list. ...
Definition: integrity.c:503
INTSTATUS IntTranslateVirtualAddress(QWORD Gva, QWORD Cr3, QWORD *PhysicalAddress)
Translates a guest virtual address to a guest physical address.
Definition: introcore.c:1999
void * GpaCache
The currently used GPA cache.
Definition: guests.h:403
#define TRUE
Definition: intro_types.h:30
#define IS_KERNEL_POINTER_WIN(is64, p)
Checks if a guest virtual address resides inside the Windows kernel address space.
Definition: wddefs.h:76
#define TRACE(fmt,...)
Definition: glue.h:58
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
#define TIMER_FRIENDLY
Definition: introdefs.h:83
static void InsertTailList(LIST_ENTRY *ListHead, LIST_ENTRY *Entry)
Definition: introlists.h:135
#define WARNING(fmt,...)
Definition: glue.h:60
void IntIntegrityDump(void)
Dumps all the INTEGRITY_REGION structures from gIntegrityRegions. Used mainly for debugging...
Definition: integrity.c:486
QWORD Gva
The guest virtual address where the region starts.
Definition: integrity.h:31
INTSTATUS IntIntegrityRemoveRegion(void *Descriptor)
Removes an integrity region from the gIntegrityRegions list.
Definition: integrity.c:313
uint32_t DWORD
Definition: intro_types.h:49
#define IC_TAG_ITGR
Integrity region.
Definition: memtags.h:20
__must_check INTSTATUS IntVirtMemMap(QWORD Gva, DWORD Length, QWORD Cr3, DWORD Flags, void **HostPtr)
Maps a guest virtual memory range inside Introcore virtual address space.
Definition: introcore.c:2134
MM Mm
Guest memory information, such as paging mode, system Cr3 value, etc.
Definition: guests.h:374
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:50
static LIST_HEAD gIntegrityRegions
The global list of integrity regions, represented by INTEGRITY_REGION structures. ...
Definition: integrity.c:39
void * OriginalContent
A buffer containing the original bytes of the associated region.
Definition: integrity.h:43
DWORD Crc32ComputeFast(const void *Buffer, size_t Size, DWORD InitialCrc)
Definition: crc32.c:136
INTSTATUS IntGpaCacheFindAndAdd(PGPA_CACHE Cache, QWORD Gpa, void **Hva)
Search for an entry in the GPA cache, and add it, if it wasn&#39;t found.
Definition: gpacache.c:451
#define LIST_HEAD_INIT(Name)
Definition: introlists.h:39
INTSTATUS IntIntegrityRecalculate(INTEGRITY_REGION *IntegrityRegion)
Recalculates the hash and reads the original content again for a given region.
Definition: integrity.c:242
#define INT_STATUS_INVALID_PARAMETER_1
Definition: introstatus.h:62
DWORD Length
The length of the current region, in bytes.
Definition: integrity.h:32
#define PAGE_MASK
Definition: pgtable.h:35
#define INT_STATUS_INVALID_PARAMETER_2
Definition: introstatus.h:65
#define FALSE
Definition: intro_types.h:34
#define INT_STATUS_INSUFFICIENT_RESOURCES
Definition: introstatus.h:281