On Wed, Aug 28, 2013 at 08:44:26PM +0200, Maxime Villard wrote: > On 08/28/13 16:30, Kenneth R Westerback wrote: > > On Wed, Aug 28, 2013 at 02:54:11PM +0200, Maxime Villard wrote: > >> 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) > > > > Since p_filesz includes the NUL and MAXPATHLEN includes the NUL as > > far as I know, could the comparison not be simply '> MAXPATHLEN'? > > > > In passing I note that 37 out of 749 declarations using MAXPATHLEN in the > > tree use 'char blah[MAXPATHLEN + 1]'. No idea if it is worth looking at > > making them do without the '+ 1'. :-) > > > > I also note in passing several "#define PATH_MAX (MAXPATHLEN + 1)' > > declarations, which strike me as odd since MAXPATHLEN is defined > > in sys/param.h by '#define MAXPATHEN PATH_MAX'. > > > > Let the POSIX lawyers apply any necessary cluebats please. > > > >> 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 */ > > > > The condition is not that the string is NUL terminated ("aaa\0aaa\0" is > > after > > all NUL terminated and valid as far as any function reading strings would be > > concerned. It just has trailing garbage.) Perhaps the comment should refer > > to making sure the string is spec compliant by being of the expected length. > > > By 'valid string' I actually meant 'without trailing garbage'... but ok > if it's not clear > /* Ensure interp is NUL-terminated and of the expected length */
That would be clear to me. .... Ken > > > > > > .... Ken > > > >> + 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? > >>> > >>> > >> > > >