summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2015-08-03 17:14:46 -0700
committerDianne Hackborn <hackbod@google.com>2015-08-03 17:33:38 -0700
commitfb81d09d359480f9e43bbf300877b60de05f4816 (patch)
tree35d575855e4c528833f78ed3d0ddaff2660bea3e
parent0ca1e98fdf300ddbbccbfce26bba5947109597f1 (diff)
downloadframeworks_base-fb81d09d359480f9e43bbf300877b60de05f4816.zip
frameworks_base-fb81d09d359480f9e43bbf300877b60de05f4816.tar.gz
frameworks_base-fb81d09d359480f9e43bbf300877b60de05f4816.tar.bz2
Fix issue #22860466: viapi security bug - rubber stamping in nested VIs
Add new Activity.isVoiceInteractionRoot() API that an activity can use to determine whether it is the root activity of a voice interaction session started by the user's designated voice interaction service. This is a special new API that apps must explicitly check, because as with visual activities the model behind an activity should usually be that it accomplishes its task by interacting with the user (implicitly getting their approval) rather than trusting that whoever invoked it is telling it to do what the user once. In the voice world, however, there are some cases where quick interactions want to allow for immediate execution without further user involvement, so this API allows for that without opening up security holes from other applications. Change-Id: Ie02d2458f16cb0b12af825641bcf8beaf086931b
-rw-r--r--api/current.txt1
-rw-r--r--api/system-current.txt1
-rw-r--r--core/java/android/app/Activity.java16
-rw-r--r--core/java/android/app/ActivityManagerNative.java23
-rw-r--r--core/java/android/app/IActivityManager.java3
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java11
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java10
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java9
-rw-r--r--tests/VoiceInteraction/res/layout/test_interaction.xml13
-rw-r--r--tests/VoiceInteraction/res/values/strings.xml1
-rw-r--r--tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java11
11 files changed, 92 insertions, 7 deletions
diff --git a/api/current.txt b/api/current.txt
index 68ecf96..e16294e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3341,6 +3341,7 @@ package android.app {
method public boolean isImmersive();
method public boolean isTaskRoot();
method public boolean isVoiceInteraction();
+ method public boolean isVoiceInteractionRoot();
method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public boolean moveTaskToBack(boolean);
method public boolean navigateUpTo(android.content.Intent);
diff --git a/api/system-current.txt b/api/system-current.txt
index 2e0613a..a784378 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3444,6 +3444,7 @@ package android.app {
method public boolean isImmersive();
method public boolean isTaskRoot();
method public boolean isVoiceInteraction();
+ method public boolean isVoiceInteractionRoot();
method public final deprecated android.database.Cursor managedQuery(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public boolean moveTaskToBack(boolean);
method public boolean navigateUpTo(android.content.Intent);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index bdea608..3c8af0d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1229,6 +1229,22 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Like {@link #isVoiceInteraction}, but only returns true if this is also the root
+ * of a voice interaction. That is, returns true if this activity was directly
+ * started by the voice interaction service as the initiation of a voice interaction.
+ * Otherwise, for example if it was started by another activity while under voice
+ * interaction, returns false.
+ */
+ public boolean isVoiceInteractionRoot() {
+ try {
+ return mVoiceInteractor != null
+ && ActivityManagerNative.getDefault().isRootVoiceInteraction(mToken);
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ /**
* Retrieve the active {@link VoiceInteractor} that the user is going through to
* interact with this activity.
*/
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index b758a7a..e144c29 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2582,6 +2582,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeInt(res ? 1 : 0);
return true;
}
+
+ case IS_ROOT_VOICE_INTERACTION_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder token = data.readStrongBinder();
+ boolean res = isRootVoiceInteraction(token);
+ reply.writeNoException();
+ reply.writeInt(res ? 1 : 0);
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -5962,5 +5971,19 @@ class ActivityManagerProxy implements IActivityManager
return res != 0;
}
+ @Override
+ public boolean isRootVoiceInteraction(IBinder token) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(token);
+ mRemote.transact(IS_ROOT_VOICE_INTERACTION_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return res != 0;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 9ebbb9b..90216af 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -515,6 +515,8 @@ public interface IActivityManager extends IInterface {
public boolean setProcessMemoryTrimLevel(String process, int uid, int level)
throws RemoteException;
+ public boolean isRootVoiceInteraction(IBinder token) throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -861,4 +863,5 @@ public interface IActivityManager extends IInterface {
int IS_SCREEN_CAPTURE_ALLOWED_ON_CURRENT_ACTIVITY_TRANSACTION
= IBinder.FIRST_CALL_TRANSACTION+299;
int SHOW_ASSIST_FROM_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+300;
+ int IS_ROOT_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+301;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 783dea5..8f2c838 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6552,6 +6552,17 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
+ public boolean isRootVoiceInteraction(IBinder token) {
+ synchronized(this) {
+ ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r == null) {
+ return false;
+ }
+ return r.rootVoiceInteraction;
+ }
+ }
+
+ @Override
public IIntentSender getIntentSender(int type,
String packageName, IBinder token, String resultWho,
int requestCode, Intent[] intents, String[] resolvedTypes,
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 0957eb5..3de2009 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -107,6 +107,7 @@ final class ActivityRecord {
boolean fullscreen; // covers the full screen?
final boolean noDisplay; // activity is not displayed?
final boolean componentSpecified; // did caller specifiy an explicit component?
+ final boolean rootVoiceInteraction; // was this the root activity of a voice interaction?
static final int APPLICATION_ACTIVITY_TYPE = 0;
static final int HOME_ACTIVITY_TYPE = 1;
@@ -207,6 +208,9 @@ final class ActivityRecord {
pw.print(prefix); pw.print("stateNotNeeded="); pw.print(stateNotNeeded);
pw.print(" componentSpecified="); pw.print(componentSpecified);
pw.print(" mActivityType="); pw.println(mActivityType);
+ if (rootVoiceInteraction) {
+ pw.print(prefix); pw.print("rootVoiceInteraction="); pw.println(rootVoiceInteraction);
+ }
pw.print(prefix); pw.print("compat="); pw.print(compat);
pw.print(" labelRes=0x"); pw.print(Integer.toHexString(labelRes));
pw.print(" icon=0x"); pw.print(Integer.toHexString(icon));
@@ -432,7 +436,8 @@ final class ActivityRecord {
int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
ActivityInfo aInfo, Configuration _configuration,
ActivityRecord _resultTo, String _resultWho, int _reqCode,
- boolean _componentSpecified, ActivityStackSupervisor supervisor,
+ boolean _componentSpecified, boolean _rootVoiceInteraction,
+ ActivityStackSupervisor supervisor,
ActivityContainer container, Bundle options) {
service = _service;
appToken = new Token(this, service);
@@ -444,6 +449,7 @@ final class ActivityRecord {
shortComponentName = _intent.getComponent().flattenToShortString();
resolvedType = _resolvedType;
componentSpecified = _componentSpecified;
+ rootVoiceInteraction = _rootVoiceInteraction;
configuration = _configuration;
stackConfigOverride = (container != null)
? container.mStack.mOverrideConfig : Configuration.EMPTY;
@@ -1257,7 +1263,7 @@ final class ActivityRecord {
}
final ActivityRecord r = new ActivityRecord(service, /*caller*/null, launchedFromUid,
launchedFromPackage, intent, resolvedType, aInfo, service.getConfiguration(),
- null, null, 0, componentSpecified, stackSupervisor, null, null);
+ null, null, 0, componentSpecified, false, stackSupervisor, null, null);
r.persistentState = persistentState;
r.taskDescription = taskDescription;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 0be2f6f..71fd49b 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1506,6 +1506,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
try {
+ intent.addCategory(Intent.CATEGORY_VOICE);
if (!AppGlobals.getPackageManager().activitySupportsIntent(
intent.getComponent(), intent, resolvedType)) {
err = ActivityManager.START_NOT_VOICE_COMPATIBLE;
@@ -1626,7 +1627,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
- requestCode, componentSpecified, this, container, options);
+ requestCode, componentSpecified, voiceSession != null, this, container, options);
if (outActivity != null) {
outActivity[0] = r;
}
@@ -3721,19 +3722,19 @@ public final class ActivityStackSupervisor implements DisplayListener {
@Override
public void onDisplayAdded(int displayId) {
- Slog.v(TAG, "Display added displayId=" + displayId);
+ if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId);
mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_ADDED, displayId, 0));
}
@Override
public void onDisplayRemoved(int displayId) {
- Slog.v(TAG, "Display removed displayId=" + displayId);
+ if (DEBUG_STACK) Slog.v(TAG, "Display removed displayId=" + displayId);
mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_REMOVED, displayId, 0));
}
@Override
public void onDisplayChanged(int displayId) {
- Slog.v(TAG, "Display changed displayId=" + displayId);
+ if (DEBUG_STACK) Slog.v(TAG, "Display changed displayId=" + displayId);
mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0));
}
diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml
index d1a7ad5..277117e 100644
--- a/tests/VoiceInteraction/res/layout/test_interaction.xml
+++ b/tests/VoiceInteraction/res/layout/test_interaction.xml
@@ -39,6 +39,19 @@
android:layout_marginTop="16dp"
android:orientation="horizontal">
+ <Button android:id="@+id/airplane"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/launchAirplane"
+ />
+
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:orientation="horizontal">
+
<Button android:id="@+id/complete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml
index 29ffe21..c665c23 100644
--- a/tests/VoiceInteraction/res/values/strings.xml
+++ b/tests/VoiceInteraction/res/values/strings.xml
@@ -21,6 +21,7 @@
<string name="tree">Tree</string>
<string name="text">Text</string>
<string name="asyncStructure">(Async structure goes here)</string>
+ <string name="launchAirplane">Launch airplane mode</string>
<string name="confirm">Confirm</string>
<string name="abort">Abort</string>
<string name="complete">Complete</string>
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
index e10d89f..ada0e21 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -21,6 +21,7 @@ import android.app.VoiceInteractor;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
+import android.provider.Settings;
import android.service.voice.VoiceInteractionService;
import android.util.Log;
import android.view.View;
@@ -39,6 +40,7 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
VoiceInteractor mInteractor;
VoiceInteractor.Request mCurrentRequest = null;
TextView mLog;
+ Button mAirplaneButton;
Button mAbortButton;
Button mCompleteButton;
Button mCommandButton;
@@ -65,6 +67,8 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
setContentView(R.layout.test_interaction);
mLog = (TextView)findViewById(R.id.log);
+ mAirplaneButton = (Button)findViewById(R.id.airplane);
+ mAirplaneButton.setOnClickListener(this);
mAbortButton = (Button)findViewById(R.id.abort);
mAbortButton.setOnClickListener(this);
mCompleteButton = (Button)findViewById(R.id.complete);
@@ -122,7 +126,12 @@ public class TestInteractionActivity extends Activity implements View.OnClickLis
@Override
public void onClick(View v) {
- if (v == mAbortButton) {
+ if (v == mAirplaneButton) {
+ Intent intent = new Intent(Settings.ACTION_VOICE_CONTROL_AIRPLANE_MODE);
+ intent.addCategory(Intent.CATEGORY_VOICE);
+ intent.putExtra(Settings.EXTRA_AIRPLANE_MODE_ENABLED, true);
+ startActivity(intent);
+ } else if (v == mAbortButton) {
VoiceInteractor.AbortVoiceRequest req = new TestAbortVoice();
mInteractor.submitRequest(req, REQUEST_ABORT);
} else if (v == mCompleteButton) {