| /* |
| * Copyright (C) 2024 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * This file is compiled into a single SO file, which we load at the very first. |
| * We can do process-wide initialization here. |
| * Please be aware that all symbols defined in this SO file will be reloaded |
| * as `RTLD_GLOBAL`, so make sure all functions are static except those we EXPLICITLY |
| * want to expose and override globally. |
| */ |
| |
| #include <dlfcn.h> |
| #include <fcntl.h> |
| |
| #include <set> |
| |
| #include "jni_helper.h" |
| |
| // Implement a rudimentary system properties data store |
| |
| #define PROP_VALUE_MAX 92 |
| |
| namespace { |
| |
| struct prop_info { |
| std::string key; |
| mutable std::string value; |
| mutable uint32_t serial; |
| |
| prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {} |
| }; |
| |
| struct prop_info_cmp { |
| using is_transparent = void; |
| bool operator()(const prop_info& lhs, const prop_info& rhs) { |
| return lhs.key < rhs.key; |
| } |
| bool operator()(std::string_view lhs, const prop_info& rhs) { |
| return lhs < rhs.key; |
| } |
| bool operator()(const prop_info& lhs, std::string_view rhs) { |
| return lhs.key < rhs; |
| } |
| }; |
| |
| } // namespace |
| |
| static auto& g_properties_lock = *new std::mutex; |
| static auto& g_properties = *new std::set<prop_info, prop_info_cmp>; |
| |
| static bool property_set(const char* key, const char* value) { |
| if (key == nullptr || *key == '\0') return false; |
| if (value == nullptr) value = ""; |
| bool read_only = !strncmp(key, "ro.", 3); |
| if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false; |
| |
| std::lock_guard lock(g_properties_lock); |
| auto [it, success] = g_properties.emplace(key, value); |
| if (read_only) return success; |
| if (!success) { |
| it->value = value; |
| ++it->serial; |
| } |
| return true; |
| } |
| |
| template <typename Func> |
| static void property_get(const char* key, Func callback) { |
| std::lock_guard lock(g_properties_lock); |
| auto it = g_properties.find(key); |
| if (it != g_properties.end()) { |
| callback(*it); |
| } |
| } |
| |
| // Redefine the __system_property_XXX functions here so we can perform |
| // logging and access checks for all sysprops in native code. |
| |
| static void check_system_property_access(const char* key, bool write); |
| |
| extern "C" { |
| |
| int __system_property_set(const char* key, const char* value) { |
| check_system_property_access(key, true); |
| return property_set(key, value) ? 0 : -1; |
| } |
| |
| int __system_property_get(const char* key, char* value) { |
| check_system_property_access(key, false); |
| *value = '\0'; |
| property_get(key, [&](const prop_info& info) { |
| snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str()); |
| }); |
| return strlen(value); |
| } |
| |
| const prop_info* __system_property_find(const char* key) { |
| check_system_property_access(key, false); |
| const prop_info* pi = nullptr; |
| property_get(key, [&](const prop_info& info) { pi = &info; }); |
| return pi; |
| } |
| |
| void __system_property_read_callback(const prop_info* pi, |
| void (*callback)(void*, const char*, const char*, uint32_t), |
| void* cookie) { |
| std::lock_guard lock(g_properties_lock); |
| callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial); |
| } |
| |
| } // extern "C" |
| |
| // ---- JNI ---- |
| |
| static JavaVM* gVM = nullptr; |
| static jclass gRunnerState = nullptr; |
| static jmethodID gCheckSystemPropertyAccess; |
| |
| static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) { |
| ScopedUtfChars path(env, javaPath); |
| // Force reload ourselves as global |
| dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD); |
| } |
| |
| // Call back into Java code to check property access |
| static void check_system_property_access(const char* key, bool write) { |
| if (gVM != nullptr && gRunnerState != nullptr) { |
| JNIEnv* env; |
| if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) { |
| ALOGV("%s access to system property '%s'", write ? "Write" : "Read", key); |
| env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess, |
| env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE); |
| return; |
| } |
| } |
| // Not on JVM thread, abort |
| LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key); |
| } |
| |
| static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) { |
| ScopedUtfChars key(env, javaKey); |
| jstring value = nullptr; |
| property_get(key.c_str(), |
| [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); }); |
| return value; |
| } |
| |
| static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) { |
| ScopedUtfChars key(env, javaKey); |
| ScopedUtfChars value(env, javaValue); |
| return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE; |
| } |
| |
| static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) { |
| std::lock_guard lock(g_properties_lock); |
| |
| if (javaKey == nullptr) { |
| g_properties.clear(); |
| return JNI_TRUE; |
| } else { |
| ScopedUtfChars key(env, javaKey); |
| auto it = g_properties.find(key); |
| if (it != g_properties.end()) { |
| g_properties.erase(it); |
| return JNI_TRUE; |
| } else { |
| return JNI_FALSE; |
| } |
| } |
| } |
| |
| static void maybeRedirectLog() { |
| auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT"); |
| if (ravenwoodLogOut == NULL) { |
| return; |
| } |
| ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut); |
| |
| // Redirect stdin / stdout to /dev/tty. |
| int ttyFd = open(ravenwoodLogOut, O_WRONLY | O_APPEND); |
| if (ttyFd == -1) { |
| ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut, |
| strerror(errno)); |
| return; |
| } |
| dup2(ttyFd, 1); |
| dup2(ttyFd, 2); |
| } |
| |
| static const JNINativeMethod sMethods[] = { |
| {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary}, |
| {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty}, |
| {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty}, |
| {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty}, |
| }; |
| |
| extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { |
| ALOGV("%s: JNI_OnLoad", __FILE__); |
| |
| maybeRedirectLog(); |
| |
| JNIEnv* env = GetJNIEnvOrDie(vm); |
| gVM = vm; |
| |
| // Fetch several references for future use |
| gRunnerState = FindGlobalClassOrDie(env, kRunnerState); |
| gCheckSystemPropertyAccess = |
| GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess", |
| "(Ljava/lang/String;Z)V"); |
| |
| // Expose raw property methods as JNI methods |
| jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods)); |
| if (res < 0) return -1; |
| |
| return JNI_VERSION_1_4; |
| } |