domain_pause_except_self() was introduced to allow a domain to pause
itself while doing altp2m operations. However, as written, it has a
risk fo deadlock if two vcpus enter the loop at the same time.
Luckily, there's already a solution for this: Attempt to call domain's
hypercall_deadlock_mutex, and restart the entire hypercall if you
fail.
Make domain_pause_except_self() attempt to grab this mutex when
pausing itself, returning -ERESTART if it fails. Have the callers
check for errors and pass the value up. In both cases, the top-level
do_hvm_op() should DTRT when -ERESTART is returned.
The (necessary) reuse of the hypercall deadlock mutex poses the risk
of getting called from a context where the lock was already acquired
(e.g. someone may (say) call domctl_lock(), then afterwards call
domain_pause_except_self()). However, in the interest of not
overcomplicating things, no changes are made here to the mutex.
Attempted nesting of this lock isn't a security issue, because all
that will happen is that the vcpu will livelock taking continuations.
Signed-off-by: George Dunlap <george.dunlap@citrix.com>
Tested-by: Razvan Cojocaru <rcojocaru@bitdefender.com>
Acked-by: Jan Beulich <jbeulich@suse.com>
Release-acked-by: Juergen Gross <jgross@suse.com>
if ( !idx || idx >= MAX_ALTP2M )
return rc;
- domain_pause_except_self(d);
+ rc = domain_pause_except_self(d);
+ if ( rc )
+ return rc;
+ rc = -EBUSY;
altp2m_list_lock(d);
if ( d->arch.altp2m_eptp[idx] != mfn_x(INVALID_MFN) )
if ( idx >= MAX_ALTP2M )
return rc;
- domain_pause_except_self(d);
+ rc = domain_pause_except_self(d);
+ if ( rc )
+ return rc;
+ rc = -EINVAL;
altp2m_list_lock(d);
if ( d->arch.altp2m_eptp[idx] != mfn_x(INVALID_MFN) )
return 0;
}
-void domain_pause_except_self(struct domain *d)
+int domain_pause_except_self(struct domain *d)
{
struct vcpu *v, *curr = current;
if ( curr->domain == d )
{
+ /* Avoid racing with other vcpus which may want to be pausing us */
+ if ( !spin_trylock(&d->hypercall_deadlock_mutex) )
+ return -ERESTART;
for_each_vcpu( d, v )
if ( likely(v != curr) )
vcpu_pause(v);
+ spin_unlock(&d->hypercall_deadlock_mutex);
}
else
domain_pause(d);
+
+ return 0;
}
void domain_unpause_except_self(struct domain *d)
}
/* domain_pause() but safe against trying to pause current. */
-void domain_pause_except_self(struct domain *d);
+int __must_check domain_pause_except_self(struct domain *d);
void domain_unpause_except_self(struct domain *d);
/*