Merge "StrictMode to catch storage while locked."
This commit is contained in:
committed by
Android (Google) Code Review
commit
ce77407144
@@ -33210,6 +33210,7 @@ package android.os {
|
||||
method public android.os.StrictMode.VmPolicy.Builder detectAll();
|
||||
method public android.os.StrictMode.VmPolicy.Builder detectCleartextNetwork();
|
||||
method public android.os.StrictMode.VmPolicy.Builder detectContentUriWithoutPermission();
|
||||
method public android.os.StrictMode.VmPolicy.Builder detectCredentialProtectedWhileLocked();
|
||||
method public android.os.StrictMode.VmPolicy.Builder detectFileUriExposure();
|
||||
method public android.os.StrictMode.VmPolicy.Builder detectImplicitDirectBoot();
|
||||
method public android.os.StrictMode.VmPolicy.Builder detectLeakedClosableObjects();
|
||||
@@ -33613,6 +33614,9 @@ package android.os.strictmode {
|
||||
public final class ContentUriWithoutPermissionViolation extends android.os.strictmode.Violation {
|
||||
}
|
||||
|
||||
public final class CredentialProtectedWhileLockedViolation extends android.os.strictmode.Violation {
|
||||
}
|
||||
|
||||
public final class CustomViolation extends android.os.strictmode.Violation {
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,8 @@ import android.view.autofill.AutofillManager.AutofillClient;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import dalvik.system.BlockGuard;
|
||||
|
||||
import libcore.io.Memory;
|
||||
|
||||
import java.io.File;
|
||||
@@ -2521,7 +2523,11 @@ class ContextImpl extends Context {
|
||||
|
||||
private File makeFilename(File base, String name) {
|
||||
if (name.indexOf(File.separatorChar) < 0) {
|
||||
return new File(base, name);
|
||||
final File res = new File(base, name);
|
||||
// We report as filesystem access here to give us the best shot at
|
||||
// detecting apps that will pass the path down to native code.
|
||||
BlockGuard.getVmPolicy().onPathAccess(res.getPath());
|
||||
return res;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"File " + name + " contains a path separator");
|
||||
|
||||
@@ -33,6 +33,8 @@ import java.util.LinkedList;
|
||||
public class Environment {
|
||||
private static final String TAG = "Environment";
|
||||
|
||||
// NOTE: keep credential-protected paths in sync with StrictMode.java
|
||||
|
||||
private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
|
||||
private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
|
||||
private static final String ENV_ANDROID_DATA = "ANDROID_DATA";
|
||||
|
||||
@@ -31,8 +31,10 @@ import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.TrafficStats;
|
||||
import android.net.Uri;
|
||||
import android.os.storage.IStorageManager;
|
||||
import android.os.strictmode.CleartextNetworkViolation;
|
||||
import android.os.strictmode.ContentUriWithoutPermissionViolation;
|
||||
import android.os.strictmode.CredentialProtectedWhileLockedViolation;
|
||||
import android.os.strictmode.CustomViolation;
|
||||
import android.os.strictmode.DiskReadViolation;
|
||||
import android.os.strictmode.DiskWriteViolation;
|
||||
@@ -284,6 +286,8 @@ public final class StrictMode {
|
||||
private static final int DETECT_VM_NON_SDK_API_USAGE = 1 << 9;
|
||||
/** @hide */
|
||||
private static final int DETECT_VM_IMPLICIT_DIRECT_BOOT = 1 << 10;
|
||||
/** @hide */
|
||||
private static final int DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED = 1 << 11;
|
||||
|
||||
/** @hide */
|
||||
private static final int DETECT_VM_ALL = 0x0000ffff;
|
||||
@@ -860,6 +864,9 @@ public final class StrictMode {
|
||||
detectContentUriWithoutPermission();
|
||||
detectUntaggedSockets();
|
||||
}
|
||||
if (targetSdk >= Build.VERSION_CODES.Q) {
|
||||
detectCredentialProtectedWhileLocked();
|
||||
}
|
||||
|
||||
// TODO: Decide whether to detect non SDK API usage beyond a certain API level.
|
||||
// TODO: enable detectImplicitDirectBoot() once system is less noisy
|
||||
@@ -994,6 +1001,28 @@ public final class StrictMode {
|
||||
return disable(DETECT_VM_IMPLICIT_DIRECT_BOOT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect access to filesystem paths stored in credential protected
|
||||
* storage areas while the user is locked.
|
||||
* <p>
|
||||
* When a user is locked, credential protected storage is
|
||||
* unavailable, and files stored in these locations appear to not
|
||||
* exist, which can result in subtle app bugs if they assume default
|
||||
* behaviors or empty states. Instead, apps should store data needed
|
||||
* while a user is locked under device protected storage areas.
|
||||
*
|
||||
* @see Context#createCredentialProtectedStorageContext()
|
||||
* @see Context#createDeviceProtectedStorageContext()
|
||||
*/
|
||||
public Builder detectCredentialProtectedWhileLocked() {
|
||||
return enable(DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public Builder permitCredentialProtectedWhileLocked() {
|
||||
return disable(DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crashes the whole process on violation. This penalty runs at the end of all enabled
|
||||
* penalties so you'll still get your logging or other violations before the process
|
||||
@@ -1155,6 +1184,16 @@ public final class StrictMode {
|
||||
androidPolicy.setThreadPolicyMask(threadPolicyMask);
|
||||
}
|
||||
|
||||
private static void setBlockGuardVmPolicy(@VmPolicyMask int vmPolicyMask) {
|
||||
// We only need to install BlockGuard for a small subset of VM policies
|
||||
vmPolicyMask &= DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED;
|
||||
if (vmPolicyMask != 0) {
|
||||
BlockGuard.setVmPolicy(VM_ANDROID_POLICY);
|
||||
} else {
|
||||
BlockGuard.setVmPolicy(BlockGuard.LAX_VM_POLICY);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets up CloseGuard in Dalvik/libcore
|
||||
private static void setCloseGuardEnabled(boolean enabled) {
|
||||
if (!(CloseGuard.getReporter() instanceof AndroidCloseGuardReporter)) {
|
||||
@@ -1742,6 +1781,34 @@ public final class StrictMode {
|
||||
}
|
||||
}
|
||||
|
||||
private static final BlockGuard.VmPolicy VM_ANDROID_POLICY = new BlockGuard.VmPolicy() {
|
||||
@Override
|
||||
public void onPathAccess(String path) {
|
||||
if (path == null) return;
|
||||
|
||||
// NOTE: keep credential-protected paths in sync with Environment.java
|
||||
if (path.startsWith("/data/user/")
|
||||
|| path.startsWith("/data/media/")
|
||||
|| path.startsWith("/data/system_ce/")
|
||||
|| path.startsWith("/data/misc_ce/")
|
||||
|| path.startsWith("/data/vendor_ce/")
|
||||
|| path.startsWith("/storage/emulated/")) {
|
||||
final int second = path.indexOf('/', 1);
|
||||
final int third = path.indexOf('/', second + 1);
|
||||
final int fourth = path.indexOf('/', third + 1);
|
||||
if (fourth == -1) return;
|
||||
|
||||
try {
|
||||
final int userId = Integer.parseInt(path.substring(third + 1, fourth));
|
||||
onCredentialProtectedPathAccess(path, userId);
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
} else if (path.startsWith("/data/data/")) {
|
||||
onCredentialProtectedPathAccess(path, UserHandle.USER_SYSTEM);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any
|
||||
* violations but not showing a dialog, not loggging, and not killing the process. In these
|
||||
@@ -1909,6 +1976,8 @@ public final class StrictMode {
|
||||
VMRuntime.setNonSdkApiUsageConsumer(null);
|
||||
VMRuntime.setDedupeHiddenApiWarnings(true);
|
||||
}
|
||||
|
||||
setBlockGuardVmPolicy(sVmPolicy.mask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1971,6 +2040,11 @@ public final class StrictMode {
|
||||
return (sVmPolicy.mask & DETECT_VM_IMPLICIT_DIRECT_BOOT) != 0;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static boolean vmCredentialProtectedWhileLockedEnabled() {
|
||||
return (sVmPolicy.mask & DETECT_VM_CREDENTIAL_PROTECTED_WHILE_LOCKED) != 0;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static void onSqliteObjectLeaked(String message, Throwable originStack) {
|
||||
onVmPolicyViolation(new SqliteObjectLeakedViolation(message, originStack));
|
||||
@@ -2044,6 +2118,42 @@ public final class StrictMode {
|
||||
onVmPolicyViolation(new ImplicitDirectBootViolation());
|
||||
}
|
||||
|
||||
/** Assume locked until we hear otherwise */
|
||||
private static volatile boolean sUserKeyUnlocked = false;
|
||||
|
||||
private static boolean isUserKeyUnlocked(int userId) {
|
||||
final IStorageManager storage = IStorageManager.Stub
|
||||
.asInterface(ServiceManager.getService("mount"));
|
||||
if (storage != null) {
|
||||
try {
|
||||
return storage.isUserKeyUnlocked(userId);
|
||||
} catch (RemoteException ignored) {
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
private static void onCredentialProtectedPathAccess(String path, int userId) {
|
||||
// We can cache the unlocked state for the userId we're running as,
|
||||
// since any relocking of that user will always result in our
|
||||
// process being killed to release any CE FDs we're holding onto.
|
||||
if (userId == UserHandle.myUserId()) {
|
||||
if (sUserKeyUnlocked) {
|
||||
return;
|
||||
} else if (isUserKeyUnlocked(userId)) {
|
||||
sUserKeyUnlocked = true;
|
||||
return;
|
||||
}
|
||||
} else if (isUserKeyUnlocked(userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
onVmPolicyViolation(new CredentialProtectedWhileLockedViolation(
|
||||
"Accessed credential protected path " + path + " while user " + userId
|
||||
+ " was locked"));
|
||||
}
|
||||
|
||||
// Map from VM violation fingerprint to uptime millis.
|
||||
private static final HashMap<Integer, Long> sLastVmViolationTime = new HashMap<>();
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ import com.android.internal.os.RoSystemProperties;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import dalvik.system.BlockGuard;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -1131,6 +1133,7 @@ public class StorageManager {
|
||||
|
||||
/** {@hide} */
|
||||
public void mkdirs(File file) {
|
||||
BlockGuard.getVmPolicy().onPathAccess(file.getAbsolutePath());
|
||||
try {
|
||||
mStorageManager.mkdirs(mContext.getOpPackageName(), file.getAbsolutePath());
|
||||
} catch (RemoteException e) {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.strictmode;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* Subclass of {@code Violation} that is used when a process accesses filesystem
|
||||
* paths stored in credential protected storage areas while the user is locked.
|
||||
* <p>
|
||||
* When a user is locked, credential protected storage is unavailable, and files
|
||||
* stored in these locations appear to not exist, which can result in subtle app
|
||||
* bugs if they assume default behaviors or empty states. Instead, apps should
|
||||
* store data needed while a user is locked under device protected storage
|
||||
* areas.
|
||||
*
|
||||
* @see Context#createCredentialProtectedStorageContext()
|
||||
* @see Context#createDeviceProtectedStorageContext()
|
||||
*/
|
||||
public final class CredentialProtectedWhileLockedViolation extends Violation {
|
||||
/** @hide */
|
||||
public CredentialProtectedWhileLockedViolation(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import android.util.Slog;
|
||||
import com.android.internal.os.BackgroundThread;
|
||||
import com.android.server.SystemService;
|
||||
|
||||
import dalvik.system.BlockGuard;
|
||||
import dalvik.system.VMRuntime;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
@@ -239,6 +240,11 @@ public class Installer extends SystemService {
|
||||
long[] ceDataInodes, String[] codePaths, PackageStats stats)
|
||||
throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
if (codePaths != null) {
|
||||
for (String codePath : codePaths) {
|
||||
BlockGuard.getVmPolicy().onPathAccess(codePath);
|
||||
}
|
||||
}
|
||||
try {
|
||||
final long[] res = mInstalld.getAppSize(uuid, packageNames, userId, flags,
|
||||
appId, ceDataInodes, codePaths);
|
||||
@@ -296,6 +302,9 @@ public class Installer extends SystemService {
|
||||
@Nullable String profileName, @Nullable String dexMetadataPath,
|
||||
@Nullable String compilationReason) throws InstallerException {
|
||||
assertValidInstructionSet(instructionSet);
|
||||
BlockGuard.getVmPolicy().onPathAccess(apkPath);
|
||||
BlockGuard.getVmPolicy().onPathAccess(outputPath);
|
||||
BlockGuard.getVmPolicy().onPathAccess(dexMetadataPath);
|
||||
if (!checkBeforeRemote()) return;
|
||||
try {
|
||||
mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
|
||||
@@ -319,6 +328,7 @@ public class Installer extends SystemService {
|
||||
public boolean dumpProfiles(int uid, String packageName, String profileName, String codePath)
|
||||
throws InstallerException {
|
||||
if (!checkBeforeRemote()) return false;
|
||||
BlockGuard.getVmPolicy().onPathAccess(codePath);
|
||||
try {
|
||||
return mInstalld.dumpProfiles(uid, packageName, profileName, codePath);
|
||||
} catch (Exception e) {
|
||||
@@ -339,6 +349,8 @@ public class Installer extends SystemService {
|
||||
public void idmap(String targetApkPath, String overlayApkPath, int uid)
|
||||
throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(targetApkPath);
|
||||
BlockGuard.getVmPolicy().onPathAccess(overlayApkPath);
|
||||
try {
|
||||
mInstalld.idmap(targetApkPath, overlayApkPath, uid);
|
||||
} catch (Exception e) {
|
||||
@@ -348,6 +360,7 @@ public class Installer extends SystemService {
|
||||
|
||||
public void removeIdmap(String overlayApkPath) throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(overlayApkPath);
|
||||
try {
|
||||
mInstalld.removeIdmap(overlayApkPath);
|
||||
} catch (Exception e) {
|
||||
@@ -358,6 +371,7 @@ public class Installer extends SystemService {
|
||||
public void rmdex(String codePath, String instructionSet) throws InstallerException {
|
||||
assertValidInstructionSet(instructionSet);
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(codePath);
|
||||
try {
|
||||
mInstalld.rmdex(codePath, instructionSet);
|
||||
} catch (Exception e) {
|
||||
@@ -367,6 +381,7 @@ public class Installer extends SystemService {
|
||||
|
||||
public void rmPackageDir(String packageDir) throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(packageDir);
|
||||
try {
|
||||
mInstalld.rmPackageDir(packageDir);
|
||||
} catch (Exception e) {
|
||||
@@ -439,6 +454,7 @@ public class Installer extends SystemService {
|
||||
public void linkNativeLibraryDirectory(String uuid, String packageName, String nativeLibPath32,
|
||||
int userId) throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(nativeLibPath32);
|
||||
try {
|
||||
mInstalld.linkNativeLibraryDirectory(uuid, packageName, nativeLibPath32, userId);
|
||||
} catch (Exception e) {
|
||||
@@ -459,6 +475,8 @@ public class Installer extends SystemService {
|
||||
public void linkFile(String relativePath, String fromBase, String toBase)
|
||||
throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(fromBase);
|
||||
BlockGuard.getVmPolicy().onPathAccess(toBase);
|
||||
try {
|
||||
mInstalld.linkFile(relativePath, fromBase, toBase);
|
||||
} catch (Exception e) {
|
||||
@@ -469,6 +487,8 @@ public class Installer extends SystemService {
|
||||
public void moveAb(String apkPath, String instructionSet, String outputPath)
|
||||
throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(apkPath);
|
||||
BlockGuard.getVmPolicy().onPathAccess(outputPath);
|
||||
try {
|
||||
mInstalld.moveAb(apkPath, instructionSet, outputPath);
|
||||
} catch (Exception e) {
|
||||
@@ -479,6 +499,8 @@ public class Installer extends SystemService {
|
||||
public void deleteOdex(String apkPath, String instructionSet, String outputPath)
|
||||
throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(apkPath);
|
||||
BlockGuard.getVmPolicy().onPathAccess(outputPath);
|
||||
try {
|
||||
mInstalld.deleteOdex(apkPath, instructionSet, outputPath);
|
||||
} catch (Exception e) {
|
||||
@@ -489,6 +511,7 @@ public class Installer extends SystemService {
|
||||
public void installApkVerity(String filePath, FileDescriptor verityInput, int contentSize)
|
||||
throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(filePath);
|
||||
try {
|
||||
mInstalld.installApkVerity(filePath, verityInput, contentSize);
|
||||
} catch (Exception e) {
|
||||
@@ -499,6 +522,7 @@ public class Installer extends SystemService {
|
||||
public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash)
|
||||
throws InstallerException {
|
||||
if (!checkBeforeRemote()) return;
|
||||
BlockGuard.getVmPolicy().onPathAccess(filePath);
|
||||
try {
|
||||
mInstalld.assertFsverityRootHashMatches(filePath, expectedHash);
|
||||
} catch (Exception e) {
|
||||
@@ -512,6 +536,7 @@ public class Installer extends SystemService {
|
||||
assertValidInstructionSet(isas[i]);
|
||||
}
|
||||
if (!checkBeforeRemote()) return false;
|
||||
BlockGuard.getVmPolicy().onPathAccess(apkPath);
|
||||
try {
|
||||
return mInstalld.reconcileSecondaryDexFile(apkPath, packageName, uid, isas,
|
||||
volumeUuid, flags);
|
||||
@@ -523,6 +548,7 @@ public class Installer extends SystemService {
|
||||
public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
|
||||
@Nullable String volumeUuid, int flags) throws InstallerException {
|
||||
if (!checkBeforeRemote()) return new byte[0];
|
||||
BlockGuard.getVmPolicy().onPathAccess(dexPath);
|
||||
try {
|
||||
return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags);
|
||||
} catch (Exception e) {
|
||||
@@ -571,6 +597,8 @@ public class Installer extends SystemService {
|
||||
public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
|
||||
String profileName, String codePath, String dexMetadataPath) throws InstallerException {
|
||||
if (!checkBeforeRemote()) return false;
|
||||
BlockGuard.getVmPolicy().onPathAccess(codePath);
|
||||
BlockGuard.getVmPolicy().onPathAccess(dexMetadataPath);
|
||||
try {
|
||||
return mInstalld.prepareAppProfile(pkg, userId, appId, profileName, codePath,
|
||||
dexMetadataPath);
|
||||
|
||||
Reference in New Issue
Block a user