Tue, 22 Sep 2020 13:09:39 +0300
8233624: Enhance JNI linkage
Reviewed-by: dholmes, jrose, rhalade, mschoene, mbalao, andrew
src/share/vm/prims/nativeLookup.cpp | file | annotate | diff | comparison | revisions | |
src/share/vm/runtime/globals.hpp | file | annotate | diff | comparison | revisions |
1.1 --- a/src/share/vm/prims/nativeLookup.cpp Mon Sep 28 01:52:34 2020 +0100 1.2 +++ b/src/share/vm/prims/nativeLookup.cpp Tue Sep 22 13:09:39 2020 +0300 1.3 @@ -61,27 +61,118 @@ 1.4 #endif 1.5 1.6 1.7 -static void mangle_name_on(outputStream* st, Symbol* name, int begin, int end) { 1.8 +/* 1.9 + 1.10 +The JNI specification defines the mapping from a Java native method name to 1.11 +a C native library implementation function name as follows: 1.12 + 1.13 + The mapping produces a native method name by concatenating the following components 1.14 + derived from a `native` method declaration: 1.15 + 1.16 + 1. the prefix Java_ 1.17 + 2. given the binary name, in internal form, of the class which declares the native method: 1.18 + the result of escaping the name. 1.19 + 3. an underscore ("_") 1.20 + 4. the escaped method name 1.21 + 5. if the native method declaration is overloaded: two underscores ("__") followed by the 1.22 + escaped parameter descriptor (JVMS 4.3.3) of the method declaration. 1.23 + 1.24 + Escaping leaves every alphanumeric ASCII character (A-Za-z0-9) unchanged, and replaces each 1.25 + UTF-16 code unit n the table below with the corresponding escape sequence. If the name to be 1.26 + escaped contains a surrogate pair, then the high-surrogate code unit and the low-surrogate code 1.27 + unit are escaped separately. The result of escaping is a string consisting only of the ASCII 1.28 + characters A-Za-z0-9 and underscore. 1.29 + 1.30 + ------------------------------ ------------------------------------ 1.31 + UTF-16 code unit Escape sequence 1.32 + ------------------------------ ------------------------------------ 1.33 + Forward slash (/, U+002F) _ 1.34 + Underscore (_, U+005F) _1 1.35 + Semicolon (;, U+003B) _2 1.36 + Left square bracket ([, U+005B) _3 1.37 + Any UTF-16 code unit \u_WXYZ_ that does not _0wxyz where w, x, y, and z are the lower-case 1.38 + represent alphanumeric ASCII (A-Za-z0-9), forms of the hexadecimal digits W, X, Y, and Z. 1.39 + forward slash, underscore, semicolon, (For example, U+ABCD becomes _0abcd.) 1.40 + or left square bracket 1.41 + ------------------------------ ------------------------------------ 1.42 + 1.43 + Note that escape sequences can safely begin _0, _1, etc, because class and method 1.44 + names in Java source code never begin with a number. However, that is not the case in 1.45 + class files that were not generated from Java source code. 1.46 + 1.47 + To preserve the 1:1 mapping to a native method name, the VM checks the resulting name as 1.48 + follows. If the process of escaping any precursor string from the native method declaration 1.49 + (class or method name, or argument type) causes a "0", "1", "2", or "3" character 1.50 + from the precursor string to appear unchanged in the result *either* immediately after an 1.51 + underscore *or* at the beginning of the escaped string (where it will follow an underscore 1.52 + in the fully assembled name), then the escaping process is said to have "failed". 1.53 + In such cases, no native library search is performed, and the attempt to link the native 1.54 + method invocation will throw UnsatisfiedLinkError. 1.55 + 1.56 + 1.57 +For example: 1.58 + 1.59 + package/my_class/method 1.60 + 1.61 +and 1.62 + 1.63 + package/my/1class/method 1.64 + 1.65 +both map to 1.66 + 1.67 + Java_package_my_1class_method 1.68 + 1.69 +To address this potential conflict we need only check if the character after 1.70 +/ is a digit 0..3, or if the first character after an injected '_' seperator 1.71 +is a digit 0..3. If we encounter an invalid identifier we reset the 1.72 +stringStream and return false. Otherwise the stringStream contains the mapped 1.73 +name and we return true. 1.74 + 1.75 +To address legacy compatibility, the UseLegacyJNINameEscaping flag can be set 1.76 +which skips the extra checks. 1.77 + 1.78 +*/ 1.79 +static bool map_escaped_name_on(stringStream* st, Symbol* name, int begin, int end) { 1.80 char* bytes = (char*)name->bytes() + begin; 1.81 char* end_bytes = (char*)name->bytes() + end; 1.82 + bool check_escape_char = true; // initially true as first character here follows '_' 1.83 while (bytes < end_bytes) { 1.84 jchar c; 1.85 bytes = UTF8::next(bytes, &c); 1.86 if (c <= 0x7f && isalnum(c)) { 1.87 + if (check_escape_char && (c >= '0' && c <= '3') && 1.88 + !UseLegacyJNINameEscaping) { 1.89 + // This is a non-Java identifier and we won't escape it to 1.90 + // ensure no name collisions with a Java identifier. 1.91 + if (PrintJNIResolving) { 1.92 + ResourceMark rm; 1.93 + tty->print_cr("[Lookup of native method with non-Java identifier rejected: %s]", 1.94 + name->as_C_string()); 1.95 + } 1.96 + st->reset(); // restore to "" on error 1.97 + return false; 1.98 + } 1.99 st->put((char) c); 1.100 + check_escape_char = false; 1.101 } else { 1.102 - if (c == '_') st->print("_1"); 1.103 - else if (c == '/') st->print("_"); 1.104 + check_escape_char = false; 1.105 + if (c == '_') st->print("_1"); 1.106 + else if (c == '/') { 1.107 + st->print("_"); 1.108 + // Following a / we must have non-escape character 1.109 + check_escape_char = true; 1.110 + } 1.111 else if (c == ';') st->print("_2"); 1.112 else if (c == '[') st->print("_3"); 1.113 else st->print("_%.5x", c); 1.114 } 1.115 } 1.116 + return true; 1.117 } 1.118 1.119 1.120 -static void mangle_name_on(outputStream* st, Symbol* name) { 1.121 - mangle_name_on(st, name, 0, name->utf8_length()); 1.122 +static bool map_escaped_name_on(stringStream* st, Symbol* name) { 1.123 + return map_escaped_name_on(st, name, 0, name->utf8_length()); 1.124 } 1.125 1.126 1.127 @@ -90,10 +181,14 @@ 1.128 // Prefix 1.129 st.print("Java_"); 1.130 // Klass name 1.131 - mangle_name_on(&st, method->klass_name()); 1.132 + if (!map_escaped_name_on(&st, method->klass_name())) { 1.133 + return NULL; 1.134 + } 1.135 st.print("_"); 1.136 // Method name 1.137 - mangle_name_on(&st, method->name()); 1.138 + if (!map_escaped_name_on(&st, method->name())) { 1.139 + return NULL; 1.140 + } 1.141 return st.as_string(); 1.142 } 1.143 1.144 @@ -103,16 +198,20 @@ 1.145 // Prefix 1.146 st.print("JavaCritical_"); 1.147 // Klass name 1.148 - mangle_name_on(&st, method->klass_name()); 1.149 + if (!map_escaped_name_on(&st, method->klass_name())) { 1.150 + return NULL; 1.151 + } 1.152 st.print("_"); 1.153 // Method name 1.154 - mangle_name_on(&st, method->name()); 1.155 + if (!map_escaped_name_on(&st, method->name())) { 1.156 + return NULL; 1.157 + } 1.158 return st.as_string(); 1.159 } 1.160 1.161 1.162 char* NativeLookup::long_jni_name(methodHandle method) { 1.163 - // Signature ignore the wrapping parenteses and the trailing return type 1.164 + // Signatures ignore the wrapping parentheses and the trailing return type 1.165 stringStream st; 1.166 Symbol* signature = method->signature(); 1.167 st.print("__"); 1.168 @@ -120,7 +219,10 @@ 1.169 int end; 1.170 for (end = 0; end < signature->utf8_length() && signature->byte_at(end) != ')'; end++); 1.171 // skip first '(' 1.172 - mangle_name_on(&st, signature, 1, end); 1.173 + if (!map_escaped_name_on(&st, signature, 1, end)) { 1.174 + return NULL; 1.175 + } 1.176 + 1.177 return st.as_string(); 1.178 } 1.179 1.180 @@ -247,6 +349,11 @@ 1.181 in_base_library = false; 1.182 // Compute pure name 1.183 char* pure_name = pure_jni_name(method); 1.184 + if (pure_name == NULL) { 1.185 + // JNI name mapping rejected this method so return 1.186 + // NULL to indicate UnsatisfiedLinkError should be thrown. 1.187 + return NULL; 1.188 + } 1.189 1.190 // Compute argument size 1.191 int args_size = 1 // JNIEnv 1.192 @@ -260,6 +367,11 @@ 1.193 1.194 // Compute long name 1.195 char* long_name = long_jni_name(method); 1.196 + if (long_name == NULL) { 1.197 + // JNI name mapping rejected this method so return 1.198 + // NULL to indicate UnsatisfiedLinkError should be thrown. 1.199 + return NULL; 1.200 + } 1.201 1.202 // 2) Try JNI long style 1.203 entry = lookup_style(method, pure_name, long_name, args_size, true, in_base_library, CHECK_NULL); 1.204 @@ -299,6 +411,11 @@ 1.205 1.206 // Compute critical name 1.207 char* critical_name = critical_jni_name(method); 1.208 + if (critical_name == NULL) { 1.209 + // JNI name mapping rejected this method so return 1.210 + // NULL to indicate UnsatisfiedLinkError should be thrown. 1.211 + return NULL; 1.212 + } 1.213 1.214 // Compute argument size 1.215 int args_size = 1 // JNIEnv 1.216 @@ -312,6 +429,11 @@ 1.217 1.218 // Compute long name 1.219 char* long_name = long_jni_name(method); 1.220 + if (long_name == NULL) { 1.221 + // JNI name mapping rejected this method so return 1.222 + // NULL to indicate UnsatisfiedLinkError should be thrown. 1.223 + return NULL; 1.224 + } 1.225 1.226 // 2) Try JNI long style 1.227 entry = lookup_critical_style(method, critical_name, long_name, args_size, true);
2.1 --- a/src/share/vm/runtime/globals.hpp Mon Sep 28 01:52:34 2020 +0100 2.2 +++ b/src/share/vm/runtime/globals.hpp Tue Sep 22 13:09:39 2020 +0300 2.3 @@ -706,6 +706,9 @@ 2.4 product(bool, CriticalJNINatives, true, \ 2.5 "Check for critical JNI entry points") \ 2.6 \ 2.7 + product(bool, UseLegacyJNINameEscaping, false, \ 2.8 + "Use the original JNI name escaping scheme") \ 2.9 + \ 2.10 notproduct(bool, StressCriticalJNINatives, false, \ 2.11 "Exercise register saving code in critical natives") \ 2.12 \