Make DownloadProvider honor the cleartext traffic policy.
Alex Klyubin [Fri, 27 Mar 2015 17:17:55 +0000 (10:17 -0700)]
This makes the Provider-side of the DownloadManager framework honor
the per-UID cleartext network traffic policy. The policy is enforced
in the Provider rather than in its client (DownloadManager) because
download URLs could get redirected between HTTPS and HTTP and only
the Provider currently has visibility into and control over this.

Whether cleartext network traffic is permitted is a per-package
policy. However, the DownloadProvider can only access the UID of the
requesting application. Multiple packages can run under the same UID.
In that scenario, cleartext traffic is permited for the UID if
cleartext traffic is permitted for any of the packages running under
the UID. This could be improved by making the DownloadManager provide
the package name in addition to the UID.

Bug: 19215516
Change-Id: Ib37585a7a2fc2869954d52a1b08052926f49bc9b

src/com/android/providers/downloads/DownloadThread.java
src/com/android/providers/downloads/RealSystemFacade.java
src/com/android/providers/downloads/SystemFacade.java
tests/src/com/android/providers/downloads/DownloadProviderFunctionalTest.java
tests/src/com/android/providers/downloads/FakeSystemFacade.java

index c75e419..325b4ee 100644 (file)
@@ -48,6 +48,7 @@ import android.net.INetworkPolicyListener;
 import android.net.NetworkInfo;
 import android.net.NetworkPolicyManager;
 import android.net.TrafficStats;
+import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.Process;
@@ -350,8 +351,17 @@ public class DownloadThread implements Runnable {
             throw new StopRequestException(STATUS_BAD_REQUEST, e);
         }
 
+        boolean cleartextTrafficPermitted = mSystemFacade.isCleartextTrafficPermitted(mInfo.mUid);
         int redirectionCount = 0;
         while (redirectionCount++ < Constants.MAX_REDIRECTS) {
+            // Enforce the cleartext traffic opt-out for the UID. This cannot be enforced earlier
+            // because of HTTP redirects which can change the protocol between HTTP and HTTPS.
+            if ((!cleartextTrafficPermitted) && ("http".equalsIgnoreCase(url.getProtocol()))) {
+                throw new StopRequestException(STATUS_BAD_REQUEST,
+                        "Cleartext traffic not permitted for UID " + mInfo.mUid + ": "
+                        + Uri.parse(url.toString()).toSafeString());
+            }
+
             // Open connection and follow any redirects until we have a useful
             // response with body.
             HttpURLConnection conn = null;
index fa4f348..b3f170f 100644 (file)
 
 package com.android.providers.downloads;
 
+import com.android.internal.util.ArrayUtils;
+
 import android.app.DownloadManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
@@ -96,4 +101,43 @@ class RealSystemFacade implements SystemFacade {
     public boolean userOwnsPackage(int uid, String packageName) throws NameNotFoundException {
         return mContext.getPackageManager().getApplicationInfo(packageName, 0).uid == uid;
     }
+
+    @Override
+    public boolean isCleartextTrafficPermitted(int uid) {
+        PackageManager packageManager = mContext.getPackageManager();
+        String[] packageNames = packageManager.getPackagesForUid(uid);
+        if (ArrayUtils.isEmpty(packageNames)) {
+            // Unknown UID -- fail safe: cleartext traffic not permitted
+            return false;
+        }
+
+        // Cleartext traffic is permitted from the UID if it's permitted for any of the packages
+        // belonging to that UID.
+        for (String packageName : packageNames) {
+            if (isCleartextTrafficPermitted(packageName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether cleartext network traffic (HTTP) is permitted for the provided package.
+     */
+    private boolean isCleartextTrafficPermitted(String packageName) {
+        PackageManager packageManager = mContext.getPackageManager();
+        PackageInfo packageInfo;
+        try {
+            packageInfo = packageManager.getPackageInfo(packageName, 0);
+        } catch (NameNotFoundException e) {
+            // Unknown package -- fail safe: cleartext traffic not permitted
+            return false;
+        }
+        ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+        if (applicationInfo == null) {
+            // No app info -- fail safe: cleartext traffic not permitted
+            return false;
+        }
+        return (applicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) != 0;
+    }
 }
index 15fc31f..83fc7a6 100644 (file)
@@ -61,4 +61,9 @@ interface SystemFacade {
      * Returns true if the specified UID owns the specified package name.
      */
     public boolean userOwnsPackage(int uid, String pckg) throws NameNotFoundException;
+
+    /**
+     * Returns true if cleartext network traffic is permitted for the specified UID.
+     */
+    public boolean isCleartextTrafficPermitted(int uid);
 }
index dbab203..41dff67 100644 (file)
@@ -101,6 +101,23 @@ public class DownloadProviderFunctionalTest extends AbstractDownloadProviderFunc
         runUntilStatus(downloadUri, Downloads.Impl.STATUS_SUCCESS);
     }
 
+    public void testCleartextTrafficPermittedFlagHonored() throws Exception {
+        enqueueResponse(buildResponse(HTTP_OK, FILE_CONTENT));
+        enqueueResponse(buildResponse(HTTP_OK, FILE_CONTENT));
+
+        // Assert that HTTP request succeeds when cleartext traffic is permitted
+        mSystemFacade.mCleartextTrafficPermitted = true;
+        Uri downloadUri = requestDownload("/path");
+        assertEquals("http", downloadUri.getScheme());
+        runUntilStatus(downloadUri, Downloads.Impl.STATUS_SUCCESS);
+
+        // Assert that HTTP request fails when cleartext traffic is not permitted
+        mSystemFacade.mCleartextTrafficPermitted = false;
+        downloadUri = requestDownload("/path");
+        assertEquals("http", downloadUri.getScheme());
+        runUntilStatus(downloadUri, Downloads.Impl.STATUS_BAD_REQUEST);
+    }
+
     /**
      * Read a downloaded file from disk.
      */
index 5a15d39..7581e6f 100644 (file)
@@ -16,6 +16,7 @@ public class FakeSystemFacade implements SystemFacade {
     Long mMaxBytesOverMobile = null;
     Long mRecommendedMaxBytesOverMobile = null;
     List<Intent> mBroadcastsSent = new ArrayList<Intent>();
+    boolean mCleartextTrafficPermitted = true;
     private boolean mReturnActualTime = false;
 
     public void setUp() {
@@ -82,6 +83,11 @@ public class FakeSystemFacade implements SystemFacade {
         return true;
     }
 
+    @Override
+    public boolean isCleartextTrafficPermitted(int uid) {
+        return mCleartextTrafficPermitted;
+    }
+
     public void setReturnActualTime(boolean flag) {
         mReturnActualTime = flag;
     }