Welcome to mirror list, hosted at ThFree Co, Russian Federation.

shvvp.c - github.com/ionescu007/SimpleVisor.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 46fcf174435865898e72ed35aad809a8b0013059 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/*++

Copyright (c) Alex Ionescu.  All rights reserved.

Module Name:

    shvvp.c

Abstract:

    This module implements Virtual Processor (VP) management for the Simple Hyper Visor.

Author:

    Alex Ionescu (@aionescu) 16-Mar-2016 - Initial version

Environment:

    Kernel mode only, IRQL DISPATCH_LEVEL.

--*/

#include "shv.h"

BOOLEAN
ShvIsOurHypervisorPresent (
    VOID
    )
{
    INT cpuInfo[4];

    //
    // Check if ECX[31h] ("Hypervisor Present Bit") is set in CPUID 1h
    //
    __cpuid(cpuInfo, 1);
    if (cpuInfo[2] & HYPERV_HYPERVISOR_PRESENT_BIT)
    {
        //
        // Next, check if this is a compatible Hypervisor, and if it has the
        // SimpleVisor signature
        //
        __cpuid(cpuInfo, HYPERV_CPUID_INTERFACE);
        if (cpuInfo[0] == ' vhS')
        {
            //
            // It's us!
            //
            return TRUE;
        }
    }

    //
    // No Hypervisor, or someone else's
    //
    return FALSE;
}

VOID
ShvCaptureSpecialRegisters (
    _In_ PSHV_SPECIAL_REGISTERS SpecialRegisters
    )
{
    //
    // Use compiler intrinsics to get the data we need
    //
    SpecialRegisters->Cr0 = __readcr0();
    SpecialRegisters->Cr3 = __readcr3();
    SpecialRegisters->Cr4 = __readcr4();
    SpecialRegisters->DebugControl = __readmsr(MSR_DEBUG_CTL);
    SpecialRegisters->MsrGsBase = __readmsr(MSR_GS_BASE);
    SpecialRegisters->KernelDr7 = __readdr(7);
    _sgdt(&SpecialRegisters->Gdtr.Limit);
    __sidt(&SpecialRegisters->Idtr.Limit);

    //
    // Use assembly to get these two
    //
    _str(&SpecialRegisters->Tr);
    _sldt(&SpecialRegisters->Ldtr);
}

DECLSPEC_NORETURN
VOID
ShvVpRestoreAfterLaunch (
    VOID
    )
{
    PSHV_VP_DATA vpData = ShvGlobalData[KeGetCurrentProcessorNumberEx(NULL)];

    //
    // Record that VMX is now enabled
    //
    vpData->ContextFrame.EFlags |= EFLAGS_ALIGN_CHECK;

    //
    // And finally, restore the context, so that all register and stack
    // state is finally restored. Note that by continuing to reference the
    // per-VP data this way, the compiler will continue to generate non-
    // optimized accesses, guaranteeing that no previous register state
    // will be used.
    //
    RtlRestoreContext(&vpData->ContextFrame, NULL);
}

VOID
ShvVpInitialize (
    _In_ PSHV_VP_DATA Data
    )
{
    //
    // Read the special control registers for this processor
    // Note: KeSaveStateForHibernate(&Data->HostState) can be used as a Windows
    // specific undocumented function that can also get this data.
    //
    ShvCaptureSpecialRegisters(&Data->SpecialRegisters);

    //
    // Then, capture the entire register state. We will need this, as once we
    // launch the VM, it will begin execution at the defined guest instruction
    // pointer, which we set to ShvVpRestoreAfterLaunch, with the registers set
    // to whatever value they were deep inside the VMCS/VMX inialization code.
    // By using RtlRestoreContext, that function sets the AC flag in EFLAGS and
    // returns here with our registers restored.
    //
    RtlCaptureContext(&Data->ContextFrame);
    if ((__readeflags() & EFLAGS_ALIGN_CHECK) == 0)
    {
        //
        // If the AC bit is not set in EFLAGS, it means that we have not yet
        // launched the VM. Attempt to initialize VMX on this processor.
        //
        ShvVmxLaunchOnVp(Data);
    }
}

VOID
ShvVpUninitialize (
    VOID
    )
{
    INT dummy[4];

    //
    // Send the magic shutdown instruction sequence
    //
    __cpuidex(dummy, 0x41414141, 0x42424242);

    //
    // The processor will return here after the hypervisor issues a VMXOFF
    // instruction and restores the CPU context to this location. Unfortunately
    // because this is done with RtlRestoreContext which returns using "iretq",
    // this causes the processor to remove the RPL bits off the segments. As
    // the x64 kernel does not expect kernel-mode code to chang ethe value of
    // any segments, this results in the DS and ES segments being stuck 0x20,
    // and the FS segment being stuck at 0x50, until the next context switch.
    //
    // If the DPC happened to have interrupted either the idle thread or system
    // thread, that's perfectly fine (albeit unusual). If the DPC interrupted a
    // 64-bit long-mode thread, that's also fine. However if the DPC interrupts
    // a thread in compatibility-mode, running as part of WoW64, it will hit a
    // GPF instantenously and crash.
    //
    // Thus, set the segments to their correct value, one more time, as a fix.
    //
    ShvVmxCleanup(KGDT64_R3_DATA | RPL_MASK, KGDT64_R3_CMTEB | RPL_MASK);
}

PSHV_VP_DATA
ShvVpAllocateData (
    VOID
    )
{
    PHYSICAL_ADDRESS lowest, highest;
    PSHV_VP_DATA data;

    //
    // The entire address range is OK for this allocation
    //
    lowest.QuadPart = 0;
    highest.QuadPart = lowest.QuadPart - 1;

    //
    // Allocate a contiguous chunk of RAM to back this allocation and make sure
    // that it is RW only, instead of RWX, by using the new Windows 8 API.
    //
    data = MmAllocateContiguousNodeMemory(sizeof(SHV_VP_DATA),
                                          lowest,
                                          highest,
                                          lowest,
                                          PAGE_READWRITE,
                                          KeGetCurrentNodeNumber());
    if (data != NULL)
    {
        //
        // Zero out the entire data region
        //
        __stosq((PULONG64)data, 0, sizeof(SHV_VP_DATA) / sizeof(ULONG64));
    }

    //
    // Return what is hopefully a valid pointer, otherwise NULL.
    //
    return data;
}

VOID
ShvVpCallbackDpc (
    _In_ PRKDPC Dpc,
    _In_opt_ PVOID Context,
    _In_opt_ PVOID SystemArgument1,
    _In_opt_ PVOID SystemArgument2
    )
{
    PSHV_DPC_CONTEXT dpcContext = Context;
    ULONG cpuIndex;
    UNREFERENCED_PARAMETER(Dpc);

    //
    // Detect if the hardware appears to support VMX root mode to start.
    // No attempts are made to enable this if it is lacking or disabled.
    //
    if (!ShvVmxProbe())
    {
        dpcContext->FailureStatus = STATUS_HV_FEATURE_UNAVAILABLE;
        goto Quickie;
    }

    //
    // Check if we are loading, or unloading, and which CPU this is
    //
    cpuIndex = KeGetCurrentProcessorNumberEx(NULL);
    if (dpcContext->Cr3 != 0)
    {
        //
        // Allocate the per-VP data for this logical processor
        //
        ShvGlobalData[cpuIndex] = ShvVpAllocateData();
        if (ShvGlobalData[cpuIndex] == NULL)
        {
            dpcContext->FailureStatus = STATUS_HV_NO_RESOURCES;
            goto Quickie;
        }

        //
        // First, capture the value of the PML4 for the SYSTEM process, so that
        // all virtual processors, regardless of which process the current LP
        // has interrupted, can share the correct kernel address space.
        //
        ShvGlobalData[cpuIndex]->SystemDirectoryTableBase = dpcContext->Cr3;

        //
        // Initialize the virtual processor
        //
        ShvVpInitialize(ShvGlobalData[cpuIndex]);

        //
        // Our hypervisor should now be seen as present on this LP,
        // as the SHV correctly handles CPUID ECX features register.
        //
        if (ShvIsOurHypervisorPresent() == FALSE)
        {
            //
            // Free the per-processor data
            //
            MmFreeContiguousMemory(ShvGlobalData[cpuIndex]);
            ShvGlobalData[cpuIndex] = NULL;
            dpcContext->FailureStatus = STATUS_HV_NOT_PRESENT;
            dpcContext->FailedCpu = cpuIndex;
            goto Quickie;
        }

        //
        // This CPU is hyperjacked!
        //
        InterlockedIncrement(&dpcContext->InitCount);
    }
    else
    {
        //
        // Tear down the virtual processor
        //
        ShvVpUninitialize();
        NT_ASSERT(ShvIsOurHypervisorPresent() == FALSE);

        //
        // Free the VP data
        //
        //MmFreeContiguousMemory(ShvGlobalData[cpuIndex]);
        ShvGlobalData[cpuIndex] = NULL;
    }

Quickie:
    //
    // Wait for all DPCs to synchronize at this point
    //
    KeSignalCallDpcSynchronize(SystemArgument2);

    //
    // Mark the DPC as being complete
    //
    KeSignalCallDpcDone(SystemArgument1);
}