8227021: VM fails if any sun.boot.library.path paths are longer than JVM_MAXPATHLEN
authorafarley
Fri, 23 Aug 2019 03:06:24 -0700
changeset 57855 00bf1e66de11
parent 57854 8b8d8a1621f2
child 57863 cf45b7945e4b
8227021: VM fails if any sun.boot.library.path paths are longer than JVM_MAXPATHLEN Summary: The size of each path in sun.boot.library.path property should not exceed JVM_MAXPATHLEN Reviewed-by: dholmes, coleenp, sspitsyn
src/hotspot/share/runtime/os.cpp
src/hotspot/share/runtime/os.hpp
test/hotspot/jtreg/runtime/LoadLibrary/TestSunBootLibraryPath.java
--- a/src/hotspot/share/runtime/os.cpp	Fri Aug 23 10:11:18 2019 +0200
+++ b/src/hotspot/share/runtime/os.cpp	Fri Aug 23 03:06:24 2019 -0700
@@ -279,6 +279,19 @@
   return false;
 }
 
+// Frees all memory allocated on the heap for the
+// supplied array of arrays of chars (a), where n
+// is the number of elements in the array.
+static void free_array_of_char_arrays(char** a, size_t n) {
+      while (n > 0) {
+          n--;
+          if (a[n] != NULL) {
+            FREE_C_HEAP_ARRAY(char, a[n]);
+          }
+      }
+      FREE_C_HEAP_ARRAY(char*, a);
+}
+
 bool os::dll_locate_lib(char *buffer, size_t buflen,
                         const char* pname, const char* fname) {
   bool retval = false;
@@ -299,10 +312,10 @@
       }
     } else if (strchr(pname, *os::path_separator()) != NULL) {
       // A list of paths. Search for the path that contains the library.
-      int n;
-      char** pelements = split_path(pname, &n);
+      size_t n;
+      char** pelements = split_path(pname, &n, fullfnamelen);
       if (pelements != NULL) {
-        for (int i = 0; i < n; i++) {
+        for (size_t i = 0; i < n; i++) {
           char* path = pelements[i];
           // Really shouldn't be NULL, but check can't hurt.
           size_t plen = (path == NULL) ? 0 : strlen(path);
@@ -314,12 +327,7 @@
           if (retval) break;
         }
         // Release the storage allocated by split_path.
-        for (int i = 0; i < n; i++) {
-          if (pelements[i] != NULL) {
-            FREE_C_HEAP_ARRAY(char, pelements[i]);
-          }
-        }
-        FREE_C_HEAP_ARRAY(char*, pelements);
+        free_array_of_char_arrays(pelements, n);
       }
     } else {
       // A definite path.
@@ -1335,17 +1343,22 @@
   return false;
 }
 
-/*
- * Splits a path, based on its separator, the number of
- * elements is returned back in n.
- * It is the callers responsibility to:
- *   a> check the value of n, and n may be 0.
- *   b> ignore any empty path elements
- *   c> free up the data.
- */
-char** os::split_path(const char* path, int* n) {
-  *n = 0;
-  if (path == NULL || strlen(path) == 0) {
+// Splits a path, based on its separator, the number of
+// elements is returned back in "elements".
+// file_name_length is used as a modifier for each path's
+// length when compared to JVM_MAXPATHLEN. So if you know
+// each returned path will have something appended when
+// in use, you can pass the length of that in
+// file_name_length, to ensure we detect if any path
+// exceeds the maximum path length once prepended onto
+// the sub-path/file name.
+// It is the callers responsibility to:
+//   a> check the value of "elements", which may be 0.
+//   b> ignore any empty path elements
+//   c> free up the data.
+char** os::split_path(const char* path, size_t* elements, size_t file_name_length) {
+  *elements = (size_t)0;
+  if (path == NULL || strlen(path) == 0 || file_name_length == (size_t)NULL) {
     return NULL;
   }
   const char psepchar = *os::path_separator();
@@ -1354,7 +1367,7 @@
     return NULL;
   }
   strcpy(inpath, path);
-  int count = 1;
+  size_t count = 1;
   char* p = strchr(inpath, psepchar);
   // Get a count of elements to allocate memory
   while (p != NULL) {
@@ -1363,20 +1376,23 @@
     p = strchr(p, psepchar);
   }
   char** opath = (char**) NEW_C_HEAP_ARRAY(char*, count, mtInternal);
-  if (opath == NULL) {
-    return NULL;
-  }
 
   // do the actual splitting
   p = inpath;
-  for (int i = 0 ; i < count ; i++) {
+  for (size_t i = 0 ; i < count ; i++) {
     size_t len = strcspn(p, os::path_separator());
-    if (len > JVM_MAXPATHLEN) {
-      return NULL;
+    if (len + file_name_length > JVM_MAXPATHLEN) {
+      // release allocated storage before exiting the vm
+      free_array_of_char_arrays(opath, i++);
+      vm_exit_during_initialization("The VM tried to use a path that exceeds the maximum path length for "
+                                    "this system. Review path-containing parameters and properties, such as "
+                                    "sun.boot.library.path, to identify potential sources for this path.");
     }
     // allocate the string and add terminator storage
-    char* s  = (char*)NEW_C_HEAP_ARRAY(char, len + 1, mtInternal);
+    char* s  = (char*)NEW_C_HEAP_ARRAY_RETURN_NULL(char, len + 1, mtInternal);
     if (s == NULL) {
+      // release allocated storage before returning null
+      free_array_of_char_arrays(opath, i++);
       return NULL;
     }
     strncpy(s, p, len);
@@ -1385,7 +1401,7 @@
     p += len + 1;
   }
   FREE_C_HEAP_ARRAY(char, inpath);
-  *n = count;
+  *elements = count;
   return opath;
 }
 
--- a/src/hotspot/share/runtime/os.hpp	Fri Aug 23 10:11:18 2019 +0200
+++ b/src/hotspot/share/runtime/os.hpp	Fri Aug 23 03:06:24 2019 -0700
@@ -821,7 +821,7 @@
   // Amount beyond the callee frame size that we bang the stack.
   static int extra_bang_size_in_bytes();
 
-  static char** split_path(const char* path, int* n);
+  static char** split_path(const char* path, size_t* elements, size_t file_name_length);
 
   // support for mapping non-volatile memory using MAP_SYNC
   static bool supports_map_sync();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/hotspot/jtreg/runtime/LoadLibrary/TestSunBootLibraryPath.java	Fri Aug 23 03:06:24 2019 -0700
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test TestSunBootLibraryPath.java
+ * @bug 8227021
+ * @summary Confirm using too-long paths in sun.boot.library.path causes failure and useful error message.
+ * @author afarley
+ * @library /test/lib
+ * @build jdk.test.lib.process.ProcessTools
+ * @run driver TestSunBootLibraryPath
+ */
+
+import jdk.test.lib.process.ProcessTools;
+
+public class TestSunBootLibraryPath {
+    static String expectedErrorMessage = "The VM tried to use a path that exceeds the maximum path length for this system.";
+
+    public static void main(String[] args) throws Exception {
+        // Allows us to re-use this class as a do-nothing test class simply by passing a "Do-Nothing" argument.
+        if (args.length == 0) {
+            // Grab any path.
+            String tooLongPath = System.getProperty("sun.boot.library.path");
+            // Add enough characters to make it "too long".
+            tooLongPath += "a".repeat(5000);
+            // Start a java process with this property set, and check that:
+            // 1) The process failed and
+            // 2) The error message was correct.
+            ProcessTools.executeTestJvm("-Dsun.boot.library.path=" + tooLongPath,
+                                        "TestSunBootLibraryPath",
+                                        "'Do-Nothing'")
+                                        .shouldNotHaveExitValue(0)
+                                        .stdoutShouldContain(expectedErrorMessage);
+        } else if (!args[0].equals("Do-Nothing")) {
+            // Fail, to prevent accidental args from causing accidental test passing.
+            throw new IllegalArgumentException("Test was launched with an invalid argument.");
+        }
+    }
+}