CVE-2017-20006 (C++, published 2021-07-01T03:15Z)
Description: value: 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 v3 base score: 7.8)
Weakness: CWE-787 \u2014 Out-of-bounds Write
The product writes data past the end, or before the beginning, of the intended buffer.
Repository: https://github.com/aawc/unrar
Files changed:
arccmt.cpp
archive.cpp
archive.hpp
arcmem.cpp
arcmem.hpp
arcread.cpp
cmddata.cpp
compress.hpp
file.hpp
makefile
options.hpp
qopen.cpp
rar.cpp
rar.hpp
rardefs.hpp
recvol5.cpp
unicode.cpp
unpack.hpp
unpack30.cpp
unpack50.cpp
unpack50mt.cpp
unpackinline.cpp
Commit message: 5.6.1.4: Use in-memory archive instead of file IO


Vulnerable code:
    CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtLength);
#ifdef USE_QOPEN
  size_t Result;
  if (QOpen.Read(Data,Size,Result))
    return (int)Result;
  if (!QOpen.Seek(Offset,Method))
    File::Seek(Offset,Method);
#endif
#ifdef USE_QOPEN
        if (MainHead.Locator && MainHead.QOpenOffset>0 && Cmd->QOpenMode!=QOPEN_NONE)
              wcsncatz(FileHead.FileName,VerText,ASIZE(FileHead.FileName));
      wcsncpy(Command,Arg,ASIZE(Command));
  if (wcslen(FileHead.FileName)>=NM)
    return 0;
  if (wcschr(L"AFUMD",*Command)==NULL)
    bool Close();
    bool IsOpened() {return hFile!=FILE_BAD_HANDLE;};
CXXFLAGS=-O2
#ifdef USE_QOPEN
  if (!Loaded) // If loading the first time, perform additional intialization.
    if (Arc->ReadHeader()==0 || Arc->GetHeaderType()!=HEAD_SERVICE ||
      if (wcrtomb(Dest+DestPos,Src[SrcPos],&ps)==-1)
    if (mbrtowc(Dest+DestPos,Src+SrcPos,MB_CUR_MAX,&ps)==-1)
    bool AddVMCode(uint FirstByte,byte *Code,int CodeSize);
bool Unpack::AddVMCode(uint FirstByte,byte *Code,int CodeSize)
    if (VMCodeSize>=0x10000 || VMCodeSize==0)
    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_LZ_MATCH+3 && WriteBorder!=UnpPtr)
    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_LZ_MATCH+3 && WriteBorder!=UnpPtr)
    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_LZ_MATCH+3 && WriteBorder!=UnpPtr)
  if (SrcPtr<MaxWinSize-MAX_LZ_MATCH && UnpPtr<MaxWinSize-MAX_LZ_MATCH)
        // But for real RAR archives Distance <= MaxWinSize - MAX_LZ_MATCH

Fixed code:
    CharToWide((char *)&CmtRaw[0],CmtData->Addr(0),CmtData->Size());
#ifdef USE_ARCMEM
#include "arcmem.cpp"
#endif

#ifdef USE_QOPEN
  ProhibitQOpen=false;
#endif

#ifdef USE_QOPEN
#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

bool Archive::Close()
{
#ifdef USE_ARCMEM
  if (ArcMem.Unload())
    return true;
#endif
  return File::Close();
}



#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
#ifdef USE_QOPEN
  if (QOpen.Seek(Offset,Method))
    return;
#endif
#ifdef USE_ARCMEM
  if (ArcMem.Seek(Offset,Method))
    return;
#endif
  File::Seek(Offset,Method);
#ifdef USE_QOPEN
#endif
#ifdef USE_ARCMEM
  int64 APos;
  if (ArcMem.Tell(&APos))
    return APos;
#endif


bool Archive::IsOpened()
{
#ifdef USE_ARCMEM
  if (ArcMem.IsLoaded())
    return true;
#endif
  return File::IsOpened();
};
    bool ProhibitQOpen;
#endif
#ifdef USE_ARCMEM
    ArcMemory ArcMem;
    bool Close();
    bool IsOpened();
#ifdef USE_QOPEN
    void SetProhibitQOpen(bool Mode) {ProhibitQOpen=Mode;}
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;
}
#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
        if (!ProhibitQOpen && MainHead.Locator && MainHead.QOpenOffset>0 && Cmd->QOpenMode!=QOPEN_NONE)
              wcsncatz(hd->FileName,VerText,ASIZE(hd->FileName));
      wcsncpyz(Command,Arg,ASIZE(Command));
//  if (wcslen(FileHead.FileName)>=NM)
//    return 0;
  if (wcschr(L"AFUMD",*Command)==NULL && !ArcInMem)
    // Maximum LZ match length we can encode even for short distances.

    // 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;

    virtual bool Close();
    virtual bool IsOpened() {return hFile!=FILE_BAD_HANDLE;};
CXXFLAGS=-O2 -Wno-logical-op-parentheses -Wno-switch -Wno-dangling-else

#ifdef USE_QOPEN

    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

  if (!Loaded)
    // If loading for the first time, perform additional intialization.

    // 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 ||
/*
    byte Buf[10000];
    File Src;
    Src.TOpen(L"123.rar");
    int Size=Src.Read(Buf,sizeof(Buf));
    Cmd->SetArcInMem(Buf,Size);
*/
#ifdef USE_ARCMEM
#include "arcmem.hpp"
#endif
#define USE_ARCMEM
  if (Num==ArcName)
    return false; // Entire volume name is numeric, not possible for REV file.
      if (wcrtomb(Dest+DestPos,Src[SrcPos],&ps)==(size_t)-1)
    size_t res=mbrtowc(Dest+DestPos,Src+SrcPos,MB_CUR_MAX,&ps);
    if (res==(size_t)-1 || res==(size_t)-2)
    bool AddVMCode(uint FirstByte,byte *Code,uint CodeSize);
bool Unpack::AddVMCode(uint FirstByte,byte *Code,uint CodeSize)
    if (VMCodeSize>=0x10000 || VMCodeSize==0 || VMCodeInp.InAddr+VMCodeSize>CodeSize)
    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
    if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
  if (SrcPtr<MaxWinSize-MAX_INC_LZ_MATCH && UnpPtr<MaxWinSize-MAX_INC_LZ_MATCH)
        // But for real RAR archives Distance <= MaxWinSize - MAX_INC_LZ_MATCH