/* * Copyright (C) 2017 Oasis Feng. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.oasisfeng.condom; import android.Manifest.permission; import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContextWrapper; import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.UserHandle; import android.provider.Settings; import android.support.annotation.CallSuper; import android.support.annotation.Nullable; import android.support.test.InstrumentationRegistry; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import javax.annotation.ParametersAreNonnullByDefault; import static android.content.Intent.FLAG_RECEIVER_REGISTERED_ONLY; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.HONEYCOMB_MR1; import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; import static android.os.Build.VERSION_CODES.N; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; @ParametersAreNonnullByDefault public class CondomContextBlockingTest { @Test public void testSelfTargeted() { final TestContext context = new TestContext(); final CondomContext condom = CondomContext.wrap(context, TAG), dry_condom = CondomContext.wrap(context, TAG, new CondomOptions().setDryRun(true)); // Self-targeting test final String self_pkg = condom.getPackageName(); final Intent[] self_targeted_intents = new Intent[] { intent().setPackage(self_pkg), intent().setComponent(new ComponentName(self_pkg, "X")) }; with(self_targeted_intents, allBroadcastApis(condom), context.EXPECT_BASE_CALLED, context.expectFlags(0)); with(self_targeted_intents, allServiceApis(condom), context.EXPECT_BASE_CALLED, context.expectFlags(0)); with(self_targeted_intents, allBroadcastApis(dry_condom), context.EXPECT_BASE_CALLED, context.expectFlags(0)); with(self_targeted_intents, allServiceApis(dry_condom), context.EXPECT_BASE_CALLED, context.expectFlags(0)); } @Test public void testPreventNone() { final TestContext context = new TestContext(); final CondomOptions options = new CondomOptions().preventServiceInBackgroundPackages(false).preventBroadcastToBackgroundPackages(false); final CondomContext condom = CondomContext.wrap(context, TAG, options), dry_condom = CondomContext.wrap(context, TAG, options.setDryRun(true)); //noinspection deprecation, intentional test for deprecated method condom.preventWakingUpStoppedPackages(false); //noinspection deprecation dry_condom.preventWakingUpStoppedPackages(false); with(ALL_SORT_OF_INTENTS, allBroadcastApis(condom), context.EXPECT_BASE_CALLED, context.expectFlags(0)); with(ALL_SORT_OF_INTENTS, allServiceApis(condom), context.EXPECT_BASE_CALLED, context.expectFlags(0)); with(ALL_SORT_OF_INTENTS, allBroadcastApis(dry_condom), context.EXPECT_BASE_CALLED, context.expectFlags(0)); with(ALL_SORT_OF_INTENTS, allServiceApis(dry_condom), context.EXPECT_BASE_CALLED, context.expectFlags(0)); } @Test public void testPreventWakingUpStoppedPackages_IncludingDryRun() { final Intent[] intents_with_inc_stop = ALL_SORT_OF_INTENTS.clone(); for (int i = 0; i < intents_with_inc_stop.length; i++) intents_with_inc_stop[i] = new Intent(intents_with_inc_stop[i]).addFlags(FLAG_INCLUDE_STOPPED_PACKAGES); final TestContext context = new TestContext(); final CondomOptions options = new CondomOptions().preventBroadcastToBackgroundPackages(false).preventServiceInBackgroundPackages(false); final CondomContext condom = CondomContext.wrap(context, TAG, options), dry_condom = CondomContext.wrap(context, TAG, options.setDryRun(true)); with(intents_with_inc_stop, allBroadcastApis(condom), context.EXPECT_BASE_CALLED, context.expectFlags(FLAG_EXCLUDE_STOPPED_PACKAGES)); with(intents_with_inc_stop, allServiceApis(condom), context.EXPECT_BASE_CALLED, context.expectFlags(FLAG_EXCLUDE_STOPPED_PACKAGES)); with(intents_with_inc_stop, allBroadcastApis(dry_condom), context.EXPECT_BASE_CALLED, context.expectFlags(FLAG_INCLUDE_STOPPED_PACKAGES)); with(intents_with_inc_stop, allServiceApis(dry_condom), context.EXPECT_BASE_CALLED, context.expectFlags(FLAG_INCLUDE_STOPPED_PACKAGES)); } @Test public void testPreventBroadcastToBackgroundPackages() { final TestContext context = new TestContext(); final CondomOptions options = new CondomOptions().preventBroadcastToBackgroundPackages(true); final CondomContext condom = CondomContext.wrap(context, TAG, options), dry_condom = CondomContext.wrap(context, TAG, options.setDryRun(true)); final int extra_flag = SDK_INT >= N ? CondomCore.FLAG_RECEIVER_EXCLUDE_BACKGROUND : FLAG_RECEIVER_REGISTERED_ONLY; with(ALL_SORT_OF_INTENTS, allBroadcastApis(condom), context.EXPECT_BASE_CALLED, context.expectFlags(FLAG_EXCLUDE_STOPPED_PACKAGES | extra_flag)); with(ALL_SORT_OF_INTENTS, allBroadcastApis(dry_condom), context.EXPECT_BASE_CALLED, context.expectFlags(0)); } @Test public void testPreventServiceInBackgroundPackages() { final TestContext context = new TestContext(); context.mTestingBackgroundUid = true; final CondomOptions options = new CondomOptions().preventServiceInBackgroundPackages(true).preventBroadcastToBackgroundPackages(false); final CondomContext condom = CondomContext.wrap(context, TAG, options), dry_condom = CondomContext.wrap(context, TAG, options.setDryRun(true)); assertEquals(3, condom.getPackageManager().queryIntentServices(intent(), 0).size()); context.assertBaseCalled(); assertEquals(4, dry_condom.getPackageManager().queryIntentServices(intent(), 0).size()); context.assertBaseCalled(); assertEquals("non.bg.service", condom.getPackageManager().resolveService(intent(), 0).serviceInfo.packageName); context.assertBaseCalled(); assertEquals(7777777, dry_condom.getPackageManager().resolveService(intent(), 0).serviceInfo.applicationInfo.uid); context.assertBaseCalled(); } @Test public void testContentProviderOutboundJudge() { final TestContext context = new TestContext(); final CondomOptions options = new CondomOptions().setOutboundJudge(new OutboundJudge() { @Override public boolean shouldAllow(final OutboundType type, final @Nullable Intent intent, final String target_pkg) { final String settings_pkg = InstrumentationRegistry.getTargetContext().getPackageManager().resolveContentProvider(Settings.System.CONTENT_URI.getAuthority(), 0).packageName; return ! settings_pkg.equals(target_pkg); }}); final CondomContext condom = CondomContext.wrap(context, TAG, options), dry_condom = CondomContext.wrap(context, TAG, options.setDryRun(true)); assertNull(condom.getPackageManager().resolveContentProvider(Settings.AUTHORITY, 0)); assertNotNull(dry_condom.getPackageManager().resolveContentProvider(Settings.AUTHORITY, 0)); assertNull(condom.getContentResolver().acquireContentProviderClient(Settings.System.CONTENT_URI)); assertNotNull(dry_condom.getContentResolver().acquireContentProviderClient(Settings.System.CONTENT_URI)); } @Test public void testContentProvider() { final TestContext context = new TestContext(); final CondomContext condom = CondomContext.wrap(context, TAG), dry_condom = CondomContext.wrap(context, TAG, new CondomOptions().setDryRun(true)); // Regular provider access final String android_id = Settings.System.getString(context.getContentResolver(), Settings.System.ANDROID_ID); assertNotNull(android_id); final String condom_android_id = Settings.System.getString(condom.getContentResolver(), Settings.System.ANDROID_ID); assertEquals(android_id, condom_android_id); final String dry_android_id = Settings.System.getString(dry_condom.getContentResolver(), Settings.System.ANDROID_ID); assertEquals(android_id, dry_android_id); // Prevent stopped package context.mTestingStoppedProvider = true; assertNull(condom.getPackageManager().resolveContentProvider(Settings.AUTHORITY, 0)); assertNotNull(dry_condom.getPackageManager().resolveContentProvider(Settings.AUTHORITY, 0)); assertNull(condom.getContentResolver().acquireContentProviderClient(Settings.System.CONTENT_URI)); assertNotNull(dry_condom.getContentResolver().acquireContentProviderClient(Settings.System.CONTENT_URI)); context.mTestingStoppedProvider = false; } @Test public void testOutboundJudge() { final TestContext context = new TestContext(); final CondomOptions options = new CondomOptions().setOutboundJudge(new OutboundJudge() { @Override public boolean shouldAllow(final OutboundType type, final @Nullable Intent intent, final String target_pkg) { mNumOutboundJudgeCalled.incrementAndGet(); return ! DISALLOWED_PACKAGE.equals(target_pkg); } }); final CondomContext condom = CondomContext.wrap(context, TAG, options), dry_condom = CondomContext.wrap(context, TAG, options.setDryRun(true)); final PackageManager pm = condom.getPackageManager(), dry_pm = dry_condom.getPackageManager(); final Runnable EXPECT_OUTBOUND_JUDGE_REFUSAL = new Runnable() { @Override public void run() { context.assertBaseNotCalled(); assertOutboundJudgeCalled(1); }}; final Runnable EXPECT_OUTBOUND_JUDGE_PASS = new Runnable() { @Override public void run() { context.assertBaseCalled(); assertOutboundJudgeCalled(1); }}; with(DISALLOWED_INTENTS, allBroadcastApis(condom), EXPECT_OUTBOUND_JUDGE_REFUSAL); with(ALLOWED_INTENTS, allBroadcastApis(condom), EXPECT_OUTBOUND_JUDGE_PASS); with(DISALLOWED_INTENTS, allBroadcastApis(dry_condom), EXPECT_OUTBOUND_JUDGE_PASS); with(ALLOWED_INTENTS, allBroadcastApis(dry_condom), EXPECT_OUTBOUND_JUDGE_PASS); assertNull(pm.resolveService(intent().setPackage(DISALLOWED_PACKAGE), 0)); context.assertBaseNotCalled(); assertOutboundJudgeCalled(1); assertNotNull(dry_pm.resolveService(intent().setPackage(DISALLOWED_PACKAGE), 0)); context.assertBaseCalled(); assertOutboundJudgeCalled(1); assertEquals(1, pm.queryIntentServices(intent(), 0).size()); context.assertBaseCalled(); assertOutboundJudgeCalled(2); assertEquals(2, dry_pm.queryIntentServices(intent(), 0).size()); context.assertBaseCalled(); assertOutboundJudgeCalled(2); assertEquals(1, pm.queryBroadcastReceivers(intent(), 0).size()); context.assertBaseCalled(); assertOutboundJudgeCalled(2); assertEquals(2, dry_pm.queryBroadcastReceivers(intent(), 0).size()); context.assertBaseCalled(); assertOutboundJudgeCalled(2); condom.sendBroadcast(intent()); context.assertBaseCalled(); assertOutboundJudgeCalled(0); dry_condom.sendBroadcast(intent()); context.assertBaseCalled(); assertOutboundJudgeCalled(0); } private static void with(final Intent[] intents, final Consumer<Intent>[] tests, final Runnable... expectations) { for (final Intent intent : intents) for (final Consumer<Intent> test : tests) { test.accept(intent); for (final Runnable expectation : expectations) expectation.run(); } } private static Intent intent() { return new Intent("com.example.TEST").addFlags(INTENT_FLAGS); } private static final UserHandle USER = SDK_INT >= JELLY_BEAN_MR1 ? android.os.Process.myUserHandle() : null; private static final int INTENT_FLAGS = Intent.FLAG_DEBUG_LOG_RESOLUTION | Intent.FLAG_FROM_BACKGROUND; // Just random flags to verify flags preservation. private static final ServiceConnection SERVICE_CONNECTION = new ServiceConnection() { @Override public void onServiceConnected(final ComponentName name, final IBinder service) {} @Override public void onServiceDisconnected(final ComponentName name) {} }; private static final String DISALLOWED_PACKAGE = "a.b.c"; private static final String ALLOWED_PACKAGE = "x.y.z"; private static final ComponentName DISALLOWED_COMPONENT = new ComponentName(DISALLOWED_PACKAGE, "A"); private static final ComponentName ALLOWED_COMPONENT = new ComponentName(ALLOWED_PACKAGE, "A"); private static final int FLAG_EXCLUDE_STOPPED_PACKAGES = SDK_INT >= HONEYCOMB_MR1 ? Intent.FLAG_EXCLUDE_STOPPED_PACKAGES : 0; private static final int FLAG_INCLUDE_STOPPED_PACKAGES = SDK_INT >= HONEYCOMB_MR1 ? Intent.FLAG_INCLUDE_STOPPED_PACKAGES : 0; private static final Intent[] ALL_SORT_OF_INTENTS = new Intent[] { intent(), intent().setPackage(ALLOWED_PACKAGE), intent().setPackage(DISALLOWED_PACKAGE), intent().setComponent(ALLOWED_COMPONENT), intent().setComponent(DISALLOWED_COMPONENT), }; private static final Intent[] ALLOWED_INTENTS = new Intent[] { intent().setPackage(ALLOWED_PACKAGE), intent().setComponent(ALLOWED_COMPONENT), }; private static final Intent[] DISALLOWED_INTENTS = new Intent[] { intent().setPackage(DISALLOWED_PACKAGE), intent().setComponent(DISALLOWED_COMPONENT), }; private static Consumer<Intent>[] allBroadcastApis(final CondomContext condom) { final List<Consumer<Intent>> tests = new ArrayList<>(); tests.add(new Consumer<Intent>() { @Override public void accept(final Intent intent) { condom.sendBroadcast(intent); }}); tests.add(new Consumer<Intent>() { @Override public void accept(final Intent intent) { condom.sendBroadcast(intent, permission.DUMP); }}); tests.add(new Consumer<Intent>() { @Override public void accept(final Intent intent) { condom.sendOrderedBroadcast(intent, permission.DUMP); }}); tests.add(new Consumer<Intent>() { @Override public void accept(final Intent intent) { condom.sendOrderedBroadcast(intent, permission.DUMP, null, null, 0, null, null); }}); tests.add(new Consumer<Intent>() { @Override public void accept(final Intent intent) { condom.sendStickyBroadcast(intent); }}); tests.add(new Consumer<Intent>() { @Override public void accept(final Intent intent) { condom.sendStickyOrderedBroadcast(intent, null, null, 0, null, null); }}); if (SDK_INT >= JELLY_BEAN_MR1) { tests.add(new Consumer<Intent>() { @TargetApi(JELLY_BEAN_MR1) @Override public void accept(final Intent intent) { condom.sendBroadcastAsUser(intent, USER); }}); tests.add(new Consumer<Intent>() { @TargetApi(JELLY_BEAN_MR1) @Override public void accept(final Intent intent) { condom.sendBroadcastAsUser(intent, USER, null); }}); tests.add(new Consumer<Intent>() { @TargetApi(JELLY_BEAN_MR1) @Override public void accept(final Intent intent) { condom.sendStickyBroadcastAsUser(intent, USER); }}); tests.add(new Consumer<Intent>() { @TargetApi(JELLY_BEAN_MR1) @Override public void accept(final Intent intent) { condom.sendOrderedBroadcastAsUser(intent, USER, null, null, null, 0, null, null); }}); tests.add(new Consumer<Intent>() { @TargetApi(JELLY_BEAN_MR1) @Override public void accept(final Intent intent) { condom.sendStickyOrderedBroadcastAsUser(intent, USER,null, null, 0, null, null); }}); } tests.add(new Consumer<Intent>() { @Override public void accept(final Intent intent) { condom.getPackageManager().queryBroadcastReceivers(intent, 0); }}); //noinspection unchecked return tests.toArray(new Consumer[tests.size()]); } @SuppressWarnings("unchecked") private static Consumer<Intent>[] allServiceApis(final CondomContext condom) { return new Consumer[] { new Consumer<Intent>() { @Override public void accept(final Intent intent) { condom.startService(intent); }}, new Consumer<Intent>() { @Override public void accept(final Intent intent) { condom.bindService(intent, SERVICE_CONNECTION, 0); }} }; } private void assertOutboundJudgeCalled(final int count) { assertEquals(count, mNumOutboundJudgeCalled.getAndSet(0)); } private final AtomicInteger mNumOutboundJudgeCalled = new AtomicInteger(); private static final String TAG = "Test"; private class TestContext extends ContextWrapper { @CallSuper void check(final Intent intent) { assertBaseNotCalled(); mBaseCalled = true; mIntentFlags = intent.getFlags(); } @Override public ComponentName startService(final Intent intent) { check(intent); return null; } @Override public boolean bindService(final Intent intent, final ServiceConnection c, final int f) { check(intent); return false; } @Override public void sendBroadcast(final Intent intent) { check(intent); } @Override public void sendBroadcast(final Intent intent, final String p) { check(intent); } @Override public void sendBroadcastAsUser(final Intent intent, final UserHandle user) { check(intent); } @Override public void sendBroadcastAsUser(final Intent intent, final UserHandle user, final String receiverPermission) { check(intent); } @Override public void sendStickyBroadcast(final Intent intent) { check(intent); } @Override public void sendStickyBroadcastAsUser(final Intent intent, final UserHandle u) { check(intent); } @Override public void sendOrderedBroadcast(final Intent intent, final String p) { check(intent); } @Override public void sendOrderedBroadcast(final Intent intent, final String p, final BroadcastReceiver r, final Handler s, final int c, final String d, final Bundle e) { check(intent); } @Override public void sendOrderedBroadcastAsUser(final Intent intent, final UserHandle u, final String p, final BroadcastReceiver r, final Handler s, final int c, final String d, final Bundle e) { check(intent); } @Override public void sendStickyOrderedBroadcast(final Intent intent, final BroadcastReceiver r, final Handler s, final int c, final String d, final Bundle e) { check(intent); } @Override public void sendStickyOrderedBroadcastAsUser(final Intent intent, final UserHandle u, final BroadcastReceiver r, final Handler s, final int c, final String d, final Bundle e) { check(intent); } @Override public PackageManager getPackageManager() { return new PackageManagerWrapper(InstrumentationRegistry.getTargetContext().getPackageManager()) { @Override public ResolveInfo resolveService(final Intent intent, final int flags) { check(intent); return buildResolveInfo(DISALLOWED_PACKAGE, true, 7777777); // Must be consistent with the first entry from queryIntentServices(). } @Override public List<ResolveInfo> queryIntentServices(final Intent intent, final int flags) { check(intent); final List<ResolveInfo> resolves = new ArrayList<>(); if (mTestingBackgroundUid) { final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); final List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(32); if (services != null) for (final ActivityManager.RunningServiceInfo service : services) { if (service.uid == android.os.Process.myUid()) continue; resolves.add(buildResolveInfo(DISALLOWED_PACKAGE, true, 7777777)); // Simulate a background UID. resolves.add(buildResolveInfo("non.bg.service", true, service.uid)); break; } } resolves.add(buildResolveInfo(ALLOWED_PACKAGE, true, android.os.Process.myUid())); resolves.add(buildResolveInfo(DISALLOWED_PACKAGE, true, android.os.Process.myUid())); return resolves; } @Override public List<ResolveInfo> queryBroadcastReceivers(final Intent intent, final int flags) { check(intent); final List<ResolveInfo> resolves = new ArrayList<>(); resolves.add(buildResolveInfo(ALLOWED_PACKAGE, false, android.os.Process.myUid())); resolves.add(buildResolveInfo(DISALLOWED_PACKAGE, false, android.os.Process.myUid())); return resolves; } @Override public ProviderInfo resolveContentProvider(final String name, final int flags) { final ProviderInfo info = super.resolveContentProvider(name, flags); if (mTestingStoppedProvider) info.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED; return info; } private ResolveInfo buildResolveInfo(final String pkg, final boolean service_or_receiver, final int uid) { final ResolveInfo r = new ResolveInfo() { @Override public String toString() { return "ResolveInfo{test}"; } }; final ComponentInfo info = service_or_receiver ? (r.serviceInfo = new ServiceInfo()) : (r.activityInfo = new ActivityInfo()); info.packageName = pkg; info.applicationInfo = new ApplicationInfo(); info.applicationInfo.packageName = pkg; info.applicationInfo.uid = uid; return r; } }; } void assertBaseCalled() { assertTrue(mBaseCalled); mBaseCalled = false; } void assertBaseNotCalled() { assertFalse(mBaseCalled); } Runnable expectFlags(final int flags) { return new Runnable() { @Override public void run() { assertEquals(flags | INTENT_FLAGS, mIntentFlags); }}; } TestContext() { super((InstrumentationRegistry.getTargetContext())); } boolean mTestingBackgroundUid; boolean mTestingStoppedProvider; private int mIntentFlags; private boolean mBaseCalled; final Runnable EXPECT_BASE_CALLED = new Runnable() { @Override public void run() { assertBaseCalled(); } }; } private interface Consumer<T> { void accept(T t); } }