x86–64 Troubleshooting Tales: I can jump to ring 3 via IRETQ but not via SYSRET… Why? — Part 1

Paulo Almeida
3 min readDec 6, 2021

I finally finished implementing my own memory allocation system and paging structure. I spent months doing that and now it was time for the long awaited ‘jump to the user-space‘ trick. What could go wrong?

As usual, I went for the low-hanging fruits to pave the way to Ring 3 and, most importantly, to be able to test individual components along the way.

Conceptual representation of the protection ring on x86_64
  • I will write the simplest program I can and link that into an ELF file using a freestanding compiler to avoid having to add “porting a compiler” to my list of TODOs.
  • I will try jumping to Ring 3 using IRETQ first and if that works, I will try enabling Fast System Calls (syscalls) and perform the jump via that mechanism.

The first user space program

The good old “Hello World” but using the well known VGA Text mode since we don’t have the system call write implemented yet.

The assembly entry point that would prepare the environment for the C code to run as well as to perform some house keeping tasks dictated by the ELF file format.

Last but not least, the linker file that puts everything together in a single file.

Jumping to Ring 3 using IRETQ

The IRETQ jump is probably the easiest one to grasp because you are almost forced to deal with hardware interrupts before even considering jumping to user mode… so basically, all the learning curve is already gone.

The recipe for this strategy is pretty straightforward. You have to ‘fabricate’ an Interrupt-Handler stack and execute IRETQ right after that. When done correctly it will:

  • jump to the right memory segment -> Denoted by CS and SS segment selectors
  • start from the desired code instruction -> Denoted by the RIP (instruction pointer)
  • point to the correct stack pointer -> Denoted by the RSP (stack pointer)
  • enable the expected flags -> Denoted by the RFLAGS

Enough theory for now, this is the code just fabricate this interrupt-handler frame:

Last but not least, let’s put it all together:

Voila, this seems to be working fine as it’s pointed out by the green message at the top.

I hope that you’ve liked what you read so far. In the next blog post, I will explain how to set up syscall/sysret and walk you through the problems that happened while doing it so.

About the x86–64 Troubleshooting Tales series

x86–64 Troubleshooting Tales is this new side-project of mine in which I describe problems I faced while debugging issues on AlmeidaOS (my pet-project).

--

--

Paulo Almeida

Interested in technical deep dives and the Linux kernel; Opinions are my own;