Bug Descriptions
We find that Xen does NOT flush I/O TLB when iommu_unmap_page is invoked in PV
mode. Attackers may be able to use this bug to compromise Xen.
(When a PV guest OS adds a new page into its page table, the hypervisor should
set the new page-table page read-only if the page is writable before, and
consequently flush the IOTLB to avoid DMA attacks. )
The following code is from __get_page_type:
if ( unlikely((x & PGT_type_mask) != type) )
{
/* Special pages should not be accessible from devices. */
struct domain *d = page_get_owner(page);
if ( d && !is_hvm_domain(d) && unlikely(need_iommu(d)) )
{
if ( (x & PGT_type_mask) == PGT_writable_page )
iommu_unmap_page(d, mfn_to_gmfn(d, page_to_mfn(page)));
else if ( type == PGT_writable_page )
iommu_map_page(d, mfn_to_gmfn(d, page_to_mfn(page)),
page_to_mfn(page),
IOMMUF_readable|IOMMUF_writable);
}
}
Details
When Xen deals with a L2 page table entry update, the possible function flow is
as follows:
do_mmu_update()
->mod_l2_entry()
->get_page_from_l2e()
->get_page_and_type_from_pagenr
->get_page_type()
->__get_page_type
->iommu_unmap_page
->intel_iommu_unmap_page
->dma_pte_clear_one()
->__intel_iommu_iotlb_flush
->iommu_flush_iotlb_psi
->flush_iotlb_qi
We have noticed that the function: flush_iotlb_qi is supposed to complete the
task of invalidating I/O TLB. However, it ALWAYS returns before really flushing
the IOTLB.
In flush_iotlb_qi
if ( flush_non_present_entry )
{
if ( !cap_caching_mode(iommu->cap) )
return 1; <-- return from this point.
else
did = 0;
}
Similarly, the function: iommu_flush_write_buffer coming next to
iommu_flush_iotlb_psi directly returns also without write buffer flushing
because the if statement: !rwbf_quirk && !cap_rwbf(iommu->cap) are
also true.
Note that some internal information can be found at the end of this email, in
the log file.
Fix Suggestions
We observe that the argument: dma_old_pte_present in the function:
__intel_iommu_iotlb_flush is always assigned as 0 in the above function flow,
the value of flush_non_present_entry in flush_iotlb_qi is always 1, the negation
of dma_old_pte_present (!dma_old_pte_present).
We suggest to set dma_old_pte_present as 1 or collect the value from
dma_pte_present(pte).
Configurations
Our experimental platform is a HP Compaq 8100 Elite CMT PC with an Intel Core
i7-870 running at 2.93 GHz with vt-d feature enabled and we use Xen version
4.2.1 as the hypervisor while domain0 uses the kernel version
3.2.0-51-generic-pae.
To obtain the same control flow, we need to do some configurations.
We have enabled the Intel VT-d technique in the BIOS.
The grub.conf of the para-guest domain 0:
menuentry 'Ubuntu GNU/Linux,Xen 4.2.1' --class ubuntu --class gnu-linux
--class gnu --class os --class xen {
insmod part_msdos
insmod ext2
set root='(hd1,msdos2)'
search --no-floppy --fs-uuid --set=root b23f1bc8-31e1-4590-a063-2109e68309fd
multiboot /boot/xen-4.2.1.gz iommu=verbose iommu=dom0-strict placeholder
sched=credit loglvl=all guest_loglvl=all debug_stack_lines=80 console=vga,com1
com1=115200,8n1
module /boot/vmlinuz-3.2.0-51-generic-pae placeholder
root=UUID=b23f1bc8-31e1-4590-a063-2109e68309fd ro quiet splash console=tty0
max_loop=128 xencons=ttyS0,115200
module /boot/initrd.img-3.2.0-51-generic-pae
}
Also, we have printed corresponding hardware registers as well as the global
initialized variables along the function flow in the log file, which strongly
determines the control flow.
Here we list the corresponding outputs from the log file:
(XEN) Xen version 4.2.1 (root@) (gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3) Fri
Aug 23 15:02:53 CST 2013
(XEN) Latest ChangeSet: unavailable
(XEN) Bootloader: GRUB 1.99-21ubuntu3
(XEN) Command line: iommu=verbose iommu=dom0-strict placeholder schde=credit
loglvl=all guest_loglvl=all debug_stack_lines=80 console=vga,com1
com1=115200,8n1
(XEN) Intel machine check reporting enabled
(XEN) Intel VT-d supported page sizes: 4kB.
(XEN) Intel VT-d Snoop Control enabled.
(XEN) Intel VT-d Dom0 DMA Passthrough not enabled.
(XEN) Intel VT-d Queued Invalidation enabled.
(XEN) Intel VT-d Interrupt Remapping not enabled.
(XEN) Intel VT-d Shared EPT tables not enabled.
(XEN) I/O virtualisation enabled
(XEN) - Dom0 mode: Strict
(XEN) is_hvm_domain(d):false, need_iommu(d):true
(XEN) iommu_enabled:true, hd->platform_ops: non-null
(XEN) iommu_passthrough:false, d->domain_id=0
(XEN) dma_pte_present(*pte):present
(XEN) this_cpu(iommu_dont_flush_iotlb):false
(XEN) cap_pgsel_inv(iommu->cap):true, order>cap_max_amask_val:false
(XEN) flush non present entry:true, cap_caching_mode(iommu->cap):false
(XEN) rwbf_quirk:false, cap_rwbf(iommu->cap):false
If you need more information, please let us know.
Hopefully our information can contribute to the security of Xen.
Best wishes
Yueqiang & Zhi & Junqing
_______________________________________________
Xen-devel mailing list
Xen-devel@lists.xen.org
http://lists.xen.org/xen-devel
>>> On 16.11.13 at 15:33, CHENG Yueqiang <yqcheng.2008@phdis.smu.edu.sg> wrote: > Bug Descriptions > We find that Xen does NOT flush I/O TLB when iommu_unmap_page is invoked in > PV mode. Attackers may be able to use this bug to compromise Xen. > (When a PV guest OS adds a new page into its page table, the hypervisor > should set the new page-table page read-only if the page is writable before, > and consequently flush the IOTLB to avoid DMA attacks. ) > > The following code is from __get_page_type: > if ( unlikely((x & PGT_type_mask) != type) ) > { > /* Special pages should not be accessible from devices. */ > struct domain *d = page_get_owner(page); > if ( d && !is_hvm_domain(d) && unlikely(need_iommu(d)) ) > { > if ( (x & PGT_type_mask) == PGT_writable_page ) > iommu_unmap_page(d, mfn_to_gmfn(d, page_to_mfn(page))); > else if ( type == PGT_writable_page ) > iommu_map_page(d, mfn_to_gmfn(d, page_to_mfn(page)), > page_to_mfn(page), > IOMMUF_readable|IOMMUF_writable); > } > } > > Details > When Xen deals with a L2 page table entry update, the possible function flow > is as follows: > do_mmu_update() > ->mod_l2_entry() > ->get_page_from_l2e() > ->get_page_and_type_from_pagenr > ->get_page_type() > ->__get_page_type > ->iommu_unmap_page > > ->intel_iommu_unmap_page > > ->dma_pte_clear_one() > > ->__intel_iommu_iotlb_flush > > ->iommu_flush_iotlb_psi > > ->flush_iotlb_qi > We have noticed that the function: flush_iotlb_qi is supposed to complete > the task of invalidating I/O TLB. However, it ALWAYS returns before really > flushing the IOTLB. > In flush_iotlb_qi > if ( flush_non_present_entry ) > { > if ( !cap_caching_mode(iommu->cap) ) > return 1; <-- return from this point. > else > did = 0; > } > Similarly, the function: iommu_flush_write_buffer coming next to > iommu_flush_iotlb_psi directly returns also without write buffer flushing > because the if statement: !rwbf_quirk && !cap_rwbf(iommu->cap) are also true. > Note that some internal information can be found at the end of this email, > in the log file.I agree, albeit the capability related parts are irrelevant here.> Fix Suggestions > We observe that the argument: dma_old_pte_present in the function: > __intel_iommu_iotlb_flush is always assigned as 0 in the above function flow, > the value of flush_non_present_entry in flush_iotlb_qi is always 1, the > negation of dma_old_pte_present (!dma_old_pte_present). > > We suggest to set dma_old_pte_present as 1 or collect the value from > dma_pte_present(pte).Since dma_pte_present() was checked earlier on that code path, just passing TRUE (1) here is The Right Thing To Do (TM). I''ll send a patch in a minute. Jan
The third parameter of __intel_iommu_iotlb_flush() is to indicate
whether the to be flushed entry was a present one. A few lines before,
we bailed if !dma_pte_present(*pte), so there''s no need to check the
flag here again - we can simply always pass TRUE here.
This is XSA-78.
Suggested-by: Cheng Yueqiang <yqcheng.2008@phdis.smu.edu.sg>
Signed-off-by: Jan Beulich <jbeulich@suse.com>
--- a/xen/drivers/passthrough/vtd/iommu.c
+++ b/xen/drivers/passthrough/vtd/iommu.c
@@ -646,7 +646,7 @@ static void dma_pte_clear_one(struct dom
iommu_flush_cache_entry(pte, sizeof(struct dma_pte));
if ( !this_cpu(iommu_dont_flush_iotlb) )
- __intel_iommu_iotlb_flush(domain, addr >> PAGE_SHIFT_4K , 0, 1);
+ __intel_iommu_iotlb_flush(domain, addr >> PAGE_SHIFT_4K, 1, 1);
unmap_vtd_domain_page(page);
Andrew Cooper
2013-Nov-18 11:43 UTC
Re: [PATCH] VT-d: fix TLB flushing in dma_pte_clear_one()
On 18/11/13 11:23, Jan Beulich wrote:> The third parameter of __intel_iommu_iotlb_flush() is to indicate > whether the to be flushed entry was a present one. A few lines before, > we bailed if !dma_pte_present(*pte), so there''s no need to check the > flag here again - we can simply always pass TRUE here. > > This is XSA-78. > > Suggested-by: Cheng Yueqiang <yqcheng.2008@phdis.smu.edu.sg> > Signed-off-by: Jan Beulich <jbeulich@suse.com>Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>> > --- a/xen/drivers/passthrough/vtd/iommu.c > +++ b/xen/drivers/passthrough/vtd/iommu.c > @@ -646,7 +646,7 @@ static void dma_pte_clear_one(struct dom > iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); > > if ( !this_cpu(iommu_dont_flush_iotlb) ) > - __intel_iommu_iotlb_flush(domain, addr >> PAGE_SHIFT_4K , 0, 1); > + __intel_iommu_iotlb_flush(domain, addr >> PAGE_SHIFT_4K, 1, 1); > > unmap_vtd_domain_page(page); > > > > > > _______________________________________________ > Xen-devel mailing list > Xen-devel@lists.xen.org > http://lists.xen.org/xen-devel
Keir Fraser
2013-Nov-18 11:58 UTC
Re: [PATCH] VT-d: fix TLB flushing in dma_pte_clear_one()
On 18/11/2013 03:23, "Jan Beulich" <JBeulich@suse.com> wrote:> The third parameter of __intel_iommu_iotlb_flush() is to indicate > whether the to be flushed entry was a present one. A few lines before, > we bailed if !dma_pte_present(*pte), so there''s no need to check the > flag here again - we can simply always pass TRUE here. > > This is XSA-78. > > Suggested-by: Cheng Yueqiang <yqcheng.2008@phdis.smu.edu.sg> > Signed-off-by: Jan Beulich <jbeulich@suse.com>Acked-by: Keir Fraser <keir@xen.org>> --- a/xen/drivers/passthrough/vtd/iommu.c > +++ b/xen/drivers/passthrough/vtd/iommu.c > @@ -646,7 +646,7 @@ static void dma_pte_clear_one(struct dom > iommu_flush_cache_entry(pte, sizeof(struct dma_pte)); > > if ( !this_cpu(iommu_dont_flush_iotlb) ) > - __intel_iommu_iotlb_flush(domain, addr >> PAGE_SHIFT_4K , 0, 1); > + __intel_iommu_iotlb_flush(domain, addr >> PAGE_SHIFT_4K, 1, 1); > > unmap_vtd_domain_page(page); > > > > > > _______________________________________________ > Xen-devel mailing list > Xen-devel@lists.xen.org > http://lists.xen.org/xen-devel