• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 /*
18  * This file is compiled into a single SO file, which we load at the very first.
19  * We can do process-wide initialization here.
20  * Please be aware that all symbols defined in this SO file will be reloaded
21  * as `RTLD_GLOBAL`, so make sure all functions are static except those we EXPLICITLY
22  * want to expose and override globally.
23  */
24 
25 #include <dlfcn.h>
26 #include <fcntl.h>
27 
28 #include <set>
29 #include <fstream>
30 #include <iostream>
31 #include <string>
32 #include <cstdlib>
33 
34 #include "jni_helper.h"
35 
36 // Implement a rudimentary system properties data store
37 
38 #define PROP_VALUE_MAX 92
39 
40 namespace {
41 
42 struct prop_info {
43     std::string key;
44     mutable std::string value;
45     mutable uint32_t serial;
46 
prop_info__anonc2fd6f3d0111::prop_info47     prop_info(const char* key, const char* value) : key(key), value(value), serial(0) {}
48 };
49 
50 struct prop_info_cmp {
51     using is_transparent = void;
operator ()__anonc2fd6f3d0111::prop_info_cmp52     bool operator()(const prop_info& lhs, const prop_info& rhs) {
53         return lhs.key < rhs.key;
54     }
operator ()__anonc2fd6f3d0111::prop_info_cmp55     bool operator()(std::string_view lhs, const prop_info& rhs) {
56         return lhs < rhs.key;
57     }
operator ()__anonc2fd6f3d0111::prop_info_cmp58     bool operator()(const prop_info& lhs, std::string_view rhs) {
59         return lhs.key < rhs;
60     }
61 };
62 
63 } // namespace
64 
65 static auto& g_properties_lock = *new std::mutex;
66 static auto& g_properties = *new std::set<prop_info, prop_info_cmp>;
67 
property_set(const char * key,const char * value)68 static bool property_set(const char* key, const char* value) {
69     if (key == nullptr || *key == '\0') return false;
70     if (value == nullptr) value = "";
71     bool read_only = !strncmp(key, "ro.", 3);
72     if (!read_only && strlen(value) >= PROP_VALUE_MAX) return false;
73 
74     std::lock_guard lock(g_properties_lock);
75     auto [it, success] = g_properties.emplace(key, value);
76     if (read_only) return success;
77     if (!success) {
78         it->value = value;
79         ++it->serial;
80     }
81     return true;
82 }
83 
84 template <typename Func>
property_get(const char * key,Func callback)85 static void property_get(const char* key, Func callback) {
86     std::lock_guard lock(g_properties_lock);
87     auto it = g_properties.find(key);
88     if (it != g_properties.end()) {
89         callback(*it);
90     }
91 }
92 
93 // Redefine the __system_property_XXX functions here so we can perform
94 // logging and access checks for all sysprops in native code.
95 
96 static void check_system_property_access(const char* key, bool write);
97 
98 extern "C" {
99 
__system_property_set(const char * key,const char * value)100 int __system_property_set(const char* key, const char* value) {
101     check_system_property_access(key, true);
102     return property_set(key, value) ? 0 : -1;
103 }
104 
__system_property_get(const char * key,char * value)105 int __system_property_get(const char* key, char* value) {
106     check_system_property_access(key, false);
107     *value = '\0';
108     property_get(key, [&](const prop_info& info) {
109         snprintf(value, PROP_VALUE_MAX, "%s", info.value.c_str());
110     });
111     return strlen(value);
112 }
113 
__system_property_find(const char * key)114 const prop_info* __system_property_find(const char* key) {
115     check_system_property_access(key, false);
116     const prop_info* pi = nullptr;
117     property_get(key, [&](const prop_info& info) { pi = &info; });
118     return pi;
119 }
120 
__system_property_read_callback(const prop_info * pi,void (* callback)(void *,const char *,const char *,uint32_t),void * cookie)121 void __system_property_read_callback(const prop_info* pi,
122                                      void (*callback)(void*, const char*, const char*, uint32_t),
123                                      void* cookie) {
124     std::lock_guard lock(g_properties_lock);
125     callback(cookie, pi->key.c_str(), pi->value.c_str(), pi->serial);
126 }
127 
128 } // extern "C"
129 
130 // ---- JNI ----
131 
132 static JavaVM* gVM = nullptr;
133 static jclass gRunnerState = nullptr;
134 static jmethodID gCheckSystemPropertyAccess;
135 
reloadNativeLibrary(JNIEnv * env,jclass,jstring javaPath)136 static void reloadNativeLibrary(JNIEnv* env, jclass, jstring javaPath) {
137     ScopedUtfChars path(env, javaPath);
138     // Force reload ourselves as global
139     dlopen(path.c_str(), RTLD_LAZY | RTLD_GLOBAL | RTLD_NOLOAD);
140 }
141 
142 // Call back into Java code to check property access
check_system_property_access(const char * key,bool write)143 static void check_system_property_access(const char* key, bool write) {
144     if (gVM != nullptr && gRunnerState != nullptr) {
145         JNIEnv* env;
146         if (gVM->GetEnv((void**)&env, JNI_VERSION_1_4) >= 0) {
147             ALOGV("%s access to system property '%s'", write ? "Write" : "Read", key);
148             env->CallStaticVoidMethod(gRunnerState, gCheckSystemPropertyAccess,
149                                       env->NewStringUTF(key), write ? JNI_TRUE : JNI_FALSE);
150             return;
151         }
152     }
153     // Not on JVM thread, abort
154     LOG_ALWAYS_FATAL("Access to system property '%s' on non-JVM threads is not allowed.", key);
155 }
156 
getSystemProperty(JNIEnv * env,jclass,jstring javaKey)157 static jstring getSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
158     ScopedUtfChars key(env, javaKey);
159     jstring value = nullptr;
160     property_get(key.c_str(),
161                  [&](const prop_info& info) { value = env->NewStringUTF(info.value.c_str()); });
162     return value;
163 }
164 
setSystemProperty(JNIEnv * env,jclass,jstring javaKey,jstring javaValue)165 static jboolean setSystemProperty(JNIEnv* env, jclass, jstring javaKey, jstring javaValue) {
166     ScopedUtfChars key(env, javaKey);
167     ScopedUtfChars value(env, javaValue);
168     return property_set(key.c_str(), value.c_str()) ? JNI_TRUE : JNI_FALSE;
169 }
170 
removeSystemProperty(JNIEnv * env,jclass,jstring javaKey)171 static jboolean removeSystemProperty(JNIEnv* env, jclass, jstring javaKey) {
172     std::lock_guard lock(g_properties_lock);
173 
174     if (javaKey == nullptr) {
175         g_properties.clear();
176         return JNI_TRUE;
177     } else {
178         ScopedUtfChars key(env, javaKey);
179         auto it = g_properties.find(key);
180         if (it != g_properties.end()) {
181             g_properties.erase(it);
182             return JNI_TRUE;
183         } else {
184             return JNI_FALSE;
185         }
186     }
187 }
188 
189 // Find the PPID of child_pid using /proc/N/stat. The 4th field is the PPID.
190 // Also returns child_pid's process name (2nd field).
getppid_of(pid_t child_pid,std::string & out_process_name)191 static pid_t getppid_of(pid_t child_pid, std::string& out_process_name) {
192     if (child_pid < 0) {
193         return -1;
194     }
195     std::string stat_file = "/proc/" + std::to_string(child_pid) + "/stat";
196     std::ifstream stat_stream(stat_file);
197     if (!stat_stream.is_open()) {
198         ALOGW("Unable to open '%s': %s", stat_file.c_str(), strerror(errno));
199         return -1;
200     }
201 
202     std::string field;
203     int field_count = 0;
204     while (std::getline(stat_stream, field, ' ')) {
205         if (++field_count == 4) {
206             return atoi(field.c_str());
207         }
208         if (field_count == 2) {
209             out_process_name = field;
210         }
211     }
212     ALOGW("Unexpected format in '%s'", stat_file.c_str());
213     return -1;
214 }
215 
216 // Find atest's PID. Climb up the process tree, and find "atest-py3".
find_atest_pid()217 static pid_t find_atest_pid() {
218     auto ret = getpid(); // self (isolation runner process)
219 
220     while (ret != -1) {
221         std::string proc;
222         ret = getppid_of(ret, proc);
223         if (proc == "(atest-py3)") {
224             return ret;
225         }
226     }
227 
228     return ret;
229 }
230 
231 // If $RAVENWOOD_LOG_OUT is set, redirect stdout/err to this file.
232 // Originally it was added to allow to monitor log in realtime, with
233 // RAVENWOOD_LOG_OUT=$(tty) atest...
234 //
235 // As a special case, if $RAVENWOOD_LOG_OUT is set to "-", we try to find
236 // atest's process and send the output to its stdout. It's sort of hacky, but
237 // this allows shell redirection to work on Ravenwood output too,
238 // so e.g. `atest ... |tee atest.log` would work on Ravenwood's output.
239 // (which wouldn't work with `RAVENWOOD_LOG_OUT=$(tty)`).
240 //
241 // Otherwise -- if $RAVENWOOD_LOG_OUT isn't set -- atest/tradefed just writes
242 // the test's output to its own log file.
maybeRedirectLog()243 static void maybeRedirectLog() {
244     auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
245     if (ravenwoodLogOut == NULL || *ravenwoodLogOut == '\0') {
246         return;
247     }
248     std::string path;
249     if (strcmp("-", ravenwoodLogOut) == 0) {
250         pid_t ppid = find_atest_pid();
251         if (ppid < 0) {
252             ALOGI("RAVENWOOD_LOG_OUT set to '-', but unable to find atest's PID");
253             return;
254         }
255         path = std::format("/proc/{}/fd/1", ppid);
256     } else {
257         path = ravenwoodLogOut;
258     }
259     ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to '%s'", path.c_str());
260 
261     // Redirect stdin / stdout to /dev/tty.
262     int ttyFd = open(path.c_str(), O_WRONLY | O_APPEND);
263     if (ttyFd == -1) {
264         ALOGW("$RAVENWOOD_LOG_OUT is set, but failed to open '%s': %s ", path.c_str(),
265                 strerror(errno));
266         return;
267     }
268     dup2(ttyFd, 1);
269     dup2(ttyFd, 2);
270 }
271 
272 static const JNINativeMethod sMethods[] = {
reloadNativeLibrary(Ljava/lang/String;)273         {"reloadNativeLibrary", "(Ljava/lang/String;)V", (void*)reloadNativeLibrary},
getSystemProperty(Ljava/lang/String;)274         {"getSystemProperty", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getSystemProperty},
setSystemProperty(Ljava/lang/String;Ljava/lang/String;)275         {"setSystemProperty", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)setSystemProperty},
removeSystemProperty(Ljava/lang/String;)276         {"removeSystemProperty", "(Ljava/lang/String;)Z", (void*)removeSystemProperty},
277 };
278 
JNI_OnLoad(JavaVM * vm,void *)279 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
280     ALOGV("%s: JNI_OnLoad", __FILE__);
281 
282     maybeRedirectLog();
283 
284     JNIEnv* env = GetJNIEnvOrDie(vm);
285     gVM = vm;
286 
287     // Fetch several references for future use
288     gRunnerState = FindGlobalClassOrDie(env, kRunnerState);
289     gCheckSystemPropertyAccess =
290             GetStaticMethodIDOrDie(env, gRunnerState, "checkSystemPropertyAccess",
291                                    "(Ljava/lang/String;Z)V");
292 
293     // Expose raw property methods as JNI methods
294     jint res = jniRegisterNativeMethods(env, kRuntimeNative, sMethods, NELEM(sMethods));
295     if (res < 0) return -1;
296 
297     return JNI_VERSION_1_4;
298 }
299