Updated diff, with small tweaks from Andres Perera, * int -> size_t, signedness issue, even if it can't be >INT_MAX * NULL -> NUL
Index: exec_elf.c =================================================================== RCS file: /cvs/src/sys/kern/exec_elf.c,v retrieving revision 1.93 diff -u -p -r1.93 exec_elf.c --- exec_elf.c 4 Jul 2013 17:37:05 -0000 1.93 +++ exec_elf.c 28 Aug 2013 14:36:58 -0000 @@ -516,7 +516,7 @@ ELFNAME2(exec,makecmds)(struct proc *p, int error, i; char *interp = NULL; u_long pos = 0, phsize; - size_t randomizequota = ELF_RANDOMIZE_LIMIT; + size_t n, randomizequota = ELF_RANDOMIZE_LIMIT; if (epp->ep_hdrvalid < sizeof(Elf_Ehdr)) return (ENOEXEC); @@ -552,11 +552,21 @@ ELFNAME2(exec,makecmds)(struct proc *p, for (i = 0, pp = ph; i < eh->e_phnum; i++, pp++) { if (pp->p_type == PT_INTERP && !interp) { - if (pp->p_filesz >= MAXPATHLEN) + if (pp->p_filesz >= MAXPATHLEN || + pp->p_filesz < 2) goto bad; interp = pool_get(&namei_pool, PR_WAITOK); if ((error = ELFNAME(read_from)(p, epp->ep_vp, pp->p_offset, interp, pp->p_filesz)) != 0) { + goto bad; + } + /* Ensure interp is a valid, NUL-terminated string */ + for (n = 0; n < pp->p_filesz; n++) { + if (interp[n] == '\0') + break; + } + if (n != pp->p_filesz - 1) { + error = ENOEXEC; goto bad; } } else if (pp->p_type == PT_LOAD) { On 08/28/13 08:44, Maxime Villard wrote: > Hi, > in the ELF format, the PT_INTERP segment contains the path of the > interpreter which must be loaded. For this segment, the kernel looks > at these values in the program header: > > p_offset: offset of the path string > p_filesz: size of the path string, including the \0 > > The path string must be a valid string, or the binary won't be loaded. > > The kernel actually doesn't check the string, it just reads p_filesz > bytes from p_offset, and later it checks if it can be loaded. Malformed > binaries which have paths like: > > "aaaaaaaaaaaaaaaaaaaaa" > or > "aaa\0aaa\0aaa\0aaa\0" > > are parsed, loaded, and then the kernel figures out that the path is > not valid, and aborts. > > When the kernel reads the path from the file, it should check directly > the string to ensure it is at least a valid string. > > Here is a patch for this. For pp->p_filesz, I could have put > if (pp->p_filesz == 0) > but values of 1 also don't make sense; "\0" can't be a valid path. > > > > Index: exec_elf.c > =================================================================== > RCS file: /cvs/src/sys/kern/exec_elf.c,v > retrieving revision 1.93 > diff -u -p -r1.93 exec_elf.c > --- exec_elf.c 4 Jul 2013 17:37:05 -0000 1.93 > +++ exec_elf.c 27 Aug 2013 22:59:21 -0000 > @@ -513,7 +513,7 @@ ELFNAME2(exec,makecmds)(struct proc *p, > Elf_Ehdr *eh = epp->ep_hdr; > Elf_Phdr *ph, *pp, *base_ph = NULL; > Elf_Addr phdr = 0, exe_base = 0; > - int error, i; > + int error, i, n; > char *interp = NULL; > u_long pos = 0, phsize; > size_t randomizequota = ELF_RANDOMIZE_LIMIT; > @@ -552,11 +552,21 @@ ELFNAME2(exec,makecmds)(struct proc *p, > > for (i = 0, pp = ph; i < eh->e_phnum; i++, pp++) { > if (pp->p_type == PT_INTERP && !interp) { > - if (pp->p_filesz >= MAXPATHLEN) > + if (pp->p_filesz >= MAXPATHLEN || > + pp->p_filesz < 2) > goto bad; > interp = pool_get(&namei_pool, PR_WAITOK); > if ((error = ELFNAME(read_from)(p, epp->ep_vp, > pp->p_offset, interp, pp->p_filesz)) != 0) { > + goto bad; > + } > + /* Ensure interp is a valid, NULL-terminated string */ > + for (n = 0; n < pp->p_filesz; n++) { > + if (interp[n] == '\0') > + break; > + } > + if (n != pp->p_filesz - 1) { > + error = ENOEXEC; > goto bad; > } > } else if (pp->p_type == PT_LOAD) { > > > > If you want to test > * Before patching > take whatever binary you have, open it with a text/hex editor, at the > beginning of the file there should be a string like "/usr/libexec/ld.so", > just add a character to it, then run the binary with execv(): you will > get abort traps. > * After patching > execv() now returns -1, and errno is set to ENOEXEC. > > There should not be any regression; if there is, it means that the binary > is not spec compliant. > > > Ok/Comments? > >