On Sat, Oct 13, 2007 at 07:52:55PM +0200, Carl-Daniel Hailfinger wrote:
> Fix a corner case access to uninitialized memory (NULL pointer
> dereference or worse) in case the archive length is exactly
> sizeof(struct lar_header). Such an archive is invalid because the
> filename directly after the LAR header is always dereferenced and has to
> be at least 1 byte in the "empty filename" case (only terminating \0).
> Improve LAR code documentation and reorder variables in one assignment
> to make the code more obvious and readable. This will help people
> understand what the code does when they look at it half a year from now.
> 
> Signed-off-by: Carl-Daniel Hailfinger <[EMAIL PROTECTED]>
> ---
> 
> Index: LinuxBIOSv3/include/lar.h
> ===================================================================
> --- LinuxBIOSv3/include/lar.h (Revision 505)
> +++ LinuxBIOSv3/include/lar.h (Arbeitskopie)
> @@ -52,10 +52,9 @@
>  
>  #include <types.h>
>  
> -/* see note in lib/lar.c as to why this is ARCHIVE and not LARCHIVE */
>  #define MAGIC "LARCHIVE"
>  #define MAX_PATHLEN 1024
> -/* NOTE -- This and the user-mode lar.h are NOT IN SYNC. Be careful. */
> +/* NOTE -- This and the user-mode lar.h may NOT be IN SYNC. Be careful. */

IN SYNC -> in sync (the NOT should stay all-caps)


>  struct lar_header {
>       char magic[8];
>       u32 len;
> Index: LinuxBIOSv3/lib/lar.c
> ===================================================================
> --- LinuxBIOSv3/lib/lar.c     (Revision 505)
> +++ LinuxBIOSv3/lib/lar.c     (Arbeitskopie)
> @@ -49,7 +49,7 @@
>   * Given a file name in the LAR , search for it, and fill out a return 
> struct with the 
>   * result. 
>   * @param archive A descriptor for current archive. This is actually a 
> mem_file type, 
> - *    which is a machine-dependent representation of hte actual archive. In 
> particular, 
> + *    which is a machine-dependent representation of the actual archive. In 
> particular, 

Yep.


>   *    things like u32 are void * in the mem_file struct. 
>   * @param filename filename to find
>   * @param result pointer to mem_file struct which is filled in if the file 
> is found
> @@ -65,30 +65,43 @@
>       printk(BIOS_SPEW, "LAR: Start %p len 0x%x\n", archive->start,
>              archive->len);
>  
> -     if (archive->len < sizeof(struct lar_header))
> -             printk(BIOS_ERR, "Error: truncated archive (%d bytes); minimum 
> possible size is %d bytes\n", 
> -                     archive->len, sizeof(struct lar_header));
> +     /* Why check for sizeof(struct lar_header) + 1? The code below expects
> +      * a filename to follow directly after the LAR header and will
> +      * dereference the address directly after the header. However, if
> +      * archive->len == sizeof(struct lar_header), printing the filename
> +      * will dereference memory outside the archive. Without looking at the
> +      * filename, the only thing we can check is that there is at least room
> +      * for an empty filename (only the terminating \0).
> +      */
> +     if (archive->len < sizeof(struct lar_header) + 1)
> +             printk(BIOS_ERR, "Error: truncated archive (%d bytes); minimum"
> +                     " possible size is %d bytes\n",
> +                     archive->len, sizeof(struct lar_header) + 1);

Is this build-tested and tested with on a real lar file etc?

 
> -     /* Getting this for loop right is harder than it looks. All quantities 
> are 
> -       * unsigned. The LAR stretches from (e.g.) 0xfff0000 for 0x100000 
> -       * bytes, i.e. to address ZERO. 
> -       * As a result, 'walk', can wrap to zero and keep going (this has been 
> -       * seen in practice). Recall that these are unsigned; walk can 
> -       * wrap to zero; so, question, when is walk less than any of these:
> -       * archive->start
> -       * Answer: once walk wraps to zero, it is < archive->start
> -       * archive->start + archive->len
> -       * archive->start + archive->len  - 1
> -       * Answer: In the case that archive->start + archive->len == 0, ALWAYS!
> -       * A lot of expressions have been tried and all have been wrong. 
> -       * So what would work? Simple:
> -       * test for walk < archive->start + archive->len - 1 to cover the case
> -       *     that the archive does NOT occupy ALL of the top of memory and 
> -       *     wrap to zero; 
> -       * and test for walk >= archive->start, 
> -       * to cover the case that you wrapped to zero. 
> -       * Unsigned pointer arithmetic that wraps to zero can be messy.
> -       */
> +     /* Getting this for loop right is harder than it looks. All quantities
> +      * are unsigned. The LAR stretches from (e.g.) 0xfff0000 for 0x100000
> +      * bytes, i.e. to address ZERO.
> +      * As a result, 'walk', can wrap to zero and keep going (this has been
> +      * seen in practice). Recall that these are unsigned; walk can
> +      * wrap to zero; so, question, when is walk less than any of these:
> +      * archive->start
> +      * Answer: once walk wraps to zero, it is < archive->start
> +      * archive->start + archive->len
> +      * archive->start + archive->len  - 1
> +      * Answer: In the case that archive->start + archive->len == 0, ALWAYS!
> +      * A lot of expressions have been tried and all have been wrong.
> +      * So what would work? Simple:
> +      * test for walk < archive->start + archive->len - sizeof(lar_header)
> +      *      to cover the case that the archive does NOT occupy ALL of the
> +      *      top of memory and wrap to zero; RESIST the temptation to change
> +      *      that comparison to <= because if a header did terminate the
> +      *      archive, the filename (stored directly after the header) would
> +      *      be outside the archive and you'd get a nice NULL pointer for
> +      *      the filename
> +      * and test for walk >= archive->start,
> +      *      to cover the case that you wrapped to zero.
> +      * Unsigned pointer arithmetic that wraps to zero can be messy.
> +      */
>       for (walk = archive->start;
>            (walk < (char *)(archive->start + archive->len - sizeof(struct 
> lar_header))) && 
>                       (walk >= (char *)archive->start); walk += 16) {
> @@ -98,7 +111,7 @@
>               header = (struct lar_header *)walk;
>               fullname = walk + sizeof(struct lar_header);
>  
> -             printk(BIOS_SPEW, "LAR: search for %s\n", fullname);
> +             printk(BIOS_SPEW, "LAR: seen member %s\n", fullname);
>               // FIXME: check checksum
>  
>               if (strcmp(fullname, filename) == 0) {
> @@ -115,11 +128,20 @@
>                               result->compression, result->entry, 
> result->loadaddress);
>                       return 0;
>               }
> -             // skip file
> -             walk += (ntohl(header->len) + ntohl(header->offset) -
> -                     1) & 0xfffffff0;
> +             /* skip file:
> +              * The next iteration of the for loop will add 16 to walk, so
> +              * we now add offset (from header start to data start) and len
> +              * (member length), subtract 1 (to get the address of the last
> +              * byte of the member) and round this down to the next 16 byte
> +              * boundary.
> +              * In the case of consecutive archive members with header-
> +              * before-member structure, the next iteration of the loop will
> +              * start exactly at the beginning of the next header.
> +              */
> +             walk += (ntohl(header->offset) + ntohl(header->len) - 1)
> +                      & 0xfffffff0;
>       }
> -     printk(BIOS_SPEW, "NO FILE FOUND\n");
> +     printk(BIOS_SPEW, "LAR: NO FILE FOUND\n");

        printk(BIOS_SPEW, "LAR: NO FILE FOUND!\n");


>       return 1;
>  }
>  
> @@ -157,7 +179,7 @@
>   * the loadaddress pointer in the mem_file struct. 
>   * @param archive A descriptor for current archive.
>   * @param filename filename to find
> - * returns 0 on success, -1 otherwise
> + * returns entry on success, (void*)-1 otherwise
>   */
>  
>  void *load_file(struct mem_file *archive, const char *filename)
> Index: LinuxBIOSv3/util/lar/lar.h
> ===================================================================
> --- LinuxBIOSv3/util/lar/lar.h        (Revision 505)
> +++ LinuxBIOSv3/util/lar/lar.h        (Arbeitskopie)
> @@ -61,13 +61,15 @@
>  typedef uint32_t u32;
>  typedef uint8_t  u8;
>  
> -/* NOTE -- This and the linuxbios lar.h are NOT IN SYNC. Be careful. */
> +/* NOTE -- This and the user-mode lar.h may NOT be IN SYNC. Be careful. */

IN SYNC -> in sync


>  struct lar_header {
>       char magic[8];
>       u32 len;
>       u32 reallen;
>       u32 checksum;
>       u32 compchecksum;
> +     /* Filenames are limited to 2^31-1-sizeof(lar_header)-1 bytes.
> +      * "Nobody will ever need more than 640k" */
>       u32 offset;
>       /* Compression:
>        * 0 = no compression
> Index: LinuxBIOSv3/arch/x86/stage1.c
> ===================================================================
> --- LinuxBIOSv3/arch/x86/stage1.c     (Revision 505)
> +++ LinuxBIOSv3/arch/x86/stage1.c     (Arbeitskopie)
> @@ -69,7 +69,7 @@
>  }
>  
>  /*
> - * This function is called from assembler code whith its argument on the
> + * This function is called from assembler code with its argument on the
>   * stack. Force the compiler to generate always correct code for this case.
>   */
>  void __attribute__((stdcall)) stage1_main(u32 bist)
> @@ -140,7 +140,7 @@
>       } else {
>               printk(BIOS_DEBUG, "Choosing fallback boot.\n");
>               ret = execute_in_place(&archive, "fallback/initram");
> -             /* Try a normal boot if fallback doesn't exists in the lar.
> +             /* Try a normal boot if fallback doesn't exist in the lar.
>                * TODO: There are other ways to do this.
>                * It could be ifdef or the boot flag could be forced.
>                */


Looks good otherwise. With the above fixes:

Acked-by: Uwe Hermann <[EMAIL PROTECTED]>


Uwe.
-- 
http://www.hermann-uwe.de  | http://www.holsham-traders.de
http://www.crazy-hacks.org | http://www.unmaintained-free-software.org

Attachment: signature.asc
Description: Digital signature

-- 
linuxbios mailing list
linuxbios@linuxbios.org
http://www.linuxbios.org/mailman/listinfo/linuxbios

Reply via email to