1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // Uncomment for verbose logging.
18 // #define LOG_NDEBUG 0
19 #define LOG_TAG "webviewchromiumloader"
20 
21 #include <dlfcn.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/mman.h>
29 #include <sys/prctl.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 
33 #include <jni.h>
34 #include <android/dlext.h>
35 #include <nativeloader/native_loader.h>
36 #include <utils/Log.h>
37 
38 #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
39 
40 namespace android {
41 namespace {
42 
43 void* gReservedAddress = NULL;
44 size_t gReservedSize = 0;
45 
46 jint LIBLOAD_SUCCESS;
47 jint LIBLOAD_FAILED_TO_OPEN_RELRO_FILE;
48 jint LIBLOAD_FAILED_TO_LOAD_LIBRARY;
49 jint LIBLOAD_FAILED_JNI_CALL;
50 jint LIBLOAD_FAILED_TO_FIND_NAMESPACE;
51 
DoReserveAddressSpace(jlong size)52 jboolean DoReserveAddressSpace(jlong size) {
53   size_t vsize = static_cast<size_t>(size);
54 
55   void* addr = mmap(NULL, vsize, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
56   if (addr == MAP_FAILED) {
57     ALOGE("Failed to reserve %zd bytes of address space for future load of "
58           "libwebviewchromium.so: %s",
59           vsize, strerror(errno));
60     return JNI_FALSE;
61   }
62   prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, addr, vsize, "libwebview reservation");
63   gReservedAddress = addr;
64   gReservedSize = vsize;
65   ALOGV("Reserved %zd bytes at %p", vsize, addr);
66   return JNI_TRUE;
67 }
68 
DoCreateRelroFile(JNIEnv * env,const char * lib,const char * relro,jobject clazzLoader)69 jboolean DoCreateRelroFile(JNIEnv* env, const char* lib, const char* relro,
70                            jobject clazzLoader) {
71   // Try to unlink the old file, since if this is being called, the old one is
72   // obsolete.
73   if (unlink(relro) != 0 && errno != ENOENT) {
74     // If something went wrong other than the file not existing, log a warning
75     // but continue anyway in the hope that we can successfully overwrite the
76     // existing file with rename() later.
77     ALOGW("Failed to unlink old file %s: %s", relro, strerror(errno));
78   }
79   static const char tmpsuffix[] = ".XXXXXX";
80   char relro_tmp[strlen(relro) + sizeof(tmpsuffix)];
81   strlcpy(relro_tmp, relro, sizeof(relro_tmp));
82   strlcat(relro_tmp, tmpsuffix, sizeof(relro_tmp));
83   int tmp_fd = TEMP_FAILURE_RETRY(mkstemp(relro_tmp));
84   if (tmp_fd == -1) {
85     ALOGE("Failed to create temporary file %s: %s", relro_tmp, strerror(errno));
86     return JNI_FALSE;
87   }
88   android_namespace_t* ns =
89       android::FindNamespaceByClassLoader(env, clazzLoader);
90   if (ns == NULL) {
91     ALOGE("Failed to find classloader namespace");
92     return JNI_FALSE;
93   }
94   android_dlextinfo extinfo;
95   extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_WRITE_RELRO |
96                   ANDROID_DLEXT_USE_NAMESPACE |
97                   ANDROID_DLEXT_RESERVED_ADDRESS_RECURSIVE;
98   extinfo.reserved_addr = gReservedAddress;
99   extinfo.reserved_size = gReservedSize;
100   extinfo.relro_fd = tmp_fd;
101   extinfo.library_namespace = ns;
102   void* handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo);
103   int close_result = close(tmp_fd);
104   if (handle == NULL) {
105     ALOGE("Failed to load library %s: %s", lib, dlerror());
106     unlink(relro_tmp);
107     return JNI_FALSE;
108   }
109   if (close_result != 0 ||
110       chmod(relro_tmp, S_IRUSR | S_IRGRP | S_IROTH) != 0 ||
111       rename(relro_tmp, relro) != 0) {
112     ALOGE("Failed to update relro file %s: %s", relro, strerror(errno));
113     unlink(relro_tmp);
114     return JNI_FALSE;
115   }
116   ALOGV("Created relro file %s for library %s", relro, lib);
117   return JNI_TRUE;
118 }
119 
DoLoadWithRelroFile(JNIEnv * env,const char * lib,const char * relro,jobject clazzLoader)120 jint DoLoadWithRelroFile(JNIEnv* env, const char* lib, const char* relro,
121                          jobject clazzLoader) {
122   int relro_fd = TEMP_FAILURE_RETRY(open(relro, O_RDONLY));
123   if (relro_fd == -1) {
124       ALOGW("Failed to open relro file %s: %s", relro, strerror(errno));
125       return LIBLOAD_FAILED_TO_OPEN_RELRO_FILE;
126   }
127   android_namespace_t* ns =
128       android::FindNamespaceByClassLoader(env, clazzLoader);
129   if (ns == NULL) {
130     ALOGE("Failed to find classloader namespace");
131     return LIBLOAD_FAILED_TO_FIND_NAMESPACE;
132   }
133   android_dlextinfo extinfo;
134   extinfo.flags = ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_USE_RELRO |
135                   ANDROID_DLEXT_USE_NAMESPACE |
136                   ANDROID_DLEXT_RESERVED_ADDRESS_RECURSIVE;
137   extinfo.reserved_addr = gReservedAddress;
138   extinfo.reserved_size = gReservedSize;
139   extinfo.relro_fd = relro_fd;
140   extinfo.library_namespace = ns;
141   void* handle = android_dlopen_ext(lib, RTLD_NOW, &extinfo);
142   close(relro_fd);
143   if (handle == NULL) {
144     ALOGE("Failed to load library %s: %s", lib, dlerror());
145     return LIBLOAD_FAILED_TO_LOAD_LIBRARY;
146   }
147   ALOGV("Loaded library %s with relro file %s", lib, relro);
148   return LIBLOAD_SUCCESS;
149 }
150 
151 /******************************************************************************/
152 /* JNI wrappers - handle string lifetimes and 32/64 ABI choice                */
153 /******************************************************************************/
154 
ReserveAddressSpace(JNIEnv *,jclass,jlong size)155 jboolean ReserveAddressSpace(JNIEnv*, jclass, jlong size) {
156   return DoReserveAddressSpace(size);
157 }
158 
CreateRelroFile(JNIEnv * env,jclass,jstring lib,jstring relro,jobject clazzLoader)159 jboolean CreateRelroFile(JNIEnv* env, jclass, jstring lib, jstring relro,
160                          jobject clazzLoader) {
161   jboolean ret = JNI_FALSE;
162   const char* lib_utf8 = env->GetStringUTFChars(lib, NULL);
163   if (lib_utf8 != NULL) {
164     const char* relro_utf8 = env->GetStringUTFChars(relro, NULL);
165     if (relro_utf8 != NULL) {
166       ret = DoCreateRelroFile(env, lib_utf8, relro_utf8, clazzLoader);
167       env->ReleaseStringUTFChars(relro, relro_utf8);
168     }
169     env->ReleaseStringUTFChars(lib, lib_utf8);
170   }
171   return ret;
172 }
173 
LoadWithRelroFile(JNIEnv * env,jclass,jstring lib,jstring relro,jobject clazzLoader)174 jint LoadWithRelroFile(JNIEnv* env, jclass, jstring lib, jstring relro,
175                        jobject clazzLoader) {
176   jint ret = LIBLOAD_FAILED_JNI_CALL;
177   const char* lib_utf8 = env->GetStringUTFChars(lib, NULL);
178   if (lib_utf8 != NULL) {
179     const char* relro_utf8 = env->GetStringUTFChars(relro, NULL);
180     if (relro_utf8 != NULL) {
181       ret = DoLoadWithRelroFile(env, lib_utf8, relro_utf8, clazzLoader);
182       env->ReleaseStringUTFChars(relro, relro_utf8);
183     }
184     env->ReleaseStringUTFChars(lib, lib_utf8);
185   }
186   return ret;
187 }
188 
189 const char kWebViewFactoryClassName[] = "android/webkit/WebViewFactory";
190 const char kWebViewLibraryLoaderClassName[] =
191     "android/webkit/WebViewLibraryLoader";
192 const JNINativeMethod kJniMethods[] = {
193   { "nativeReserveAddressSpace", "(J)Z",
194       reinterpret_cast<void*>(ReserveAddressSpace) },
195   { "nativeCreateRelroFile",
196       "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)Z",
197       reinterpret_cast<void*>(CreateRelroFile) },
198   { "nativeLoadWithRelroFile",
199       "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/ClassLoader;)I",
200       reinterpret_cast<void*>(LoadWithRelroFile) },
201 };
202 
203 }  // namespace
204 
RegisterWebViewFactory(JNIEnv * env)205 void RegisterWebViewFactory(JNIEnv* env) {
206   // If either of these fail, it will set an exception that will be thrown on
207   // return, so no need to handle errors here.
208   jclass clazz = env->FindClass(kWebViewFactoryClassName);
209   if (clazz) {
210     LIBLOAD_SUCCESS = env->GetStaticIntField(
211         clazz,
212         env->GetStaticFieldID(clazz, "LIBLOAD_SUCCESS", "I"));
213 
214     LIBLOAD_FAILED_TO_OPEN_RELRO_FILE = env->GetStaticIntField(
215         clazz,
216         env->GetStaticFieldID(clazz, "LIBLOAD_FAILED_TO_OPEN_RELRO_FILE", "I"));
217 
218     LIBLOAD_FAILED_TO_LOAD_LIBRARY = env->GetStaticIntField(
219         clazz,
220         env->GetStaticFieldID(clazz, "LIBLOAD_FAILED_TO_LOAD_LIBRARY", "I"));
221 
222     LIBLOAD_FAILED_JNI_CALL = env->GetStaticIntField(
223         clazz,
224         env->GetStaticFieldID(clazz, "LIBLOAD_FAILED_JNI_CALL", "I"));
225 
226     LIBLOAD_FAILED_TO_FIND_NAMESPACE = env->GetStaticIntField(
227         clazz,
228         env->GetStaticFieldID(clazz, "LIBLOAD_FAILED_TO_FIND_NAMESPACE", "I"));
229   }
230 }
231 
RegisterWebViewLibraryLoader(JNIEnv * env)232 void RegisterWebViewLibraryLoader(JNIEnv* env) {
233   // If either of these fail, it will set an exception that will be thrown on
234   // return, so no need to handle errors here.
235   jclass clazz = env->FindClass(kWebViewLibraryLoaderClassName);
236   if (clazz) {
237     env->RegisterNatives(clazz, kJniMethods, NELEM(kJniMethods));
238   }
239 }
240 
241 }  // namespace android
242 
JNI_OnLoad(JavaVM * vm,void *)243 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
244   JNIEnv* env = NULL;
245   if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
246     ALOGE("GetEnv failed");
247     return JNI_ERR;
248   }
249   android::RegisterWebViewFactory(env);
250   // Ensure there isn't a pending Java exception before registering methods from
251   // WebViewLibraryLoader
252   if (!env->ExceptionCheck()) {
253     android::RegisterWebViewLibraryLoader(env);
254   }
255   return JNI_VERSION_1_6;
256 }
257