More file-based encryption work.
Add granular StorageManager APIs for key creation/destruction and
unlocking/locking. Start passing through an opaque token as part
of the unlock command, but leave it empty for now. We now have a
separate "prepare" method that sanity checks that user directories
are correctly setup.
Define a handful of system properties used for marking devices that
should be operating in FBE mode, and if they're emulating FBE. Wire
a command to "sm", but persisting will come later.
Start using new "encryptionAware" flag on apps previously marked with
coreApp flag, which were apps running in the legacy CryptKeeper
model. Small tweaks to handle non-encryptionAware voice interaction
services. Switch PackageManager to consult StorageManager about the
unlocked state of a user.
Bug: 22358539
Change-Id: Ic2865f9b81c10ea39369c441422f7427a3c3c3d6
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 6e4f238..37dd884 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -96,6 +96,7 @@
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.NativeDaemonConnector.Command;
@@ -119,6 +120,7 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -284,6 +286,8 @@
@GuardedBy("mLock")
private int[] mStartedUsers = EmptyArray.INT;
+ @GuardedBy("mLock")
+ private int[] mUnlockedUsers = EmptyArray.INT;
/** Map from disk ID to disk */
@GuardedBy("mLock")
@@ -402,6 +406,17 @@
}
}
+ private static String escapeNull(String arg) {
+ if (TextUtils.isEmpty(arg)) {
+ return "!";
+ } else {
+ if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) {
+ throw new IllegalArgumentException(arg);
+ }
+ return arg;
+ }
+ }
+
/** List of crypto types.
* These must match CRYPT_TYPE_XXX in cryptfs.h AND their
* corresponding commands in CommandListener.cpp */
@@ -1892,6 +1907,9 @@
if ((mask & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0) {
mForceAdoptable = (flags & StorageManager.DEBUG_FORCE_ADOPTABLE) != 0;
}
+ if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
+ // TODO: persist through vold and reboot
+ }
writeSettingsLocked();
mHandler.obtainMessage(H_RESET).sendToTarget();
@@ -2654,65 +2672,99 @@
}
@Override
- public void createNewUserDir(int userHandle, String path) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only SYSTEM_UID can create user directories");
- }
-
+ public void createUserKey(int userId, int serialNumber) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
waitForReady();
- if (DEBUG_EVENTS) {
- Slog.i(TAG, "Creating new user dir");
- }
-
try {
- NativeDaemonEvent event = mCryptConnector.execute(
- "cryptfs", "createnewuserdir", userHandle, path);
- if (!"0".equals(event.getMessage())) {
- String error = "createnewuserdir sent unexpected message: "
- + event.getMessage();
- Slog.e(TAG, error);
- // ext4enc:TODO is this the right exception?
- throw new RuntimeException(error);
- }
+ mCryptConnector.execute("cryptfs", "create_user_key", userId, serialNumber);
} catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "createnewuserdir threw exception", e);
- throw new RuntimeException("createnewuserdir threw exception", e);
+ throw e.rethrowAsParcelableException();
}
}
- // ext4enc:TODO duplication between this and createNewUserDir is nasty
@Override
- public void deleteUserKey(int userHandle) {
- if (Binder.getCallingUid() != Process.SYSTEM_UID) {
- throw new SecurityException("Only SYSTEM_UID can delete user keys");
- }
-
+ public void destroyUserKey(int userId) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
waitForReady();
- if (DEBUG_EVENTS) {
- Slog.i(TAG, "Deleting user key");
+ try {
+ mCryptConnector.execute("cryptfs", "destroy_user_key", userId);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ }
+
+ @Override
+ public void unlockUserKey(int userId, int serialNumber, byte[] token) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+ waitForReady();
+
+ final String encodedToken;
+ if (ArrayUtils.isEmpty(token)) {
+ encodedToken = "!";
+ } else {
+ encodedToken = HexDump.toHexString(token);
}
try {
- NativeDaemonEvent event = mCryptConnector.execute(
- "cryptfs", "deleteuserkey", userHandle);
- if (!"0".equals(event.getMessage())) {
- String error = "deleteuserkey sent unexpected message: "
- + event.getMessage();
- Slog.e(TAG, error);
- // ext4enc:TODO is this the right exception?
- throw new RuntimeException(error);
- }
+ mCryptConnector.execute("cryptfs", "unlock_user_key", userId, serialNumber,
+ new SensitiveArg(encodedToken));
} catch (NativeDaemonConnectorException e) {
- Slog.e(TAG, "deleteuserkey threw exception", e);
- throw new RuntimeException("deleteuserkey threw exception", e);
+ throw e.rethrowAsParcelableException();
+ }
+
+ synchronized (mLock) {
+ mUnlockedUsers = ArrayUtils.appendInt(mUnlockedUsers, userId);
+ }
+ }
+
+ @Override
+ public void lockUserKey(int userId) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+ waitForReady();
+
+ try {
+ mCryptConnector.execute("cryptfs", "lock_user_key", userId);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+
+ synchronized (mLock) {
+ mUnlockedUsers = ArrayUtils.removeInt(mUnlockedUsers, userId);
+ }
+ }
+
+ @Override
+ public boolean isUserKeyUnlocked(int userId) {
+ if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+ synchronized (mLock) {
+ return ArrayUtils.contains(mUnlockedUsers, userId);
+ }
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public void prepareUserStorage(String volumeUuid, int userId, int serialNumber) {
+ enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
+ waitForReady();
+
+ try {
+ mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
+ userId, serialNumber);
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
}
}
@Override
public boolean isPerUserEncryptionEnabled() {
- return "file".equals(SystemProperties.get("ro.crypto.type", "none"));
+ // TODO: switch this over to a single property; currently using two to
+ // handle the emulated case
+ return "file".equals(SystemProperties.get("ro.crypto.type", "none"))
+ || SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false);
}
@Override
@@ -3449,6 +3501,9 @@
pw.println();
pw.println("Primary storage UUID: " + mPrimaryStorageUuid);
pw.println("Force adoptable: " + mForceAdoptable);
+ pw.println();
+ pw.println("Started users: " + Arrays.toString(mStartedUsers));
+ pw.println("Unlocked users: " + Arrays.toString(mUnlockedUsers));
}
synchronized (mObbMounts) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index cbc13fe..d6fced6 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
import static android.os.Process.SYSTEM_UID;
@@ -57,8 +56,11 @@
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -134,7 +136,9 @@
mService = service;
mHandler = mService.mHandler;
// User 0 is the first and only user that runs at boot.
- mStartedUsers.put(UserHandle.USER_SYSTEM, new UserState(UserHandle.SYSTEM, true));
+ final UserState uss = new UserState(UserHandle.SYSTEM);
+ mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
+ updateUserUnlockedState(uss);
mUserLru.add(UserHandle.USER_SYSTEM);
updateStartedUserArrayLocked();
}
@@ -409,6 +413,21 @@
return userManager;
}
+ private void updateUserUnlockedState(UserState uss) {
+ final IMountService mountService = IMountService.Stub
+ .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE));
+ if (mountService != null) {
+ try {
+ uss.unlocked = mountService.isUserKeyUnlocked(uss.mHandle.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ } else {
+ // System isn't fully booted yet, so guess based on property
+ uss.unlocked = !SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false);
+ }
+ }
+
boolean startUser(final int userId, final boolean foreground) {
if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
@@ -453,11 +472,14 @@
// If the user we are switching to is not currently started, then
// we need to start it now.
if (mStartedUsers.get(userId) == null) {
- mStartedUsers.put(userId, new UserState(new UserHandle(userId), false));
+ mStartedUsers.put(userId, new UserState(new UserHandle(userId)));
updateStartedUserArrayLocked();
needStart = true;
}
+ final UserState uss = mStartedUsers.get(userId);
+ updateUserUnlockedState(uss);
+
final Integer userIdInt = userId;
mUserLru.remove(userIdInt);
mUserLru.add(userIdInt);
@@ -479,8 +501,6 @@
mUserLru.add(currentUserIdInt);
}
- final UserState uss = mStartedUsers.get(userId);
-
// Make sure user is in the started state. If it is currently
// stopping, we need to knock that off.
if (uss.mState == UserState.STATE_STOPPING) {
@@ -956,8 +976,11 @@
return true;
}
if ((flags & ActivityManager.FLAG_WITH_AMNESIA) != 0) {
- // TODO: add in amnesia lifecycle
- return false;
+ // If user is currently locked, we fall through to default "running"
+ // behavior below
+ if (state.unlocked) {
+ return false;
+ }
}
return state.mState != UserState.STATE_STOPPING
&& state.mState != UserState.STATE_SHUTDOWN;
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index b3d82bc..b5b5c1d 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -40,6 +40,7 @@
public int mState = STATE_BOOTING;
public boolean switching;
public boolean initializing;
+ public boolean unlocked;
/**
* The last time that a provider was reported to usage stats as being brought to important
@@ -47,7 +48,7 @@
*/
public final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
- public UserState(UserHandle handle, boolean initial) {
+ public UserState(UserHandle handle) {
mHandle = handle;
}
@@ -62,6 +63,11 @@
}
if (switching) pw.print(" SWITCHING");
if (initializing) pw.print(" INITIALIZING");
+ if (unlocked) {
+ pw.print(" UNLOCKED");
+ } else {
+ pw.print(" LOCKED");
+ }
pw.println();
}
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 150c849..99a051a 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -185,15 +185,6 @@
return mInstaller.execute(builder.toString());
}
- public int rename(String oldname, String newname) {
- StringBuilder builder = new StringBuilder("rename");
- builder.append(' ');
- builder.append(oldname);
- builder.append(' ');
- builder.append(newname);
- return mInstaller.execute(builder.toString());
- }
-
@Deprecated
public int fixUid(String name, int uid, int gid) {
return fixUid(null, name, uid, gid);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ad1cbe6..64628aa 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2981,23 +2981,25 @@
* purposefully done before acquiring {@link #mPackages} lock.
*/
private int augmentFlagsForUser(int flags, int userId) {
- // TODO: bring back once locking fixed
-// final IActivityManager am = ActivityManagerNative.getDefault();
-// if (am == null) {
-// // We must be early in boot, so the best we can do is assume the
-// // user is fully running.
-// return flags;
-// }
-// final long token = Binder.clearCallingIdentity();
-// try {
-// if (am.isUserRunning(userId, ActivityManager.FLAG_WITH_AMNESIA)) {
-// flags |= PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA;
-// }
-// } catch (RemoteException e) {
-// throw e.rethrowAsRuntimeException();
-// } finally {
-// Binder.restoreCallingIdentity(token);
-// }
+ if (SystemProperties.getBoolean(StorageManager.PROP_HAS_FBE, false)) {
+ final IMountService mount = IMountService.Stub
+ .asInterface(ServiceManager.getService(Context.STORAGE_SERVICE));
+ if (mount == null) {
+ // We must be early in boot, so the best we can do is assume the
+ // user is fully running.
+ return flags;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!mount.isUserKeyUnlocked(userId)) {
+ flags |= PackageManager.FLAG_USER_RUNNING_WITH_AMNESIA;
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
return flags;
}
@@ -15918,13 +15920,14 @@
}
}
+ final StorageManager sm = mContext.getSystemService(StorageManager.class);
final UserManager um = mContext.getSystemService(UserManager.class);
for (UserInfo user : um.getUsers()) {
final File userDir = Environment.getDataUserDirectory(volumeUuid, user.id);
if (userDir.exists()) continue;
try {
- UserManagerService.prepareUserDirectory(mContext, volumeUuid, user.id);
+ sm.prepareUserStorage(volumeUuid, user.id, user.serialNumber);
UserManagerService.enforceSerialNumber(userDir, user.serialNumber);
} catch (IOException e) {
Log.wtf(TAG, "Failed to create user directory on " + volumeUuid, e);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 9a87abe..3a1d2de 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1563,13 +1563,15 @@
userInfo.restrictedProfileParentId = parent.restrictedProfileParentId;
}
}
+
final StorageManager storage = mContext.getSystemService(StorageManager.class);
+ storage.createUserKey(userId, userInfo.serialNumber);
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final String volumeUuid = vol.getFsUuid();
try {
final File userDir = Environment.getDataUserDirectory(volumeUuid,
userId);
- prepareUserDirectory(mContext, volumeUuid, userId);
+ storage.prepareUserStorage(volumeUuid, userId, userInfo.serialNumber);
enforceSerialNumber(userDir, userInfo.serialNumber);
} catch (IOException e) {
Log.wtf(LOG_TAG, "Failed to create user directory on " + volumeUuid, e);
@@ -1797,8 +1799,7 @@
}
private void removeUserStateLILP(final int userHandle) {
- mContext.getSystemService(StorageManager.class)
- .deleteUserKey(userHandle);
+ mContext.getSystemService(StorageManager.class).destroyUserKey(userHandle);
// Cleanup package manager settings
mPm.cleanUpUserLILPw(this, userHandle);
@@ -2183,16 +2184,6 @@
}
/**
- * Create new {@code /data/user/[id]} directory and sets default
- * permissions.
- */
- public static void prepareUserDirectory(Context context, String volumeUuid, int userId) {
- final StorageManager storage = context.getSystemService(StorageManager.class);
- final File userDir = Environment.getDataUserDirectory(volumeUuid, userId);
- storage.createNewUserDir(userId, userDir);
- }
-
- /**
* Enforce that serial number stored in user directory inode matches the
* given expected value. Gracefully sets the serial number if currently
* undefined.