1efea58
From 22700942fec895b2d3e5ed6741756deb8666eaae Mon Sep 17 00:00:00 2001
1efea58
From: Daniel Axtens <dja@axtens.net>
1efea58
Date: Tue, 4 Dec 2018 00:55:22 +1100
1efea58
Subject: [PATCH] rar: file split across multi-part archives must match
1efea58
1efea58
Fuzzing uncovered some UAF and memory overrun bugs where a file in a
1efea58
single file archive reported that it was split across multiple
1efea58
volumes. This was caused by ppmd7 operations calling
1efea58
rar_br_fillup. This would invoke rar_read_ahead, which would in some
1efea58
situations invoke archive_read_format_rar_read_header.  That would
1efea58
check the new file name against the old file name, and if they didn't
1efea58
match up it would free the ppmd7 buffer and allocate a new
1efea58
one. However, because the ppmd7 decoder wasn't actually done with the
1efea58
buffer, it would continue to used the freed buffer. Both reads and
1efea58
writes to the freed region can be observed.
1efea58
1efea58
This is quite tricky to solve: once the buffer has been freed it is
1efea58
too late, as the ppmd7 decoder functions almost universally assume
1efea58
success - there's no way for ppmd_read to signal error, nor are there
1efea58
good ways for functions like Range_Normalise to propagate them. So we
1efea58
can't detect after the fact that we're in an invalid state - e.g. by
1efea58
checking rar->cursor, we have to prevent ourselves from ever ending up
1efea58
there. So, when we are in the dangerous part or rar_read_ahead that
1efea58
assumes a valid split, we set a flag force read_header to either go
1efea58
down the path for split files or bail. This means that the ppmd7
1efea58
decoder keeps a valid buffer and just runs out of data.
1efea58
1efea58
Found with a combination of AFL, afl-rb and qsym.
1efea58
---
1efea58
 libarchive/archive_read_support_format_rar.c | 9 +++++++++
1efea58
 1 file changed, 9 insertions(+)
1efea58
1efea58
diff --git a/libarchive/archive_read_support_format_rar.c b/libarchive/archive_read_support_format_rar.c
1efea58
index 6f419c27..a8cc5c94 100644
1efea58
--- a/libarchive/archive_read_support_format_rar.c
1efea58
+++ b/libarchive/archive_read_support_format_rar.c
1efea58
@@ -258,6 +258,7 @@ struct rar
1efea58
   struct data_block_offsets *dbo;
1efea58
   unsigned int cursor;
1efea58
   unsigned int nodes;
1efea58
+  char filename_must_match;
1efea58
 
1efea58
   /* LZSS members */
1efea58
   struct huffman_code maincode;
1efea58
@@ -1560,6 +1561,12 @@ read_header(struct archive_read *a, struct archive_entry *entry,
1efea58
     }
1efea58
     return ret;
1efea58
   }
1efea58
+  else if (rar->filename_must_match)
1efea58
+  {
1efea58
+    archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
1efea58
+      "Mismatch of file parts split across multi-volume archive");
1efea58
+    return (ARCHIVE_FATAL);
1efea58
+  }
1efea58
 
1efea58
   rar->filename_save = (char*)realloc(rar->filename_save,
1efea58
                                       filename_size + 1);
1efea58
@@ -2933,12 +2940,14 @@ rar_read_ahead(struct archive_read *a, size_t min, ssize_t *avail)
1efea58
     else if (*avail == 0 && rar->main_flags & MHD_VOLUME &&
1efea58
       rar->file_flags & FHD_SPLIT_AFTER)
1efea58
     {
1efea58
+      rar->filename_must_match = 1;
1efea58
       ret = archive_read_format_rar_read_header(a, a->entry);
1efea58
       if (ret == (ARCHIVE_EOF))
1efea58
       {
1efea58
         rar->has_endarc_header = 1;
1efea58
         ret = archive_read_format_rar_read_header(a, a->entry);
1efea58
       }
1efea58
+      rar->filename_must_match = 0;
1efea58
       if (ret != (ARCHIVE_OK))
1efea58
         return NULL;
1efea58
       return rar_read_ahead(a, min, avail);
1efea58
-- 
1efea58
2.17.1
1efea58