19 comments

[ 0.24 ms ] story [ 55.6 ms ] thread
What would a practical application of something like this be? Maybe a JIT compiler that takes advantage of the C stack?
- JIT / optimizing for the host device

- hot patching

- making sure sysadmins have healthy sense of panic available to them at all times

None, this is wildly undefined behavior, even considering GCC's extension and its documentation. I doubt that GCC implementers would be interested in defining the behavior here.

edit: There might be room for some other extension here, but as an other commenter already wrote:

1. functions might not be contiguous in memory

2. the code that you are moving should better be position independent

So any such extension should handle these issues one way or an other.

Instead of if( condition) then code1: else code2, computed goto will just switch the branch to unconditional jump to either code1/code2, which bypasses branch misprediction penalty and is generally more elegant.
Does that not actually cause a mispredict if the speculative execution happened before the code change?
Worse. It's more than likely to cause a cache miss, and waiting on memory is going to be even slower than a mispredicted branch.
It could be even worse. I've seen x86 cores where the icache/dcache consistency logic ended up internally being most of a page fault and recovery once noticed by the processor.
Oof on that note: at least in the example in the article, it's going to cost a TLB miss as page permissions are getting changed. So that's a page-table walk which means %cr3 -> PML4 -> PDPT -> PD -> PT -> the actual address you want to access; potentially up to 5 memory accesses (6 if your system has more than 256TB of RAM and you're using 5-level paging).

That being said, in a loop you'd only take this hit on the first run. You'd be back to cache misses after that.

> One of my research topics last year was self-modifying code mainly for *obfuscation*.

I think obfuscation is generally a really bad idea, but that was brought up in the article. One practical application for self-modifying code is hotpatching. Sometimes you really don't want to stop the process but want to change the implementation for some function.

Hold on, How you can actually do hotpatching on x86 protected mode? code is usualy executable but read only. data + stack is usualy NX but writable.
The processor itself doesn't enforce W^X protection; even though writable pages are NX by convention, nothing inherently stops you from using (the equivalent of) mmap(PROT_READ | PROT_WRITE | PROT_EXEC) and copying the instructions in, or doing the same with existing pages using mprotect(), at which point you can hotpatch as much as you want. Of course, some OSes forbid this as a hardening measure, but allowing it remains very common.
You do the same thing JIT does and change the permissions on the page to NX, apply whatever update you want, then remove the write permission and make it executable again. Even though it seems like it doesn't make a difference, it's much more difficult for an attacker to change the page permissions than it is for the hotpatcher/JIT. I think this could still fail though depending on how strict your OS is in which case you might need to use explicit function pointers which are obviously writable.

Some systems apparently allow you to add executable without removing write as that is what the OP does with "mprotect(page, getpagesize(), PROT_READ | PROT_WRITE | PROT_EXEC);". However,

Why is it more difficult for an attacker to change the code compared the JIT?
Attackers love overflowing buffers but since that memory is not executable they can't put code in there directly. The JIT compiler is almost always the only component which writes to executable pages so they need to somehow trick it into emitting the code they want. It's my understanding that JIT spray attacks are harder to develop.
Better hope the compiler generated contiguous PIC.
> mainly for obfuscation.

Right. It stopped being a useful optimization decades ago.

One of the few architectural mistakes von Neumann made back in 1945 is that he didn't think of index registers. He was addressing arrays by storing into the address part of instructions. By 1949, the index register (the "B Box") had been invented, and storing into instructions was mostly obsolete.

Would calculating algorithms that can be passed to a JIT-wise compiler callable directly, returning a pointer to object code that called can be called immediately count?

Turns out going from calculating an algorithm, optimized for whatever specialization is needed for performance, functionality & formats; to object code; to execution, with the simplest syntax imaginable, is pretty useful.

And fun

Cough, Forth, cough!