summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Bugaev <bugaevc@gmail.com>2023-06-26 02:11:37 +0300
committerSamuel Thibault <samuel.thibault@ens-lyon.org>2023-07-03 13:52:11 +0200
commit7abafc25f97897fd61762c45f8729fa952aa822c (patch)
treea7c802acf7a649c51e109ab64f83e54c7362478b
parentc3b3053b9e8ac3c9f70cbe12f884ab8ca69dd991 (diff)
exec: Properly preallocate address space
The existing code mapped the first PT_LOAD segment using anywhere=1, letting Mach pick an arbitrary location, and then uses anywhere=0 for all the remaining segments to place them next to the first one, as expected by the program. This, however, runs into many issues, primarily because of conflicts between interpreter and executable mappings. Some of those the existing code tried to work around. Instead, do it the way it's implemented in glibc: calculate upfront how much space the overall mapping will need, and ask Mach to preallocate this much address space. Then we deallocate it back, and instead map the program segments onto this very same place. Message-Id: <20230625231137.403096-4-bugaevc@gmail.com>
-rw-r--r--exec/exec.c168
1 files changed, 67 insertions, 101 deletions
diff --git a/exec/exec.c b/exec/exec.c
index 4415fa50..24d71220 100644
--- a/exec/exec.c
+++ b/exec/exec.c
@@ -48,21 +48,19 @@ pthread_rwlock_t std_lock = PTHREAD_RWLOCK_INITIALIZER;
#include <hurd/sigpreempt.h>
-/* Load or allocate a section.
- Returns the address of the end of the section. */
-static vm_address_t
-load_section (void *section, struct execdata *u, int interp)
+/* Load or allocate a section. */
+static void
+load_section (void *section, struct execdata *u)
{
- vm_address_t addr = 0, end = 0;
+ vm_address_t addr = 0;
vm_offset_t filepos = 0;
vm_size_t filesz = 0, memsz = 0;
vm_prot_t vm_prot;
- int anywhere;
vm_address_t mask = 0;
const ElfW(Phdr) *const ph = section;
if (u->error)
- return 0;
+ return;
vm_prot = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE;
@@ -76,28 +74,15 @@ load_section (void *section, struct execdata *u, int interp)
vm_prot &= ~VM_PROT_WRITE;
if ((ph->p_flags & PF_X) == 0)
vm_prot &= ~VM_PROT_EXECUTE;
- anywhere = u->info.elf.anywhere;
- if (! anywhere)
- addr += u->info.elf.loadbase;
- else if (interp)
- {
-#ifdef __i386__
- /* On the i386, programs normally load at 0x08000000, and
- expect their data segment to be able to grow dynamically
- upward from its start near that address. We need to make
- sure that the dynamic linker is not mapped in a conflicting
- address. */
- mask = 0xf8000000UL; /* XXX */
-#endif
- }
- if (anywhere && addr < vm_page_size)
- addr = vm_page_size;
- end = addr + memsz;
+ /* The mapping should have been resolved to a specific address
+ by this point. */
+ assert_backtrace (!u->info.elf.anywhere);
+ addr += u->info.elf.loadbase;
if (memsz == 0)
/* This section is empty; ignore it. */
- return 0;
+ return;
if (filesz != 0)
{
@@ -110,7 +95,7 @@ load_section (void *section, struct execdata *u, int interp)
vm_size_t off = size % vm_page_size;
/* Allocate with vm_map to set max protections. */
u->error = vm_map (u->task,
- mapstart, size, mask, anywhere,
+ mapstart, size, mask, 0,
MACH_PORT_NULL, 0, 1,
vm_prot|VM_PROT_WRITE,
VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE,
@@ -157,7 +142,7 @@ load_section (void *section, struct execdata *u, int interp)
/* Map the data into the task directly from the file. */
u->error = vm_map (u->task,
&mapstart, filesz - (mapstart - addr),
- mask, anywhere,
+ mask, 0,
u->filemap, filepos + (mapstart - addr), 1,
vm_prot,
VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE,
@@ -172,18 +157,7 @@ load_section (void *section, struct execdata *u, int interp)
write_to_task (&mapstart, size, vm_prot, (vm_address_t) buf);
}
if (u->error)
- return 0;
-
- if (anywhere)
- {
- /* We let the kernel choose the location of the mapping.
- Now record where it ended up. Later sections cannot
- be mapped anywhere, they must come after this one. */
- u->info.elf.loadbase = mapstart;
- addr = mapstart + (addr % vm_page_size);
- anywhere = u->info.elf.anywhere = 0;
- mask = 0;
- }
+ return;
}
/* If this segment is executable, adjust start_code and end_code
@@ -229,7 +203,7 @@ load_section (void *section, struct execdata *u, int interp)
{
maplose:
vm_deallocate (u->task, mapstart, filesz);
- return 0;
+ return;
}
}
@@ -290,21 +264,10 @@ load_section (void *section, struct execdata *u, int interp)
/* MAPSTART is the first page that starts inside the section.
Allocate all the pages that start inside the section. */
u->error = vm_map (u->task, &mapstart, memsz - (mapstart - addr),
- mask, anywhere, MACH_PORT_NULL, 0, 1,
+ mask, 0, MACH_PORT_NULL, 0, 1,
vm_prot, VM_PROT_ALL, VM_INHERIT_COPY);
if (u->error)
- return 0;
- }
-
- if (anywhere)
- {
- /* We let the kernel choose the location of the zero space.
- Now record where it ended up. Later sections cannot
- be mapped anywhere, they must come after this one. */
- u->info.elf.loadbase = mapstart;
- addr = mapstart + (addr % vm_page_size);
- anywhere = u->info.elf.anywhere = 0;
- mask = 0;
+ return;
}
if (mapstart > addr)
@@ -318,7 +281,7 @@ load_section (void *section, struct execdata *u, int interp)
if (u->error)
{
vm_deallocate (u->task, mapstart, memsz);
- return 0;
+ return;
}
u->error = hurd_safe_memset (
(void *) (ourpage + (addr - overlap_page)),
@@ -334,7 +297,7 @@ load_section (void *section, struct execdata *u, int interp)
munmap ((caddr_t) ourpage, size);
}
}
- return end;
+ return;
}
/* XXX all accesses of the mapped data need to use fault handling
@@ -730,48 +693,60 @@ set_name (task_t task, const char *exec_name, pid_t pid)
free (name);
}
-/* Load the file. Returns the address of the end of the load. */
-static vm_offset_t
-load (task_t usertask, struct execdata *e, vm_offset_t anywhere_start, int interp)
+/* Load the file. */
+static void
+load (task_t usertask, struct execdata *e)
{
- int anywhere = e->info.elf.anywhere;
- vm_offset_t end;
+ ElfW(Word) i;
+
e->task = usertask;
- if (! e->error)
- {
- ElfW(Word) i;
+ if (e->error)
+ goto out;
- if (anywhere && anywhere_start)
- {
- /* Make sure this anywhere-load will go at the end of the previous
- anywhere-load. */
- /* TODO: Rather compute how much contiguous room is needed, allocate
- the area from the kernel, and then map memory sections. */
- /* TODO: Possibly implement Adresse Space Layout Randomization. */
- e->info.elf.loadbase = anywhere_start;
- e->info.elf.anywhere = 0;
- }
+ if (e->info.elf.anywhere)
+ {
+ vm_address_t mapping_size = 0;
+ vm_address_t anywhere_start = 0;
+
+ /* Find out the overall mapping size. TODO: This assumes that the
+ lowest PT_LOAD p_vaddr is always zero for PIC. */
for (i = 0; i < e->info.elf.phnum; ++i)
- if (e->info.elf.phdr[i].p_type == PT_LOAD)
- {
- end = load_section (&e->info.elf.phdr[i], e, interp);
- if (anywhere && end > anywhere_start)
- /* This section pushes the next anywhere-load further */
- anywhere_start = end;
- }
+ {
+ ElfW(Phdr) *phdr = &e->info.elf.phdr[i];
+ if (phdr->p_type == PT_LOAD)
+ mapping_size = phdr->p_vaddr + phdr->p_memsz;
+ }
+
+ /* Ask the kernel to find this much contiguous memory. */
+ e->error = vm_allocate (usertask, &anywhere_start, mapping_size, 1);
+ if (e->error)
+ goto out; /* not enough memory? */
+
+ e->info.elf.loadbase = anywhere_start;
+ e->info.elf.anywhere = 0;
- /* The entry point address is relative to wherever we loaded the
- program text. */
- e->entry += e->info.elf.loadbase;
+ /* Deallocate the space, to be replaced by the actual mappings
+ we do next. */
+ e->error = vm_deallocate (usertask, anywhere_start, mapping_size);
+ /* This may return an error if USERTASK dies, or if it's something
+ magical and not a real task port. */
+ if (e->error)
+ goto out;
}
+ for (i = 0; i < e->info.elf.phnum; ++i)
+ if (e->info.elf.phdr[i].p_type == PT_LOAD)
+ load_section (&e->info.elf.phdr[i], e);
+
+ /* The entry point address is relative to wherever we loaded the program
+ text. */
+ e->entry += e->info.elf.loadbase;
+
+ out:
/* Release the conch for the file. */
finish_mapping (e);
-
- /* Return potentially-new start for anywhere-loads. */
- return round_page (anywhere_start);
}
@@ -822,7 +797,6 @@ do_exec (file_t file,
mach_msg_type_number_t i;
int intarray_dealloc = 0; /* Dealloc INTARRAY before returning? */
int oldtask_trashed = 0; /* Have we trashed the old task? */
- vm_address_t anywhere_start = 0;
/* Prime E for executing FILE and check its validity. This must be an
inline function because it stores pointers into alloca'd storage in E
@@ -1283,12 +1257,15 @@ do_exec (file_t file,
goto out;
}
-/* XXX this should be below
- it is here to work around a vm_map kernel bug. */
+ /* Load the file into the task. */
+ load (newtask, &e);
+ if (e.error)
+ goto out;
+
if (interp.file != MACH_PORT_NULL)
{
/* Load the interpreter file. */
- anywhere_start = load (newtask, &interp, anywhere_start, 1);
+ load (newtask, &interp);
if (interp.error)
{
e.error = interp.error;
@@ -1297,17 +1274,6 @@ do_exec (file_t file,
finish (&interp, 1);
}
-
- /* Leave room for mmaps etc. before PIE binaries.
- * Could add address randomization here. */
- anywhere_start += 128 << 20;
- /* Load the file into the task. */
- anywhere_start = load (newtask, &e, anywhere_start, 0);
- if (e.error)
- goto out;
-
- /* XXX loading of interp belongs here */
-
/* Clean up. */
finish (&e, 0);