Bitdefender Hypervisor Memory Introspection
swapmem.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "swapmem.h"
6 #include "winagent.h"
7 #include "hook.h"
8 #include "introcpu.h"
9 
10 
41 
42 
47 typedef struct _SWAPMEM_TRANSACTION
48 {
60  void *Context;
70 
71 
76 typedef struct _SWAPMEM_PAGE
77 {
81  PSWAPMEM_TRANSACTION Transaction;
88 
89 
93 typedef struct _SWAPMEM_STATE
94 {
97  PSWAPMEM_PAGE PendingPage;
99 
100 
101 static SWAPMEM_STATE gSwapState = { 0 };
102 
103 
104 static INTSTATUS
106  _In_ void *DataAddress,
107  _In_ QWORD DataInfo
108  )
117 {
118  PSWAPMEM_TRANSACTION pCtx;
119 
120  UNREFERENCED_PARAMETER(DataInfo);
121 
122  pCtx = (PSWAPMEM_TRANSACTION)DataAddress;
123 
124  if (NULL != pCtx->Data)
125  {
127  }
128 
129  if ((0 != pCtx->ContextTag) && (NULL != pCtx->Context))
130  {
131  HpFreeAndNullWithTag(&pCtx->Context, pCtx->ContextTag);
132  }
133 
135 
136  return INT_STATUS_SUCCESS;
137 }
138 
139 
140 static INTSTATUS
142  _In_ PSWAPMEM_TRANSACTION Transaction
143  )
146 //
151 {
152  INTSTATUS status;
153  LIST_ENTRY *list;
154 
155  list = Transaction->Pages.Flink;
156  while (list != &Transaction->Pages)
157  {
158  PSWAPMEM_PAGE pPage = CONTAINING_RECORD(list, SWAPMEM_PAGE, Link);
159 
160  list = list->Flink;
161 
162  RemoveEntryList(&pPage->Link);
163 
164  pPage->IsEnqueued = FALSE;
165  pPage->IsReady = pPage->IsPending = pPage->IsDone = FALSE;
166 
167  if (NULL != pPage->Hook)
168  {
169  status = IntHookPtsRemoveHook((HOOK_PTS **)&pPage->Hook, 0);
170  if (!INT_SUCCESS(status))
171  {
172  ERROR("[ERROR] IntHookPtsRemoveHook failed: 0x%08x\n", status);
173  }
174  }
175 
176  if (pPage == gSwapState.PendingPage)
177  {
178  gSwapState.PendingPage = NULL;
179  }
180 
182  }
183 
184  status = IntSwapMemCleanupCallback(Transaction, 0);
185  if (!INT_SUCCESS(status))
186  {
187  ERROR("[ERROR] IntSwapMemCleanupCallback failed: 0x%08x\n", status);
188  }
189 
190  return INT_STATUS_SUCCESS;
191 }
192 
193 
194 static INTSTATUS
196  _In_ QWORD GuestVirtualAddress,
197  _In_ DWORD AgentTag,
198  _In_opt_ void *Context
199  )
213 {
214  INTSTATUS status;
215 
216  UNREFERENCED_PARAMETER(GuestVirtualAddress);
217  UNREFERENCED_PARAMETER(AgentTag);
218 
219  TRACE("[SWAPMEM] Injecting a #PF from #BP handler at %p on cpu %d\n", Context, gVcpu->Index);
220 
221  status = IntInjectExceptionInGuest(VECTOR_PF, (QWORD)Context, 0, gVcpu->Index);
222  if (!INT_SUCCESS(status))
223  {
224  ERROR("[ERROR] IntInjectExceptionInGuest failed for 0x%016llx: 0x%08x\n", (QWORD)Context, status);
225 
226  // Injection failed, we will retry injecting this #PF later.
228  }
229 
230  return status;
231 }
232 
233 
234 static INTSTATUS
237  )
246 {
248  {
249  return IntWinAgentInjectBreakpoint(IntSwapMemHandleBreakpointAgent, (void *)VirtualAddress, NULL);
250  }
251  else
252  {
254  }
255 }
256 
257 
258 static INTSTATUS
260  _In_ void *Context,
262  _In_ QWORD OldEntry,
263  _In_ QWORD NewEntry,
264  _In_ QWORD OldPageSize,
265  _In_ QWORD NewPageSize
266  )
285 {
286  INTSTATUS status;
287  PSWAPMEM_TRANSACTION pCtx;
288  PSWAPMEM_PAGE pPage;
289  DWORD readSize, offset;
290 
291  UNREFERENCED_PARAMETER(OldEntry);
292  UNREFERENCED_PARAMETER(OldPageSize);
293 
294  // Make sure the entry is present.
295  if (0 == (NewEntry & PT_P))
296  {
297  return INT_STATUS_SUCCESS;
298  }
299 
300  pPage = (PSWAPMEM_PAGE)Context;
301  if (NULL == pPage)
302  {
304  }
305 
306  pCtx = pPage->Transaction;
307  if (NULL == pCtx->Data)
308  {
310  }
311 
312  if (pPage->IsDone || pCtx->IsCanceled)
313  {
314  return INT_STATUS_SUCCESS;
315  }
316 
317  // Indicate that the current VCPU is not busy waiting for this swap anymore. We do this only if the page is
318  // pending. If the page is not pending, this means we didn't inject a #PF for it but it was swapped in anyway.
319  if (pPage == gSwapState.PendingPage)
320  {
321  gSwapState.PendingPage = NULL;
322  }
323 
324  // If we get here, this will be an async callback call.
326 
327  if (0 != (NewEntry & PT_XD))
328  {
329  pCtx->Flags |= SWAPMEM_FLAG_ENTRY_XD;
330  }
331 
332  // Copy the newly mapped physical page.
333  NewEntry = NewEntry & PHYS_PAGE_MASK & ~(NewPageSize - 1);
334 
335  if (VirtualAddress == pCtx->VirtualAddress)
336  {
337  pCtx->PhysicalAddress = NewEntry + (pCtx->VirtualAddress & (NewPageSize - 1));
338  }
339 
340  offset = (DWORD)(pPage->VirtualAddress - pCtx->VirtualAddress);
341 
342  readSize = MIN((DWORD)(NewPageSize - (pPage->VirtualAddress & (NewPageSize - 1))), pCtx->DataMaxSize - offset);
343 
344  TRACE("[SWAPMEM] Page %llx was swapped in at %llx, will read 0x%x bytes at offset 0x%x\n",
345  VirtualAddress, NewEntry, readSize, offset);
346 
347  status = IntPhysicalMemReadAnySize(NewEntry + (pPage->VirtualAddress & (NewPageSize - 1)),
348  readSize,
349  pCtx->Data + offset,
350  NULL);
351  if (!INT_SUCCESS(status))
352  {
353  ERROR("[ERROR] IntPhysicalMemReadAnySize failed for 0x%016llx : %08x (%08x) : 0x%08x\n",
354  NewEntry, (DWORD)NewPageSize, readSize, status);
355  }
356 
357  pCtx->DataCurrentSize += readSize;
358 
359  pPage->IsReady = FALSE;
360  pPage->IsPending = FALSE;
361  pPage->IsDone = TRUE;
362 
363  if (NULL != pPage->Hook)
364  {
365  status = IntHookPtsRemoveHook((HOOK_PTS **)&pPage->Hook, 0);
366  if (!INT_SUCCESS(status))
367  {
368  ERROR("[ERROR] IntHookPtsRemoveHook failed: 0x%08x\n", status);
369  }
370  }
371 
372  // Remove the current page, as we read its content.
373  if (pPage->IsEnqueued)
374  {
375  RemoveEntryList(&pPage->Link);
376  pPage->IsEnqueued = FALSE;
377 
379  }
380 
381  // Check if we've read everything.
382  if (pCtx->DataCurrentSize == pCtx->DataMaxSize)
383  {
384  // Invoke the callback, if set.
385  if (NULL != pCtx->Callback)
386  {
387  status = pCtx->Callback(pCtx->Context, pCtx->Cr3, pCtx->VirtualAddress, pCtx->PhysicalAddress,
388  pCtx->Data, pCtx->DataMaxSize, pCtx->Flags);
389  if (!INT_SUCCESS(status))
390  {
391  ERROR("[ERROR] Callback failed: 0x%08x\n", status);
392  }
393  }
394 
395  // Successfully called callback, invalidate the context & the tag.
396  pCtx->Context = NULL;
397  pCtx->ContextTag = 0;
398 
399  if (pCtx->IsEnqueued)
400  {
401  RemoveEntryList(&pCtx->Link);
402  pCtx->IsEnqueued = FALSE;
403 
404  status = IntSwapMemCleanupCallback(pCtx, 0);
405  if (!INT_SUCCESS(status))
406  {
407  ERROR("[ERROR] IntSwapMemCleanupCallback failed: 0x%08x\n", status);
408  }
409  }
410  }
411 
412  return INT_STATUS_SUCCESS;
413 }
414 
415 
416 INTSTATUS
418  _In_ QWORD Cr3,
420  _In_ DWORD Length,
422  _In_opt_ void *Context,
426  _Out_opt_ void **SwapHandle
427  )
462 {
463  INTSTATUS status;
464  PSWAPMEM_TRANSACTION pCtx;
465  PSWAPMEM_PAGE pPage;
466  VA_TRANSLATION tr = {0};
467  QWORD tCr3, page = 0;
468  BOOLEAN bInvoke = FALSE;
469 
470  pCtx = NULL;
471  pPage = NULL;
472  tCr3 = Cr3;
473 
474  if (0 == tCr3)
475  {
476  tCr3 = gGuest.Mm.SystemCr3;
477  }
478 
479  // If no duplicate transactions are allowed, make sure this one is unique.
480  if (0 != (Options & SWAPMEM_OPT_NO_DUPS))
481  {
482  LIST_ENTRY *list;
483 
484  list = gSwapState.SwapTranzactionsList.Flink;
485  while (list != &gSwapState.SwapTranzactionsList)
486  {
487  PSWAPMEM_TRANSACTION p = CONTAINING_RECORD(list, SWAPMEM_TRANSACTION, Link);
488 
489  list = list->Flink;
490 
491  // The transaction is already present, bail out.
492  if ((p->Cr3 == Cr3) && (p->VirtualAddress == VirtualAddress) &&
493  (p->DataMaxSize == Length) && (p->Options == Options))
494  {
495  if (NULL != SwapHandle)
496  {
497  *SwapHandle = NULL;
498  }
499  return INT_STATUS_SUCCESS;
500  }
501  }
502  }
503 
504  pCtx = HpAllocWithTag(sizeof(*pCtx), IC_TAG_SWCX);
505  if (NULL == pCtx)
506  {
508  goto cleanup_and_exit;
509  }
510 
511  pCtx->Cr3 = Cr3;
512  pCtx->Callback = Callback;
513  pCtx->PreInject = PreInject;
514  pCtx->Context = Context;
515  pCtx->ContextTag = ContextTag;
516  pCtx->DataCurrentSize = 0;
517  pCtx->DataMaxSize = Length;
519  pCtx->Options = Options;
520  pCtx->Flags = 0;
521  InitializeListHead(&pCtx->Pages);
522 
523  pCtx->Data = HpAllocWithTag(Length, IC_TAG_SWPP);
524  if (NULL == pCtx->Data)
525  {
527  goto cleanup_and_exit;
528  }
529 
530  for (page = VirtualAddress; page < VirtualAddress + Length; page = (page & PAGE_MASK) + PAGE_SIZE_4K)
531  {
532  status = IntTranslateVirtualAddressEx(page, tCr3, TRFLG_NONE, &tr);
533  if ((INT_STATUS_NO_MAPPING_STRUCTURES == status) ||
534  (INT_STATUS_PAGE_NOT_PRESENT == status) ||
535  (0 == (tr.Flags & PT_P)) ||
536  ((0 != (Options & SWAPMEM_OPT_RW_FAULT)) && (!tr.IsWritable)))
537  {
538  TRACE("[SWAPMEM] Page %llx is at %llx with flags %llx and opts %x, "
539  "scheduling #PF injection to read %d bytes...\n",
540  page, tr.PhysicalAddress, tr.Flags, Options, Length);
541 
542  // The current page is not present. Allocate a SWAPMEM_PAGE structure and insert it inside the pending
543  // pages list.
544  pPage = HpAllocWithTag(sizeof(*pPage), IC_TAG_SWPG);
545  if (NULL == pPage)
546  {
548  goto cleanup_and_exit;
549  }
550 
551  pPage->Transaction = pCtx;
552  pPage->VirtualAddress = page;
553  pPage->Hook = NULL;
554  pPage->IsReady = TRUE;
555  pPage->IsPending = FALSE;
556  pPage->IsDone = FALSE;
557  pPage->IsEnqueued = TRUE;
558 
559  // All set, this page is ready to be swapped in.
560  InsertTailList(&pCtx->Pages, &pPage->Link);
561 
562  // Place the hook after we insert to page in the list, so in case of failure, the page gets cleaned up.
563  status = IntHookPtsSetHook(Cr3, page, IntSwapMemPageSwappedIn, pPage, NULL, 0, &pPage->Hook);
564  if (!INT_SUCCESS(status))
565  {
566  ERROR("[ERROR] IntHookPtsSetHook failed: 0x%08x\n", status);
567  goto cleanup_and_exit;
568  }
569  }
570  else if (!INT_SUCCESS(status))
571  {
572  ERROR("[ERROR] IntTranslateVirtualAddressEx failed: 0x%08x\n", status);
573  goto cleanup_and_exit;
574  }
575  else
576  {
577  // We purposely use PAGE_SIZE, since we iterate in 4K chunks.
578  DWORD toRead = (DWORD)MIN(PAGE_REMAINING(page), Length - (page - VirtualAddress));
579 
580  // The page is already present. We can read it right now!
582  toRead,
583  pCtx->Data + (page - VirtualAddress),
584  NULL);
585  if (!INT_SUCCESS(status))
586  {
587  ERROR("[ERROR] IntPhysicalMemReadAnySize failed: 0x%08x\n", status);
588  goto cleanup_and_exit;
589  }
590 
591  pCtx->Flags |= (tr.Flags & PT_XD) ? SWAPMEM_FLAG_ENTRY_XD : 0;
592 
593  if (page == VirtualAddress)
594  {
595  pCtx->PhysicalAddress = tr.PhysicalAddress;
596  }
597 
598  pCtx->DataCurrentSize += toRead;
599  }
600  }
601 
602  if (pCtx->DataCurrentSize == pCtx->DataMaxSize)
603  {
604  bInvoke = TRUE;
605  }
606  else
607  {
608  InsertTailList(&gSwapState.SwapTranzactionsList, &pCtx->Link);
609  pCtx->IsEnqueued = TRUE;
610  }
611 
612  status = INT_STATUS_SUCCESS;
613 
614 cleanup_and_exit:
615 
616  // pCtx can be safely used outside the locked region, since it will be freed via a post-commit callback.
617  // In addition, if we get here, pCtx was not enqueued to the transactions list, so we're all good.
618  if (bInvoke)
619  {
620  if (NULL != pCtx->Callback)
621  {
622  // Everything was present, we can invoke the callback right now.
623  status = pCtx->Callback(pCtx->Context, pCtx->Cr3, pCtx->VirtualAddress, pCtx->PhysicalAddress,
624  pCtx->Data, pCtx->DataMaxSize, pCtx->Flags);
625  if (!INT_SUCCESS(status))
626  {
627  ERROR("[ERROR] Callback failed: 0x%08x\n", status);
628  }
629  }
630 
631  // Successfully called callback, invalidate the context & the tag.
632  pCtx->Context = NULL;
633  pCtx->ContextTag = 0;
634 
635  status = INT_STATUS_SUCCESS;
636  }
637 
638  if (!INT_SUCCESS(status) || bInvoke)
639  {
640  if (NULL != pCtx)
641  {
642  INTSTATUS status2;
643 
644  status2 = IntSwapMemCancelTransaction(pCtx);
645  if (!INT_SUCCESS(status2))
646  {
647  ERROR("[ERROR] IntSwapMemCancelTransaction failed: 0x%08x\n", status2);
648  }
649 
650  pCtx = NULL;
651  }
652  }
653 
654  if (NULL != SwapHandle)
655  {
656  *SwapHandle = pCtx;
657  }
658 
659  return status;
660 }
661 
662 
663 static PSWAPMEM_PAGE
666  _In_ QWORD Cr3
667  )
676 {
677  for (LIST_ENTRY *list = gSwapState.SwapTranzactionsList.Flink; list != &gSwapState.SwapTranzactionsList;
678  list = list->Flink)
679  {
680  PSWAPMEM_TRANSACTION tr = CONTAINING_RECORD(list, SWAPMEM_TRANSACTION, Link);
681 
682  for (LIST_ENTRY *pages = tr->Pages.Flink; pages != &tr->Pages; pages = pages->Flink)
683  {
684  PSWAPMEM_PAGE pPage = CONTAINING_RECORD(pages, SWAPMEM_PAGE, Link);
685 
686  if ((pPage->VirtualAddress == VirtualAddress) && (pPage->Transaction->Cr3 == Cr3))
687  {
688  return pPage;
689  }
690  }
691  }
692 
693  return NULL;
694 }
695 
696 
697 INTSTATUS
699  void
700  )
717 {
718  INTSTATUS status;
719  LIST_ENTRY *listCtx, *listPage;
720  VA_TRANSLATION tr;
721  QWORD cr3;
722  DWORD ring;
723  BOOLEAN bInjected;
724 
725  cr3 = 0;
726  ring = 0;
727  bInjected = FALSE;
728 
729  // Fast check - if the list of transactions is empty, bail out.
730  if (IsListEmpty(&gSwapState.SwapTranzactionsList))
731  {
732  return INT_STATUS_SUCCESS;
733  }
734 
735  // A pending exception exists, bail out now.
736  if (gVcpu->Exception.Valid)
737  {
738  return INT_STATUS_SUCCESS;
739  }
740 
741  // A pending #PF is being processed, wait for it.
742  if (NULL != gSwapState.PendingPage)
743  {
744  return INT_STATUS_SUCCESS;
745  }
746 
747  status = IntCr3Read(IG_CURRENT_VCPU, &cr3);
748  if (!INT_SUCCESS(status))
749  {
750  ERROR("[ERROR] IntCr3Read failed: 0x%08x\n", status);
751  return status;
752  }
753 
754  status = IntGetCurrentRing(IG_CURRENT_VCPU, &ring);
755  if (!INT_SUCCESS(status))
756  {
757  ERROR("[ERROR] IntGetCurrentRing failed: 0x%08x\n", status);
758  return status;
759  }
760 
761  // Pick a good #PF to inject.
762  listCtx = gSwapState.SwapTranzactionsList.Flink;
763  while (listCtx != &gSwapState.SwapTranzactionsList)
764  {
765  PSWAPMEM_TRANSACTION pCtx = CONTAINING_RECORD(listCtx, SWAPMEM_TRANSACTION, Link);
766 
767  listCtx = listCtx->Flink;
768 
769  // If a #PF should not be injected, bail out now; we'll wait for the page to be swapped in when it may...
770  if (0 != (pCtx->Options & SWAPMEM_OPT_NO_FAULT))
771  {
772  continue;
773  }
774 
775  // If the VA spaces don't match, bail out.
776  if ((cr3 != pCtx->Cr3) && (0 != pCtx->Cr3))
777  {
778  continue;
779  }
780 
781  // If we requested user fault and we're in kernel mode or kernel fault and we're in user mode, bail out.
782  if (((IG_CS_RING_0 != ring) && (0 != (pCtx->Options & SWAPMEM_OPT_KM_FAULT))) ||
783  ((IG_CS_RING_3 != ring) && (0 != (pCtx->Options & SWAPMEM_OPT_UM_FAULT))))
784  {
785  continue;
786  }
787 
788  // Now check for pending pages.
789  listPage = pCtx->Pages.Flink;
790  while (listPage != &pCtx->Pages)
791  {
792  PSWAPMEM_PAGE pPage = CONTAINING_RECORD(listPage, SWAPMEM_PAGE, Link);
793  DWORD pfec = 0;
794 
795  listPage = listPage->Flink;
796 
797  if (!pPage->IsReady)
798  {
799  continue;
800  }
801 
802  // No need to bail out in case of error, we'll just assume the page is not present.
803  status = IntTranslateVirtualAddressEx(pPage->VirtualAddress, cr3, TRFLG_NONE, &tr);
804  if (INT_SUCCESS(status))
805  {
806  // The page is already present.
807  pfec |= ((0 != (tr.Flags & PT_P)) ? PFEC_P : 0);
808  }
809 
810  // Check for user or kernel access.
811  pfec |= ((ring == IG_CS_RING_3) ? PFEC_US : 0);
812 
813  // If we force a write fault, set the write flag.
814  pfec |= ((0 != (pCtx->Options & SWAPMEM_OPT_RW_FAULT)) ? PFEC_RW : 0);
815 
816  TRACE("[SWAPMEM] [VCPU %d] Translated page 0x%016llx to 0x%016llx, entry 0x%016llx\n", gVcpu->Index,
817  pPage->VirtualAddress, tr.PhysicalAddress, tr.Flags);
818 
819  // Call the pre-inject callback, which has the last word in deciding whether the #PF should be injected.
820  if (NULL != pCtx->PreInject)
821  {
822  status = pCtx->PreInject(pCtx->Context, cr3, pPage->VirtualAddress);
823  if (!INT_SUCCESS(status) || (INT_STATUS_NOT_NEEDED_HINT == status))
824  {
825  TRACE("[SWAPMEM] The pre-injection callback decided we cannot inject a #PF yet for 0x%016llx!\n",
826  pPage->VirtualAddress);
827  continue;
828  }
829  }
830 
831  // If a #PF must be generated only inside user mode, do so. Also, more checks should be done, to make sure
832  // the #PF won't crash the guest, but as of now, we only use this to inject faults in user-mode,
833  // which is safe.
834  if (0 != (pCtx->Options & (SWAPMEM_OPT_BP_FAULT)))
835  {
836  TRACE("[SWAPMEM] Injecting AG/BP #PF for %llx/%llx!\n", cr3, pPage->VirtualAddress);
837 
839  if (!INT_SUCCESS(status))
840  {
841  ERROR("[ERROR] IntSwapMemInjectMiniSwapper failed: 0x%08x\n", status);
842  }
843  }
844  else
845  {
846  TRACE("[SWAPMEM] Injecting UM/KM/direct #PF for %llx/%llx!\n", cr3, pPage->VirtualAddress);
847 
849  if (!INT_SUCCESS(status))
850  {
851  ERROR("[ERROR] IntInjectExceptionInGuest failed: 0x%08x\n", status);
852  }
853  }
854 
855  if (INT_SUCCESS(status))
856  {
857  pPage->IsReady = FALSE;
858  pPage->IsPending = TRUE;
859  pPage->IsDone = FALSE;
860  pPage->TimeStamp = gGuest.TimerCalls;
861  gSwapState.PendingPage = pPage;
862  bInjected = TRUE;
863  }
864 
865  break;
866  }
867 
868  if (bInjected || !INT_SUCCESS(status))
869  {
870  break;
871  }
872  }
873 
874  return status;
875 }
876 
877 
878 void
881  )
891 {
892  // There can be only one pending #PF injection from swapmem at any given time, no matter how many CPUs we have.
893  // However, make sure that the pending page is the same as the page for which an injection was requested, in order
894  // to not cancel a valid transaction due to an injection error from an unrelated exception.
895  if (NULL != gSwapState.PendingPage &&
896  (gSwapState.PendingPage->VirtualAddress & PAGE_MASK) == (VirtualAddress & PAGE_MASK))
897  {
898  TRACE("[SWAPMEM] Canceling pending #PF for 0x%016llx, CR3 0x%016llx, CPU %d...\n",
899  gSwapState.PendingPage->VirtualAddress, gSwapState.PendingPage->Transaction->Cr3, gVcpu->Index);
900 
901  // All other faults need to wait.
902  gSwapState.PendingPage->IsReady = TRUE;
903  gSwapState.PendingPage->IsPending = FALSE;
904  gSwapState.PendingPage->IsDone = FALSE;
905  gSwapState.PendingPage->TimeStamp = 0;
906 
907  gSwapState.PendingPage = NULL;
908  }
909 }
910 
911 
912 void
914  void
915  )
923 {
924  if (NULL != gSwapState.PendingPage)
925  {
926  if (gSwapState.PendingPage->TimeStamp + 1 < gGuest.TimerCalls)
927  {
928  LOG("[SWAPMEM] Page 0x%016llx with CR3 0x%016llx exceeds the age limit, will retry injection...\n",
929  gSwapState.PendingPage->VirtualAddress, gSwapState.PendingPage->Transaction->Cr3);
930 
931  gSwapState.PendingPage->IsPending = FALSE;
932  gSwapState.PendingPage->IsReady = TRUE;
933  gSwapState.PendingPage->TimeStamp = 0;
934 
935  gSwapState.PendingPage = NULL;
936  }
937  }
938 }
939 
940 
941 INTSTATUS
943  _In_ void *Transaction
944  )
957 {
958  INTSTATUS status;
959  PSWAPMEM_TRANSACTION pCtx;
960 
961  if (NULL == Transaction)
962  {
964  }
965 
966  pCtx = (PSWAPMEM_TRANSACTION)Transaction;
967 
968  RemoveEntryList(&pCtx->Link);
969  pCtx->IsEnqueued = FALSE;
970 
971  status = IntSwapMemCancelTransaction(pCtx);
972  if (!INT_SUCCESS(status))
973  {
974  ERROR("[ERROR] IntSwapMemCancelTransaction failed: 0x%08x\n", status);
975  }
976 
977  return status;
978 }
979 
980 
981 INTSTATUS
983  _In_ QWORD Cr3
984  )
995 {
996  INTSTATUS status;
997  LIST_ENTRY *list;
998 
999  list = gSwapState.SwapTranzactionsList.Flink;
1000  while (list != &gSwapState.SwapTranzactionsList)
1001  {
1002  PSWAPMEM_TRANSACTION pCtx = CONTAINING_RECORD(list, SWAPMEM_TRANSACTION, Link);
1003  list = list->Flink;
1004 
1005  if (pCtx->Cr3 == Cr3)
1006  {
1007  TRACE("[SWAPMEM] Removing transaction request at 0x%016llx for VA space 0x%016llx.\n",
1008  pCtx->VirtualAddress, pCtx->Cr3);
1009 
1010  RemoveEntryList(&pCtx->Link);
1011  pCtx->IsEnqueued = FALSE;
1012 
1013  status = IntSwapMemCancelTransaction(pCtx);
1014  if (!INT_SUCCESS(status))
1015  {
1016  ERROR("[ERROR] IntSwapMemCancelTransaction failed: 0x%08x\n", status);
1017  }
1018  }
1019  }
1020 
1021  return INT_STATUS_SUCCESS;
1022 }
1023 
1024 
1025 INTSTATUS
1027  void
1028  )
1034 {
1036 
1037  gSwapState.Initialized = TRUE;
1038 
1039  return INT_STATUS_SUCCESS;
1040 }
1041 
1042 
1043 INTSTATUS
1045  void
1046  )
1053 {
1054  if (!gSwapState.Initialized)
1055  {
1057  }
1058 
1059  memzero(&gSwapState, sizeof(gSwapState));
1060 
1061  return INT_STATUS_SUCCESS;
1062 }
1063 
1064 
1065 void
1067  void
1068  )
1072 {
1073  LIST_ENTRY *list1, *list2;
1074 
1075  list1 = gSwapState.SwapTranzactionsList.Flink;
1076  while (list1 != &gSwapState.SwapTranzactionsList)
1077  {
1078  PSWAPMEM_TRANSACTION pCtx = CONTAINING_RECORD(list1, SWAPMEM_TRANSACTION, Link);
1079 
1080  list1 = list1->Flink;
1081 
1082  LOG("Transaction %p: CR3 = 0x%016llx, GVA = 0x%016llx, max len = %d, cur len = %d, options = %d\n",
1083  pCtx, pCtx->Cr3, pCtx->VirtualAddress, pCtx->DataMaxSize, pCtx->DataCurrentSize, pCtx->Options);
1084 
1085  list2 = pCtx->Pages.Flink;
1086  while (list2 != &pCtx->Pages)
1087  {
1088  PSWAPMEM_PAGE pPage = CONTAINING_RECORD(list2, SWAPMEM_PAGE, Link);
1089 
1090  list2 = list2->Flink;
1091 
1092  LOG(" Page %p: GVA = 0x%016llx, state: %d %d %d\n",
1093  pPage, pPage->VirtualAddress, pPage->IsReady, pPage->IsPending, pPage->IsDone);
1094  }
1095  }
1096 }
#define SWAPMEM_OPT_NO_FAULT
If set, no PF will be injected. Introcore will wait for the pages to be naturally swapped in...
Definition: swapmem.h:19
#define _In_opt_
Definition: intro_sal.h:16
#define INT_STATUS_PAGE_NOT_PRESENT
Indicates that a virtual address is not present.
Definition: introstatus.h:438
#define PT_XD
Definition: pgtable.h:92
QWORD PhysicalAddress
The physical address to which VirtualAddress translates to.
Definition: introcore.h:107
_Bool BOOLEAN
Definition: intro_types.h:58
#define CONTAINING_RECORD(List, Type, Member)
Definition: introlists.h:36
#define IC_TAG_SWPP
Swapmem pages data area.
Definition: memtags.h:41
#define PFEC_P
Definition: processor.h:83
static PSWAPMEM_PAGE IntSwapMemFindPendingPage(QWORD VirtualAddress, QWORD Cr3)
Finds a pending page, given the guest virtual address and the Cr3.
Definition: swapmem.c:664
BOOLEAN IsEnqueued
True if the page has been inserted inside the list of pages.
Definition: swapmem.c:86
void IntSwapMemReinjectFailedPF(void)
Reinject timed-out PFs.
Definition: swapmem.c:913
struct _SWAPMEM_PAGE SWAPMEM_PAGE
static INTSTATUS IntSwapMemPageSwappedIn(void *Context, QWORD VirtualAddress, QWORD OldEntry, QWORD NewEntry, QWORD OldPageSize, QWORD NewPageSize)
Handle a page swap-in event.
Definition: swapmem.c:259
static INTSTATUS IntSwapMemInjectMiniSwapper(QWORD VirtualAddress)
Injects the mini swapper, which is basically just a breakpoint agent.
Definition: swapmem.c:235
DWORD Index
The VCPU number.
Definition: guests.h:172
#define _In_
Definition: intro_sal.h:21
struct _SWAPMEM_STATE SWAPMEM_STATE
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
PSWAPMEM_TRANSACTION Transaction
Parent transaction.
Definition: swapmem.c:81
BOOLEAN IsWritable
True if this page is writable.
Definition: introcore.h:132
DWORD ContextTag
freed.
Definition: swapmem.c:59
LIST_HEAD SwapTranzactionsList
List of transactions.
Definition: swapmem.c:96
struct _LIST_ENTRY * Flink
Definition: introlists.h:20
PHOOK_PTS Hook
Swap in hook handle set on this page.
Definition: swapmem.c:80
LIST_ENTRY Link
List entry element.
Definition: swapmem.c:49
void IntSwapMemDump(void)
Dump all active transactions & pages.
Definition: swapmem.c:1066
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
static BOOLEAN IsListEmpty(const LIST_ENTRY *ListHead)
Definition: introlists.h:78
PSWAPMEM_PAGE PendingPage
Currently pending page. There can be only one pending page.
Definition: swapmem.c:97
#define SWAPMEM_OPT_NO_DUPS
If set, will make sure that a single PF is scheduled for this page.
Definition: swapmem.h:31
DWORD DataCurrentSize
How much we&#39;ve read so far.
Definition: swapmem.c:56
#define INT_STATUS_NOT_NEEDED_HINT
Definition: introstatus.h:317
#define ERROR(fmt,...)
Definition: glue.h:62
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
#define PHYS_PAGE_MASK
Definition: pgtable.h:38
int INTSTATUS
The status data type.
Definition: introstatus.h:24
INTSTATUS(* PFUNC_PagesReadCallback)(void *Context, QWORD Cr3, QWORD VirtualAddress, QWORD PhysicalAddress, void *Data, DWORD DataSize, DWORD Flags)
Called when all the required data is available.
Definition: swapmem.h:52
LIST_ENTRY Link
List entry link.
Definition: swapmem.c:78
INTSTATUS IntInjectExceptionInGuest(BYTE Vector, QWORD Cr2, DWORD ErrorCode, DWORD CpuNumber)
Injects an exception inside the guest.
Definition: introcore.c:2264
#define TRFLG_NONE
No special options.
Definition: introcore.h:82
#define SWAPMEM_FLAG_ENTRY_XD
Definition: swapmem.h:15
PBYTE Data
memory will be read.
Definition: swapmem.c:53
static INTSTATUS IntSwapMemCleanupCallback(void *DataAddress, QWORD DataInfo)
Cleans up a transaction, by freeing the data buffer, the context and the transaction itself...
Definition: swapmem.c:105
INTRO_GUEST_TYPE OSType
The type of the guest.
Definition: guests.h:278
#define INT_STATUS_OPERATION_NOT_IMPLEMENTED
Definition: introstatus.h:125
#define MIN(a, b)
Definition: introdefs.h:146
INTSTATUS IntSwapMemInjectPendingPF(void)
Inject a PF for a pending page.
Definition: swapmem.c:698
BOOLEAN IsEnqueued
transactions list.
Definition: swapmem.c:62
#define LOG(fmt,...)
Definition: glue.h:61
INTSTATUS IntHookPtsSetHook(QWORD Cr3, QWORD VirtualAddress, PFUNC_SwapCallback Callback, void *Context, void *Parent, DWORD Flags, PHOOK_PTS *Hook)
Start monitoring translation modifications for the given VirtualAddress.
Definition: hook_pts.c:1535
QWORD TimeStamp
When was the last time we injected a PF for this page.
Definition: swapmem.c:82
INTSTATUS IntSwapMemRemoveTransactionsForVaSpace(QWORD Cr3)
Remove all transactions initiated for a virtual address space.
Definition: swapmem.c:982
static INTSTATUS IntSwapMemCancelTransaction(PSWAPMEM_TRANSACTION Transaction)
Cancels a transaction.
Definition: swapmem.c:141
static INTSTATUS IntSwapMemHandleBreakpointAgent(QWORD GuestVirtualAddress, DWORD AgentTag, void *Context)
Handles a breakpoint agent.
Definition: swapmem.c:195
static SWAPMEM_STATE gSwapState
Definition: swapmem.c:101
QWORD Flags
The entry that maps VirtualAddress to PhysicalAddress, together with all the control bits...
Definition: introcore.h:119
struct _SWAPMEM_TRANSACTION * PSWAPMEM_TRANSACTION
#define _Out_opt_
Definition: intro_sal.h:30
#define IG_CURRENT_VCPU
For APIs that take a VCPU number as a parameter, this can be used to specify that the current VCPU sh...
Definition: glueiface.h:324
uint8_t * PBYTE
Definition: intro_types.h:47
static BOOLEAN RemoveEntryList(LIST_ENTRY *Entry)
Definition: introlists.h:87
#define memzero(a, s)
Definition: introcrt.h:35
#define PT_P
Definition: pgtable.h:83
BOOLEAN IsReady
True if the page is ready to be read.
Definition: swapmem.c:83
unsigned long long QWORD
Definition: intro_types.h:53
QWORD PhysicalAddress
Guest physical address, once we get a translation.
Definition: swapmem.c:52
#define SWAPMEM_OPT_RW_FAULT
If set, the PF will be generated with write access. Useful when CoW must be done. ...
Definition: swapmem.h:29
#define TRUE
Definition: intro_types.h:30
DWORD Flags
Transaction flags. Take a look at SWAPMEM_FLAG* for more info.
Definition: swapmem.c:57
#define TRACE(fmt,...)
Definition: glue.h:58
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
INTSTATUS IntHookPtsRemoveHook(HOOK_PTS **Hook, DWORD Flags)
Remove a PTS hook.
Definition: hook_pts.c:1944
BOOLEAN IsDone
True if the page has been read.
Definition: swapmem.c:85
BOOLEAN IsPending
True if we injected a PF for the page, and we are waiting for it.
Definition: swapmem.c:84
QWORD VirtualAddress
Guest virtual address of the page.
Definition: swapmem.c:79
static void InsertTailList(LIST_ENTRY *ListHead, LIST_ENTRY *Entry)
Definition: introlists.h:135
INTSTATUS IntTranslateVirtualAddressEx(QWORD Gva, QWORD Cr3, DWORD Flags, VA_TRANSLATION *Translation)
Translates a guest virtual address to a guest physical address.
Definition: introcore.c:1863
#define IC_TAG_SWCX
Swapmem context.
Definition: memtags.h:40
#define SWAPMEM_FLAG_ASYNC_CALL
Definition: swapmem.h:12
INTSTATUS IntSwapMemReadData(QWORD Cr3, QWORD VirtualAddress, DWORD Length, DWORD Options, void *Context, DWORD ContextTag, PFUNC_PagesReadCallback Callback, PFUNC_PreInjectCallback PreInject, void **SwapHandle)
Reads a region of guest virtual memory, and calls the indicated callback when all the data is availab...
Definition: swapmem.c:417
#define IC_TAG_SWPG
Swapmem page.
Definition: memtags.h:42
DWORD DataMaxSize
Maximum data size to be read.
Definition: swapmem.c:55
QWORD Cr3
Virtual address space from where we read memory.
Definition: swapmem.c:50
PFUNC_PreInjectCallback PreInject
returns INT_STATUS_NOT_NEEDED_HINT, the PF will be canceled.
Definition: swapmem.c:67
static void InitializeListHead(LIST_ENTRY *ListHead)
Definition: introlists.h:69
INTSTATUS IntSwapMemRemoveTransaction(void *Transaction)
Remove a transaction.
Definition: swapmem.c:942
struct _SWAPMEM_TRANSACTION SWAPMEM_TRANSACTION
#define UNREFERENCED_PARAMETER(P)
Definition: introdefs.h:29
void * Context
Options context to be passed to the callbacks.
Definition: swapmem.c:61
struct _SWAPMEM_STATE * PSWAPMEM_STATE
QWORD VirtualAddress
Guest virtual address to be read.
Definition: swapmem.c:51
INTSTATUS IntSwapMemInit(void)
Init the swapmem system.
Definition: swapmem.c:1026
uint32_t DWORD
Definition: intro_types.h:49
#define SWAPMEM_OPT_KM_FAULT
Definition: swapmem.h:25
#define SWAPMEM_OPT_BP_FAULT
If set, the #PF will be generated from an int3 detour. Use this when injecting kernel PFs...
Definition: swapmem.h:27
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
PFUNC_PagesReadCallback Callback
Callback called as soon as all the requested data is available.
Definition: swapmem.c:66
#define PFEC_US
Definition: processor.h:85
INTSTATUS(* PFUNC_PreInjectCallback)(void *Context, QWORD Cr3, QWORD VirtualAddress)
Called before injecting a PF inside the guest.
Definition: swapmem.h:80
QWORD TimerCalls
The number of times the timer callback has been invoked.
Definition: guests.h:274
struct _VCPU_STATE::@80 Exception
The exception to be injected in guest.
INTSTATUS IntPhysicalMemReadAnySize(QWORD PhysicalAddress, DWORD Length, void *Buffer, DWORD *RetLength)
Reads data from a guest physical memory range, regardless of how many pages it spans across...
Definition: introcore.c:764
INTSTATUS IntWinAgentInjectBreakpoint(PFUNC_AgentInjection InjectionCallback, void *Context, PWIN_AGENT *Agent)
Injects a breakpoint agent inside the guest.
Definition: winagent.c:2921
#define INT_STATUS_NO_MAPPING_STRUCTURES
Indicates that not all mapping structures of a virtual address are present.
Definition: introstatus.h:434
#define VECTOR_PF
Definition: processor.h:116
BOOLEAN Initialized
True if the state has been initialized.
Definition: swapmem.c:95
INTSTATUS IntCr3Read(DWORD CpuNumber, QWORD *Cr3Value)
Reads the value of the guest CR3.
Definition: introcpu.c:415
#define INT_STATUS_NOT_INITIALIZED_HINT
Definition: introstatus.h:320
Encapsulates information about a virtual to physical memory translation.
Definition: introcore.h:102
BOOLEAN Valid
True if the fields are valid; False if they are not.
Definition: guests.h:132
#define INT_STATUS_INVALID_PARAMETER_1
Definition: introstatus.h:62
VCPU_STATE * gVcpu
The state of the current VCPU.
Definition: guests.c:59
BOOLEAN IsCanceled
True if the transaction has been canceled.
Definition: swapmem.c:64
struct _SWAPMEM_PAGE * PSWAPMEM_PAGE
INTSTATUS IntGetCurrentRing(DWORD CpuNumber, DWORD *Ring)
Read the current protection level.
Definition: introcpu.c:959
DWORD Options
Transaction options. Take a look at SWAPMEM_OPT* for more info.
Definition: swapmem.c:58
LIST_HEAD Pages
List of pages to be read (list of SWAPMEM_PAGE).
Definition: swapmem.c:65
#define PFEC_RW
Definition: processor.h:84
#define PAGE_MASK
Definition: pgtable.h:35
#define PAGE_SIZE_4K
Definition: pgtable.h:10
#define INT_STATUS_INVALID_PARAMETER_2
Definition: introstatus.h:65
INTSTATUS IntSwapMemUnInit(void)
Uninit the swapmem system.
Definition: swapmem.c:1044
#define SWAPMEM_OPT_UM_FAULT
If set, the PF must be injected only while in user-mode. Use it when reading user-mode memory...
Definition: swapmem.h:21
#define FALSE
Definition: intro_types.h:34
void IntSwapMemCancelPendingPF(QWORD VirtualAddress)
Cancel a pending PF.
Definition: swapmem.c:879
#define INT_STATUS_INSUFFICIENT_RESOURCES
Definition: introstatus.h:281