dynsystem: Fix memory leak

Right now the MemoryFile object isn't closed after use, and dangling
file descriptors are leaked into memory each time a MemoryFile object
is created.

Replace android.os.MemoryFile with android.os.SharedMemory and use
try-with-resources to auto manage the opened file descriptors and mapped
memory regions.

Bug: 225310919
Test: Start a DSU installation and check /proc/$pid/maps
Change-Id: I15d9f658c926ef0a46fb558215bfab40f76ddc9a
This commit is contained in:
Yi-Yo Chiang
2022-03-20 17:08:36 +08:00
parent 9b420e664a
commit bf8d20acb7

View File

@@ -16,17 +16,18 @@
package com.android.dynsystem;
import android.annotation.NonNull;
import android.content.Context;
import android.gsi.AvbPublicKey;
import android.gsi.IGsiService;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.os.SharedMemory;
import android.os.SystemProperties;
import android.os.image.DynamicSystemManager;
import android.service.persistentdata.PersistentDataBlockManager;
import android.system.ErrnoException;
import android.util.Log;
import android.util.Pair;
import android.util.Range;
@@ -39,6 +40,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
@@ -154,6 +156,22 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
void onResult(int resultCode, Throwable detail);
}
private static class MappedMemoryBuffer implements AutoCloseable {
public ByteBuffer mBuffer;
MappedMemoryBuffer(@NonNull ByteBuffer buffer) {
mBuffer = buffer;
}
@Override
public void close() {
if (mBuffer != null) {
SharedMemory.unmap(mBuffer);
mBuffer = null;
}
}
}
private final int mSharedMemorySize;
private final String mUrl;
private final String mDsuSlot;
@@ -674,59 +692,66 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
Log.d(TAG, "Start installing: " + partitionName);
MemoryFile memoryFile = new MemoryFile("dsu_" + partitionName, mSharedMemorySize);
ParcelFileDescriptor pfd = new ParcelFileDescriptor(memoryFile.getFileDescriptor());
mInstallationSession.setAshmem(pfd, memoryFile.length());
initPartitionProgress(partitionName, partitionSize, /* readonly = */ true);
publishProgress(/* installedSize = */ 0L);
long prevInstalledSize = 0;
long installedSize = 0;
byte[] bytes = new byte[memoryFile.length()];
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Boolean> submitPromise = null;
try (SharedMemory sharedMemory =
SharedMemory.create("dsu_buffer_" + partitionName, mSharedMemorySize);
MappedMemoryBuffer mappedBuffer =
new MappedMemoryBuffer(sharedMemory.mapReadWrite())) {
mInstallationSession.setAshmem(sharedMemory.getFdDup(), sharedMemory.getSize());
while (true) {
final int numBytesRead = sis.read(bytes, 0, bytes.length);
initPartitionProgress(partitionName, partitionSize, /* readonly = */ true);
publishProgress(/* installedSize = */ 0L);
if (submitPromise != null) {
// Wait until the previous submit task is complete.
while (true) {
try {
if (!submitPromise.get()) {
throw new IOException("Failed submitFromAshmem() to DynamicSystem");
long installedSize = 0;
byte[] readBuffer = new byte[sharedMemory.getSize()];
ByteBuffer buffer = mappedBuffer.mBuffer;
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Boolean> submitPromise = null;
while (true) {
final int numBytesRead = sis.read(readBuffer, 0, readBuffer.length);
if (submitPromise != null) {
// Wait until the previous submit task is complete.
while (true) {
try {
if (!submitPromise.get()) {
throw new IOException("Failed submitFromAshmem() to DynamicSystem");
}
break;
} catch (InterruptedException e) {
// Ignore.
}
break;
} catch (InterruptedException e) {
// Ignore.
}
// Publish the progress of the previous submit task.
if (installedSize > prevInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
publishProgress(installedSize);
prevInstalledSize = installedSize;
}
}
// Publish the progress of the previous submit task.
if (installedSize > prevInstalledSize + MIN_PROGRESS_TO_PUBLISH) {
publishProgress(installedSize);
prevInstalledSize = installedSize;
// Ensure the previous submit task (submitPromise) is complete before exiting the
// loop.
if (numBytesRead < 0) {
break;
}
if (isCancelled()) {
return;
}
buffer.position(0);
buffer.put(readBuffer, 0, numBytesRead);
submitPromise =
executor.submit(() -> mInstallationSession.submitFromAshmem(numBytesRead));
// Even though we update the bytes counter here, the actual progress is updated only
// after the submit task (submitPromise) is complete.
installedSize += numBytesRead;
}
// Ensure the previous submit task (submitPromise) is complete before exiting the loop.
if (numBytesRead < 0) {
break;
}
if (isCancelled()) {
return;
}
memoryFile.writeBytes(bytes, 0, 0, numBytesRead);
submitPromise =
executor.submit(() -> mInstallationSession.submitFromAshmem(numBytesRead));
// Even though we update the bytes counter here, the actual progress is updated only
// after the submit task (submitPromise) is complete.
installedSize += numBytesRead;
} catch (ErrnoException e) {
e.rethrowAsIOException();
}
AvbPublicKey avbPublicKey = new AvbPublicKey();