CVE-2018-11756: Insufficient Information (NVD-CWE-noinfo)
value: In PHP Runtime for Apache OpenWhisk, a Docker action inheriting one of the Docker tags openwhisk/action-php-v7.2:1.0.0 or openwhisk/action-php-v7.1:1.0.1 (or earlier) may allow an attacker to replace the user function inside the container if the user code is vulnerable to code exploitation.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2018-07-23T17:29Z
Repository: https://github.com/apache/incubator-openwhisk-runtime-php
Language: PHP
Files changed: core/php7.1Action/CHANGELOG.md, core/php7.1Action/router.php, core/php7.2Action/CHANGELOG.md, core/php7.2Action/router.php, tests/src/test/scala/runtime/actionContainers/Php7ActionContainerTests.scala
Commit date: 2018-07-09 22:34:29 -0400
Commit message: Update tests to pick up upstream changes.

Diff: diff --git a/core/php7.1Action/CHANGELOG.md b/core/php7.1Action/CHANGELOG.md
index 57ff219..8ad347b 100644
--- a/core/php7.1Action/CHANGELOG.md
+++ b/core/php7.1Action/CHANGELOG.md
@@ -15,10 +15,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
 -->
 
+## 1.0.2
+Changes:
+  - Disallow re-initialization of function.
+
 ## 1.0.1
 
 - Change: Update PHP to 7.1.18
 - Change: Update [guzzlehttp/guzzle](https://packagist.org/packages/guzzlehttp/guzzle) to 6.3.3
 - Change: Update [ramsey/uuid](https://packagist.org/packages/ramsey/uuid) to 3.7.3
diff --git a/core/php7.1Action/router.php b/core/php7.1Action/router.php
index d023f1a..76d8765 100644
--- a/core/php7.1Action/router.php
+++ b/core/php7.1Action/router.php
@@ -106,11 +106,11 @@ function init() : array
     $main = $data['main'] ?? 'main';     // function to call (default: main)
     $code = trim($data['code'] ?? '');   // source code to run
     $binary = $data['binary'] ?? false;  // code is binary?
 
     if (!$code) {
-        throw new RuntimeException("No code to execute");
+        throw new RuntimeException("Missing main/no code to execute.");
     }
 
     if ($binary) {
         // binary code is a zip file that's been base64 encoded, so unzip it
         unzipString($code, SRC_DIR);
diff --git a/core/php7.2Action/CHANGELOG.md b/core/php7.2Action/CHANGELOG.md
index 702b708..4e4c56e 100644
--- a/core/php7.2Action/CHANGELOG.md
+++ b/core/php7.2Action/CHANGELOG.md
@@ -15,10 +15,14 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
 -->
 
+## 1.0.1
+Changes:
+  - Disallow re-initialization of function.
+
 ## 1.0.0
 Initial release
 
 - Added: PHP: 7.2.6
 - Added: PHP extensions in addition to the standard ones:
diff --git a/core/php7.2Action/router.php b/core/php7.2Action/router.php
index 87456d1..bbf50e1 100644
--- a/core/php7.2Action/router.php
+++ b/core/php7.2Action/router.php
@@ -137,11 +137,11 @@ function init() : array
     $main = $data['main'] ?? 'main';     // function to call (default: main)
     $code = trim($data['code'] ?? '');   // source code to run
     $binary = $data['binary'] ?? false;  // code is binary?
 
     if (!$code) {
-        throw new RuntimeException("No code to execute");
+        throw new RuntimeException("Missing main/no code to execute.");
     }
 
     if ($binary) {
         // binary code is a zip file that's been base64 encoded, so unzip it
         unzipString($code, SRC_DIR);
diff --git a/tests/.pydevproject b/tests/.pydevproject
deleted file mode 100644
index 40e9f40..0000000
--- a/tests/.pydevproject
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<?eclipse-pydev version="1.0"?><pydev_project>
-<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
-<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
-</pydev_project>
diff --git a/tests/src/test/scala/runtime/actionContainers/Php7ActionContainerTests.scala b/tests/src/test/scala/runtime/actionContainers/Php7ActionContainerTests.scala
index 000d0b0..5e5c9e7 100644
--- a/tests/src/test/scala/runtime/actionContainers/Php7ActionContainerTests.scala
+++ b/tests/src/test/scala/runtime/actionContainers/Php7ActionContainerTests.scala
@@ -25,86 +25,111 @@ import actionContainers.ActionContainer.withContainer
 import actionContainers.ResourceHelpers.ZipBuilder
 import spray.json._
 
 @RunWith(classOf[JUnitRunner])
 abstract class Php7ActionContainerTests extends BasicActionRunnerTests with WskActorSystem {
-  // note: "out" will not be empty as the PHP web server outputs a message when
-  // it starts up
+  // note: "out" will not be empty as the PHP web server outputs a message when it starts up
   val enforceEmptyOutputStream = false
 
-  lazy val phpContainerImageName = "action-php-v7.x"
+  lazy val phpContainerImageName: String = ???
 
   override def withActionContainer(env: Map[String, String] = Map.empty)(code: ActionContainer => Unit) = {
     withContainer(phpContainerImageName, env)(code)
   }
 
   def withPhp7Container(code: ActionContainer => Unit) = withActionContainer()(code)
 
   behavior of phpContainerImageName
 
-  testEcho(Seq {
-    (
-      "PHP",
+  override val testNoSourceOrExec = TestConfig("")
+
+  override val testNotReturningJson = {
+    TestConfig(
+      """
+       |<?php
+       |function main(array $args) {
+       |    return "not a json object";
+       |}
+     """.stripMargin,
+      enforceEmptyOutputStream = enforceEmptyOutputStream,
+      enforceEmptyErrorStream = false)
+  }
+
+  override val testInitCannotBeCalledMoreThanOnce = {
+    TestConfig(
       """
-          |<?php
-          |function main(array $args) : array {
-          |    echo 'hello stdout';
-          |    error_log('hello stderr');
-          |    return $args;
-          |}
-          """.stripMargin)
-  })
-
-  testNotReturningJson("""
         |<?php
-        |function main(array $args) {
-        |    return "not a json object";
+        |function main(array $args) : array {
+        |    return $args;
         |}
-        """.stripMargin)
+      """.stripMargin,
+      enforceEmptyOutputStream = enforceEmptyOutputStream)
+  }
 
-  testUnicode(Seq {
-    (
-      "PHP",
+  override val testEntryPointOtherThanMain = {
+    TestConfig(
       """
+        | <?php
+        | function niam(array $args) {
+        |     return $args;
+        | }
+      """.stripMargin,
+      main = "niam",
+      enforceEmptyOutputStream = enforceEmptyOutputStream)
+  }
+
+  override val testEcho = {
+    TestConfig("""
+                 |<?php
+                 |function main(array $args) : array {
+                 |    echo 'hello stdout';
+                 |    error_log('hello stderr');
+                 |    return $args;
+                 |}
+               """.stripMargin)
+  }
+
+  override val testUnicode = {
+    TestConfig("""
          |<?php
          |function main(array $args) : array {
          |    $str = $args['delimiter'] . " ☃ " . $args['delimiter'];
          |    echo $str . "\n";
          |    return  ["winter" => $str];
          |}
          """.stripMargin.trim)
-  })
+  }
 
-  testEnv(
-    Seq {
-      (
-        "PHP",
-        """
-         |<?php
-         |function main(array $args) : array {
-         |    return [
-         |       "env" => $_ENV,
-         |       "api_host" => $_ENV['__OW_API_HOST'],
-         |       "api_key" => $_ENV['__OW_API_KEY'],
-         |       "namespace" => $_ENV['__OW_NAMESPACE'],
-         |       "action_name" => $_ENV['__OW_ACTION_NAME'],
-         |       "activation_id" => $_ENV['__OW_ACTIVATION_ID'],
-         |       "deadline" => $_ENV['__OW_DEADLINE'],
-         |    ];
-         |}
-         """.stripMargin.trim)
-    },
-    enforceEmptyOutputStream)
-
-  testInitCannotBeCalledMoreThanOnce("""
-          |<?php
-          |function main(array $args) : array {
-          |    echo 'hello stdout';
-          |    error_log('hello stderr');
-          |    return $args;
-          |}
-          """.stripMargin)
+  override val testEnv = {
+    TestConfig(
+      """
+        |<?php
+        |function main(array $args) : array {
+        |    return [
+        |       "env" => $_ENV,
+        |       "api_host" => $_ENV['__OW_API_HOST'],
+        |       "api_key" => $_ENV['__OW_API_KEY'],
+        |       "namespace" => $_ENV['__OW_NAMESPACE'],
+        |       "action_name" => $_ENV['__OW_ACTION_NAME'],
+        |       "activation_id" => $_ENV['__OW_ACTIVATION_ID'],
+        |       "deadline" => $_ENV['__OW_DEADLINE'],
+        |    ];
+        |}
+      """.stripMargin.trim,
+      enforceEmptyOutputStream = enforceEmptyOutputStream)
+  }
+
+  override val testLargeInput = {
+    TestConfig("""
+                 |<?php
+                 |function main(array $args) : array {
+                 |    echo 'hello stdout';
+                 |    error_log('hello stderr');
+                 |    return $args;
+                 |}
+               """.stripMargin)
+  }
 
   it should "fail to initialize with bad code" in {
     val (out, err) = withPhp7Container { c =>
       val code = """
                 |<?php
@@ -125,23 +150,10 @@ abstract class Php7ActionContainerTests extends BasicActionRunnerTests with WskA
         (o + e).toLowerCase should include("error")
         (o + e).toLowerCase should include("syntax")
     })
   }
 
-  it should "fail to initialize with no code" in {
-    val (out, err) = withPhp7Container { c =>
-      val code = ""
-
-      val (initCode, error) = c.init(initPayload(code))
-
-      initCode should not be (200)
-      error shouldBe a[Some[_]]
-      error.get shouldBe a[JsObject]
-      error.get.fields("error").toString should include("No code to execute")
-    }
-  }
-
   it should "return some error on action error" in {
     val (out, err) = withPhp7Container { c =>
       val code = """
                 |<?php
                 | function main(array $args) : array {
@@ -465,25 +477,10 @@ abstract class Php7ActionContainerTests extends BasicActionRunnerTests with WskA
         (o + e).toLowerCase should include("error")
         (o + e).toLowerCase should include("syntax")
     })
   }
 
-  it should "support actions using non-default entry point" in {
-    val (out, err) = withPhp7Container { c =>
-      val code = """
-            | <?php
-            | function niam(array $args) {
-            |     return [result => "it works"];
-            | }
-            """.stripMargin
-
-      c.init(initPayload(code, main = "niam"))._1 should be(200)
-      val (runCode, runRes) = c.run(runPayload(JsObject()))
-      runRes.get.fields.get("result") shouldBe Some(JsString("it works"))
-    }
-  }
-
   it should "support zipped actions using non-default entry point" in {
     val srcs = Seq(Seq("index.php") -> """
                 | <?php
                 | function niam(array $args) {
                 |     return [result => "it works"];

Vulnerable code:         throw new RuntimeException("No code to execute");
        throw new RuntimeException("No code to execute");
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?eclipse-pydev version="1.0"?><pydev_project>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
</pydev_project>
  // note: "out" will not be empty as the PHP web server outputs a message when
  // it starts up
  lazy val phpContainerImageName = "action-php-v7.x"
  testEcho(Seq {
    (
      "PHP",
          |<?php
          |function main(array $args) : array {
          |    echo 'hello stdout';
          |    error_log('hello stderr');
          |    return $args;
          |}
          """.stripMargin)
  })

  testNotReturningJson("""
        |function main(array $args) {
        |    return "not a json object";
        """.stripMargin)
  testUnicode(Seq {
    (
      "PHP",
  })
  testEnv(
    Seq {
      (
        "PHP",
        """
         |<?php
         |function main(array $args) : array {
         |    return [
         |       "env" => $_ENV,
         |       "api_host" => $_ENV['__OW_API_HOST'],
         |       "api_key" => $_ENV['__OW_API_KEY'],
         |       "namespace" => $_ENV['__OW_NAMESPACE'],
         |       "action_name" => $_ENV['__OW_ACTION_NAME'],
         |       "activation_id" => $_ENV['__OW_ACTIVATION_ID'],
         |       "deadline" => $_ENV['__OW_DEADLINE'],
         |    ];
         |}
         """.stripMargin.trim)
    },
    enforceEmptyOutputStream)

  testInitCannotBeCalledMoreThanOnce("""
          |<?php
          |function main(array $args) : array {
          |    echo 'hello stdout';
          |    error_log('hello stderr');
          |    return $args;
          |}
          """.stripMargin)
  it should "fail to initialize with no code" in {
    val (out, err) = withPhp7Container { c =>
      val code = ""

      val (initCode, error) = c.init(initPayload(code))

      initCode should not be (200)
      error shouldBe a[Some[_]]
      error.get shouldBe a[JsObject]
      error.get.fields("error").toString should include("No code to execute")
    }
  }

  it should "support actions using non-default entry point" in {
    val (out, err) = withPhp7Container { c =>
      val code = """
            | <?php
            | function niam(array $args) {
            |     return [result => "it works"];
            | }
            """.stripMargin

      c.init(initPayload(code, main = "niam"))._1 should be(200)
      val (runCode, runRes) = c.run(runPayload(JsObject()))
      runRes.get.fields.get("result") shouldBe Some(JsString("it works"))
    }
  }

Fixed code: ## 1.0.2
Changes:
  - Disallow re-initialization of function.

        throw new RuntimeException("Missing main/no code to execute.");
## 1.0.1
Changes:
  - Disallow re-initialization of function.

        throw new RuntimeException("Missing main/no code to execute.");
  // note: "out" will not be empty as the PHP web server outputs a message when it starts up
  lazy val phpContainerImageName: String = ???
  override val testNoSourceOrExec = TestConfig("")

  override val testNotReturningJson = {
    TestConfig(
      """
       |<?php
       |function main(array $args) {
       |    return "not a json object";
       |}
     """.stripMargin,
      enforceEmptyOutputStream = enforceEmptyOutputStream,
      enforceEmptyErrorStream = false)
  }

  override val testInitCannotBeCalledMoreThanOnce = {
    TestConfig(
        |function main(array $args) : array {
        |    return $args;
      """.stripMargin,
      enforceEmptyOutputStream = enforceEmptyOutputStream)
  }
  override val testEntryPointOtherThanMain = {
    TestConfig(
        | <?php
        | function niam(array $args) {
        |     return $args;
        | }
      """.stripMargin,
      main = "niam",
      enforceEmptyOutputStream = enforceEmptyOutputStream)
  }

  override val testEcho = {
    TestConfig("""
                 |<?php
                 |function main(array $args) : array {
                 |    echo 'hello stdout';
                 |    error_log('hello stderr');
                 |    return $args;
                 |}
               """.stripMargin)
  }

  override val testUnicode = {
    TestConfig("""
  }
  override val testEnv = {
    TestConfig(
      """
        |<?php
        |function main(array $args) : array {
        |    return [
        |       "env" => $_ENV,
        |       "api_host" => $_ENV['__OW_API_HOST'],
        |       "api_key" => $_ENV['__OW_API_KEY'],
        |       "namespace" => $_ENV['__OW_NAMESPACE'],
        |       "action_name" => $_ENV['__OW_ACTION_NAME'],
        |       "activation_id" => $_ENV['__OW_ACTIVATION_ID'],
        |       "deadline" => $_ENV['__OW_DEADLINE'],
        |    ];
        |}
      """.stripMargin.trim,
      enforceEmptyOutputStream = enforceEmptyOutputStream)
  }

  override val testLargeInput = {
    TestConfig("""
                 |<?php
                 |function main(array $args) : array {
                 |    echo 'hello stdout';
                 |    error_log('hello stderr');
                 |    return $args;
                 |}
               """.stripMargin)
  }