FLAG_SECURE
can be placed on a Window
to indicate that the
contents of this Window
should be visible to the user but not captured
in screenshots and similar facilities.
Unfortunatately, the Android framework sometimes creates its own
Window
instances, such as the drop-down in a Spinner
. Even if you
set FLAG_SECURE
on the Window
for an activity, the Android framework
does not pass that flag to any other windows created on behalf of that
activity, and those windows show up in:
-
Screenshots and screencasts taken by the media projection APIs on Android 5.0+
-
The Assist API (e.g., Now On Tap) on Android 6.0+
-
Android Studio screen recordings on Android 4.4+
They might also show up in earlier devices using device-supplied screenshot options, though this has not yet been tested.
This has been demonstrated to affect:
AutoCompleteTextView
Spinner
(both dropdown and dialog modes)- the overflow menu of the framework-supplied action bar
ShareActionProvider
Dialog
and subclasses (e.g.,AlertDialog
)Toast
PopupWindow
ListPopupWindow
PopupMenu
- legacy context menus
Of these, only the Dialog
offers us access to its Window
, on which
we could apply FLAG_SECURE
, for developers that realize that this is
required.
For example, this screencast shows a sample app, without any use
of FLAG_SECURE
:
This is the same sample app, where FLAG_SECURE
has been applied to the
activity, but where other windows spawned by the aforementioned UI
elements still appear:
In addition to the aforementioned UI elements that exhibit this bug, this bug might also affect:
appcompat-v7
ports of those elements (e.g., its own overflow)Toolbar
(and itsappcompat-v7
port)Snackbar
These have not yet been tested.
Google has officially stated that all of this is working as intended.
If you are using FLAG_SECURE
, you should thoroughly exercise your app's
UI on Android 4.4+ while recording a screencast — the Android Studio screen recorder
would be a simple tool to use. Then, play back that screencast, see what
windows show up, and identify those that contain sensitive information
that should not appear. Some of the windows that appear will not contain
sensitive information — here, the risk is that you might add
sensitive information to them in the future but forget about this bug.
Then, you have two main courses of action: rewrite your UI to avoid the UI elements that are leaking this information, or attempt to patch the problem via some utility code in this library.
The security
library in this project offers a FlagSecureHelper
that
wraps up various mitigation utilities to help work around the FLAG_SECURE
issue. Specifically, it attempts to add FLAG_SECURE
to all windows
that are created "behind your back" by the Android framework. Instructions
on the project home page
show you how to add this library to your project.
Then, in your Activity
subclass, override the getSystemService(String name)
method to look like this:
@Override
public Object getSystemService(String name) {
Object result=super.getSystemService(name);
return(FlagSecureHelper.getWrappedSystemService(result, name));
}
This will handle several of the cases, notably AutoCompleteTextView
and Spinner
.
If you have your own dialogs, in your onCreate()
method of your
DialogFragment
, after you create your Dialog
, pass the Dialog
to FlagSecureHelper.markDialogAsSecure()
. This returns your
Dialog
to you, so you can pass it along (e.g., as the return value
from onCreate()
):
AlertDialog.Builder builder=new AlertDialog.Builder(getActivity());
Dialog dlg=builder
.setTitle(R.string.activity_label)
.setView(form)
.setNegativeButton(android.R.string.cancel, null)
.create();
return(FlagSecureHelper.markDialogAsSecure(dlg));
To raise a secure Toast
, use FlagSecureHelper.makeSecureToast()
in much
the same way as you would use Toast.makeText()
:
FlagSecureHelper
.makeSecureToast(this, R.string.msg_toast, Toast.LENGTH_LONG)
.show();
For example, here is a screencast of the same sample app as shown
above, but where FlagSecureHelper
has been applied:
As you can see (or, rather, not see), all windows used in the app
are marked with FLAG_SECURE
.
Since the issue is seen most on Android 4.4+, those are the versions
of Android that this code is tested on. You are welcome to try using
FlagSecureHelper
on older Android versions, or only apply it when
Build.VERSION.SDK_INT
is 19 or higher.
This matrix lists the test environments, what works, what does not work, and what has not yet been tested.
If you create a widget or other UI element in Android, and you directly or indirectly display a separate window that is logically tied to the activity, you need to do one of two things:
The best solution, in the author's opinion, is for you to check to see
if the hosting activity has FLAG_SECURE
on its window, and if so apply
FLAG_SECURE
to the window(s) that you create. These helper methods
let you find out if FLAG_SECURE
is applied to a Window
or
Activity
:
public static boolean usesFlagSecure(Context ctxt) {
if (ctxt instanceof Activity) {
return(usesFlagSecure((Activity)ctxt));
}
throw new IllegalArgumentException("Not an activity context!");
}
public static boolean usesFlagSecure(Activity a) {
return(usesFlagSecure(a.getWindow()));
}
public static boolean usesFlagSecure(Window w) {
int flags=w.getAttributes().flags;
return((flags & WindowManager.LayoutParams.FLAG_SECURE)!=0);
}
Alternatively, you could have a public
API for your UI element that
exposes access to the Window
, so developers using your UI element can
apply FLAG_SECURE
if desired.
The demoFlagSecure/
module contains demonstration activities for confirming
that the utility code does what it says it does. An early version of this
sample app was used for the screencasts shown earlier in this document.
This module has three product flavors:
-
insecure
does not applyFLAG_SECURE
-
bug
appliesFLAG_SECURE
to the activity, but does not do anything beyond that -
secure
appliesFLAG_SECURE
to the activity and applies the mitigation utility code outlined in this repository
The mitigation utility code relies upon wrapping the WindowManager
system service, to inject FLAG_SECURE
on any windows it is asked to
create. It also wraps the LayoutManager
system service, to ensure
that it uses a Context
that employs the wrapped WindowManager
. This
is all handled inside of FlagSecureHelper.getWrappedSystemService()
;
any requests for system services other than those two are left
unmodified.
Also, due to this bug,
we cannot use the wrapped WindowManager
inside of a dialog.
As a result, this code is seriously nasty. If it were not because this represents a privacy and security issue, the author would never dream of trying to use these techniques, as the author expects a never-ending parade of compatibility issues.
Please file an issue if you encounter problems using this code (e.g., it crashes in certain scenarios) or if you find other framework-created child windows that need to be made secure.
While you are welcome to submit a pull request, it might be used more as inspiration for a separate implementation rather than being used directly.
The author would like to acknowledge the assistance of Vivart Pandey, who pointed out the problem.
Here are the key events related to this issue, in chronological order:
- 2016/05/19: The problem comes to the author's attention
- 2016/05/20
- The author reproduces the problem
- The author files a security bug report following Google's guidelines
- Google assigns an internal tracking ID to the issue
- 2016/05/26: Google assigns a severity of "Moderate" to the issue
- 2016/05/31: Google retracts the severity and states that they "have concluded this is currently working as intended for previous versions of Android"
- 2016/06/01
- The author notifies Google about disclosure plans, since they have declined the issue
- Google opens the issue to the public
- 2016/06/06: The author opens this repository to the public
Mark Murphy is the founder and owner of CommonsWare and is the author of The Busy Coder's Guide to Android Development.