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 // See: ApplicationSharedMemory.md
18
19 #include <cutils/ashmem.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <nativehelper/JNIHelp.h>
23 #include <string.h>
24 #include <sys/mman.h>
25
26 #include <array>
27 #include <atomic>
28 #include <cstddef>
29 #include <new>
30
31 #include "android_app_PropertyInvalidatedCache.h"
32 #include "core_jni_helpers.h"
33
34 namespace {
35
36 using namespace android::app::PropertyInvalidatedCache;
37
38 class alignas(8) SystemFeaturesCache {
39 public:
40 // We only need enough space to handle the official set of SDK-defined system features (~200).
41 // TODO(b/326623529): Reuse the exact value defined by PackageManager.SDK_FEATURE_COUNT.
42 static constexpr int32_t kMaxSystemFeatures = 512;
43
writeSystemFeatures(JNIEnv * env,jintArray jfeatures)44 void writeSystemFeatures(JNIEnv* env, jintArray jfeatures) {
45 if (featuresLength.load(std::memory_order_seq_cst) > 0) {
46 jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
47 "SystemFeaturesCache already written.");
48 return;
49 }
50
51 int32_t jfeaturesLength = env->GetArrayLength(jfeatures);
52 if (jfeaturesLength > kMaxSystemFeatures) {
53 jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
54 "SystemFeaturesCache only supports %d elements (vs %d requested).",
55 kMaxSystemFeatures, jfeaturesLength);
56 return;
57 }
58 env->GetIntArrayRegion(jfeatures, 0, jfeaturesLength, features.data());
59 featuresLength.store(jfeaturesLength, std::memory_order_seq_cst);
60 }
61
readSystemFeatures(JNIEnv * env) const62 jintArray readSystemFeatures(JNIEnv* env) const {
63 jint jfeaturesLength = static_cast<jint>(featuresLength.load(std::memory_order_seq_cst));
64 jintArray jfeatures = env->NewIntArray(jfeaturesLength);
65 if (env->ExceptionCheck()) {
66 return nullptr;
67 }
68
69 env->SetIntArrayRegion(jfeatures, 0, jfeaturesLength, features.data());
70 return jfeatures;
71 }
72
73 private:
74 // A fixed length array of feature versions, with |featuresLength| dictating the actual size
75 // of features that have been written.
76 std::array<int32_t, kMaxSystemFeatures> features = {};
77 // The atomic acts as a barrier that precedes reads and follows writes, ensuring a
78 // consistent view of |features| across processes. Note that r/w synchronization *within* a
79 // process is handled at a higher level.
80 std::atomic<int64_t> featuresLength = 0;
81 };
82
83 static_assert(sizeof(SystemFeaturesCache) ==
84 sizeof(int32_t) * SystemFeaturesCache::kMaxSystemFeatures + sizeof(int64_t),
85 "Unexpected SystemFeaturesCache size");
86
87 // Atomics should be safe to use across processes if they are lock free.
88 static_assert(std::atomic<int64_t>::is_always_lock_free == true,
89 "atomic<int64_t> is not always lock free");
90
91 // This is the data structure that is shared between processes.
92 //
93 // Tips for extending:
94 // - Atomics are safe for cross-process use as they are lock free, if they are accessed as
95 // individual values.
96 // - Consider multi-ABI systems, e.g. devices that support launching both 64-bit and 32-bit
97 // app processes. Use fixed-size types (e.g. `int64_t`) to ensure that the data structure is
98 // the same size across all ABIs. Avoid implicit assumptions about struct packing/padding.
99 class alignas(8) SharedMemory { // Ensure that `sizeof(SharedMemory)` is the same across 32-bit and
100 // 64-bit systems.
101 private:
102 volatile std::atomic<int64_t> latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis;
103
104 // LINT.IfChange(invalid_network_time)
105 static constexpr int64_t INVALID_NETWORK_TIME = -1;
106 // LINT.ThenChange(frameworks/base/core/java/com/android/internal/os/ApplicationSharedMemory.java:invalid_network_time)
107
108 public:
109 // Default constructor sets initial values
SharedMemory()110 SharedMemory()
111 : latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(INVALID_NETWORK_TIME) {}
112
getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis() const113 int64_t getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis() const {
114 return latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis;
115 }
116
setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(int64_t offset)117 void setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(int64_t offset) {
118 latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis = offset;
119 }
120
121 // The fixed size cache storage for SDK-defined system features.
122 SystemFeaturesCache systemFeaturesCache;
123
124 // The nonce storage for pic. The sizing is suitable for the system server module.
125 SystemCacheNonce systemPic;
126 };
127
128 // Update the expected values when modifying the members of SharedMemory.
129 // The goal of this assertion is to ensure that the data structure is the same size across 32-bit
130 // and 64-bit systems.
131 // TODO(b/396674280): Add an additional fixed size check for SystemCacheNonce after resolving
132 // ABI discrepancies.
133 static_assert(sizeof(SharedMemory) == 8 + sizeof(SystemFeaturesCache) + sizeof(SystemCacheNonce),
134 "Unexpected SharedMemory size");
135 static_assert(offsetof(SharedMemory, systemFeaturesCache) == sizeof(int64_t),
136 "Unexpected SystemFeaturesCache offset in SharedMemory");
137 static_assert(offsetof(SharedMemory, systemPic) ==
138 offsetof(SharedMemory, systemFeaturesCache) + sizeof(SystemFeaturesCache),
139 "Unexpected SystemCachceNonce offset in SharedMemory");
140
nativeCreate(JNIEnv * env,jclass)141 static jint nativeCreate(JNIEnv* env, jclass) {
142 // Create anonymous shared memory region
143 int fd = ashmem_create_region("ApplicationSharedMemory", sizeof(SharedMemory));
144 if (fd < 0) {
145 jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Failed to create ashmem: %s",
146 strerror(errno));
147 }
148 return fd;
149 }
150
nativeMap(JNIEnv * env,jclass,jint fd,jboolean isMutable)151 static jlong nativeMap(JNIEnv* env, jclass, jint fd, jboolean isMutable) {
152 void* ptr = mmap(nullptr, sizeof(SharedMemory), isMutable ? PROT_READ | PROT_WRITE : PROT_READ,
153 MAP_SHARED, fd, 0);
154 if (ptr == MAP_FAILED) {
155 close(fd);
156 jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Failed to mmap shared memory: %s",
157 strerror(errno));
158 }
159
160 return reinterpret_cast<jlong>(ptr);
161 }
162
nativeInit(JNIEnv * env,jclass,jlong ptr)163 static void nativeInit(JNIEnv* env, jclass, jlong ptr) {
164 new (reinterpret_cast<SharedMemory*>(ptr)) SharedMemory();
165 }
166
nativeUnmap(JNIEnv * env,jclass,jlong ptr)167 static void nativeUnmap(JNIEnv* env, jclass, jlong ptr) {
168 if (munmap(reinterpret_cast<void*>(ptr), sizeof(SharedMemory)) == -1) {
169 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
170 "Failed to munmap shared memory: %s", strerror(errno));
171 }
172 }
173
nativeDupAsReadOnly(JNIEnv * env,jclass,jint fd)174 static jint nativeDupAsReadOnly(JNIEnv* env, jclass, jint fd) {
175 // Duplicate file descriptor
176 fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
177 if (fd < 0) {
178 jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Failed to dup fd: %s",
179 strerror(errno));
180 }
181
182 // Set new file descriptor to read-only
183 if (ashmem_set_prot_region(fd, PROT_READ)) {
184 close(fd);
185 jniThrowExceptionFmt(env, "java/lang/RuntimeException",
186 "Failed to ashmem_set_prot_region: %s", strerror(errno));
187 }
188
189 return fd;
190 }
191
nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(jlong ptr,jlong offset)192 static void nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(jlong ptr,
193 jlong offset) {
194 SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
195 sharedMemory->setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(offset);
196 }
197
nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(jlong ptr)198 static jlong nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(jlong ptr) {
199 SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
200 return sharedMemory->getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
201 }
202
203 // This is a FastNative method. It takes the usual JNIEnv* and jclass* arguments.
nativeGetSystemNonceBlock(JNIEnv *,jclass *,jlong ptr)204 static jlong nativeGetSystemNonceBlock(JNIEnv*, jclass*, jlong ptr) {
205 SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
206 return reinterpret_cast<jlong>(&sharedMemory->systemPic);
207 }
208
nativeWriteSystemFeaturesCache(JNIEnv * env,jclass *,jlong ptr,jintArray jfeatures)209 static void nativeWriteSystemFeaturesCache(JNIEnv* env, jclass*, jlong ptr, jintArray jfeatures) {
210 SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
211 sharedMemory->systemFeaturesCache.writeSystemFeatures(env, jfeatures);
212 }
213
nativeReadSystemFeaturesCache(JNIEnv * env,jclass *,jlong ptr)214 static jintArray nativeReadSystemFeaturesCache(JNIEnv* env, jclass*, jlong ptr) {
215 SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
216 return sharedMemory->systemFeaturesCache.readSystemFeatures(env);
217 }
218
219 static const JNINativeMethod gMethods[] = {
nativeCreate()220 {"nativeCreate", "()I", (void*)nativeCreate},
nativeMap(IZ)221 {"nativeMap", "(IZ)J", (void*)nativeMap},
nativeInit(J)222 {"nativeInit", "(J)V", (void*)nativeInit},
nativeUnmap(J)223 {"nativeUnmap", "(J)V", (void*)nativeUnmap},
nativeDupAsReadOnly(I)224 {"nativeDupAsReadOnly", "(I)I", (void*)nativeDupAsReadOnly},
nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(JJ)225 {"nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis", "(JJ)V",
226 (void*)nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(J)227 {"nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis", "(J)J",
228 (void*)nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
nativeGetSystemNonceBlock(J)229 {"nativeGetSystemNonceBlock", "(J)J", (void*)nativeGetSystemNonceBlock},
nativeWriteSystemFeaturesCache(J[I)230 {"nativeWriteSystemFeaturesCache", "(J[I)V", (void*)nativeWriteSystemFeaturesCache},
nativeReadSystemFeaturesCache(J)231 {"nativeReadSystemFeaturesCache", "(J)[I", (void*)nativeReadSystemFeaturesCache},
232 };
233
234 static const char kApplicationSharedMemoryClassName[] =
235 "com/android/internal/os/ApplicationSharedMemory";
236 static jclass gApplicationSharedMemoryClass;
237
238 } // anonymous namespace
239
240 namespace android {
241
register_com_android_internal_os_ApplicationSharedMemory(JNIEnv * env)242 int register_com_android_internal_os_ApplicationSharedMemory(JNIEnv* env) {
243 gApplicationSharedMemoryClass =
244 MakeGlobalRefOrDie(env, FindClassOrDie(env, kApplicationSharedMemoryClassName));
245 RegisterMethodsOrDie(env, "com/android/internal/os/ApplicationSharedMemory", gMethods,
246 NELEM(gMethods));
247 return JNI_OK;
248 }
249
250 } // namespace android
251