execve updates for v5.18-rc1

- Handle unusual AT_PHDR offsets (Akira Kawata)
 - Fix initial mapping size when PT_LOADs are not ordered (Alexey Dobriyan)
 - Move more code under CONFIG_COREDUMP (Alexey Dobriyan)
 - Fix missing mmap_lock in file_files_note (Eric W. Biederman)
 - Remove a.out support for alpha and m68k (Eric W. Biederman)
 - Include first pages of non-exec ELF libraries in coredump (Jann Horn)
 - Don't write past end of notes for regset gap in coredump (Rick Edgecombe)
 - Comment clean-ups (Tom Rix)
 - Force single empty string when argv is empty (Kees Cook)
 - Add NULL argv selftest (Kees Cook)
 - Properly redefine PT_GNU_* in terms of PT_LOOS (Kees Cook)
 - MAINTAINERS: Update execve entry with tree (Kees Cook)
 - Introduce initial KUnit testing for binfmt_elf (Kees Cook)
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEEpcP2jyKd1g9yPm4TiXL039xtwCYFAmI4ji4WHGtlZXNjb29r
 QGNocm9taXVtLm9yZwAKCRCJcvTf3G3AJi7VD/9o+PndYkeGclL7sYfouhSzK21W
 go4SGCrTl0oK/mfz3qXVYeS4VFjNTCTEs8rSZdjHN8a9VAVSJ38z6FPwbSQobzEP
 zXPuvwxe4GM4jb8FsBTcTEl1Wfw6kUV9JHXqFje6MuiZMXa8YDD+UMl95CgmGi1L
 5sOw4quHXkG8nlC0v1PI9XSpmzK2nHmXBWVddnPXTUmEfitvoIJdf0iTJ4/4mYM/
 OwrCiufGHvGtQFUrYTxgiZ3nvFdAkZDt+P8GA8NJOBCMDTPvsk57uTok1sW6CRFT
 lSymgoc3SczBtHYO6nFl5U04XGsNY+iHYhjhNL10IoucdCvS2VS0vEb8ZXKg6wtQ
 /tbgf1Mcfu7eoClA0ZjQX/pQbkPYL/s++Lwkc7pzknbmdwq+1yZF1+4Y1XItR4jJ
 kUhVsewQuU0os7BnaREkFOcwqXfA4hixb9w79p+SjMX8/XrnSkLJ3cFswkGTUxdO
 DOwhVcmqsZdVXMMk0R3oOtm9ABSp/FqvT8At2kZI0W93jhZGHWzOrU+psnkTUcDt
 KpFEJzdoh4ImZvBK8F5f07dAlqeVEZvVDhBt+x1Wxcu90p7rmZJT8OV2mJCDVhZG
 E2PW7UuLOAbgRM+E+gxz7SkpIMtOSFlxT2xGuygcRbIxOOeVnj1x9NwGdI9xcgpF
 s021x7TcHbpvYakRsg==
 =SyEY
 -----END PGP SIGNATURE-----

Merge tag 'execve-v5.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux

Pull execve updates from Kees Cook:
 "Execve and binfmt updates.

  Eric and I have stepped up to be the active maintainers of this area,
  so here's our first collection. The bulk of the work was in coredump
  handling fixes; additional details are noted below:

   - Handle unusual AT_PHDR offsets (Akira Kawata)

   - Fix initial mapping size when PT_LOADs are not ordered (Alexey
     Dobriyan)

   - Move more code under CONFIG_COREDUMP (Alexey Dobriyan)

   - Fix missing mmap_lock in file_files_note (Eric W. Biederman)

   - Remove a.out support for alpha and m68k (Eric W. Biederman)

   - Include first pages of non-exec ELF libraries in coredump (Jann
     Horn)

   - Don't write past end of notes for regset gap in coredump (Rick
     Edgecombe)

   - Comment clean-ups (Tom Rix)

   - Force single empty string when argv is empty (Kees Cook)

   - Add NULL argv selftest (Kees Cook)

   - Properly redefine PT_GNU_* in terms of PT_LOOS (Kees Cook)

   - MAINTAINERS: Update execve entry with tree (Kees Cook)

   - Introduce initial KUnit testing for binfmt_elf (Kees Cook)"

* tag 'execve-v5.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux:
  binfmt_elf: Don't write past end of notes for regset gap
  a.out: Stop building a.out/osf1 support on alpha and m68k
  coredump: Don't compile flat_core_dump when coredumps are disabled
  coredump: Use the vma snapshot in fill_files_note
  coredump/elf: Pass coredump_params into fill_note_info
  coredump: Remove the WARN_ON in dump_vma_snapshot
  coredump: Snapshot the vmas in do_coredump
  coredump: Move definition of struct coredump_params into coredump.h
  binfmt_elf: Introduce KUnit test
  ELF: Properly redefine PT_GNU_* in terms of PT_LOOS
  MAINTAINERS: Update execve entry with more details
  exec: cleanup comments
  fs/binfmt_elf: Refactor load_elf_binary function
  fs/binfmt_elf: Fix AT_PHDR for unusual ELF files
  binfmt: move more stuff undef CONFIG_COREDUMP
  selftests/exec: Test for empty string on NULL argv
  exec: Force single empty string when argv is empty
  coredump: Also dump first pages of non-executable ELF libraries
  ELF: fix overflow in total mapping size calculation
This commit is contained in:
Linus Torvalds 2022-03-21 19:16:02 -07:00
commit b7a801f395
16 changed files with 374 additions and 127 deletions

View File

@ -7234,6 +7234,9 @@ F: net/core/of_net.c
EXEC & BINFMT API
R: Eric Biederman <ebiederm@xmission.com>
R: Kees Cook <keescook@chromium.org>
L: linux-mm@kvack.org
S: Supported
T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/execve
F: arch/alpha/kernel/binfmt_loader.c
F: arch/x86/ia32/ia32_aout.c
F: fs/*binfmt_*.c
@ -7241,6 +7244,7 @@ F: fs/exec.c
F: include/linux/binfmts.h
F: include/linux/elf.h
F: include/uapi/linux/binfmts.h
F: include/uapi/linux/elf.h
F: tools/testing/selftests/exec/
N: asm/elf.h
N: binfmt

View File

@ -12,7 +12,6 @@ config ALPHA
select FORCE_PCI if !ALPHA_JENSEN
select PCI_DOMAINS if PCI
select PCI_SYSCALL if PCI
select HAVE_AOUT
select HAVE_ASM_MODVERSIONS
select HAVE_PCSPKR_PLATFORM
select HAVE_PERF_EVENTS

View File

@ -17,7 +17,6 @@ config M68K
select GENERIC_CPU_DEVICES
select GENERIC_IOMAP
select GENERIC_IRQ_SHOW
select HAVE_AOUT if MMU
select HAVE_ASM_MODVERSIONS
select HAVE_DEBUG_BUGVERBOSE
select HAVE_EFFICIENT_UNALIGNED_ACCESS if !CPU_HAS_NO_UNALIGNED

View File

@ -28,6 +28,16 @@ config BINFMT_ELF
ld.so (check the file <file:Documentation/Changes> for location and
latest version).
config BINFMT_ELF_KUNIT_TEST
bool "Build KUnit tests for ELF binary support" if !KUNIT_ALL_TESTS
depends on KUNIT=y && BINFMT_ELF=y
default KUNIT_ALL_TESTS
help
This builds the ELF loader KUnit tests, which try to gather
prior bug fixes into a regression test collection. This is really
only needed for debugging. Note that with CONFIG_COMPAT=y, the
compat_binfmt_elf KUnit test is also created.
config COMPAT_BINFMT_ELF
def_bool y
depends on COMPAT && BINFMT_ELF

View File

@ -93,7 +93,7 @@ static int elf_core_dump(struct coredump_params *cprm);
#define ELF_CORE_EFLAGS 0
#endif
#define ELF_PAGESTART(_v) ((_v) & ~(unsigned long)(ELF_MIN_ALIGN-1))
#define ELF_PAGESTART(_v) ((_v) & ~(int)(ELF_MIN_ALIGN-1))
#define ELF_PAGEOFFSET(_v) ((_v) & (ELF_MIN_ALIGN-1))
#define ELF_PAGEALIGN(_v) (((_v) + ELF_MIN_ALIGN - 1) & ~(ELF_MIN_ALIGN - 1))
@ -101,8 +101,10 @@ static struct linux_binfmt elf_format = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,
.load_shlib = load_elf_library,
#ifdef CONFIG_COREDUMP
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
#endif
};
#define BAD_ADDR(x) (unlikely((unsigned long)(x) >= TASK_SIZE))
@ -170,8 +172,8 @@ static int padzero(unsigned long elf_bss)
static int
create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
unsigned long load_addr, unsigned long interp_load_addr,
unsigned long e_entry)
unsigned long interp_load_addr,
unsigned long e_entry, unsigned long phdr_addr)
{
struct mm_struct *mm = current->mm;
unsigned long p = bprm->p;
@ -257,7 +259,7 @@ create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
NEW_AUX_ENT(AT_HWCAP, ELF_HWCAP);
NEW_AUX_ENT(AT_PAGESZ, ELF_EXEC_PAGESIZE);
NEW_AUX_ENT(AT_CLKTCK, CLOCKS_PER_SEC);
NEW_AUX_ENT(AT_PHDR, load_addr + exec->e_phoff);
NEW_AUX_ENT(AT_PHDR, phdr_addr);
NEW_AUX_ENT(AT_PHENT, sizeof(struct elf_phdr));
NEW_AUX_ENT(AT_PHNUM, exec->e_phnum);
NEW_AUX_ENT(AT_BASE, interp_load_addr);
@ -399,22 +401,21 @@ static unsigned long elf_map(struct file *filep, unsigned long addr,
return(map_addr);
}
static unsigned long total_mapping_size(const struct elf_phdr *cmds, int nr)
static unsigned long total_mapping_size(const struct elf_phdr *phdr, int nr)
{
int i, first_idx = -1, last_idx = -1;
elf_addr_t min_addr = -1;
elf_addr_t max_addr = 0;
bool pt_load = false;
int i;
for (i = 0; i < nr; i++) {
if (cmds[i].p_type == PT_LOAD) {
last_idx = i;
if (first_idx == -1)
first_idx = i;
if (phdr[i].p_type == PT_LOAD) {
min_addr = min(min_addr, ELF_PAGESTART(phdr[i].p_vaddr));
max_addr = max(max_addr, phdr[i].p_vaddr + phdr[i].p_memsz);
pt_load = true;
}
}
if (first_idx == -1)
return 0;
return cmds[last_idx].p_vaddr + cmds[last_idx].p_memsz -
ELF_PAGESTART(cmds[first_idx].p_vaddr);
return pt_load ? (max_addr - min_addr) : 0;
}
static int elf_read(struct file *file, void *buf, size_t len, loff_t pos)
@ -823,8 +824,8 @@ static int parse_elf_properties(struct file *f, const struct elf_phdr *phdr,
static int load_elf_binary(struct linux_binprm *bprm)
{
struct file *interpreter = NULL; /* to shut gcc up */
unsigned long load_addr = 0, load_bias = 0;
int load_addr_set = 0;
unsigned long load_bias = 0, phdr_addr = 0;
int first_pt_load = 1;
unsigned long error;
struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;
struct elf_phdr *elf_property_phdata = NULL;
@ -1074,12 +1075,12 @@ out_free_interp:
vaddr = elf_ppnt->p_vaddr;
/*
* The first time through the loop, load_addr_set is false:
* The first time through the loop, first_pt_load is true:
* layout will be calculated. Once set, use MAP_FIXED since
* we know we've already safely mapped the entire region with
* MAP_FIXED_NOREPLACE in the once-per-binary logic following.
*/
if (load_addr_set) {
if (!first_pt_load) {
elf_flags |= MAP_FIXED;
} else if (elf_ex->e_type == ET_EXEC) {
/*
@ -1170,16 +1171,25 @@ out_free_interp:
goto out_free_dentry;
}
if (!load_addr_set) {
load_addr_set = 1;
load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);
if (first_pt_load) {
first_pt_load = 0;
if (elf_ex->e_type == ET_DYN) {
load_bias += error -
ELF_PAGESTART(load_bias + vaddr);
load_addr += load_bias;
reloc_func_desc = load_bias;
}
}
/*
* Figure out which segment in the file contains the Program
* Header table, and map to the associated memory address.
*/
if (elf_ppnt->p_offset <= elf_ex->e_phoff &&
elf_ex->e_phoff < elf_ppnt->p_offset + elf_ppnt->p_filesz) {
phdr_addr = elf_ex->e_phoff - elf_ppnt->p_offset +
elf_ppnt->p_vaddr;
}
k = elf_ppnt->p_vaddr;
if ((elf_ppnt->p_flags & PF_X) && k < start_code)
start_code = k;
@ -1215,6 +1225,7 @@ out_free_interp:
}
e_entry = elf_ex->e_entry + load_bias;
phdr_addr += load_bias;
elf_bss += load_bias;
elf_brk += load_bias;
start_code += load_bias;
@ -1278,8 +1289,8 @@ out_free_interp:
goto out;
#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES */
retval = create_elf_tables(bprm, elf_ex,
load_addr, interp_load_addr, e_entry);
retval = create_elf_tables(bprm, elf_ex, interp_load_addr,
e_entry, phdr_addr);
if (retval < 0)
goto out;
@ -1630,17 +1641,16 @@ static void fill_siginfo_note(struct memelfnote *note, user_siginfo_t *csigdata,
* long file_ofs
* followed by COUNT filenames in ASCII: "FILE1" NUL "FILE2" NUL...
*/
static int fill_files_note(struct memelfnote *note)
static int fill_files_note(struct memelfnote *note, struct coredump_params *cprm)
{
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
unsigned count, size, names_ofs, remaining, n;
user_long_t *data;
user_long_t *start_end_ofs;
char *name_base, *name_curpos;
int i;
/* *Estimated* file count and total data size needed */
count = mm->map_count;
count = cprm->vma_count;
if (count > UINT_MAX / 64)
return -EINVAL;
size = count * 64;
@ -1662,11 +1672,12 @@ static int fill_files_note(struct memelfnote *note)
name_base = name_curpos = ((char *)data) + names_ofs;
remaining = size - names_ofs;
count = 0;
for (vma = mm->mmap; vma != NULL; vma = vma->vm_next) {
for (i = 0; i < cprm->vma_count; i++) {
struct core_vma_metadata *m = &cprm->vma_meta[i];
struct file *file;
const char *filename;
file = vma->vm_file;
file = m->file;
if (!file)
continue;
filename = file_path(file, name_curpos, remaining);
@ -1686,9 +1697,9 @@ static int fill_files_note(struct memelfnote *note)
memmove(name_curpos, filename, n);
name_curpos += n;
*start_end_ofs++ = vma->vm_start;
*start_end_ofs++ = vma->vm_end;
*start_end_ofs++ = vma->vm_pgoff;
*start_end_ofs++ = m->start;
*start_end_ofs++ = m->end;
*start_end_ofs++ = m->pgoff;
count++;
}
@ -1699,7 +1710,7 @@ static int fill_files_note(struct memelfnote *note)
* Count usually is less than mm->map_count,
* we need to move filenames down.
*/
n = mm->map_count - count;
n = cprm->vma_count - count;
if (n != 0) {
unsigned shift_bytes = n * 3 * sizeof(data[0]);
memmove(name_base - shift_bytes, name_base,
@ -1755,9 +1766,9 @@ static void do_thread_regset_writeback(struct task_struct *task,
static int fill_thread_core_info(struct elf_thread_core_info *t,
const struct user_regset_view *view,
long signr, size_t *total)
long signr, struct elf_note_info *info)
{
unsigned int i;
unsigned int note_iter, view_iter;
/*
* NT_PRSTATUS is the one special case, because the regset data
@ -1771,17 +1782,17 @@ static int fill_thread_core_info(struct elf_thread_core_info *t,
fill_note(&t->notes[0], "CORE", NT_PRSTATUS,
PRSTATUS_SIZE, &t->prstatus);
*total += notesize(&t->notes[0]);
info->size += notesize(&t->notes[0]);
do_thread_regset_writeback(t->task, &view->regsets[0]);
/*
* Each other regset might generate a note too. For each regset
* that has no core_note_type or is inactive, we leave t->notes[i]
* all zero and we'll know to skip writing it later.
* that has no core_note_type or is inactive, skip it.
*/
for (i = 1; i < view->n; ++i) {
const struct user_regset *regset = &view->regsets[i];
note_iter = 1;
for (view_iter = 1; view_iter < view->n; ++view_iter) {
const struct user_regset *regset = &view->regsets[view_iter];
int note_type = regset->core_note_type;
bool is_fpreg = note_type == NT_PRFPREG;
void *data;
@ -1797,13 +1808,17 @@ static int fill_thread_core_info(struct elf_thread_core_info *t,
if (ret < 0)
continue;
if (WARN_ON_ONCE(note_iter >= info->thread_notes))
break;
if (is_fpreg)
SET_PR_FPVALID(&t->prstatus);
fill_note(&t->notes[i], is_fpreg ? "CORE" : "LINUX",
fill_note(&t->notes[note_iter], is_fpreg ? "CORE" : "LINUX",
note_type, ret, data);
*total += notesize(&t->notes[i]);
info->size += notesize(&t->notes[note_iter]);
note_iter++;
}
return 1;
@ -1811,7 +1826,7 @@ static int fill_thread_core_info(struct elf_thread_core_info *t,
static int fill_note_info(struct elfhdr *elf, int phdrs,
struct elf_note_info *info,
const kernel_siginfo_t *siginfo, struct pt_regs *regs)
struct coredump_params *cprm)
{
struct task_struct *dump_task = current;
const struct user_regset_view *view = task_user_regset_view(dump_task);
@ -1883,7 +1898,7 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
* Now fill in each thread's information.
*/
for (t = info->thread; t != NULL; t = t->next)
if (!fill_thread_core_info(t, view, siginfo->si_signo, &info->size))
if (!fill_thread_core_info(t, view, cprm->siginfo->si_signo, info))
return 0;
/*
@ -1892,13 +1907,13 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
fill_psinfo(psinfo, dump_task->group_leader, dump_task->mm);
info->size += notesize(&info->psinfo);
fill_siginfo_note(&info->signote, &info->csigdata, siginfo);
fill_siginfo_note(&info->signote, &info->csigdata, cprm->siginfo);
info->size += notesize(&info->signote);
fill_auxv_note(&info->auxv, current->mm);
info->size += notesize(&info->auxv);
if (fill_files_note(&info->files) == 0)
if (fill_files_note(&info->files, cprm) == 0)
info->size += notesize(&info->files);
return 1;
@ -2040,7 +2055,7 @@ static int elf_note_info_init(struct elf_note_info *info)
static int fill_note_info(struct elfhdr *elf, int phdrs,
struct elf_note_info *info,
const kernel_siginfo_t *siginfo, struct pt_regs *regs)
struct coredump_params *cprm)
{
struct core_thread *ct;
struct elf_thread_status *ets;
@ -2061,13 +2076,13 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
list_for_each_entry(ets, &info->thread_list, list) {
int sz;
sz = elf_dump_thread_status(siginfo->si_signo, ets);
sz = elf_dump_thread_status(cprm->siginfo->si_signo, ets);
info->thread_status_size += sz;
}
/* now collect the dump for the current */
memset(info->prstatus, 0, sizeof(*info->prstatus));
fill_prstatus(&info->prstatus->common, current, siginfo->si_signo);
elf_core_copy_regs(&info->prstatus->pr_reg, regs);
fill_prstatus(&info->prstatus->common, current, cprm->siginfo->si_signo);
elf_core_copy_regs(&info->prstatus->pr_reg, cprm->regs);
/* Set up header */
fill_elf_header(elf, phdrs, ELF_ARCH, ELF_CORE_EFLAGS);
@ -2083,18 +2098,18 @@ static int fill_note_info(struct elfhdr *elf, int phdrs,
fill_note(info->notes + 1, "CORE", NT_PRPSINFO,
sizeof(*info->psinfo), info->psinfo);
fill_siginfo_note(info->notes + 2, &info->csigdata, siginfo);
fill_siginfo_note(info->notes + 2, &info->csigdata, cprm->siginfo);
fill_auxv_note(info->notes + 3, current->mm);
info->numnote = 4;
if (fill_files_note(info->notes + info->numnote) == 0) {
if (fill_files_note(info->notes + info->numnote, cprm) == 0) {
info->notes_files = info->notes + info->numnote;
info->numnote++;
}
/* Try to dump the FPU. */
info->prstatus->pr_fpvalid = elf_core_copy_task_fpregs(current, regs,
info->fpu);
info->prstatus->pr_fpvalid =
elf_core_copy_task_fpregs(current, cprm->regs, info->fpu);
if (info->prstatus->pr_fpvalid)
fill_note(info->notes + info->numnote++,
"CORE", NT_PRFPREG, sizeof(*info->fpu), info->fpu);
@ -2180,8 +2195,7 @@ static void fill_extnum_info(struct elfhdr *elf, struct elf_shdr *shdr4extnum,
static int elf_core_dump(struct coredump_params *cprm)
{
int has_dumped = 0;
int vma_count, segs, i;
size_t vma_data_size;
int segs, i;
struct elfhdr elf;
loff_t offset = 0, dataoff;
struct elf_note_info info = { };
@ -2189,16 +2203,12 @@ static int elf_core_dump(struct coredump_params *cprm)
struct elf_shdr *shdr4extnum = NULL;
Elf_Half e_phnum;
elf_addr_t e_shoff;
struct core_vma_metadata *vma_meta;
if (dump_vma_snapshot(cprm, &vma_count, &vma_meta, &vma_data_size))
return 0;
/*
* The number of segs are recored into ELF header as 16bit value.
* Please check DEFAULT_MAX_MAP_COUNT definition when you modify here.
*/
segs = vma_count + elf_core_extra_phdrs();
segs = cprm->vma_count + elf_core_extra_phdrs();
/* for notes section */
segs++;
@ -2212,7 +2222,7 @@ static int elf_core_dump(struct coredump_params *cprm)
* Collect all the non-memory information about the process for the
* notes. This also sets up the file header.
*/
if (!fill_note_info(&elf, e_phnum, &info, cprm->siginfo, cprm->regs))
if (!fill_note_info(&elf, e_phnum, &info, cprm))
goto end_coredump;
has_dumped = 1;
@ -2237,7 +2247,7 @@ static int elf_core_dump(struct coredump_params *cprm)
dataoff = offset = roundup(offset, ELF_EXEC_PAGESIZE);
offset += vma_data_size;
offset += cprm->vma_data_size;
offset += elf_core_extra_data_size();
e_shoff = offset;
@ -2257,8 +2267,8 @@ static int elf_core_dump(struct coredump_params *cprm)
goto end_coredump;
/* Write program headers for segments dump */
for (i = 0; i < vma_count; i++) {
struct core_vma_metadata *meta = vma_meta + i;
for (i = 0; i < cprm->vma_count; i++) {
struct core_vma_metadata *meta = cprm->vma_meta + i;
struct elf_phdr phdr;
phdr.p_type = PT_LOAD;
@ -2295,8 +2305,8 @@ static int elf_core_dump(struct coredump_params *cprm)
/* Align to page */
dump_skip_to(cprm, dataoff);
for (i = 0; i < vma_count; i++) {
struct core_vma_metadata *meta = vma_meta + i;
for (i = 0; i < cprm->vma_count; i++) {
struct core_vma_metadata *meta = cprm->vma_meta + i;
if (!dump_user_range(cprm, meta->start, meta->dump_size))
goto end_coredump;
@ -2313,7 +2323,6 @@ static int elf_core_dump(struct coredump_params *cprm)
end_coredump:
free_note_info(&info);
kfree(shdr4extnum);
kvfree(vma_meta);
kfree(phdr4note);
return has_dumped;
}
@ -2335,3 +2344,7 @@ static void __exit exit_elf_binfmt(void)
core_initcall(init_elf_binfmt);
module_exit(exit_elf_binfmt);
MODULE_LICENSE("GPL");
#ifdef CONFIG_BINFMT_ELF_KUNIT_TEST
#include "binfmt_elf_test.c"
#endif

View File

@ -83,8 +83,8 @@ static struct linux_binfmt elf_fdpic_format = {
.load_binary = load_elf_fdpic_binary,
#ifdef CONFIG_ELF_CORE
.core_dump = elf_fdpic_core_dump,
#endif
.min_coredump = ELF_EXEC_PAGESIZE,
#endif
};
static int __init init_elf_fdpic_binfmt(void)
@ -1465,7 +1465,7 @@ static bool elf_fdpic_dump_segments(struct coredump_params *cprm,
static int elf_fdpic_core_dump(struct coredump_params *cprm)
{
int has_dumped = 0;
int vma_count, segs;
int segs;
int i;
struct elfhdr *elf = NULL;
loff_t offset = 0, dataoff;
@ -1480,8 +1480,6 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
elf_addr_t e_shoff;
struct core_thread *ct;
struct elf_thread_status *tmp;
struct core_vma_metadata *vma_meta = NULL;
size_t vma_data_size;
/* alloc memory for large data structures: too large to be on stack */
elf = kmalloc(sizeof(*elf), GFP_KERNEL);
@ -1491,9 +1489,6 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
if (!psinfo)
goto end_coredump;
if (dump_vma_snapshot(cprm, &vma_count, &vma_meta, &vma_data_size))
goto end_coredump;
for (ct = current->signal->core_state->dumper.next;
ct; ct = ct->next) {
tmp = elf_dump_thread_status(cprm->siginfo->si_signo,
@ -1513,7 +1508,7 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
tmp->next = thread_list;
thread_list = tmp;
segs = vma_count + elf_core_extra_phdrs();
segs = cprm->vma_count + elf_core_extra_phdrs();
/* for notes section */
segs++;
@ -1558,7 +1553,7 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
/* Page-align dumped data */
dataoff = offset = roundup(offset, ELF_EXEC_PAGESIZE);
offset += vma_data_size;
offset += cprm->vma_data_size;
offset += elf_core_extra_data_size();
e_shoff = offset;
@ -1578,8 +1573,8 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
goto end_coredump;
/* write program headers for segments dump */
for (i = 0; i < vma_count; i++) {
struct core_vma_metadata *meta = vma_meta + i;
for (i = 0; i < cprm->vma_count; i++) {
struct core_vma_metadata *meta = cprm->vma_meta + i;
struct elf_phdr phdr;
size_t sz;
@ -1628,7 +1623,7 @@ static int elf_fdpic_core_dump(struct coredump_params *cprm)
dump_skip_to(cprm, dataoff);
if (!elf_fdpic_dump_segments(cprm, vma_meta, vma_count))
if (!elf_fdpic_dump_segments(cprm, cprm->vma_meta, cprm->vma_count))
goto end_coredump;
if (!elf_core_write_extra_data(cprm))
@ -1652,7 +1647,6 @@ end_coredump:
thread_list = thread_list->next;
kfree(tmp);
}
kvfree(vma_meta);
kfree(phdr4note);
kfree(elf);
kfree(psinfo);

64
fs/binfmt_elf_test.c Normal file
View File

@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <kunit/test.h>
static void total_mapping_size_test(struct kunit *test)
{
struct elf_phdr empty[] = {
{ .p_type = PT_LOAD, .p_vaddr = 0, .p_memsz = 0, },
{ .p_type = PT_INTERP, .p_vaddr = 10, .p_memsz = 999999, },
};
/*
* readelf -lW /bin/mount | grep '^ .*0x0' | awk '{print "\t\t{ .p_type = PT_" \
* $1 ", .p_vaddr = " $3 ", .p_memsz = " $6 ", },"}'
*/
struct elf_phdr mount[] = {
{ .p_type = PT_PHDR, .p_vaddr = 0x00000040, .p_memsz = 0x0002d8, },
{ .p_type = PT_INTERP, .p_vaddr = 0x00000318, .p_memsz = 0x00001c, },
{ .p_type = PT_LOAD, .p_vaddr = 0x00000000, .p_memsz = 0x0033a8, },
{ .p_type = PT_LOAD, .p_vaddr = 0x00004000, .p_memsz = 0x005c91, },
{ .p_type = PT_LOAD, .p_vaddr = 0x0000a000, .p_memsz = 0x0022f8, },
{ .p_type = PT_LOAD, .p_vaddr = 0x0000d330, .p_memsz = 0x000d40, },
{ .p_type = PT_DYNAMIC, .p_vaddr = 0x0000d928, .p_memsz = 0x000200, },
{ .p_type = PT_NOTE, .p_vaddr = 0x00000338, .p_memsz = 0x000030, },
{ .p_type = PT_NOTE, .p_vaddr = 0x00000368, .p_memsz = 0x000044, },
{ .p_type = PT_GNU_PROPERTY, .p_vaddr = 0x00000338, .p_memsz = 0x000030, },
{ .p_type = PT_GNU_EH_FRAME, .p_vaddr = 0x0000b490, .p_memsz = 0x0001ec, },
{ .p_type = PT_GNU_STACK, .p_vaddr = 0x00000000, .p_memsz = 0x000000, },
{ .p_type = PT_GNU_RELRO, .p_vaddr = 0x0000d330, .p_memsz = 0x000cd0, },
};
size_t mount_size = 0xE070;
/* https://lore.kernel.org/linux-fsdevel/YfF18Dy85mCntXrx@fractal.localdomain */
struct elf_phdr unordered[] = {
{ .p_type = PT_LOAD, .p_vaddr = 0x00000000, .p_memsz = 0x0033a8, },
{ .p_type = PT_LOAD, .p_vaddr = 0x0000d330, .p_memsz = 0x000d40, },
{ .p_type = PT_LOAD, .p_vaddr = 0x00004000, .p_memsz = 0x005c91, },
{ .p_type = PT_LOAD, .p_vaddr = 0x0000a000, .p_memsz = 0x0022f8, },
};
/* No headers, no size. */
KUNIT_EXPECT_EQ(test, total_mapping_size(NULL, 0), 0);
KUNIT_EXPECT_EQ(test, total_mapping_size(empty, 0), 0);
/* Empty headers, no size. */
KUNIT_EXPECT_EQ(test, total_mapping_size(empty, 1), 0);
/* No PT_LOAD headers, no size. */
KUNIT_EXPECT_EQ(test, total_mapping_size(&empty[1], 1), 0);
/* Empty PT_LOAD and non-PT_LOAD headers, no size. */
KUNIT_EXPECT_EQ(test, total_mapping_size(empty, 2), 0);
/* Normal set of PT_LOADS, and expected size. */
KUNIT_EXPECT_EQ(test, total_mapping_size(mount, ARRAY_SIZE(mount)), mount_size);
/* Unordered PT_LOADs result in same size. */
KUNIT_EXPECT_EQ(test, total_mapping_size(unordered, ARRAY_SIZE(unordered)), mount_size);
}
static struct kunit_case binfmt_elf_test_cases[] = {
KUNIT_CASE(total_mapping_size_test),
{},
};
static struct kunit_suite binfmt_elf_test_suite = {
.name = KBUILD_MODNAME,
.test_cases = binfmt_elf_test_cases,
};
kunit_test_suite(binfmt_elf_test_suite);

View File

@ -37,6 +37,7 @@
#include <linux/flat.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/coredump.h>
#include <asm/byteorder.h>
#include <asm/unaligned.h>
@ -97,13 +98,17 @@ static int load_flat_shared_library(int id, struct lib_info *p);
#endif
static int load_flat_binary(struct linux_binprm *);
#ifdef CONFIG_COREDUMP
static int flat_core_dump(struct coredump_params *cprm);
#endif
static struct linux_binfmt flat_format = {
.module = THIS_MODULE,
.load_binary = load_flat_binary,
#ifdef CONFIG_COREDUMP
.core_dump = flat_core_dump,
.min_coredump = PAGE_SIZE
#endif
};
/****************************************************************************/
@ -112,12 +117,14 @@ static struct linux_binfmt flat_format = {
* Currently only a stub-function.
*/
#ifdef CONFIG_COREDUMP
static int flat_core_dump(struct coredump_params *cprm)
{
pr_warn("Process %s:%d received signr %d and should have core dumped\n",
current->comm, current->pid, cprm->siginfo->si_signo);
return 1;
}
#endif
/****************************************************************************/
/*

View File

@ -135,6 +135,8 @@
#define elf_format compat_elf_format
#define init_elf_binfmt init_compat_elf_binfmt
#define exit_elf_binfmt exit_compat_elf_binfmt
#define binfmt_elf_test_cases compat_binfmt_elf_test_cases
#define binfmt_elf_test_suite compat_binfmt_elf_test_suite
/*
* We share all the actual code with the native (64-bit) version.

View File

@ -42,6 +42,7 @@
#include <linux/path.h>
#include <linux/timekeeping.h>
#include <linux/sysctl.h>
#include <linux/elf.h>
#include <linux/uaccess.h>
#include <asm/mmu_context.h>
@ -53,6 +54,9 @@
#include <trace/events/sched.h>
static bool dump_vma_snapshot(struct coredump_params *cprm);
static void free_vma_snapshot(struct coredump_params *cprm);
static int core_uses_pid;
static unsigned int core_pipe_limit;
static char core_pattern[CORENAME_MAX_SIZE] = "core";
@ -531,6 +535,7 @@ void do_coredump(const kernel_siginfo_t *siginfo)
* by any locks.
*/
.mm_flags = mm->flags,
.vma_meta = NULL,
};
audit_core_dumps(siginfo->si_signo);
@ -745,6 +750,9 @@ void do_coredump(const kernel_siginfo_t *siginfo)
pr_info("Core dump to |%s disabled\n", cn.corename);
goto close_fail;
}
if (!dump_vma_snapshot(&cprm))
goto close_fail;
file_start_write(cprm.file);
core_dumped = binfmt->core_dump(&cprm);
/*
@ -758,6 +766,7 @@ void do_coredump(const kernel_siginfo_t *siginfo)
dump_emit(&cprm, "", 1);
}
file_end_write(cprm.file);
free_vma_snapshot(&cprm);
}
if (ispipe && core_pipe_limit)
wait_for_dump_helpers(cprm.file);
@ -980,6 +989,8 @@ static bool always_dump_vma(struct vm_area_struct *vma)
return false;
}
#define DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER 1
/*
* Decide how much of @vma's contents should be included in a core dump.
*/
@ -1039,9 +1050,20 @@ static unsigned long vma_dump_size(struct vm_area_struct *vma,
* dump the first page to aid in determining what was mapped here.
*/
if (FILTER(ELF_HEADERS) &&
vma->vm_pgoff == 0 && (vma->vm_flags & VM_READ) &&
(READ_ONCE(file_inode(vma->vm_file)->i_mode) & 0111) != 0)
return PAGE_SIZE;
vma->vm_pgoff == 0 && (vma->vm_flags & VM_READ)) {
if ((READ_ONCE(file_inode(vma->vm_file)->i_mode) & 0111) != 0)
return PAGE_SIZE;
/*
* ELF libraries aren't always executable.
* We'll want to check whether the mapping starts with the ELF
* magic, but not now - we're holding the mmap lock,
* so copy_from_user() doesn't work here.
* Use a placeholder instead, and fix it up later in
* dump_vma_snapshot().
*/
return DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER;
}
#undef FILTER
@ -1078,18 +1100,29 @@ static struct vm_area_struct *next_vma(struct vm_area_struct *this_vma,
return gate_vma;
}
static void free_vma_snapshot(struct coredump_params *cprm)
{
if (cprm->vma_meta) {
int i;
for (i = 0; i < cprm->vma_count; i++) {
struct file *file = cprm->vma_meta[i].file;
if (file)
fput(file);
}
kvfree(cprm->vma_meta);
cprm->vma_meta = NULL;
}
}
/*
* Under the mmap_lock, take a snapshot of relevant information about the task's
* VMAs.
*/
int dump_vma_snapshot(struct coredump_params *cprm, int *vma_count,
struct core_vma_metadata **vma_meta,
size_t *vma_data_size_ptr)
static bool dump_vma_snapshot(struct coredump_params *cprm)
{
struct vm_area_struct *vma, *gate_vma;
struct mm_struct *mm = current->mm;
int i;
size_t vma_data_size = 0;
/*
* Once the stack expansion code is fixed to not change VMA bounds
@ -1097,36 +1130,51 @@ int dump_vma_snapshot(struct coredump_params *cprm, int *vma_count,
* mmap_lock in read mode.
*/
if (mmap_write_lock_killable(mm))
return -EINTR;
return false;
cprm->vma_data_size = 0;
gate_vma = get_gate_vma(mm);
*vma_count = mm->map_count + (gate_vma ? 1 : 0);
cprm->vma_count = mm->map_count + (gate_vma ? 1 : 0);
*vma_meta = kvmalloc_array(*vma_count, sizeof(**vma_meta), GFP_KERNEL);
if (!*vma_meta) {
cprm->vma_meta = kvmalloc_array(cprm->vma_count, sizeof(*cprm->vma_meta), GFP_KERNEL);
if (!cprm->vma_meta) {
mmap_write_unlock(mm);
return -ENOMEM;
return false;
}
for (i = 0, vma = first_vma(current, gate_vma); vma != NULL;
vma = next_vma(vma, gate_vma), i++) {
struct core_vma_metadata *m = (*vma_meta) + i;
struct core_vma_metadata *m = cprm->vma_meta + i;
m->start = vma->vm_start;
m->end = vma->vm_end;
m->flags = vma->vm_flags;
m->dump_size = vma_dump_size(vma, cprm->mm_flags);
m->pgoff = vma->vm_pgoff;
vma_data_size += m->dump_size;
m->file = vma->vm_file;
if (m->file)
get_file(m->file);
}
mmap_write_unlock(mm);
if (WARN_ON(i != *vma_count)) {
kvfree(*vma_meta);
return -EFAULT;
for (i = 0; i < cprm->vma_count; i++) {
struct core_vma_metadata *m = cprm->vma_meta + i;
if (m->dump_size == DUMP_SIZE_MAYBE_ELFHDR_PLACEHOLDER) {
char elfmag[SELFMAG];
if (copy_from_user(elfmag, (void __user *)m->start, SELFMAG) ||
memcmp(elfmag, ELFMAG, SELFMAG) != 0) {
m->dump_size = 0;
} else {
m->dump_size = PAGE_SIZE;
}
}
cprm->vma_data_size += m->dump_size;
}
*vma_data_size_ptr = vma_data_size;
return 0;
return true;
}

View File

@ -118,7 +118,7 @@ bool path_noexec(const struct path *path)
* Note that a shared library must be both readable and executable due to
* security reasons.
*
* Also note that we take the address to load from from the file itself.
* Also note that we take the address to load from the file itself.
*/
SYSCALL_DEFINE1(uselib, const char __user *, library)
{
@ -495,8 +495,14 @@ static int bprm_stack_limits(struct linux_binprm *bprm)
* the stack. They aren't stored until much later when we can't
* signal to the parent that the child has run out of stack space.
* Instead, calculate it here so it's possible to fail gracefully.
*
* In the case of argc = 0, make sure there is space for adding a
* empty string (which will bump argc to 1), to ensure confused
* userspace programs don't start processing from argv[1], thinking
* argc can never be 0, to keep them from walking envp by accident.
* See do_execveat_common().
*/
ptr_size = (bprm->argc + bprm->envc) * sizeof(void *);
ptr_size = (max(bprm->argc, 1) + bprm->envc) * sizeof(void *);
if (limit <= ptr_size)
return -E2BIG;
limit -= ptr_size;
@ -536,7 +542,7 @@ static int copy_strings(int argc, struct user_arg_ptr argv,
if (!valid_arg_len(bprm, len))
goto out;
/* We're going to work our way backwords. */
/* We're going to work our way backwards. */
pos = bprm->p;
str += len;
bprm->p -= len;
@ -1269,7 +1275,7 @@ int begin_new_exec(struct linux_binprm * bprm)
/*
* Must be called _before_ exec_mmap() as bprm->mm is
* not visibile until then. This also enables the update
* not visible until then. This also enables the update
* to be lockless.
*/
retval = set_mm_exe_file(bprm->mm, bprm->file);
@ -1897,6 +1903,9 @@ static int do_execveat_common(int fd, struct filename *filename,
}
retval = count(argv, MAX_ARG_STRINGS);
if (retval == 0)
pr_warn_once("process '%s' launched '%s' with NULL argv: empty string added\n",
current->comm, bprm->filename);
if (retval < 0)
goto out_free;
bprm->argc = retval;
@ -1923,6 +1932,19 @@ static int do_execveat_common(int fd, struct filename *filename,
if (retval < 0)
goto out_free;
/*
* When argv is empty, add an empty string ("") as argv[0] to
* ensure confused userspace programs that start processing
* from argv[1] won't end up walking envp. See also
* bprm_stack_limits().
*/
if (bprm->argc == 0) {
retval = copy_string_kernel("", bprm);
if (retval < 0)
goto out_free;
bprm->argc = 1;
}
retval = bprm_execve(bprm, fd, filename, flags);
out_free:
free_bprm(bprm);
@ -1951,6 +1973,8 @@ int kernel_execve(const char *kernel_filename,
}
retval = count_strings_kernel(argv);
if (WARN_ON_ONCE(retval == 0))
retval = -EINVAL;
if (retval < 0)
goto out_free;
bprm->argc = retval;

View File

@ -8,6 +8,7 @@
#include <uapi/linux/binfmts.h>
struct filename;
struct coredump_params;
#define CORENAME_MAX_SIZE 128
@ -77,18 +78,6 @@ struct linux_binprm {
#define BINPRM_FLAGS_PRESERVE_ARGV0_BIT 3
#define BINPRM_FLAGS_PRESERVE_ARGV0 (1 << BINPRM_FLAGS_PRESERVE_ARGV0_BIT)
/* Function parameter for binfmt->coredump */
struct coredump_params {
const kernel_siginfo_t *siginfo;
struct pt_regs *regs;
struct file *file;
unsigned long limit;
unsigned long mm_flags;
loff_t written;
loff_t pos;
loff_t to_skip;
};
/*
* This structure defines the functions that are used to load the binary formats that
* linux accepts.
@ -98,8 +87,10 @@ struct linux_binfmt {
struct module *module;
int (*load_binary)(struct linux_binprm *);
int (*load_shlib)(struct file *);
#ifdef CONFIG_COREDUMP
int (*core_dump)(struct coredump_params *cprm);
unsigned long min_coredump; /* minimal dump size */
#endif
} __randomize_layout;
extern void __register_binfmt(struct linux_binfmt *fmt, int insert);

View File

@ -12,22 +12,34 @@ struct core_vma_metadata {
unsigned long start, end;
unsigned long flags;
unsigned long dump_size;
unsigned long pgoff;
struct file *file;
};
struct coredump_params {
const kernel_siginfo_t *siginfo;
struct pt_regs *regs;
struct file *file;
unsigned long limit;
unsigned long mm_flags;
loff_t written;
loff_t pos;
loff_t to_skip;
int vma_count;
size_t vma_data_size;
struct core_vma_metadata *vma_meta;
};
/*
* These are the only things you should do on a core-file: use only these
* functions to write out all the necessary info.
*/
struct coredump_params;
extern void dump_skip_to(struct coredump_params *cprm, unsigned long to);
extern void dump_skip(struct coredump_params *cprm, size_t nr);
extern int dump_emit(struct coredump_params *cprm, const void *addr, int nr);
extern int dump_align(struct coredump_params *cprm, int align);
int dump_user_range(struct coredump_params *cprm, unsigned long start,
unsigned long len);
int dump_vma_snapshot(struct coredump_params *cprm, int *vma_count,
struct core_vma_metadata **vma_meta,
size_t *vma_data_size_ptr);
extern void do_coredump(const kernel_siginfo_t *siginfo);
#else
static inline void do_coredump(const kernel_siginfo_t *siginfo) {}

View File

@ -35,10 +35,11 @@ typedef __s64 Elf64_Sxword;
#define PT_HIOS 0x6fffffff /* OS-specific */
#define PT_LOPROC 0x70000000
#define PT_HIPROC 0x7fffffff
#define PT_GNU_EH_FRAME 0x6474e550
#define PT_GNU_PROPERTY 0x6474e553
#define PT_GNU_EH_FRAME (PT_LOOS + 0x474e550)
#define PT_GNU_STACK (PT_LOOS + 0x474e551)
#define PT_GNU_RELRO (PT_LOOS + 0x474e552)
#define PT_GNU_PROPERTY (PT_LOOS + 0x474e553)
/* ARM MTE memory tag segment type */
#define PT_ARM_MEMTAG_MTE (PT_LOPROC + 0x1)

View File

@ -10,6 +10,7 @@ TEST_GEN_FILES := execveat.symlink execveat.denatured script subdir
TEST_FILES := Makefile
TEST_GEN_PROGS += recursion-depth
TEST_GEN_PROGS += null-argv
EXTRA_CLEAN := $(OUTPUT)/subdir.moved $(OUTPUT)/execveat.moved $(OUTPUT)/xxxxx* \
$(OUTPUT)/S_I*.test

View File

@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Test that empty argvs are swapped out for a single empty string. */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "../kselftest.h"
#define FORK(exec) \
do { \
pid = fork(); \
if (pid == 0) { \
/* Child */ \
exec; /* Some kind of exec */ \
perror("# " #exec); \
return 1; \
} \
check_result(pid, #exec); \
} while (0)
void check_result(pid_t pid, const char *msg)
{
int wstatus;
if (pid == (pid_t)-1) {
perror("# fork");
ksft_test_result_fail("fork failed: %s\n", msg);
return;
}
if (waitpid(pid, &wstatus, 0) < 0) {
perror("# waitpid");
ksft_test_result_fail("waitpid failed: %s\n", msg);
return;
}
if (!WIFEXITED(wstatus)) {
ksft_test_result_fail("child did not exit: %s\n", msg);
return;
}
if (WEXITSTATUS(wstatus) != 0) {
ksft_test_result_fail("non-zero exit: %s\n", msg);
return;
}
ksft_test_result_pass("%s\n", msg);
}
int main(int argc, char *argv[], char *envp[])
{
pid_t pid;
static char * const args[] = { NULL };
static char * const str[] = { "", NULL };
/* argc counting checks */
if (argc < 1) {
fprintf(stderr, "# FAIL: saw argc == 0 (old kernel?)\n");
return 1;
}
if (argc != 1) {
fprintf(stderr, "# FAIL: unknown argc (%d)\n", argc);
return 1;
}
if (argv[0][0] == '\0') {
/* Good, we found a NULL terminated string at argv[0]! */
return 0;
}
/* Test runner. */
ksft_print_header();
ksft_set_plan(5);
FORK(execve(argv[0], str, NULL));
FORK(execve(argv[0], NULL, NULL));
FORK(execve(argv[0], NULL, envp));
FORK(execve(argv[0], args, NULL));
FORK(execve(argv[0], args, envp));
ksft_exit(ksft_cnt.ksft_pass == ksft_plan);
}