diff options
Diffstat (limited to 'sysdeps/mips/dl-machine-reject-phdr.h')
-rw-r--r-- | sysdeps/mips/dl-machine-reject-phdr.h | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/sysdeps/mips/dl-machine-reject-phdr.h b/sysdeps/mips/dl-machine-reject-phdr.h new file mode 100644 index 0000000000..b277450c4d --- /dev/null +++ b/sysdeps/mips/dl-machine-reject-phdr.h @@ -0,0 +1,326 @@ +/* Machine-dependent program header inspection for the ELF loader. + Copyright (C) 2014 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <http://www.gnu.org/licenses/>. */ + +#ifndef _DL_MACHINE_REJECT_PHDR_H +#define _DL_MACHINE_REJECT_PHDR_H 1 + +#include <unistd.h> +#include <sys/prctl.h> + +#if defined PR_GET_FP_MODE && defined PR_SET_FP_MODE +# define HAVE_PRCTL_FP_MODE 1 +#else +# define HAVE_PRCTL_FP_MODE 0 +#endif + +/* Reject an object with a debug message. */ +#define REJECT(str, args...) \ + { \ + if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS)) \ + _dl_debug_printf (str, ##args); \ + return true; \ + } + +/* Search the program headers for the ABI Flags. */ + +static inline const ElfW(Phdr) * +find_mips_abiflags (const ElfW(Phdr) *phdr, ElfW(Half) phnum) +{ + const ElfW(Phdr) *ph; + + for (ph = phdr; ph < &phdr[phnum]; ++ph) + if (ph->p_type == PT_MIPS_ABIFLAGS) + return ph; + return NULL; +} + +/* Cache the FP ABI value from the PT_MIPS_ABIFLAGS program header. */ + +static bool +cached_fpabi_reject_phdr_p (struct link_map *l) +{ + if (l->l_mach.fpabi == 0) + { + const ElfW(Phdr) *ph = find_mips_abiflags (l->l_phdr, l->l_phnum); + + if (ph) + { + Elf_MIPS_ABIFlags_v0 * mips_abiflags; + if (ph->p_filesz < sizeof (Elf_MIPS_ABIFlags_v0)) + REJECT (" %s: malformed PT_MIPS_ABIFLAGS found\n", l->l_name); + + mips_abiflags = (Elf_MIPS_ABIFlags_v0 *) (l->l_addr + ph->p_vaddr); + + if (__glibc_unlikely (mips_abiflags->flags2 != 0)) + REJECT (" %s: unknown MIPS.abiflags flags2: %u\n", l->l_name, + mips_abiflags->flags2); + + l->l_mach.fpabi = mips_abiflags->fp_abi; + l->l_mach.odd_spreg = (mips_abiflags->flags1 + & MIPS_AFL_FLAGS1_ODDSPREG) != 0; + } + else + { + l->l_mach.fpabi = -1; + l->l_mach.odd_spreg = true; + } + } + return false; +} + +/* Return a description of the specified floating-point ABI. */ + +static const char * +fpabi_string (int fpabi) +{ + switch (fpabi) + { + case Val_GNU_MIPS_ABI_FP_ANY: + return "Hard or soft float"; + case Val_GNU_MIPS_ABI_FP_DOUBLE: + return "Hard float (double precision)"; + case Val_GNU_MIPS_ABI_FP_SINGLE: + return "Hard float (single precision)"; + case Val_GNU_MIPS_ABI_FP_SOFT: + return "Soft float"; + case Val_GNU_MIPS_ABI_FP_OLD_64: + return "Unsupported FP64"; + case Val_GNU_MIPS_ABI_FP_XX: + return "Hard float (32-bit CPU, Any FPU)"; + case Val_GNU_MIPS_ABI_FP_64: + return "Hard float (32-bit CPU, 64-bit FPU)"; + case Val_GNU_MIPS_ABI_FP_64A: + return "Hard float compat (32-bit CPU, 64-bit FPU)"; + case -1: + return "Double precision, single precision or soft float"; + default: + return "Unknown FP ABI"; + } +} + +/* A structure to describe the requirements of each FP ABI extension. + Each field says whether the ABI can be executed in that mode. The FR0 field + is actually overloaded and means 'default' FR mode for the ABI. I.e. For + O32 it is FR0 and for N32/N64 it is actually FR1. Since this logic is + focussed on the intricacies of mode management for O32 we call the field + FR0. */ + +struct abi_req +{ + bool single; + bool soft; + bool fr0; + bool fr1; + bool fre; +}; + +/* FP ABI requirements for all Val_GNU_MIPS_ABI_FP_* values. */ + +static const struct abi_req reqs[Val_GNU_MIPS_ABI_FP_MAX + 1] = + {{true, true, true, true, true}, /* Any */ + {false, false, true, false, true}, /* Double-float */ + {true, false, false, false, false}, /* Single-float */ + {false, true, false, false, false}, /* Soft-float */ + {false, false, false, false, false}, /* old-FP64 */ + {false, false, true, true, true}, /* FPXX */ + {false, false, false, true, false}, /* FP64 */ + {false, false, false, true, true}}; /* FP64A */ + +/* FP ABI requirements for objects without a PT_MIPS_ABIFLAGS segment. */ + +static const struct abi_req none_req = { true, true, true, false, true }; + +/* Return true iff ELF program headers are incompatible with the running + host. This verifies that floating-point ABIs are compatible and + re-configures the hardware mode if necessary. This code handles both the + DT_NEEDED libraries and the dlopen'ed libraries. It also accounts for the + impact of dlclose. */ + +static bool __attribute_used__ +elf_machine_reject_phdr_p (const ElfW(Phdr) *phdr, uint_fast16_t phnum, + const char *buf, size_t len, struct link_map *map, + int fd) +{ + const ElfW(Phdr) *ph = find_mips_abiflags (phdr, phnum); + struct link_map *l; + Lmid_t nsid; + int in_abi = -1; + struct abi_req in_req; + Elf_MIPS_ABIFlags_v0 *mips_abiflags = NULL; + bool perfect_match = false; +#if _MIPS_SIM == _ABIO32 + unsigned int cur_mode = -1; +# if HAVE_PRCTL_FP_MODE + bool cannot_mode_switch = false; + + /* Get the current hardware mode. */ + cur_mode = __prctl (PR_GET_FP_MODE); +# endif +#endif + + /* Read the attributes section. */ + if (ph != NULL) + { + ElfW(Addr) size = ph->p_filesz; + + if (ph->p_offset + size <= len) + mips_abiflags = (Elf_MIPS_ABIFlags_v0 *) (buf + ph->p_offset); + else + { + mips_abiflags = alloca (size); + __lseek (fd, ph->p_offset, SEEK_SET); + if (__libc_read (fd, (void *) mips_abiflags, size) != size) + REJECT (" unable to read PT_MIPS_ABIFLAGS\n"); + } + + if (size < sizeof (Elf_MIPS_ABIFlags_v0)) + REJECT (" contains malformed PT_MIPS_ABIFLAGS\n"); + + if (__glibc_unlikely (mips_abiflags->flags2 != 0)) + REJECT (" unknown MIPS.abiflags flags2: %u\n", mips_abiflags->flags2); + + in_abi = mips_abiflags->fp_abi; + } + + /* ANY is compatible with anything. */ + perfect_match |= (in_abi == Val_GNU_MIPS_ABI_FP_ANY); + + /* Unknown ABIs are rejected. */ + if (in_abi != -1 && in_abi > Val_GNU_MIPS_ABI_FP_MAX) + REJECT (" uses unknown FP ABI: %u\n", in_abi); + + /* Obtain the initial requirements. */ + in_req = (in_abi == -1) ? none_req : reqs[in_abi]; + + /* Check that the new requirement does not conflict with any currently + loaded object. */ + for (nsid = 0; nsid < DL_NNS; ++nsid) + for (l = GL(dl_ns)[nsid]._ns_loaded; l != NULL; l = l->l_next) + { + struct abi_req existing_req; + + if (cached_fpabi_reject_phdr_p (l)) + return true; + +#if _MIPS_SIM == _ABIO32 + /* A special case arises for O32 FP64 and FP64A where the kernel + pre-dates PT_MIPS_ABIFLAGS. These ABIs will be blindly loaded even + if the hardware mode is unavailable or disabled. In this + circumstance the prctl call to obtain the current mode will fail. + Detect this situation here and reject everything. This will + effectively prevent dynamically linked applications from failing in + unusual ways but there is nothing we can do to help static + applications. */ + if ((l->l_mach.fpabi == Val_GNU_MIPS_ABI_FP_64A + || l->l_mach.fpabi == Val_GNU_MIPS_ABI_FP_64) + && cur_mode == -1) + REJECT (" found %s running in the wrong mode\n", + fpabi_string (l->l_mach.fpabi)); +#endif + + /* Found a perfect match, success. */ + perfect_match |= (in_abi == l->l_mach.fpabi); + + /* Unknown ABIs are rejected. */ + if (l->l_mach.fpabi != -1 && l->l_mach.fpabi > Val_GNU_MIPS_ABI_FP_MAX) + REJECT (" found unknown FP ABI: %u\n", l->l_mach.fpabi); + + existing_req = (l->l_mach.fpabi == -1 ? none_req + : reqs[l->l_mach.fpabi]); + + /* Merge requirements. */ + in_req.soft &= existing_req.soft; + in_req.single &= existing_req.single; + in_req.fr0 &= existing_req.fr0; + in_req.fr1 &= existing_req.fr1; + in_req.fre &= existing_req.fre; + + /* If there is at least one mode which is still usable then the new + object can be loaded. */ + if (in_req.single || in_req.soft || in_req.fr1 || in_req.fr0 + || in_req.fre) + { +#if _MIPS_SIM == _ABIO32 && HAVE_PRCTL_FP_MODE + /* Account for loaded ABIs which prohibit mode switching. */ + if (l->l_mach.fpabi == Val_GNU_MIPS_ABI_FP_XX) + cannot_mode_switch |= l->l_mach.odd_spreg; +#endif + } + else + REJECT (" uses %s, already loaded %s\n", + fpabi_string (in_abi), + fpabi_string (l->l_mach.fpabi)); + } + +#if _MIPS_SIM == _ABIO32 + /* At this point we know that the newly loaded object is compatible with all + existing objects but the hardware mode may not be correct. */ + if ((in_req.fr1 || in_req.fre || in_req.fr0) + && !perfect_match) + { + if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_LIBS)) + _dl_debug_printf (" needs %s%s mode\n", in_req.fr0 ? "FR0 or " : "", + (in_req.fre && !in_req.fr1) ? "FRE" : "FR1"); + + /* If the PR_GET_FP_MODE is not supported then only FR0 is available. + If the overall requirements cannot be met by FR0 then reject the + object. */ + if (cur_mode == -1) + return !in_req.fr0; + +# if HAVE_PRCTL_FP_MODE + { + unsigned int fr1_mode = PR_FP_MODE_FR; + + /* It is not possible to change the mode of a thread which may be + executing FPXX code with odd-singles. If an FPXX object with + odd-singles is loaded then just check the current mode is OK. This + can be either the FR1 mode or FR0 if the requirements are met by + FR0. */ + if (cannot_mode_switch) + return (!(in_req.fre && cur_mode == (PR_FP_MODE_FR | PR_FP_MODE_FRE)) + && !(in_req.fr1 && cur_mode == PR_FP_MODE_FR) + && !(in_req.fr0 && cur_mode == 0)); + + /* If the overall requirements can be satisfied by FRE but not FR1 then + fr1_mode must become FRE. */ + if (in_req.fre && !in_req.fr1) + fr1_mode |= PR_FP_MODE_FRE; + + /* Set the new mode. Use fr1_mode if the requirements cannot be met by + FR0. */ + if (!in_req.fr0) + return __prctl (PR_SET_FP_MODE, fr1_mode) != 0; + else if (__prctl (PR_SET_FP_MODE, /* fr0_mode */ 0) != 0) + { + /* Setting FR0 can validly fail on an R6 core so retry with the FR1 + mode as a fall back. */ + if (errno != ENOTSUP) + return true; + + return __prctl (PR_SET_FP_MODE, fr1_mode) != 0; + } + } +# endif /* HAVE_PRCTL_FP_MODE */ + } +#endif /* _MIPS_SIM == _ABIO32 */ + + return false; +} + +#endif /* dl-machine-reject-phdr.h */ |