Brian Wolff has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/393539 )

Change subject: Add taint for db related function. Fix handling of subclasses
......................................................................


Add taint for db related function. Fix handling of subclasses

Change-Id: I8b8c6a4350ebb605216411124631984c8bdaf54c
---
M .phpcs.xml
M src/MediaWikiSecurityCheckPlugin.php
M src/SecurityCheckPlugin.php
M src/TaintednessBaseVisitor.php
A tests/integration/subclasses/db.php
A tests/integration/subclasses/expectedResults.txt
A tests/integration/subclasses/test.php
M tests/runtests.sh
8 files changed, 149 insertions(+), 2 deletions(-)



diff --git a/.phpcs.xml b/.phpcs.xml
index d5331bd..60518ae 100644
--- a/.phpcs.xml
+++ b/.phpcs.xml
@@ -26,4 +26,7 @@
        <rule ref="MediaWiki.Files.ClassMatchesFilename.NotMatch">
                <exclude-pattern>./tests</exclude-pattern>
        </rule>
+       <rule ref="MediaWiki.Files.OneClassPerFile.MultipleFound">
+               <exclude-pattern>./tests</exclude-pattern>
+       </rule>
        </ruleset>
diff --git a/src/MediaWikiSecurityCheckPlugin.php 
b/src/MediaWikiSecurityCheckPlugin.php
index 75d5166..232ecfe 100644
--- a/src/MediaWikiSecurityCheckPlugin.php
+++ b/src/MediaWikiSecurityCheckPlugin.php
@@ -11,6 +11,51 @@
         */
        protected function getCustomFuncTaints() : array {
                return [
+                       // Note, at the moment, this checks where the function
+                       // is implemented, so you can't use IDatabase.
+                       '\Wikimedia\Rdbms\Database::query' => [
+                               self::SQL_EXEC_TAINT,
+                               // What should DB results be considered?
+                               'overall' => self::YES_TAINT
+                       ],
+                       '\Wikimedia\Rdbms\IDatabase::query' => [
+                               self::SQL_EXEC_TAINT,
+                               // What should DB results be considered?
+                               'overall' => self::YES_TAINT
+                       ],
+                       '\Wikimedia\Rdbms\DBConnRef::query' => [
+                               self::SQL_EXEC_TAINT,
+                               // What should DB results be considered?
+                               'overall' => self::YES_TAINT
+                       ],
+                       '\Wikimedia\Rdbms\Database::addQuotes' => [
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT,
+                       ],
+                       '\Wikimedia\Rdbms\DBConnRef::addQuotes' => [
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT,
+                       ],
+                       '\Wikimedia\Rdbms\DatabaseMysqlBase::addQuotes' => [
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT,
+                       ],
+                       '\Wikimedia\Rdbms\DatabaseMssql::addQuotes' => [
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT,
+                       ],
+                       '\Wikimedia\Rdbms\IDatabase::addQuotes' => [
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT,
+                       ],
+                       '\Wikimedia\Rdbms\DatabasePostgres::addQuotes' => [
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT,
+                       ],
+                       '\Wikimedia\Rdbms\DatabaseSqlite::addQuotes' => [
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT,
+                       ],
                        // '\Message::__construct' => 
SecurityCheckPlugin::YES_TAINT,
                        // '\wfMessage' => SecurityCheckPlugin::YES_TAINT,
                        '\Message::plain' => [ 'overall' => 
SecurityCheckPlugin::YES_TAINT, ],
diff --git a/src/SecurityCheckPlugin.php b/src/SecurityCheckPlugin.php
index 94d6b5b..e566975 100644
--- a/src/SecurityCheckPlugin.php
+++ b/src/SecurityCheckPlugin.php
@@ -152,6 +152,46 @@
                                self::SERIALIZE_EXEC_TAINT,
                                'overall' => self::NO_TAINT,
                        ],
+                       '\mysql_query' => [
+                               self::SQL_EXEC_TAINT,
+                               'overall' => self::UNKNOWN_TAINT
+                       ],
+                       '\mysqli_query' => [
+                               self::NO_TAINT,
+                               self::SQL_EXEC_TAINT,
+                               'overall' => self::UNKNOWN_TAINT
+                       ],
+                       '\mysqli::query' => [
+                               self::SQL_EXEC_TAINT,
+                               'overall' => self::UNKNOWN_TAINT
+                       ],
+                       '\mysqli_real_query' => [
+                               self::NO_TAINT,
+                               self::SQL_EXEC_TAINT,
+                               'overall' => self::UNKNOWN_TAINT
+                       ],
+                       '\mysqli::real_query' => [
+                               self::SQL_EXEC_TAINT,
+                               'overall' => self::UNKNOWN_TAINT
+                       ],
+                       '\mysqli_escape_string' => [
+                               self::NO_TAINT,
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT
+                       ],
+                       '\mysqli_real_escape_string' => [
+                               self::NO_TAINT,
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT
+                       ],
+                       '\mysqli::escape_string' => [
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT
+                       ],
+                       '\mysqli::real_escape_string' => [
+                               self::YES_TAINT & ~self::SQL_TAINT,
+                               'overall' => self::NO_TAINT
+                       ],
                ];
        }
 }
diff --git a/src/TaintednessBaseVisitor.php b/src/TaintednessBaseVisitor.php
index d04cd3b..58195b7 100644
--- a/src/TaintednessBaseVisitor.php
+++ b/src/TaintednessBaseVisitor.php
@@ -7,6 +7,7 @@
 use Phan\Language\Element\FunctionInterface;
 use Phan\Language\Element\Variable;
 use Phan\Language\Element\TypedElementInterface;
+use Phan\Language\Element\ClassElement;
 use Phan\Language\UnionType;
 use Phan\Language\FQSEN\FullyQualifiedFunctionLikeName;
 use Phan\Plugin;
@@ -49,11 +50,22 @@
         * @param FunctionInterface $func
         * @param int[] $taint Numeric keys for each arg and an 'overall' key.
         * @param bool $override Whether to merge taint or override
+        * @suppress PhanUndeclaredMethod
         */
        protected function setFuncTaint( FunctionInterface $func, array $taint, 
bool $override = false ) {
+               if (
+                       $func instanceof ClassElement &&
+                       (string)$func->getDefiningFQSEN() !== 
(string)$func->getFQSEN()
+               ) {
+                       $this->debug( __METHOD__, "Setting taint on function " 
. $func->getFQSEN() . " other than"
+                               . " its implementation " . 
$func->getDefiningFQSEN()
+                       );
+                       // FIXME we should maybe do something here.
+                       // As it stands, this case probably can't be reached.
+               }
                $curTaint = [];
                $newTaint = [];
-               // What taint we're setting, to do bookkeeping about whenever
+               // What taint we're setting, to do book-keeping about whenever
                // we add a dangerous taint.
                $mergedTaint = SecurityCheckPlugin::NO_TAINT;
                if ( property_exists( $func, 'funcTaint' ) ) {
@@ -245,6 +257,7 @@
         *                     TAINT flags for what taint gets passed through 
func.
         *   If func has an arg that is mssing from array, then it should be
         *   treated as TAINT_NO if its a number or bool. TAINT_YES otherwise.
+        * @suppress PhanUndeclaredMethod
         */
        protected function getTaintOfFunction( FunctionInterface $func ) {
                $funcName = $func->getFQSEN();
@@ -261,6 +274,20 @@
                }
                if ( property_exists( $func, 'funcTaint' ) ) {
                        $taint = $func->funcTaint;
+               } elseif (
+                       $func instanceof ClassElement
+                       && $func->hasDefiningFQSEN()
+                       && (string)$func->getDefiningFQSEN() !== 
(string)$func->getFQSEN()
+               ) {
+                       // @todo Should we check this earlier. Should we skip 
right to
+                       // the taint of the defining method, and not even look 
at
+                       // whatever the method is called as?
+                       // In the case of builtin taints, perhaps it should 
still look
+                       // at base classes even if the base class got 
overriden(?)
+                       $definingFqsen = $func->getDefiningFQSEN();
+                       $definingFunc = $this->code_base->getMethodByFQSEN( 
$definingFqsen );
+                       $this->debug( __METHOD__, "Checking base implemntation 
$definingFqsen of $funcName" );
+                       return $this->getTaintOfFunction( $definingFunc );
                } else {
                        // Ensure we don't indef loop.
                        if (
diff --git a/tests/integration/subclasses/db.php 
b/tests/integration/subclasses/db.php
new file mode 100644
index 0000000..517e4e2
--- /dev/null
+++ b/tests/integration/subclasses/db.php
@@ -0,0 +1,20 @@
+<?php
+namespace Wikimedia\Rdbms;
+
+interface IDatabase {
+       public function query( $sql, $method );
+}
+
+class Database implements IDatabase {
+       public function query( $sql, $method ) {
+               // do some stuff
+               return (object)[ 'some_field' => 'some value' ];
+       }
+
+}
+
+class MysqlDatabase extends Database {
+       public function getType() {
+               return 'mysql';
+       }
+}
diff --git a/tests/integration/subclasses/expectedResults.txt 
b/tests/integration/subclasses/expectedResults.txt
new file mode 100644
index 0000000..e43c804
--- /dev/null
+++ b/tests/integration/subclasses/expectedResults.txt
@@ -0,0 +1 @@
+integration/subclasses/test.php:10 SecurityCheckTaintedOutput Calling Method 
\Wikimedia\Rdbms\MysqlDatabase::query in [no method] that outputs using tainted 
(680; 64) argument $myQuery. (Caused by: integration/subclasses/test.php +8; 
integration/subclasses/test.php +7)
diff --git a/tests/integration/subclasses/test.php 
b/tests/integration/subclasses/test.php
new file mode 100644
index 0000000..e13ee6c
--- /dev/null
+++ b/tests/integration/subclasses/test.php
@@ -0,0 +1,10 @@
+<?php
+
+use Wikimedia\Rdbms\MysqlDatabase;
+
+$db = new MysqlDatabase;
+
+$name = $_GET['name'];
+$myQuery = "Select * from foo where name = '$name' LIMIT 3;";
+
+$db->query( $myQuery, "query" );
diff --git a/tests/runtests.sh b/tests/runtests.sh
index 5a32398..d5bcd5c 100755
--- a/tests/runtests.sh
+++ b/tests/runtests.sh
@@ -12,6 +12,7 @@
 failedTests=0
 php=`which php7.0`
 php=${php:-`which php`}
+SECCHECK_DEBUG=${SECCHECK_DEBUG:-/dev/null}
 
 for i in $testList
 do
@@ -21,7 +22,7 @@
                --project-root-directory "." \
                --config-file "integration-test-config.php" \
                --output "php://stdout" \
-               -l "integration/$i" | grep SecurityCheckTaintedOutput  > 
$tmpFile
+               -l "integration/$i" | tee "$SECCHECK_DEBUG" | grep 
SecurityCheckTaintedOutput  > $tmpFile
        diff -u "integration/$i/expectedResults.txt" "$tmpFile"
        if [ $? -gt 0 ]
                then failedTests=$((failedTests+1))

-- 
To view, visit https://gerrit.wikimedia.org/r/393539
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I8b8c6a4350ebb605216411124631984c8bdaf54c
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/tools/phan/SecurityCheckPlugin
Gerrit-Branch: master
Gerrit-Owner: Brian Wolff <[email protected]>
Gerrit-Reviewer: Brian Wolff <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to