OSDN Git Service

Fix a buffer overflow bug in getbytes()
[lha/lha.git] / src / header.c
index e4c2d56..035d5d2 100644 (file)
 #endif
 
 static char    *get_ptr;
-#define GET_BYTE()      (*get_ptr++ & 0xff)
+static char    *start_ptr;
+static off_t   storage_size;
+#define setup_get(PTR, SIZE)  (start_ptr = get_ptr = (PTR), storage_size = (SIZE))
 
 #if DUMP_HEADER
-static char    *start_ptr;
-#define setup_get(PTR)  (start_ptr = get_ptr = (PTR))
 #define get_byte()      dump_get_byte()
 #define skip_bytes(len) dump_skip_bytes(len)
 #else
-#define setup_get(PTR)  (get_ptr = (PTR))
-#define get_byte()      GET_BYTE()
-#define skip_bytes(len) (get_ptr += (len))
+#define get_byte()      _get_byte()
+#define skip_bytes(len) _skip_bytes(len)
 #endif
 #define put_ptr         get_ptr
 #define setup_put(PTR)  (put_ptr = (PTR))
@@ -61,12 +60,34 @@ calc_sum(p, len)
     int len;
 {
     int sum = 0;
+    unsigned char *pc = (unsigned char*)p;
 
-    while (len--) sum += *(unsigned char*)p++;
+    while (len--) sum += *pc++;
 
     return sum & 0xff;
 }
 
+static void
+_skip_bytes(len)
+{
+    if (len < 0) {
+      error("Invalid header: %d", len);
+      exit(1);
+    }
+    get_ptr += len;
+}
+
+static int
+_get_byte()
+{
+  if (get_ptr < start_ptr || get_ptr - start_ptr >= storage_size) {
+      error("Invalid header");
+      exit(1);
+  }
+
+  return (*get_ptr++ & 0xff);
+}
+
 #if DUMP_HEADER
 static int
 dump_get_byte()
@@ -75,7 +96,7 @@ dump_get_byte()
 
     if (verbose_listing && verbose > 1)
         printf("%02d %2d: ", get_ptr - start_ptr, 1);
-    c = GET_BYTE();
+    c = _get_byte();
     if (verbose_listing && verbose > 1) {
         if (isprint(c))
             printf("%d(0x%02x) '%c'\n", c, c, c);
@@ -92,12 +113,16 @@ dump_skip_bytes(len)
     if (len == 0) return;
     if (verbose_listing && verbose > 1) {
         printf("%02d %2d: ", get_ptr - start_ptr, len);
+        if (len < 0) {
+          error("Invalid header: %d", len);
+          exit(1);
+        }
         while (len--)
-            printf("0x%02x ", GET_BYTE());
+            printf("0x%02x ", _get_byte());
         printf("... ignored\n");
     }
     else
-        get_ptr += len;
+        _skip_bytes(len);
 }
 #endif
 
@@ -111,8 +136,8 @@ get_word()
     if (verbose_listing && verbose > 1)
         printf("%02d %2d: ", get_ptr - start_ptr, 2);
 #endif
-    b0 = GET_BYTE();
-    b1 = GET_BYTE();
+    b0 = _get_byte();
+    b1 = _get_byte();
     w = (b1 << 8) + b0;
 #if DUMP_HEADER
     if (verbose_listing && verbose > 1)
@@ -139,10 +164,10 @@ get_longword()
     if (verbose_listing && verbose > 1)
         printf("%02d %2d: ", get_ptr - start_ptr, 4);
 #endif
-    b0 = GET_BYTE();
-    b1 = GET_BYTE();
-    b2 = GET_BYTE();
-    b3 = GET_BYTE();
+    b0 = _get_byte();
+    b1 = _get_byte();
+    b2 = _get_byte();
+    b3 = _get_byte();
     l = (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
 #if DUMP_HEADER
     if (verbose_listing && verbose > 1)
@@ -152,8 +177,7 @@ get_longword()
 }
 
 static void
-put_longword(v)
-    long v;
+put_longword(long v)
 {
     put_byte(v);
     put_byte(v >> 8);
@@ -161,6 +185,50 @@ put_longword(v)
     put_byte(v >> 24);
 }
 
+#ifdef HAVE_UINT64_T
+static uint64_t
+get_longlongword()
+{
+    uint64_t b0, b1, b2, b3, b4, b5, b6, b7;
+    uint64_t l;
+
+#if DUMP_HEADER
+    if (verbose_listing && verbose > 1)
+        printf("%02d %2d: ", get_ptr - start_ptr, 4);
+#endif
+    b0 = _get_byte();
+    b1 = _get_byte();
+    b2 = _get_byte();
+    b3 = _get_byte();
+    b4 = _get_byte();
+    b5 = _get_byte();
+    b6 = _get_byte();
+    b7 = _get_byte();
+
+    l = (b7 << 24) + (b6 << 16) + (b5 << 8) + b4;
+    l <<= 32;
+    l |= (b3 << 24) + (b2 << 16) + (b1 << 8) + b0;
+#if DUMP_HEADER
+    if (verbose_listing && verbose > 1)
+        printf("%lld(%#016llx)\n", l, l);
+#endif
+    return l;
+}
+
+static void
+put_longlongword(uint64_t v)
+{
+    put_byte(v);
+    put_byte(v >> 8);
+    put_byte(v >> 16);
+    put_byte(v >> 24);
+    put_byte(v >> 32);
+    put_byte(v >> 40);
+    put_byte(v >> 48);
+    put_byte(v >> 56);
+}
+#endif
+
 static int
 get_bytes(buf, len, size)
     char *buf;
@@ -171,9 +239,13 @@ get_bytes(buf, len, size)
 #if DUMP_HEADER
     if (verbose_listing && verbose > 1)
         printf("%02d %2d: \"", get_ptr - start_ptr, len);
+    if (len < 0) {
+      error("Invalid header: %d", len);
+      exit(1);
+    }
 
-    for (i = 0; i < len; i++) {
-        if (i < size) buf[i] = get_ptr[i];
+    for (i = 0; i < len && i < size; i++) {
+        buf[i] = get_ptr[i];
 
         if (verbose_listing && verbose > 1) {
             if (isprint(buf[i]))
@@ -186,6 +258,10 @@ get_bytes(buf, len, size)
     if (verbose_listing && verbose > 1)
         printf("\"\n");
 #else
+    if (len < 0) {
+      error("Invalid header: %d", len);
+      exit(1);
+    }
     for (i = 0; i < len && i < size; i++)
         buf[i] = get_ptr[i];
 #endif
@@ -234,11 +310,34 @@ convert_filename(name, len, size,
         to_code_save = CODE_CAP;
         to_code = CODE_SJIS;
     }
+#endif
 
+    /* special case: if `name' has small lettter, not convert case. */
+    if (from_code == CODE_SJIS && case_to == TO_LOWER) {
+        for (i = 0; i < len; i++) {
+#ifdef MULTIBYTE_FILENAME
+            if (SJIS_FIRST_P(name[i]) && SJIS_SECOND_P(name[i+1]))
+                i++;
+            else
+#endif
+            if (islower(name[i])) {
+                case_to = NONE;
+                break;
+            }
+        }
+    }
+
+#ifdef MULTIBYTE_FILENAME
     if (from_code == CODE_SJIS && to_code == CODE_UTF8) {
-        for (i = 0; i < len; i++)
-            /* FIXME: provisionally fix for the Mac OS CoreFoundation */
-            if ((unsigned char)name[i] == LHA_PATHSEP)  name[i] = '/';
+        for (i = 0; i < len; i++) {
+            if (SJIS_FIRST_P(name[i]) && SJIS_SECOND_P(name[i+1]))
+                i++;
+            else {
+                /* FIXME: provisionally fix for the Mac OS CoreFoundation */
+                if (strchr(from_delim, name[i]))
+                    name[i] = '/';
+            }
+        }
         sjis_to_utf8(tmp, name, sizeof(tmp));
         strncpy(name, tmp, size);
         name[size-1] = 0;
@@ -261,21 +360,6 @@ convert_filename(name, len, size,
     }
 #endif
 
-    /* special case: if `name' has small lettter, not convert case. */
-    if (from_code == CODE_SJIS && case_to == TO_LOWER) {
-        for (i = 0; i < len; i++) {
-#ifdef MULTIBYTE_FILENAME
-            if (SJIS_FIRST_P(name[i]) && SJIS_SECOND_P(name[i+1]))
-                i++;
-            else
-#endif
-            if (islower(name[i])) {
-                case_to = NONE;
-                break;
-            }
-        }
-    }
-
     for (i = 0; i < len; i ++) {
 #ifdef MULTIBYTE_FILENAME
         if (from_code == CODE_EUC &&
@@ -499,7 +583,11 @@ get_extended_header(fp, hdr, header_size, hcrc)
     name_length = strlen(hdr->name);
 
     while (header_size) {
-        setup_get(data);
+#if DUMP_HEADER
+        if (verbose_listing && verbose > 1)
+            printf("---\n");
+#endif
+        setup_get(data, sizeof(data));
         if (sizeof(data) < header_size) {
             error("header size (%ld) too large.", header_size);
             exit(1);
@@ -513,6 +601,9 @@ get_extended_header(fp, hdr, header_size, hcrc)
         ext_type = get_byte();
         switch (ext_type) {
         case 0:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < header crc >\n");
+#endif
             /* header crc (CRC-16) */
             hdr->header_crc = get_word();
             /* clear buffer for CRC calculation. */
@@ -520,21 +611,33 @@ get_extended_header(fp, hdr, header_size, hcrc)
             skip_bytes(header_size - n - 2);
             break;
         case 1:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < filename >\n");
+#endif
             /* filename */
             name_length =
                 get_bytes(hdr->name, header_size-n, sizeof(hdr->name)-1);
             hdr->name[name_length] = 0;
             break;
         case 2:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < directory >\n");
+#endif
             /* directory */
             dir_length = get_bytes(dirname, header_size-n, sizeof(dirname)-1);
             dirname[dir_length] = 0;
             break;
         case 0x40:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < MS-DOS attribute >\n");
+#endif
             /* MS-DOS attribute */
             hdr->attribute = get_word();
             break;
         case 0x41:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < Windows time stamp (FILETIME) >\n");
+#endif
             /* Windows time stamp (FILETIME structure) */
             /* it is time in 100 nano seconds since 1601-01-01 00:00:00 */
 
@@ -549,26 +652,55 @@ get_extended_header(fp, hdr, header_size, hcrc)
             skip_bytes(8); /* last access time is ignored */
 
             break;
+        case 0x42:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < 64bits file size header >\n");
+#endif
+#ifdef HAVE_UINT64_T
+            /* 64bits file size header (UNLHA32 extension) */
+            hdr->packed_size = get_longlongword();
+            hdr->original_size = get_longlongword();
+#else
+            skip_bytes(8);
+            skip_bytes(8);
+#endif
+
+            break;
         case 0x50:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX permission >\n");
+#endif
             /* UNIX permission */
             hdr->unix_mode = get_word();
             break;
         case 0x51:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX gid and uid >\n");
+#endif
             /* UNIX gid and uid */
             hdr->unix_gid = get_word();
             hdr->unix_uid = get_word();
             break;
         case 0x52:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX group name >\n");
+#endif
             /* UNIX group name */
             i = get_bytes(hdr->group, header_size-n, sizeof(hdr->group)-1);
             hdr->group[i] = '\0';
             break;
         case 0x53:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX user name >\n");
+#endif
             /* UNIX user name */
             i = get_bytes(hdr->user, header_size-n, sizeof(hdr->user)-1);
             hdr->user[i] = '\0';
             break;
         case 0x54:
+#if DUMP_HEADER
+            if (verbose_listing && verbose > 1) printf("     < UNIX last modifed time (time_t) >\n");
+#endif
             /* UNIX last modified time */
             hdr->unix_last_modified_stamp = (time_t) get_longword();
             break;
@@ -688,6 +820,7 @@ get_header_level0(fp, hdr, data)
     char *data;
 {
     size_t header_size;
+    ssize_t remain_size;
     ssize_t extend_size;
     int checksum;
     int name_length;
@@ -697,8 +830,14 @@ get_header_level0(fp, hdr, data)
     hdr->header_size = header_size = get_byte();
     checksum = get_byte();
 
-    if (fread(data + COMMON_HEADER_SIZE,
-              header_size + 2 - COMMON_HEADER_SIZE, 1, fp) == 0) {
+    /* The data variable has been already read as COMMON_HEADER_SIZE bytes.
+       So we must read the remaining header size by the header_size. */
+    remain_size = header_size + 2 - COMMON_HEADER_SIZE;
+    if (remain_size <= 0) {
+        error("Invalid header size (LHarc file ?)");
+        return FALSE;
+    }
+    if (fread(data + COMMON_HEADER_SIZE, remain_size, 1, fp) == 0) {
         error("Invalid header (LHarc file ?)");
         return FALSE;   /* finish */
     }
@@ -709,8 +848,8 @@ get_header_level0(fp, hdr, data)
     }
 
     get_bytes(hdr->method, 5, sizeof(hdr->method));
-    hdr->packed_size = get_longword();
-    hdr->original_size = get_longword();
+    hdr->packed_size = (unsigned long)get_longword();
+    hdr->original_size = (unsigned long)get_longword();
     hdr->unix_last_modified_stamp = generic_to_unix_stamp(get_longword());
     hdr->attribute = get_byte(); /* MS-DOS attribute */
     hdr->header_level = get_byte();
@@ -804,6 +943,7 @@ get_header_level1(fp, hdr, data)
     char *data;
 {
     size_t header_size;
+    ssize_t remain_size;
     ssize_t extend_size;
     int checksum;
     int name_length;
@@ -813,8 +953,14 @@ get_header_level1(fp, hdr, data)
     hdr->header_size = header_size = get_byte();
     checksum = get_byte();
 
-    if (fread(data + COMMON_HEADER_SIZE,
-              header_size + 2 - COMMON_HEADER_SIZE, 1, fp) == 0) {
+    /* The data variable has been already read as COMMON_HEADER_SIZE bytes.
+       So we must read the remaining header size by the header_size. */
+    remain_size = header_size + 2 - COMMON_HEADER_SIZE;
+    if (remain_size <= 0) {
+        error("Invalid header size (LHarc file ?)");
+        return FALSE;
+    }
+    if (fread(data + COMMON_HEADER_SIZE, remain_size, 1, fp) == 0) {
         error("Invalid header (LHarc file ?)");
         return FALSE;   /* finish */
     }
@@ -825,8 +971,8 @@ get_header_level1(fp, hdr, data)
     }
 
     get_bytes(hdr->method, 5, sizeof(hdr->method));
-    hdr->packed_size = get_longword(); /* skip size */
-    hdr->original_size = get_longword();
+    hdr->packed_size = (unsigned long)get_longword(); /* skip size */
+    hdr->original_size = (unsigned long)get_longword();
     hdr->unix_last_modified_stamp = generic_to_unix_stamp(get_longword());
     hdr->attribute = get_byte(); /* 0x20 fixed */
     hdr->header_level = get_byte();
@@ -898,6 +1044,7 @@ get_header_level2(fp, hdr, data)
     char *data;
 {
     size_t header_size;
+    ssize_t remain_size;
     ssize_t extend_size;
     int padding;
     unsigned int hcrc;
@@ -905,6 +1052,13 @@ get_header_level2(fp, hdr, data)
     hdr->size_field_length = 2; /* in bytes */
     hdr->header_size = header_size = get_word();
 
+    /* The data variable has been already read as COMMON_HEADER_SIZE bytes.
+       So we must read the remaining header size without ext-header. */
+    remain_size = header_size - I_LEVEL2_HEADER_SIZE;
+    if (remain_size < 0) {
+        error("Invalid header size (LHarc file ?)");
+        return FALSE;
+    }
     if (fread(data + COMMON_HEADER_SIZE,
               I_LEVEL2_HEADER_SIZE - COMMON_HEADER_SIZE, 1, fp) == 0) {
         error("Invalid header (LHarc file ?)");
@@ -912,8 +1066,8 @@ get_header_level2(fp, hdr, data)
     }
 
     get_bytes(hdr->method, 5, sizeof(hdr->method));
-    hdr->packed_size = get_longword();
-    hdr->original_size = get_longword();
+    hdr->packed_size = (unsigned long)get_longword();
+    hdr->original_size = (unsigned long)get_longword();
     hdr->unix_last_modified_stamp = get_longword();
     hdr->attribute = get_byte(); /* reserved */
     hdr->header_level = get_byte();
@@ -936,7 +1090,12 @@ get_header_level2(fp, hdr, data)
         return FALSE;
 
     padding = header_size - I_LEVEL2_HEADER_SIZE - extend_size;
-    while (padding--)           /* padding should be 0 or 1 */
+    /* padding should be 0 or 1 */
+    if (padding != 0 && padding != 1) {
+        error("Invalid header size (padding: %d)", padding);
+        return FALSE;
+    }
+    while (padding--)
         hcrc = UPDATE_CRC(hcrc, fgetc(fp));
 
     if (hdr->header_crc != hcrc)
@@ -979,6 +1138,7 @@ get_header_level3(fp, hdr, data)
     char *data;
 {
     size_t header_size;
+    ssize_t remain_size;
     ssize_t extend_size;
     int padding;
     unsigned int hcrc;
@@ -992,8 +1152,8 @@ get_header_level3(fp, hdr, data)
     }
 
     get_bytes(hdr->method, 5, sizeof(hdr->method));
-    hdr->packed_size = get_longword();
-    hdr->original_size = get_longword();
+    hdr->packed_size = (unsigned long)get_longword();
+    hdr->original_size = (unsigned long)get_longword();
     hdr->unix_last_modified_stamp = get_longword();
     hdr->attribute = get_byte(); /* reserved */
     hdr->header_level = get_byte();
@@ -1007,6 +1167,11 @@ get_header_level3(fp, hdr, data)
     hdr->crc = get_word();
     hdr->extend_type = get_byte();
     hdr->header_size = header_size = get_longword();
+    remain_size = header_size - I_LEVEL3_HEADER_SIZE;
+    if (remain_size < 0) {
+        error("Invalid header size (LHarc file ?)");
+        return FALSE;
+    }
     extend_size = get_longword();
 
     INITIALIZE_CRC(hcrc);
@@ -1016,9 +1181,12 @@ get_header_level3(fp, hdr, data)
     if (extend_size == -1)
         return FALSE;
 
-    padding = header_size - I_LEVEL3_HEADER_SIZE - extend_size;
-    while (padding--)           /* padding should be 0 */
-        hcrc = UPDATE_CRC(hcrc, fgetc(fp));
+    padding = remain_size - extend_size;
+    /* padding should be 0 */
+    if (padding != 0) {
+        error("Invalid header size (padding: %d)", padding);
+        return FALSE;
+    }
 
     if (hdr->header_crc != hcrc)
         error("header CRC error");
@@ -1043,7 +1211,7 @@ get_header(fp, hdr)
 
     memset(hdr, 0, sizeof(LzHeader));
 
-    setup_get(data);
+    setup_get(data, sizeof(data));
 
     if ((end_mark = getc(fp)) == EOF || end_mark == 0) {
         return FALSE;           /* finish */
@@ -1148,9 +1316,11 @@ seek_lha_header(fp)
     n = fread(buffer, 1, sizeof(buffer), fp);
 
     for (p = buffer; p < buffer + n; p++) {
-        if (! (p[I_METHOD]=='-' && p[I_METHOD+1]=='l' && p[I_METHOD+4]=='-'))
+        if (! (p[I_METHOD]=='-' &&
+               (p[I_METHOD+1]=='l' || p[I_METHOD+1]=='p') &&
+               p[I_METHOD+4]=='-'))
             continue;
-        /* found "-l??-" keyword (as METHOD type string) */
+        /* found "-[lp]??-" keyword (as METHOD type string) */
 
         /* level 0 or 1 header */
         if ((p[I_HEADER_LEVEL] == 0 || p[I_HEADER_LEVEL] == 1)
@@ -1236,11 +1406,16 @@ copy_path_element(char *dst, const char *src, int size)
     return i;
 }
 
-/* remove leading "xxx/../" and "./" */
+/*
+  canonicalize path
+
+  remove leading "xxx/../"
+  remove "./", "././", "././ ... ./"
+  remove duplicated "/"
+*/
 static int
-remove_dots(char *newpath, char *path, size_t size)
+canon_path(char *newpath, char *path, size_t size)
 {
-    int len;
     char *p = newpath;
 
     path = remove_leading_dots(path);
@@ -1258,6 +1433,9 @@ remove_dots(char *newpath, char *path, size_t size)
             if (size <= 1)
                 break;
         }
+
+        /* remove duplicated '/' */
+        while (*path == '/') path++;
     }
 
     /* When newpath is empty, set "." */
@@ -1288,7 +1466,7 @@ init_header(name, v_stat, hdr)
     hdr->attribute = GENERIC_ATTRIBUTE;
     hdr->header_level = header_level;
 
-    len = remove_dots(hdr->name, name, sizeof(hdr->name));
+    len = canon_path(hdr->name, name, sizeof(hdr->name));
 
     hdr->crc = 0x0000;
     hdr->extend_type = EXTEND_UNIX;
@@ -1909,9 +2087,9 @@ utf8_to_sjis(char *dst, const char *src, size_t dstsize)
 }
 
 /*
- * SJIS <-> EUC ÊÑ´¹´Ø¿ô
- * ¡ÖÆüËܸì¾ðÊó½èÍý¡×   ¥½¥Õ¥È¥Ð¥ó¥¯(³ô)
- *  ¤è¤êÈ´¿è(by Koji Arai)
+ * SJIS <-> EUC 変換関数
+ * 「日本語情報処理」   ソフトバンク(株)
+ *  より抜粋(by Koji Arai)
  */
 void
 euc2sjis(int *p1, int *p2)