From patchwork Fri Aug 18 21:02:30 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christopher Clark X-Patchwork-Id: 9909931 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id A288F60385 for ; Fri, 18 Aug 2017 21:06:46 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 926AE28CA1 for ; Fri, 18 Aug 2017 21:06:46 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 8757528D2C; Fri, 18 Aug 2017 21:06:46 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-3.6 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_MED, RCVD_IN_SORBS_SPAM, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from lists.xenproject.org (lists.xenproject.org [192.237.175.120]) (using TLSv1.2 with cipher AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 87DF528CA1 for ; Fri, 18 Aug 2017 21:06:45 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=lists.xenproject.org) by lists.xenproject.org with esmtp (Exim 4.84_2) (envelope-from ) id 1dioQD-0006QD-V4; Fri, 18 Aug 2017 21:03:13 +0000 Received: from mail6.bemta5.messagelabs.com ([195.245.231.135]) by lists.xenproject.org with esmtp (Exim 4.84_2) (envelope-from ) id 1dioQD-0006Q7-4F for xen-devel@lists.xen.org; Fri, 18 Aug 2017 21:03:13 +0000 Received: from [85.158.139.211] by server-10.bemta-5.messagelabs.com id 87/FD-01732-01657995; Fri, 18 Aug 2017 21:03:12 +0000 X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFprMIsWRWlGSWpSXmKPExsXiVRvsossfNj3 SYNsfSYslHxezODB6HN39mymAMYo1My8pvyKBNaNr/22mgkelFU3/G9kaGGcmdDFycQgJ9DNK XH63mhXEYRG4xCLx7fszNhBHQuAdi8TMMzMYuxg5gZwkiWWPJgFVcQDZVRJPj2SAhIUEdCTa/ t1khJi0kUmi/fhZNpAEm4CyxN1VbewgtoiAtMS1z5cZQXqZBSQlTt7PBwkLC/hJrHi2GaycRU BV4uuMRawgNq+Ar8Sv9TuZINbKSdw818k8gZFvASPDKkaN4tSistQiXSNLvaSizPSMktzEzBx dQwNTvdzU4uLE9NScxKRiveT83E2MwECpZ2Bg3MF4eYvfIUZJDiYlUd7fs6ZECvEl5adUZiQW Z8QXleakFh9ilOHgUJLg9QyZHikkWJSanlqRlpkDDFmYtAQHj5IIbwVImre4IDG3ODMdInWK0 ZLjwf81X5g4NqxeDyQnHdj+hUmIJS8/L1VKnFcxFKhBAKQhozQPbhwsri4xykoJ8zIyMDAI8R SkFuVmlqDKv2IU52BUEuYNBJnCk5lXArf1FdBBTEAHGbZOAzmoJBEhJdXAWPshbtHjvn/FUia K8kvCo5N1SthNxZzZNESaDSUTz7/7coAtsfDj7i2/T/2xCQtbal/kbLh81q64i5MCb19T9ip9 8dbKIHsXS0z/z+DU/AdW+wUnLGPN+Mj3488MWxuGad+c5bnVhfMifqq8yNn438TV3XrWpdbO8 PslZ2tOCafsFi2JOD5ZiaU4I9FQi7moOBEAjpycNqYCAAA= X-Env-Sender: christopher.w.clark@gmail.com X-Msg-Ref: server-7.tower-206.messagelabs.com!1503090189!105022385!1 X-Originating-IP: [74.125.83.68] X-SpamReason: No, hits=0.5 required=7.0 tests=BODY_RANDOM_LONG X-StarScan-Received: X-StarScan-Version: 9.4.45; banners=-,-,- X-VirusChecked: Checked Received: (qmail 39403 invoked from network); 18 Aug 2017 21:03:11 -0000 Received: from mail-pg0-f68.google.com (HELO mail-pg0-f68.google.com) (74.125.83.68) by server-7.tower-206.messagelabs.com with AES128-GCM-SHA256 encrypted SMTP; 18 Aug 2017 21:03:11 -0000 Received: by mail-pg0-f68.google.com with SMTP id n4so1779938pgn.0 for ; Fri, 18 Aug 2017 14:03:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id; bh=3/t4QEGdsgnlY1LKpIpaEk9tESVn7ZjLOm8xVusE+AE=; b=i3cM5CewatIXwtyN5VIsFGv2CoOcvTvZaUXASP1jJqB96vpbx/pIDrEJp4kV3ho7pL bQhjAL9Fves276/fUseqN5sgYU9eASt+DAPMIXzkkNRJHab8x6J7kCJm6eTNtotS5Gyg AgaHmXlyiWHguVingfqSFsjI48loLWsG70z3KCG4jMXpjI/WyTAAl9tV0/L6qbAKi0U/ 02S0F9g2Ok4T/1JOTnpPEWq8OObkxNO8C4QXFe8ACeXF8n2TBK2QgoJRm74Dypja1gVL siqEgnrh80XK6mPPg5J2nwo4lQ9ZOBwRrqX9wPRk0lSjSBq/8mZXuy8oR2XH82Bjdx9M EP5A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=3/t4QEGdsgnlY1LKpIpaEk9tESVn7ZjLOm8xVusE+AE=; b=MoLqCGnvV0Q//IV3wcp9sct18PWquIXJyEcBWkQSWBj+nb1sws5hD3HRyO+W6/zjmu sRqL8YaWxDyUdZB27cd2Ma0SD6vdhGyHHyaj8AaMSh7bgvp+fYi9VcO7o5yY+0MP0Nf3 Jjbk9oM+KDSQcWtFW2ACKuFHhnLKKcuIi2gSOaSdRE5LJF11AeoVFEIF6Xt5RS5AsF2k 1Tnc6owRUY8ARV/WZUS9eUcUCisKlZaWtoOCnJnccAL00FjQkMtA9w0fr+fiJWc6ZaNm 3lvY1dCuZSFw05jdjOiUUFu6+To76k9XS/2kVmRaiOC70I0Tn1u3FrpGA1FYCPEASs+2 eu1g== X-Gm-Message-State: AHYfb5gmkza0e58qn/+yBWOiHsVVZKnBoBd7kIaRDJCwzMJ4lkd54Oln QkGlSmtYVAIOmUgNewk= X-Received: by 10.99.51.200 with SMTP id z191mr9700322pgz.329.1503090189036; Fri, 18 Aug 2017 14:03:09 -0700 (PDT) Received: from WorkStation-T3500.ice.pyrology.org (static-50-53-74-115.bvtn.or.frontiernet.net. [50.53.74.115]) by smtp.gmail.com with ESMTPSA id c19sm13634839pfk.3.2017.08.18.14.03.08 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 18 Aug 2017 14:03:08 -0700 (PDT) From: christopher.w.clark@gmail.com To: xen-devel@lists.xen.org Date: Fri, 18 Aug 2017 14:02:30 -0700 Message-Id: <1503090150-20715-1-git-send-email-christopher.w.clark@gmail.com> X-Mailer: git-send-email 2.7.4 Cc: dgdegra@tycho.nsa.gov Subject: [Xen-devel] [PATCH] xsm: policy hooks to require an IOMMU and interrupt remapping X-BeenThere: xen-devel@lists.xen.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Xen developer discussion List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: xen-devel-bounces@lists.xen.org Sender: "Xen-devel" X-Virus-Scanned: ClamAV using ClamSMTP From: Christopher Clark Isolation of devices passed through to domains usually requires an active IOMMU. The existing method of requiring an IOMMU is via a Xen boot parameter ("iommu=force") which will abort boot if an IOMMU is not available. More graceful degradation of behaviour when an IOMMU is absent can be achieved by enabling XSM to perform enforcement of IOMMU requirement. This patch enables an enforceable XSM policy to specify that an IOMMU is required for particular domains to access devices and how capable that IOMMU must be. This allows a Xen system to boot whilst still ensuring that an IOMMU is active before permitting device use. Using a XSM policy ensures that the isolation properties remain enforced even when the large, complex toolstack software changes. For some hardware platforms interrupt remapping is a strict requirement for secure isolation. Not all IOMMUs provide interrupt remapping. The XSM policy can now optionally require interrupt remapping. The device use hooks now check whether an IOMMU is: * Active and securely isolating: -- current criteria for this is that interrupt remapping is ok * Active but interrupt remapping is not available * Not active This patch also updates the reference XSM policy to use the new primitives, with policy entries that do not require an active IOMMU. Signed-off-by: Christopher Clark Acked-by: Daniel De Graaf Reviewed-by: Ross Philipson Reviewed-by: Daniel De Graaf --- Patch author: Christopher Clark Copyright belongs to BAE Systems. Written for OpenXT. [OXT-826] The author is grateful to Daniel De Graaf, Stephen Smalley, Daniel Smith and Ross Philipson for feedback on earlier revisions of this patch. This patch was developed for OpenXT for the 2017 stable-7 release to ensure that a network interface card cannot be passed through to the network driver domain unless the IOMMU is active. Earlier versions of OpenXT had ensured this via logic in the toolstack, but this behaviour was discovered to have been lost after porting the upper level of the toolstack to use libxl. This motivated introduction of a robust way of ensuring that this important system policy would be preserved across any future toolstack changes. The XSM hook code in this patch is the same as in OpenXT; the reference policy is not. The hooks have been validated as behaving correctly on several generations of Dell and HP Intel-based hardware, with this patch applied to Xen 4.6, with and without interrupt remapping capability; and further testing with Xen 4.9 on a subset of that hardware. The reference policy in this patch has been compile-tested only. An OpenXT system will still boot even with the IOMMU disabled -- which is different behaviour than would be the case if the IOMMU was required via the Xen command line. The system retains its isolation from the network by preventing passthrough of the NIC(s) to the domain containing the device drivers, whilst still allowing user access to VMs stored locally on the system. Since OpenXT supports older hardware with less capable IOMMUs, its default configuration is to allow use without interrupt remapping, but derivative projects of OpenXT with different hardware support requirements are able to change their policy to the stronger setting that insists on interrupt remapping availability. Device isolation can be: Useful, eg. for resiliency against occasionally buggy devices or Necessary, eg. strictly required for system security and sometimes both are true: The hooks in this patch enable a single XSM policy to be created for a common software build that is usable across diverse hardware to express: * that allowing GPU passthrough to a particular class of VMs does require an IOMMU, but it can proceed without interrupt remapping, * whereas the network interface card is not allowed to be used by the network driver domain unless an IOMMU is active and it has interrupt remapping capability. tools/flask/policy/modules/nic_dev.te | 2 +- tools/flask/policy/modules/xen.if | 29 +++++++++++++++++++---- tools/flask/policy/modules/xen.te | 3 ++- xen/xsm/flask/hooks.c | 44 +++++++++++++++++++++++++++++------ xen/xsm/flask/policy/access_vectors | 20 ++++++++++++++-- 5 files changed, 83 insertions(+), 15 deletions(-) diff --git a/tools/flask/policy/modules/nic_dev.te b/tools/flask/policy/modules/nic_dev.te index e0484af..5206f1e 100644 --- a/tools/flask/policy/modules/nic_dev.te +++ b/tools/flask/policy/modules/nic_dev.te @@ -11,4 +11,4 @@ type nic_dev_t, resource_type; admin_device(dom0_t, nic_dev_t) -use_device(domU_t, nic_dev_t) +use_device_noiommu(domU_t, nic_dev_t) diff --git a/tools/flask/policy/modules/xen.if b/tools/flask/policy/modules/xen.if index ed0df4f..9126400 100644 --- a/tools/flask/policy/modules/xen.if +++ b/tools/flask/policy/modules/xen.if @@ -167,11 +167,32 @@ define(`make_device_model', ` # ################################################################################ -# use_device(domain, device) +# use_device_iommu(domain, device) # Allow a device to be used by a domain -define(`use_device', ` +# only if an IOMMU provides isolation. +define(`use_device_iommu', ` allow $1 $1_self:mmu exchange; - allow $1 $2:resource use; + allow $1 $2:resource use_iommu; + allow $1 domio_t:mmu { map_read map_write }; +') + +# use_device_iommu_nointremap(domain, device) +# Allow a device to be used by a domain +# only if an IOMMU is active, even if it does not support +# interrupt remapping. +# Allows acceptance of (typically older) less isolating hardware. +define(`use_device_iommu_nointremap', ` + allow $1 $1_self:mmu exchange; + allow $1 $2:resource { use_iommu use_iommu_nointremap }; + allow $1 domio_t:mmu { map_read map_write }; +') + +# use_device_noiommu(domain, device) +# Allow a device to be used by a domain +# even without an IOMMU available. +define(`use_device_noiommu', ` + allow $1 $1_self:mmu exchange; + allow $1 $2:resource { use_iommu use_iommu_nointremap use_noiommu }; allow $1 domio_t:mmu { map_read map_write }; ') @@ -180,7 +201,7 @@ define(`use_device', ` define(`admin_device', ` allow $1 $2:resource { setup stat_device add_device add_irq add_iomem add_ioport remove_device remove_irq remove_iomem remove_ioport plug unplug }; allow $1 $2:hvm bind_irq; - use_device($1, $2) + use_device_noiommu($1, $2) ') # delegate_devices(priv-domain, target-domain) diff --git a/tools/flask/policy/modules/xen.te b/tools/flask/policy/modules/xen.te index 0cff2df..3dbf93d 100644 --- a/tools/flask/policy/modules/xen.te +++ b/tools/flask/policy/modules/xen.te @@ -67,7 +67,8 @@ allow xen_t resource_type : resource { remove_irq remove_ioport remove_iomem }; neverallow * ~domain_type:domain { create transition }; # Resources must be declared using resource_type -neverallow * ~resource_type:resource use; +neverallow * ~resource_type:resource { use use_iommu use_iommu_nointremap + use_noiommu }; # Events must use event_type (see create_channel for a template) neverallow ~event_type *:event bind; diff --git a/xen/xsm/flask/hooks.c b/xen/xsm/flask/hooks.c index 9114627..276ca97 100644 --- a/xen/xsm/flask/hooks.c +++ b/xen/xsm/flask/hooks.c @@ -20,6 +20,7 @@ #include #include #include +#include #ifdef CONFIG_HAS_PCI #include #endif @@ -886,11 +887,31 @@ static int flask_map_domain_msi (struct domain *d, int irq, void *data, #endif } +static u32 flask_iommu_resource_use_perm(void) +{ + /* Obtain the permission level required for allowing a domain + * to use an assigned device. + * + * An active IOMMU with interrupt remapping capability is essential + * for ensuring strict isolation of devices, so provide a distinct + * permission for that case and also enable optional support for + * less capable hardware (no IOMMU or IOMMU missing intremap capability) + * via other separate permissions. + */ + u32 perm = RESOURCE__USE_NOIOMMU; + + if (iommu_enabled) + perm = ( iommu_intremap ? RESOURCE__USE_IOMMU : + RESOURCE__USE_IOMMU_NOINTREMAP ); + return perm; +} + static int flask_map_domain_irq (struct domain *d, int irq, void *data) { u32 sid, dsid; int rc = -EPERM; struct avc_audit_data ad; + u32 dperm = flask_iommu_resource_use_perm(); if ( irq >= nr_static_irqs && data ) { rc = flask_map_domain_msi(d, irq, data, &sid, &ad); @@ -907,7 +928,7 @@ static int flask_map_domain_irq (struct domain *d, int irq, void *data) if ( rc ) return rc; - rc = avc_has_perm(dsid, sid, SECCLASS_RESOURCE, RESOURCE__USE, &ad); + rc = avc_has_perm(dsid, sid, SECCLASS_RESOURCE, dperm, &ad); return rc; } @@ -956,6 +977,7 @@ static int flask_bind_pt_irq (struct domain *d, struct xen_domctl_bind_pt_irq *b int rc = -EPERM; int irq; struct avc_audit_data ad; + u32 dperm = flask_iommu_resource_use_perm(); rc = current_has_perm(d, SECCLASS_RESOURCE, RESOURCE__ADD); if ( rc ) @@ -972,7 +994,7 @@ static int flask_bind_pt_irq (struct domain *d, struct xen_domctl_bind_pt_irq *b return rc; dsid = domain_sid(d); - return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, RESOURCE__USE, &ad); + return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, dperm, &ad); } static int flask_unbind_pt_irq (struct domain *d, struct xen_domctl_bind_pt_irq *bind) @@ -990,6 +1012,7 @@ struct iomem_has_perm_data { u32 ssid; u32 dsid; u32 perm; + u32 use_perm; }; static int _iomem_has_perm(void *v, u32 sid, unsigned long start, unsigned long end) @@ -1007,7 +1030,7 @@ static int _iomem_has_perm(void *v, u32 sid, unsigned long start, unsigned long if ( rc ) return rc; - return avc_has_perm(data->dsid, sid, SECCLASS_RESOURCE, RESOURCE__USE, &ad); + return avc_has_perm(data->dsid, sid, SECCLASS_RESOURCE, data->use_perm, &ad); } static int flask_iomem_permission(struct domain *d, uint64_t start, uint64_t end, uint8_t access) @@ -1027,6 +1050,7 @@ static int flask_iomem_permission(struct domain *d, uint64_t start, uint64_t end data.ssid = domain_sid(current->domain); data.dsid = domain_sid(d); + data.use_perm = flask_iommu_resource_use_perm(); return security_iterate_iomem_sids(start, end, _iomem_has_perm, &data); } @@ -1041,7 +1065,7 @@ static int flask_pci_config_permission(struct domain *d, uint32_t machine_bdf, u u32 dsid, rsid; int rc = -EPERM; struct avc_audit_data ad; - u32 perm = RESOURCE__USE; + u32 perm; rc = security_device_sid(machine_bdf, &rsid); if ( rc ) @@ -1050,6 +1074,8 @@ static int flask_pci_config_permission(struct domain *d, uint32_t machine_bdf, u /* Writes to the BARs count as setup */ if ( access && (end >= 0x10 && start < 0x28) ) perm = RESOURCE__SETUP; + else + perm = flask_iommu_resource_use_perm(); AVC_AUDIT_DATA_INIT(&ad, DEV); ad.device = (unsigned long) machine_bdf; @@ -1279,6 +1305,7 @@ static int flask_assign_device(struct domain *d, uint32_t machine_bdf) u32 dsid, rsid; int rc = -EPERM; struct avc_audit_data ad; + u32 dperm = flask_iommu_resource_use_perm(); rc = current_has_perm(d, SECCLASS_RESOURCE, RESOURCE__ADD); if ( rc ) @@ -1295,7 +1322,7 @@ static int flask_assign_device(struct domain *d, uint32_t machine_bdf) return rc; dsid = domain_sid(d); - return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, RESOURCE__USE, &ad); + return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, dperm, &ad); } static int flask_deassign_device(struct domain *d, uint32_t machine_bdf) @@ -1334,6 +1361,7 @@ static int flask_assign_dtdevice(struct domain *d, const char *dtpath) u32 dsid, rsid; int rc = -EPERM; struct avc_audit_data ad; + u32 dperm = flask_iommu_resource_use_perm(); rc = current_has_perm(d, SECCLASS_RESOURCE, RESOURCE__ADD); if ( rc ) @@ -1350,7 +1378,7 @@ static int flask_assign_dtdevice(struct domain *d, const char *dtpath) return rc; dsid = domain_sid(d); - return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, RESOURCE__USE, &ad); + return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, dperm, &ad); } static int flask_deassign_dtdevice(struct domain *d, const char *dtpath) @@ -1476,6 +1504,7 @@ struct ioport_has_perm_data { u32 ssid; u32 dsid; u32 perm; + u32 use_perm; }; static int _ioport_has_perm(void *v, u32 sid, unsigned long start, unsigned long end) @@ -1493,7 +1522,7 @@ static int _ioport_has_perm(void *v, u32 sid, unsigned long start, unsigned long if ( rc ) return rc; - return avc_has_perm(data->dsid, sid, SECCLASS_RESOURCE, RESOURCE__USE, &ad); + return avc_has_perm(data->dsid, sid, SECCLASS_RESOURCE, data->use_perm, &ad); } static int flask_ioport_permission(struct domain *d, uint32_t start, uint32_t end, uint8_t access) @@ -1514,6 +1543,7 @@ static int flask_ioport_permission(struct domain *d, uint32_t start, uint32_t en data.ssid = domain_sid(current->domain); data.dsid = domain_sid(d); + data.use_perm = flask_iommu_resource_use_perm(); return security_iterate_ioport_sids(start, end, _ioport_has_perm, &data); } diff --git a/xen/xsm/flask/policy/access_vectors b/xen/xsm/flask/policy/access_vectors index 1f7eb35..f276f04 100644 --- a/xen/xsm/flask/policy/access_vectors +++ b/xen/xsm/flask/policy/access_vectors @@ -420,11 +420,27 @@ class resource # source = domain making the hypercall # target = domain which will no longer have access to the resource remove +# checked when using some core Xen devices (target xen_t) +# source = domain which will have access to the resource +# target = xen_t + use # checked when adding a resource to a domain: # source = domain which will have access to the resource # target = resource's security label -# also checked when using some core Xen devices (target xen_t) - use +# Requires an active IOMMU capable of interrupt remapping in order to +# enforce isolation. + use_iommu +# checked when adding a resource to a domain when an IOMMU is available +# but it is not capable of interrupt mapping: +# source = domain which will have access to the resource +# target = resource's security label +# Enable this to allow some less secure systems to still work. + use_iommu_nointremap +# checked when adding a resource to a domain when no IOMMU present: +# source = domain which will have access to the resource +# target = resource's security label +# Enable this to allow resource use without an active IOMMU. + use_noiommu # PHYSDEVOP_map_pirq and ioapic writes for dom0, when acting on real IRQs # For GSI interrupts, the IRQ's label is indexed by the IRQ number # For MSI interrupts, the label of the PCI device is used