| /* |
| * Copyright (C) 2017 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 com.android.server.pm; |
| |
| import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; |
| |
| import android.content.Context; |
| import android.content.pm.UserInfo; |
| import android.os.Environment; |
| import android.os.FileUtils; |
| import android.os.RecoverySystem; |
| import android.os.storage.StorageManager; |
| import android.os.storage.VolumeInfo; |
| import android.os.SystemProperties; |
| import android.os.UserHandle; |
| import android.system.ErrnoException; |
| import android.system.Os; |
| import android.system.OsConstants; |
| import android.util.Log; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** |
| * Helper class for preparing and destroying user storage |
| */ |
| class UserDataPreparer { |
| private static final String TAG = "UserDataPreparer"; |
| private static final String XATTR_SERIAL = "user.serial"; |
| |
| private final Object mInstallLock; |
| private final Context mContext; |
| private final boolean mOnlyCore; |
| private final Installer mInstaller; |
| |
| UserDataPreparer(Installer installer, Object installLock, Context context, boolean onlyCore) { |
| mInstallLock = installLock; |
| mContext = context; |
| mOnlyCore = onlyCore; |
| mInstaller = installer; |
| } |
| |
| /** |
| * 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 && !mOnlyCore) { |
| enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial); |
| if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) { |
| enforceSerialNumber(getDataSystemDeDirectory(userId), userSerial); |
| } |
| } |
| if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) { |
| enforceSerialNumber(getDataUserCeDirectory(volumeUuid, userId), userSerial); |
| if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) { |
| enforceSerialNumber(getDataSystemCeDirectory(userId), userSerial); |
| } |
| } |
| |
| mInstaller.createUserData(volumeUuid, userId, userSerial, flags); |
| |
| // CE storage is available after they are prepared. |
| if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && |
| (userId == UserHandle.USER_SYSTEM)) { |
| String propertyName = "sys.user." + userId + ".ce_available"; |
| Slog.d(TAG, "Setting property: " + propertyName + "=true"); |
| SystemProperties.set(propertyName, "true"); |
| } |
| } 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 | StorageManager.FLAG_STORAGE_DE, false); |
| } else { |
| try { |
| Log.wtf(TAG, "prepareUserData failed for user " + userId, e); |
| if (userId == UserHandle.USER_SYSTEM) { |
| RecoverySystem.rebootPromptAndWipeUserData(mContext, |
| "prepareUserData failed for system user"); |
| } |
| } catch (IOException e2) { |
| throw new RuntimeException("error rebooting into recovery", e2); |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| } |
| |
| 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(getUserSystemDirectory(userId)); |
| FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId)); |
| } |
| if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) { |
| FileUtils.deleteContentsAndDir(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. |
| */ |
| void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList) { |
| final List<File> files = new ArrayList<>(); |
| Collections.addAll(files, FileUtils |
| .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid))); |
| Collections.addAll(files, FileUtils |
| .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid))); |
| Collections.addAll(files, FileUtils |
| .listFilesOrEmpty(Environment.getDataSystemDeDirectory())); |
| Collections.addAll(files, FileUtils |
| .listFilesOrEmpty(Environment.getDataSystemCeDirectory())); |
| Collections.addAll(files, FileUtils |
| .listFilesOrEmpty(Environment.getDataMiscCeDirectory())); |
| reconcileUsers(volumeUuid, validUsersList, files); |
| } |
| |
| @VisibleForTesting |
| void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList, List<File> files) { |
| final int userCount = validUsersList.size(); |
| SparseArray<UserInfo> users = new SparseArray<>(userCount); |
| for (int i = 0; i < userCount; i++) { |
| UserInfo user = validUsersList.get(i); |
| users.put(user.id, user); |
| } |
| for (File file : files) { |
| if (!file.isDirectory()) { |
| continue; |
| } |
| |
| final int userId; |
| final UserInfo info; |
| try { |
| userId = Integer.parseInt(file.getName()); |
| info = users.get(userId); |
| } catch (NumberFormatException e) { |
| Slog.w(TAG, "Invalid user directory " + file); |
| continue; |
| } |
| |
| boolean destroyUser = false; |
| if (info == null) { |
| logCriticalInfo(Log.WARN, "Destroying user directory " + file |
| + " because no matching user was found"); |
| destroyUser = true; |
| } else if (!mOnlyCore) { |
| try { |
| enforceSerialNumber(file, info.serialNumber); |
| } catch (IOException e) { |
| logCriticalInfo(Log.WARN, "Destroying user directory " + file |
| + " because we failed to enforce serial number: " + e); |
| destroyUser = true; |
| } |
| } |
| |
| if (destroyUser) { |
| synchronized (mInstallLock) { |
| destroyUserDataLI(volumeUuid, userId, |
| StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); |
| } |
| } |
| } |
| } |
| |
| @VisibleForTesting |
| protected File getDataMiscCeDirectory(int userId) { |
| return Environment.getDataMiscCeDirectory(userId); |
| } |
| |
| @VisibleForTesting |
| protected File getDataSystemCeDirectory(int userId) { |
| return Environment.getDataSystemCeDirectory(userId); |
| } |
| |
| @VisibleForTesting |
| protected File getDataMiscDeDirectory(int userId) { |
| return Environment.getDataMiscDeDirectory(userId); |
| } |
| |
| @VisibleForTesting |
| protected File getUserSystemDirectory(int userId) { |
| return Environment.getUserSystemDirectory(userId); |
| } |
| |
| @VisibleForTesting |
| protected File getDataUserCeDirectory(String volumeUuid, int userId) { |
| return Environment.getDataUserCeDirectory(volumeUuid, userId); |
| } |
| |
| @VisibleForTesting |
| protected File getDataSystemDeDirectory(int userId) { |
| return Environment.getDataSystemDeDirectory(userId); |
| } |
| |
| @VisibleForTesting |
| protected File getDataUserDeDirectory(String volumeUuid, int userId) { |
| return Environment.getDataUserDeDirectory(volumeUuid, userId); |
| } |
| |
| @VisibleForTesting |
| protected boolean isFileEncryptedEmulatedOnly() { |
| return StorageManager.isFileEncryptedEmulatedOnly(); |
| } |
| |
| /** |
| * Enforce that serial number stored in user directory inode matches the |
| * given expected value. Gracefully sets the serial number if currently |
| * undefined. |
| * |
| * @throws IOException when problem extracting serial number, or serial |
| * number is mismatched. |
| */ |
| void enforceSerialNumber(File file, int serialNumber) throws IOException { |
| if (isFileEncryptedEmulatedOnly()) { |
| // When we're emulating FBE, the directory may have been chmod |
| // 000'ed, meaning we can't read the serial number to enforce it; |
| // instead of destroying the user, just log a warning. |
| Slog.w(TAG, "Device is emulating FBE; assuming current serial number is valid"); |
| return; |
| } |
| |
| final int foundSerial = getSerialNumber(file); |
| Slog.v(TAG, "Found " + file + " with serial number " + foundSerial); |
| |
| if (foundSerial == -1) { |
| Slog.d(TAG, "Serial number missing on " + file + "; assuming current is valid"); |
| try { |
| setSerialNumber(file, serialNumber); |
| } catch (IOException e) { |
| Slog.w(TAG, "Failed to set serial number on " + file, e); |
| } |
| |
| } else if (foundSerial != serialNumber) { |
| throw new IOException("Found serial number " + foundSerial |
| + " doesn't match expected " + serialNumber); |
| } |
| } |
| |
| /** |
| * Set serial number stored in user directory inode. |
| * |
| * @throws IOException if serial number was already set |
| */ |
| private static void setSerialNumber(File file, int serialNumber) throws IOException { |
| try { |
| final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8); |
| Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE); |
| } catch (ErrnoException e) { |
| throw e.rethrowAsIOException(); |
| } |
| } |
| |
| /** |
| * Return serial number stored in user directory inode. |
| * |
| * @return parsed serial number, or -1 if not set |
| */ |
| @VisibleForTesting |
| static int getSerialNumber(File file) throws IOException { |
| try { |
| final byte[] buf = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL); |
| final String serial = new String(buf); |
| try { |
| return Integer.parseInt(serial); |
| } catch (NumberFormatException e) { |
| throw new IOException("Bad serial number: " + serial); |
| } |
| } catch (ErrnoException e) { |
| if (e.errno == OsConstants.ENODATA) { |
| return -1; |
| } else { |
| throw e.rethrowAsIOException(); |
| } |
| } |
| } |
| |
| } |