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 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..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 b8cb641..0df0452 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;
@@ -136,6 +137,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 ab4613f..45ef146 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;
@@ -350,4 +352,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;
+        }
+    }
 }