Bitdefender Hypervisor Memory Introspection
lixfiles.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "lixfiles.h"
6 #include "guests.h"
7 
8 
12 typedef struct _DENTRY_PATH
13 {
15 
16  char *Path;
18 
21 
22 
26 typedef struct _DENTRY_STRING
27 {
28  char *String;
31 
32 
39 {
40  { .String = "/lib/x86_64-linux-gnu/", .Length = CSTRLEN("/lib/x86_64-linux-gnu/") },
41  { .String = "/usr/lib/", .Length = CSTRLEN("/usr/lib/") },
42  { .String = "/usr/bin/", .Length = CSTRLEN("/usr/bin/") },
43  { .String = "/usr/", .Length = CSTRLEN("/usr/") },
44  { .String = "/bin/", .Length = CSTRLEN("/bin/") },
45  { .String = "/sbin/", .Length = CSTRLEN("/sbin/") },
46  { .String = "/lib/", .Length = CSTRLEN("/lib/") },
47 };
48 
52 static LIST_HEAD gLixDentryCache = LIST_HEAD_INIT(gLixDentryCache);
53 
54 static char gLixPath[PAGE_SIZE] = { 0 };
55 
56 
57 #define for_each_dentry(_var_name) list_for_each(gLixDentryCache, DENTRY_PATH, _var_name)
58 
59 
60 static BOOLEAN
62  _In_ char *Path
63  )
72 {
73  for (DWORD index = 0; index < ARRAYSIZE(gLixDentryCacheStrings); index++)
74  {
75  if (memcmp(gLixDentryCacheStrings[index].String, Path, gLixDentryCacheStrings[index].Length) == 0)
76  {
77  return TRUE;
78  }
79  }
80 
81  return FALSE;
82 }
83 
84 
85 void
87  void
88  )
92 {
93  for_each_dentry(pDentry)
94  {
95  RemoveEntryList(&pDentry->Link);
96 
97  if (pDentry->Path)
98  {
99  HpFreeAndNullWithTag(&pDentry->Path, IC_TAG_NAME);
100  }
101 
103  }
104 }
105 
106 
107 CHAR *
109  _In_ char *Path,
110  _In_ DWORD Length,
111  _In_ QWORD DentryGva
112  )
124 {
125  for_each_dentry(pExtDentry)
126  {
127  if (pExtDentry->Length == Length && 0 == strcmp(pExtDentry->Path, Path))
128  {
129  LOG("[LIX-FILES] Update cache for path '%s' with length %d from %llx to %llx\n",
130  pExtDentry->Path, pExtDentry->Length, pExtDentry->Gva, DentryGva);
131 
132  pExtDentry->Gva = DentryGva;
133 
134  return pExtDentry->Path;
135  }
136  }
137 
138  if (Length > sizeof(gLixPath))
139  {
140  ERROR("[ERROR] The length (0x%x) of the 'd_entry' path exceed the our internal buffer\n", Length);
141  return NULL;
142  }
143 
144  DENTRY_PATH *pDentry = HpAllocWithTag(sizeof(*pDentry), IC_TAG_NAME);
145  if (NULL == pDentry)
146  {
147  return NULL;
148  }
149 
150  pDentry->Path = HpAllocWithTag((size_t)Length + 1, IC_TAG_NAME);
151  if (NULL == pDentry->Path)
152  {
154 
155  return NULL;
156  }
157 
158  pDentry->Gva = DentryGva;
159  pDentry->Length = Length;
160  memcpy(pDentry->Path, Path, Length);
161 
162  pDentry->Path[Length] = 0;
163 
164  InsertTailList(&gLixDentryCache, &pDentry->Link);
165 
166  return pDentry->Path;
167 }
168 
169 
170 DENTRY_PATH *
172  _In_ QWORD DentryGva
173  )
181 {
182  for_each_dentry(pDentry)
183  {
184  if (pDentry->Gva == DentryGva)
185  {
186  return pDentry;
187  }
188  }
189 
190  return NULL;
191 }
192 
193 
194 INTSTATUS
196  _In_ QWORD File,
197  _Out_ QWORD *Dentry
198  )
208 {
209  INTSTATUS status = IntKernVirtMemFetchQword(File + LIX_FIELD(Ungrouped, FileDentry), Dentry);
210  if (!INT_SUCCESS(status))
211  {
212  ERROR("[ERROR] IntKernVirtMemFetchQword failed with status: 0x%08x\n", status);
213  return status;
214  }
215 
216  if (!IS_KERNEL_POINTER_LIX(*Dentry))
217  {
218  ERROR("[ERROR] The value of the dentry is not a linux-kernel pointer!\n");
220  }
221 
222  return INT_STATUS_SUCCESS;
223 }
224 
225 
226 INTSTATUS
228  _In_ QWORD Dentry,
229  _Outptr_ char **FileName,
230  _Out_opt_ DWORD *NameLength
231  )
247 {
248  INTSTATUS status;
249  LIX_QSTR qstr;
250 
251  if (!IS_KERNEL_POINTER_LIX(Dentry))
252  {
254  }
255 
256  if (FileName == NULL)
257  {
259  }
260 
261  status = IntKernVirtMemRead(Dentry + LIX_FIELD(Dentry, Name), sizeof(qstr), &qstr, NULL);
262  if (!INT_SUCCESS(status))
263  {
264  ERROR("[ERROR] IntKernVirtMemRead failed for GVA 0x%016llx: 0x%08x\n",
265  Dentry + LIX_FIELD(Dentry, Name), status);
266  return status;
267  }
268 
269  if (qstr.Length == 0 || !IS_KERNEL_POINTER_LIX(qstr.Name))
270  {
271  ERROR("[ERROR] Invalid q_str {%llx; %d} dentry %llx\n", qstr.Name, qstr.Length, Dentry);
273  }
274 
275  DWORD readLen = MIN(qstr.Length, LIX_MAX_PATH);
276 
277  char *fileName = HpAllocWithTag(readLen + 1ull, IC_TAG_NAME);
278  if (NULL == fileName)
279  {
281  }
282 
283  status = IntKernVirtMemRead(qstr.Name, readLen, fileName, NULL);
284  if (!INT_SUCCESS(status))
285  {
286  ERROR("[ERROR] IntKernVirtMemRead failed for GVA 0x%016llx: 0x%08x\n", qstr.Name, status);
287  HpFreeAndNullWithTag(&fileName, IC_TAG_NAME);
288  return status;
289  }
290 
291  *FileName = fileName;
292 
293  if (NameLength)
294  {
295  *NameLength = readLen;
296  }
297 
298  return INT_STATUS_SUCCESS;
299 }
300 
301 
302 static INTSTATUS
304  _In_ QWORD DentryGva,
305  _Out_ char *Name,
307  )
318 {
319  INTSTATUS status = INT_STATUS_SUCCESS;
320  LIX_QSTR qstr = { 0 };
321 
322  status = IntKernVirtMemRead(DentryGva + LIX_FIELD(Dentry, Name), sizeof(qstr), &qstr, NULL);
323  if (!INT_SUCCESS(status))
324  {
325  ERROR("[ERROR] IntKernVirtMemRead failed for GVA 0x%016llx: 0x%08x\n",
326  DentryGva + LIX_FIELD(Dentry, Name), status);
327  return status;
328  }
329 
330  if (qstr.Length == 0 || qstr.Length >= LIX_MAX_PATH)
331  {
332  ERROR("[ERROR] Invalid q_str length in dentry %llx: %d)\n", DentryGva, qstr.Length);
334  }
335 
336  status = IntKernVirtMemRead(qstr.Name, qstr.Length, Name, NULL);
337  if (!INT_SUCCESS(status))
338  {
339  ERROR("[ERROR] IntKernVirtMemRead failed for GVA 0x%016llx: 0x%08x\n", qstr.Name, status);
340  return status;
341  }
342 
343  *(Name + qstr.Length) = 0;
344 
345  *Length = qstr.Length;
346 
347  return INT_STATUS_SUCCESS;
348 }
349 
350 
351 INTSTATUS
353  _In_ QWORD FileStructGva,
354  _Out_ char **Path,
356  )
373 {
374  INTSTATUS status = INT_STATUS_SUCCESS;
375  DENTRY_PATH *pDentry = NULL;
376  QWORD cacheDentryGva = 0;
377  QWORD crtDentryGva = 0;
378  QWORD parentDentry = 0;
379  QWORD prevHashList = 0;
380  DWORD fileNameLength = 0;
381  DWORD dentryLevel = 0;
382  INT32 index = 0;
383  char tmpOutput[LIX_MAX_PATH] = { 0 };
384 
385  if (!IS_KERNEL_POINTER_LIX(FileStructGva))
386  {
388  }
389 
390  if (Path == NULL)
391  {
393  }
394 
395  *Path = NULL;
396 
397  status = IntLixFileGetDentry(FileStructGva, &crtDentryGva);
398  if (!INT_SUCCESS(status))
399  {
400  ERROR("[ERROR] IntLixFileGetDentry failed for file %llx: 0x%08x\n", FileStructGva, status);
401  return status;
402  }
403 
404  status = IntKernVirtMemFetchQword(crtDentryGva + LIX_FIELD(Dentry, Parent), &parentDentry);
405  if (!INT_SUCCESS(status))
406  {
407  ERROR("[ERROR] IntKernVirtMemFetchQword failed for GVA 0x%016llx: 0x%08x\n",
408  crtDentryGva + LIX_FIELD(Dentry, Parent), status);
409  return status;
410  }
411 
412  status = IntKernVirtMemFetchQword(crtDentryGva + LIX_FIELD(Dentry, HashList) + sizeof(QWORD), &prevHashList);
413  if (!INT_SUCCESS(status))
414  {
415  ERROR("[ERROR] IntKernVirtMemFetchQword failed for GVA 0x%016llx: 0x%08x\n",
416  crtDentryGva + LIX_FIELD(Dentry, HashList) + sizeof(QWORD), status);
417  return status;
418  }
419 
420  if (!prevHashList && crtDentryGva == parentDentry)
421  {
422  DWORD length = CSTRLEN("(deleted)");
423  gLixPath[sizeof(gLixPath) - 1] = 0;
424  memcpy(gLixPath + sizeof(gLixPath) - length - 1, "(deleted)", length);
425  *Path = gLixPath + sizeof(gLixPath) - length - 1;
426 
427  if (Length)
428  {
429  *Length = length;
430  }
431 
432  return INT_STATUS_SUCCESS;
433  }
434 
435  status = IntLixFileReadDentry(crtDentryGva, tmpOutput, &fileNameLength);
436  if (!INT_SUCCESS(status))
437  {
438  ERROR("[ERROR] IntLixFileReadDentry failed for dentry @ 0x%016llx : %08x", crtDentryGva, status);
439  return status;
440  }
441 
442  index = sizeof(gLixPath) -1;
443  gLixPath[index] = 0;
444 
445  // fileNameLength is ok, it can be maximum LIX_MAX_PATH (256). Check out IntLixFileReadDentry.
446  index -= fileNameLength;
447  memcpy(gLixPath + index, tmpOutput, fileNameLength);
448 
449  index--;
450  gLixPath[index] = '/';
451 
452  status = IntKernVirtMemFetchQword(crtDentryGva + LIX_FIELD(Dentry, Parent), &crtDentryGva);
453  if (!INT_SUCCESS(status))
454  {
455  ERROR("[ERROR] IntKernVirtMemFetchQword failed for GVA 0x%016llx: 0x%08x\n",
456  crtDentryGva + LIX_FIELD(Dentry, Parent), status);
457  return status;
458  }
459 
460  if (!IS_KERNEL_POINTER_LIX(crtDentryGva))
461  {
463  }
464 
465  cacheDentryGva = crtDentryGva;
466 
467  while (crtDentryGva && dentryLevel < LIX_MAX_DENTRY_DEPTH)
468  {
469  QWORD dentryParentGva = 0;
470  DWORD crtNameLength = 0;
471 
472  pDentry = IntLixFileCacheFindDentry(crtDentryGva);
473  if (pDentry)
474  {
475  break;
476  }
477 
478  status = IntLixFileReadDentry(crtDentryGva, tmpOutput, &crtNameLength);
479  if (!INT_SUCCESS(status))
480  {
481  ERROR("[ERROR] IntLixFileReadDentry failed for dentry @ 0x%016llx : %08x", crtDentryGva, status);
482  return status;
483  }
484 
485  status = IntKernVirtMemFetchQword(crtDentryGva + LIX_FIELD(Dentry, Parent), &dentryParentGva);
486  if (!INT_SUCCESS(status))
487  {
488  ERROR("[ERROR] IntKernVirtMemFetchQword failed for GVA 0x%016llx: 0x%08x\n",
489  crtDentryGva + LIX_FIELD(Dentry, Parent), status);
490  return status;
491  }
492 
493  // crtNameLength can be max LIX_MAX_PATH.
494  index -= crtNameLength;
495  if (index <= 0)
496  {
497  ERROR("[ERROR] Path for file 0x%llx is too big\n", FileStructGva);
499  }
500 
501  memcpy(gLixPath + index, tmpOutput, crtNameLength);
502  if (dentryParentGva != crtDentryGva)
503  {
504  index--;
505  gLixPath[index] = '/';
506  }
507  else if (index < (INT32)sizeof(gLixPath) - 1)
508  {
509  // 99.9% of cases the parent is '/', so handle that
510  if (gLixPath[index] == '/' && gLixPath[index + 1] == '/')
511  {
512  index++;
513  }
514  }
515 
516  if (!IS_KERNEL_POINTER_LIX(dentryParentGva))
517  {
518  ERROR("[ERROR] Got to a invalid parent %llx in dentry %llx!\n", dentryParentGva, crtDentryGva);
519  break;
520  }
521  else if (dentryParentGva == crtDentryGva)
522  {
523  break;
524  }
525 
526  crtDentryGva = dentryParentGva;
527 
528  dentryLevel++;
529  }
530 
531  if (!pDentry && IntLixFileCachePathIsValid(gLixPath + index))
532  {
534  sizeof(gLixPath) - index - fileNameLength - 1,
535  cacheDentryGva);
536  }
537  else if (pDentry)
538  {
539  // The length of the 'd_entry' path is validated when the cache entry is created.
540  INT32 size = index - pDentry->Length + sizeof(char);
541 
542  if (size < 0)
543  {
544  ERROR("[ERROR] The length (0x%08x) of the 'd_entry' path underflows the our buffer (0x%08x)\n",
545  pDentry->Length, index);
547  }
548 
549  memcpy(gLixPath + index - pDentry->Length + 1, pDentry->Path, pDentry->Length);
550  index -= pDentry->Length - 1;
551  }
552 
553  *Path = gLixPath + index;
554 
555  if (Length != NULL)
556  {
557  *Length = sizeof(gLixPath) - index - 1;
558  }
559 
560  return INT_STATUS_SUCCESS;
561 }
562 
563 
564 INTSTATUS
566  _In_ QWORD FileStruct,
567  _Outptr_ char **FileName,
568  _Out_opt_ DWORD *NameLength,
569  _Out_opt_ QWORD *DentryGva
570  )
583 {
584 
585  QWORD dentry;
586  INTSTATUS status;
587 
588  if (!IS_KERNEL_POINTER_LIX(FileStruct))
589  {
591  }
592 
593  if (NULL == FileName)
594  {
596  }
597 
598  status = IntLixFileGetDentry(FileStruct, &dentry);
599  if (!INT_SUCCESS(status))
600  {
601  ERROR("[ERROR] IntLixFileGetDentry failed for file %llx: 0x%08x\n", FileStruct, status);
602  return status;
603  }
604 
605  if (DentryGva)
606  {
607  *DentryGva = dentry;
608  }
609 
610  return IntLixDentryGetName(dentry, FileName, NameLength);
611 }
#define _Out_
Definition: intro_sal.h:22
_Bool BOOLEAN
Definition: intro_types.h:58
#define for_each_dentry(_var_name)
Definition: lixfiles.c:57
#define LIX_MAX_DENTRY_DEPTH
The maximum entries to be parsed.
Definition: lixfiles.h:38
struct _DENTRY_STRING * PDENTRY_STRING
char * String
The path (string).
Definition: lixfiles.c:28
#define _In_
Definition: intro_sal.h:21
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
Describes a path that will be cached.
Definition: lixfiles.c:26
#define CSTRLEN(String)
Definition: introdefs.h:105
INTSTATUS IntLixDentryGetName(QWORD Dentry, char **FileName, DWORD *NameLength)
Gets the file-name that corresponds to the provided Dentry (guest virtual address).
Definition: lixfiles.c:227
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
#define ARRAYSIZE(A)
Definition: introdefs.h:101
int32_t INT32
Definition: intro_types.h:44
INTSTATUS IntLixFileGetPath(QWORD FileStructGva, char **Path, DWORD *Length)
Gets the path that corresponds to the provided FileStructGva (guest virtual address of the &#39;struct fi...
Definition: lixfiles.c:352
#define ERROR(fmt,...)
Definition: glue.h:62
#define _Outptr_
Definition: intro_sal.h:19
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
int INTSTATUS
The status data type.
Definition: introstatus.h:24
char * Path
The content of the path (string).
Definition: lixfiles.c:16
#define MIN(a, b)
Definition: introdefs.h:146
#define LOG(fmt,...)
Definition: glue.h:61
static INTSTATUS IntLixFileReadDentry(QWORD DentryGva, char *Name, DWORD *Length)
Reads the name and the length form &#39;struct dentry&#39;.
Definition: lixfiles.c:303
#define _Out_opt_
Definition: intro_sal.h:30
static char gLixPath[PAGE_SIZE]
Definition: lixfiles.c:54
#define IS_KERNEL_POINTER_LIX(p)
Definition: lixguest.h:11
INTSTATUS IntKernVirtMemFetchQword(QWORD GuestVirtualAddress, QWORD *Data)
Reads 8 bytes from the guest kernel memory.
Definition: introcore.c:811
LIST_ENTRY Link
///< List entry element.
Definition: lixfiles.c:14
static BOOLEAN RemoveEntryList(LIST_ENTRY *Entry)
Definition: introlists.h:87
struct _DENTRY_PATH DENTRY_PATH
Describes an entry from dentry-cache.
unsigned long long QWORD
Definition: intro_types.h:53
#define TRUE
Definition: intro_types.h:30
static BOOLEAN IntLixFileCachePathIsValid(char *Path)
Verify if the provided path starts with at least one entry from gLixDentryCacheStrings.
Definition: lixfiles.c:61
#define LIX_FIELD(Structure, Field)
Macro used to access fields inside the LIX_OPAQUE_FIELDS structure.
Definition: lixguest.h:429
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
static LIST_HEAD gLixDentryCache
A list that contains the cached entries.
Definition: lixfiles.c:52
void IntLixFilesCacheUninit(void)
Removes and frees the entries of the dentry-cache.
Definition: lixfiles.c:86
static void InsertTailList(LIST_ENTRY *ListHead, LIST_ENTRY *Entry)
Definition: introlists.h:135
#define PAGE_SIZE
Definition: common.h:70
DWORD Length
The length of the string.
Definition: lixfiles.h:20
uint32_t DWORD
Definition: intro_types.h:49
INTSTATUS IntLixGetFileName(QWORD FileStruct, char **FileName, DWORD *NameLength, QWORD *DentryGva)
Gets the file-name that corresponds to the provided FileStruct (guest virtual address).
Definition: lixfiles.c:565
#define LIX_MAX_PATH
The maximum length of a dentry-path.
Definition: lixfiles.h:33
#define IC_TAG_NAME
Object name.
Definition: memtags.h:56
QWORD Name
A pointer to the string.
Definition: lixfiles.h:26
CHAR * IntLixFileCacheCreateDentryPath(char *Path, DWORD Length, QWORD DentryGva)
Creates a new cache entry and returns the path string from the newly created entry.
Definition: lixfiles.c:108
INTSTATUS IntLixFileGetDentry(QWORD File, QWORD *Dentry)
Reads the value of the dentry field of the &#39;struct file&#39;.
Definition: lixfiles.c:195
DENTRY_PATH * IntLixFileCacheFindDentry(QWORD DentryGva)
Search for an entry that has the provided DentryGva in the gLixDentryCache array. ...
Definition: lixfiles.c:171
struct _DENTRY_STRING DENTRY_STRING
Describes a path that will be cached.
INTSTATUS IntKernVirtMemRead(QWORD KernelGva, DWORD Length, void *Buffer, DWORD *RetLength)
Reads data from a guest kernel virtual memory range.
Definition: introcore.c:674
struct _DENTRY_PATH * PDENTRY_PATH
#define LIST_HEAD_INIT(Name)
Definition: introlists.h:39
#define INT_STATUS_INVALID_PARAMETER_1
Definition: introstatus.h:62
#define INT_STATUS_NOT_SUPPORTED
Definition: introstatus.h:287
static DENTRY_STRING gLixDentryCacheStrings[]
An array that contains the paths that will be cached.
Definition: lixfiles.c:38
Describes a string used for paths by the linux kernel (quick string).
Definition: lixfiles.h:13
char CHAR
Definition: intro_types.h:56
DWORD Length
The length of the path.
Definition: lixfiles.c:17
Describes an entry from dentry-cache.
Definition: lixfiles.c:12
#define INT_STATUS_INVALID_PARAMETER_2
Definition: introstatus.h:65
QWORD Gva
The guest virtual address of the &#39;struct dentry&#39;.
Definition: lixfiles.c:19
#define INT_STATUS_INVALID_DATA_SIZE
Definition: introstatus.h:142
#define FALSE
Definition: intro_types.h:34
#define INT_STATUS_INSUFFICIENT_RESOURCES
Definition: introstatus.h:281
DWORD Length
The length of the path.
Definition: lixfiles.c:29