/* * 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 #include #include #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; 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 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; }