PTR_MANGLE/PTR_DEMANGLE の実装は xor + ror/rol になっていた

マイクロスレッドの実装で、%ESP と %EIP の切り替えに setjmp()/longjmp() を利用しようとしたらハマってしまった。調べてみるとセキュアコーディングの関係で jmp_buf の %EIP, %ESP のポインタがエンコーディングされていた。

で、参考にさせてもらって自分のコードをいじったがまだ動かない…。結局、libc-2.7 の、

/sysdeps/unix/sysv/linux/i386/sysdep.h

あたりのコードをよく見たら、最近は xor だけではなく ror/rol による 9bit のローテーションが追加されていた。以下に抜き出して実験してみた。(0x18 など一部ハードコーディングに置き換えている。0x18 は tcbhead_t の pointer_guard までのオフセット)

#include <stdio.h>
#include <setjmp.h>

#define PTR_MANGLE(var) asm("xorl %%gs:0x18, %0;" \
                            "roll $9, %0;" : "=r"(var) : "0"(var))

#define PTR_DEMANGLE(var) asm("rorl $9, %0;" \
                              "xorl %%gs:0x18, %0;" : "=r"(var) : "0"(var))


int main(int argc, char** argv)
{
    int direct_esp, direct_eip;
    int setjmp_esp, setjmp_eip;

    jmp_buf jmpBuf;

    asm("movl %%esp, %0;" : "=r"(direct_esp));

    asm("call label;"
        "label: popl %0;" : "=r"(direct_eip));

    printf("direct_ESP = %X, direct_EIP = %X\n", direct_esp, direct_eip);
    
    setjmp(jmpBuf);
    
    setjmp_esp = jmpBuf[0].__jmpbuf[4]; // ESP
    setjmp_eip = jmpBuf[0].__jmpbuf[5]; // EIP
    
    PTR_DEMANGLE(setjmp_esp);
    PTR_DEMANGLE(setjmp_eip);

    printf("setjmp_ESP = %X, setjmp_EIP = %X\n", setjmp_esp, setjmp_eip);

    return 0;
}

実行結果は以下の通り。(EIP が異なるのは保存しているところが違うから。だいたい合っている、という参考までに)

direct_ESP = BFC930B0, direct_EIP = 8048418
setjmp_ESP = BFC930B0, setjmp_EIP = 8048444