Skip to content

Commit 11854fe

Browse files
committed
binfmt_elf: Move brk for static PIE even if ASLR disabled
In commit bbdc607 ("binfmt_elf: move brk out of mmap when doing direct loader exec"), the brk was moved out of the mmap region when loading static PIE binaries (ET_DYN without INTERP). The common case for these binaries was testing new ELF loaders, so the brk needed to be away from mmap to avoid colliding with stack, future mmaps (of the loader-loaded binary), etc. But this was only done when ASLR was enabled, in an attempt to minimize changes to memory layouts. After adding support to respect alignment requirements for static PIE binaries in commit 3545def ("binfmt_elf: Honor PT_LOAD alignment for static PIE"), it became possible to have a large gap after the final PT_LOAD segment and the top of the mmap region. This means that future mmap allocations might go after the last PT_LOAD segment (where brk might be if ASLR was disabled) instead of before them (where they traditionally ended up). On arm64, running with ASLR disabled, Ubuntu 22.04's "ldconfig" binary, a static PIE, has alignment requirements that leaves a gap large enough after the last PT_LOAD segment to fit the vdso and vvar, but still leave enough space for the brk (which immediately follows the last PT_LOAD segment) to be allocated by the binary. fffff7f20000-fffff7fde000 r-xp 00000000 fe:02 8110426 /sbin/ldconfig.real fffff7fee000-fffff7ff5000 rw-p 000be000 fe:02 8110426 /sbin/ldconfig.real fffff7ff5000-fffff7ffa000 rw-p 00000000 00:00 0 ***[brk will go here at fffff7ffa000]*** fffff7ffc000-fffff7ffe000 r--p 00000000 00:00 0 [vvar] fffff7ffe000-fffff8000000 r-xp 00000000 00:00 0 [vdso] fffffffdf000-1000000000000 rw-p 00000000 00:00 0 [stack] After commit 0b3bc33 ("arm64: vdso: Switch to generic storage implementation"), the arm64 vvar grew slightly, and suddenly the brk collided with the allocation. fffff7f20000-fffff7fde000 r-xp 00000000 fe:02 8110426 /sbin/ldconfig.real fffff7fee000-fffff7ff5000 rw-p 000be000 fe:02 8110426 /sbin/ldconfig.real fffff7ff5000-fffff7ffa000 rw-p 00000000 00:00 0 ***[oops, no room any more, vvar is at fffff7ffa000!]*** fffff7ffa000-fffff7ffe000 r--p 00000000 00:00 0 [vvar] fffff7ffe000-fffff8000000 r-xp 00000000 00:00 0 [vdso] fffffffdf000-1000000000000 rw-p 00000000 00:00 0 [stack] The solution is to unconditionally move the brk out of the mmap region for static PIE binaries. Whether ASLR is enabled or not does not change if there may be future mmap allocation collisions with a growing brk region. Update memory layout comments (with kernel-doc headings), consolidate the setting of mm->brk to later (it isn't needed early), move static PIE brk out of mmap unconditionally, and make sure brk(2) knows to base brk position off of mm->start_brk not mm->end_data no matter what the cause of moving it is (via current->brk_randomized). For the CONFIG_COMPAT_BRK case, though, leave the logic unchanged, as we can never safely move the brk. These systems, however, are not using specially aligned static PIE binaries. Reported-by: Ryan Roberts <[email protected]> Closes: https://lore.kernel.org/lkml/[email protected]/ Fixes: bbdc607 ("binfmt_elf: move brk out of mmap when doing direct loader exec") Link: https://lore.kernel.org/r/[email protected] Reviewed-by: Ryan Roberts <[email protected]> Tested-by: Ryan Roberts <[email protected]> Signed-off-by: Kees Cook <[email protected]>
1 parent 8ffd015 commit 11854fe

File tree

1 file changed

+47
-24
lines changed

1 file changed

+47
-24
lines changed

fs/binfmt_elf.c

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,7 @@ static int load_elf_binary(struct linux_binprm *bprm)
830830
struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
831831
struct elf_phdr *elf_property_phdata = NULL;
832832
unsigned long elf_brk;
833+
bool brk_moved = false;
833834
int retval, i;
834835
unsigned long elf_entry;
835836
unsigned long e_entry;
@@ -1097,15 +1098,19 @@ static int load_elf_binary(struct linux_binprm *bprm)
10971098
/* Calculate any requested alignment. */
10981099
alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum);
10991100

1100-
/*
1101-
* There are effectively two types of ET_DYN
1102-
* binaries: programs (i.e. PIE: ET_DYN with PT_INTERP)
1103-
* and loaders (ET_DYN without PT_INTERP, since they
1104-
* _are_ the ELF interpreter). The loaders must
1105-
* be loaded away from programs since the program
1106-
* may otherwise collide with the loader (especially
1107-
* for ET_EXEC which does not have a randomized
1108-
* position). For example to handle invocations of
1101+
/**
1102+
* DOC: PIE handling
1103+
*
1104+
* There are effectively two types of ET_DYN ELF
1105+
* binaries: programs (i.e. PIE: ET_DYN with
1106+
* PT_INTERP) and loaders (i.e. static PIE: ET_DYN
1107+
* without PT_INTERP, usually the ELF interpreter
1108+
* itself). Loaders must be loaded away from programs
1109+
* since the program may otherwise collide with the
1110+
* loader (especially for ET_EXEC which does not have
1111+
* a randomized position).
1112+
*
1113+
* For example, to handle invocations of
11091114
* "./ld.so someprog" to test out a new version of
11101115
* the loader, the subsequent program that the
11111116
* loader loads must avoid the loader itself, so
@@ -1118,6 +1123,9 @@ static int load_elf_binary(struct linux_binprm *bprm)
11181123
* ELF_ET_DYN_BASE and loaders are loaded into the
11191124
* independently randomized mmap region (0 load_bias
11201125
* without MAP_FIXED nor MAP_FIXED_NOREPLACE).
1126+
*
1127+
* See below for "brk" handling details, which is
1128+
* also affected by program vs loader and ASLR.
11211129
*/
11221130
if (interpreter) {
11231131
/* On ET_DYN with PT_INTERP, we do the ASLR. */
@@ -1234,8 +1242,6 @@ static int load_elf_binary(struct linux_binprm *bprm)
12341242
start_data += load_bias;
12351243
end_data += load_bias;
12361244

1237-
current->mm->start_brk = current->mm->brk = ELF_PAGEALIGN(elf_brk);
1238-
12391245
if (interpreter) {
12401246
elf_entry = load_elf_interp(interp_elf_ex,
12411247
interpreter,
@@ -1291,27 +1297,44 @@ static int load_elf_binary(struct linux_binprm *bprm)
12911297
mm->end_data = end_data;
12921298
mm->start_stack = bprm->p;
12931299

1294-
if ((current->flags & PF_RANDOMIZE) && (snapshot_randomize_va_space > 1)) {
1300+
/**
1301+
* DOC: "brk" handling
1302+
*
1303+
* For architectures with ELF randomization, when executing a
1304+
* loader directly (i.e. static PIE: ET_DYN without PT_INTERP),
1305+
* move the brk area out of the mmap region and into the unused
1306+
* ELF_ET_DYN_BASE region. Since "brk" grows up it may collide
1307+
* early with the stack growing down or other regions being put
1308+
* into the mmap region by the kernel (e.g. vdso).
1309+
*
1310+
* In the CONFIG_COMPAT_BRK case, though, everything is turned
1311+
* off because we're not allowed to move the brk at all.
1312+
*/
1313+
if (!IS_ENABLED(CONFIG_COMPAT_BRK) &&
1314+
IS_ENABLED(CONFIG_ARCH_HAS_ELF_RANDOMIZE) &&
1315+
elf_ex->e_type == ET_DYN && !interpreter) {
1316+
elf_brk = ELF_ET_DYN_BASE;
1317+
/* This counts as moving the brk, so let brk(2) know. */
1318+
brk_moved = true;
1319+
}
1320+
mm->start_brk = mm->brk = ELF_PAGEALIGN(elf_brk);
1321+
1322+
if ((current->flags & PF_RANDOMIZE) && snapshot_randomize_va_space > 1) {
12951323
/*
1296-
* For architectures with ELF randomization, when executing
1297-
* a loader directly (i.e. no interpreter listed in ELF
1298-
* headers), move the brk area out of the mmap region
1299-
* (since it grows up, and may collide early with the stack
1300-
* growing down), and into the unused ELF_ET_DYN_BASE region.
1324+
* If we didn't move the brk to ELF_ET_DYN_BASE (above),
1325+
* leave a gap between .bss and brk.
13011326
*/
1302-
if (IS_ENABLED(CONFIG_ARCH_HAS_ELF_RANDOMIZE) &&
1303-
elf_ex->e_type == ET_DYN && !interpreter) {
1304-
mm->brk = mm->start_brk = ELF_ET_DYN_BASE;
1305-
} else {
1306-
/* Otherwise leave a gap between .bss and brk. */
1327+
if (!brk_moved)
13071328
mm->brk = mm->start_brk = mm->brk + PAGE_SIZE;
1308-
}
13091329

13101330
mm->brk = mm->start_brk = arch_randomize_brk(mm);
1331+
brk_moved = true;
1332+
}
1333+
13111334
#ifdef compat_brk_randomized
1335+
if (brk_moved)
13121336
current->brk_randomized = 1;
13131337
#endif
1314-
}
13151338

13161339
if (current->personality & MMAP_PAGE_ZERO) {
13171340
/* Why this, you ask??? Well SVr4 maps page 0 as read-only,

0 commit comments

Comments
 (0)