Bitdefender Hypervisor Memory Introspection
lixcrash.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "lixcrash.h"
6 #include "alerts.h"
7 #include "decoder.h"
8 #include "guests.h"
9 #include "lixksym.h"
10 
11 
12 #define MAX_STACKTRACES 16
13 #define MAX_FUNC_NAME 128
14 #define MAX_LOG_SIZE 512
15 
16 #define PREFIX_MAX 32
17 #define LOG_LINE_MAX (1024 - PREFIX_MAX)
18 
19 
23 #define LIX_SIGNAL_STOP_MASK ( \
24  BIT(SIGSTOP) | BIT(SIGTSTP) | \
25  BIT(SIGTTIN) | BIT(SIGTTOU) )
26 
30 #define LIX_SIGNAL_IGNORE_MASK (\
31  BIT(SIGCONT) | BIT(SIGCHLD) | \
32  BIT(SIGWINCH) | BIT(SIGURG) )
33 
45 #define LIX_SIGNAL_FATAL(sig) \
46  !((sig) > 0 && (sig) < SIGRTMIN && (BIT(sig) & (LIX_SIGNAL_IGNORE_MASK | LIX_SIGNAL_STOP_MASK)))
47 
48 
52 typedef struct _PRINTK_LOG_HEADER
53 {
59  BYTE Flags: 5;
60  BYTE Level: 3;
62 
63 
64 static INTSTATUS
66  void
67  )
74 {
76  EVENT_CRASH_EVENT *pCrashEvent = &gAlert.Crash;
77 
78  memzero(pCrashEvent, sizeof(*pCrashEvent));
79 
81 
82  status = IntNotifyIntroEvent(introEventCrashEvent, pCrashEvent, sizeof(*pCrashEvent));
83  if (!INT_SUCCESS(status))
84  {
85  ERROR("[ERROR] IntNotifyIntroEvent failed: %08x\n", status);
86  }
87 
88  return status;
89 }
90 
91 
92 static INTSTATUS
94  _Out_ QWORD *LogBufferGva,
95  _Out_ QWORD *LogBufferLengthGva,
96  _Out_ QWORD *LogFirstIdxGva
97  )
113 {
114  INTSTATUS status;
115  QWORD ksymLogBuffer;
116  QWORD ksymLogBufferLength = 0;
117  QWORD ksymLogFirstIdx = 0;
118  QWORD ksymStart = 0;
119  QWORD ksymEnd = 0;
120  QWORD ksymOffset;
121 
122  ksymLogBuffer = IntKsymFindByName("log_buf", NULL);
123  if (!ksymLogBuffer)
124  {
125  goto _disassemble;
126  }
127 
128  ksymLogBufferLength = IntKsymFindByName("log_buf_len", NULL);
129  if (!ksymLogBufferLength)
130  {
131  goto _disassemble;
132  }
133 
134  ksymLogFirstIdx = IntKsymFindByName("log_first_idx", NULL);
135  if (!ksymLogFirstIdx)
136  {
137  goto _disassemble;
138  }
139 
140  *LogBufferGva = ksymLogBuffer;
141  *LogBufferLengthGva = ksymLogBufferLength;
142  *LogFirstIdxGva = ksymLogFirstIdx;
143 
144  TRACE("[LIX CRASH] Found 'log_buf' at %llx, 'log_buf_len' at %llx and 'log_first_idx' at 0x%llx\n",
145  ksymLogBuffer, ksymLogBufferLength, ksymLogFirstIdx);
146 
147  return INT_STATUS_SUCCESS;
148 
149 _disassemble:
150  ksymStart = IntKsymFindByName("log_buf_kexec_setup", &ksymEnd);
151  if (!ksymStart)
152  {
153  ERROR("[ERROR] IntKsymFindByName could not find log_buf_kexec_setup\n");
154  return INT_STATUS_NOT_FOUND;
155  }
156 
157  // VMCOREINFO_SYMBOL(log_buf);
158  // VMCOREINFO_SYMBOL(log_buf_len);
159  ksymLogBuffer = 0;
160  ksymLogBufferLength = 0;
161  ksymOffset = 0;
162  while (ksymStart + ksymOffset < ksymEnd)
163  {
164  INSTRUX instrux;
165 
166  status = IntDecDecodeInstruction(IG_CS_TYPE_64B, ksymStart + ksymOffset, &instrux);
167  if (!INT_SUCCESS(status))
168  {
169  ERROR("[ERROR] IntDecDecodeInstructionFromBuffer failed with status: 0x%08x.\n", status);
170  ksymOffset++;
171  continue;
172  }
173 
174  ksymOffset += instrux.Length;
175 
176  if (instrux.Instruction == ND_INS_MOV &&
177  instrux.Operands[0].Type == ND_OP_REG &&
178  instrux.Operands[0].Info.Register.Reg == NDR_RDX &&
179  instrux.Operands[1].Type == ND_OP_IMM &&
180  IS_KERNEL_POINTER_LIX(instrux.Operands[1].Info.Immediate.Imm))
181  {
182  // RDX contains log_buf/log_buf_len address; 3rd argument of VMCOREINFO_SYMBOL
183  if (0 == ksymLogBuffer)
184  {
185  ksymLogBuffer = instrux.Operands[1].Info.Immediate.Imm;
186  }
187  else if (0 == ksymLogBufferLength)
188  {
189  ksymLogBufferLength = instrux.Operands[1].Info.Immediate.Imm;
190  }
191  else if (0 == ksymLogFirstIdx)
192  {
193  ksymLogFirstIdx = instrux.Operands[1].Info.Immediate.Imm;
194  }
195 
196  if (ksymLogBuffer && ksymLogBufferLength && ksymLogFirstIdx)
197  {
198  *LogBufferGva = ksymLogBuffer;
199  *LogBufferLengthGva = ksymLogBufferLength;
200  *LogFirstIdxGva = ksymLogFirstIdx;
201 
202  TRACE("[LIX CRASH] Found 'log_buf' at %llx, 'log_buf_len' at %llx and 'log_buf_idx' at %llx\n",
203  ksymLogBuffer, ksymLogBufferLength, ksymLogFirstIdx);
204 
205  return INT_STATUS_SUCCESS;
206  }
207  }
208  }
209 
210  return INT_STATUS_NOT_FOUND;
211 }
212 
213 
214 static BOOLEAN
216  _In_ DWORD Size
217  )
225 {
226  size_t totalHeapSize, freeHeapSize;
227 
228  INTSTATUS status = IntQueryHeapSize(&totalHeapSize, &freeHeapSize);
229  if (!INT_SUCCESS(status))
230  {
231  ERROR("[ERROR] IntQueryHeapSize failed: 0x%08x.\n", status);
232  return FALSE;
233  }
234 
235  return (freeHeapSize >= Size);
236 }
237 
238 
239 INTSTATUS
241  _In_ DWORD Signal,
242  _In_ LIX_TASK_OBJECT *Task
243  )
254 {
256  {
258  }
259 
260  INTSTATUS status = INT_STATUS_SUCCESS;
262  LIX_TRAP_FRAME trapFrame = { 0 };
263 
264  memzero(pEvent, sizeof(*pEvent));
265 
266  pEvent->ExceptionCode = Signal;
267 
268  status = IntLixTaskGetTrapFrame(Task, &trapFrame);
269  if (!INT_SUCCESS(status))
270  {
271  WARNING("[WARNING] IntLixTaskGetTrapFrame failed with status: 0x%08x\n", status);
272  pEvent->Rip = 0;
273  }
274  else
275  {
276  pEvent->Rip = trapFrame.Rip;
277  }
278 
279  pEvent->Continuable = !LIX_SIGNAL_FATAL(Signal);
280 
281  IntAlertFillLixProcess(Task, &pEvent->CurrentProcess);
282 
283  TRACE("[UMEXCEPTION] Code: 0x%08x at RIP 0x%016llx inside process '%s' (Pid %d, Cr3 0x%016llx). Continuable: %d\n",
284  Signal, pEvent->Rip, pEvent->CurrentProcess.ImageName, pEvent->CurrentProcess.Pid,
285  pEvent->CurrentProcess.Cr3, pEvent->Continuable);
286 
287  status = IntNotifyIntroEvent(introEventExceptionEvent, pEvent, sizeof(*pEvent));
288  if (!INT_SUCCESS(status))
289  {
290  WARNING("[WARNING] IntNotifyIntroEvent failed: 0x%08x\n", status);
291  }
292 
293  return status;
294 }
295 
296 
297 INTSTATUS
299  _In_ void *Detour
300  )
310 {
311  UNREFERENCED_PARAMETER(Detour);
312 
314  if (!pTask)
315  {
316  WARNING("[WARNING] IntLixTaskFindByGva failed for process 0x%016llx\n", gVcpu->Regs.R8);
317  goto _exit;
318  }
319 
321 
322 _exit:
323  if (pTask && pTask->MustKill)
324  {
325  LOG("[SIGNAL] Override signal %d with SIGKILL for task %llx\n",
326  (DWORD)(gVcpu->Regs.R9), pTask->Gva);
327  }
328 
329  INTSTATUS status = IntDetSetReturnValue(Detour, &gVcpu->Regs, (pTask && pTask->MustKill) ? SIGKILL : 0);
330  if (!INT_SUCCESS(status))
331  {
332  ERROR("[ERROR] IntDetSetReturnValue failed: %08x\n", status);
333  }
334 
335  return INT_STATUS_SUCCESS;
336 }
337 
338 
339 void
341  void
342  )
350 {
351  INTSTATUS status = INT_STATUS_SUCCESS;
352  CHAR dmesgLine[LOG_LINE_MAX];
353  PRINTK_LOG_HEADER *pHeader = NULL;
354  char *pLogBufferStart = NULL;
355  char *pLogBufferEnd = NULL;
356  QWORD logBufferPointerGva, logBufferLengthGva, logFirstIdxGva;
357  QWORD logBufferGva;
358  DWORD logBufferLength, logFirstIdx;
359  QWORD maxIterations = 0x10000;
360 
361  status = IntLixCrashFetchDmesgSymbol(&logBufferPointerGva, &logBufferLengthGva, &logFirstIdxGva);
362  if (!INT_SUCCESS(status))
363  {
364  ERROR("[ERROR] IntLixCrashFetchDmesgSymbol failed: 0x%08x\n", status);
365  return;
366  }
367 
368  status = IntKernVirtMemFetchQword(logBufferPointerGva, &logBufferGva);
369  if (!INT_SUCCESS(status))
370  {
371  ERROR("[ERROR] IntKernVirtMemFetchQword failed for %llx: 0x%08x\n", logBufferGva, status);
372  return;
373  }
374 
375  status = IntKernVirtMemFetchDword(logBufferLengthGva, &logBufferLength);
376  if (!INT_SUCCESS(status))
377  {
378  ERROR("[ERROR] IntKernVirtMemFetchDword failed for %llx: 0x%08x\n", logBufferLengthGva, status);
379  return;
380  }
381 
382  status = IntKernVirtMemFetchDword(logFirstIdxGva, &logFirstIdx);
383  if (!INT_SUCCESS(status))
384  {
385  ERROR("[ERROR] IntKernVirtMemFetchDword failed for %llx: 0x%08x\n", logFirstIdxGva, status);
386  return;
387  }
388 
389  if (!IntLixCrashEnoughHeapAvailable(logBufferLength))
390  {
391  WARNING("[WARNING] Not enough heap is available (requested %d bytes)\n", logBufferLength);
392  return;
393  }
394 
395  status = IntVirtMemMap(logBufferGva, logBufferLength, gGuest.Mm.SystemCr3, 0, &pLogBufferStart);
396  if (!INT_SUCCESS(status))
397  {
398  ERROR("[ERROR] IntVirtMemMap failed with status 0x%08x for @%llx.", status, logBufferGva);
399  return;
400  }
401 
402  if (gLixGuest->Version.Version < 3)
403  {
404  for (DWORD index = 0; index < logBufferLength; index++)
405  {
406  DWORD indexLine = 0;
407  while ((indexLine < sizeof(dmesgLine) - 1) &&
408  (index < logBufferLength) &&
409  (pLogBufferStart[index] != '\n'))
410  {
411  // The overflow (pLogBufferStart) check should be made using 'index' var because this is incremented
412  // after each copied char.
413  dmesgLine[indexLine++] = pLogBufferStart[index];
414  index++;
415  }
416 
417  dmesgLine[indexLine] = 0;
418  LOG("[DMESG]%s", dmesgLine);
419  }
420 
421  goto _exit;
422  }
423 
424  pLogBufferEnd = pLogBufferStart + logBufferLength;
425 
426  pHeader = (PRINTK_LOG_HEADER *)(pLogBufferStart + logFirstIdx);
427 
428  LOG("[INFO] start_idx is: %d", logFirstIdx);
429 
430  while (--maxIterations)
431  {
432  size_t toPrint;
433  const char *line = NULL;
434 
435  if ((char *)pHeader + sizeof(*pHeader) >= pLogBufferEnd)
436  {
437  ERROR("[ERROR] In-guest DMESG buffer may be corrupted!\n");
438  break;
439  }
440 
441  if (0 == pHeader->TextLength && logFirstIdx > 0)
442  {
443  LOG("[INFO] pHeader->TextLength is 0, will start from the beginning!");
444  pHeader = (PRINTK_LOG_HEADER *)pLogBufferStart;
445  continue;
446  }
447 
448  if (!pHeader->RecordLength)
449  {
450  LOG("[DMESG] End of DMESG\n");
451  break;
452  }
453 
454  toPrint = pHeader->TextLength + 1ull;
455 
456  if (toPrint > sizeof(dmesgLine))
457  {
458  WARNING("[WARNING] TextLength %lu is bigger than our buffer size: %lu\n",
459  toPrint, sizeof(dmesgLine));
460 
461  toPrint = sizeof(dmesgLine);
462  }
463 
464  line = (const char *)pHeader + sizeof(*pHeader);
465 
466  if (line + toPrint >= pLogBufferEnd)
467  {
468  ERROR("[ERROR] Last dmesg line is outside the mapped buffer. Will stop.");
469  break;
470  }
471 
472  memcpy(dmesgLine, line, toPrint);
473 
474  // Make sure the NULL terminator is there
475  dmesgLine[toPrint - 1] = 0;
476 
477  LOG("[DMESG] [%llu.%llu] %s\n", NSEC_TO_SEC(pHeader->TimeStamp), NSEC_TO_USEC(pHeader->TimeStamp), dmesgLine);
478 
479  pHeader = (PRINTK_LOG_HEADER *)((BYTE *)pHeader + pHeader->RecordLength);
480  }
481 
482  if (!maxIterations)
483  {
484  WARNING("[WARNING] Reached max iterations count. DMESG log may be truncated!");
485  }
486 _exit:
487 
488  IntVirtMemUnmap(&pLogBufferStart);
489 }
490 
491 
492 INTSTATUS
494  _In_ void *Detour
495  )
505 {
506  UNREFERENCED_PARAMETER(Detour);
507 
509 
511 
513 
514  return IntGuestUninitOnBugcheck(Detour);
515 }
_Bool BOOLEAN
Definition: intro_types.h:58
#define _Out_
Definition: intro_sal.h:22
INTSTATUS IntVirtMemUnmap(void **HostPtr)
Unmaps a memory range previously mapped with IntVirtMemMap.
Definition: introcore.c:2234
#define NSEC_TO_SEC(nsec)
Definition: introdefs.h:97
uint8_t BYTE
Definition: intro_types.h:47
IG_ARCH_REGS Regs
The current state of the guest registers.
Definition: guests.h:95
EVENT_EXCEPTION_EVENT Exception
Definition: alerts.h:28
#define _In_
Definition: intro_sal.h:21
QWORD SystemCr3
The Cr3 used to map the kernel.
Definition: guests.h:211
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
DWORD MustKill
Will kill the process with the first occasion.
Definition: lixprocess.h:101
#define LOG_LINE_MAX
Definition: lixcrash.c:17
uint16_t WORD
Definition: intro_types.h:48
QWORD Gva
The guest virtual address of the task_struct.
Definition: lixprocess.h:42
INTSTATUS IntLixTaskSendExceptionEvent(DWORD Signal, LIX_TASK_OBJECT *Task)
Sends an event that contains the information about signal received by the provided task...
Definition: lixcrash.c:240
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
INTSTATUS IntDetSetReturnValue(DETOUR const *Detour, IG_ARCH_REGS *Registers, QWORD ReturnValue)
Sets the return value for a hooked guest function.
Definition: detours.c:1528
Informational event sent when the guest crashes. See EVENT_CRASH_EVENT.
Definition: intro_types.h:109
#define INT_STATUS_NOT_NEEDED_HINT
Definition: introstatus.h:317
#define ERROR(fmt,...)
Definition: glue.h:62
Linux &#39;struct printk_log&#39; buffer header.
Definition: lixcrash.c:52
int INTSTATUS
The status data type.
Definition: introstatus.h:24
WORD DirectoryLength
Definition: lixcrash.c:57
#define INT_STATUS_NOT_FOUND
Definition: introstatus.h:284
BOOLEAN Continuable
True if the exception is considered to be continuable.
Definition: intro_types.h:1992
INTSTATUS IntQueryHeapSize(size_t *TotalHeapSize, size_t *FreeHeapSize)
Definition: glue.c:1112
EVENT_CRASH_EVENT Crash
Definition: alerts.h:27
Event structure for guest OS crashes.
Definition: intro_types.h:1967
#define LOG(fmt,...)
Definition: glue.h:61
#define INTRO_OPT_EVENT_PROCESS_CRASH
Enable application crash events (generates introEventExceptionEvent).
Definition: intro_types.h:449
INTSTATUS IntLixCrashPanicHandler(void *Detour)
Called if the &#39;panic&#39; or &#39;kcrash_exec&#39; handler is hit.
Definition: lixcrash.c:493
INTSTATUS IntGuestUninitOnBugcheck(void const *Detour)
Prepares Introcore unload in case of a guest crash in order to clean up the code and data injected in...
Definition: introcore.c:2522
struct _PRINTK_LOG_HEADER PRINTK_LOG_HEADER
Linux &#39;struct printk_log&#39; buffer header.
GENERIC_ALERT gAlert
Global alert buffer.
Definition: alerts.c:27
static BOOLEAN IntLixCrashEnoughHeapAvailable(DWORD Size)
Checks if the size of the free heap is bigger than the provided size.
Definition: lixcrash.c:215
INTSTATUS IntLixCrashHandle(void *Detour)
Sends an event that contains the information about signal received by the current task...
Definition: lixcrash.c:298
INTSTATUS IntKernVirtMemFetchDword(QWORD GuestVirtualAddress, DWORD *Data)
Reads 4 bytes from the guest kernel memory.
Definition: introcore.c:829
#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
INTSTATUS IntNotifyIntroEvent(INTRO_EVENT_TYPE EventClass, void *Param, size_t EventSize)
Notifies the integrator about an introspection alert.
Definition: glue.c:1042
#define memzero(a, s)
Definition: introcrt.h:35
INTRO_PROCESS CurrentProcess
The currently active process.
Definition: intro_types.h:1975
unsigned long long QWORD
Definition: intro_types.h:53
QWORD Current
The currently used options.
Definition: guests.h:236
#define TRUE
Definition: intro_types.h:30
#define LIX_SIGNAL_FATAL(sig)
Check if the provided signal is fatal.
Definition: lixcrash.c:45
#define TRACE(fmt,...)
Definition: glue.h:58
#define WARNING(fmt,...)
Definition: glue.h:60
#define UNREFERENCED_PARAMETER(P)
Definition: introdefs.h:29
#define SIGKILL
Definition: lixddefs.h:204
QWORD ExceptionCode
The code of the exception.
Definition: intro_types.h:1988
struct _PRINTK_LOG_HEADER * PPRINTK_LOG_HEADER
uint32_t DWORD
Definition: intro_types.h:49
INTSTATUS IntLixTaskGetTrapFrame(const LIX_TASK_OBJECT *Task, LIX_TRAP_FRAME *TrapFrame)
Retrieves the trap frame for a Linux task.
Definition: lixprocess.c:1098
INTRO_PROCESS CurrentProcess
The process in which the exception was triggered.
Definition: intro_types.h:1995
__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
Event structure for process exceptions.
Definition: intro_types.h:1983
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 INTSTATUS IntLixCrashSendPanicEvent(void)
Send an event, if the operating system crashed, that contains information about the task that generat...
Definition: lixcrash.c:65
static INTSTATUS IntLixCrashFetchDmesgSymbol(QWORD *LogBufferGva, QWORD *LogBufferLengthGva, QWORD *LogFirstIdxGva)
Find the address of the symbols &#39;log_buf&#39;, &#39;log_buf_len&#39; and &#39;log_first_idx&#39;.
Definition: lixcrash.c:93
QWORD Rip
The RIP at which the exception was triggered.
Definition: intro_types.h:1990
CHAR ImageName[ALERT_IMAGE_NAME_LEN]
Image base name of the current process..
Definition: intro_types.h:909
LIX_TASK_OBJECT * IntLixTaskFindByGva(QWORD TaskStruct)
Finds Linux process with the provided "task_struct" guest virtual address.
Definition: lixprocess.c:1025
VCPU_STATE * gVcpu
The state of the current VCPU.
Definition: guests.c:59
#define NSEC_TO_USEC(nsec)
Definition: introdefs.h:98
64-bit selector.
Definition: glueiface.h:188
void IntLixCrashDumpDmesg(void)
Dumps the &#39;dmesg&#39; buffer from guest.
Definition: lixcrash.c:340
Informational event sent when a hardware exception is triggered by a guest process. See EVENT_EXCEPTION_EVENT.
Definition: intro_types.h:111
void IntAlertFillLixCurrentProcess(INTRO_PROCESS *EventProcess)
Saves the current Linux process inside an event.
Definition: alerts.c:1310
BOOLEAN DisableOnReturn
Set to True if after returning from this event handler, introcore must be unloaded.
Definition: guests.h:328
BYTE Version
The version field of the version string.
Definition: lixguest.h:487
char CHAR
Definition: intro_types.h:56
QWORD IntKsymFindByName(const char *Name, QWORD *SymEnd)
Searches the given Name in kallsyms and returns the Start & End offset.
Definition: lixksym.c:1399
DWORD Pid
The PID of the process.
Definition: intro_types.h:905
void IntAlertFillLixProcess(const LIX_TASK_OBJECT *Task, INTRO_PROCESS *EventProcess)
Saves information about a Linux process inside an event.
Definition: alerts.c:1264
INTRO_PROT_OPTIONS CoreOptions
The activation and protection options for this guest.
Definition: guests.h:271
INTSTATUS IntDecDecodeInstruction(IG_CS_TYPE CsType, QWORD Gva, void *Instrux)
Decode an instruction from the provided guest linear address.
Definition: decoder.c:180
LINUX_GUEST * gLixGuest
Global variable holding the state of a Linux guest.
Definition: lixguest.c:30
#define FALSE
Definition: intro_types.h:34