For INT $N instructions (besides $0x80 for which there is a dedicated fast path), handling is mostly fault-based because of DPL0 gates in the IDT. This means that when the guest kernel allows the instruction too, Xen must increment %rip to the end of the instruction before passing a trap to the guest kernel.
When an INT $N instruction has a prefix, it's longer than two bytes, and Xen will deliver the "trap" with %rip pointing into the middle of the instruction. Introduce a new pv_emulate_sw_interrupt() which uses x86_insn_length() to determine the instruction length, rather than assuming two. This is a change in behaviour for PV guests, but the prior behaviour cannot reasonably be said to be intentional. This change does not affect the INT $0x80 fastpath. Prefixed INT $N instructions occur almost exclusively in test code or exploits, and INT $0x80 appears to be the only user-usable interrupt gate in contemporary PV guests. Signed-off-by: Andrew Cooper <[email protected]> --- CC: Jan Beulich <[email protected]> CC: Roger Pau Monné <[email protected]> v4: * New --- xen/arch/x86/include/asm/pv/traps.h | 2 ++ xen/arch/x86/pv/emul-priv-op.c | 48 +++++++++++++++++++++++++++++ xen/arch/x86/traps.c | 3 +- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/xen/arch/x86/include/asm/pv/traps.h b/xen/arch/x86/include/asm/pv/traps.h index 8c201190923d..16e9a8d2aa3f 100644 --- a/xen/arch/x86/include/asm/pv/traps.h +++ b/xen/arch/x86/include/asm/pv/traps.h @@ -17,6 +17,7 @@ int pv_raise_nmi(struct vcpu *v); int pv_emulate_privileged_op(struct cpu_user_regs *regs); +void pv_emulate_sw_interrupt(struct cpu_user_regs *regs); void pv_emulate_gate_op(struct cpu_user_regs *regs); bool pv_emulate_invalid_op(struct cpu_user_regs *regs); @@ -31,6 +32,7 @@ static inline bool pv_trap_callback_registered(const struct vcpu *v, static inline int pv_raise_nmi(struct vcpu *v) { return -EOPNOTSUPP; } static inline int pv_emulate_privileged_op(struct cpu_user_regs *regs) { return 0; } +static inline void pv_emulate_sw_interrupt(struct cpu_user_regs *regs) {} static inline void pv_emulate_gate_op(struct cpu_user_regs *regs) {} static inline bool pv_emulate_invalid_op(struct cpu_user_regs *regs) { return true; } diff --git a/xen/arch/x86/pv/emul-priv-op.c b/xen/arch/x86/pv/emul-priv-op.c index a3c1fd12621d..87d3bbcf901f 100644 --- a/xen/arch/x86/pv/emul-priv-op.c +++ b/xen/arch/x86/pv/emul-priv-op.c @@ -8,6 +8,7 @@ */ #include <xen/domain_page.h> +#include <xen/err.h> #include <xen/event.h> #include <xen/guest_access.h> #include <xen/hypercall.h> @@ -1401,6 +1402,53 @@ int pv_emulate_privileged_op(struct cpu_user_regs *regs) return 0; } +/* + * Hardware already decoded the INT $N instruction and determinted that there + * was a DPL issue, hence the #GP. Xen has already determined that the guest + * kernel has permitted this software interrupt. + * + * All that is needed is the instruction length, to turn the fault into a + * trap. All errors are turned back into the original #GP, as that's the + * action that really happened. + */ +void pv_emulate_sw_interrupt(struct cpu_user_regs *regs) +{ + struct vcpu *curr = current; + struct domain *currd = curr->domain; + struct priv_op_ctxt ctxt = { + .ctxt.regs = regs, + .ctxt.lma = !is_pv_32bit_domain(currd), + }; + struct x86_emulate_state *state; + uint8_t vector = regs->error_code >> 3; + unsigned int len, ar; + + if ( !pv_emul_read_descriptor(regs->cs, curr, &ctxt.cs.base, + &ctxt.cs.limit, &ar, 1) || + !(ar & _SEGMENT_S) || + !(ar & _SEGMENT_P) || + !(ar & _SEGMENT_CODE) ) + goto error; + + state = x86_decode_insn(&ctxt.ctxt, insn_fetch); + if ( IS_ERR_OR_NULL(state) ) + goto error; + + len = x86_insn_length(state, &ctxt.ctxt); + x86_emulate_free_state(state); + + /* Note: Checked slightly late to simplify 'state' handling. */ + if ( ctxt.ctxt.opcode != 0xcd /* INT $imm8 */ ) + goto error; + + regs->rip += len; + pv_inject_sw_interrupt(vector); + return; + + error: + pv_inject_hw_exception(X86_EXC_GP, regs->entry_vector); +} + /* * Local variables: * mode: C diff --git a/xen/arch/x86/traps.c b/xen/arch/x86/traps.c index 5feac88d6c0b..907fb4c186c0 100644 --- a/xen/arch/x86/traps.c +++ b/xen/arch/x86/traps.c @@ -1379,8 +1379,7 @@ void do_general_protection(struct cpu_user_regs *regs) if ( permit_softint(TI_GET_DPL(ti), v, regs) ) { - regs->rip += 2; - pv_inject_sw_interrupt(vector); + pv_emulate_sw_interrupt(regs); return; } } -- 2.39.5
