xen/arm: grant-table: Protect gnttab_clear_flag against guest misbehavior

The function gnttab_clear_flag is used to clear the access flags. On
Arm, it is implemented using a loop and guest_cmpxchg.

It is possible that guest_cmpxchg will always return a different value
than old. This can happen if the guest updated the memory before Xen has
time to do the exchange. Because of that, there are no way for to
promise the loop will end.

It is possible to make the current code safe by re-using the same
principle as applied on the guest atomic helper. However this patch
takes a different approach that should lead to more efficient code in
the default case.

A new helper is introduced to clear a set of bits on a 16-bits word.
This should avoid a an extra loop to check cmpxchg succeeded.

Note that a mask is used instead of a bit, so the helper can be re-used
later on for clearing multiple flags at the same time.

This is part of XSA-295.

Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
Signed-off-by: Julien Grall <julien.grall@arm.com>
Signed-off-by: Stefano Stabellini <stefanos@xilinx.com>
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
This commit is contained in:
Julien Grall committed 2019-06-14 14:32:19 +01:00
1 parent f8c866a1a3
commit f41dbf33e7
5 files changed
+86 -9

No files matched your search

+35
View File
@@ -126,6 +126,41 @@ testop(test_and_change_bit, eor)
testop(test_and_clear_bit, bic)
testop(test_and_set_bit, orr)
static always_inline bool int_clear_mask16(uint16_t mask, volatile uint16_t *p,
bool timeout, unsigned int max_try)
{
unsigned long res, tmp;
prefetchw((const uint16_t *)p);
do
{
asm volatile ("// int_clear_mask16\n"
" ldrexh %2, %1\n"
" bic %2, %2, %3\n"
" strexh %0, %2, %1\n"
: "=&r" (res), "+Qo" (*p), "=&r" (tmp)
: "r" (mask));
if ( !res )
break;
} while ( !timeout || ((--max_try) > 0) );
return !res;
}
void clear_mask16(uint16_t mask, volatile void *p)
{
if ( !int_clear_mask16(mask, p, false, 0) )
ASSERT_UNREACHABLE();
}
bool clear_mask16_timeout(uint16_t mask, volatile void *p,
unsigned int max_try)
{
return int_clear_mask16(mask, p, true, max_try);
}
/*
* Local variables:
* mode: C
+33
View File
@@ -118,6 +118,39 @@ testop(test_and_change_bit, eor)
testop(test_and_clear_bit, bic)
testop(test_and_set_bit, orr)
static always_inline bool int_clear_mask16(uint16_t mask, volatile uint16_t *p,
bool timeout, unsigned int max_try)
{
unsigned long res, tmp;
do
{
asm volatile ("// int_clear_mask16\n"
" ldxrh %w2, %1\n"
" bic %w2, %w2, %w3\n"
" stxrh %w0, %w2, %1\n"
: "=&r" (res), "+Q" (*p), "=&r" (tmp)
: "r" (mask));
if ( !res )
break;
} while ( !timeout || ((--max_try) > 0) );
return !res;
}
void clear_mask16(uint16_t mask, volatile void *p)
{
if ( !int_clear_mask16(mask, p, false, 0) )
ASSERT_UNREACHABLE();
}
bool clear_mask16_timeout(uint16_t mask, volatile void *p,
unsigned int max_try)
{
return int_clear_mask16(mask, p, true, max_try);
}
/*
* Local variables:
* mode: C
+1 -9
View File
@@ -1384,15 +1384,7 @@ void put_page_type(struct page_info *page)
void gnttab_clear_flag(struct domain *d, unsigned long nr, uint16_t *addr)
{
/*
* Note that this cannot be clear_bit(), as the access must be
* confined to the specified 2 bytes.
*/
uint16_t mask = ~(1 << nr), old;
do {
old = *addr;
} while (guest_cmpxchg(d, addr, old, old & mask) != old);
guest_clear_mask16(d, BIT(nr), addr);
}
void gnttab_mark_dirty(struct domain *d, mfn_t mfn)
+4
View File
@@ -53,6 +53,8 @@ int test_and_set_bit(int nr, volatile void *p);
int test_and_clear_bit(int nr, volatile void *p);
int test_and_change_bit(int nr, volatile void *p);
void clear_mask16(uint16_t mask, volatile void *p);
/*
* The helpers below may fail to update the memory if the action takes
* too long.
@@ -71,6 +73,8 @@ bool test_and_clear_bit_timeout(int nr, volatile void *p,
int *oldbit, unsigned int max_try);
bool test_and_change_bit_timeout(int nr, volatile void *p,
int *oldbit, unsigned int max_try);
bool clear_mask16_timeout(uint16_t mask, volatile void *p,
unsigned int max_try);
/**
* __test_and_set_bit - Set a bit and return its old value
+13
View File
@@ -73,6 +73,19 @@ guest_testop(test_and_change_bit)
#undef guest_testop
static inline void guest_clear_mask16(struct domain *d, uint16_t mask,
volatile uint16_t *p)
{
perfc_incr(atomics_guest);
if ( clear_mask16_timeout(mask, p, this_cpu(guest_safe_atomic_max)) )
return;
domain_pause_nosync(d);
clear_mask16(mask, p);
domain_unpause(d);
}
static inline unsigned long __guest_cmpxchg(struct domain *d,
volatile void *ptr,
unsigned long old,
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.