diff options
| author | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2025-03-14 16:09:57 -0300 |
|---|---|---|
| committer | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2025-03-21 15:46:48 -0300 |
| commit | ed6a68bac7cd056abda9008019c71b167f0362dc (patch) | |
| tree | 7ceb7f6403f423e4773724c518e537e13f140a3d /elf | |
| parent | 1894e219dc530d7074085e95ffe3c1e66cebc072 (diff) | |
| download | glibc-ed6a68bac7cd056abda9008019c71b167f0362dc.tar.xz glibc-ed6a68bac7cd056abda9008019c71b167f0362dc.zip | |
debug: Improve '%n' fortify detection (BZ 30932)
The 7bb8045ec0 path made the '%n' fortify check ignore EMFILE errors
while trying to open /proc/self/maps, and this added a security
issue where EMFILE can be attacker-controlled thus making it
ineffective for some cases.
The EMFILE failure is reinstated but with a different error
message. Also, to improve the false positive of the hardening for
the cases where no new files can be opened, the
_dl_readonly_area now uses _dl_find_object to check if the
memory area is within a writable ELF segment. The procfs method is
still used as fallback.
Checked on x86_64-linux-gnu and i686-linux-gnu.
Reviewed-by: Arjun Shankar <arjun@redhat.com>
Diffstat (limited to 'elf')
| -rw-r--r-- | elf/Makefile | 1 | ||||
| -rw-r--r-- | elf/dl-readonly-area.c | 86 | ||||
| -rw-r--r-- | elf/rtld.c | 1 |
3 files changed, 88 insertions, 0 deletions
diff --git a/elf/Makefile b/elf/Makefile index 3d60000ec9..5566d39dac 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -72,6 +72,7 @@ dl-routines = \ dl-open \ dl-origin \ dl-printf \ + dl-readonly-area \ dl-reloc \ dl-runtime \ dl-scope \ diff --git a/elf/dl-readonly-area.c b/elf/dl-readonly-area.c new file mode 100644 index 0000000000..22769ec9d9 --- /dev/null +++ b/elf/dl-readonly-area.c @@ -0,0 +1,86 @@ +/* Check if range is within a read-only from a loaded ELF object. + Copyright (C) 2025 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 + <https://www.gnu.org/licenses/>. */ + +#include <ldsodefs.h> + +static bool +check_relro (const struct link_map *l, uintptr_t start, uintptr_t end) +{ + if (l->l_relro_addr != 0) + { + uintptr_t relro_start = ALIGN_DOWN (l->l_addr + l->l_relro_addr, + GLRO(dl_pagesize)); + uintptr_t relro_end = ALIGN_DOWN (l->l_addr + l->l_relro_addr + + l->l_relro_size, + GLRO(dl_pagesize)); + /* RELRO is caved out from a RW segment, so the next range is either + RW or nonexistent. */ + return relro_start <= start && end <= relro_end + ? dl_readonly_area_rdonly : dl_readonly_area_writable; + + } + return dl_readonly_area_writable; +} + +enum dl_readonly_area_error_type +_dl_readonly_area (const void *ptr, size_t size) +{ + struct dl_find_object dlfo; + if (_dl_find_object ((void *)ptr, &dlfo) != 0) + return dl_readonly_area_not_found; + + const struct link_map *l = dlfo.dlfo_link_map; + uintptr_t ptr_start = (uintptr_t) ptr; + uintptr_t ptr_end = ptr_start + size; + + for (const ElfW(Phdr) *ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph) + if (ph->p_type == PT_LOAD) + { + /* For segments with alignment larger than the page size, + _dl_map_segment allocates additional space that is mark as + PROT_NONE (so we can ignore). */ + uintptr_t from = l->l_addr + + ALIGN_DOWN (ph->p_vaddr, GLRO(dl_pagesize)); + uintptr_t to = l->l_addr + + ALIGN_UP (ph->p_vaddr + ph->p_filesz, GLRO(dl_pagesize)); + + /* Found an entry that at least partially covers the area. */ + if (from < ptr_end && to > ptr_start) + { + if (ph->p_flags & PF_W) + return check_relro (l, ptr_start, ptr_end); + + if ((ph->p_flags & PF_R) == 0) + return dl_readonly_area_writable; + + if (from <= ptr_start && to >= ptr_end) + return dl_readonly_area_rdonly; + else if (from <= ptr_start) + size -= to - ptr_start; + else if (to >= ptr_end) + size -= ptr_end - from; + else + size -= to - from; + + if (size == 0) + break; + } + } + + return size == 0 ? dl_readonly_area_rdonly : dl_readonly_area_not_found; +} diff --git a/elf/rtld.c b/elf/rtld.c index 00b25c1a73..099c447e80 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -371,6 +371,7 @@ struct rtld_global_ro _rtld_global_ro attribute_relro = ._dl_error_free = _dl_error_free, ._dl_tls_get_addr_soft = _dl_tls_get_addr_soft, ._dl_libc_freeres = __rtld_libc_freeres, + ._dl_readonly_area = _dl_readonly_area, }; /* If we would use strong_alias here the compiler would see a non-hidden definition. This would undo the effect of the previous |
