Bitdefender Hypervisor Memory Introspection
winumpath.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "winumpath.h"
6 #include "crc32.h"
7 #include "introcore.h"
8 
9 
35 
36 
40 static WCHAR gInvalidModulePath[8] = u"<error>";
41 
42 
47 {
49  .Name = gInvalidModulePath,
50 
51  .PathSize = CWSTRLEN(gInvalidModulePath) * 2,
52  .NameSize = CWSTRLEN(gInvalidModulePath) * 2,
53 
54  .NameHash = INITIAL_CRC_VALUE,
55 
56  .RefCount = 1,
57 
58  .SubsectionGva = 0,
59 };
60 
61 
62 static
65  _Inout_ RBNODE *Node
66  )
71 {
73 }
74 
75 
76 static
79  _In_ RBNODE *Left,
80  _In_ RBNODE *Right
81  )
99 {
100  WINUM_PATH *p1 = CONTAINING_RECORD(Left, WINUM_PATH, RbNode);
101  WINUM_PATH *p2 = CONTAINING_RECORD(Right, WINUM_PATH, RbNode);
102 
103  if (p1->SubsectionGva < p2->SubsectionGva)
104  {
105  return -1;
106  }
107  else if (p1->SubsectionGva > p2->SubsectionGva)
108  {
109  return 1;
110  }
111  else
112  {
113  return 0;
114  }
115 }
116 
117 
122 
123 
124 static WINUM_PATH *
126  _In_ QWORD SubsectionGva
127  )
147 {
148  RBNODE *result;
149  WINUM_PATH target;
150  INTSTATUS status;
151 
152  target.SubsectionGva = SubsectionGva;
153 
154  status = RbLookupNode(&gPaths, &target.RbNode, &result);
155  if (!INT_SUCCESS(status))
156  {
157  return NULL;
158  }
159 
160  return CONTAINING_RECORD(result, WINUM_PATH, RbNode);
161 }
162 
163 
164 __nonnull() static void
166  _In_ WINUM_PATH *Path
167  )
173 {
174  if (Path->Path)
175  {
176  HpFreeAndNullWithTag(&Path->Path, IC_TAG_UMPT);
177  }
178 
180 }
181 
182 
183 WINUM_PATH *
185  _In_ const WCHAR *Path,
186  _In_ DWORD PathSize,
187  _In_ QWORD SubsectionGva
188  )
206 {
207  WINUM_PATH *pPath;
208  INTSTATUS status;
209 
210  // Expecting at least one character and the NULL terminator. The path length cannot exceed a word in length.
211  if ((PathSize < 2) || (PathSize > 0xFFFF))
212  {
213  ERROR("[ERROR] Path size (%d) is invalid for module\n", PathSize);
214  return &gInvalidUmPath;
215  }
216 
217  pPath = HpAllocWithTag(sizeof(*pPath), IC_TAG_PTHP);
218  if (NULL == pPath)
219  {
220  return &gInvalidUmPath;
221  }
222 
223  pPath->RefCount = 1;
224  pPath->PathSize = PathSize;
225  pPath->SubsectionGva = SubsectionGva;
226 
227  // Copy the module path. PathLength + 2 OK - PathLength is less than 0xFFFF and PathLength is DWORD.
228  pPath->Path = HpAllocWithTag(PathSize + 2ull, IC_TAG_UMPT);
229  if (NULL == pPath->Path)
230  {
232 
233  return &gInvalidUmPath;
234  }
235  else
236  {
237  DWORD i = 0, ni = 0;
238 
239  for (i = 0; i < pPath->PathSize / 2; i++)
240  {
241  pPath->Path[i] = ((Path[i] >= u'A') && (Path[i] <= u'Z')) ? (Path[i] | 0x20) : Path[i];
242 
243  if (pPath->Path[i] == u'\\')
244  {
245  ni = i + 1;
246  }
247  }
248 
249  pPath->Path[i] = 0;
250 
251  pPath->Name = pPath->Path + ni;
252  pPath->NameSize = ((pPath->PathSize / 2) - ni) * 2;
253  pPath->NameHash = Crc32Wstring(pPath->Name, INITIAL_CRC_VALUE);
254 
255  if (0 == ni)
256  {
257  WARNING("[WARNING] Path `%s` seems to be incomplete\n", utf16_for_log(pPath->Path));
258  }
259 
260  status = RbInsertNode(&gPaths, &pPath->RbNode);
261  if (!INT_SUCCESS(status))
262  {
263  IntWinUmPathFree(pPath);
264 
265  // This will happen in the following pretty stressful case:
266  // 1. N processes load the same dll at ~ the same time
267  // 2. The dll subsection is not cached
268  // 3. The module path is swapped out from guest memory
269  // Then, introcore will inject N page faults for each process to fetch the module path from memory, the
270  // first page fault will call IntWinUmPathCreate (which will insert the first path in the red-black tree),
271  // but the following page fault callbacks will also call IntWinUmPathCreate for each process,
272  // resulting in the same subsection, thus the same key
273  if (INT_STATUS_KEY_ALREADY_EXISTS == status)
274  {
275  WARNING("[WARNING] IntWinUmPathCreate called with duplicated subsection: %llx Path: %s!\n",
276  SubsectionGva, Path ? utf16_for_log(Path) : "");
277 
278  return IntWinUmPathFetchAndReferenceBySubsection(SubsectionGva);
279  }
280 
281  ERROR("[ERROR] RbInsertNode failed: 0x%08x\n", status);
282 
283  return &gInvalidUmPath;
284  }
285  }
286 
287  return pPath;
288 }
289 
290 
291 WINUM_PATH *
293  _In_ WINUM_PATH *Path
294  )
306 {
307  Path->RefCount++;
308 
309  return Path;
310 }
311 
312 
313 WINUM_PATH *
315  _In_ QWORD SubsectionGva
316  )
328 {
329  WINUM_PATH *pPath = IntWinUmPathFetchBySubsection(SubsectionGva);
330  if (NULL != pPath)
331  {
332  pPath->RefCount++;
333  }
334 
335  return pPath;
336 }
337 
338 
339 void
341  _Inout_ WINUM_PATH **Path
342  )
357 {
358  WINUM_PATH *pPath;
359 
360  if (NULL == Path)
361  {
362  return;
363  }
364 
365  pPath = *Path;
366  *Path = NULL;
367 
368  if (NULL == pPath || pPath == &gInvalidUmPath)
369  {
370  return;
371  }
372 
373  if (--pPath->RefCount > 0)
374  {
375  return;
376  }
377 
378  RbDeleteNode(&gPaths, &pPath->RbNode);
379 
380  IntWinUmPathFree(pPath);
381 }
WINUM_PATH * IntWinUmPathReference(WINUM_PATH *Path)
Increases the reference count of the given WINUM_PATH object.
Definition: winumpath.c:292
#define CONTAINING_RECORD(List, Type, Member)
Definition: introlists.h:36
#define IC_TAG_UMPT
UM object path (cached)
Definition: memtags.h:54
#define _In_
Definition: intro_sal.h:21
void IntWinUmPathDereference(WINUM_PATH **Path)
Dereferences a WINUM_PATH object, releasing the resources if the reference count has reached 0...
Definition: winumpath.c:340
DWORD NameSize
The number of bytes in the name string.
Definition: winumpath.h:21
static WINUM_PATH gInvalidUmPath
Definition: winumpath.c:46
void FUNC_RbTreeNodeFree(RBNODE *Node)
Definition: rbtree.h:47
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
Definition: rbtree.h:34
INTSTATUS RbLookupNode(RBTREE *Tree, RBNODE *NodeToSearch, RBNODE **NodeFound)
Definition: rbtree.c:517
DWORD PathSize
The number of bytes in the path string.
Definition: winumpath.h:20
#define ERROR(fmt,...)
Definition: glue.h:62
WINUM_PATH * IntWinUmPathFetchAndReferenceBySubsection(QWORD SubsectionGva)
Fetches a WINUM_PATH object by the unique identifier and increments the reference counter on it...
Definition: winumpath.c:314
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
int INTSTATUS
The status data type.
Definition: introstatus.h:24
int FUNC_RbTreeNodeCompare(RBNODE *Left, RBNODE *Right)
Definition: rbtree.h:59
#define _Inout_
Definition: intro_sal.h:20
#define INITIAL_CRC_VALUE
Definition: introdefs.h:221
static void IntWinUmPathFree(WINUM_PATH *Path)
Releases resources associated to a WINUM_PATH object.
Definition: winumpath.c:165
unsigned long long QWORD
Definition: intro_types.h:53
QWORD SubsectionGva
The subsection guest virtual address from where the path was read. Serves as an unique identifier...
Definition: winumpath.h:29
#define IC_TAG_PTHP
Object path (cached)
Definition: memtags.h:53
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
#define INT_STATUS_KEY_ALREADY_EXISTS
Definition: introstatus.h:239
static WINUM_PATH * IntWinUmPathFetchBySubsection(QWORD SubsectionGva)
Fetches a path object by the given unique identifier, which is the subsection virtual address...
Definition: winumpath.c:125
#define WARNING(fmt,...)
Definition: glue.h:60
static void IntWinUmPathRbTreeNodeFree(RBNODE *Node)
Function called whenever a node from the red-black tree is freed.
Definition: winumpath.c:64
static int IntWinUmPathRbTreeNodeCompare(RBNODE *Left, RBNODE *Right)
Function used for comparison of two red-black tree nodes describing two different paths...
Definition: winumpath.c:78
DWORD Crc32Wstring(const WCHAR *String, DWORD InitialCrc)
Computes the CRC for a NULL-terminated wide char string.
Definition: crc32.c:226
#define UNREFERENCED_PARAMETER(P)
Definition: introdefs.h:29
DWORD NameHash
The CRC32 hash of the name. Used for fast matching.
Definition: winumpath.h:23
WINUM_PATH * IntWinUmPathCreate(const WCHAR *Path, DWORD PathSize, QWORD SubsectionGva)
Creates a WINUM_PATH object from the given parameters.
Definition: winumpath.c:184
uint16_t WCHAR
Definition: intro_types.h:63
uint32_t DWORD
Definition: intro_types.h:49
Definition: rbtree.h:84
WCHAR * Name
The name of the module contained in the path.
Definition: winumpath.h:18
#define _Function_class_(expr)
Definition: intro_sal.h:40
#define CWSTRLEN(Wstring)
Definition: introdefs.h:104
void RbDeleteNode(RBTREE *Tree, RBNODE *Node)
Definition: rbtree.c:710
static WCHAR gInvalidModulePath[8]
Definition: winumpath.c:40
INTSTATUS RbInsertNode(RBTREE *Tree, RBNODE *Node)
Definition: rbtree.c:606
char * utf16_for_log(const WCHAR *WString)
Converts a UTF-16 to a UTF-8 string to be used inside logging macros.
Definition: introcore.c:2845
RBNODE RbNode
The node which is inserted into gPaths tree.
Definition: winumpath.h:15
static RBTREE gPaths
Definition: winumpath.c:121
#define RB_TREE_INIT(Name, Free, Compare)
Initializes a RBTREE structure.
Definition: introcore.h:39
INT32 RefCount
The reference count of the current object. When reaching 0, the path will be freed.
Definition: winumpath.h:26
WCHAR * Path
The string which represents the user-mode module path.
Definition: winumpath.h:17