CVE-2017-20006 (published 2021-07-01T03:15Z)

UnRAR 5.6.1.2 and 5.6.1.3 has a heap-based buffer overflow in Unpack::CopyString (called from Unpack::Unpack5 and CmdExtract::ExtractCurrentFile).

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 7.8, HIGH (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-787
cwe_name: Out-of-bounds Write
description: The product writes data past the end, or before the beginning, of the intended buffer.
extended_description: 
            
            
             
			 
         
         

Fix information:
repo_url: https://github.com/aawc/unrar
commit_details: repo_url: https://github.com/aawc/unrar
author: Varun Khaneja
author_date: 2017-11-29 14:39:10-08:00
msg: 5.6.1.4: Use in-memory archive instead of file IO
repository: repo_url: https://github.com/aawc/unrar
repo_name: aawc/unrar
description: A copy of the unrar library from https://www.rarlab.com/
repo_language: C++
owner: aawc
file_changes: filename: arccmt.cpp
change_type: ModificationType.MODIFY
diff: @@ -120,7 +120,7 @@ bool Archive::GetComment(Array<wchar> *CmtData)
     // 4x memory for OEM to UTF-8 output here.
     OemToCharA((char *)&CmtRaw[0],(char *)&CmtRaw[0]);
 #endif
-    CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtLength);
+    CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size());
     CmtData->Alloc(wcslen(CmtData->Addr(0)));
   }
 #endif

programming_language: C++
filename: archive.cpp
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,10 @@
 #include "arccmt.cpp"
 
 
+#ifdef USE_ARCMEM
+#include "arcmem.cpp"
+#endif
+
 Archive::Archive(RAROptions *InitCmd)
 {
   Cmd=NULL; // Just in case we'll have an exception in 'new' below.
@@ -48,6 +52,10 @@ Archive::Archive(RAROptions *InitCmd)
 
   SilentOpen=false;
 
+#ifdef USE_QOPEN
+  ProhibitQOpen=false;
+#endif
+
 }
 
 
@@ -294,39 +302,92 @@ uint Archive::FullHeaderSize(size_t Size)
 
 
 
-#ifdef USE_QOPEN
 bool Archive::Open(const wchar *Name,uint Mode)
 {
+#ifdef USE_QOPEN
   // Important if we reuse Archive object and it has virtual QOpen
   // file position not matching real. For example, for 'l -v volname'.
   QOpen.Unload();
+#endif
+
+#ifdef USE_ARCMEM
+  if (Cmd->ArcInMem)
+  {
+    wcsncpyz(FileName,Name,ASIZE(FileName));
+    ArcMem.Load(Cmd->ArcMemData,Cmd->ArcMemSize);
+    Cmd->SetArcInMem(NULL,0); // Return in memory data for first volume only, not for next volumes.
+    return true;
+  }
+#endif
 
   return File::Open(Name,Mode);
 }
 
 
+
+bool Archive::Close()
+{
+#ifdef USE_ARCMEM
+  if (ArcMem.Unload())
+    return true;
+#endif
+  return File::Close();
+}
+
+
+
 int Archive::Read(void *Data,size_t Size)
 {
-  size_t Result;
-  if (QOpen.Read(Data,Size,Result))
-    return (int)Result;
+#ifdef USE_QOPEN
+  size_t QResult;
+  if (QOpen.Read(Data,Size,QResult))
+    return (int)QResult;
+#endif
+#ifdef USE_ARCMEM
+  size_t AResult;
+  if (ArcMem.Read(Data,Size,AResult))
+    return (int)AResult;
+#endif
   return File::Read(Data,Size);
 }
 
 
 void Archive::Seek(int64 Offset,int Method)
 {
-  if (!QOpen.Seek(Offset,Method))
-    File::Seek(Offset,Method);
+#ifdef USE_QOPEN
+  if (QOpen.Seek(Offset,Method))
+    return;
+#endif
+#ifdef USE_ARCMEM
+  if (ArcMem.Seek(Offset,Method))
+    return;
+#endif
+  File::Seek(Offset,Method);
 }
 
 
 int64 Archive::Tell()
 {
+#ifdef USE_QOPEN
   int64 QPos;
   if (QOpen.Tell(&QPos))
     return QPos;
+#endif
+#ifdef USE_ARCMEM
+  int64 APos;
+  if (ArcMem.Tell(&APos))
+    return APos;
+#endif
   return File::Tell();
 }
-#endif
 
+
+
+bool Archive::IsOpened()
+{
+#ifdef USE_ARCMEM
+  if (ArcMem.IsLoaded())
+    return true;
+#endif
+  return File::IsOpened();
+};

programming_language: C++
filename: archive.hpp
change_type: ModificationType.MODIFY
diff: @@ -55,6 +55,10 @@ class Archive:public File
     bool SilentOpen;
 #ifdef USE_QOPEN
     QuickOpen QOpen;
+    bool ProhibitQOpen;
+#endif
+#ifdef USE_ARCMEM
+    ArcMemory ArcMem;
 #endif
   public:
     Archive(RAROptions *InitCmd=NULL);
@@ -89,12 +93,15 @@ class Archive:public File
 #if 0
     void GetRecoveryInfo(bool Required,int64 *Size,int *Percent);
 #endif
-#ifdef USE_QOPEN
     bool Open(const wchar *Name,uint Mode=FMF_READ);
+    bool Close();
     int Read(void *Data,size_t Size);
     void Seek(int64 Offset,int Method);
     int64 Tell();
+    bool IsOpened();
+#ifdef USE_QOPEN
     void QOpenUnload() {QOpen.Unload();}
+    void SetProhibitQOpen(bool Mode) {ProhibitQOpen=Mode;}
 #endif
 
     BaseBlock ShortBlock;

programming_language: C++
filename: arcmem.cpp
change_type: ModificationType.ADD
diff: @@ -0,0 +1,62 @@
+ArcMemory::ArcMemory()
+{
+  Loaded=false;
+  SeekPos=0;
+}
+
+
+void ArcMemory::Load(const byte *Data,size_t Size)
+{
+  ArcData.Alloc(Size);
+  memcpy(&ArcData[0],Data,Size);
+  Loaded=true;
+  SeekPos=0;
+}
+
+
+bool ArcMemory::Unload()
+{
+  if (!Loaded)
+    return false;
+  Loaded=false;
+  return true;
+}
+
+
+bool ArcMemory::Read(void *Data,size_t Size,size_t &Result)
+{
+  if (!Loaded)
+    return false;
+  Result=(size_t)Min(Size,ArcData.Size()-SeekPos);
+  memcpy(Data,&ArcData[(size_t)SeekPos],Result);
+  SeekPos+=Result;
+  return true;
+}
+
+
+bool ArcMemory::Seek(int64 Offset,int Method)
+{
+  if (!Loaded)
+    return false;
+  if (Method==SEEK_SET)
+    SeekPos=Min(Offset,ArcData.Size());
+  else
+    if (Method==SEEK_CUR || Method==SEEK_END)
+    {
+      if (Method==SEEK_END)
+        SeekPos=ArcData.Size();
+      SeekPos+=(uint64)Offset;
+      if (SeekPos>ArcData.Size())
+        SeekPos=Offset<0 ? 0 : ArcData.Size();
+    }
+  return true;
+}
+
+
+bool ArcMemory::Tell(int64 *Pos)
+{
+  if (!Loaded)
+    return false;
+  *Pos=SeekPos;
+  return true;
+}

programming_language: C++
filename: arcmem.hpp
change_type: ModificationType.ADD
diff: @@ -0,0 +1,22 @@
+#ifndef _RAR_ARCMEM_
+#define _RAR_ARCMEM_
+
+// Memory interface for software fuzzers.
+
+class ArcMemory
+{
+  private:
+    bool Loaded;
+    Array<byte> ArcData;
+    uint64 SeekPos;
+  public:
+    ArcMemory();
+    void Load(const byte *Data,size_t Size);
+    bool Unload();
+    bool IsLoaded() {return Loaded;}
+    bool Read(void *Data,size_t Size,size_t &Result);
+    bool Seek(int64 Offset,int Method);
+    bool Tell(int64 *Pos);
+};
+
+#endif

programming_language: C++
filename: arcread.cpp
change_type: ModificationType.MODIFY
diff: @@ -757,7 +757,7 @@ size_t Archive::ReadHeader50()
           ProcessExtra50(&Raw,(size_t)ExtraSize,&MainHead);
 
 #ifdef USE_QOPEN
-        if (MainHead.Locator && MainHead.QOpenOffset>0 && Cmd->QOpenMode!=QOPEN_NONE)
+        if (!ProhibitQOpen && MainHead.Locator && MainHead.QOpenOffset>0 && Cmd->QOpenMode!=QOPEN_NONE)
         {
           // We seek to QO block in the end of archive when processing
           // QOpen.Load, so we need to preserve current block positions
@@ -1091,7 +1091,7 @@ void Archive::ProcessExtra50(RawRead *Raw,size_t ExtraSize,BaseBlock *bb)
 
               wchar VerText[20];
               swprintf(VerText,ASIZE(VerText),L";%u",Version);
-              wcsncatz(FileHead.FileName,VerText,ASIZE(FileHead.FileName));
+              wcsncatz(hd->FileName,VerText,ASIZE(hd->FileName));
             }
           }
           break;

programming_language: C++
filename: cmddata.cpp
change_type: ModificationType.MODIFY
diff: @@ -97,7 +97,7 @@ void CommandData::ParseArg(wchar *Arg)
   else
     if (*Command==0)
     {
-      wcsncpy(Command,Arg,ASIZE(Command));
+      wcsncpyz(Command,Arg,ASIZE(Command));
 
 
       *Command=toupperw(*Command);
@@ -1194,8 +1194,8 @@ int CommandData::IsProcessFile(FileHeader &FileHead,bool *ExactMatch,int MatchTy
 {
   if (MatchedArg!=NULL && MatchedArgSize>0)
     *MatchedArg=0;
-  if (wcslen(FileHead.FileName)>=NM)
-    return 0;
+//  if (wcslen(FileHead.FileName)>=NM)
+//    return 0;
   bool Dir=FileHead.Dir;
   if (ExclCheck(FileHead.FileName,Dir,false,true))
     return 0;
@@ -1249,7 +1249,7 @@ void CommandData::ProcessCommand()
       wcsncpyz(ArcName,Name,ASIZE(ArcName));
   }
 
-  if (wcschr(L"AFUMD",*Command)==NULL)
+  if (wcschr(L"AFUMD",*Command)==NULL && !ArcInMem)
   {
     if (GenerateArcName)
       GenerateArchiveName(ArcName,ASIZE(ArcName),GenerateMask,false);

programming_language: C++
filename: compress.hpp
change_type: ModificationType.MODIFY
diff: @@ -6,7 +6,16 @@
 class PackDef
 {
   public:
+    // Maximum LZ match length we can encode even for short distances.
     static const uint MAX_LZ_MATCH = 0x1001;
+
+    // We increment LZ match length for longer distances, because shortest
+    // matches are not allowed for them. Maximum length increment is 3
+    // for distances larger than 256KB (0x40000). Here we define the maximum
+    // incremented LZ match. Normally packer does not use it, but we must be
+    // ready to process it in corrupt archives.
+    static const uint MAX_INC_LZ_MATCH = MAX_LZ_MATCH + 3;
+
     static const uint MAX3_LZ_MATCH = 0x101; // Maximum match length for RAR v3.
     static const uint LOW_DIST_REP_COUNT = 16;
 

programming_language: C
filename: file.hpp
change_type: ModificationType.MODIFY
diff: @@ -78,7 +78,7 @@ class File
     bool Create(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD);
     void TCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD);
     bool WCreate(const wchar *Name,uint Mode=FMF_UPDATE|FMF_SHAREREAD);
-    bool Close();
+    virtual bool Close();
     bool Delete();
     bool Rename(const wchar *NewName);
     bool Write(const void *Data,size_t Size);
@@ -96,7 +96,7 @@ class File
     void SetCloseFileTime(RarTime *ftm,RarTime *fta=NULL);
     static void SetCloseFileTimeByName(const wchar *Name,RarTime *ftm,RarTime *fta);
     void GetOpenFileTime(RarTime *ft);
-    bool IsOpened() {return hFile!=FILE_BAD_HANDLE;};
+    virtual bool IsOpened() {return hFile!=FILE_BAD_HANDLE;};
     int64 FileLength();
     void SetHandleType(FILE_HANDLETYPE Type) {HandleType=Type;}
     FILE_HANDLETYPE GetHandleType() {return HandleType;}

programming_language: C++
filename: makefile
change_type: ModificationType.MODIFY
diff: @@ -3,7 +3,7 @@
 
 # Linux using GCC
 CXX=c++
-CXXFLAGS=-O2
+CXXFLAGS=-O2 -Wno-logical-op-parentheses -Wno-switch -Wno-dangling-else
 LIBFLAGS=-fPIC
 DEFINES=-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -DRAR_SMP
 STRIP=strip

programming_language: Shell
filename: options.hpp
change_type: ModificationType.MODIFY
diff: @@ -89,10 +89,24 @@ class RAROptions
     bool InclAttrSet;
     size_t WinSize;
     wchar TempPath[NM];
-#ifdef USE_QOPEN
     wchar SFXModule[NM];
+
+#ifdef USE_QOPEN
     QOPEN_MODE QOpenMode;
 #endif
+
+    bool ArcInMem;
+#ifdef USE_ARCMEM
+    void SetArcInMem(byte *Data,size_t Size)
+    {
+      ArcMemData=Data;
+      ArcMemSize=Size;
+      ArcInMem=Data!=NULL && Size>0;
+    }
+    byte *ArcMemData;
+    size_t ArcMemSize;
+#endif
+
     bool ConfigDisabled; // Switch -cfg-.
     wchar ExtrPath[NM];
     wchar CommentFile[NM];

programming_language: C++
filename: qopen.cpp
change_type: ModificationType.MODIFY
diff: @@ -61,14 +61,27 @@ void QuickOpen::Close()
 
 void QuickOpen::Load(uint64 BlockPos)
 {
-  if (!Loaded) // If loading the first time, perform additional intialization.
+  if (!Loaded)
   {
+    // If loading for the first time, perform additional intialization.
     SeekPos=Arc->Tell();
     UnsyncSeekPos=false;
 
     SaveFilePos SavePos(*Arc);
     Arc->Seek(BlockPos,SEEK_SET);
-    if (Arc->ReadHeader()==0 || Arc->GetHeaderType()!=HEAD_SERVICE ||
+
+    // If BlockPos points to original main header, we'll have the infinite
+    // recursion, because ReadHeader() for main header will attempt to load
+    // QOpen and call QuickOpen::Load again. If BlockPos points to long chain
+    // of other main headers, we'll have multiple recursive calls of this
+    // function wasting resources. So we prohibit QOpen temporarily to
+    // prevent this. ReadHeader() calls QOpen.Init and sets MainHead Locator
+    // and QOpenOffset fields, so we cannot use them to prohibit QOpen.
+    Arc->SetProhibitQOpen(true);
+    size_t ReadSize=Arc->ReadHeader();
+    Arc->SetProhibitQOpen(false);
+
+    if (ReadSize==0 || Arc->GetHeaderType()!=HEAD_SERVICE ||
         !Arc->SubHead.CmpName(SUBHEAD_TYPE_QOPEN))
       return;
     QLHeaderPos=Arc->CurBlockPos;

programming_language: C++
filename: rar.cpp
change_type: ModificationType.MODIFY
diff: @@ -75,6 +75,13 @@ int main(int argc, char *argv[])
     ErrHandler.SetSilent(Cmd->AllYes || Cmd->MsgStream==MSG_NULL);
 
     Cmd->OutTitle();
+/*
+    byte Buf[10000];
+    File Src;
+    Src.TOpen(L"123.rar");
+    int Size=Src.Read(Buf,sizeof(Buf));
+    Cmd->SetArcInMem(Buf,Size);
+*/
     Cmd->ProcessCommand();
     delete Cmd;
   }

programming_language: C++
filename: rar.hpp
change_type: ModificationType.MODIFY
diff: @@ -45,6 +45,9 @@
 #ifdef USE_QOPEN
 #include "qopen.hpp"
 #endif
+#ifdef USE_ARCMEM
+#include "arcmem.hpp"
+#endif
 #include "archive.hpp"
 #include "match.hpp"
 #include "cmddata.hpp"

programming_language: C++
filename: rardefs.hpp
change_type: ModificationType.MODIFY
diff: @@ -24,6 +24,7 @@
 #ifndef SFX_MODULE
 #define USE_QOPEN
 #endif
+#define USE_ARCMEM
 
 // Produce the value, which is equal or larger than 'v' and aligned to 'a'.
 #define ALIGN_VALUE(v,a) (size_t(v) + ( (~size_t(v) + 1) & (a - 1) ) )

programming_language: C
filename: recvol5.cpp
change_type: ModificationType.MODIFY
diff: @@ -143,6 +143,8 @@ bool RecVolumes5::Restore(RAROptions *Cmd,const wchar *Name,bool Silent)
     return false; // Number part is missing in the name.
   while (Num>ArcName && IsDigit(*(Num-1)))
     Num--;
+  if (Num==ArcName)
+    return false; // Entire volume name is numeric, not possible for REV file.
   wcsncpyz(Num,L"*.*",ASIZE(ArcName)-(Num-ArcName));
   
   wchar FirstVolName[NM];

programming_language: C++
filename: unicode.cpp
change_type: ModificationType.MODIFY
diff: @@ -155,7 +155,7 @@ bool WideToCharMap(const wchar *Src,char *Dest,size_t DestSize,bool &Success)
     {
       mbstate_t ps;
       memset(&ps,0,sizeof(ps));
-      if (wcrtomb(Dest+DestPos,Src[SrcPos],&ps)==-1)
+      if (wcrtomb(Dest+DestPos,Src[SrcPos],&ps)==(size_t)-1)
       {
         Dest[DestPos]='_';
         Success=false;
@@ -192,7 +192,8 @@ void CharToWideMap(const char *Src,wchar *Dest,size_t DestSize,bool &Success)
     }
     mbstate_t ps;
     memset(&ps,0,sizeof(ps));
-    if (mbrtowc(Dest+DestPos,Src+SrcPos,MB_CUR_MAX,&ps)==-1)
+    size_t res=mbrtowc(Dest+DestPos,Src+SrcPos,MB_CUR_MAX,&ps);
+    if (res==(size_t)-1 || res==(size_t)-2)
     {
       // For security reasons we do not want to map low ASCII characters,
       // so we do not have additional .. and path separator codes.

programming_language: C++
filename: unpack.hpp
change_type: ModificationType.MODIFY
diff: @@ -329,7 +329,7 @@ class Unpack:PackDef
     bool ReadEndOfBlock();
     bool ReadVMCode();
     bool ReadVMCodePPM();
-    bool AddVMCode(uint FirstByte,byte *Code,int CodeSize);
+    bool AddVMCode(uint FirstByte,byte *Code,uint CodeSize);
     int SafePPMDecodeChar();
     bool ReadTables30();
     bool UnpReadBuf30();

programming_language: C++
filename: unpack30.cpp
change_type: ModificationType.MODIFY
diff: @@ -358,7 +358,7 @@ bool Unpack::ReadVMCodePPM()
 }
 
 
-bool Unpack::AddVMCode(uint FirstByte,byte *Code,int CodeSize)
+bool Unpack::AddVMCode(uint FirstByte,byte *Code,uint CodeSize)
 {
   VMCodeInp.InitBitInput();
   memcpy(VMCodeInp.InBuf,Code,Min(BitInput::MAX_SIZE,CodeSize));
@@ -470,7 +470,7 @@ bool Unpack::AddVMCode(uint FirstByte,byte *Code,int CodeSize)
   if (NewFilter)
   {
     uint VMCodeSize=RarVM::ReadData(VMCodeInp);
-    if (VMCodeSize>=0x10000 || VMCodeSize==0)
+    if (VMCodeSize>=0x10000 || VMCodeSize==0 || VMCodeInp.InAddr+VMCodeSize>CodeSize)
       return false;
     Array<byte> VMCode(VMCodeSize);
     for (uint I=0;I<VMCodeSize;I++)

programming_language: C++
filename: unpack50.cpp
change_type: ModificationType.MODIFY
diff: @@ -42,7 +42,7 @@ void Unpack::Unpack5(bool Solid)
         break;
     }
 
-    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_LZ_MATCH+3 && WriteBorder!=UnpPtr)
+    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
     {
       UnpWriteBuf();
       if (WrittenFileSize>DestUnpSize)

programming_language: C++
filename: unpack50mt.cpp
change_type: ModificationType.MODIFY
diff: @@ -451,7 +451,7 @@ bool Unpack::ProcessDecoded(UnpackThreadData &D)
   while (Item<Border)
   {
     UnpPtr&=MaxWinMask;
-    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_LZ_MATCH+3 && WriteBorder!=UnpPtr)
+    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
     {
       UnpWriteBuf();
       if (WrittenFileSize>DestUnpSize)
@@ -559,7 +559,7 @@ bool Unpack::UnpackLargeBlock(UnpackThreadData &D)
         break;
       }
     }
-    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_LZ_MATCH+3 && WriteBorder!=UnpPtr)
+    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
     {
       UnpWriteBuf();
       if (WrittenFileSize>DestUnpSize)

programming_language: C++
filename: unpackinline.cpp
change_type: ModificationType.MODIFY
diff: @@ -13,7 +13,7 @@ _forceinline void Unpack::InsertOldDist(uint Distance)
 _forceinline void Unpack::CopyString(uint Length,uint Distance)
 {
   size_t SrcPtr=UnpPtr-Distance;
-  if (SrcPtr<MaxWinSize-MAX_LZ_MATCH && UnpPtr<MaxWinSize-MAX_LZ_MATCH)
+  if (SrcPtr<MaxWinSize-MAX_INC_LZ_MATCH && UnpPtr<MaxWinSize-MAX_INC_LZ_MATCH)
   {
     // If we are not close to end of window, we do not need to waste time
     // to "& MaxWinMask" pointer protection.
@@ -46,7 +46,7 @@ _forceinline void Unpack::CopyString(uint Length,uint Distance)
       {
         // In theory we still could overlap here.
         // Supposing Distance == MaxWinSize - 1 we have memcpy(Src, Src + 1, 8).
-        // But for real RAR archives Distance <= MaxWinSize - MAX_LZ_MATCH
+        // But for real RAR archives Distance <= MaxWinSize - MAX_INC_LZ_MATCH
         // always, so overlap here is impossible.
 
         // This memcpy expanded inline by MSVC. We could also use uint64

programming_language: C++