Changeset View
Standalone View
usr.bin/ldd/ldd.c
Show All 27 Lines | |||||||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||||||||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||||||||
*/ | */ | ||||||||||
#include <sys/cdefs.h> | #include <sys/cdefs.h> | ||||||||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||||||||
#include <sys/param.h> | |||||||||||
#include <sys/wait.h> | #include <sys/wait.h> | ||||||||||
#include <machine/elf.h> | #include <machine/elf.h> | ||||||||||
#include <arpa/inet.h> | #include <arpa/inet.h> | ||||||||||
#include <dlfcn.h> | #include <dlfcn.h> | ||||||||||
#include <err.h> | #include <err.h> | ||||||||||
#include <errno.h> | #include <errno.h> | ||||||||||
#include <fcntl.h> | #include <fcntl.h> | ||||||||||
#include <gelf.h> | |||||||||||
#include <libelf.h> | |||||||||||
#include <stdbool.h> | |||||||||||
#include <stdio.h> | #include <stdio.h> | ||||||||||
#include <stdlib.h> | #include <stdlib.h> | ||||||||||
#include <string.h> | #include <string.h> | ||||||||||
#include <unistd.h> | #include <unistd.h> | ||||||||||
/* | /* | ||||||||||
* 32-bit ELF data structures can only be used if the system header[s] declare | * 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, | * them. There is no official macro for determining whether they are declared, | ||||||||||
▲ Show 20 Lines • Show All 200 Lines • ▼ Show 20 Lines | |||||||||||
static void | static void | ||||||||||
usage(void) | usage(void) | ||||||||||
{ | { | ||||||||||
fprintf(stderr, "usage: ldd [-a] [-v] [-f format] program ...\n"); | fprintf(stderr, "usage: ldd [-a] [-v] [-f format] program ...\n"); | ||||||||||
exit(1); | exit(1); | ||||||||||
} | } | ||||||||||
static int | static bool | ||||||||||
is_executable(const char *fname, int fd, int *is_shlib, int *type) | has_freebsd_abi_tag(const char *fname, Elf *elf, GElf_Ehdr *ehdr, off_t offset, | ||||||||||
size_t len) | |||||||||||
{ | { | ||||||||||
union { | Elf_Data dst, src; | ||||||||||
#if __ELF_WORD_SIZE > 32 && defined(ELF32_SUPPORTED) | const Elf_Note *note; | ||||||||||
Elf32_Ehdr elf32; | char *buf; | ||||||||||
#endif | const char *name; | ||||||||||
Elf_Ehdr elf; | void *copy; | ||||||||||
} hdr; | size_t namesz, descsz; | ||||||||||
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; | buf = elf_rawfile(elf, NULL); | ||||||||||
*type = TYPE_UNKNOWN; | if (buf == NULL) { | ||||||||||
df1pie = 0; | 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); | |||||||||||
} | } | ||||||||||
kib: Isn't this section iteration, as opposed to segments? I.e. the new code seems to iterate over… | |||||||||||
Done Inline Actionslibelf's APIs only let you (gracefully) get data from sections. However, I could use gelf_xlatetom() on data manually read from the binary and still only iterate over a segment. I would probably do the same for notes in that case. That said, do we really expect to have FreeBSD ELF files that only have program headers and no section headers? jhb: libelf's APIs only let you (gracefully) get data from sections. However, I could use… | |||||||||||
Done Inline Actionsls30 KBDownload kib: {F15921059} | |||||||||||
Done Inline ActionsThe updated version works on this file fine (though being an executable with EI_OSABI set, it probably also worked with the first version. We only need to check PT_NOTE if EI_OSABI isn't set (and even then we only care if it is a shared library), and we only need to check PT_DYNAMIC for PIE binaries (PDEs will still work even if the PT_DYNAMIC bits were broken). jhb: The updated version works on this file fine (though being an executable with EI_OSABI set, it… | |||||||||||
#if __ELF_WORD_SIZE > 32 && defined(ELF32_SUPPORTED) | memset(&src, 0, sizeof(src)); | ||||||||||
if ((size_t)n >= sizeof(hdr.elf32) && IS_ELF(hdr.elf32) && | src.d_buf = buf + offset; | ||||||||||
hdr.elf32.e_ident[EI_CLASS] == ELFCLASS32) { | src.d_size = len; | ||||||||||
/* Handle 32 bit ELF objects */ | src.d_type = ELF_T_NOTE; | ||||||||||
src.d_version = EV_CURRENT; | |||||||||||
dynamic = 0; | memset(&dst, 0, sizeof(dst)); | ||||||||||
*type = TYPE_ELF32; | dst.d_buf = copy = malloc(len); | ||||||||||
dst.d_size = len; | |||||||||||
dst.d_type = ELF_T_NOTE; | |||||||||||
dst.d_version = EV_CURRENT; | |||||||||||
if (lseek(fd, hdr.elf32.e_phoff, SEEK_SET) == -1) { | if (gelf_xlatetom(elf, &dst, &src, ehdr->e_ident[EI_DATA]) == NULL) { | ||||||||||
warnx("%s: header too short", fname); | warnx("%s: failed to parse notes: %s", fname, elf_errmsg(0)); | ||||||||||
return (0); | free(copy); | ||||||||||
return (false); | |||||||||||
} | } | ||||||||||
for (i = 0; i < hdr.elf32.e_phnum; i++) { | |||||||||||
if (read(fd, &phdr32, hdr.elf32.e_phentsize) != | buf = copy; | ||||||||||
sizeof(phdr32)) { | for (;;) { | ||||||||||
warnx("%s: can't read program header", fname); | if (len < sizeof(*note)) | ||||||||||
return (0); | |||||||||||
} | |||||||||||
if (phdr32.p_type == PT_DYNAMIC) { | |||||||||||
dynamic = 1; | |||||||||||
dynphdr32 = phdr32; | |||||||||||
break; | break; | ||||||||||
note = (const void *)buf; | |||||||||||
buf += sizeof(*note); | |||||||||||
len -= sizeof(*note); | |||||||||||
namesz = roundup2(note->n_namesz, 4); | |||||||||||
kibUnsubmitted Not Done Inline ActionsI believe it is more holistic to use sizeof(Elf32_Addr) instead of 4. kib: I believe it is more holistic to use sizeof(Elf32_Addr) instead of 4. | |||||||||||
jhbAuthorUnsubmitted Done Inline ActionsThe only thing is that notes are aligned on a 4 byte boundary always and not due to holding an address. Someone might think while reading it that it should be Elf64_Addr for 64-bit. libelf's source uses 'ROUNDUP2(namesz, 4U)` (contrib/elftoolchain/libelf/libelf_convert.m4). I think the actual "true" source of the alignment might be something like: namesz = roundup2(note->n_namesz, _Alignof(*note)); But that seems verbose. Perhaps sizeof(uint32_t) would be ok as a Elf_Note is just 3 of those. Hmm, in imgact_elf.c we have a private #define ELF_NOTE_ROUNDSIZE 4 that is used in parse_notes(). jhb: The only thing is that notes are aligned on a 4 byte boundary always and not due to holding an… | |||||||||||
arichardsonUnsubmitted Not Done Inline ActionsMaybe add a comment that elf notes must be multiples of 4 bytes according to the spec? That would stop people from changing it to sizeof(Elf_Addr). arichardson: Maybe add a comment that elf notes must be multiples of 4 bytes according to the spec? That… | |||||||||||
jhbAuthorUnsubmitted Done Inline ActionsI went with sizeof(uint32_t). Better than a bare 4 and matches what we use in the definition of Elf_Note. jhb: I went with `sizeof(uint32_t)`. Better than a bare 4 and matches what we use in the definition… | |||||||||||
descsz = roundup2(note->n_descsz, 4); | |||||||||||
if (len < namesz + descsz) | |||||||||||
break; | |||||||||||
name = buf; | |||||||||||
Done Inline ActionsMaybe I don't need the uintptr_t cast here as both are the source and destination types are const jhb: Maybe I don't need the `uintptr_t` cast here as both are the source and destination types are… | |||||||||||
Done Inline ActionsBah, it's due to a warning about the alignment changing. I might still replace it with a const void * cast to make it a bit shorter. jhb: Bah, it's due to a warning about the alignment changing. I might still replace it with a… | |||||||||||
if (strncmp(name, "FreeBSD", namesz) == 0 && | |||||||||||
kibUnsubmitted Done Inline ActionsPerhaps you need to compare namesz before doing strncmp(). I also checked that descsz == sizeof(int32_t). kib: Perhaps you need to compare namesz before doing strncmp(). I also checked that descsz ==… | |||||||||||
note->n_type == NT_FREEBSD_ABI_TAG) { | |||||||||||
free(copy); | |||||||||||
Not Done Inline ActionsCould you please add static const char[] array for "FreeBSD" ? kib: Could you please add static const char[] array for "FreeBSD" ? | |||||||||||
Done Inline ActionsShould this be spelled ELF_NOTE_FREEBSD while we're at it? kevans: Should this be spelled ELF_NOTE_FREEBSD while we're at it? | |||||||||||
Done Inline ActionsELF_NOTE_FREEBSD works. I don't know that we really need a static const char array for it jhb: ELF_NOTE_FREEBSD works. I don't know that we really need a static const char array for it… | |||||||||||
return (true); | |||||||||||
} | } | ||||||||||
buf += namesz + descsz; | |||||||||||
len -= namesz + descsz; | |||||||||||
} | } | ||||||||||
if (!dynamic) { | free(copy); | ||||||||||
warnx("%s: not a dynamic ELF executable", fname); | return (false); | ||||||||||
return (0); | |||||||||||
} | } | ||||||||||
if (hdr.elf32.e_type == ET_DYN) { | static bool | ||||||||||
if (lseek(fd, dynphdr32.p_offset, SEEK_SET) == -1) { | is_pie(const char *fname, Elf *elf, GElf_Ehdr *ehdr, off_t offset, size_t len) | ||||||||||
warnx("%s: dynamic segment out of range", | { | ||||||||||
fname); | Elf_Data dst, src; | ||||||||||
return (0); | char *buf; | ||||||||||
void *copy; | |||||||||||
const GElf_Dyn *dyn; | |||||||||||
size_t dynsize; | |||||||||||
u_int count, i; | |||||||||||
buf = elf_rawfile(elf, NULL); | |||||||||||
if (buf == NULL) { | |||||||||||
warnx("%s: %s", fname, elf_errmsg(0)); | |||||||||||
return (false); | |||||||||||
} | } | ||||||||||
dyndata = malloc(dynphdr32.p_filesz); | |||||||||||
if (dyndata == NULL) { | dynsize = gelf_fsize(elf, ELF_T_DYN, 1, EV_CURRENT); | ||||||||||
warn("malloc"); | if (dynsize == 0) { | ||||||||||
return (0); | warnx("%s: %s", fname, elf_errmsg(0)); | ||||||||||
return (false); | |||||||||||
} | } | ||||||||||
if (read(fd, dyndata, dynphdr32.p_filesz) != | count = len / dynsize; | ||||||||||
(ssize_t)dynphdr32.p_filesz) { | |||||||||||
free(dyndata); | memset(&src, 0, sizeof(src)); | ||||||||||
warnx("%s: can't read dynamic segment", fname); | src.d_buf = buf + offset; | ||||||||||
return (0); | 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); | |||||||||||
} | } | ||||||||||
for (dynp32 = dyndata; dynp32->d_tag != DT_NULL; | |||||||||||
dynp32++) { | dyn = copy; | ||||||||||
if (dynp32->d_tag != DT_FLAGS_1) | for (i = 0; i < count; i++) { | ||||||||||
if (dyn[i].d_tag != DT_FLAGS_1) | |||||||||||
continue; | continue; | ||||||||||
df1pie = (dynp32->d_un.d_val & DF_1_PIE) != 0; | |||||||||||
break; | |||||||||||
} | |||||||||||
free(dyndata); | |||||||||||
if (hdr.elf32.e_ident[EI_OSABI] == ELFOSABI_FREEBSD) { | if ((dyn[i].d_un.d_val & DF_1_PIE) != 0) { | ||||||||||
if (!df1pie) | free(copy); | ||||||||||
*is_shlib = 1; | return (true); | ||||||||||
return (1); | } else { | ||||||||||
free(copy); | |||||||||||
return (false); | |||||||||||
} | } | ||||||||||
warnx("%s: not a FreeBSD ELF shared object", fname); | |||||||||||
return (0); | |||||||||||
} | } | ||||||||||
return (1); | free(copy); | ||||||||||
kibUnsubmitted Done Inline ActionsIMO it is more clean to save the result of DF_1_PIE check into local variable and only leave one free() call instead of three. kib: IMO it is more clean to save the result of DF_1_PIE check into local variable and only leave… | |||||||||||
return (false); | |||||||||||
} | } | ||||||||||
#endif | |||||||||||
if ((size_t)n >= sizeof(hdr.elf) && IS_ELF(hdr.elf) && | static int | ||||||||||
Not Done Inline Actions
The other two functions also return bool. arichardson: The other two functions also return bool. | |||||||||||
Done Inline ActionsThat would be a bit of a larger change as the caller stores the return value in an int. I prefer bool for new code, and would use bool if refactoring the calling code, but would be hesitant to change the calling code to bool especially when the meat of the changes are in this function and not the caller. If we did change the caller to use bool in the future I would also change is_shlib to be bool instead of int at the same time. jhb: That would be a bit of a larger change as the caller stores the return value in an int. I… | |||||||||||
hdr.elf.e_ident[EI_CLASS] == ELF_TARG_CLASS) { | is_executable(const char *fname, int fd, int *is_shlib, int *type) | ||||||||||
/* Handle default ELF objects on this architecture */ | { | ||||||||||
Elf *elf; | |||||||||||
GElf_Ehdr ehdr; | |||||||||||
GElf_Phdr phdr, dynphdr; | |||||||||||
bool dynamic, freebsd; | |||||||||||
int i; | |||||||||||
dynamic = 0; | *is_shlib = 0; | ||||||||||
*type = TYPE_ELF; | *type = TYPE_UNKNOWN; | ||||||||||
dynamic = false; | |||||||||||
freebsd = false; | |||||||||||
if (lseek(fd, hdr.elf.e_phoff, SEEK_SET) == -1) { | if (elf_version(EV_CURRENT) == EV_NONE) { | ||||||||||
warnx("%s: header too short", fname); | warnx("unsupported libelf"); | ||||||||||
return (0); | return (0); | ||||||||||
} | } | ||||||||||
for (i = 0; i < hdr.elf.e_phnum; i++) { | elf = elf_begin(fd, ELF_C_READ, NULL); | ||||||||||
if (read(fd, &phdr, hdr.elf.e_phentsize) | if (elf == NULL) { | ||||||||||
!= sizeof(phdr)) { | warnx("%s: %s", fname, elf_errmsg(0)); | ||||||||||
warnx("%s: can't read program header", fname); | |||||||||||
return (0); | return (0); | ||||||||||
} | } | ||||||||||
if (phdr.p_type == PT_DYNAMIC) { | if (elf_kind(elf) != ELF_K_ELF) { | ||||||||||
dynamic = 1; | elf_end(elf); | ||||||||||
dynphdr = phdr; | |||||||||||
break; | |||||||||||
} | |||||||||||
} | |||||||||||
if (!dynamic) { | |||||||||||
warnx("%s: not a dynamic ELF executable", fname); | warnx("%s: not a dynamic ELF executable", fname); | ||||||||||
return (0); | return (0); | ||||||||||
} | } | ||||||||||
if (gelf_getehdr(elf, &ehdr) == NULL) { | |||||||||||
if (hdr.elf.e_type == ET_DYN) { | warnx("%s: %s", fname, elf_errmsg(0)); | ||||||||||
if (lseek(fd, dynphdr.p_offset, SEEK_SET) == -1) { | elf_end(elf); | ||||||||||
warnx("%s: dynamic segment out of range", | |||||||||||
fname); | |||||||||||
return (0); | return (0); | ||||||||||
} | } | ||||||||||
dyndata = malloc(dynphdr.p_filesz); | |||||||||||
if (dyndata == NULL) { | *type = TYPE_ELF; | ||||||||||
warn("malloc"); | #if __ELF_WORD_SIZE > 32 && defined(ELF32_SUPPORTED) | ||||||||||
return (0); | if (gelf_getclass(elf) == ELFCLASS32) { | ||||||||||
*type = TYPE_ELF32; | |||||||||||
} | } | ||||||||||
if (read(fd, dyndata, dynphdr.p_filesz) != | #endif | ||||||||||
(ssize_t)dynphdr.p_filesz) { | |||||||||||
free(dyndata); | freebsd = ehdr.e_ident[EI_OSABI] == ELFOSABI_FREEBSD; | ||||||||||
warnx("%s: can't read dynamic segment", fname); | 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); | return (0); | ||||||||||
} | } | ||||||||||
for (dynp = dyndata; dynp->d_tag != DT_NULL; dynp++) { | switch (phdr.p_type) { | ||||||||||
if (dynp->d_tag != DT_FLAGS_1) | case PT_NOTE: | ||||||||||
continue; | if (ehdr.e_ident[EI_OSABI] == ELFOSABI_NONE && !freebsd) | ||||||||||
df1pie = (dynp->d_un.d_val & DF_1_PIE) != 0; | freebsd = has_freebsd_abi_tag(fname, elf, &ehdr, | ||||||||||
phdr.p_offset, phdr.p_filesz); | |||||||||||
break; | break; | ||||||||||
case PT_DYNAMIC: | |||||||||||
dynamic = true; | |||||||||||
dynphdr = phdr; | |||||||||||
jhbAuthorUnsubmitted Done Inline ActionsI could perhaps have a bool pie and call is_pie here in the ET_DYN case to save the 'dynphdr' variable. jhb: I could perhaps have a `bool pie` and call `is_pie` here in the ET_DYN case to save the… | |||||||||||
break; | |||||||||||
} | } | ||||||||||
free(dyndata); | } | ||||||||||
switch (hdr.elf.e_ident[EI_OSABI]) { | if (!dynamic) { | ||||||||||
case ELFOSABI_FREEBSD: | elf_end(elf); | ||||||||||
if (!df1pie) | warnx("%s: not a dynamic ELF executable", fname); | ||||||||||
*is_shlib = 1; | return (0); | ||||||||||
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 && !is_pie(fname, elf, &ehdr, | |||||||||||
dynphdr.p_offset, dynphdr.p_filesz)) { | |||||||||||
*is_shlib = 1; | |||||||||||
if (!freebsd) { | |||||||||||
elf_end(elf); | |||||||||||
warnx("%s: not a FreeBSD ELF shared object", fname); | warnx("%s: not a FreeBSD ELF shared object", fname); | ||||||||||
return (0); | return (0); | ||||||||||
} | } | ||||||||||
return (1); | |||||||||||
} | } | ||||||||||
warnx("%s: not a dynamic executable", fname); | elf_end(elf); | ||||||||||
return (0); | return (1); | ||||||||||
} | } |
Isn't this section iteration, as opposed to segments? I.e. the new code seems to iterate over optional sections headers, which, unless I am mistaken, would be a regression against current code. You need to iterate over segments from program headers, find dynamic segment and then find flags_1.