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?
>>>
>>>
>>
> 

Reply via email to