[RESTRICT AUTOMERGE] Add protections agains use-after-free issues if cancel() or queue() is called after a device connection has been closed. am: 7a8d56b2fe

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20234866

Change-Id: Ib21057b8df8c9d5ad4719de60e9db7fb88765046
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Khoa Hong
2022-10-31 04:08:37 +00:00
committed by Automerger Merge Worker
2 changed files with 143 additions and 14 deletions

View File

@@ -52,6 +52,8 @@ public class UsbDeviceConnection {
private final CloseGuard mCloseGuard = CloseGuard.get(); private final CloseGuard mCloseGuard = CloseGuard.get();
private final Object mLock = new Object();
/** /**
* UsbDevice should only be instantiated by UsbService implementation * UsbDevice should only be instantiated by UsbService implementation
* @hide * @hide
@@ -62,6 +64,8 @@ public class UsbDeviceConnection {
/* package */ boolean open(String name, ParcelFileDescriptor pfd, @NonNull Context context) { /* package */ boolean open(String name, ParcelFileDescriptor pfd, @NonNull Context context) {
mContext = context.getApplicationContext(); mContext = context.getApplicationContext();
synchronized (mLock) {
boolean wasOpened = native_open(name, pfd.getFileDescriptor()); boolean wasOpened = native_open(name, pfd.getFileDescriptor());
if (wasOpened) { if (wasOpened) {
@@ -70,6 +74,14 @@ public class UsbDeviceConnection {
return wasOpened; return wasOpened;
} }
}
/***
* @return If this connection is currently open and usable.
*/
boolean isOpen() {
return mNativeContext != 0;
}
/** /**
* @return The application context the connection was created for. * @return The application context the connection was created for.
@@ -80,6 +92,49 @@ public class UsbDeviceConnection {
return mContext; return mContext;
} }
/**
* Cancel a request which relates to this connection.
*
* @return true if the request was successfully cancelled.
*/
/* package */ boolean cancelRequest(UsbRequest request) {
synchronized (mLock) {
if (!isOpen()) {
return false;
}
return request.cancelIfOpen();
}
}
/**
* This is meant to be called by UsbRequest's queue() in order to synchronize on
* UsbDeviceConnection's mLock to prevent the connection being closed while queueing.
*/
/* package */ boolean queueRequest(UsbRequest request, ByteBuffer buffer, int length) {
synchronized (mLock) {
if (!isOpen()) {
return false;
}
return request.queueIfConnectionOpen(buffer, length);
}
}
/**
* This is meant to be called by UsbRequest's queue() in order to synchronize on
* UsbDeviceConnection's mLock to prevent the connection being closed while queueing.
*/
/* package */ boolean queueRequest(UsbRequest request, @Nullable ByteBuffer buffer) {
synchronized (mLock) {
if (!isOpen()) {
return false;
}
return request.queueIfConnectionOpen(buffer);
}
}
/** /**
* Releases all system resources related to the device. * Releases all system resources related to the device.
* Once the object is closed it cannot be used again. * Once the object is closed it cannot be used again.
@@ -87,11 +142,13 @@ public class UsbDeviceConnection {
* to retrieve a new instance to reestablish communication with the device. * to retrieve a new instance to reestablish communication with the device.
*/ */
public void close() { public void close() {
if (mNativeContext != 0) { synchronized (mLock) {
if (isOpen()) {
native_close(); native_close();
mCloseGuard.close(); mCloseGuard.close();
} }
} }
}
/** /**
* Returns the native file descriptor for the device, or * Returns the native file descriptor for the device, or

View File

@@ -112,6 +112,7 @@ public class UsbRequest {
* Releases all resources related to this request. * Releases all resources related to this request.
*/ */
public void close() { public void close() {
synchronized (mLock) {
if (mNativeContext != 0) { if (mNativeContext != 0) {
mEndpoint = null; mEndpoint = null;
mConnection = null; mConnection = null;
@@ -119,6 +120,7 @@ public class UsbRequest {
mCloseGuard.close(); mCloseGuard.close();
} }
} }
}
@Override @Override
protected void finalize() throws Throwable { protected void finalize() throws Throwable {
@@ -190,10 +192,32 @@ public class UsbRequest {
*/ */
@Deprecated @Deprecated
public boolean queue(ByteBuffer buffer, int length) { public boolean queue(ByteBuffer buffer, int length) {
UsbDeviceConnection connection = mConnection;
if (connection == null) {
// The expected exception by CTS Verifier - USB Device test
throw new NullPointerException("invalid connection");
}
// Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent
// the connection being closed while queueing.
return connection.queueRequest(this, buffer, length);
}
/**
* This is meant to be called from UsbDeviceConnection after synchronizing using the lock over
* there, to prevent the connection being closed while queueing.
*/
/* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) {
UsbDeviceConnection connection = mConnection;
if (connection == null || !connection.isOpen()) {
// The expected exception by CTS Verifier - USB Device test
throw new NullPointerException("invalid connection");
}
boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
boolean result; boolean result;
if (mConnection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P
&& length > MAX_USBFS_BUFFER_SIZE) { && length > MAX_USBFS_BUFFER_SIZE) {
length = MAX_USBFS_BUFFER_SIZE; length = MAX_USBFS_BUFFER_SIZE;
} }
@@ -242,6 +266,28 @@ public class UsbRequest {
* @return true if the queueing operation succeeded * @return true if the queueing operation succeeded
*/ */
public boolean queue(@Nullable ByteBuffer buffer) { public boolean queue(@Nullable ByteBuffer buffer) {
UsbDeviceConnection connection = mConnection;
if (connection == null) {
// The expected exception by CTS Verifier - USB Device test
throw new IllegalStateException("invalid connection");
}
// Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent
// the connection being closed while queueing.
return connection.queueRequest(this, buffer);
}
/**
* This is meant to be called from UsbDeviceConnection after synchronizing using the lock over
* there, to prevent the connection being closed while queueing.
*/
/* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) {
UsbDeviceConnection connection = mConnection;
if (connection == null || !connection.isOpen()) {
// The expected exception by CTS Verifier - USB Device test
throw new IllegalStateException("invalid connection");
}
// Request need to be initialized // Request need to be initialized
Preconditions.checkState(mNativeContext != 0, "request is not initialized"); Preconditions.checkState(mNativeContext != 0, "request is not initialized");
@@ -259,7 +305,7 @@ public class UsbRequest {
mIsUsingNewQueue = true; mIsUsingNewQueue = true;
wasQueued = native_queue(null, 0, 0); wasQueued = native_queue(null, 0, 0);
} else { } else {
if (mConnection.getContext().getApplicationInfo().targetSdkVersion if (connection.getContext().getApplicationInfo().targetSdkVersion
< Build.VERSION_CODES.P) { < Build.VERSION_CODES.P) {
// Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once
Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE, Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE,
@@ -362,6 +408,32 @@ public class UsbRequest {
* @return true if cancelling succeeded * @return true if cancelling succeeded
*/ */
public boolean cancel() { public boolean cancel() {
UsbDeviceConnection connection = mConnection;
if (connection == null) {
return false;
}
return connection.cancelRequest(this);
}
/**
* Cancels a pending queue operation (for use when the UsbDeviceConnection associated
* with this request is synchronized). This ensures we don't have a race where the
* device is closed and then the request is canceled which would lead to a
* use-after-free because the cancel operation uses the device connection
* information freed in the when UsbDeviceConnection is closed.<br/>
*
* This method assumes the connected is not closed while this method is executed.
*
* @return true if cancelling succeeded.
*/
/* package */ boolean cancelIfOpen() {
UsbDeviceConnection connection = mConnection;
if (mNativeContext == 0 || (connection != null && !connection.isOpen())) {
Log.w(TAG,
"Detected attempt to cancel a request on a connection which isn't open");
return false;
}
return native_cancel(); return native_cancel();
} }