UserDataPreparer.java revision 7c4c55dcb6d386fb3843069a02c177df66df09c7
1/*
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
19import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
20
21import android.content.Context;
22import android.content.pm.UserInfo;
23import android.os.Environment;
24import android.os.FileUtils;
25import android.os.storage.StorageManager;
26import android.os.storage.VolumeInfo;
27import android.system.ErrnoException;
28import android.system.Os;
29import android.system.OsConstants;
30import android.util.Log;
31import android.util.Slog;
32import android.util.SparseArray;
33
34import com.android.internal.annotations.VisibleForTesting;
35
36import java.io.File;
37import java.io.IOException;
38import java.nio.charset.StandardCharsets;
39import java.util.ArrayList;
40import java.util.Collections;
41import java.util.List;
42import java.util.Objects;
43import java.util.Set;
44
45/**
46 * Helper class for preparing and destroying user storage
47 */
48class UserDataPreparer {
49    private static final String TAG = "UserDataPreparer";
50    private static final String XATTR_SERIAL = "user.serial";
51
52    private final Object mInstallLock;
53    private final Context mContext;
54    private final boolean mOnlyCore;
55    private final Installer mInstaller;
56
57    UserDataPreparer(Installer installer, Object installLock, Context context, boolean onlyCore) {
58        mInstallLock = installLock;
59        mContext = context;
60        mOnlyCore = onlyCore;
61        mInstaller = installer;
62    }
63
64    /**
65     * Prepare storage areas for given user on all mounted devices.
66     */
67    void prepareUserData(int userId, int userSerial, int flags) {
68        synchronized (mInstallLock) {
69            final StorageManager storage = mContext.getSystemService(StorageManager.class);
70            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
71                final String volumeUuid = vol.getFsUuid();
72                prepareUserDataLI(volumeUuid, userId, userSerial, flags, true);
73            }
74        }
75    }
76
77    private void prepareUserDataLI(String volumeUuid, int userId, int userSerial, int flags,
78            boolean allowRecover) {
79        // Prepare storage and verify that serial numbers are consistent; if
80        // there's a mismatch we need to destroy to avoid leaking data
81        final StorageManager storage = mContext.getSystemService(StorageManager.class);
82        try {
83            storage.prepareUserStorage(volumeUuid, userId, userSerial, flags);
84
85            if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) {
86                enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial);
87                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
88                    enforceSerialNumber(getDataSystemDeDirectory(userId), userSerial);
89                }
90            }
91            if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
92                enforceSerialNumber(getDataUserCeDirectory(volumeUuid, userId), userSerial);
93                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
94                    enforceSerialNumber(getDataSystemCeDirectory(userId), userSerial);
95                }
96            }
97
98            mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
99        } catch (Exception e) {
100            logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
101                    + " because we failed to prepare: " + e);
102            destroyUserDataLI(volumeUuid, userId, flags);
103
104            if (allowRecover) {
105                // Try one last time; if we fail again we're really in trouble
106                prepareUserDataLI(volumeUuid, userId, userSerial, flags, false);
107            }
108        }
109    }
110
111    /**
112     * Destroy storage areas for given user on all mounted devices.
113     */
114    void destroyUserData(int userId, int flags) {
115        synchronized (mInstallLock) {
116            final StorageManager storage = mContext.getSystemService(StorageManager.class);
117            for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
118                final String volumeUuid = vol.getFsUuid();
119                destroyUserDataLI(volumeUuid, userId, flags);
120            }
121        }
122    }
123
124    void destroyUserDataLI(String volumeUuid, int userId, int flags) {
125        final StorageManager storage = mContext.getSystemService(StorageManager.class);
126        try {
127            // Clean up app data, profile data, and media data
128            mInstaller.destroyUserData(volumeUuid, userId, flags);
129
130            // Clean up system data
131            if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
132                if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
133                    FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
134                    FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
135                }
136                if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
137                    FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
138                }
139            }
140
141            // Data with special labels is now gone, so finish the job
142            storage.destroyUserStorage(volumeUuid, userId, flags);
143
144        } catch (Exception e) {
145            logCriticalInfo(Log.WARN,
146                    "Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e);
147        }
148    }
149
150    /**
151     * Examine all users present on given mounted volume, and destroy data
152     * belonging to users that are no longer valid, or whose user ID has been
153     * recycled.
154     */
155    void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList) {
156        final List<File> files = new ArrayList<>();
157        Collections.addAll(files, FileUtils
158                .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid)));
159        Collections.addAll(files, FileUtils
160                .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)));
161        Collections.addAll(files, FileUtils
162                .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
163        Collections.addAll(files, FileUtils
164                .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
165        Collections.addAll(files, FileUtils
166                .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
167        reconcileUsers(volumeUuid, validUsersList, files);
168    }
169
170    @VisibleForTesting
171    void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList, List<File> files) {
172        final int userCount = validUsersList.size();
173        SparseArray<UserInfo> users = new SparseArray<>(userCount);
174        for (int i = 0; i < userCount; i++) {
175            UserInfo user = validUsersList.get(i);
176            users.put(user.id, user);
177        }
178        for (File file : files) {
179            if (!file.isDirectory()) {
180                continue;
181            }
182
183            final int userId;
184            final UserInfo info;
185            try {
186                userId = Integer.parseInt(file.getName());
187                info = users.get(userId);
188            } catch (NumberFormatException e) {
189                Slog.w(TAG, "Invalid user directory " + file);
190                continue;
191            }
192
193            boolean destroyUser = false;
194            if (info == null) {
195                logCriticalInfo(Log.WARN, "Destroying user directory " + file
196                        + " because no matching user was found");
197                destroyUser = true;
198            } else if (!mOnlyCore) {
199                try {
200                    enforceSerialNumber(file, info.serialNumber);
201                } catch (IOException e) {
202                    logCriticalInfo(Log.WARN, "Destroying user directory " + file
203                            + " because we failed to enforce serial number: " + e);
204                    destroyUser = true;
205                }
206            }
207
208            if (destroyUser) {
209                synchronized (mInstallLock) {
210                    destroyUserDataLI(volumeUuid, userId,
211                            StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
212                }
213            }
214        }
215    }
216
217    @VisibleForTesting
218    protected File getDataMiscCeDirectory(int userId) {
219        return Environment.getDataMiscCeDirectory(userId);
220    }
221
222    @VisibleForTesting
223    protected File getDataSystemCeDirectory(int userId) {
224        return Environment.getDataSystemCeDirectory(userId);
225    }
226
227    @VisibleForTesting
228    protected File getDataMiscDeDirectory(int userId) {
229        return Environment.getDataMiscDeDirectory(userId);
230    }
231
232    @VisibleForTesting
233    protected File getUserSystemDirectory(int userId) {
234        return Environment.getUserSystemDirectory(userId);
235    }
236
237    @VisibleForTesting
238    protected File getDataUserCeDirectory(String volumeUuid, int userId) {
239        return Environment.getDataUserCeDirectory(volumeUuid, userId);
240    }
241
242    @VisibleForTesting
243    protected File getDataSystemDeDirectory(int userId) {
244        return Environment.getDataSystemDeDirectory(userId);
245    }
246
247    @VisibleForTesting
248    protected File getDataUserDeDirectory(String volumeUuid, int userId) {
249        return Environment.getDataUserDeDirectory(volumeUuid, userId);
250    }
251
252    @VisibleForTesting
253    protected boolean isFileEncryptedEmulatedOnly() {
254        return StorageManager.isFileEncryptedEmulatedOnly();
255    }
256
257    /**
258     * Enforce that serial number stored in user directory inode matches the
259     * given expected value. Gracefully sets the serial number if currently
260     * undefined.
261     *
262     * @throws IOException when problem extracting serial number, or serial
263     *             number is mismatched.
264     */
265    void enforceSerialNumber(File file, int serialNumber) throws IOException {
266        if (isFileEncryptedEmulatedOnly()) {
267            // When we're emulating FBE, the directory may have been chmod
268            // 000'ed, meaning we can't read the serial number to enforce it;
269            // instead of destroying the user, just log a warning.
270            Slog.w(TAG, "Device is emulating FBE; assuming current serial number is valid");
271            return;
272        }
273
274        final int foundSerial = getSerialNumber(file);
275        Slog.v(TAG, "Found " + file + " with serial number " + foundSerial);
276
277        if (foundSerial == -1) {
278            Slog.d(TAG, "Serial number missing on " + file + "; assuming current is valid");
279            try {
280                setSerialNumber(file, serialNumber);
281            } catch (IOException e) {
282                Slog.w(TAG, "Failed to set serial number on " + file, e);
283            }
284
285        } else if (foundSerial != serialNumber) {
286            throw new IOException("Found serial number " + foundSerial
287                    + " doesn't match expected " + serialNumber);
288        }
289    }
290
291    /**
292     * Set serial number stored in user directory inode.
293     *
294     * @throws IOException if serial number was already set
295     */
296    private static void setSerialNumber(File file, int serialNumber) throws IOException {
297        try {
298            final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8);
299            Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE);
300        } catch (ErrnoException e) {
301            throw e.rethrowAsIOException();
302        }
303    }
304
305    /**
306     * Return serial number stored in user directory inode.
307     *
308     * @return parsed serial number, or -1 if not set
309     */
310    @VisibleForTesting
311    static int getSerialNumber(File file) throws IOException {
312        try {
313            final byte[] buf = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL);
314            final String serial = new String(buf);
315            try {
316                return Integer.parseInt(serial);
317            } catch (NumberFormatException e) {
318                throw new IOException("Bad serial number: " + serial);
319            }
320        } catch (ErrnoException e) {
321            if (e.errno == OsConstants.ENODATA) {
322                return -1;
323            } else {
324                throw e.rethrowAsIOException();
325            }
326        }
327    }
328
329}
330