blob: 045a295da965f78c4dd054f7d9d6b5c04be20e35 [file] [log] [blame]
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -08001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.pm;
18
Todd Kennedy7c4c55d2017-11-02 10:01:39 -070019import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
20
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080021import android.content.Context;
Fyodor Kupolov50979d12017-01-27 17:36:32 -080022import android.content.pm.UserInfo;
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080023import android.os.Environment;
24import android.os.FileUtils;
25import android.os.storage.StorageManager;
26import android.os.storage.VolumeInfo;
Farid Zare Seisan7f6b8122018-03-30 11:19:10 -070027import android.os.SystemProperties;
28import android.os.UserHandle;
Fyodor Kupolov50979d12017-01-27 17:36:32 -080029import android.system.ErrnoException;
30import android.system.Os;
31import android.system.OsConstants;
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080032import android.util.Log;
Fyodor Kupolov50979d12017-01-27 17:36:32 -080033import android.util.Slog;
34import android.util.SparseArray;
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080035
Fyodor Kupolov50979d12017-01-27 17:36:32 -080036import com.android.internal.annotations.VisibleForTesting;
37
38import java.io.File;
39import java.io.IOException;
40import java.nio.charset.StandardCharsets;
41import java.util.ArrayList;
42import java.util.Collections;
43import java.util.List;
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080044import java.util.Objects;
Fyodor Kupolov50979d12017-01-27 17:36:32 -080045import java.util.Set;
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080046
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080047/**
48 * Helper class for preparing and destroying user storage
49 */
50class UserDataPreparer {
Fyodor Kupolov50979d12017-01-27 17:36:32 -080051 private static final String TAG = "UserDataPreparer";
52 private static final String XATTR_SERIAL = "user.serial";
53
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080054 private final Object mInstallLock;
55 private final Context mContext;
56 private final boolean mOnlyCore;
57 private final Installer mInstaller;
58
59 UserDataPreparer(Installer installer, Object installLock, Context context, boolean onlyCore) {
60 mInstallLock = installLock;
61 mContext = context;
62 mOnlyCore = onlyCore;
63 mInstaller = installer;
64 }
65
66 /**
67 * Prepare storage areas for given user on all mounted devices.
68 */
69 void prepareUserData(int userId, int userSerial, int flags) {
70 synchronized (mInstallLock) {
71 final StorageManager storage = mContext.getSystemService(StorageManager.class);
72 for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
73 final String volumeUuid = vol.getFsUuid();
74 prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
75 }
76 }
77 }
78
79 private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags,
80 boolean allowRecover) {
81 // Prepare storage and verify that serial numbers are consistent; if
82 // there's a mismatch we need to destroy to avoid leaking data
83 final StorageManager storage = mContext.getSystemService(StorageManager.class);
84 try {
85 storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
86
87 if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
Fyodor Kupolov50979d12017-01-27 17:36:32 -080088 enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial);
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080089 if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
Fyodor Kupolov50979d12017-01-27 17:36:32 -080090 enforceSerialNumber(getDataSystemDeDirectory(userId), userSerial);
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080091 }
92 }
93 if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
Fyodor Kupolov50979d12017-01-27 17:36:32 -080094 enforceSerialNumber(getDataUserCeDirectory(volumeUuid, userId), userSerial);
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080095 if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
Fyodor Kupolov50979d12017-01-27 17:36:32 -080096 enforceSerialNumber(getDataSystemCeDirectory(userId), userSerial);
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -080097 }
98 }
99
100 mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
Farid Zare Seisan7f6b8122018-03-30 11:19:10 -0700101
102 // CE storage is available after they are prepared.
103 if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 &&
104 (userId == UserHandle.USER_SYSTEM)) {
105 String propertyName = "sys.user." + userId + ".ce_available";
106 Slog.d(TAG, "Setting property: " + propertyName + "=true");
107 SystemProperties.set(propertyName, "true");
108 }
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -0800109 } catch (Exception e) {
110 logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
111 + " because we failed to prepare: " + e);
Jeff Sharkeya0c5ee12017-08-11 15:45:58 -0600112 destroyUserDataLI(volumeUuid, userId, flags);
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -0800113
114 if (allowRecover) {
115 // Try one last time; if we fail again we're really in trouble
Farid Zare Seisan7f6b8122018-03-30 11:19:10 -0700116 prepareUserDataLI(volumeUuid, userId, userSerial,
117 flags | StorageManager.FLAG_STORAGE_DE, false);
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -0800118 }
119 }
120 }
121
122 /**
123 * Destroy storage areas for given user on all mounted devices.
124 */
125 void destroyUserData(int userId, int flags) {
126 synchronized (mInstallLock) {
127 final StorageManager storage = mContext.getSystemService(StorageManager.class);
128 for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
129 final String volumeUuid = vol.getFsUuid();
130 destroyUserDataLI(volumeUuid, userId, flags);
131 }
132 }
133 }
134
135 void destroyUserDataLI(String volumeUuid, int userId, int flags) {
136 final StorageManager storage = mContext.getSystemService(StorageManager.class);
137 try {
138 // Clean up app data, profile data, and media data
139 mInstaller.destroyUserData(volumeUuid, userId, flags);
140
141 // Clean up system data
142 if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
143 if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
Fyodor Kupolov50979d12017-01-27 17:36:32 -0800144 FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
145 FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -0800146 }
147 if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
Fyodor Kupolov50979d12017-01-27 17:36:32 -0800148 FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -0800149 }
150 }
151
152 // Data with special labels is now gone, so finish the job
153 storage.destroyUserStorage(volumeUuid, userId, flags);
154
155 } catch (Exception e) {
156 logCriticalInfo(Log.WARN,
157 "Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e);
158 }
159 }
160
Fyodor Kupolov50979d12017-01-27 17:36:32 -0800161 /**
162 * Examine all users present on given mounted volume, and destroy data
163 * belonging to users that are no longer valid, or whose user ID has been
164 * recycled.
165 */
166 void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList) {
167 final List<File> files = new ArrayList<>();
168 Collections.addAll(files, FileUtils
169 .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid)));
170 Collections.addAll(files, FileUtils
171 .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)));
172 Collections.addAll(files, FileUtils
173 .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
174 Collections.addAll(files, FileUtils
175 .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
176 Collections.addAll(files, FileUtils
177 .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
178 reconcileUsers(volumeUuid, validUsersList, files);
179 }
180
181 @VisibleForTesting
182 void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList, List<File> files) {
183 final int userCount = validUsersList.size();
184 SparseArray<UserInfo> users = new SparseArray<>(userCount);
185 for (int i = 0; i < userCount; i++) {
186 UserInfo user = validUsersList.get(i);
187 users.put(user.id, user);
188 }
189 for (File file : files) {
190 if (!file.isDirectory()) {
191 continue;
192 }
193
194 final int userId;
195 final UserInfo info;
196 try {
197 userId = Integer.parseInt(file.getName());
198 info = users.get(userId);
199 } catch (NumberFormatException e) {
200 Slog.w(TAG, "Invalid user directory " + file);
201 continue;
202 }
203
204 boolean destroyUser = false;
205 if (info == null) {
206 logCriticalInfo(Log.WARN, "Destroying user directory " + file
207 + " because no matching user was found");
208 destroyUser = true;
209 } else if (!mOnlyCore) {
210 try {
211 enforceSerialNumber(file, info.serialNumber);
212 } catch (IOException e) {
213 logCriticalInfo(Log.WARN, "Destroying user directory " + file
214 + " because we failed to enforce serial number: " + e);
215 destroyUser = true;
216 }
217 }
218
219 if (destroyUser) {
220 synchronized (mInstallLock) {
221 destroyUserDataLI(volumeUuid, userId,
222 StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
223 }
224 }
225 }
226 }
227
228 @VisibleForTesting
229 protected File getDataMiscCeDirectory(int userId) {
230 return Environment.getDataMiscCeDirectory(userId);
231 }
232
233 @VisibleForTesting
234 protected File getDataSystemCeDirectory(int userId) {
235 return Environment.getDataSystemCeDirectory(userId);
236 }
237
238 @VisibleForTesting
239 protected File getDataMiscDeDirectory(int userId) {
240 return Environment.getDataMiscDeDirectory(userId);
241 }
242
243 @VisibleForTesting
244 protected File getUserSystemDirectory(int userId) {
245 return Environment.getUserSystemDirectory(userId);
246 }
247
248 @VisibleForTesting
249 protected File getDataUserCeDirectory(String volumeUuid, int userId) {
250 return Environment.getDataUserCeDirectory(volumeUuid, userId);
251 }
252
253 @VisibleForTesting
254 protected File getDataSystemDeDirectory(int userId) {
255 return Environment.getDataSystemDeDirectory(userId);
256 }
257
258 @VisibleForTesting
259 protected File getDataUserDeDirectory(String volumeUuid, int userId) {
260 return Environment.getDataUserDeDirectory(volumeUuid, userId);
261 }
262
263 @VisibleForTesting
264 protected boolean isFileEncryptedEmulatedOnly() {
265 return StorageManager.isFileEncryptedEmulatedOnly();
266 }
267
268 /**
269 * Enforce that serial number stored in user directory inode matches the
270 * given expected value. Gracefully sets the serial number if currently
271 * undefined.
272 *
273 * @throws IOException when problem extracting serial number, or serial
274 * number is mismatched.
275 */
276 void enforceSerialNumber(File file, int serialNumber) throws IOException {
277 if (isFileEncryptedEmulatedOnly()) {
278 // When we're emulating FBE, the directory may have been chmod
279 // 000'ed, meaning we can't read the serial number to enforce it;
280 // instead of destroying the user, just log a warning.
281 Slog.w(TAG, "Device is emulating FBE; assuming current serial number is valid");
282 return;
283 }
284
285 final int foundSerial = getSerialNumber(file);
286 Slog.v(TAG, "Found " + file + " with serial number " + foundSerial);
287
288 if (foundSerial == -1) {
289 Slog.d(TAG, "Serial number missing on " + file + "; assuming current is valid");
290 try {
291 setSerialNumber(file, serialNumber);
292 } catch (IOException e) {
293 Slog.w(TAG, "Failed to set serial number on " + file, e);
294 }
295
296 } else if (foundSerial != serialNumber) {
297 throw new IOException("Found serial number " + foundSerial
298 + " doesn't match expected " + serialNumber);
299 }
300 }
301
302 /**
303 * Set serial number stored in user directory inode.
304 *
305 * @throws IOException if serial number was already set
306 */
307 private static void setSerialNumber(File file, int serialNumber) throws IOException {
308 try {
309 final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8);
310 Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE);
311 } catch (ErrnoException e) {
312 throw e.rethrowAsIOException();
313 }
314 }
315
316 /**
317 * Return serial number stored in user directory inode.
318 *
319 * @return parsed serial number, or -1 if not set
320 */
321 @VisibleForTesting
322 static int getSerialNumber(File file) throws IOException {
323 try {
324 final byte[] buf = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL);
325 final String serial = new String(buf);
326 try {
327 return Integer.parseInt(serial);
328 } catch (NumberFormatException e) {
329 throw new IOException("Bad serial number: " + serial);
330 }
331 } catch (ErrnoException e) {
332 if (e.errno == OsConstants.ENODATA) {
333 return -1;
334 } else {
335 throw e.rethrowAsIOException();
336 }
337 }
338 }
339
Fyodor Kupolov5c0ecfd2017-01-27 11:11:57 -0800340}