Origin: https://github.com/drupal/drupal/commit/75689e47e6
Forwarded: not-needed
From: samuel.mortenson, larowlan, pwolanin, Sam152, Jasu_M, David_Rothstein, michieltcs, Ayesh, alexpott, xjm, vijaycs85, mcdruid
Date: Mon May 25 13:55:50 CDT 2020
Subject: Fixes for SA-CORE-2019-012
 Backported the diff between 7.68 and 7.69, applying it to the
 version in the Old-stable Debian release (7.52)
 .
 This vulnerability fixes third-party library Archive_Tar, which has
 released a security improvement that is needed to protect some Drupal
 configurations.
 .
 Do note that three other Drupal Security Advisories were sent out
 together with SA-CORE-2019-012, but they are all specific to 8.x.
 .
 This issue is tracked by MITRE's CVE list as CVE-2019-19826.

Index: drupal7/modules/system/system.archiver.inc
===================================================================
--- drupal7.orig/modules/system/system.archiver.inc
+++ drupal7/modules/system/system.archiver.inc
@@ -38,10 +38,10 @@ class ArchiverTar implements ArchiverInt
 
   public function extract($path, Array $files = array()) {
     if ($files) {
-      $this->tar->extractList($files, $path);
+      $this->tar->extractList($files, $path, '', FALSE, FALSE);
     }
     else {
-      $this->tar->extract($path);
+      $this->tar->extract($path, FALSE, FALSE);
     }
 
     return $this;
Index: drupal7/modules/system/system.tar.inc
===================================================================
--- drupal7.orig/modules/system/system.tar.inc
+++ drupal7/modules/system/system.tar.inc
@@ -40,35 +40,23 @@
  */
 
  /**
- * Note on Drupal 8 porting.
- * This file origin is Tar.php, release 1.4.0 (stable) with some code
- * from PEAR.php, release 1.9.5 (stable) both at http://pear.php.net.
+ * Note on Drupal 7 porting.
+ * This file origin is Tar.php, release 1.4.9 (stable) with some code
+ * from PEAR.php, release 1.10.10 (stable) both at http://pear.php.net.
  * To simplify future porting from pear of this file, you should not
  * do cosmetic or other non significant changes to this file.
  * The following changes have been done:
- *  Added namespace Drupal\Core\Archiver.
  *  Removed require_once 'PEAR.php'.
  *  Added defintion of OS_WINDOWS taken from PEAR.php.
- *  Renamed class to ArchiveTar.
  *  Removed extends PEAR from class.
  *  Removed call parent:: __construct().
  *  Changed PEAR::loadExtension($extname) to this->loadExtension($extname).
  *  Added function loadExtension() taken from PEAR.php.
  *  Changed all calls of unlink() to drupal_unlink().
  *  Changed $this->error_object = &$this->raiseError($p_message)
- *  to throw new \Exception($p_message).
+ *  to throw new Exception($p_message).
  */
 
- /**
- * Note on Drupal 7 backporting from Drupal 8.
- * File origin is core/lib/Drupal/Core/Archiver/ArchiveTar.php from Drupal 8.
- * The following changes have been done:
- *  Removed namespace Drupal\Core\Archiver.
- *  Renamed class to Archive_Tar.
- *  Changed \Exception to Exception.
- */
-
-
 // Drupal removal require_once 'PEAR.php'.
 
 // Drupal addition OS_WINDOWS as defined in PEAR.php.
@@ -154,6 +142,18 @@ class Archive_Tar
     public $error_object = null;
 
     /**
+     * Format for data extraction
+     *
+     * @var string
+     */
+    public $_fmt = '';
+
+    /**
+     * @var int Length of the read buffer in bytes
+     */
+    protected $buffer_length;
+
+    /**
      * Archive_Tar Class constructor. This flavour of the constructor only
      * declare a new Archive_Tar object, identifying it by the name of the
      * tar file.
@@ -165,10 +165,11 @@ class Archive_Tar
      *               parameter indicates if gzip, bz2 or lzma2 compression
      *               is required.  For compatibility reason the
      *               boolean value 'true' means 'gz'.
+     * @param int $buffer_length Length of the read buffer in bytes
      *
      * @return bool
      */
-    public function __construct($p_tarname, $p_compress = null)
+    public function __construct($p_tarname, $p_compress = null, $buffer_length = 512)
     {
         // Drupal removal parent::__construct().
 
@@ -259,6 +260,8 @@ class Archive_Tar
                 return false;
             }
         }
+
+        $this->buffer_length = $buffer_length;
     }
 
     public function __destruct()
@@ -359,11 +362,12 @@ class Archive_Tar
     /**
      * @param string $p_path
      * @param bool $p_preserve
+     * @param bool $p_symlinks
      * @return bool
      */
-    public function extract($p_path = '', $p_preserve = false)
+    public function extract($p_path = '', $p_preserve = false, $p_symlinks = true)
     {
-        return $this->extractModify($p_path, '', $p_preserve);
+        return $this->extractModify($p_path, '', $p_preserve, $p_symlinks);
     }
 
     /**
@@ -604,11 +608,12 @@ class Archive_Tar
      *                               removed if present at the beginning of
      *                               the file/dir path.
      * @param boolean $p_preserve Preserve user/group ownership of files
+     * @param boolean $p_symlinks Allow symlinks.
      *
      * @return boolean true on success, false on error.
      * @see    extractList()
      */
-    public function extractModify($p_path, $p_remove_path, $p_preserve = false)
+    public function extractModify($p_path, $p_remove_path, $p_preserve = false, $p_symlinks = true)
     {
         $v_result = true;
         $v_list_detail = array();
@@ -620,7 +625,8 @@ class Archive_Tar
                 "complete",
                 0,
                 $p_remove_path,
-                $p_preserve
+                $p_preserve,
+                $p_symlinks
             );
             $this->_close();
         }
@@ -664,11 +670,12 @@ class Archive_Tar
      *                               removed if present at the beginning of
      *                               the file/dir path.
      * @param boolean $p_preserve Preserve user/group ownership of files
+     * @param boolean $p_symlinks Allow symlinks.
      *
      * @return true on success, false on error.
      * @see    extractModify()
      */
-    public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false)
+    public function extractList($p_filelist, $p_path = '', $p_remove_path = '', $p_preserve = false, $p_symlinks = true)
     {
         $v_result = true;
         $v_list_detail = array();
@@ -689,7 +696,8 @@ class Archive_Tar
                 "partial",
                 $v_list,
                 $p_remove_path,
-                $p_preserve
+                $p_preserve,
+                $p_symlinks
             );
             $this->_close();
         }
@@ -1314,8 +1322,15 @@ class Archive_Tar
                 return false;
             }
 
-            while (($v_buffer = fread($v_file, 512)) != '') {
-                $v_binary_data = pack("a512", "$v_buffer");
+            while (($v_buffer = fread($v_file, $this->buffer_length)) != '') {
+                $buffer_length = strlen("$v_buffer");
+                if ($buffer_length != $this->buffer_length) {
+                    $pack_size = ((int)($buffer_length / 512) + 1) * 512;
+                    $pack_format = sprintf('a%d', $pack_size);
+                } else {
+                    $pack_format = sprintf('a%d', $this->buffer_length);
+                }
+                $v_binary_data = pack($pack_format, "$v_buffer");
                 $this->_writeBlock($v_binary_data);
             }
 
@@ -1520,7 +1535,8 @@ class Archive_Tar
         $p_type = '',
         $p_uid = 0,
         $p_gid = 0
-    ) {
+    )
+    {
         $p_filename = $this->_pathReduction($p_filename);
 
         if (strlen($p_filename) > 99) {
@@ -1748,7 +1764,16 @@ class Archive_Tar
         }
 
         // ----- Extract the checksum
-        $v_header['checksum'] = OctDec(trim($v_data['checksum']));
+        $v_data_checksum = trim($v_data['checksum']);
+        if (!preg_match('/^[0-7]*$/', $v_data_checksum)) {
+            $this->_error(
+                'Invalid checksum for file "' . $v_data['filename']
+                . '" : ' . $v_data_checksum . ' extracted'
+            );
+            return false;
+        }
+
+        $v_header['checksum'] = OctDec($v_data_checksum);
         if ($v_header['checksum'] != $v_checksum) {
             $v_header['filename'] = '';
 
@@ -1808,10 +1833,7 @@ class Archive_Tar
         if (strpos($file, 'phar://') === 0) {
             return true;
         }
-        if (strpos($file, DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR) !== false) {
-            return true;
-        }
-        if (strpos($file, '..' . DIRECTORY_SEPARATOR) === 0) {
+        if (strpos($file, '../') !== false || strpos($file, '..\\') !== false) {
             return true;
         }
         return false;
@@ -1877,19 +1899,23 @@ class Archive_Tar
             }
 
             switch ($v_header['typeflag']) {
-                case 'L': {
-                    if (!$this->_readLongHeader($v_header)) {
-                        return null;
-                    }
-                } break;
-
-                case 'K': {
-                    $v_link_header = $v_header;
-                    if (!$this->_readLongHeader($v_link_header)) {
-                        return null;
+                case 'L':
+                    {
+                        if (!$this->_readLongHeader($v_header)) {
+                            return null;
+                        }
                     }
-                    $v_header['link'] = $v_link_header['filename'];
-                } break;
+                    break;
+
+                case 'K':
+                    {
+                        $v_link_header = $v_header;
+                        if (!$this->_readLongHeader($v_link_header)) {
+                            return null;
+                        }
+                        $v_header['link'] = $v_link_header['filename'];
+                    }
+                    break;
             }
 
             if ($v_header['filename'] == $p_filename) {
@@ -1929,6 +1955,7 @@ class Archive_Tar
      * @param string $p_file_list
      * @param string $p_remove_path
      * @param bool $p_preserve
+     * @param bool $p_symlinks
      * @return bool
      */
     public function _extractList(
@@ -1937,8 +1964,10 @@ class Archive_Tar
         $p_mode,
         $p_file_list,
         $p_remove_path,
-        $p_preserve = false
-    ) {
+        $p_preserve = false,
+        $p_symlinks = true
+    )
+    {
         $v_result = true;
         $v_nb = 0;
         $v_extract_all = true;
@@ -1991,19 +2020,23 @@ class Archive_Tar
             }
 
             switch ($v_header['typeflag']) {
-                case 'L': {
-                    if (!$this->_readLongHeader($v_header)) {
-                        return null;
-                    }
-                } break;
-
-                case 'K': {
-                    $v_link_header = $v_header;
-                    if (!$this->_readLongHeader($v_link_header)) {
-                        return null;
+                case 'L':
+                    {
+                        if (!$this->_readLongHeader($v_header)) {
+                            return null;
+                        }
                     }
-                    $v_header['link'] = $v_link_header['filename'];
-                } break;
+                    break;
+
+                case 'K':
+                    {
+                        $v_link_header = $v_header;
+                        if (!$this->_readLongHeader($v_link_header)) {
+                            return null;
+                        }
+                        $v_header['link'] = $v_link_header['filename'];
+                    }
+                    break;
             }
 
             // ignore extended / pax headers
@@ -2115,6 +2148,13 @@ class Archive_Tar
                             }
                         }
                     } elseif ($v_header['typeflag'] == "2") {
+                        if (!$p_symlinks) {
+                            $this->_warning('Symbolic links are not allowed. '
+                                . 'Unable to extract {'
+                                . $v_header['filename'] . '}'
+                            );
+                            return false;
+                        }
                         if (@file_exists($v_header['filename'])) {
                             @drupal_unlink($v_header['filename']);
                         }
