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 package com.android.internal.os; 18 19 import android.annotation.NonNull; 20 import android.util.Log; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import dalvik.annotation.optimization.CriticalNative; 25 import dalvik.annotation.optimization.FastNative; 26 27 import libcore.io.IoUtils; 28 29 import java.io.FileDescriptor; 30 import java.io.IOException; 31 import java.time.DateTimeException; 32 33 /** 34 * This class is used to create and access a shared memory region. 35 * 36 * <p>The intended use case is that memory is shared between system processes and application 37 * processes such that it's readable to all apps and writable only to system processes. 38 * 39 * <p>This shared memory region can be used as an alternative to Binder IPC for driving 40 * communication between system processes and application processes at a lower latency and higher 41 * throughput than Binder IPC can provide, under circumstances where the additional features of 42 * Binder IPC are not required. 43 * 44 * <p>Unlike Binder IPC, shared memory doesn't support synchronous transactions and associated 45 * ordering guarantees, client identity (and therefore caller permission checking), and access 46 * auditing. Therefore it's not a suitable alternative to Binder IPC for most use cases. 47 * 48 * <p>Additionally, because the intended use case is to make this shared memory region readable to 49 * all apps, it's not suitable for sharing sensitive data. 50 * 51 * @see {@link ApplicationSharedMemoryTestRule} for unit testing support. 52 * @hide 53 */ 54 public class ApplicationSharedMemory implements AutoCloseable { 55 56 // LINT.IfChange(invalid_network_time) 57 public static final long INVALID_NETWORK_TIME = -1; 58 // LINT.ThenChange(frameworks/base/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp:invalid_network_time) 59 60 private static final boolean DEBUG = false; 61 private static final String LOG_TAG = "ApplicationSharedMemory"; 62 63 @VisibleForTesting public static ApplicationSharedMemory sInstance; 64 65 /** Get the process-global instance. */ getInstance()66 public static ApplicationSharedMemory getInstance() { 67 ApplicationSharedMemory instance = sInstance; 68 if (instance == null) { 69 throw new IllegalStateException("ApplicationSharedMemory not initialized"); 70 } 71 return instance; 72 } 73 74 /** Set the process-global instance. */ setInstance(ApplicationSharedMemory instance)75 public static void setInstance(ApplicationSharedMemory instance) { 76 if (DEBUG) { 77 Log.d(LOG_TAG, "setInstance: " + instance); 78 } 79 80 if (sInstance != null) { 81 throw new IllegalStateException("ApplicationSharedMemory already initialized"); 82 } 83 sInstance = instance; 84 } 85 86 /** Allocate mutable shared memory region. */ create()87 public static ApplicationSharedMemory create() { 88 if (DEBUG) { 89 Log.d(LOG_TAG, "create"); 90 } 91 92 int fd = nativeCreate(); 93 FileDescriptor fileDescriptor = new FileDescriptor(); 94 fileDescriptor.setInt$(fd); 95 96 final boolean mutable = true; 97 long ptr = nativeMap(fd, mutable); 98 nativeInit(ptr); 99 100 return new ApplicationSharedMemory(fileDescriptor, mutable, ptr); 101 } 102 103 /** 104 * Open shared memory region from a given {@link FileDescriptor}. 105 * 106 * @param fileDescriptor Handle to shared memory region. 107 * @param mutable Whether the shared memory region is mutable. If true, will be mapped as 108 * read-write memory. If false, will be mapped as read-only memory. Passing true (mutable) 109 * if |pfd| is a handle to read-only memory will result in undefined behavior. 110 */ fromFileDescriptor( @onNull FileDescriptor fileDescriptor, boolean mutable)111 public static ApplicationSharedMemory fromFileDescriptor( 112 @NonNull FileDescriptor fileDescriptor, boolean mutable) { 113 if (DEBUG) { 114 Log.d(LOG_TAG, "fromFileDescriptor: " + fileDescriptor + " mutable: " + mutable); 115 } 116 117 long ptr = nativeMap(fileDescriptor.getInt$(), mutable); 118 return new ApplicationSharedMemory(fileDescriptor, mutable, ptr); 119 } 120 121 /** 122 * Allocate read-write shared memory region. 123 * 124 * @return File descriptor of the shared memory region. 125 */ nativeCreate()126 private static native int nativeCreate(); 127 128 /** 129 * Map the shared memory region. 130 * 131 * @param fd File descriptor of the shared memory region. 132 * @param isMutable Whether the shared memory region is mutable. If true, will be mapped as 133 * read-write memory. If false, will be mapped as read-only memory. 134 * @return Pointer to the mapped shared memory region. 135 */ nativeMap(int fd, boolean isMutable)136 private static native long nativeMap(int fd, boolean isMutable); 137 138 /** 139 * Initialize read-write shared memory region. 140 * 141 * @param Pointer to the mapped shared memory region. 142 */ nativeInit(long ptr)143 private static native void nativeInit(long ptr); 144 145 /** 146 * Unmap the shared memory region. 147 * 148 * @param ptr Pointer to the mapped shared memory region. 149 */ nativeUnmap(long ptr)150 private static native void nativeUnmap(long ptr); 151 152 /** 153 * If true, this object owns the read-write instance of the shared memory region. If false, this 154 * object can only read. 155 */ 156 private final boolean mMutable; 157 158 /** 159 * Handle to the shared memory region. This can be send to other processes over Binder calls or 160 * Intent extras. Recipients can use this handle to obtain read-only access to the shared memory 161 * region. 162 */ 163 private FileDescriptor mFileDescriptor; 164 165 /** Native pointer to the mapped shared memory region. */ 166 private volatile long mPtr; 167 ApplicationSharedMemory(@onNull FileDescriptor fileDescriptor, boolean mutable, long ptr)168 ApplicationSharedMemory(@NonNull FileDescriptor fileDescriptor, boolean mutable, long ptr) { 169 mFileDescriptor = fileDescriptor; 170 mMutable = mutable; 171 mPtr = ptr; 172 } 173 174 /** 175 * Returns the file descriptor of the shared memory region. 176 * 177 * <p>This file descriptor retains the mutability properties of this object instance, and can be 178 * sent over Binder IPC or Intent extras to another process to allow the remote process to map 179 * the same shared memory region with the same access rights. 180 * 181 * @throws IllegalStateException if the file descriptor is closed. 182 */ getFileDescriptor()183 public FileDescriptor getFileDescriptor() { 184 checkFileOpen(); 185 return mFileDescriptor; 186 } 187 188 /** 189 * Returns a read-only file descriptor of the shared memory region. This object can be sent over 190 * Binder IPC or Intent extras to another process to allow the remote process to map the same 191 * shared memory region with read-only access. 192 * 193 * @return a read-only handle to the shared memory region. 194 * @throws IllegalStateException if the file descriptor is closed. 195 */ getReadOnlyFileDescriptor()196 public FileDescriptor getReadOnlyFileDescriptor() throws IOException { 197 checkFileOpen(); 198 FileDescriptor readOnlyFileDescriptor = new FileDescriptor(); 199 int readOnlyFd = nativeDupAsReadOnly(mFileDescriptor.getInt$()); 200 readOnlyFileDescriptor.setInt$(readOnlyFd); 201 return readOnlyFileDescriptor; 202 } 203 204 /** Return a read-only duplicate of the file descriptor. */ nativeDupAsReadOnly(int fd)205 private static native int nativeDupAsReadOnly(int fd); 206 207 /** Set the latest network Unix Epoch minus realtime millis. */ setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(long offset)208 public void setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(long offset) { 209 checkMutable(); 210 nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(mPtr, offset); 211 } 212 213 /** Clear the latest network Unix Epoch minus realtime millis. */ clearLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()214 public void clearLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis() { 215 checkMutable(); 216 nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis( 217 mPtr, INVALID_NETWORK_TIME); 218 } 219 220 @CriticalNative nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis( long ptr, long offset)221 private static native void nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis( 222 long ptr, long offset); 223 224 /** 225 * Get the latest network Unix Epoch minus realtime millis. 226 * 227 * @throws DateTimeException when no network time can be provided. 228 */ getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis()229 public long getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis() 230 throws DateTimeException { 231 checkMapped(); 232 long offset = nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(mPtr); 233 if (offset == INVALID_NETWORK_TIME) { 234 throw new DateTimeException("No network time available"); 235 } 236 return offset; 237 } 238 239 @CriticalNative nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis( long ptr)240 public static native long nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis( 241 long ptr); 242 243 /** 244 * Close the associated file descriptor. 245 * 246 * <p>This method is safe to call if you never intend to pass the file descriptor to another 247 * process, whether via {@link #getFileDescriptor()} or {@link #getReadOnlyFileDescriptor()}. 248 * After calling this method, subsequent calls to {@link #getFileDescriptor()} or {@link 249 * #getReadOnlyFileDescriptor()} will throw an {@link IllegalStateException}. 250 */ closeFileDescriptor()251 public void closeFileDescriptor() { 252 if (mFileDescriptor != null) { 253 IoUtils.closeQuietly(mFileDescriptor); 254 mFileDescriptor = null; 255 } 256 } 257 close()258 public void close() { 259 if (mPtr != 0) { 260 nativeUnmap(mPtr); 261 mPtr = 0; 262 } 263 264 if (mFileDescriptor != null) { 265 IoUtils.closeQuietly(mFileDescriptor); 266 mFileDescriptor = null; 267 } 268 } 269 checkFileOpen()270 private void checkFileOpen() { 271 if (mFileDescriptor == null) { 272 throw new IllegalStateException("File descriptor is closed"); 273 } 274 } 275 276 /** 277 * Check that the shared memory region is mapped. 278 * 279 * @throws IllegalStateException if the shared memory region is not mapped. 280 */ checkMapped()281 private void checkMapped() { 282 if (mPtr == 0) { 283 throw new IllegalStateException("Instance is closed"); 284 } 285 } 286 287 /** 288 * Check that the shared memory region is mapped and mutable. 289 * 290 * @throws IllegalStateException if the shared memory region is not mapped or not mutable. 291 */ checkMutable()292 private void checkMutable() { 293 checkMapped(); 294 if (!mMutable) { 295 throw new IllegalStateException("Not mutable"); 296 } 297 } 298 299 /** 300 * Return true if the memory has been mapped. This never throws. 301 */ isMapped()302 public boolean isMapped() { 303 return mPtr != 0; 304 } 305 306 /** 307 * Return true if the memory is mapped and mutable. This never throws. Note that it returns 308 * false if the memory is not mapped. 309 */ isMutable()310 public boolean isMutable() { 311 return isMapped() && mMutable; 312 } 313 314 /** 315 * Provide access to the nonce block needed by {@link PropertyInvalidatedCache}. This method 316 * returns 0 if the shared memory is not (yet) mapped. 317 */ getSystemNonceBlock()318 public long getSystemNonceBlock() { 319 return isMapped() ? nativeGetSystemNonceBlock(mPtr) : 0; 320 } 321 322 /** 323 * Return a pointer to the system nonce cache in the shared memory region. The method is 324 * idempotent. 325 */ 326 @FastNative nativeGetSystemNonceBlock(long ptr)327 private static native long nativeGetSystemNonceBlock(long ptr); 328 329 /** 330 * Perform a one-time write of cached SDK feature versions. 331 * 332 * @throws IllegalStateException if the feature versions have already been written or the ashmem 333 * is immutable. 334 * @throws IllegalArgumentException if the provided feature version array is too large. 335 */ writeSystemFeaturesCache(@onNull int[] featureVersions)336 public void writeSystemFeaturesCache(@NonNull int[] featureVersions) { 337 checkMutable(); 338 nativeWriteSystemFeaturesCache(mPtr, featureVersions); 339 } 340 341 /** 342 * Read the cached SDK feature versions previously written to shared memory. 343 * 344 * Note: The result should generally be cached elsewhere for global reuse. 345 */ 346 // TODO(b/326623529): Consider using a MappedByteBuffer or equivalent to avoid needing a 347 // Java copy of the cached data for potentially frequent reads. Alternatively, the JNI query 348 // lookup for a given feature could be cheap enough to avoid the cached Java copy entirely. readSystemFeaturesCache()349 public @NonNull int[] readSystemFeaturesCache() { 350 checkMapped(); 351 return nativeReadSystemFeaturesCache(mPtr); 352 } 353 354 @FastNative nativeWriteSystemFeaturesCache(long ptr, int[] cache)355 private static native void nativeWriteSystemFeaturesCache(long ptr, int[] cache); 356 357 @FastNative nativeReadSystemFeaturesCache(long ptr)358 private static native int[] nativeReadSystemFeaturesCache(long ptr); 359 } 360