Issue 185025
Summary [PAC][libunwind] Signing oracles in `findUnwindSectionsByPhdr`
Labels libunwind
Assignees atrosinenko
Reporter atrosinenko
    Two signing oracles are reported by PAuth gadget scanner of `llvm-bolt-binary-analysis` (see https://github.com/llvm/llvm-project/issues/165244#issuecomment-4007695426 for the details) for `findUnwindSectionsByPhdr` function from [`AddressSpace.hpp`](https://github.com/llvm/llvm-project/blob/24ac5987b482edb33b73f0ebff509e0a520eca1c/libunwind/src/AddressSpace.hpp#L456). The reports actually belong to two other `static` functions called by `findUnwindSectionsByPhdr`, here are the sources (lines that are always removed by the preprocessor when `_LIBUNWIND_SUPPORT_DWARF_INDEX` is defined are omitted for clarity):

```cpp
static bool checkAddrInSegment(const Elf_Phdr *phdr, size_t image_base,
 dl_iterate_cb_data *cbdata) {
  if (phdr->p_type == PT_LOAD) {
 uintptr_t begin = image_base + phdr->p_vaddr;
    uintptr_t end = begin + phdr->p_memsz;
    if (cbdata->targetAddr >= begin && cbdata->targetAddr < end) {
      cbdata->sects->dso_base = begin;                      // <-- signing oracle
      cbdata->sects->text_segment_length = phdr->p_memsz;
 return true;
    }
  }
  return false;
}

static bool checkForUnwindInfoSegment(const Elf_Phdr *phdr, size_t image_base,
 dl_iterate_cb_data *cbdata) {
  if (phdr->p_type == PT_GNU_EH_FRAME) {
    EHHeaderParser<LocalAddressSpace>::EHHeaderInfo hdrInfo;
    uintptr_t eh_frame_hdr_start = image_base + phdr->p_vaddr;
 cbdata->sects->dwarf_index_section = eh_frame_hdr_start;
 cbdata->sects->dwarf_index_section_length = phdr->p_memsz;
    if (EHHeaderParser<LocalAddressSpace>::decodeEHHdr(
 *cbdata->addressSpace, eh_frame_hdr_start,
            eh_frame_hdr_start + phdr->p_memsz, hdrInfo)) {
      // .eh_frame_hdr records the start of .eh_frame, but not its size.
      // Rely on a zero terminator to find the end of the section.
      cbdata->sects->dwarf_section = hdrInfo.eh_frame_ptr;  // <-- signing oracle
 cbdata->sects->dwarf_section_length = SIZE_MAX;
      return true;
    }
 }
  return false;
}

static int findUnwindSectionsByPhdr(struct dl_phdr_info *pinfo,
                                    size_t pinfo_size, void *data) {
  auto cbdata = static_cast<dl_iterate_cb_data *>(data);
  if (pinfo->dlpi_phnum == 0 || cbdata->targetAddr < pinfo->dlpi_addr)
    return 0;
#if defined(_LIBUNWIND_USE_FRAME_HEADER_CACHE)
  if (TheFrameHeaderCache.find(pinfo, pinfo_size, data))
    return 1;
#else
 // Avoid warning about unused variable.
  (void)pinfo_size;
#endif

 Elf_Addr image_base = pinfo->dlpi_addr;

  // Most shared objects seen in this callback function likely don't contain the
  // target address, so optimize for that. Scan for a matching PT_LOAD segment
  // first and bail when it isn't found.
  bool found_text = false;
  for (Elf_Half i = 0; i < pinfo->dlpi_phnum; ++i) {
    if (checkAddrInSegment(&pinfo->dlpi_phdr[i], image_base, cbdata)) {
      found_text = true;
      break;
    }
  }
 if (!found_text)
    return 0;

  // PT_GNU_EH_FRAME and PT_ARM_EXIDX are usually near the end. Iterate
  // backward.
  bool found_unwind = false;
 for (Elf_Half i = pinfo->dlpi_phnum; i > 0; i--) {
    const Elf_Phdr *phdr = &pinfo->dlpi_phdr[i - 1];
    if (checkForUnwindInfoSegment(phdr, image_base, cbdata)) {
      found_unwind = true;
      break;
    }
 }
  if (!found_unwind)
    return 0;

#if defined(_LIBUNWIND_USE_FRAME_HEADER_CACHE)
 TheFrameHeaderCache.add(cbdata->sects);
#endif
  return 1;
}
```

[`dso_base`](https://github.com/llvm/llvm-project/blob/24ac5987b482edb33b73f0ebff509e0a520eca1c/libunwind/src/AddressSpace.hpp#L132) and [`dwarf_section`](https://github.com/llvm/llvm-project/blob/24ac5987b482edb33b73f0ebff509e0a520eca1c/libunwind/src/AddressSpace.hpp#L140) are the fields of `struct UnwindInfoSections` defined in the same file.

The first oracle corresponds to assigning an initially untrusted value (sum of `dl_phdr_info::dlpi_addr` and `Elf64_Phdr::p_vaddr` fields of structures returned by the dynamic linker).

The second oracle corresponds to copying from an unprotected `EHHeaderInfo::eh_frame_ptr` to `__ptrauth`-qualified `dwarf_section` field. Here, it should be possible to strictly improve the protection by providing a strong signing schema for `EHHeaderInfo::eh_frame_ptr` which is only assigned once, in [`EHHeaderParser::decodeEHHdr`](https://github.com/llvm/llvm-project/blob/24ac5987b482edb33b73f0ebff509e0a520eca1c/libunwind/src/EHHeaderParser.hpp#L83). There still would be a signing oracle in the latter function, thus such change merely extends the time span when this variable is protected.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to