From 8b54307ce665be16658d5865bec29e731a2652bb Mon Sep 17 00:00:00 2001 From: Songchun Fan Date: Thu, 27 May 2021 08:45:22 -0700 Subject: [PATCH] [SettingsProvider] add maxTargetSdk in @Readable New annotation field that allows Settings key owners to specify the maximum target sdk level of the caller app allowed to read the hidden settings key. Test: atest android.appsecurity.cts.ReadableSettingsFieldsTest BUG: 187314081 Change-Id: Ibb603df4c8bca75e3788258e69862595c22e5154 --- core/java/android/provider/Settings.java | 73 ++++++++++++++----- .../providers/settings/SettingsProvider.java | 51 ++++++++++--- 2 files changed, 96 insertions(+), 28 deletions(-) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e3302d1ca9a04..13e5cda7cca31 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2773,6 +2773,7 @@ public final class Settings { private final ArraySet mReadableFields; private final ArraySet mAllFields; + private final ArrayMap mReadableFieldsWithMaxTargetSdk; @GuardedBy("this") private GenerationTracker mGenerationTracker; @@ -2794,7 +2795,9 @@ public final class Settings { mProviderHolder = providerHolder; mReadableFields = new ArraySet<>(); mAllFields = new ArraySet<>(); - getPublicSettingsForClass(callerClass, mAllFields, mReadableFields); + mReadableFieldsWithMaxTargetSdk = new ArrayMap<>(); + getPublicSettingsForClass(callerClass, mAllFields, mReadableFields, + mReadableFieldsWithMaxTargetSdk); } public boolean putStringForUser(ContentResolver cr, String name, String value, @@ -2851,13 +2854,34 @@ public final class Settings { // Settings.Global and is not annotated as @Readable. // Notice that a key string that is not defined in any of the Settings.* classes will // still be regarded as readable. - if (!isCallerExemptFromReadableRestriction() - && mAllFields.contains(name) && !mReadableFields.contains(name)) { - throw new SecurityException( - "Settings key: <" + name + "> is not readable. From S+, settings keys " - + "annotated with @hide are restricted to system_server and system " - + "apps only, unless they are annotated with @Readable."); + if (!isCallerExemptFromReadableRestriction() && mAllFields.contains(name)) { + if (!mReadableFields.contains(name)) { + throw new SecurityException( + "Settings key: <" + name + "> is not readable. From S+, settings keys " + + "annotated with @hide are restricted to system_server and " + + "system apps only, unless they are annotated with @Readable." + ); + } else { + // When the target settings key has @Readable annotation, if the caller app's + // target sdk is higher than the maxTargetSdk of the annotation, reject access. + if (mReadableFieldsWithMaxTargetSdk.containsKey(name)) { + final int maxTargetSdk = mReadableFieldsWithMaxTargetSdk.get(name); + final Application application = ActivityThread.currentApplication(); + final boolean targetSdkCheckOk = application != null + && application.getApplicationInfo() != null + && application.getApplicationInfo().targetSdkVersion + <= maxTargetSdk; + if (!targetSdkCheckOk) { + throw new SecurityException( + "Settings key: <" + name + "> is only readable to apps with " + + "targetSdkVersion lower than or equal to: " + + maxTargetSdk + ); + } + } + } } + final boolean isSelf = (userHandle == UserHandle.myUserId()); int currentGeneration = -1; if (isSelf) { @@ -3225,10 +3249,12 @@ public final class Settings { @Target({ ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) private @interface Readable { + int maxTargetSdk() default 0; } private static void getPublicSettingsForClass( - Class callerClass, Set allKeys, Set readableKeys) { + Class callerClass, Set allKeys, Set readableKeys, + ArrayMap keysWithMaxTargetSdk) { final Field[] allFields = callerClass.getDeclaredFields(); try { for (int i = 0; i < allFields.length; i++) { @@ -3241,8 +3267,15 @@ public final class Settings { continue; } allKeys.add((String) value); - if (field.getAnnotation(Readable.class) != null) { - readableKeys.add((String) value); + final Readable annotation = field.getAnnotation(Readable.class); + + if (annotation != null) { + final String key = (String) value; + final int maxTargetSdk = annotation.maxTargetSdk(); + readableKeys.add(key); + if (maxTargetSdk != 0) { + keysWithMaxTargetSdk.put(key, maxTargetSdk); + } } } } catch (IllegalAccessException ignored) { @@ -3404,8 +3437,10 @@ public final class Settings { } /** @hide */ - public static void getPublicSettings(Set allKeys, Set readableKeys) { - getPublicSettingsForClass(System.class, allKeys, readableKeys); + public static void getPublicSettings(Set allKeys, Set readableKeys, + ArrayMap readableKeysWithMaxTargetSdk) { + getPublicSettingsForClass(System.class, allKeys, readableKeys, + readableKeysWithMaxTargetSdk); } /** @@ -5734,8 +5769,10 @@ public final class Settings { } /** @hide */ - public static void getPublicSettings(Set allKeys, Set readableKeys) { - getPublicSettingsForClass(Secure.class, allKeys, readableKeys); + public static void getPublicSettings(Set allKeys, Set readableKeys, + ArrayMap readableKeysWithMaxTargetSdk) { + getPublicSettingsForClass(Secure.class, allKeys, readableKeys, + readableKeysWithMaxTargetSdk); } /** @@ -11023,7 +11060,7 @@ public final class Settings { * @hide */ @UnsupportedAppUsage - @Readable + @Readable(maxTargetSdk = Build.VERSION_CODES.R) public static final String MOBILE_DATA = "mobile_data"; /** @@ -14965,8 +15002,10 @@ public final class Settings { } /** @hide */ - public static void getPublicSettings(Set allKeys, Set readableKeys) { - getPublicSettingsForClass(Global.class, allKeys, readableKeys); + public static void getPublicSettings(Set allKeys, Set readableKeys, + ArrayMap readableKeysWithMaxTargetSdk) { + getPublicSettingsForClass(Global.class, allKeys, readableKeys, + readableKeysWithMaxTargetSdk); } /** diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index bce576d1e1d8d..dd9a6eee7327b 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -310,20 +310,29 @@ public class SettingsProvider extends ContentProvider { private static final Set sAllSecureSettings = new ArraySet<>(); private static final Set sReadableSecureSettings = new ArraySet<>(); + private static final ArrayMap sReadableSecureSettingsWithMaxTargetSdk = + new ArrayMap<>(); static { - Settings.Secure.getPublicSettings(sAllSecureSettings, sReadableSecureSettings); + Settings.Secure.getPublicSettings(sAllSecureSettings, sReadableSecureSettings, + sReadableSecureSettingsWithMaxTargetSdk); } private static final Set sAllSystemSettings = new ArraySet<>(); private static final Set sReadableSystemSettings = new ArraySet<>(); + private static final ArrayMap sReadableSystemSettingsWithMaxTargetSdk = + new ArrayMap<>(); static { - Settings.System.getPublicSettings(sAllSystemSettings, sReadableSystemSettings); + Settings.System.getPublicSettings(sAllSystemSettings, sReadableSystemSettings, + sReadableSystemSettingsWithMaxTargetSdk); } private static final Set sAllGlobalSettings = new ArraySet<>(); private static final Set sReadableGlobalSettings = new ArraySet<>(); + private static final ArrayMap sReadableGlobalSettingsWithMaxTargetSdk = + new ArrayMap<>(); static { - Settings.Global.getPublicSettings(sAllGlobalSettings, sReadableGlobalSettings); + Settings.Global.getPublicSettings(sAllGlobalSettings, sReadableGlobalSettings, + sReadableGlobalSettingsWithMaxTargetSdk); } private final Object mLock = new Object(); @@ -2065,7 +2074,7 @@ public class SettingsProvider extends ContentProvider { } if ((ai.flags & ApplicationInfo.FLAG_TEST_ONLY) == 0) { // Skip checking readable annotations for test_only apps - checkReadableAnnotation(settingsType, settingName); + checkReadableAnnotation(settingsType, settingName, ai.targetSandboxVersion); } /** * some settings need additional permission check, this is to have a matching security @@ -2101,35 +2110,55 @@ public class SettingsProvider extends ContentProvider { /** * Check if the target settings key is readable. Reject if the caller app is trying to access a * settings key defined in the Settings.Secure, Settings.System or Settings.Global and is not - * annotated as @Readable. + * annotated as @Readable. Reject if the caller app is targeting an SDK level that is higher + * than the maxTargetSdk specified in the @Readable annotation. * Notice that a key string that is not defined in any of the Settings.* classes will still be * regarded as readable. */ - private void checkReadableAnnotation(int settingsType, String settingName) { + private void checkReadableAnnotation(int settingsType, String settingName, + int targetSdkVersion) { final Set allFields; final Set readableFields; + final ArrayMap readableFieldsWithMaxTargetSdk; switch (settingsType) { case SETTINGS_TYPE_GLOBAL: allFields = sAllGlobalSettings; readableFields = sReadableGlobalSettings; + readableFieldsWithMaxTargetSdk = sReadableGlobalSettingsWithMaxTargetSdk; break; case SETTINGS_TYPE_SYSTEM: allFields = sAllSystemSettings; readableFields = sReadableSystemSettings; + readableFieldsWithMaxTargetSdk = sReadableSystemSettingsWithMaxTargetSdk; break; case SETTINGS_TYPE_SECURE: allFields = sAllSecureSettings; readableFields = sReadableSecureSettings; + readableFieldsWithMaxTargetSdk = sReadableSecureSettingsWithMaxTargetSdk; break; default: throw new IllegalArgumentException("Invalid settings type: " + settingsType); } - if (allFields.contains(settingName) && !readableFields.contains(settingName)) { - throw new SecurityException( - "Settings key: <" + settingName + "> is not readable. From S+, settings keys " - + "annotated with @hide are restricted to system_server and system " - + "apps only, unless they are annotated with @Readable."); + if (allFields.contains(settingName)) { + if (!readableFields.contains(settingName)) { + throw new SecurityException( + "Settings key: <" + settingName + "> is not readable. From S+, settings " + + "keys annotated with @hide are restricted to system_server and " + + "system apps only, unless they are annotated with @Readable." + ); + } else { + if (readableFieldsWithMaxTargetSdk.containsKey(settingName)) { + final int maxTargetSdk = readableFieldsWithMaxTargetSdk.get(settingName); + if (targetSdkVersion > maxTargetSdk) { + throw new SecurityException( + "Settings key: <" + settingName + "> is only readable to apps with " + + "targetSdkVersion lower than or equal to: " + + maxTargetSdk + ); + } + } + } } }