I’m trying to invoke sys_write
via the syscall
instruction under Linux environment on an x86_64 machine with inline assembly in C files. To achieve this, I wrote the following code:
[tjm@ArchPad poc]$ cat poc.c const char S[] = "abcdn"; int main() { __asm __volatile(" movq $1, %%rax; movq $1, %%rdi; movq %0, %%rsi; movq $5, %%rdx; syscall;" : : "i"(S) ); return 0; }
While my compiling it with GCC, the following error promoted:
[tjm@ArchPad poc]$ gcc -O0 poc.c -o poc /usr/bin/ld: /tmp/ccWYxmSb.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE collect2: error: ld returned 1 exit status
Then I added -fPIE
to the compiler command. Unfortunately, nothing changed:
[tjm@ArchPad poc]$ gcc -fPIE -O0 poc.c -o poc /usr/bin/ld: /tmp/ccdK36lx.o: relocation R_X86_64_32S against symbol `S' can not be used when making a PIE object; recompile with -fPIE collect2: error: ld returned 1 exit status
This is so weird that it made me curious to compile and link it manually:
[tjm@ArchPad poc]$ gcc -fPIE -O0 poc.c -S [tjm@ArchPad poc]$ as -O0 poc.s -o poc.o [tjm@ArchPad poc]$ ld -O0 -melf_x86_64 --dynamic-linker /usr/lib/ld-linux-x86-64.so.2 /usr/lib/crt{1,i,n}.o -lc poc.o -o poc
Surprisingly, no error occurred during the attempt above. I then tested the executable’s functionality, which seems working:
[tjm@ArchPad poc]$ ./poc abcd
Any ideas of why this happens and how to prevent GCC from promoting the error above?
Advertisement
Answer
The immediate move instruction movq $S, %rsi
, despite taking a 64-bit register, takes only a 32-bit signed immediate. When building a position-independent executable which is to run with ASLR, as is the default on most Linux systems, your program is typically not located in the low or high 31 bits of virtual memory, so the address of a global or static object like S
won’t fit in a signed 32-bit immediate.
One fix is to use movabs
instead, which does take a full 64-bit immediate. Replacing movq %0, %%rsi
with movabs %0, %%rsi
lets the code build. However, the better approach is to use RIP-relative addressing lea S(%rip), %rsi
, which is a shorter instruction and avoids the need for a relocation at load time. This is a little awkward to do inside inline asm, so you can let the compiler load the address into the register for you, with an input operand like "S" (S)
(confusingly the constraint for the rsi
register is S
, which happens to coincide with the name you chose for your array variable).
When you linked manually, you built a non-position-independent executable, which meant that treating the address as a constant worked. You can get the same effect by passing -no-pie
to gcc
.
There is another serious problem with your code, in that it doesn’t declare the registers it clobbers – not only those which you explicitly mov
into, but also rcx
and r11
which syscall
modifies implicitly. You should take a look at How to invoke a system call via syscall or sysenter in inline assembly? which has a correct example. It would be wise to study those examples carefully – GCC inline asm is powerful, but also very easy to get wrong in subtle ways that the compiler will not help you detect, and which may appear to work in a simple program but fail unpredictably in a more complex one.