llvm / llvm/llvm-project

[PAC][libunwind] Signing oracles in `findUnwindSectionsByPhdr`

Open
#185,025 1 comment 0 reactions 1 assignee Claimed by @atrosinenko View on GitHub
libunwind
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

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::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::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(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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.