Return from interrupt without iret
Return from interrupt without iret
Say a software interrupt just happened. How would you "acknowledge" the interrupt without using an IRET instruction? I tried simply clearing the interrupt data from the stack and then setting the interrupt flag but that does not work very well. The wiki also does not make it clear what steps are required in order to exit the interrupt state.
Thanks!
Thanks!
Re: Return from interrupt without iret
Interrupts always return with IRET. Very rarely is there an exception to that rule. If you want to just acknowledge, then send EOI in the case of a hardware interrupt, and then use IRET
Re: Return from interrupt without iret
How would a software interrupt "just happen"? Either you executed an "int" instruction or you didn't. And why are you reluctant to use an "iret" instruction to end the interrupt handler?
Re: Return from interrupt without iret
It's not really possible to return from an interrupt without iret unless you're willing to add strange hacks. You need an atomic way to jump to a user mode address and to set IF to zero at the same time¹. Outside iret, you could use something like sysret for that purpose, but it is more messy and not intended for the purpose of returning from an arbitrary interrupt.
¹ Technically, that's not required but any other design can easily run into nasty pitfalls.
¹ Technically, that's not required but any other design can easily run into nasty pitfalls.
managarm: Microkernel-based OS capable of running a Wayland desktop (Discord: https://discord.gg/7WB6Ur3). My OS-dev projects: [mlibc: Portable C library for managarm, qword, Linux, Sigma, ...] [LAI: AML interpreter] [xbstrap: Build system for OS distributions].
Re: Return from interrupt without iret
Upon entry the interrupt flag gets cleared. The iret instruction restores it because it pops the previous flags from the stack.
iret is basically: popf + retf, just that the ordering on the stack is reversed. In order to actually replace it with these two instructions, you would need to do quite some stack shuffling. But I cannot think of a good reason to do so.
iret is basically: popf + retf, just that the ordering on the stack is reversed. In order to actually replace it with these two instructions, you would need to do quite some stack shuffling. But I cannot think of a good reason to do so.
-
- Member
- Posts: 5563
- Joined: Mon Mar 25, 2013 7:01 pm
Re: Return from interrupt without iret
Why don't you want to use an IRET instruction?YDeeps1 wrote:without using an IRET instruction
Re: Return from interrupt without iret
I had to exit interrupt handler without iretq too. But that was very special case. There is no way to block #NMI in Intel VMX root mode unlike at AMD SVM where you can (using CLGI instruction). While my hypervisor accepted #NMI I wanted to let the CPU in #NMI blocking mode to prevent more #NMI to arrive as I was able to inject only 1 #NMI into guest during every VM entry. These #NMI came as IPIs from different cpus/cores where hyper-v was running as a child under nested environment. At the end I solved my problem but during observation and development I had to try that way to leave #NMI blocked so I had to avoid iretq as iretq unblocks #NMI.
I used this way at the end of my #NMI handler:
I used this way at the end of my #NMI handler:
Code: Select all
push rax
; now the stack looks this way:
; qword [rsp+8*0] RAX
; qword [rsp+8*1] RIP
; word [rsp+8*2] CS
; qword [rsp+8*3] RFLAGS
; qword [rsp+8*4] RSP
; word [rsp+8*5] SS
; this leaves interrupt handler and lets NMIs blocked:
mov rax,rsp
lss rsp,[rax+8*4] ; load RSP from qword [rax+8*4] and SS from [rax+8*4+8]
push qword [rax+8*3]
popf
push qword [rax+8*2] ; CS
push qword [rax+8*1] ; RIP
mov rax,[rax+8*0] ; restore RAX
retf
hypervisor-based solutions developer (Intel, AMD)
Re: Return from interrupt without iret
This solution has the drawback of executing kernel code on user stack. After the lss instruction, any exception that occurs will be taken on user stack. After the popf, any external interrupt will be as well. User space can make you hit unmapped space, leading to double-fault. Alternatively, second user-space thread can manipulate the stack frame, leading to ring 0 arbitrary code execution. Attacker only has to get lucky once, and can try as often as they want.
Carpe diem!
Re: Return from interrupt without iret
First, I feel it is a bad idea to let software interrupts from the application disable interrupts. You should use the IDT entry that keeps interrupts enabled.
When it comes to not using iret, it's possible to use retf instead, provided you don't care about restoring the state of flags (which assumes you either enabled interrupts specifically, or used a interrupt gate that doesn't disable interrupts.
However, I think it would be more useful to modify the contents of the flag image on the stack instead if you want to return specific flags (like NC/CY) to the caller.
Another issue is if the kernel even allow software interrupts from user space. This is decided per IDT entry. In my kernel, all IDTs disallow software interrupts from the application, and they are instead emulated in the GPF handler.
Also, iret doesn't acknowledge an interrupt. It's just a way to unwind the call stack. It's EOI that acknowledge a hardware interrupt, and if you forget that and just use iret then hardware interrupts will be messed up. For software interrupts, there is no acknowledge.
When it comes to not using iret, it's possible to use retf instead, provided you don't care about restoring the state of flags (which assumes you either enabled interrupts specifically, or used a interrupt gate that doesn't disable interrupts.
However, I think it would be more useful to modify the contents of the flag image on the stack instead if you want to return specific flags (like NC/CY) to the caller.
Another issue is if the kernel even allow software interrupts from user space. This is decided per IDT entry. In my kernel, all IDTs disallow software interrupts from the application, and they are instead emulated in the GPF handler.
Also, iret doesn't acknowledge an interrupt. It's just a way to unwind the call stack. It's EOI that acknowledge a hardware interrupt, and if you forget that and just use iret then hardware interrupts will be messed up. For software interrupts, there is no acknowledge.
Re: Return from interrupt without iret
nullplan - exactly as you wrote, this method is useless for OS development.
The #NMI arrived in vmx root mode so before #NMI hypervisor stack was loaded (ring-1, VCMS.host_RSP, VMCS.host_SS etc) and when transferring to #NMI handler the private NMI stack was loaded using hypervisor VMCS.host_TSS.IST feature. So at the end of NMI handler the LSS loaded original hypervisor stack, not guest kernelmode stack (ring0) either guest usermode stack (ring3).
These #NMI always arrived as IPI from different cpus/cores and were send by hyper-v, I had to prevent delivering more than 1 #NMI during 1 cycle of vmx root mode (from vm exit till vm entry). During vm entry I had to inject this captured #NMI back into guest, I did not care whether the guest was going to be in ring0 or ring3, handling that was let for cpu (hardware). But the origin of these #NMI was certainly from hyper-v because disabling hyper-v in registry and rebooting then no more #NMI captured in my hypervisor. Hyper-v was running in ring0 mode, not in ring-1. My hypervisor was running in ring-1 as a parent and provided nested capabilities for child hyper-v (so hyper-v was thinking it was running in ring-1 but in fact it was running in ring0 and my parent hypervisor provided emulation of ring-1 instructions for hyper-v).
It was a nightmare but finally I solved that. I just had to keep ring-1 execution with #NMI blocked after capturing #NMI (avoid IRETQ at the end of #NMI handler) so no more than 1 #NMI arrived, then during vm entry I had to clear NMI blocking for guest in VMCS and inject the NMI into guest using VMCS fields so the #NMI was delivered to guest immediately at vm entry. It was very specific situation. For OS development I do not see any advantage of avoiding IRETQ. From hypervisor point you can easily manipulate guest state whether you want your guest is in NMI blocking mode or unblocked mode using 1 bit in VMCS but for root mode there is no such way how to change NMI blocking. My only one idea was to avoid IRETQ which let cpu in NMI blocking mode.
On AMD there is no such problem as at AMD SVM you can easily toggle NMI blocking so #NMI never arrives into your hypervisor. The problem was only at Intel VMX where you can't (at Intel you can only unblock NMI blocking by execution of IRETQ but there is no way how to set CPU into NMI blocking mode).
The #NMI arrived in vmx root mode so before #NMI hypervisor stack was loaded (ring-1, VCMS.host_RSP, VMCS.host_SS etc) and when transferring to #NMI handler the private NMI stack was loaded using hypervisor VMCS.host_TSS.IST feature. So at the end of NMI handler the LSS loaded original hypervisor stack, not guest kernelmode stack (ring0) either guest usermode stack (ring3).
These #NMI always arrived as IPI from different cpus/cores and were send by hyper-v, I had to prevent delivering more than 1 #NMI during 1 cycle of vmx root mode (from vm exit till vm entry). During vm entry I had to inject this captured #NMI back into guest, I did not care whether the guest was going to be in ring0 or ring3, handling that was let for cpu (hardware). But the origin of these #NMI was certainly from hyper-v because disabling hyper-v in registry and rebooting then no more #NMI captured in my hypervisor. Hyper-v was running in ring0 mode, not in ring-1. My hypervisor was running in ring-1 as a parent and provided nested capabilities for child hyper-v (so hyper-v was thinking it was running in ring-1 but in fact it was running in ring0 and my parent hypervisor provided emulation of ring-1 instructions for hyper-v).
It was a nightmare but finally I solved that. I just had to keep ring-1 execution with #NMI blocked after capturing #NMI (avoid IRETQ at the end of #NMI handler) so no more than 1 #NMI arrived, then during vm entry I had to clear NMI blocking for guest in VMCS and inject the NMI into guest using VMCS fields so the #NMI was delivered to guest immediately at vm entry. It was very specific situation. For OS development I do not see any advantage of avoiding IRETQ. From hypervisor point you can easily manipulate guest state whether you want your guest is in NMI blocking mode or unblocked mode using 1 bit in VMCS but for root mode there is no such way how to change NMI blocking. My only one idea was to avoid IRETQ which let cpu in NMI blocking mode.
On AMD there is no such problem as at AMD SVM you can easily toggle NMI blocking so #NMI never arrives into your hypervisor. The problem was only at Intel VMX where you can't (at Intel you can only unblock NMI blocking by execution of IRETQ but there is no way how to set CPU into NMI blocking mode).
hypervisor-based solutions developer (Intel, AMD)