1. Overview

The position-independent code (PIC) and position-independent executable (PIE) terms refer to machine code that runs properly regardless of the memory location it’s loaded at, i.e., the memory location doesn’t need to be a fixed address.

PIC is commonly used in building shared libraries. Since the operating system loads a shared library into the address space of a process that uses the library, the shared library must be loadable to any memory location. Therefore, shared libraries are compiled as PIC.

Building executables as PIE is important for Address Space Layout Randomization (ASLR) in Linux. ASLR is a security method that randomizes the base address of a process together with the locations of its heap, stack, and libraries. Therefore, it mitigates several exploits and security risks like buffer overflows.

In this tutorial, we’ll discuss how to check whether a Linux executable is position-independent. First, we’ll build an example program using the compiler’s PIE and non-PIE options. Then, we’ll check the position-independency of the executables using the file, readelf, hardening-check, and checksec binaries and utilities.

2. Example Setup

We’ll use the following C program, hello_baeldung.c, in our examples:

$ cat hello_baeldung.c
#include <stdio.h>

int main(void)
{
    printf("Hello Baeldung\n");

    return 0;
}

The program prints Hello Baeldung and exits.

2.1. Building With the -fpie and -pie Options

We’ll build hello_baeldung.c using gcc to generate the position-independent hello_baeldung_pie executable.

First, we begin with compiling hello_baeldung.c:

$ gcc -c hello_baeldung.c -o hello_baeldung_pie.o -fpie
$ ls hello_baeldung_pie.o
hello_baeldung_pie.o

The *-c hello_baeldung.*c part of the command compiles *hello_baeldung.c*. The *–o* option of gcc specifies the name of the output object file, which is hello_baeldung_pie.o in our case. The -fpie option generates position-independent code suitable for linking into an executable. We use this option to compile code that will be linked using the -pie option of gcc.

Next, we generate the executable file using the object file:

$ gcc hello_baeldung_pie.o -o hello_baeldung_pie -pie
$ ls hello_baeldung_pie
hello_baeldung_pie

The –**o hello_baeldung_pie part of the command specifies the executable’s name. It’s hello_baeldung_pie in our case. The -pie option generates a dynamically linked position-independent executable.

There are other similar options of gcc for producing position-independent code. For example, the -fpic option generates position-independent code like the -fpie option, but the generated code is suitable for use in a shared library instead of an executable.

Although we’ve used the -fpie and -pie options explicitly while compiling and linking, gcc uses them implicitly by default. So, we don’t need to specify them.

We’ve built the executable in two steps, first by compiling and then linking, to show the usage of the -fpie option. However, it’s possible to skip the generation of object files and directly generate the executable from the source file:

$ gcc hello_baeldung.c -o hello_baeldung_pie -pie

It’s sufficient to use only the -pie option in this case.

2.2. Building With the -fno-pie and -no-pie Options

We’ll now build hello_baeldung.c using gcc to generate the hello_baeldung_no_pie executable, which isn’t position-independent:

$ gcc -c hello_baeldung.c -o hello_baeldung_no_pie.o -fno-pie
$ ls hello_baeldung_no_pie.o
hello_baeldung_no_pie.o
$ gcc hello_baeldung_no_pie.o -o hello_baeldung_no_pie -no-pie
$ ls hello_baeldung_no_pie
hello_baeldung_no_pie

The steps are similar to the ones we’ve seen earlier. However, we use the -fno-pie and -no-pie options during compiling and linking, respectively. These options are the counterparts of -fpie and -pie, and they specify not to produce a position-independent executable.

It’s also possible to directly generate the executable from the source file as before:

$ gcc hello_baeldung.c -o hello_baeldung_no_pie -no-pie

It’s sufficient to use only the -no-pie option in this case.

2.3. Running the Executables

Having built the executables, let’s check whether they run successfully:

$ ./hello_baeldung_pie
Hello Baeldung
$ ./hello_baeldung_no_pie
Hello Baeldung

Both executables run successfully.

3. Using file

The file command is commonly used to check the type of a file. The version of the file command we use is 5.41.

Besides giving information about the type of a file, the file command specifies whether the executable was built as a position-independent executable:

$ file hello_baeldung_pie
hello_baeldung_pie: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ed92176c00739acc54e27075768fcf61dbc0cf88, for GNU/Linux 3.2.0, not stripped

The ELF 64-bit LSB pie executable part in the output shows that the executable is position-independent. Additionally, we can filter the output of the file command by using its -e option:

$ file -e elf hello_baeldung_pie
hello_baeldung_pie: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV)

The -e elf option prints only the ELF (Executable and Linkable Format) file details. ELF is the standard binary file format in Unix and Unix-like operating systems, including Linux.

Let’s check the other executable, hello_baeldung_no_pie:

$ file -e elf hello_baeldung_no_pie
hello_baeldung_no_pie: ELF 64-bit LSB executable, x86-64, version 1 (SYSV)

As is apparent from the output, this executable isn’t position-independent since the word pie doesn’t appear in ELF 64-bit LSB executable.

We can further automate the detection of position-independency by filtering the output of the file command using grep:

$ file -e elf hello_baeldung_pie | grep "pie executable"
hello_baeldung_pie: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV)
$ echo $?
0
$ file -e elf hello_baeldung_no_pie | grep "pie executable"
$ echo $?
1

We filter the output using grep “pie executable”. The exit status is 0 if the executable is position-independent. Otherwise, the exit status is 1.

4. Using readelf

Another option for checking whether an executable is position-independent is the readelf command. The readelf command displays information about ELF files. The version of the readelf command we use is 2.38.

Let’s check whether hello_baeldung_pie is position-independent using readelf:

$ readelf -h hello_baeldung_pie
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x1060
  Start of program headers:          64 (bytes into file)
  Start of section headers:          13984 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 30

The -h option of readelf displays the information in the ELF header. The Type field in the output specifies the file type. According to the listed value, namely Position-Independent Executable file, the executable is position-independent, as expected.

We can filter the output using grep to print only the type of the executable:

$ readelf -h hello_baeldung_pie | grep Type
  Type:                              DYN (Position-Independent Executable file)

Let’s now check hello_baeldung_no_pie:

$ readelf -h hello_baeldung_no_pie | grep Type
  Type:                              EXEC (Executable file)

In this case, the executable file isn’t position-independent according to the output.

5. Using hardening-check

We can use the Perl script, hardening-check, to check whether an executable is position-independent. We need to install the devscripts package on Ubuntu to use hardening-check. The version of the package we use is 2.22.1.

The hardening-check script checks binaries for security hardening features. Stack buffer overflow protection and using safe glibc functions are examples of security hardening checks the script performs. It also checks if the executable is position-independent:

$ hardening-check hello_baeldung_pie
hello_baeldung_pie:
 Position Independent Executable: yes
 Stack protected: no, not found!
 Fortify Source functions: unknown, no protectable libc functions used
 Read-only relocations: yes
 Immediate binding: yes
 Stack clash protection: unknown, no -fstack-clash-protection instructions found
 Control flow integrity: yes

We pass the executable as an input to the script. As is apparent from the second line of the output, Position Independent Executable: yes, the hello_baeldung_pie executable is position-independent.

Let’s also check hello_baeldung_no_pie:

$ hardening-check hello_baeldung_no_pie
hello_baeldung_no_pie:
 Position Independent Executable: no, normal executable!
 Stack protected: no, not found!
 Fortify Source functions: unknown, no protectable libc functions used
 Read-only relocations: yes
 Immediate binding: no, not found!
 Stack clash protection: unknown, no -fstack-clash-protection instructions found
 Control flow integrity: yes

Obviously, hello_baeldung_no_pie isn’t position-independent according to the second line in the output, Position Independent Executable: no, normal executable!.

6. Using checksec

The checksec tool is another alternative for checking if an executable is position-independent. It’s a shell script that checks the security features added to an executable while it’s being built. We need to install the checksec package on Ubuntu to use it. The version of checksec we use is 2.4.0-1.

Let’s use checksec for hello_baeldung_pie:

$ checksec --output=json --file=hello_baeldung_pie | jq
{
  "hello_baeldung_pie": {
    "relro": "full",
    "canary": "no",
    "nx": "yes",
    "pie": "yes",
    "rpath": "no",
    "runpath": "no",
    "symbols": "yes",
    "fortify_source": "no",
    "fortified": "0",
    "fortify-able": "0"
  }
}

checksec can display the results in different formats including JSON, XML, and CSV. The –output=json option displays the results in JSON format. The –file option specifies the executable’s name. It’s hello_baeldung_pie in our case. We pass the output of checksec to the jq command using a pipe to pretty-print the result on multiple lines.

As is apparent from the “pie”: “yes” line in the output, the executable is position-independent.

Let’s also check hello_baeldung_no_pie:

$ checksec --output=json --file=hello_baeldung_no_pie | jq
{
  "hello_baeldung_no_pie": {
    "relro": "partial",
    "canary": "no",
    "nx": "yes",
    "pie": "no",
    "rpath": "no",
    "runpath": "no",
    "symbols": "yes",
    "fortify_source": "no",
    "fortified": "0",
    "fortify-able": "0"
  }
}

The line in the output indicating “pie”: “no” shows that hello_baeldung_no_pie isn’t position-independent, as expected.

7. Conclusion

In this article, we discussed how to check whether a Linux executable is position-independent.

First, we built two executables using the same C program. We used the -fpie and -pie options of gcc to obtain a position-independent executable, and the -fno-pie and -no-pie options to obtain an executable that isn’t position-independent. Then, we learned that we can use the file and readelf commands to check for position-independency. Finally, we saw that the hardening-check and checksec scripts are alternatives for achieving the same task.