OPP: Restrict file based URI access to external storage * Allow only external storage paths in file based URI in BluetoothOppSendFileInfo when the file send request comes from an external source * Fix a potential NPE when using Uri.getPath() Bug: 35310991 Test: Make, test various cases of Bluetooth file share Change-Id: I8ff00d63d3c880667302f8d7ff8eaa0c0b533921 (cherry picked from commit 3edd7f0a8aadf2f44bc62ea5b567c74d39a534c8) (cherry picked from commit 3ce229fa602f739d1e98e2a85a3bd955c3a308f6)
diff --git a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java index 3553d34..4f03c96 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java +++ b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
@@ -59,7 +59,7 @@ Thread t = new Thread(new Runnable() { public void run() { BluetoothOppManager.getInstance(context).saveSendingFileInfo(finalType, - finalUris, true); + finalUris, true /* isHandover */, true /* fromExternal */); BluetoothOppManager.getInstance(context).startTransfer(device); } });
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java index 0d4b5b2..c298745 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java +++ b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -112,7 +112,8 @@ Thread t = new Thread(new Runnable() { public void run() { BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this) - .saveSendingFileInfo(type,stream.toString(), false); + .saveSendingFileInfo(type,stream.toString(), + false /* isHandover */, true /* fromExternal */); //Done getting file info..Launch device picker and finish this activity launchDevicePicker(); finish(); @@ -128,7 +129,8 @@ Thread t = new Thread(new Runnable() { public void run() { BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this) - .saveSendingFileInfo(type,fileUri.toString(), false); + .saveSendingFileInfo(type,fileUri.toString(), + false /* isHandover */, false /* fromExternal */); //Done getting file info..Launch device picker //and finish this activity launchDevicePicker(); @@ -156,7 +158,8 @@ Thread t = new Thread(new Runnable() { public void run() { BluetoothOppManager.getInstance(BluetoothOppLauncherActivity.this) - .saveSendingFileInfo(mimeType,uris, false); + .saveSendingFileInfo(mimeType,uris, + false /* isHandover */, true /* fromExternal */); //Done getting file info..Launch device picker //and finish this activity launchDevicePicker();
diff --git a/src/com/android/bluetooth/opp/BluetoothOppManager.java b/src/com/android/bluetooth/opp/BluetoothOppManager.java index dd8efe0..39929aa 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppManager.java +++ b/src/com/android/bluetooth/opp/BluetoothOppManager.java
@@ -246,28 +246,32 @@ if (V) Log.v(TAG, "Application data stored to SharedPreference! "); } - public void saveSendingFileInfo(String mimeType, String uriString, boolean isHandover) { + public void saveSendingFileInfo(String mimeType, String uriString, boolean isHandover, + boolean fromExternal) { synchronized (BluetoothOppManager.this) { mMultipleFlag = false; mMimeTypeOfSendingFile = mimeType; mUriOfSendingFile = uriString; mIsHandoverInitiated = isHandover; Uri uri = Uri.parse(uriString); - BluetoothOppUtility.putSendFileInfo(uri, - BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType)); + BluetoothOppUtility.putSendFileInfo( + uri, BluetoothOppSendFileInfo.generateFileInfo( + mContext, uri, mimeType, fromExternal)); storeApplicationData(); } } - public void saveSendingFileInfo(String mimeType, ArrayList<Uri> uris, boolean isHandover) { + public void saveSendingFileInfo(String mimeType, ArrayList<Uri> uris, boolean isHandover, + boolean fromExternal) { synchronized (BluetoothOppManager.this) { mMultipleFlag = true; mMimeTypeOfSendingFiles = mimeType; mUrisOfSendingFiles = uris; mIsHandoverInitiated = isHandover; for (Uri uri : uris) { - BluetoothOppUtility.putSendFileInfo(uri, - BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType)); + BluetoothOppUtility.putSendFileInfo( + uri, BluetoothOppSendFileInfo.generateFileInfo( + mContext, uri, mimeType, fromExternal)); } storeApplicationData(); }
diff --git a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java index b9d2b9f..84ebc82 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java +++ b/src/com/android/bluetooth/opp/BluetoothOppSendFileInfo.java
@@ -39,6 +39,7 @@ import android.database.sqlite.SQLiteException; import android.net.Uri; import android.provider.OpenableColumns; +import android.util.EventLog; import android.util.Log; import java.io.File; @@ -97,8 +98,8 @@ mStatus = status; } - public static BluetoothOppSendFileInfo generateFileInfo(Context context, Uri uri, - String type) { + public static BluetoothOppSendFileInfo generateFileInfo( + Context context, Uri uri, String type, boolean fromExternal) { ContentResolver contentResolver = context.getContentResolver(); String scheme = uri.getScheme(); String fileName = null; @@ -140,6 +141,16 @@ fileName = uri.getLastPathSegment(); } } else if ("file".equals(scheme)) { + if (uri.getPath() == null) { + Log.e(TAG, "Invalid URI path: " + uri); + return SEND_FILE_INFO_ERROR; + } + if (fromExternal && !BluetoothOppUtility.isInExternalStorageDir(uri)) { + EventLog.writeEvent(0x534e4554, "35310991", -1, uri.getPath()); + Log.e(TAG, + "File based URI not in Environment.getExternalStorageDirectory() is not allowed."); + return SEND_FILE_INFO_ERROR; + } fileName = uri.getLastPathSegment(); contentType = type; File f = new File(uri.getPath());
diff --git a/src/com/android/bluetooth/opp/BluetoothOppUtility.java b/src/com/android/bluetooth/opp/BluetoothOppUtility.java index bd671b2..6b94ab5 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppUtility.java +++ b/src/com/android/bluetooth/opp/BluetoothOppUtility.java
@@ -39,6 +39,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.net.Uri; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.ActivityNotFoundException; @@ -46,6 +47,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; +import android.os.Environment; import android.util.Log; import java.io.File; @@ -353,4 +355,40 @@ } } } + + /** + * Checks if the URI is in Environment.getExternalStorageDirectory() as it + * is the only directory that is possibly readable by both the sender and + * the Bluetooth process. + */ + static boolean isInExternalStorageDir(Uri uri) { + if (!ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { + Log.e(TAG, "Not a file URI: " + uri); + return false; + } + final File file = new File(uri.getCanonicalUri().getPath()); + return isSameOrSubDirectory(Environment.getExternalStorageDirectory(), file); + } + + /** + * Checks, whether the child directory is the same as, or a sub-directory of the base + * directory. Neither base nor child should be null. + */ + static boolean isSameOrSubDirectory(File base, File child) { + try { + base = base.getCanonicalFile(); + child = child.getCanonicalFile(); + File parentFile = child; + while (parentFile != null) { + if (base.equals(parentFile)) { + return true; + } + parentFile = parentFile.getParentFile(); + } + return false; + } catch (IOException ex) { + Log.e(TAG, "Error while accessing file", ex); + return false; + } + } }