mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 18:50:05 +01:00
In a classic HVI + Xen setup, the introspection engine would monitor legacy guest page-tables by marking them read-only inside the EPT; this way any modification explicitly made by the guest or implicitly made by the CPU page walker would trigger an EPT violation, which would be forwarded by Xen to the SVA and thus the HVI agent. The HVI agent would analyse the modification, and act upon it - for example, a virtual page may be remapped (its guest physical address changed inside the page-table), in which case the introspection logic would update the protection accordingly (remove EPT hook on the old gpa, and place a new EPT hook on the new gpa). In other cases, the modification may be of no interest to the introspection engine - for example, the accessed/dirty bits may be cleared by the operating system or the accessed/dirty bits may be set by the CPU page walker. In our tests we discovered that the vast majority of guest page-table modifications fall in the second category (especially on Windows 10 RS4 x64 - more than 95% of ALL the page-table modifications are irrelevant to us) - they are of no interest to the introspection logic, but they trigger a very costly EPT violation nonetheless. Therefore, we decided to make use of the new #VE & VMFUNC features in recent Intel CPUs to accelerate the guest page-tables monitoring in the following way: 1. Each monitored page-table would be flagged as being convertible inside the EPT, thus enabling the CPU to deliver a virtualization exception to he guest instead of generating a traditional EPT violation. 2. We inject a small filtering driver inside the protected guest VM, which would intercept the virtualization exception in order to handle guest page-table modifications. 3. We create a dedicated EPT view (altp2m) for the in-guest agent, which would isolate the agent from the rest of the operating system; the agent will switch in and out of the protected EPT view via the VMFUNC instruction placed inside a trampoline page, thus making the agent immune to malicious code inside the guest. This way, all the page-table accesses would generate a virtualization-exception inside the guest instead of a costly EPT violation; the #VE agent would emulate and analyse the modification, and decide whether it is relevant for the main introspection logic; if it is relevant, it would do a VMCALL and notify the introspection engine about the modification; otherwise, it would resume normal instruction execution, thus avoiding a very costly VM exit. Signed-off-by: Adrian Pop <apop@bitdefender.com> Reviewed-by: Tamas K Lengyel <tamas@tklengyel.com> Acked-by: Wei Liu <wei.liu2@citrix.com>
306 lines
9.1 KiB
C
306 lines
9.1 KiB
C
/******************************************************************************
|
|
*
|
|
* xc_altp2m.c
|
|
*
|
|
* Interface to altp2m related HVMOPs
|
|
*
|
|
* Copyright (c) 2015 Tamas K Lengyel (tamas@tklengyel.com)
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "xc_private.h"
|
|
#include <stdbool.h>
|
|
#include <xen/hvm/hvm_op.h>
|
|
|
|
int xc_altp2m_get_domain_state(xc_interface *handle, uint32_t dom, bool *state)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_get_domain_state;
|
|
arg->domain = dom;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
if ( !rc )
|
|
*state = arg->u.domain_state.state;
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
int xc_altp2m_set_domain_state(xc_interface *handle, uint32_t dom, bool state)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_set_domain_state;
|
|
arg->domain = dom;
|
|
arg->u.domain_state.state = state;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
int xc_altp2m_set_vcpu_enable_notify(xc_interface *handle, uint32_t domid,
|
|
uint32_t vcpuid, xen_pfn_t gfn)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_vcpu_enable_notify;
|
|
arg->domain = domid;
|
|
arg->u.enable_notify.vcpu_id = vcpuid;
|
|
arg->u.enable_notify.gfn = gfn;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
int xc_altp2m_create_view(xc_interface *handle, uint32_t domid,
|
|
xenmem_access_t default_access, uint16_t *view_id)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_create_p2m;
|
|
arg->domain = domid;
|
|
arg->u.view.view = -1;
|
|
arg->u.view.hvmmem_default_access = default_access;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
if ( !rc )
|
|
*view_id = arg->u.view.view;
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
int xc_altp2m_destroy_view(xc_interface *handle, uint32_t domid,
|
|
uint16_t view_id)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_destroy_p2m;
|
|
arg->domain = domid;
|
|
arg->u.view.view = view_id;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
/* Switch all vCPUs of the domain to the specified altp2m view */
|
|
int xc_altp2m_switch_to_view(xc_interface *handle, uint32_t domid,
|
|
uint16_t view_id)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_switch_p2m;
|
|
arg->domain = domid;
|
|
arg->u.view.view = view_id;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
int xc_altp2m_get_suppress_ve(xc_interface *handle, uint32_t domid,
|
|
uint16_t view_id, xen_pfn_t gfn, bool *sve)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_get_suppress_ve;
|
|
arg->domain = domid;
|
|
arg->u.suppress_ve.view = view_id;
|
|
arg->u.suppress_ve.gfn = gfn;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
if ( !rc )
|
|
*sve = arg->u.suppress_ve.suppress_ve;
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
int xc_altp2m_set_suppress_ve(xc_interface *handle, uint32_t domid,
|
|
uint16_t view_id, xen_pfn_t gfn, bool sve)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_set_suppress_ve;
|
|
arg->domain = domid;
|
|
arg->u.suppress_ve.view = view_id;
|
|
arg->u.suppress_ve.gfn = gfn;
|
|
arg->u.suppress_ve.suppress_ve = sve;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
int xc_altp2m_set_mem_access(xc_interface *handle, uint32_t domid,
|
|
uint16_t view_id, xen_pfn_t gfn,
|
|
xenmem_access_t access)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_set_mem_access;
|
|
arg->domain = domid;
|
|
arg->u.set_mem_access.view = view_id;
|
|
arg->u.set_mem_access.hvmmem_access = access;
|
|
arg->u.set_mem_access.gfn = gfn;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
int xc_altp2m_change_gfn(xc_interface *handle, uint32_t domid,
|
|
uint16_t view_id, xen_pfn_t old_gfn,
|
|
xen_pfn_t new_gfn)
|
|
{
|
|
int rc;
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
|
|
arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_change_gfn;
|
|
arg->domain = domid;
|
|
arg->u.change_gfn.view = view_id;
|
|
arg->u.change_gfn.old_gfn = old_gfn;
|
|
arg->u.change_gfn.new_gfn = new_gfn;
|
|
|
|
rc = xencall2(handle->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
xc_hypercall_buffer_free(handle, arg);
|
|
return rc;
|
|
}
|
|
|
|
int xc_altp2m_set_mem_access_multi(xc_interface *xch, uint32_t domid,
|
|
uint16_t view_id, uint8_t *access,
|
|
uint64_t *gfns, uint32_t nr)
|
|
{
|
|
int rc;
|
|
|
|
DECLARE_HYPERCALL_BUFFER(xen_hvm_altp2m_op_t, arg);
|
|
DECLARE_HYPERCALL_BOUNCE(access, nr * sizeof(*access),
|
|
XC_HYPERCALL_BUFFER_BOUNCE_IN);
|
|
DECLARE_HYPERCALL_BOUNCE(gfns, nr * sizeof(*gfns),
|
|
XC_HYPERCALL_BUFFER_BOUNCE_IN);
|
|
|
|
arg = xc_hypercall_buffer_alloc(xch, arg, sizeof(*arg));
|
|
if ( arg == NULL )
|
|
return -1;
|
|
|
|
arg->version = HVMOP_ALTP2M_INTERFACE_VERSION;
|
|
arg->cmd = HVMOP_altp2m_set_mem_access_multi;
|
|
arg->domain = domid;
|
|
arg->u.set_mem_access_multi.view = view_id;
|
|
arg->u.set_mem_access_multi.nr = nr;
|
|
|
|
if ( xc_hypercall_bounce_pre(xch, gfns) ||
|
|
xc_hypercall_bounce_pre(xch, access) )
|
|
{
|
|
PERROR("Could not bounce memory for HVMOP_altp2m_set_mem_access_multi");
|
|
return -1;
|
|
}
|
|
|
|
set_xen_guest_handle(arg->u.set_mem_access_multi.pfn_list, gfns);
|
|
set_xen_guest_handle(arg->u.set_mem_access_multi.access_list, access);
|
|
|
|
rc = xencall2(xch->xcall, __HYPERVISOR_hvm_op, HVMOP_altp2m,
|
|
HYPERCALL_BUFFER_AS_ARG(arg));
|
|
|
|
xc_hypercall_buffer_free(xch, arg);
|
|
xc_hypercall_bounce_post(xch, access);
|
|
xc_hypercall_bounce_post(xch, gfns);
|
|
|
|
return rc;
|
|
}
|