Description: Fix a symlink directory traversal vulnerability.
 Backported from version 5.2.7.
Bug-Debian: https://bugs.debian.org/774171
Bug-Ubuntu: https://bugs.launchpad.net/bugs/1451260

--- unrar-nonfree-5.0.10.orig/cmddata.cpp
+++ unrar-nonfree-5.0.10/cmddata.cpp
@@ -609,6 +609,8 @@ void CommandData::ProcessSwitch(const wc
 #ifdef SAVE_LINKS
         case 'L':
           SaveSymLinks=true;
+          if (toupperw(Switch[2])=='A')
+            AbsoluteLinks=true;
           break;
 #endif
         case 'R':
--- unrar-nonfree-5.0.10.orig/extinfo.cpp
+++ unrar-nonfree-5.0.10/extinfo.cpp
@@ -60,14 +60,45 @@ void SetExtraInfo(CommandData *Cmd,Archi
 
 
 
+bool IsRelativeSymlinkSafe(const wchar *SrcName,const wchar *TargetName)
+{
+  if (IsFullRootPath(SrcName))
+    return false;
+  int AllowedDepth=0;
+  while (*SrcName!=0)
+  {
+    if (IsPathDiv(SrcName[0]) && SrcName[1]!=0 && !IsPathDiv(SrcName[1]))
+    {
+      bool Dot=SrcName[1]=='.' && (IsPathDiv(SrcName[2]) || SrcName[2]==0);
+      bool Dot2=SrcName[1]=='.' && SrcName[2]=='.' && (IsPathDiv(SrcName[3]) || SrcName[3]==0);
+      if (!Dot && !Dot2)
+        AllowedDepth++;
+    }
+    SrcName++;
+  }
+  if (IsFullRootPath(TargetName)) // Catch root dir based /path/file paths.
+    return false;
+  for (int Pos=0;*TargetName!=0;Pos++)
+  {
+    bool Dot2=TargetName[0]=='.' && TargetName[1]=='.' && 
+              (IsPathDiv(TargetName[2]) || TargetName[2]==0) &&
+              (Pos==0 || IsPathDiv(*(TargetName-1)));
+    if (Dot2)
+      AllowedDepth--;
+    TargetName++;
+  }
+  return AllowedDepth>=0;
+}
+
+
 bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName)
 {
 #if defined(SAVE_LINKS) && defined(_UNIX)
   // For RAR 3.x archives we process links even in test mode to skip link data.
   if (Arc.Format==RARFMT15)
-    return ExtractUnixLink30(DataIO,Arc,LinkName);
+    return ExtractUnixLink30(Cmd,DataIO,Arc,LinkName);
   if (Arc.Format==RARFMT50)
-    return ExtractUnixLink50(LinkName,&Arc.FileHead);
+    return ExtractUnixLink50(Cmd,LinkName,&Arc.FileHead);
 #elif defined _WIN_ALL
   // RAR 5.0 archives store link information in file header, so there is
   // no need to additionally test it if we do not create a file.
--- unrar-nonfree-5.0.10.orig/extinfo.hpp
+++ unrar-nonfree-5.0.10/extinfo.hpp
@@ -1,6 +1,7 @@
 #ifndef _RAR_EXTINFO_
 #define _RAR_EXTINFO_
 
+bool IsRelativeSymlinkSafe(const wchar *SrcName,const wchar *TargetName);
 bool ExtractSymlink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName);
 #ifdef _UNIX
 void SetUnixOwner(Archive &Arc,const wchar *FileName);
--- unrar-nonfree-5.0.10.orig/loclang.hpp
+++ unrar-nonfree-5.0.10/loclang.hpp
@@ -100,7 +100,7 @@
 #define   MCHelpSwOC         "\n  oc            Set NTFS Compressed attribute"
 #define   MCHelpSwOH         "\n  oh            Save hard links as the link instead of the file"
 #define   MCHelpSwOI         "\n  oi[0-4][:min] Save identical files as references"
-#define   MCHelpSwOL         "\n  ol            Save symbolic links as the link instead of the file"
+#define   MCHelpSwOL         "\n  ol[a]         Process symbolic links as the link [absolute paths]"
 #define   MCHelpSwOR         "\n  or            Rename files automatically"
 #define   MCHelpSwOS         "\n  os            Save NTFS streams"
 #define   MCHelpSwOW         "\n  ow            Save or restore file owner and group"
--- unrar-nonfree-5.0.10.orig/options.hpp
+++ unrar-nonfree-5.0.10/options.hpp
@@ -129,6 +129,7 @@ class RAROptions
     bool ProcessOwners;
     bool SaveSymLinks;
     bool SaveHardLinks;
+    bool AbsoluteLinks;
     int Priority;
     int SleepTime;
     bool KeepBroken;
--- unrar-nonfree-5.0.10.orig/pathfn.cpp
+++ unrar-nonfree-5.0.10/pathfn.cpp
@@ -593,6 +593,12 @@ bool IsFullPath(const wchar *Path)
 }
 
 
+bool IsFullRootPath(const wchar *Path)
+{
+  return IsFullPath(Path) || IsPathDiv(Path[0]);
+}
+
+
 bool IsDiskLetter(const wchar *Path)
 {
   wchar Letter=etoupperw(Path[0]);
--- unrar-nonfree-5.0.10.orig/pathfn.hpp
+++ unrar-nonfree-5.0.10/pathfn.hpp
@@ -31,6 +31,7 @@ wchar* UnixSlashToDos(wchar *SrcName,wch
 wchar* DosSlashToUnix(wchar *SrcName,wchar *DestName=NULL,size_t MaxLength=NM);
 void ConvertNameToFull(const wchar *Src,wchar *Dest,size_t MaxSize);
 bool IsFullPath(const wchar *Path);
+bool IsFullRootPath(const wchar *Path);
 bool IsDiskLetter(const wchar *Path);
 void GetPathRoot(const wchar *Path,wchar *Root,size_t MaxSize);
 int ParseVersionFileName(wchar *Name,bool Truncate);
--- unrar-nonfree-5.0.10.orig/ulinks.cpp
+++ unrar-nonfree-5.0.10/ulinks.cpp
@@ -24,7 +24,13 @@ static bool UnixSymlink(const char *Targ
 }
 
 
-bool ExtractUnixLink30(ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName)
+static bool IsFullPath(const char *PathA) // Unix ASCII version.
+{
+  return *PathA==CPATHDIVIDER;
+}
+
+
+bool ExtractUnixLink30(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const wchar *LinkName)
 {
   char Target[NM];
   if (IsLink(Arc.FileHead.FileAttr))
@@ -42,23 +48,32 @@ bool ExtractUnixLink30(ComprDataIO &Data
     if (!DataIO.UnpHash.Cmp(&Arc.FileHead.FileHash,Arc.FileHead.UseHashKey ? Arc.FileHead.HashKey:NULL))
       return true;
 
+    if (!Cmd->AbsoluteLinks && (IsFullPath(Target) ||
+        !IsRelativeSymlinkSafe(Arc.FileHead.FileName,Arc.FileHead.RedirName)))
+      return false;
+
     return UnixSymlink(Target,LinkName);
   }
   return false;
 }
 
 
-bool ExtractUnixLink50(const wchar *Name,FileHeader *hd)
+bool ExtractUnixLink50(CommandData *Cmd,const wchar *Name,FileHeader *hd)
 {
   char Target[NM];
   WideToChar(hd->RedirName,Target,ASIZE(Target));
   if (hd->RedirType==FSREDIR_WINSYMLINK || hd->RedirType==FSREDIR_JUNCTION)
   {
     // Cannot create Windows absolute path symlinks in Unix. Only relative path
-    // Windows symlinks can be created here.
-    if (strncmp(Target,"\\??\\",4)==0)
+    // Windows symlinks can be created here. RAR 5.0 used \??\ prefix
+    // for Windows absolute symlinks, since RAR 5.1 /??/ is used.
+    // We escape ? as \? to avoid "trigraph" warning
+    if (strncmp(Target,"\\??\\",4)==0 || strncmp(Target,"/\?\?/",4)==0)
       return false;
     DosSlashToUnix(Target,Target,ASIZE(Target));
   }
+  if (!Cmd->AbsoluteLinks && (IsFullPath(Target) ||
+      !IsRelativeSymlinkSafe(hd->FileName,hd->RedirName)))
+    return false;
   return UnixSymlink(Target,Name);
 }
--- unrar-nonfree-5.0.10.orig/ulinks.hpp
+++ unrar-nonfree-5.0.10/ulinks.hpp
@@ -1,9 +1,9 @@
 #ifndef _RAR_ULINKS_
 #define _RAR_ULINKS_
 
-void SaveLinkData(ComprDataIO &DataIO,Archive &TempArc,FileHeader &hd,
+void SaveLinkData(CommandData *Cmd,ComprDataIO &DataIO,Archive &TempArc,FileHeader &hd,
                   const char *Name);
-bool ExtractLink(ComprDataIO &DataIO,Archive &Arc,const char *LinkName,
+bool ExtractLink(CommandData *Cmd,ComprDataIO &DataIO,Archive &Arc,const char *LinkName,
                  uint &LinkCRC,bool Create);
 
 #endif
