Merge tag 'execve-v5.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees...
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 22 Mar 2022 02:16:02 +0000 (19:16 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 22 Mar 2022 02:16:02 +0000 (19:16 -0700)
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

16 files changed:
MAINTAINERS
arch/alpha/Kconfig
arch/m68k/Kconfig
fs/Kconfig.binfmt
fs/binfmt_elf.c
fs/binfmt_elf_fdpic.c
fs/binfmt_elf_test.c [new file with mode: 0644]
fs/binfmt_flat.c
fs/compat_binfmt_elf.c
fs/coredump.c
fs/exec.c
include/linux/binfmts.h
include/linux/coredump.h
include/uapi/linux/elf.h
tools/testing/selftests/exec/Makefile
tools/testing/selftests/exec/null-argv.c [new file with mode: 0644]

index b986a7b..625ae3c 100644 (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
index 4e87783..14c97ac 100644 (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
index 936e180..268b386 100644 (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
index 68e5862..21c6332 100644 (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
index d61543f..6556e13 100644 (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
index c6f588d..08d0c87 100644 (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);
diff --git a/fs/binfmt_elf_test.c b/fs/binfmt_elf_test.c
new file mode 100644 (file)
index 0000000..11d734f
--- /dev/null
@@ -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);
index 5d776f8..6268981 100644 (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
 
 /****************************************************************************/
 /*
index 95e72d2..8f0af4f 100644 (file)
 #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.
index 1c060c0..7ed7d60 100644 (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;
 }
index 79f2c94..8256e8b 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -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;
index 049cf94..3dc20c4 100644 (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);
index 248a68c..08a1d3e 100644 (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) {}
index fe8e5b7..787c657 100644 (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)
index 2d7fca4..a89ba6d 100644 (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
diff --git a/tools/testing/selftests/exec/null-argv.c b/tools/testing/selftests/exec/null-argv.c
new file mode 100644 (file)
index 0000000..c19726e
--- /dev/null
@@ -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);
+}