Chromium Code Reviews| Index: content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java |
| diff --git a/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java b/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java |
| index b59028b8611a3fe368f5dfa9578d06e61687d41c..27aa246e14f8089023cd75ef066b1eff1ef2a575 100644 |
| --- a/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java |
| +++ b/content/public/android/java/src/org/chromium/content/browser/installedapp/InstalledAppProviderImpl.java |
| @@ -10,6 +10,7 @@ import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.res.Resources; |
| import android.os.AsyncTask; |
| +import android.os.Handler; |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| @@ -24,6 +25,7 @@ import org.chromium.mojo.system.MojoException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.ArrayList; |
| +import java.util.concurrent.atomic.AtomicInteger; |
| /** |
| * Android implementation of the InstalledAppProvider service defined in |
| @@ -76,18 +78,26 @@ public class InstalledAppProviderImpl implements InstalledAppProvider { |
| } |
| final URI frameUrl = mFrameUrlDelegate.getUrl(); |
| + final AtomicInteger delayMillis = new AtomicInteger(); |
| // Use an AsyncTask to execute the installed/related checks on a background thread (so as |
| // not to block the UI thread). |
| new AsyncTask<Void, Void, RelatedApplication[]>() { |
|
Ted C
2017/04/12 15:53:50
I "think" it would be slightly cleaner to change t
Matt Giuca
2017/04/13 00:29:55
Done. Thanks, a good suggestion.
|
| @Override |
| protected RelatedApplication[] doInBackground(Void... unused) { |
| - return filterInstalledAppsOnBackgroundThread(relatedApps, frameUrl); |
| + return filterInstalledAppsOnBackgroundThread(relatedApps, frameUrl, delayMillis); |
| } |
| @Override |
| - protected void onPostExecute(RelatedApplication[] installedApps) { |
| - callback.call(installedApps); |
| + protected void onPostExecute(final RelatedApplication[] installedApps) { |
| + // Before calling the callback, delay for the amount of time that has been |
| + // calculated in |delayMillis|. |
| + delayThenRun(new Runnable() { |
| + @Override |
| + public void run() { |
| + callback.call(installedApps); |
| + } |
| + }, delayMillis.get()); |
| } |
| } |
| .execute(); |
| @@ -106,10 +116,13 @@ public class InstalledAppProviderImpl implements InstalledAppProvider { |
| * |
| * @param relatedApps A list of applications to be filtered. |
| * @param frameUrl The URL of the frame this operation was called from. |
| + * @param delayMillis (Output) Number that will be incremented by the total amount of time in ms |
| + * that should be delayed before returning to the user, to mask the installed |
| + * state of the requested apps. |
| * @return A subsequence of applications that meet the criteria. |
| */ |
| private RelatedApplication[] filterInstalledAppsOnBackgroundThread( |
| - RelatedApplication[] relatedApps, URI frameUrl) { |
| + RelatedApplication[] relatedApps, URI frameUrl, AtomicInteger delayMillis) { |
| ArrayList<RelatedApplication> installedApps = new ArrayList<RelatedApplication>(); |
| PackageManager pm = mContext.getPackageManager(); |
| for (RelatedApplication app : relatedApps) { |
| @@ -119,9 +132,11 @@ public class InstalledAppProviderImpl implements InstalledAppProvider { |
| // between the app not being installed and the origin not being associated with the app |
| // (otherwise, arbitrary websites would be able to test whether un-associated apps are |
| // installed on the user's device). |
| - if (app.platform.equals(RELATED_APP_PLATFORM_ANDROID) && app.id != null |
| - && isAppInstalledAndAssociatedWithOrigin(app.id, frameUrl, pm)) { |
| - installedApps.add(app); |
| + if (app.platform.equals(RELATED_APP_PLATFORM_ANDROID) && app.id != null) { |
| + delayMillis.addAndGet(calculateDelayForPackageMs(app.id)); |
| + if (isAppInstalledAndAssociatedWithOrigin(app.id, frameUrl, pm)) { |
| + installedApps.add(app); |
| + } |
| } |
| } |
| @@ -131,6 +146,23 @@ public class InstalledAppProviderImpl implements InstalledAppProvider { |
| } |
| /** |
| + * Determines how long to artifically delay for, for a particular package name. |
| + */ |
| + private int calculateDelayForPackageMs(String packageName) { |
| + // Important timing-attack prevention measure: delay by a pseudo-random amount of time, to |
| + // add significant noise to the time taken to check whether this app is installed and |
| + // related. Otherwise, it would be possible to tell whether a non-related app is installed, |
| + // based on the time this operation takes. |
| + // |
| + // Generate a 16-bit hash based on a unique device ID + the package name. |
| + short hash = PackageHash.hashForPackage(packageName); |
| + |
| + // The time delay is the low 10 bits of the hash in 100ths of a ms (between 0 and 10ms). |
| + int delayHundredthsOfMs = hash & 0x3ff; |
| + return delayHundredthsOfMs / 100; |
| + } |
| + |
| + /** |
| * Determines whether a particular app is installed and matches the origin. |
| * |
| * @param packageName Name of the Android package to check if installed. Returns false if the |
| @@ -138,7 +170,7 @@ public class InstalledAppProviderImpl implements InstalledAppProvider { |
| * @param frameUrl Returns false if the Android package does not declare association with the |
| * origin of this URL. Can be null. |
| */ |
| - private static boolean isAppInstalledAndAssociatedWithOrigin( |
| + private boolean isAppInstalledAndAssociatedWithOrigin( |
| String packageName, URI frameUrl, PackageManager pm) { |
| if (frameUrl == null) return false; |
| @@ -276,4 +308,17 @@ public class InstalledAppProviderImpl implements InstalledAppProvider { |
| return assetUrl.getScheme().equals(frameUrl.getScheme()) |
| && assetUrl.getAuthority().equals(frameUrl.getAuthority()); |
| } |
| + |
| + /** |
| + * Runs a Runnable task after a given delay. |
| + * |
| + * Protected and non-static for testing. |
| + * |
| + * @param r The Runnable that will be executed. |
| + * @param delayMillis The delay (in ms) until the Runnable will be executed. |
| + * @return True if the Runnable was successfully placed into the message queue. |
| + */ |
| + protected boolean delayThenRun(Runnable r, long delayMillis) { |
| + return new Handler().postDelayed(r, delayMillis); |
|
Ted C
2017/04/12 15:53:50
Use can use ThreadUtils.postOnUiThreadDelayed here
Matt Giuca
2017/04/13 00:29:55
Done.
|
| + } |
| } |