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 */
>
> .... 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?
>>>
>>>
>>
>