file 5.36 says it clearly
file 5.36 actually prints it clearly if the executable is PIE or not, as shown at: https://unix.stackexchange.com/questions/89211/how-to-test-whether-a-linux-binary-was-compiled Positionally Independent Code / 435038 # 435038
For example, the PIE executable shows how:
main.out: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, not stripped
and not -pie like:
main.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
This function was introduced in 5.33, but it performed a simple check on chmod +x . Before that, he simply printed a shared object for PIE.
In 5.34, it was supposed to start checking more specialized ELF metadata DF_1_PIE , but due to an implementation error on commit 9109a696f3289ba00eaa222fd432755ec4287e28, it actually broke things and showed GCC PIE executables as shared objects .
The error was fixed in 5.36 when commit 03084b161cf888b5286dbbcd964c31ccad4f64d9 .
This error is present, in particular, in Ubuntu 18.10, in which there is file 5.34.
It does not show up when associating assembler code with ld -pie due to a match.
For a breakdown of the source code, see the “Analyzing the source file 5.36” section of this answer.
Linux kernel 5.0 determines whether ET_DYN based ASLR can be used
The main reason for file "confusion" is that both PIE executables and shared libraries are position-independent and can be placed in randomized memory locations.
In fs / binfmt_elf.c, the kernel accepts only these two types of ELF files:
if (interp_elf_ex->e_type != ET_EXEC && interp_elf_ex->e_type != ET_DYN) goto out;
Then only for ET_DYN it set load_bias to the value, load_bias from zero. Then load_bias determines the ELF offset: How is the text section address of the PIE executable on Linux determined?
if (loc->elf_ex.e_type == ET_EXEC || load_addr_set) { elf_flags |= elf_fixed; } else if (loc->elf_ex.e_type == ET_DYN) { if (elf_interpreter) { load_bias = ELF_ET_DYN_BASE; if (current->flags & PF_RANDOMIZE) load_bias += arch_mmap_rnd(); elf_flags |= elf_fixed; } else load_bias = 0;
I confirm this experimentally at: What is the -fPIE option for position-independent executables in gcc and ld?
breakdown of behavior file 5.36
Having studied how file works from its source. We conclude that:
- if
Elf32_Ehdr.e_type == ET_EXEC - otherwise if
Elf32_Ehdr.e_type == ET_DYN- if
DT_FLAGS_1 dynamic section entry is present- if
DF_1_PIE set to DT_FLAGS_1 : - yet
- yet
- if the file is executable by a user, group or others
- yet
And here are a few experiments that confirm that:
Executable generation ELF type DT_FLAGS_1 DF_1_PIE chdmod +x file 5.36 --------------------------- -------- ---------- -------- -------------- -------------- gcc -fpie -pie ET_DYN yyy pie executable gcc -fno-pie -no-pie ET_EXEC nny executable gcc -shared ET_DYN nny pie executable gcc -shared ET_DYN nnn shared object ld ET_EXEC nny executable ld -pie --dynamic-linker ET_DYN yyy pie executable ld -pie --no-dynamic-linker ET_DYN yyy pie executable
Tested on Ubuntu 18.10, GCC 8.2.0, Binutils 2.31.1.
A complete test example for each type of experiment is described at:
ELF type and DF_1_PIE defined respectively using:
readelf --file-header main.out | grep Type readelf --dynamic main.out | grep FLAGS_1
file 5.36 source code analysis
The key file for analysis is magic / Magdir / elf .
This magic format defines file types depending only on byte values in fixed positions.
The format itself is documented at:
man 5 magic
Therefore, at the moment, you will need the following documents:
At the end of the file we see:
0 string \177ELF ELF !:strength *2 >4 byte 0 invalid class >4 byte 1 32-bit >4 byte 2 64-bit >5 byte 0 invalid byte order >5 byte 1 LSB >>0 use elf-le >5 byte 2 MSB >>0 use \^elf-le
\177ELF is 4 magic bytes at the beginning of each ELF file. \177 is octal for 0x7F .
Then, comparing with the Elf32_Ehdr structure from the standard, we see that byte 4 (5th byte, the first after the magic identifier) defines the ELF class:
e_ident[EI_CLASSELFCLASS]
and some of its possible meanings:
ELFCLASS32 1 ELFCLASS64 2
In the original file we have:
1 32-bit 2 64-bit
both 32-bit and 64-bit lines are file which are output to standard output!
So now we are looking for a shared object in this file, and we are led to:
0 name elf-le >16 leshort 0 no file type, !:mime application/octet-stream >16 leshort 1 relocatable, !:mime application/x-object >16 leshort 2 executable, !:mime application/x-executable >16 leshort 3 ${x?pie executable:shared object},
So this elf-le is a kind of identifier that is included in the previous part of the code.
Byte 16 exactly corresponds to the ELF type:
Elf32_Ehdr.e_type
and some of its meanings:
ET_EXEC 2 ET_DYN 3
Therefore, ET_EXEC always printed as executable .
However, ET_DYN has two possibilities depending on ${x :
pie executableshared object
${x asks: is the executable file or not a user, group or other? If yes, show pie executable , otherwise a shared object .
This extension is executed in the varexpand function in src/softmagic.c :
static int varexpand(struct magic_set *ms, char *buf, size_t len, const char *str) { [...] case 'x': if (ms->mode & 0111) { ptr = t; l = et - t; } else { ptr = e; l = ee - e; } break;
There is, however, another hack! In the dodynamic function src/readelf.c dodynamic , if DT_FLAGS_1 dynamic section DT_FLAGS_1 entry ( PT_DYNAMIC ), then permissions in st->mode are canceled by the presence or absence of the DF_1_PIE flag:
case DT_FLAGS_1: if (xdh_val & DF_1_PIE) ms->mode |= 0111; else ms->mode &= ~0111; break;
The error in 5.34 is that the source code was written as:
if (xdh_val == DF_1_PIE)
this means that if another flag was set, which GCC makes by default due to DF_1_NOW , the executable DF_1_NOW is a shared object .
This flag entry is not described in the ELF standard, so it must be an extension of Binutils.
This flag does not make sense in the Linux kernel 5.0 or glibc 2.27, so it seems to me that it is informative enough to indicate whether the file is a PIE or not.