|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] xc_hvm_inject_trap() races
On 11/08/2016 10:15 AM, Jan Beulich wrote:
>>>> On 07.11.16 at 18:01, <rcojocaru@xxxxxxxxxxxxxxx> wrote:
>> On 11/07/2016 06:10 PM, Jan Beulich wrote:
>>>>>> On 07.11.16 at 16:24, <rcojocaru@xxxxxxxxxxxxxxx> wrote:
>>>> The one-shot vm_event does sound reasonable. I could set a flag
>>>> per-VCPU, basically similar to v->arch.hvm_vcpu.inject_trap.vector, and
>>>> fire a single event from hvm_inject_trap() if it's set (then unset it) -
>>>> the flag would be set via an xc_monitor_next_interrupt() call in libxc.
>>>
>>> Doing this in hvm_inject_trap() would not cover all cases afict.
>>> I'd suggest doing this from hvm_do_resume() _after_ the
>>> (conditional) call to hvm_inject_trap(), if there is _any_ event
>>> pending.
>>
>> But that would only cover the hypercall-injected traps. The condition in
>> hvm_do_resume() is "if ( v->arch.hvm_vcpu.inject_trap.vector != -1 )",
>> and inject_trap.vector seems to only ever be set by the hypercall:
>> [...]
>> So if the next interrupt is not caused by the hypercall, we'll never get
>> another event. Am I reading the code wrong?
>
> No, maybe I expressed myself ambiguously: I meant to say that the
> event should be delivered from hvm_do_resume(), but _outside_ the
> conditional guarding the call to hvm_inject_trap(). Otherwise things
> would have been worse than when doing it inside hvm_inject_trap().
While working on this patch, I've had a new idea, which would require
less changes and fix the problem in a more elegant manner if validated.
Looking at vmx_idtv_reinject(), the real problem seems to be that
VM_ENTRY_INTR_INFO is being written to directly:
3229 static void vmx_idtv_reinject(unsigned long idtv_info)
3230 {
3231
3232 /* Event delivery caused this intercept? Queue for redelivery. */
3233 if ( unlikely(idtv_info & INTR_INFO_VALID_MASK) )
3234 {
3235 if ( hvm_event_needs_reinjection(MASK_EXTR(idtv_info,
3236
INTR_INFO_INTR_TYPE_MASK),
3237 idtv_info &
INTR_INFO_VECTOR_MASK) )
3238 {
3239 /* See SDM 3B 25.7.1.1 and .2 for info about masking
resvd bits. */
3240 __vmwrite(VM_ENTRY_INTR_INFO,
3241 idtv_info & ~INTR_INFO_RESVD_BITS_MASK);
3242 if ( idtv_info & INTR_INFO_DELIVER_CODE_MASK )
3243 {
3244 unsigned long ec;
3245
3246 __vmread(IDT_VECTORING_ERROR_CODE, &ec);
3247 __vmwrite(VM_ENTRY_EXCEPTION_ERROR_CODE, ec);
3248 }
3249 }
3250
3251 /*
3252 * Clear NMI-blocking interruptibility info if an NMI
delivery faulted.
3253 * Re-delivery will re-set it (see SDM 3B 25.7.1.2).
3254 */
3255 if ( cpu_has_vmx_vnmi &&
3256 ((idtv_info & INTR_INFO_INTR_TYPE_MASK) ==
3257 MASK_INSR(X86_EVENTTYPE_NMI, INTR_INFO_INTR_TYPE_MASK)) )
3258 {
3259 unsigned long intr_info;
3260
3261 __vmread(GUEST_INTERRUPTIBILITY_INFO, &intr_info);
3262 __vmwrite(GUEST_INTERRUPTIBILITY_INFO,
3263 intr_info & ~VMX_INTR_SHADOW_NMI);
3264 }
3265 }
3266 }
where the hypercall looks at v->arch.hvm_vcpu.inject_trap.vector only.
Then we notice that the hypercall _fails_immediately_ with -EBUSY if
v->arch.hvm_vcpu.inject_trap.vector is already set:
5922 case HVMOP_inject_trap:
5923 {
5924 xen_hvm_inject_trap_t tr;
5925 struct domain *d;
5926 struct vcpu *v;
5927
5928 if ( copy_from_guest(&tr, arg, 1 ) )
5929 return -EFAULT;
5930
5931 rc = rcu_lock_remote_domain_by_id(tr.domid, &d);
5932 if ( rc != 0 )
5933 return rc;
5934
5935 rc = -EINVAL;
5936 if ( !is_hvm_domain(d) )
5937 goto injtrap_fail;
5938
5939 rc = xsm_hvm_control(XSM_DM_PRIV, d, op);
5940 if ( rc )
5941 goto injtrap_fail;
5942
5943 rc = -ENOENT;
5944 if ( tr.vcpuid >= d->max_vcpus || (v = d->vcpu[tr.vcpuid])
== NULL )
5945 goto injtrap_fail;
5946
5947 if ( v->arch.hvm_vcpu.inject_trap.vector != -1 )
5948 rc = -EBUSY;
5949 else
5950 {
5951 v->arch.hvm_vcpu.inject_trap.vector = tr.vector;
5952 v->arch.hvm_vcpu.inject_trap.type = tr.type;
5953 v->arch.hvm_vcpu.inject_trap.error_code = tr.error_code;
5954 v->arch.hvm_vcpu.inject_trap.insn_len = tr.insn_len;
5955 v->arch.hvm_vcpu.inject_trap.cr2 = tr.cr2;
5956 rc = 0;
5957 }
5958
5959 injtrap_fail:
5960 rcu_unlock_domain(d);
5961 break;
5962 }
The conclusion: instead of __vmwrite(VM_ENTRY_INTR_INFO, ...);
__vmwrite(VM_ENTRY_EXCEPTION_ERROR_CODE, ...) in vmx_idtv_reinject(),
simply do what the hypercall would have done, i.e. write
inject_trap.vector, inject_trap.type, etc.
This way, the hypercall will fail immediately if there's a pending
interrupt set on exit.
Is this too good to be true? :)
Thanks,
Razvan
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
https://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |