Merge "StrictMode to catch storage while locked."

This commit is contained in:
TreeHugger Robot
2018-06-30 22:04:26 +00:00
committed by Android (Google) Code Review
7 changed files with 193 additions and 1 deletions

View File

@@ -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 {
}

View File

@@ -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");

View File

@@ -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";

View File

@@ -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<>();

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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);