aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2026-07-27 15:42:49 +0000
committerMark Johnston <markj@FreeBSD.org>2026-07-28 17:34:59 +0000
commit475a72f754781d781ef3b48a4faf3c350a2e1cca (patch)
tree9422022e5e74288cf988ea1e815371f6256cbf4b
parent20c738692c6117edb5496aab8147c519539676a7 (diff)
coredump: Don't assume that the number of ELF segments is consistent
In an ELF coredump, each dumped vm_map_entry is represented by a segment. __elfN(coredump) first computes the number of segments by looping over the vm_map entries (in each_dumpable_segment()), then allocates a buffer to hold the ELF header and program headers, then loops over the entries again to populate the program headers. each_dumpable_segment() holds the vm_map read lock, but that lock is dropped between the two calls. If the map is shared with another process, via rfork(), then the map can change. cb_put_phdr() did not account for this, and so could write out of bounds. Add a check to prevent this; simply do not write out excess segments. Approved by: so Security: FreeBSD-SA-26:55.elf Security: CVE-2026-58088 Reported by: Maik Muench of Secfault Security Reviewed by: kib, emaste Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D58416
-rw-r--r--sys/kern/imgact_elf.c22
1 files changed, 19 insertions, 3 deletions
diff --git a/sys/kern/imgact_elf.c b/sys/kern/imgact_elf.c
index ca5066c634f8..c43993ef0667 100644
--- a/sys/kern/imgact_elf.c
+++ b/sys/kern/imgact_elf.c
@@ -1555,6 +1555,8 @@ typedef void (*segment_callback)(vm_map_entry_t, void *);
struct phdr_closure {
Elf_Phdr *phdr; /* Program header to fill in */
Elf_Off offset; /* Offset of segment in core file */
+ int numsegs; /* Maximum number of segments */
+ int nextseg; /* Next segment to fill in */
};
struct note_info {
@@ -1671,10 +1673,15 @@ __elfN(coredump)(struct thread *td, struct coredump_writer *cdw, off_t limit, in
}
/*
- * Allocate memory for building the header, fill it up,
- * and write it out following the notes.
+ * Allocate memory for building the header, fill it up, and write it out
+ * following the notes.
+ *
+ * Note that a process sharing our vmspace might be concurrently
+ * mutating the map, in which case we could populate fewer than
+ * seginfo.count headers. Zero the buffer to ensure that unpopulated
+ * headers are still initialized.
*/
- hdr = malloc(hdrsize, M_TEMP, M_WAITOK);
+ hdr = malloc(hdrsize, M_TEMP, M_WAITOK | M_ZERO);
error = __elfN(corehdr)(&params, seginfo.count, hdr, hdrsize, &notelst,
notesz, flags);
@@ -1727,6 +1734,11 @@ cb_put_phdr(vm_map_entry_t entry, void *closure)
struct phdr_closure *phc = (struct phdr_closure *)closure;
Elf_Phdr *phdr = phc->phdr;
+ if (phc->nextseg >= phc->numsegs) {
+ /* Only write as many headers as we have space for. */
+ return;
+ }
+
phc->offset = round_page(phc->offset);
phdr->p_type = PT_LOAD;
@@ -1739,6 +1751,8 @@ cb_put_phdr(vm_map_entry_t entry, void *closure)
phc->offset += phdr->p_filesz;
phc->phdr++;
+
+ phc->nextseg++;
}
/*
@@ -2001,6 +2015,8 @@ __elfN(puthdr)(struct thread *td, void *hdr, size_t hdrsize, int numsegs,
/* All the writable segments from the program. */
phc.phdr = phdr;
phc.offset = round_page(hdrsize + notesz);
+ phc.numsegs = numsegs;
+ phc.nextseg = 0;
each_dumpable_segment(td, cb_put_phdr, &phc, flags);
}