Version 2.3.2

* Cherry-pick 3972f738ca4e91104e2d6dad9bb1f36147879e98 to stable
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0cd891b..931efb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,38 @@
+## 2.3.2
+
+* Cherry-pick 3972f738ca4e91104e2d6dad9bb1f36147879e98 to stable
+
+## 2.3.2 - 2019-06-11
+
+This is a patch version release with a security improvement.
+
+### Security vulnerability
+
+*  **Security improvement:** On Linux and Android, starting a process with
+   `Process.run`, `Process.runSync`, or `Process.start` would first search the
+   current directory before searching `PATH` (Issue [37101][]). This behavior
+   effectively put the current working directory in the front of `PATH`, even if
+   it wasn't in the `PATH`. This release changes that behavior to only searching
+   the directories in the `PATH` environment variable. Operating systems other
+   than Linux and Android didn't have this behavior and aren't affected by this
+   vulnerability.
+
+   This vulnerability could result in execution of untrusted code if a command
+   without a slash in its name was run inside an untrusted directory containing
+   an executable file with that name:
+
+   ```dart
+   Process.run("ls", workingDirectory: "/untrusted/directory")
+   ```
+
+   This would attempt to run `/untrusted/directory/ls` if it existed, even
+   though it is not in the `PATH`. It was always safe to instead use an absolute
+   path or a path containing a slash.
+
+   This vulnerability was introduced in Dart 2.0.0.
+
+[37101]: https://github.com/dart-lang/sdk/issues/37101
+
 ## 2.3.1 - 2019-05-21
 
 This is a patch version release with bug fixes.
diff --git a/runtime/bin/process_android.cc b/runtime/bin/process_android.cc
index c26e462..5995e8a 100644
--- a/runtime/bin/process_android.cc
+++ b/runtime/bin/process_android.cc
@@ -436,24 +436,24 @@
     }
   }
 
-  // Tries to find path_ relative to the current namespace.
+  // Tries to find path_ relative to the current namespace unless it should be
+  // searched in the PATH.
   // The path that should be passed to exec is returned in realpath.
   // Returns true on success, and false if there was an error that should
   // be reported to the parent.
   bool FindPathInNamespace(char* realpath, intptr_t realpath_size) {
+    // Perform a PATH search if there's no slash in the path.
+    if (strchr(path_, '/') == NULL) {
+      // TODO(zra): If there is a non-default namespace, the entries in PATH
+      // should be treated as relative to the namespace.
+      strncpy(realpath, path_, realpath_size);
+      realpath[realpath_size - 1] = '\0';
+      return true;
+    }
     NamespaceScope ns(namespc_, path_);
     const int fd =
         TEMP_FAILURE_RETRY(openat(ns.fd(), ns.path(), O_RDONLY | O_CLOEXEC));
     if (fd == -1) {
-      if ((errno == ENOENT) && (strchr(path_, '/') == NULL)) {
-        // path_ was not found relative to the namespace, but since it didn't
-        // contain a '/', we can pass it directly to execvp, which will do a
-        // lookup in PATH.
-        // TODO(zra): If there is a non-default namespace, the entries in PATH
-        // should be treated as relative to the namespace.
-        strncpy(realpath, path_, realpath_size);
-        return true;
-      }
       return false;
     }
     char procpath[PATH_MAX];
diff --git a/runtime/bin/process_linux.cc b/runtime/bin/process_linux.cc
index 93c7f6c..d3df465 100644
--- a/runtime/bin/process_linux.cc
+++ b/runtime/bin/process_linux.cc
@@ -436,24 +436,24 @@
     }
   }
 
-  // Tries to find path_ relative to the current namespace.
+  // Tries to find path_ relative to the current namespace unless it should be
+  // searched in the PATH.
   // The path that should be passed to exec is returned in realpath.
   // Returns true on success, and false if there was an error that should
   // be reported to the parent.
   bool FindPathInNamespace(char* realpath, intptr_t realpath_size) {
+    // Perform a PATH search if there's no slash in the path.
+    if (strchr(path_, '/') == NULL) {
+      // TODO(zra): If there is a non-default namespace, the entries in PATH
+      // should be treated as relative to the namespace.
+      strncpy(realpath, path_, realpath_size);
+      realpath[realpath_size - 1] = '\0';
+      return true;
+    }
     NamespaceScope ns(namespc_, path_);
     const int fd =
         TEMP_FAILURE_RETRY(openat64(ns.fd(), ns.path(), O_RDONLY | O_CLOEXEC));
     if (fd == -1) {
-      if ((errno == ENOENT) && (strchr(path_, '/') == NULL)) {
-        // path_ was not found relative to the namespace, but since it didn't
-        // contain a '/', we can pass it directly to execvp, which will do a
-        // lookup in PATH.
-        // TODO(zra): If there is a non-default namespace, the entries in PATH
-        // should be treated as relative to the namespace.
-        strncpy(realpath, path_, realpath_size);
-        return true;
-      }
       return false;
     }
     char procpath[PATH_MAX];
diff --git a/tools/VERSION b/tools/VERSION
index b9a33e9..e9acdfb 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -32,7 +32,7 @@
 CHANNEL stable
 MAJOR 2
 MINOR 3
-PATCH 1
+PATCH 2
 PRERELEASE 0
 PRERELEASE_PATCH 0
 ABI_VERSION 4