cgccx86elfgot

Why is there no push instruction in the PLT table?


I have written a sample C code as below

#include<stdio.h>

int foo(void);
int main()
{
    puts("Hello World");
    int x = 10;
    fprintf(stdout,"Hello World 22");
    return foo();
}

I compile it via the

gcc -shared -fpic hello_world.c -o hello_world.so

above gcc command.

The assembly for the plt call for puts looks like as below

0000000000001070 <puts@plt>:
    1070:   f3 0f 1e fa             endbr64 
    1074:   f2 ff 25 9d 2f 00 00    bnd jmpq *0x2f9d(%rip)        # 4018 <puts@GLIBC_2.2.5>
    107b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1) 

Similarly for foo()


0000000000001080 <foo@plt>:
    1080:   f3 0f 1e fa             endbr64 
    1084:   f2 ff 25 95 2f 00 00    bnd jmpq *0x2f95(%rip)        # 4020 <foo>
    108b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

but the assembly looks the same.

I thought, the instructions are usually like this

  1. jump to GOT table
  2. push relocation identifier
  3. jump to dynamic linker

But here it seems like it directly jumps to GLIBC. Have I understood something wrong ? or am i compiling it incorrectly ?


Solution

  • This has to do with -fcf-protection. Ubuntu's GCC is configured with --enable-cet, which enables -fcf-protection instrumentation by default. If you compile with -fcf-protection=none you will see the "normal" PLT entries that do JMP [<got-entry>]; PUSH <idx>; JMP <pltstub>.

    With -fcf-protection (also =branch or =full), an additional section is added called .plt.sec, where the actual function call stubs sit. Each stub starts with an ENDBR64 (see What does the endbr64 instruction actually do?) to mark it as a valid branching target and all JMP instructions become BND JMP (see Meaning of BND RET in x86). The PUSH <idx> instructions to push the relocation index are all in .plt, which now holds a sequence of ENDBR64; PUSH <idx>; BND JUMP <pltstub> (as you can see below).

    In the "normal" case, each GOT entry is initially filled with the address of the next instruction of the corresponding PLT entry.

    In the case of .plt.sec, the GOT entries are instead initially filled with .plt + <offset> where <offset> points to the corresponding sequence of ENDBR64; PUSH <idx>; BND JMP <pltstub> in .plt.

    As to why this was designed like this, I am not sure. I found this perf patch that seems to give some insights about .plt.sec.


    Here's how the PLT looks with -fcf-protection=none, which is what you are expecting:

    Disassembly of section .plt:
    
    0000000000001020 <puts@plt-0x10>:
        1020:       ff 35 e2 2f 00 00       push   0x2fe2(%rip)        # 4008 <_GLOBAL_OFFSET_TABLE_+0x8>
        1026:       ff 25 e4 2f 00 00       jmp    *0x2fe4(%rip)        # 4010 <_GLOBAL_OFFSET_TABLE_+0x10>
        102c:       0f 1f 40 00             nopl   0x0(%rax)
    
    0000000000001030 <puts@plt>:
        1030:       ff 25 e2 2f 00 00       jmp    *0x2fe2(%rip)        # 4018 <puts@GLIBC_2.2.5>
        1036:       68 00 00 00 00          push   $0x0
        103b:       e9 e0 ff ff ff          jmp    1020 <_init+0x20>
    

    And here's how it looks with -fcf-protection:

    Disassembly of section .plt:
    
    0000000000001020 <.plt>:
        1020:       ff 35 e2 2f 00 00       push   0x2fe2(%rip)        # 4008 <_GLOBAL_OFFSET_TABLE_+0x8>
        1026:       f2 ff 25 e3 2f 00 00    bnd jmp *0x2fe3(%rip)        # 4010 <_GLOBAL_OFFSET_TABLE_+0x10>
        102d:       0f 1f 00                nopl   (%rax)
        1030:       f3 0f 1e fa             endbr64
        1034:       68 00 00 00 00          push   $0x0
        1039:       f2 e9 e1 ff ff ff       bnd jmp 1020 <_init+0x20>
        103f:       90                      nop
        1040:       f3 0f 1e fa             endbr64
        1044:       68 01 00 00 00          push   $0x1
        1049:       f2 e9 d1 ff ff ff       bnd jmp 1020 <_init+0x20>
        104f:       90                      nop
        1050:       f3 0f 1e fa             endbr64
        1054:       68 02 00 00 00          push   $0x2
        1059:       f2 e9 c1 ff ff ff       bnd jmp 1020 <_init+0x20>
        105f:       90                      nop
    
    Disassembly of section .plt.got:
    
    0000000000001060 <__cxa_finalize@plt>:
        1060:       f3 0f 1e fa             endbr64
        1064:       f2 ff 25 8d 2f 00 00    bnd jmp *0x2f8d(%rip)        # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
        106b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
    
    Disassembly of section .plt.sec:
    
    0000000000001070 <puts@plt>:
        1070:       f3 0f 1e fa             endbr64
        1074:       f2 ff 25 9d 2f 00 00    bnd jmp *0x2f9d(%rip)        # 4018 <puts@GLIBC_2.2.5>
        107b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)