Block untrusted touches in InputDispatcher
With topic CL InputDispatcher now has the effect of each window for
cross-UID touch occlusion rules: ALLOW, USE_OPACITY or BLOCK_UNTRUSTED.
Check topic CL for exact meaning of each. USE_OPACITY is only used by
SAWs for now.
In this CL, InputDispatcher make use of that information for the stack
of windows above the touch-consuming window to block a touch or not.
The summary of the rules are:
* If there is any visible untrusted window from a different UID (than
the touch-consuming window UID) that has state BLOCK_UNTRUSTED, the
touch is blocked.
* Else, if there is any visible untrusted window from a different UID
that has state USE_OPACITY, we compute the composed obscuring opacity
by each stack of USE_OPACITY windows per UID of occluding window.
We take maximum of those and compare with secure setting
"maximum_obscuring_opacity_for_touch", if it's greater than the
setting the touch is blocked. This means the remaining visibility on the
touch-consuming window is not high enough to let the touch happen.
* Else we don't block the touch.
More details on go/cross-uid-touches. This doesn't interfere with
existing flags FLAG_WINDOW_IS_OBSCURED and
FLAG_WINDOW_IS_PARTIALLY_OBSCURED.
To compute the opacity we also propagate the alpha of each window from
SurfaceFlinger to InputDispatcher.
Test: atest WindowUntrustedTouchTest
Test: atest inputflinger_tests inputflinger_benchmarks libinput_tests
Test: go/try-cross-uid-touches for manual testing
Bug: 158002302
Change-Id: I673d7a5f16b19952311e8cb44a48af4349a4bd40
diff --git a/include/input/InputWindow.h b/include/input/InputWindow.h
index 7372022..36097d6 100644
--- a/include/input/InputWindow.h
+++ b/include/input/InputWindow.h
@@ -17,6 +17,7 @@
#ifndef _UI_INPUT_WINDOW_H
#define _UI_INPUT_WINDOW_H
+#include <android/os/TouchOcclusionMode.h>
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
#include <input/Flags.h>
@@ -30,6 +31,8 @@
#include "InputApplication.h"
+using android::os::TouchOcclusionMode;
+
namespace android {
/*
@@ -158,6 +161,10 @@
// in scaling of the TOUCH_MAJOR/TOUCH_MINOR axis.
float globalScaleFactor = 1.0f;
+ // The opacity of this window, from 0.0 to 1.0 (inclusive).
+ // An alpha of 1.0 means fully opaque and 0.0 means fully transparent.
+ float alpha;
+
// Transform applied to individual windows.
ui::Transform transform;
@@ -176,8 +183,10 @@
* motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
*/
bool trustedOverlay = false;
+ TouchOcclusionMode touchOcclusionMode = TouchOcclusionMode::BLOCK_UNTRUSTED;
int32_t ownerPid = -1;
int32_t ownerUid = -1;
+ std::string packageName;
Flags<Feature> inputFeatures;
int32_t displayId = ADISPLAY_ID_NONE;
int32_t portalToDisplayId = ADISPLAY_ID_NONE;
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 8f575a8..b442700 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -18,6 +18,8 @@
name: "inputconstants_aidl",
srcs: [
"android/os/IInputConstants.aidl",
+ "android/os/TouchOcclusionMode.aidl",
+ "android/os/BlockUntrustedTouchesMode.aidl",
],
}
@@ -71,10 +73,14 @@
"android/FocusRequest.aidl",
"android/InputApplicationInfo.aidl",
"android/os/IInputConstants.aidl",
+ "android/os/TouchOcclusionMode.aidl",
+ "android/os/BlockUntrustedTouchesMode.aidl",
"android/os/IInputFlinger.aidl",
"android/os/ISetInputWindowsListener.aidl",
],
+ export_shared_lib_headers: ["libbinder"],
+
shared_libs: [
"libutils",
"libbinder",
diff --git a/libs/input/InputWindow.cpp b/libs/input/InputWindow.cpp
index 885dc9b..8546bbb 100644
--- a/libs/input/InputWindow.cpp
+++ b/libs/input/InputWindow.cpp
@@ -59,10 +59,11 @@
info.surfaceInset == surfaceInset && info.globalScaleFactor == globalScaleFactor &&
info.transform == transform && info.touchableRegion.hasSameRects(touchableRegion) &&
info.visible == visible && info.trustedOverlay == trustedOverlay &&
- info.focusable == focusable && info.hasWallpaper == hasWallpaper &&
- info.paused == paused && info.ownerPid == ownerPid && info.ownerUid == ownerUid &&
- info.inputFeatures == inputFeatures && info.displayId == displayId &&
- info.portalToDisplayId == portalToDisplayId &&
+ info.focusable == focusable && info.touchOcclusionMode == touchOcclusionMode &&
+ info.hasWallpaper == hasWallpaper && info.paused == paused &&
+ info.ownerPid == ownerPid && info.ownerUid == ownerUid &&
+ info.packageName == packageName && info.inputFeatures == inputFeatures &&
+ info.displayId == displayId && info.portalToDisplayId == portalToDisplayId &&
info.replaceTouchableRegionWithCrop == replaceTouchableRegionWithCrop &&
info.applicationInfo == applicationInfo;
}
@@ -91,6 +92,7 @@
parcel->writeInt32(frameBottom) ?:
parcel->writeInt32(surfaceInset) ?:
parcel->writeFloat(globalScaleFactor) ?:
+ parcel->writeFloat(alpha) ?:
parcel->writeFloat(transform.dsdx()) ?:
parcel->writeFloat(transform.dtdx()) ?:
parcel->writeFloat(transform.tx()) ?:
@@ -102,8 +104,10 @@
parcel->writeBool(hasWallpaper) ?:
parcel->writeBool(paused) ?:
parcel->writeBool(trustedOverlay) ?:
+ parcel->writeInt32(static_cast<int32_t>(touchOcclusionMode)) ?:
parcel->writeInt32(ownerPid) ?:
parcel->writeInt32(ownerUid) ?:
+ parcel->writeUtf8AsUtf16(packageName) ?:
parcel->writeInt32(inputFeatures.get()) ?:
parcel->writeInt32(displayId) ?:
parcel->writeInt32(portalToDisplayId) ?:
@@ -134,6 +138,7 @@
flags = Flags<Flag>(parcel->readInt32());
type = static_cast<Type>(parcel->readInt32());
float dsdx, dtdx, tx, dtdy, dsdy, ty;
+ int32_t touchOcclusionModeInt;
// clang-format off
status = parcel->readInt32(&frameLeft) ?:
parcel->readInt32(&frameTop) ?:
@@ -141,6 +146,7 @@
parcel->readInt32(&frameBottom) ?:
parcel->readInt32(&surfaceInset) ?:
parcel->readFloat(&globalScaleFactor) ?:
+ parcel->readFloat(&alpha) ?:
parcel->readFloat(&dsdx) ?:
parcel->readFloat(&dtdx) ?:
parcel->readFloat(&tx) ?:
@@ -152,14 +158,18 @@
parcel->readBool(&hasWallpaper) ?:
parcel->readBool(&paused) ?:
parcel->readBool(&trustedOverlay) ?:
+ parcel->readInt32(&touchOcclusionModeInt) ?:
parcel->readInt32(&ownerPid) ?:
- parcel->readInt32(&ownerUid);
+ parcel->readInt32(&ownerUid) ?:
+ parcel->readUtf8FromUtf16(&packageName);
// clang-format on
if (status != OK) {
return status;
}
+ touchOcclusionMode = static_cast<TouchOcclusionMode>(touchOcclusionModeInt);
+
inputFeatures = Flags<Feature>(parcel->readInt32());
status = parcel->readInt32(&displayId) ?:
parcel->readInt32(&portalToDisplayId) ?:
diff --git a/libs/input/android/os/BlockUntrustedTouchesMode.aidl b/libs/input/android/os/BlockUntrustedTouchesMode.aidl
new file mode 100644
index 0000000..9504e99
--- /dev/null
+++ b/libs/input/android/os/BlockUntrustedTouchesMode.aidl
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * 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 android.os;
+
+
+/**
+ * Block untrusted touches feature mode.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum BlockUntrustedTouchesMode {
+ /** Feature is off. */
+ DISABLED,
+
+ /** Untrusted touches are flagged but not blocked. */
+ PERMISSIVE,
+
+ /** Untrusted touches are blocked. */
+ BLOCK
+}
diff --git a/libs/input/android/os/TouchOcclusionMode.aidl b/libs/input/android/os/TouchOcclusionMode.aidl
new file mode 100644
index 0000000..106f159
--- /dev/null
+++ b/libs/input/android/os/TouchOcclusionMode.aidl
@@ -0,0 +1,47 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * 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 android.os;
+
+
+/**
+ * Touch occlusion modes: These modes represent how windows are taken into
+ * consideration in order to decide whether to block obscured touches or
+ * not.
+ *
+ * @hide
+ */
+@Backing(type="int")
+enum TouchOcclusionMode {
+ /**
+ * Touches that pass through this window will be blocked if they are
+ * consumed by a different UID and this window is not trusted.
+ */
+ BLOCK_UNTRUSTED,
+
+ /**
+ * The window's opacity will be taken into consideration for touch
+ * occlusion rules if the touch passes through it and the window is not
+ * trusted.
+ */
+ USE_OPACITY,
+
+ /**
+ * The window won't count for touch occlusion rules if the touch passes
+ * through it.
+ */
+ ALLOW
+}
diff --git a/libs/input/tests/InputWindow_test.cpp b/libs/input/tests/InputWindow_test.cpp
index 65a7761..c18a17f 100644
--- a/libs/input/tests/InputWindow_test.cpp
+++ b/libs/input/tests/InputWindow_test.cpp
@@ -53,13 +53,16 @@
i.frameBottom = 19;
i.surfaceInset = 17;
i.globalScaleFactor = 0.3;
+ i.alpha = 0.7;
i.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1});
i.visible = false;
i.focusable = false;
i.hasWallpaper = false;
i.paused = false;
+ i.touchOcclusionMode = TouchOcclusionMode::ALLOW;
i.ownerPid = 19;
i.ownerUid = 24;
+ i.packageName = "com.example.package";
i.inputFeatures = InputWindowInfo::Feature::DISABLE_USER_ACTIVITY;
i.displayId = 34;
i.portalToDisplayId = 2;
@@ -86,13 +89,16 @@
ASSERT_EQ(i.frameBottom, i2.frameBottom);
ASSERT_EQ(i.surfaceInset, i2.surfaceInset);
ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
+ ASSERT_EQ(i.alpha, i2.alpha);
ASSERT_EQ(i.transform, i2.transform);
ASSERT_EQ(i.visible, i2.visible);
ASSERT_EQ(i.focusable, i2.focusable);
ASSERT_EQ(i.hasWallpaper, i2.hasWallpaper);
ASSERT_EQ(i.paused, i2.paused);
+ ASSERT_EQ(i.touchOcclusionMode, i2.touchOcclusionMode);
ASSERT_EQ(i.ownerPid, i2.ownerPid);
ASSERT_EQ(i.ownerUid, i2.ownerUid);
+ ASSERT_EQ(i.packageName, i2.packageName);
ASSERT_EQ(i.inputFeatures, i2.inputFeatures);
ASSERT_EQ(i.displayId, i2.displayId);
ASSERT_EQ(i.portalToDisplayId, i2.portalToDisplayId);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 3ccb0c9..ab0d340 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -402,6 +402,7 @@
// To avoid leaking stack in case that call never comes, and for tests,
// initialize it here anyways.
mInTouchMode(true),
+ mMaximumObscuringOpacityForTouch(1.0f),
mFocusedDisplayId(ADISPLAY_ID_DEFAULT) {
mLooper = new Looper(false);
mReporter = createInputReporter();
@@ -1708,6 +1709,22 @@
}
}
+ // Drop events that can't be trusted due to occlusion
+ if (newTouchedWindowHandle != nullptr &&
+ mBlockUntrustedTouchesMode != BlockUntrustedTouchesMode::DISABLED) {
+ TouchOcclusionInfo occlusionInfo =
+ computeTouchOcclusionInfoLocked(newTouchedWindowHandle, x, y);
+ // The order of the operands in the 'if' below is important because even if the feature
+ // is not BLOCK we want isTouchTrustedLocked() to execute in order to log details to
+ // logcat.
+ if (!isTouchTrustedLocked(occlusionInfo) &&
+ mBlockUntrustedTouchesMode == BlockUntrustedTouchesMode::BLOCK) {
+ ALOGW("Dropping untrusted touch event due to %s/%d",
+ occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid);
+ newTouchedWindowHandle = nullptr;
+ }
+ }
+
// Also don't send the new touch event to unresponsive gesture monitors
newGestureMonitors = selectResponsiveMonitorsLocked(newGestureMonitors);
@@ -2121,6 +2138,84 @@
return true;
}
+/**
+ * Returns touch occlusion information in the form of TouchOcclusionInfo. To check if the touch is
+ * untrusted, one should check:
+ *
+ * 1. If result.hasBlockingOcclusion is true.
+ * If it's, it means the touch should be blocked due to a window with occlusion mode of
+ * BLOCK_UNTRUSTED.
+ *
+ * 2. If result.obscuringOpacity > mMaximumObscuringOpacityForTouch.
+ * If it is (and 1 is false), then the touch should be blocked because a stack of windows
+ * (possibly only one) with occlusion mode of USE_OPACITY from one UID resulted in a composed
+ * obscuring opacity above the threshold. Note that if there was no window of occlusion mode
+ * USE_OPACITY, result.obscuringOpacity would've been 0 and since
+ * mMaximumObscuringOpacityForTouch >= 0, the condition above would never be true.
+ *
+ * If neither of those is true, then it means the touch can be allowed.
+ */
+InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLocked(
+ const sp<InputWindowHandle>& windowHandle, int32_t x, int32_t y) const {
+ int32_t displayId = windowHandle->getInfo()->displayId;
+ const std::vector<sp<InputWindowHandle>>& windowHandles = getWindowHandlesLocked(displayId);
+ TouchOcclusionInfo info;
+ info.hasBlockingOcclusion = false;
+ info.obscuringOpacity = 0;
+ info.obscuringUid = -1;
+ std::map<int32_t, float> opacityByUid;
+ for (const sp<InputWindowHandle>& otherHandle : windowHandles) {
+ if (windowHandle == otherHandle) {
+ break; // All future windows are below us. Exit early.
+ }
+ const InputWindowInfo* otherInfo = otherHandle->getInfo();
+ if (canBeObscuredBy(windowHandle, otherHandle) &&
+ windowHandle->getInfo()->ownerUid != otherInfo->ownerUid &&
+ otherInfo->frameContainsPoint(x, y)) {
+ // canBeObscuredBy() has returned true above, which means this window is untrusted, so
+ // we perform the checks below to see if the touch can be propagated or not based on the
+ // window's touch occlusion mode
+ if (otherInfo->touchOcclusionMode == TouchOcclusionMode::BLOCK_UNTRUSTED) {
+ info.hasBlockingOcclusion = true;
+ info.obscuringUid = otherInfo->ownerUid;
+ info.obscuringPackage = otherInfo->packageName;
+ break;
+ }
+ if (otherInfo->touchOcclusionMode == TouchOcclusionMode::USE_OPACITY) {
+ uint32_t uid = otherInfo->ownerUid;
+ float opacity =
+ (opacityByUid.find(uid) == opacityByUid.end()) ? 0 : opacityByUid[uid];
+ // Given windows A and B:
+ // opacity(A, B) = 1 - [1 - opacity(A)] * [1 - opacity(B)]
+ opacity = 1 - (1 - opacity) * (1 - otherInfo->alpha);
+ opacityByUid[uid] = opacity;
+ if (opacity > info.obscuringOpacity) {
+ info.obscuringOpacity = opacity;
+ info.obscuringUid = uid;
+ info.obscuringPackage = otherInfo->packageName;
+ }
+ }
+ }
+ }
+ return info;
+}
+
+bool InputDispatcher::isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const {
+ if (occlusionInfo.hasBlockingOcclusion) {
+ ALOGW("Untrusted touch due to occlusion by %s/%d", occlusionInfo.obscuringPackage.c_str(),
+ occlusionInfo.obscuringUid);
+ return false;
+ }
+ if (occlusionInfo.obscuringOpacity > mMaximumObscuringOpacityForTouch) {
+ ALOGW("Untrusted touch due to occlusion by %s/%d (obscuring opacity = "
+ "%.2f, maximum allowed = %.2f)",
+ occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid,
+ occlusionInfo.obscuringOpacity, mMaximumObscuringOpacityForTouch);
+ return false;
+ }
+ return true;
+}
+
bool InputDispatcher::isWindowObscuredAtPointLocked(const sp<InputWindowHandle>& windowHandle,
int32_t x, int32_t y) const {
int32_t displayId = windowHandle->getInfo()->displayId;
@@ -4060,6 +4155,21 @@
mInTouchMode = inTouchMode;
}
+void InputDispatcher::setMaximumObscuringOpacityForTouch(float opacity) {
+ if (opacity < 0 || opacity > 1) {
+ LOG_ALWAYS_FATAL("Maximum obscuring opacity for touch should be >= 0 and <= 1");
+ return;
+ }
+
+ std::scoped_lock lock(mLock);
+ mMaximumObscuringOpacityForTouch = opacity;
+}
+
+void InputDispatcher::setBlockUntrustedTouchesMode(BlockUntrustedTouchesMode mode) {
+ std::scoped_lock lock(mLock);
+ mBlockUntrustedTouchesMode = mode;
+}
+
bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken) {
if (fromToken == toToken) {
if (DEBUG_FOCUS) {
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 4fcdcc2..d361b17 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -112,6 +112,8 @@
virtual void setInputDispatchMode(bool enabled, bool frozen) override;
virtual void setInputFilterEnabled(bool enabled) override;
virtual void setInTouchMode(bool inTouchMode) override;
+ virtual void setMaximumObscuringOpacityForTouch(float opacity) override;
+ virtual void setBlockUntrustedTouchesMode(BlockUntrustedTouchesMode mode) override;
virtual bool transferTouchFocus(const sp<IBinder>& fromToken,
const sp<IBinder>& toToken) override;
@@ -296,6 +298,8 @@
bool mDispatchFrozen GUARDED_BY(mLock);
bool mInputFilterEnabled GUARDED_BY(mLock);
bool mInTouchMode GUARDED_BY(mLock);
+ float mMaximumObscuringOpacityForTouch GUARDED_BY(mLock);
+ BlockUntrustedTouchesMode mBlockUntrustedTouchesMode GUARDED_BY(mLock);
std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>> mWindowHandlesByDisplay
GUARDED_BY(mLock);
@@ -446,6 +450,17 @@
void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
bool checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
const InjectionState* injectionState);
+
+ struct TouchOcclusionInfo {
+ bool hasBlockingOcclusion;
+ float obscuringOpacity;
+ std::string obscuringPackage;
+ int32_t obscuringUid;
+ };
+
+ TouchOcclusionInfo computeTouchOcclusionInfoLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t x, int32_t y) const REQUIRES(mLock);
+ bool isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const REQUIRES(mLock);
bool isWindowObscuredAtPointLocked(const sp<InputWindowHandle>& windowHandle, int32_t x,
int32_t y) const REQUIRES(mLock);
bool isWindowObscuredLocked(const sp<InputWindowHandle>& windowHandle) const REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 67d9a06..65687c4 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -20,12 +20,15 @@
#include <InputListener.h>
#include <android-base/result.h>
#include <android/FocusRequest.h>
+#include <android/os/BlockUntrustedTouchesMode.h>
#include <android/os/ISetInputWindowsListener.h>
#include <input/InputApplication.h>
#include <input/InputTransport.h>
#include <input/InputWindow.h>
#include <unordered_map>
+using android::os::BlockUntrustedTouchesMode;
+
namespace android {
/*
@@ -145,6 +148,21 @@
*/
virtual void setInTouchMode(bool inTouchMode) = 0;
+ /**
+ * Sets the maximum allowed obscuring opacity by UID to propagate touches.
+ * For certain window types (eg. SAWs), the decision of honoring
+ * FLAG_NOT_TOUCHABLE or not depends on the combined obscuring opacity of
+ * the windows above the touch-consuming window.
+ */
+ virtual void setMaximumObscuringOpacityForTouch(float opacity) = 0;
+
+ /**
+ * Sets the mode of the block untrusted touches feature.
+ *
+ * TODO(b/169067926): Clean-up feature modes.
+ */
+ virtual void setBlockUntrustedTouchesMode(BlockUntrustedTouchesMode mode) = 0;
+
/* Transfers touch focus from one window to another window.
*
* Returns true on success. False if the window did not actually have touch focus.
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 85046a4..0127b31 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2485,6 +2485,7 @@
// InputDispatcher, and obviously if they aren't visible they can't occlude
// anything.
info.visible = hasInputInfo() ? canReceiveInput() : isVisible();
+ info.alpha = getAlpha();
auto cropLayer = mDrawingState.touchableRegionCrop.promote();
if (info.replaceTouchableRegionWithCrop) {