From fcf1e55821b694df3b8434f40aa3b6d3c3e7ea50 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Thu, 14 Apr 2016 20:44:58 -0600 Subject: [PATCH] Consistent creation/destruction of user data. Preparing and destroying users currently needs to be split across installd, system_server, and vold, since no single party has all the required SELinux permissions. When preparing user directories on a storage device, always enforce the serial number and destroy data if we run into a mismatch. When deleting a user, write the updated user list first before we start destroying data. Also start reconciling users on internal storage at boot, so we can recover from stale data left behind from partially destroyed users. Check both CE and DE user directories when reconciling user storage on a newly mounted storage device. Bug: 27896918 Change-Id: I4536c82b0196e2720628c4f73fccb742c233350b --- core/java/android/os/FileUtils.java | 8 ++ .../android/os/storage/IMountService.java | 29 ++++ .../android/os/storage/StorageManager.java | 9 ++ .../java/com/android/server/MountService.java | 13 ++ .../java/com/android/server/pm/Installer.java | 9 +- .../server/pm/PackageManagerService.java | 127 +++++++++++++----- .../android/server/pm/UserManagerService.java | 42 ++---- 7 files changed, 170 insertions(+), 67 deletions(-) diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index dd73e53f65e6b..7d74a1234c26d 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -437,6 +437,14 @@ public class FileUtils { return filePath.startsWith(dirPath); } + public static boolean deleteContentsAndDir(File dir) { + if (deleteContents(dir)) { + return dir.delete(); + } else { + return false; + } + } + public static boolean deleteContents(File dir) { File[] files = dir.listFiles(); boolean success = true; diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index c21c65a706a36..3915b02574931 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -1324,6 +1324,24 @@ public interface IMountService extends IInterface { } } + @Override + public void destroyUserStorage(String volumeUuid, int userId, int flags) + throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volumeUuid); + _data.writeInt(userId); + _data.writeInt(flags); + mRemote.transact(Stub.TRANSACTION_destroyUserStorage, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException { Parcel _data = Parcel.obtain(); @@ -1465,6 +1483,7 @@ public interface IMountService extends IInterface { static final int TRANSACTION_isUserKeyUnlocked = IBinder.FIRST_CALL_TRANSACTION + 65; static final int TRANSACTION_prepareUserStorage = IBinder.FIRST_CALL_TRANSACTION + 66; + static final int TRANSACTION_destroyUserStorage = IBinder.FIRST_CALL_TRANSACTION + 67; static final int TRANSACTION_isConvertibleToFBE = IBinder.FIRST_CALL_TRANSACTION + 68; @@ -2096,6 +2115,15 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_destroyUserStorage: { + data.enforceInterface(DESCRIPTOR); + String volumeUuid = data.readString(); + int userId = data.readInt(); + int _flags = data.readInt(); + destroyUserStorage(volumeUuid, userId, _flags); + reply.writeNoException(); + return true; + } case TRANSACTION_mountAppFuse: { data.enforceInterface(DESCRIPTOR); String name = data.readString(); @@ -2434,6 +2462,7 @@ public interface IMountService extends IInterface { public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) throws RemoteException; + public void destroyUserStorage(String volumeUuid, int userId, int flags) throws RemoteException; public ParcelFileDescriptor mountAppFuse(String name) throws RemoteException; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 0a8fdd9fac3b0..f68e227a32a3e 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1052,6 +1052,15 @@ public class StorageManager { } } + /** {@hide} */ + public void destroyUserStorage(String volumeUuid, int userId, int flags) { + try { + mMountService.destroyUserStorage(volumeUuid, userId, flags); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** {@hide} */ public boolean isUserKeyUnlocked(int userId) { try { diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 229a3f4dd8355..ec05dae68a85f 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -2874,6 +2874,19 @@ class MountService extends IMountService.Stub } } + @Override + public void destroyUserStorage(String volumeUuid, int userId, int flags) { + enforcePermission(android.Manifest.permission.STORAGE_INTERNAL); + waitForReady(); + + try { + mCryptConnector.execute("cryptfs", "destroy_user_storage", escapeNull(volumeUuid), + userId, flags); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + @Override public ParcelFileDescriptor mountAppFuse(final String name) throws RemoteException { try { diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index a11ee74b97f8c..66c1a53f587a0 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -174,12 +174,13 @@ public final class Installer extends SystemService { mInstaller.execute("destroy_app_profiles", pkgName); } - public void createUserConfig(int userid) throws InstallerException { - mInstaller.execute("mkuserconfig", userid); + public void createUserData(String uuid, int userId, int userSerial, int flags) + throws InstallerException { + mInstaller.execute("create_user_data", uuid, userId, userSerial, flags); } - public void removeUserDataDirs(String uuid, int userid) throws InstallerException { - mInstaller.execute("rmuser", uuid, userid); + public void destroyUserData(String uuid, int userId, int flags) throws InstallerException { + mInstaller.execute("destroy_user_data", uuid, userId, flags); } public void markBootComplete(String instructionSet) throws InstallerException { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 1fb260d3864aa..d02e452372c6e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -109,7 +109,6 @@ import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; import android.app.admin.SecurityLog; import android.app.backup.IBackupManager; -import android.app.usage.UsageStatsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -17382,6 +17381,10 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); return true; } }); + + // Now that we're mostly running, clean up stale users and apps + reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL); + reconcileApps(StorageManager.UUID_PRIVATE_INTERNAL); } @Override @@ -18613,15 +18616,101 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); sendResourcesChangedBroadcast(false, false, unloaded, null); } + /** + * Prepare storage areas for given user on all mounted devices. + */ + void prepareUserData(int userId, int userSerial, int flags) { + synchronized (mInstallLock) { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { + final String volumeUuid = vol.getFsUuid(); + prepareUserDataLI(volumeUuid, userId, userSerial, flags, true); + } + } + } + + private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags, + boolean allowRecover) { + // Prepare storage and verify that serial numbers are consistent; if + // there's a mismatch we need to destroy to avoid leaking data + final StorageManager storage = mContext.getSystemService(StorageManager.class); + try { + storage.prepareUserStorage(volumeUuid, userId, userSerial, flags); + + if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) { + UserManagerService.enforceSerialNumber( + Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial); + } + if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) { + UserManagerService.enforceSerialNumber( + Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial); + } + + synchronized (mInstallLock) { + mInstaller.createUserData(volumeUuid, userId, userSerial, flags); + } + } catch (Exception e) { + logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid + + " because we failed to prepare: " + e); + destroyUserDataLI(volumeUuid, userId, flags); + + if (allowRecover) { + // Try one last time; if we fail again we're really in trouble + prepareUserDataLI(volumeUuid, userId, userSerial, flags, false); + } + } + } + + /** + * Destroy storage areas for given user on all mounted devices. + */ + void destroyUserData(int userId, int flags) { + synchronized (mInstallLock) { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { + final String volumeUuid = vol.getFsUuid(); + destroyUserDataLI(volumeUuid, userId, flags); + } + } + } + + private void destroyUserDataLI(String volumeUuid, int userId, int flags) { + final StorageManager storage = mContext.getSystemService(StorageManager.class); + try { + // Clean up app data, profile data, and media data + mInstaller.destroyUserData(volumeUuid, userId, flags); + + // Clean up system data + if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) { + if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) { + FileUtils.deleteContentsAndDir(Environment.getUserSystemDirectory(userId)); + FileUtils.deleteContentsAndDir(Environment.getDataSystemDeDirectory(userId)); + } + if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) { + FileUtils.deleteContentsAndDir(Environment.getDataSystemCeDirectory(userId)); + } + } + + // Data with special labels is now gone, so finish the job + storage.destroyUserStorage(volumeUuid, userId, flags); + + } catch (Exception e) { + logCriticalInfo(Log.WARN, + "Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e); + } + } + /** * Examine all users present on given mounted volume, and destroy data * belonging to users that are no longer valid, or whose user ID has been * recycled. */ private void reconcileUsers(String volumeUuid) { - // TODO: also reconcile DE directories - final File[] files = FileUtils - .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)); + final List files = new ArrayList<>(); + Collections.addAll(files, FileUtils + .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid))); + Collections.addAll(files, FileUtils + .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid))); for (File file : files) { if (!file.isDirectory()) continue; @@ -18652,11 +18741,8 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); if (destroyUser) { synchronized (mInstallLock) { - try { - mInstaller.removeUserDataDirs(volumeUuid, userId); - } catch (InstallerException e) { - Slog.w(TAG, "Failed to clean up user dirs", e); - } + destroyUserDataLI(volumeUuid, userId, + StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); } } } @@ -19451,21 +19537,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); mSettings.removeUserLPw(userHandle); mPendingBroadcasts.remove(userHandle); mEphemeralApplicationRegistry.onUserRemovedLPw(userHandle); - } - synchronized (mInstallLock) { - final StorageManager storage = mContext.getSystemService(StorageManager.class); - for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { - final String volumeUuid = vol.getFsUuid(); - if (DEBUG_INSTALL) Slog.d(TAG, "Removing user data on volume " + volumeUuid); - try { - mInstaller.removeUserDataDirs(volumeUuid, userHandle); - } catch (InstallerException e) { - Slog.w(TAG, "Failed to remove user data", e); - } - } - synchronized (mPackages) { - removeUnusedPackagesLILPw(userManager, userHandle); - } + removeUnusedPackagesLPw(userManager, userHandle); } } @@ -19474,7 +19546,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); * that are no longer in use by any other user. * @param userHandle the user being removed */ - private void removeUnusedPackagesLILPw(UserManagerService userManager, final int userHandle) { + private void removeUnusedPackagesLPw(UserManagerService userManager, final int userHandle) { final boolean DEBUG_CLEAN_APKS = false; int [] users = userManager.getUserIds(); Iterator psit = mSettings.mPackages.values().iterator(); @@ -19524,11 +19596,6 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); /** Called by UserManagerService */ void createNewUser(int userHandle) { synchronized (mInstallLock) { - try { - mInstaller.createUserConfig(userHandle); - } catch (InstallerException e) { - Slog.w(TAG, "Failed to create user config", e); - } mSettings.createNewUserLI(this, mInstaller, userHandle); } synchronized (mPackages) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 60ea2544cadc4..f9ef0781bbdce 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -60,7 +60,6 @@ import android.os.UserManager; import android.os.UserManagerInternal; import android.os.UserManagerInternal.UserRestrictionsListener; import android.os.storage.StorageManager; -import android.os.storage.VolumeInfo; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -2024,7 +2023,7 @@ public class UserManagerService extends IUserManager.Stub { } final StorageManager storage = mContext.getSystemService(StorageManager.class); storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral()); - prepareUserStorage(userId, userInfo.serialNumber, + mPm.prepareUserData(userId, userInfo.serialNumber, StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); mPm.createNewUser(userId); userInfo.partial = false; @@ -2264,9 +2263,9 @@ public class UserManagerService extends IUserManager.Stub { Slog.i(LOG_TAG, "Destroying key for user " + userHandle + " failed, continuing anyway", e); } + // Cleanup package manager settings mPm.cleanUpUser(this, userHandle); - // Remove this user from the list synchronized (mUsersLock) { mUsers.remove(userHandle); @@ -2286,24 +2285,12 @@ public class UserManagerService extends IUserManager.Stub { AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX)); userFile.delete(); updateUserIds(); - File userDir = Environment.getUserSystemDirectory(userHandle); - File renamedUserDir = Environment.getUserSystemDirectory(UserHandle.USER_NULL - userHandle); - if (userDir.renameTo(renamedUserDir)) { - removeDirectoryRecursive(renamedUserDir); - } else { - removeDirectoryRecursive(userDir); - } - } - private void removeDirectoryRecursive(File parent) { - if (parent.isDirectory()) { - String[] files = parent.list(); - for (String filename : files) { - File child = new File(parent, filename); - removeDirectoryRecursive(child); - } - } - parent.delete(); + // Now that we've purged all the metadata above, destroy the actual data + // on disk; if we battery pull in here we'll finish cleaning up when + // reconciling after reboot. + mPm.destroyUserData(userHandle, + StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); } private void sendProfileRemovedBroadcast(int parentUserId, int removedUserId) { @@ -2597,24 +2584,13 @@ public class UserManagerService extends IUserManager.Stub { } } - /** - * Prepare storage areas for given user on all mounted devices. - */ - private void prepareUserStorage(int userId, int userSerial, int flags) { - final StorageManager storage = mContext.getSystemService(StorageManager.class); - for (VolumeInfo vol : storage.getWritablePrivateVolumes()) { - final String volumeUuid = vol.getFsUuid(); - storage.prepareUserStorage(volumeUuid, userId, userSerial, flags); - } - } - /** * Called right before a user is started. This gives us a chance to prepare * app storage and apply any user restrictions. */ public void onBeforeStartUser(int userId) { final int userSerial = getUserSerialNumber(userId); - prepareUserStorage(userId, userSerial, StorageManager.FLAG_STORAGE_DE); + mPm.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_DE); mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_DE); if (userId != UserHandle.USER_SYSTEM) { @@ -2630,7 +2606,7 @@ public class UserManagerService extends IUserManager.Stub { */ public void onBeforeUnlockUser(@UserIdInt int userId) { final int userSerial = getUserSerialNumber(userId); - prepareUserStorage(userId, userSerial, StorageManager.FLAG_STORAGE_CE); + mPm.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE); mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE); }