/*
|
* 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.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);
|
}
|
}
|
}
|
|
/**
|
* 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();
|
}
|
}
|
}
|
|
}
|