This patch adds an elfclassify tool, mainly for the benefit of RPM's find-debuginfo.sh.
I still need to implement an --unstripped option and fix the iteration over the dynamic section. Suggestions for improving the argp/help output are welcome as well. I'm not familiar with argp at all. I'm keeping a branch with these changes here: <https://pagure.io/fweimer/elfutils/commits/elfclassify> Thanks, Florian diff --git a/src/Makefile.am b/src/Makefile.am index 2b1c0dcb..966d1da7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,7 +26,8 @@ AM_CPPFLAGS += -I$(srcdir)/../libelf -I$(srcdir)/../libebl \ AM_LDFLAGS = -Wl,-rpath-link,../libelf:../libdw bin_PROGRAMS = readelf nm size strip elflint findtextrel addr2line \ - elfcmp objdump ranlib strings ar unstrip stack elfcompress + elfcmp objdump ranlib strings ar unstrip stack elfcompress \ + elfclassify noinst_LIBRARIES = libar.a @@ -83,6 +84,7 @@ ar_LDADD = libar.a $(libelf) $(libeu) $(argp_LDADD) unstrip_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) -ldl stack_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) -ldl $(demanglelib) elfcompress_LDADD = $(libebl) $(libelf) $(libdw) $(libeu) $(argp_LDADD) +elfclassify_LDADD = $(libelf) $(libeu) $(argp_LDADD) installcheck-binPROGRAMS: $(bin_PROGRAMS) bad=0; pid=$$$$; list="$(bin_PROGRAMS)"; for p in $$list; do \ diff --git a/src/elfclassify.c b/src/elfclassify.c new file mode 100644 index 00000000..ead3260b --- /dev/null +++ b/src/elfclassify.c @@ -0,0 +1,387 @@ +/* Classification of ELF files. + Copyright (C) 2019 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> + +#include <argp.h> +#include <error.h> +#include <fcntl.h> +#include <gelf.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include ELFUTILS_HEADER(elf) +#include "printversion.h" + +/* Name and version of program. */ +ARGP_PROGRAM_VERSION_HOOK_DEF = print_version; + +/* Bug report address. */ +ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT; + +enum classify_command +{ + classify_file = 1000, + classify_elf, + classify_executable, + classify_shared, + classify_loadable +}; + +/* Set by parse_opt. */ +static enum classify_command command; +static const char *command_path; +static int verbose; + +/* Set by map_file. */ +static int file_fd = -1; + +static void +open_file (void) +{ + if (verbose > 1) + fprintf (stderr, "debug: processing file: %s\n", command_path); + + file_fd = open (command_path, O_RDONLY); + if (file_fd < 0) + { + if (errno == ENOENT) + exit (1); + else + error (2, errno, N_("opening %s"), command_path); + } + struct stat st; + if (fstat (file_fd, &st) != 0) + error (2, errno, N_("reading %s\n"), command_path); + if (!S_ISREG (st.st_mode)) + exit (1); +} + +/* Set by open_elf. */ +static Elf *elf; + +static void +open_elf (void) +{ + open_file (); + elf = elf_begin (file_fd, ELF_C_READ, NULL); + if (elf == NULL) + error (2, 0, "%s: %s", command_path, elf_errmsg (-1)); + if (elf_kind (elf) != ELF_K_ELF && elf_kind (elf) != ELF_K_AR) + exit (1); +} + +static int elf_type; +static bool has_program_interpreter; +static bool has_dynamic; +static bool has_soname; +static bool has_pie_flag; +static bool has_dt_debug; + +static void +run_classify (void) +{ + if (elf_kind (elf) != ELF_K_ELF) + return; + + GElf_Ehdr ehdr_storage; + GElf_Ehdr *ehdr = gelf_getehdr (elf, &ehdr_storage); + if (ehdr == NULL) + exit (1); + elf_type = ehdr->e_type; + + /* Examine program headers. */ + { + size_t nphdrs; + if (elf_getphdrnum (elf, &nphdrs) != 0) + error (2, 0, "%s: program header: %s", command_path, elf_errmsg (-1)); + if (nphdrs > INT_MAX) + error (2, 0, "%s: number of program headers is too large: %zu", + command_path, nphdrs); + for (size_t phdr_idx = 0; phdr_idx < nphdrs; ++phdr_idx) + { + GElf_Phdr phdr_storage; + GElf_Phdr *phdr = gelf_getphdr (elf, phdr_idx, &phdr_storage); + if (phdr == NULL) + error (2, 0, "%s: %s", command_path, elf_errmsg (-1)); + if (phdr->p_type == PT_DYNAMIC) + has_dynamic = true; + if (phdr->p_type == PT_INTERP) + has_program_interpreter = true; + } + } + + /* Examine the dynamic section. */ + if (has_dynamic) + { + Elf_Scn *dyn_section = NULL; + { + Elf_Scn *scn = NULL; + while (true) + { + scn = elf_nextscn (elf, scn); + if (scn == NULL) + break; + GElf_Shdr shdr_storage; + GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage); + if (shdr == NULL) + error (2, 0, N_("could not obtain section header: %s"), + elf_errmsg (-1)); + if (verbose > 2) + fprintf (stderr, "debug: section header %d found\n", + shdr->sh_type); + if (shdr->sh_type == SHT_DYNAMIC) + { + if (verbose > 1) + fputs ("debug: dynamic section found", stderr); + dyn_section = scn; + break; + } + } + } + if (dyn_section != NULL) + { + Elf_Data *data = elf_getdata (dyn_section, NULL); + if (verbose > 2) + fprintf (stderr, "debug: Elf_Data for dynamic section: %p\n", + data); + + if (data != NULL) + for (int dyn_idx = 0; ; ++dyn_idx) + { + GElf_Dyn dyn_storage; + GElf_Dyn *dyn = gelf_getdyn (data, dyn_idx, &dyn_storage); + if (dyn == NULL) + break; + if (verbose > 2) + fprintf (stderr, "debug: dynamic entry %d" + " with tag %llu found\n", + dyn_idx, (unsigned long long int) dyn->d_tag); + if (dyn->d_tag == DT_SONAME) + has_soname = true; + if (dyn->d_tag == DT_FLAGS_1 && (dyn->d_un.d_val & DF_1_PIE)) + has_pie_flag = true; + if (dyn->d_tag == DT_DEBUG) + has_dt_debug = true; + if (dyn->d_tag == DT_NULL) + break; + } + } + } + + if (verbose) + { + fprintf (stderr, "info: ELF type: %d\n", elf_type); + if (has_program_interpreter) + fputs ("info: program interpreter found\n", stderr); + if (has_dynamic) + fputs ("info: dynamic segment found\n", stderr); + if (has_soname) + fputs ("info: soname found\n", stderr); + if (has_pie_flag) + fputs ("info: PIE flag found\n", stderr); + if (has_dt_debug) + fputs ("info: DT_DEBUG found\n", stderr); + } +} + +/* Return true if the file is a loadable object, which basically means + it is an ELF file, but not a relocatable object file. (The kernel + and various userspace components can load ET_REL files, but we + disregard that for our classification purposes.) */ +static bool +is_loadable (void) +{ + return elf_kind (elf) == ELF_K_ELF && elf_type != ET_REL; +} + +static bool +is_shared (void) +{ + if (!is_loadable ()) + return false; + + /* The ELF type is very clear: this is an executable. */ + if (elf_type == ET_EXEC) + return false; + + /* If the object is marked as PIE, it is definitely an executable, + and not a loadlable shared object. */ + if (has_pie_flag) + return false; + + /* Treat a DT_SONAME tag as a strong indicator that this is a shared + object. */ + if (has_soname) + return true; + + /* This is probably a PIE program: there is no soname, but a program + interpreter. In theory, this file could be also */ + if (has_program_interpreter) + return false; + + /* Roland McGrath mentions in + <https://www.sourceware.org/ml/libc-alpha/2015-03/msg00605.html>, + that “we defined a PIE as an ET_DYN with a DT_DEBUG”. This + matches current binutils behavior (version 2.32). DT_DEBUG is + added if bfd_link_executable returns true or if bfd_link_pic + returns false, depending on the architectures. However, DT_DEBUG + is not documented as being specific to executables, therefore use + it only as a low-priority discriminator. */ + if (has_dt_debug) + return false; + + /* If there is no dynamic section, the file cannot be loaded as a + shared object. */ + if (!has_dynamic) + return false; + return true; +} + +static bool +is_executable (void) +{ + if (!is_loadable ()) + return false; + + /* A loadable object which is not a shared object is treated as an + executable. */ + return !is_shared (); +} + +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + switch (key) + { + case classify_file: + case classify_elf: + case classify_executable: + case classify_shared: + case classify_loadable: + command = key; + command_path = arg; + break; + + case 'v': + ++verbose; + break; + + case ARGP_KEY_ARG: + argp_usage (state); + exit (2); + } + + return 0; +} + +int +main (int argc, char **argv) +{ + const struct argp_option options[] = + { + { "file", classify_file, "PATH", 0, + N_("Check PATH is file that can be read"), 0 }, + { "elf", classify_elf, "PATH", 0, + N_("Check if the file at PATH is a valid ELF object"), 0 }, + { "executable", classify_executable, "PATH", 0, + N_("Check if the file at PATH is an ELF program executable"), 0 }, + { "shared", classify_shared, "PATH", 0, + N_("Check if the file at PATH is an ELF shared object (DSO)"), 0 }, + { "loadable", classify_loadable, "PATH", 0, + N_("Check if the file at PATH is a loadable object (program or shared object)"), 0 }, + { "verbose", 'v', NULL, 0, + N_("Output additional information (can be specified multiple times)"), 0 }, + { NULL, 0, NULL, 0, NULL, 0 } + }; + + const struct argp argp = + { + .options = options, + .parser = parse_opt, + .doc = N_("Determine the type of an ELF file.") + }; + + if (argp_parse (&argp, argc, argv, ARGP_NO_EXIT, NULL, NULL) != 0) + return 2; + + elf_version (EV_CURRENT); + + switch (command) + { + case classify_file: + open_file (); + return 0; + case classify_elf: + open_elf (); + return 0; + + case classify_executable: + open_elf (); + run_classify (); + if (is_executable ()) + { + if (verbose) + fputs ("info: executable\n", stderr); + return 0; + } + else + { + if (verbose) + fputs ("info: not an executable\n", stderr); + return 1; + } + + case classify_shared: + open_elf (); + run_classify (); + if (is_shared ()) + { + if (verbose) + fputs ("info: shared object\n", stderr); + return 0; + } + else + { + if (verbose) + fputs ("info: not a shared object\n", stderr); + return 1; + } + + case classify_loadable: + open_elf (); + run_classify (); + if (is_loadable ()) + { + if (verbose) + fputs ("info: loadable object\n", stderr); + return 0; + } + else + { + if (verbose) + fputs ("info: not a loadable object\n", stderr); + return 1; + } + } + + return 2; +}