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 496ed8db359f447c1ac4e65fcc81227bd35ec438)
diff --git a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java index c2cd172..14a0fd0 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java +++ b/src/com/android/bluetooth/opp/BluetoothOppHandoverReceiver.java
@@ -50,7 +50,7 @@ // Save type/stream, will be used when adding transfer // session to DB. BluetoothOppManager.getInstance(context).saveSendingFileInfo(type, - stream.toString(), true); + stream.toString(), true /* isHandover */, true /* fromExternal */); } else { if (D) Log.d(TAG, "No mimeType or stream attached to handover request"); } @@ -60,7 +60,7 @@ uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); if (mimeType != null && uris != null) { BluetoothOppManager.getInstance(context).saveSendingFileInfo(mimeType, - uris, true); + uris, true /* isHandover */, true /* fromExternal */); } else { if (D) Log.d(TAG, "No mimeType or stream attached to handover request"); return;
diff --git a/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java index 39116a0..b0ff6d9 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..8e6bfcd 100644 --- a/src/com/android/bluetooth/opp/BluetoothOppManager.java +++ b/src/com/android/bluetooth/opp/BluetoothOppManager.java
@@ -246,7 +246,7 @@ 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; @@ -254,12 +254,12 @@ mIsHandoverInitiated = isHandover; Uri uri = Uri.parse(uriString); BluetoothOppUtility.putSendFileInfo(uri, - BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType)); + 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; @@ -267,7 +267,7 @@ mIsHandoverInitiated = isHandover; for (Uri uri : uris) { BluetoothOppUtility.putSendFileInfo(uri, - BluetoothOppSendFileInfo.generateFileInfo(mContext, uri, mimeType)); + 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 30f19f8..48579e0 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; @@ -98,7 +99,7 @@ } public static BluetoothOppSendFileInfo generateFileInfo(Context context, Uri uri, - String type) { + String type, boolean fromExternal) { ContentResolver contentResolver = context.getContentResolver(); String scheme = uri.getScheme(); String fileName = null; @@ -134,6 +135,15 @@ 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 58f4677..97c17a9 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; @@ -346,4 +348,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; + } + } }