diff --git a/usr.bin/ldd/Makefile b/usr.bin/ldd/Makefile index 5d2beb7bb6c0..78e551d80a33 100644 --- a/usr.bin/ldd/Makefile +++ b/usr.bin/ldd/Makefile @@ -1,6 +1,8 @@ # $FreeBSD$ PROG?= ldd SRCS= ldd.c +LIBADD= elf + .include diff --git a/usr.bin/ldd/ldd.c b/usr.bin/ldd/ldd.c index 08c91d829bd7..f948b7312863 100644 --- a/usr.bin/ldd/ldd.c +++ b/usr.bin/ldd/ldd.c @@ -1,437 +1,472 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1993 Paul Kranenburg * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Paul Kranenburg. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); +#include #include #include #include #include #include #include #include +#include +#include +#include #include #include #include #include /* * 32-bit ELF data structures can only be used if the system header[s] declare * them. There is no official macro for determining whether they are declared, * so check for the existence of one of the 32-macros defined in elf(5). */ #ifdef ELF32_R_TYPE #define ELF32_SUPPORTED #endif #define LDD_SETENV(name, value, overwrite) do { \ setenv("LD_" name, value, overwrite); \ setenv("LD_32_" name, value, overwrite); \ } while (0) #define LDD_UNSETENV(name) do { \ unsetenv("LD_" name); \ unsetenv("LD_32_" name); \ } while (0) static int is_executable(const char *fname, int fd, int *is_shlib, int *type); static void usage(void); #define TYPE_UNKNOWN 0 #define TYPE_ELF 1 /* Architecture default */ #if __ELF_WORD_SIZE > 32 && defined(ELF32_SUPPORTED) #define TYPE_ELF32 2 /* Explicit 32 bits on architectures >32 bits */ #define _PATH_LDD32 "/usr/bin/ldd32" static int execldd32(char *file, char *fmt1, char *fmt2, int aflag, int vflag) { char *argv[9]; int i, rval, status; LDD_UNSETENV("TRACE_LOADED_OBJECTS"); rval = 0; i = 0; argv[i++] = strdup(_PATH_LDD32); if (aflag) argv[i++] = strdup("-a"); if (vflag) argv[i++] = strdup("-v"); if (fmt1 != NULL) { argv[i++] = strdup("-f"); argv[i++] = strdup(fmt1); } if (fmt2 != NULL) { argv[i++] = strdup("-f"); argv[i++] = strdup(fmt2); } argv[i++] = strdup(file); argv[i++] = NULL; switch (fork()) { case -1: err(1, "fork"); break; case 0: execv(_PATH_LDD32, argv); warn("%s", _PATH_LDD32); _exit(127); break; default: if (wait(&status) < 0) rval = 1; else if (WIFSIGNALED(status)) rval = 1; else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) rval = 1; break; } while (i--) free(argv[i]); LDD_SETENV("TRACE_LOADED_OBJECTS", "yes", 1); return (rval); } #endif int main(int argc, char *argv[]) { char *fmt1, *fmt2; int rval, c, aflag, vflag; aflag = vflag = 0; fmt1 = fmt2 = NULL; while ((c = getopt(argc, argv, "af:v")) != -1) { switch (c) { case 'a': aflag++; break; case 'f': if (fmt1 != NULL) { if (fmt2 != NULL) errx(1, "too many formats"); fmt2 = optarg; } else fmt1 = optarg; break; case 'v': vflag++; break; default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (vflag && fmt1 != NULL) errx(1, "-v may not be used with -f"); if (argc <= 0) { usage(); /* NOTREACHED */ } rval = 0; for (; argc > 0; argc--, argv++) { int fd, status, is_shlib, rv, type; if ((fd = open(*argv, O_RDONLY, 0)) < 0) { warn("%s", *argv); rval |= 1; continue; } rv = is_executable(*argv, fd, &is_shlib, &type); close(fd); if (rv == 0) { rval |= 1; continue; } switch (type) { case TYPE_ELF: break; #if __ELF_WORD_SIZE > 32 && defined(ELF32_SUPPORTED) case TYPE_ELF32: rval |= execldd32(*argv, fmt1, fmt2, aflag, vflag); continue; #endif case TYPE_UNKNOWN: default: /* * This shouldn't happen unless is_executable() * is broken. */ errx(EDOOFUS, "unknown executable type"); } /* ld.so magic */ LDD_SETENV("TRACE_LOADED_OBJECTS", "yes", 1); if (fmt1 != NULL) LDD_SETENV("TRACE_LOADED_OBJECTS_FMT1", fmt1, 1); if (fmt2 != NULL) LDD_SETENV("TRACE_LOADED_OBJECTS_FMT2", fmt2, 1); LDD_SETENV("TRACE_LOADED_OBJECTS_PROGNAME", *argv, 1); if (aflag) LDD_SETENV("TRACE_LOADED_OBJECTS_ALL", "1", 1); else if (fmt1 == NULL && fmt2 == NULL) /* Default formats */ printf("%s:\n", *argv); fflush(stdout); switch (fork()) { case -1: err(1, "fork"); break; default: if (wait(&status) < 0) { warn("wait"); rval |= 1; } else if (WIFSIGNALED(status)) { fprintf(stderr, "%s: signal %d\n", *argv, WTERMSIG(status)); rval |= 1; } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { fprintf(stderr, "%s: exit status %d\n", *argv, WEXITSTATUS(status)); rval |= 1; } break; case 0: if (is_shlib == 0) { execl(*argv, *argv, (char *)NULL); warn("%s", *argv); } else { dlopen(*argv, RTLD_TRACE); warnx("%s: %s", *argv, dlerror()); } _exit(1); } } return rval; } static void usage(void) { fprintf(stderr, "usage: ldd [-a] [-v] [-f format] program ...\n"); exit(1); } -static int -is_executable(const char *fname, int fd, int *is_shlib, int *type) +static bool +has_freebsd_abi_tag(const char *fname, Elf *elf, GElf_Ehdr *ehdr, off_t offset, + size_t len) { - union { -#if __ELF_WORD_SIZE > 32 && defined(ELF32_SUPPORTED) - Elf32_Ehdr elf32; -#endif - Elf_Ehdr elf; - } hdr; - Elf_Phdr phdr, dynphdr; - Elf_Dyn *dynp; - void *dyndata; -#if __ELF_WORD_SIZE > 32 && defined(ELF32_SUPPORTED) - Elf32_Phdr phdr32, dynphdr32; - Elf32_Dyn *dynp32; -#endif - int df1pie, dynamic, i, n; - - *is_shlib = 0; - *type = TYPE_UNKNOWN; - df1pie = 0; + Elf_Data dst, src; + const Elf_Note *note; + char *buf; + const char *name; + void *copy; + size_t namesz, descsz; + bool has_abi_tag; + + buf = elf_rawfile(elf, NULL); + if (buf == NULL) { + warnx("%s: %s", fname, elf_errmsg(0)); + return (false); + } - if ((n = read(fd, &hdr, sizeof(hdr))) == -1) { - warn("%s: can't read program header", fname); - return (0); + memset(&src, 0, sizeof(src)); + src.d_buf = buf + offset; + src.d_size = len; + src.d_type = ELF_T_NOTE; + src.d_version = EV_CURRENT; + + memset(&dst, 0, sizeof(dst)); + dst.d_buf = copy = malloc(len); + dst.d_size = len; + dst.d_type = ELF_T_NOTE; + dst.d_version = EV_CURRENT; + + if (gelf_xlatetom(elf, &dst, &src, ehdr->e_ident[EI_DATA]) == NULL) { + warnx("%s: failed to parse notes: %s", fname, elf_errmsg(0)); + free(copy); + return (false); } -#if __ELF_WORD_SIZE > 32 && defined(ELF32_SUPPORTED) - if ((size_t)n >= sizeof(hdr.elf32) && IS_ELF(hdr.elf32) && - hdr.elf32.e_ident[EI_CLASS] == ELFCLASS32) { - /* Handle 32 bit ELF objects */ + buf = copy; + has_abi_tag = false; + for (;;) { + if (len < sizeof(*note)) + break; - dynamic = 0; - *type = TYPE_ELF32; + note = (const void *)buf; + buf += sizeof(*note); + len -= sizeof(*note); - if (lseek(fd, hdr.elf32.e_phoff, SEEK_SET) == -1) { - warnx("%s: header too short", fname); - return (0); - } - for (i = 0; i < hdr.elf32.e_phnum; i++) { - if (read(fd, &phdr32, hdr.elf32.e_phentsize) != - sizeof(phdr32)) { - warnx("%s: can't read program header", fname); - return (0); - } - if (phdr32.p_type == PT_DYNAMIC) { - dynamic = 1; - dynphdr32 = phdr32; - break; - } - } + namesz = roundup2(note->n_namesz, sizeof(uint32_t)); + descsz = roundup2(note->n_descsz, sizeof(uint32_t)); + if (len < namesz + descsz) + break; - if (!dynamic) { - warnx("%s: not a dynamic ELF executable", fname); - return (0); + name = buf; + if (note->n_namesz == sizeof(ELF_NOTE_FREEBSD) && + strncmp(name, ELF_NOTE_FREEBSD, note->n_namesz) == 0 && + note->n_type == NT_FREEBSD_ABI_TAG && + note->n_descsz == sizeof(uint32_t)) { + has_abi_tag = true; + break; } - if (hdr.elf32.e_type == ET_DYN) { - if (lseek(fd, dynphdr32.p_offset, SEEK_SET) == -1) { - warnx("%s: dynamic segment out of range", - fname); - return (0); - } - dyndata = malloc(dynphdr32.p_filesz); - if (dyndata == NULL) { - warn("malloc"); - return (0); - } - if (read(fd, dyndata, dynphdr32.p_filesz) != - (ssize_t)dynphdr32.p_filesz) { - free(dyndata); - warnx("%s: can't read dynamic segment", fname); - return (0); - } - for (dynp32 = dyndata; dynp32->d_tag != DT_NULL; - dynp32++) { - if (dynp32->d_tag != DT_FLAGS_1) - continue; - df1pie = (dynp32->d_un.d_val & DF_1_PIE) != 0; - break; - } - free(dyndata); + buf += namesz + descsz; + len -= namesz + descsz; + } - if (hdr.elf32.e_ident[EI_OSABI] == ELFOSABI_FREEBSD) { - if (!df1pie) - *is_shlib = 1; - return (1); - } - warnx("%s: not a FreeBSD ELF shared object", fname); - return (0); - } + free(copy); + return (has_abi_tag); +} - return (1); +static bool +is_pie(const char *fname, Elf *elf, GElf_Ehdr *ehdr, off_t offset, size_t len) +{ + Elf_Data dst, src; + char *buf; + void *copy; + const GElf_Dyn *dyn; + size_t dynsize; + u_int count, i; + bool pie; + + buf = elf_rawfile(elf, NULL); + if (buf == NULL) { + warnx("%s: %s", fname, elf_errmsg(0)); + return (false); } -#endif - if ((size_t)n >= sizeof(hdr.elf) && IS_ELF(hdr.elf) && - hdr.elf.e_ident[EI_CLASS] == ELF_TARG_CLASS) { - /* Handle default ELF objects on this architecture */ + dynsize = gelf_fsize(elf, ELF_T_DYN, 1, EV_CURRENT); + if (dynsize == 0) { + warnx("%s: %s", fname, elf_errmsg(0)); + return (false); + } + count = len / dynsize; + + memset(&src, 0, sizeof(src)); + src.d_buf = buf + offset; + src.d_size = len; + src.d_type = ELF_T_DYN; + src.d_version = EV_CURRENT; + + memset(&dst, 0, sizeof(dst)); + dst.d_buf = copy = malloc(count * sizeof(*dyn)); + dst.d_size = count * sizeof(*dyn); + dst.d_type = ELF_T_DYN; + dst.d_version = EV_CURRENT; + + if (gelf_xlatetom(elf, &dst, &src, ehdr->e_ident[EI_DATA]) == NULL) { + warnx("%s: failed to parse .dynamic: %s", fname, elf_errmsg(0)); + free(copy); + return (false); + } + + dyn = copy; + pie = false; + for (i = 0; i < count; i++) { + if (dyn[i].d_tag != DT_FLAGS_1) + continue; + + pie = (dyn[i].d_un.d_val & DF_1_PIE) != 0; + break; + } + + free(copy); + return (pie); +} + +static int +is_executable(const char *fname, int fd, int *is_shlib, int *type) +{ + Elf *elf; + GElf_Ehdr ehdr; + GElf_Phdr phdr; + bool dynamic, freebsd, pie; + int i; + + *is_shlib = 0; + *type = TYPE_UNKNOWN; + dynamic = false; + freebsd = false; + pie = false; + + if (elf_version(EV_CURRENT) == EV_NONE) { + warnx("unsupported libelf"); + return (0); + } + elf = elf_begin(fd, ELF_C_READ, NULL); + if (elf == NULL) { + warnx("%s: %s", fname, elf_errmsg(0)); + return (0); + } + if (elf_kind(elf) != ELF_K_ELF) { + elf_end(elf); + warnx("%s: not a dynamic ELF executable", fname); + return (0); + } + if (gelf_getehdr(elf, &ehdr) == NULL) { + warnx("%s: %s", fname, elf_errmsg(0)); + elf_end(elf); + return (0); + } - dynamic = 0; - *type = TYPE_ELF; + *type = TYPE_ELF; +#if __ELF_WORD_SIZE > 32 && defined(ELF32_SUPPORTED) + if (gelf_getclass(elf) == ELFCLASS32) { + *type = TYPE_ELF32; + } +#endif - if (lseek(fd, hdr.elf.e_phoff, SEEK_SET) == -1) { - warnx("%s: header too short", fname); + freebsd = ehdr.e_ident[EI_OSABI] == ELFOSABI_FREEBSD; + for (i = 0; i < ehdr.e_phnum; i++) { + if (gelf_getphdr(elf, i, &phdr) == NULL) { + warnx("%s: %s", fname, elf_errmsg(0)); + elf_end(elf); return (0); } - for (i = 0; i < hdr.elf.e_phnum; i++) { - if (read(fd, &phdr, hdr.elf.e_phentsize) - != sizeof(phdr)) { - warnx("%s: can't read program header", fname); - return (0); - } - if (phdr.p_type == PT_DYNAMIC) { - dynamic = 1; - dynphdr = phdr; - break; - } + switch (phdr.p_type) { + case PT_NOTE: + if (ehdr.e_ident[EI_OSABI] == ELFOSABI_NONE && !freebsd) + freebsd = has_freebsd_abi_tag(fname, elf, &ehdr, + phdr.p_offset, phdr.p_filesz); + break; + case PT_DYNAMIC: + dynamic = true; + if (ehdr.e_type == ET_DYN) + pie = is_pie(fname, elf, &ehdr, phdr.p_offset, + phdr.p_filesz); + break; } + } - if (!dynamic) { - warnx("%s: not a dynamic ELF executable", fname); - return (0); - } + if (!dynamic) { + elf_end(elf); + warnx("%s: not a dynamic ELF executable", fname); + return (0); + } - if (hdr.elf.e_type == ET_DYN) { - if (lseek(fd, dynphdr.p_offset, SEEK_SET) == -1) { - warnx("%s: dynamic segment out of range", - fname); - return (0); - } - dyndata = malloc(dynphdr.p_filesz); - if (dyndata == NULL) { - warn("malloc"); - return (0); - } - if (read(fd, dyndata, dynphdr.p_filesz) != - (ssize_t)dynphdr.p_filesz) { - free(dyndata); - warnx("%s: can't read dynamic segment", fname); - return (0); - } - for (dynp = dyndata; dynp->d_tag != DT_NULL; dynp++) { - if (dynp->d_tag != DT_FLAGS_1) - continue; - df1pie = (dynp->d_un.d_val & DF_1_PIE) != 0; - break; - } - free(dyndata); - - switch (hdr.elf.e_ident[EI_OSABI]) { - case ELFOSABI_FREEBSD: - if (!df1pie) - *is_shlib = 1; - return (1); -#ifdef __ARM_EABI__ - case ELFOSABI_NONE: - if (hdr.elf.e_machine != EM_ARM) - break; - if (EF_ARM_EABI_VERSION(hdr.elf.e_flags) < - EF_ARM_EABI_FREEBSD_MIN) - break; - *is_shlib = 1; - return (1); -#endif - } + if (ehdr.e_type == ET_DYN && !pie) { + *is_shlib = 1; + + if (!freebsd) { + elf_end(elf); warnx("%s: not a FreeBSD ELF shared object", fname); return (0); } - - return (1); } - warnx("%s: not a dynamic executable", fname); - return (0); + elf_end(elf); + return (1); }