1 /*
2  * Copyright (C) 2017 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 android.os;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.system.ErrnoException;
23 import android.system.Os;
24 import android.system.OsConstants;
25 
26 import dalvik.system.VMRuntime;
27 
28 import java.io.Closeable;
29 import java.io.FileDescriptor;
30 import java.io.IOException;
31 import java.nio.ByteBuffer;
32 import java.nio.DirectByteBuffer;
33 import java.nio.NioUtils;
34 
35 import sun.misc.Cleaner;
36 
37 /**
38  * SharedMemory enables the creation, mapping, and protection control over anonymous shared memory.
39  */
40 public final class SharedMemory implements Parcelable, Closeable {
41 
42     private final FileDescriptor mFileDescriptor;
43     private final int mSize;
44     private final MemoryRegistration mMemoryRegistration;
45     private Cleaner mCleaner;
46 
SharedMemory(FileDescriptor fd)47     private SharedMemory(FileDescriptor fd) {
48         // This constructor is only used internally so it should be impossible to hit any of the
49         // exceptions unless something goes horribly wrong.
50         if (fd == null) {
51             throw new IllegalArgumentException(
52                     "Unable to create SharedMemory from a null FileDescriptor");
53         }
54         if (!fd.valid()) {
55             throw new IllegalArgumentException(
56                     "Unable to create SharedMemory from closed FileDescriptor");
57         }
58         mFileDescriptor = fd;
59         mSize = nGetSize(mFileDescriptor);
60         if (mSize <= 0) {
61             throw new IllegalArgumentException("FileDescriptor is not a valid ashmem fd");
62         }
63 
64         mMemoryRegistration = new MemoryRegistration(mSize);
65         mCleaner = Cleaner.create(mFileDescriptor,
66                 new Closer(mFileDescriptor.getInt$(), mMemoryRegistration));
67     }
68 
69     /**
70      * Creates an anonymous SharedMemory instance with the provided debug name and size. The name
71      * is only used for debugging purposes and can help identify what the shared memory is used
72      * for when inspecting memory maps for the processes that have mapped this SharedMemory
73      * instance.
74      *
75      * @param name The debug name to use for this SharedMemory instance. This can be null, however
76      *             a debug name is recommended to help identify memory usage when using tools
77      *             such as lsof or examining /proc/[pid]/maps
78      * @param size The size of the shared memory to create. Must be greater than 0.
79      * @return A SharedMemory instance of the requested size
80      * @throws ErrnoException if the requested allocation fails.
81      */
create(@ullable String name, int size)82     public static @NonNull SharedMemory create(@Nullable String name, int size)
83             throws ErrnoException {
84         if (size <= 0) {
85             throw new IllegalArgumentException("Size must be greater than zero");
86         }
87         return new SharedMemory(nCreate(name, size));
88     }
89 
checkOpen()90     private void checkOpen() {
91         if (!mFileDescriptor.valid()) {
92             throw new IllegalStateException("SharedMemory is closed");
93         }
94     }
95 
96     /**
97      * Creates an instance from existing shared memory passed as {@link ParcelFileDescriptor}.
98      *
99      * <p> The {@code fd} should be a shared memory created from
100        {@code SharedMemory or ASharedMemory}. This can be useful when shared memory is passed as
101        file descriptor through JNI or binder service implemented in cpp.
102      * <p> Note that newly created {@code SharedMemory} takes ownership of passed {@code fd} and
103      * the original {@code fd} becomes detached (Check {@link ParcelFileDescriptor#detachFd()}).
104      * If the caller wants to use the file descriptor after the call, the caller should duplicate
105      * the file descriptor (Check {@link ParcelFileDescriptor#dup()}) and pass the duped version
106      * instead.
107      *
108      * @param fd File descriptor of shared memory passed as {@link ParcelFileDescriptor}.
109      */
fromFileDescriptor(@onNull ParcelFileDescriptor fd)110     public static @NonNull SharedMemory fromFileDescriptor(@NonNull ParcelFileDescriptor fd) {
111         FileDescriptor f = new FileDescriptor();
112         f.setInt$(fd.detachFd());
113         return new SharedMemory(f);
114     }
115 
116     private static final int PROT_MASK = OsConstants.PROT_READ | OsConstants.PROT_WRITE
117             | OsConstants.PROT_EXEC | OsConstants.PROT_NONE;
118 
validateProt(int prot)119     private static void validateProt(int prot) {
120         if ((prot & ~PROT_MASK) != 0) {
121             throw new IllegalArgumentException("Invalid prot value");
122         }
123     }
124 
125     /**
126      * Sets the protection on the shared memory to the combination specified in prot, which
127      * is either a bitwise-or'd combination of {@link android.system.OsConstants#PROT_READ},
128      * {@link android.system.OsConstants#PROT_WRITE}, {@link android.system.OsConstants#PROT_EXEC}
129      * from {@link android.system.OsConstants}, or {@link android.system.OsConstants#PROT_NONE},
130      * to remove all further access.
131      *
132      * Note that protection can only ever be removed, not added. By default shared memory
133      * is created with protection set to PROT_READ | PROT_WRITE | PROT_EXEC. The protection
134      * passed here also only applies to any mappings created after calling this method. Existing
135      * mmaps of the shared memory retain whatever protection they had when they were created.
136      *
137      * A common usage of this is to share a read-only copy of the data with something else. To do
138      * that first create the read/write mapping with PROT_READ | PROT_WRITE,
139      * then call setProtect(PROT_READ) to remove write capability, then send the SharedMemory
140      * to another process. That process will only be able to mmap with PROT_READ.
141      *
142      * @param prot Any bitwise-or'ed combination of
143      *                  {@link android.system.OsConstants#PROT_READ},
144      *                  {@link android.system.OsConstants#PROT_WRITE}, and
145      *                  {@link android.system.OsConstants#PROT_EXEC}; or
146      *                  {@link android.system.OsConstants#PROT_NONE}
147      * @return Whether or not the requested protection was applied. Returns true on success,
148      * false if the requested protection was broader than the existing protection.
149      */
setProtect(int prot)150     public boolean setProtect(int prot) {
151         checkOpen();
152         validateProt(prot);
153         int errno = nSetProt(mFileDescriptor, prot);
154         return errno == 0;
155     }
156 
157     /**
158      * Returns the backing {@link FileDescriptor} for this SharedMemory object. The SharedMemory
159      * instance retains ownership of the FileDescriptor.
160      *
161      * This FileDescriptor is interoperable with the ASharedMemory NDK APIs.
162      *
163      * @return Returns the FileDescriptor associated with this object.
164      *
165      * @hide Exists only for MemoryFile interop
166      */
getFileDescriptor()167     public @NonNull FileDescriptor getFileDescriptor() {
168         return mFileDescriptor;
169     }
170 
171     /**
172      * Returns the backing native fd int for this SharedMemory object. The SharedMemory
173      * instance retains ownership of the fd.
174      *
175      * This fd is interoperable with the ASharedMemory NDK APIs.
176      *
177      * @return Returns the native fd associated with this object, or -1 if it is already closed.
178      *
179      * @hide Exposed for native ASharedMemory_dupFromJava()
180      */
181     @UnsupportedAppUsage(trackingBug = 171971817)
getFd()182     public int getFd() {
183         return mFileDescriptor.getInt$();
184     }
185 
186     /**
187      * @return The size of the SharedMemory region.
188      */
getSize()189     public int getSize() {
190         checkOpen();
191         return mSize;
192     }
193 
194     /**
195      * Creates a read/write mapping of the entire shared memory region. This requires the the
196      * protection level of the shared memory is at least PROT_READ|PROT_WRITE or the map will fail.
197      *
198      * Use {@link #map(int, int, int)} to have more control over the mapping if desired.
199      * This is equivalent to map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, getSize())
200      *
201      * @return A ByteBuffer mapping
202      * @throws ErrnoException if the mmap call failed.
203      */
mapReadWrite()204     public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {
205         return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);
206     }
207 
208     /**
209      * Creates a read-only mapping of the entire shared memory region. This requires the the
210      * protection level of the shared memory is at least PROT_READ or the map will fail.
211      *
212      * Use {@link #map(int, int, int)} to have more control over the mapping if desired.
213      * This is equivalent to map(OsConstants.PROT_READ, 0, getSize())
214      *
215      * @return A ByteBuffer mapping
216      * @throws ErrnoException if the mmap call failed.
217      */
mapReadOnly()218     public @NonNull ByteBuffer mapReadOnly() throws ErrnoException {
219         return map(OsConstants.PROT_READ, 0, mSize);
220     }
221 
222     /**
223      * Creates an mmap of the SharedMemory with the specified prot, offset, and length. This will
224      * always produce a new ByteBuffer window to the backing shared memory region. Every call
225      * to map() may be paired with a call to {@link #unmap(ByteBuffer)} when the ByteBuffer
226      * returned by map() is no longer needed.
227      *
228      * @param prot A bitwise-or'd combination of PROT_READ, PROT_WRITE, PROT_EXEC, or PROT_NONE.
229      * @param offset The offset into the shared memory to begin mapping. Must be >= 0 and less than
230      *         getSize().
231      * @param length The length of the region to map. Must be > 0 and offset + length must not
232      *         exceed getSize().
233      * @return A ByteBuffer mapping.
234      * @throws ErrnoException if the mmap call failed.
235      */
map(int prot, int offset, int length)236     public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
237         checkOpen();
238         validateProt(prot);
239         if (offset < 0) {
240             throw new IllegalArgumentException("Offset must be >= 0");
241         }
242         if (length <= 0) {
243             throw new IllegalArgumentException("Length must be > 0");
244         }
245         if (offset + length > mSize) {
246             throw new IllegalArgumentException("offset + length must not exceed getSize()");
247         }
248         long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);
249         boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;
250         Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());
251         return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
252     }
253 
254     /**
255      * Unmaps a buffer previously returned by {@link #map(int, int, int)}. This will immediately
256      * release the backing memory of the ByteBuffer, invalidating all references to it. Only
257      * call this method if there are no duplicates of the ByteBuffer in use and don't
258      * access the ByteBuffer after calling this method.
259      *
260      * @param buffer The buffer to unmap
261      */
unmap(@onNull ByteBuffer buffer)262     public static void unmap(@NonNull ByteBuffer buffer) {
263         if (buffer instanceof DirectByteBuffer) {
264             NioUtils.freeDirectBuffer(buffer);
265         } else {
266             throw new IllegalArgumentException(
267                     "ByteBuffer wasn't created by #map(int, int, int); can't unmap");
268         }
269     }
270 
271     /**
272      * Close the backing {@link FileDescriptor} of this SharedMemory instance. Note that all
273      * open mappings of the shared memory will remain valid and may continue to be used. The
274      * shared memory will not be freed until all file descriptor handles are closed and all
275      * memory mappings are unmapped.
276      */
277     @Override
close()278     public void close() {
279         mFileDescriptor.setInt$(-1);
280         if (mCleaner != null) {
281             mCleaner.clean();
282             mCleaner = null;
283         }
284     }
285 
286     @Override
describeContents()287     public int describeContents() {
288         return CONTENTS_FILE_DESCRIPTOR;
289     }
290 
291     @Override
writeToParcel(@onNull Parcel dest, int flags)292     public void writeToParcel(@NonNull Parcel dest, int flags) {
293         checkOpen();
294         dest.writeFileDescriptor(mFileDescriptor);
295     }
296 
297     /**
298      * Returns a dup'd ParcelFileDescriptor from the SharedMemory FileDescriptor.
299      * This obeys standard POSIX semantics, where the
300      * new file descriptor shared state such as file position with the
301      * original file descriptor.
302      * TODO: propose this method as a public or system API for next release to achieve parity with
303      *  NDK ASharedMemory_dupFromJava.
304      *
305      * @hide
306      */
getFdDup()307     public ParcelFileDescriptor getFdDup() throws IOException {
308         return ParcelFileDescriptor.dup(mFileDescriptor);
309     }
310 
311     public static final @android.annotation.NonNull Parcelable.Creator<SharedMemory> CREATOR =
312             new Parcelable.Creator<SharedMemory>() {
313         @Override
314         public SharedMemory createFromParcel(Parcel source) {
315             FileDescriptor descriptor = source.readRawFileDescriptor();
316             return new SharedMemory(descriptor);
317         }
318 
319         @Override
320         public SharedMemory[] newArray(int size) {
321             return new SharedMemory[size];
322         }
323     };
324 
325     /**
326      * Cleaner that closes the FD
327      */
328     private static final class Closer implements Runnable {
329         private int mFd;
330         private MemoryRegistration mMemoryReference;
331 
Closer(int fd, MemoryRegistration memoryReference)332         private Closer(int fd, MemoryRegistration memoryReference) {
333             mFd = fd;
334             mMemoryReference = memoryReference;
335         }
336 
337         @Override
run()338         public void run() {
339             try {
340                 FileDescriptor fd = new FileDescriptor();
341                 fd.setInt$(mFd);
342                 Os.close(fd);
343             } catch (ErrnoException e) { /* swallow error */ }
344             mMemoryReference.release();
345             mMemoryReference = null;
346         }
347     }
348 
349     /**
350      * Cleaner that munmap regions
351      */
352     private static final class Unmapper implements Runnable {
353         private long mAddress;
354         private int mSize;
355         private MemoryRegistration mMemoryReference;
356 
Unmapper(long address, int size, MemoryRegistration memoryReference)357         private Unmapper(long address, int size, MemoryRegistration memoryReference) {
358             mAddress = address;
359             mSize = size;
360             mMemoryReference = memoryReference;
361         }
362 
363         @Override
run()364         public void run() {
365             try {
366                 Os.munmap(mAddress, mSize);
367             } catch (ErrnoException e) { /* swallow exception */ }
368             mMemoryReference.release();
369             mMemoryReference = null;
370         }
371     }
372 
373     /**
374      * Helper class that ensures that the native allocation pressure against the VM heap stays
375      * active until the FD is closed as well as all mappings from that FD are closed.
376      */
377     private static final class MemoryRegistration {
378         private int mSize;
379         private int mReferenceCount;
380 
MemoryRegistration(int size)381         private MemoryRegistration(int size) {
382             mSize = size;
383             mReferenceCount = 1;
384             VMRuntime.getRuntime().registerNativeAllocation(mSize);
385         }
386 
acquire()387         public synchronized MemoryRegistration acquire() {
388             mReferenceCount++;
389             return this;
390         }
391 
release()392         public synchronized void release() {
393             mReferenceCount--;
394             if (mReferenceCount == 0) {
395                 VMRuntime.getRuntime().registerNativeFree(mSize);
396             }
397         }
398     }
399 
nCreate(String name, int size)400     private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;
nGetSize(FileDescriptor fd)401     private static native int nGetSize(FileDescriptor fd);
nSetProt(FileDescriptor fd, int prot)402     private static native int nSetProt(FileDescriptor fd, int prot);
403 }
404