From a68c0462ee3c7d19c68bdc0ba4d17447f03e867a Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Wed, 1 Apr 2015 10:11:54 +0100
Subject: [PATCH] Issue #2388255 by dawehner, hussainweb, pwolanin: Limit PDO
 MySQL to executing single statements if PHP supports it

---
 core/INSTALL.txt                              |  7 +++-
 core/includes/install.inc                     |  2 +
 .../Core/Database/Driver/mysql/Connection.php | 11 +++++-
 .../modules/system/src/SystemRequirements.php | 29 ++++++++++++++
 .../src/Tests/Database/ConnectionTest.php     | 18 +++++++++
 core/modules/system/system.install            | 12 ++++++
 .../tests/src/Unit/SystemRequirementsTest.php | 39 +++++++++++++++++++
 7 files changed, 115 insertions(+), 3 deletions(-)
 create mode 100644 core/modules/system/src/SystemRequirements.php
 create mode 100644 core/modules/system/tests/src/Unit/SystemRequirementsTest.php

diff --git a/core/INSTALL.txt b/core/INSTALL.txt
index ab25c46af550..079533c7b6ab 100644
--- a/core/INSTALL.txt
+++ b/core/INSTALL.txt
@@ -18,7 +18,8 @@ Drupal requires:
 - A web server with PHP support, for example:
   - Apache 2.0 (or greater) (http://httpd.apache.org/).
   - Nginx 1.1 (or greater) (http://nginx.com/).
-- PHP 5.4.5 (or greater) (http://php.net/).
+- PHP 5.4.5 (or greater) (http://php.net/). For better security support its
+  recommended to update to at least 5.5.21 or 5.6.5.
 - One of the following databases:
   - MySQL 5.0.15 (or greater) (http://www.mysql.com/).
   - MariaDB 5.1.44 (or greater) (https://mariadb.org/). MariaDB is a fully
@@ -62,6 +63,10 @@ OPTIONAL SERVER REQUIREMENTS
   configuration allows the web server to initiate outbound connections. Most web
   hosting setups allow this.
 
+- PHP 5.5.21 provides features for improved security when used with MySQL. While
+  this is not required, it is highly encouraged to use PHP 5.5.21 or 5.6.5 and
+  above.
+
 INSTALLATION
 ------------
 
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 61d286bdec82..7e0e32e65cad 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -925,6 +925,8 @@ function drupal_check_profile($profile, array $install_state) {
   foreach ($info['dependencies'] as $module) {
     module_load_install($module);
     $function = $module . '_requirements';
+
+    drupal_classloader_register($module, drupal_get_path('module', $module));
     if (function_exists($function)) {
       $requirements = array_merge($requirements, $function('install'));
     }
diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
index 8a70fb0cf2d4..f31389262d70 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Connection.php
@@ -83,6 +83,11 @@ public static function open(array &$connection_options = array()) {
       // Because MySQL's prepared statements skip the query cache, because it's dumb.
       \PDO::ATTR_EMULATE_PREPARES => TRUE,
     );
+    if (defined('\PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
+      // An added connection option in PHP 5.5.21 to optionally limit SQL to a
+      // single statement like mysqli.
+      $connection_options['pdo'] += [\PDO::MYSQL_ATTR_MULTI_STATEMENTS => FALSE];
+    }
 
     $pdo = new \PDO($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
 
@@ -110,8 +115,10 @@ public static function open(array &$connection_options = array()) {
     $connection_options['init_commands'] += array(
       'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'",
     );
-    // Set connection options.
-    $pdo->exec(implode('; ', $connection_options['init_commands']));
+    // Execute initial commands.
+    foreach ($connection_options['init_commands'] as $sql) {
+      $pdo->exec($sql);
+    }
 
     return $pdo;
   }
diff --git a/core/modules/system/src/SystemRequirements.php b/core/modules/system/src/SystemRequirements.php
new file mode 100644
index 000000000000..ecaf0cf4b83c
--- /dev/null
+++ b/core/modules/system/src/SystemRequirements.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\SystemRequirements.
+ */
+
+namespace Drupal\system;
+
+/**
+ * Class for helper methods used for the system requirements.
+ */
+class SystemRequirements {
+
+  /**
+   * Determines whether the passed in PHP version disallows multiple statements.
+   *
+   * @param string $phpversion
+   *
+   * @return bool
+   */
+  public static function phpVersionWithPdoDisallowMultipleStatements($phpversion) {
+    // PDO::MYSQL_ATTR_MULTI_STATEMENTS was introduced in PHP versions 5.5.21
+    // and 5.6.5.
+    return (version_compare($phpversion, '5.5.21', '>=') && version_compare($phpversion, '5.6.0', '<'))
+      || version_compare($phpversion, '5.6.5', '>=');
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Database/ConnectionTest.php b/core/modules/system/src/Tests/Database/ConnectionTest.php
index c0d9253a1766..d6fe5f9ca25e 100644
--- a/core/modules/system/src/Tests/Database/ConnectionTest.php
+++ b/core/modules/system/src/Tests/Database/ConnectionTest.php
@@ -118,4 +118,22 @@ function testConnectionOptions() {
     $this->assertNotEqual($connection_info['default']['database'], $connectionOptions['database'], 'The test connection info database does not match the current connection options database.');
   }
 
+  /**
+   * Ensure that you cannot execute multiple statements on phpversion() > 5.5.21 or > 5.6.5.
+   */
+  public function testMultipleStatementsForNewPhp() {
+    if (!defined('\PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
+      return;
+    }
+
+    $db = Database::getConnection('default', 'default');
+    try {
+      $db->query('SELECT * FROM {test}; SELECT * FROM {test_people}')->execute();
+      $this->fail('NO PDO exception thrown for multiple statements.');
+    }
+    catch (\Exception $e) {
+      $this->pass('PDO exception thrown for multiple statements.');
+    }
+  }
+
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 94c8e33b6fb7..8355a2c52a28 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -14,6 +14,7 @@
 use Drupal\Core\Site\Settings;
 use Drupal\Core\StreamWrapper\PrivateStream;
 use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\system\SystemRequirements;
 
 /**
  * Implements hook_requirements().
@@ -80,6 +81,17 @@ function system_requirements($phase) {
     return $requirements;
   }
 
+  // Suggest to update to at least 5.5.21 or 5.6.5 for disabling multiple
+  // statements.
+  if (($phase === 'install' || \Drupal::database()->driver() === 'mysql') && !SystemRequirements::phpVersionWithPdoDisallowMultipleStatements($phpversion)) {
+    $requirements['php'] = array(
+      'title' => t('PHP (multiple statement disabling)'),
+      'value' => $phpversion,
+      'description' => t('PHP versions higher than 5.6.5 or 5.5.21 provide built-in SQL injection protection for mysql databases. It is recommended to update.'),
+      'severity' => REQUIREMENT_INFO,
+    );
+  }
+
   // Test for PHP extensions.
   $requirements['php_extensions'] = array(
     'title' => t('PHP extensions'),
diff --git a/core/modules/system/tests/src/Unit/SystemRequirementsTest.php b/core/modules/system/tests/src/Unit/SystemRequirementsTest.php
new file mode 100644
index 000000000000..8d547ea3cf7b
--- /dev/null
+++ b/core/modules/system/tests/src/Unit/SystemRequirementsTest.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\system\Unit\SystemRequirementsTest.
+ */
+
+namespace Drupal\Tests\system\Unit;
+
+use Drupal\system\SystemRequirements;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass Drupal\system\SystemRequirements
+ */
+class SystemRequirementsTest extends UnitTestCase {
+
+  /**
+   * @dataProvider providerTestPhpVersionWithPdoDisallowMultipleStatements
+   */
+  public function testPhpVersionWithPdoDisallowMultipleStatements($version, $expected) {
+    $this->assertEquals($expected, SystemRequirements::phpVersionWithPdoDisallowMultipleStatements($version));
+  }
+
+  public function providerTestPhpVersionWithPdoDisallowMultipleStatements() {
+    $data = [];
+    $data[] = ['5.4.2', FALSE];
+    $data[] = ['5.4.21', FALSE];
+    $data[] = ['5.5.9', FALSE];
+    $data[] = ['5.5.20', FALSE];
+    $data[] = ['5.5.21', TRUE];
+    $data[] = ['5.5.30', TRUE];
+    $data[] = ['5.6.2', FALSE];
+    $data[] = ['5.6.5', TRUE];
+    $data[] = ['5.5.21', TRUE];
+    return $data;
+  }
+
+}
-- 
GitLab