• 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 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