UserDataPreparer.java revision 7f6b8120da3de99bace28eb1c1602016d0e5fbb0
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.os.SystemProperties;
28import android.os.UserHandle;
29import android.system.ErrnoException;
30import android.system.Os;
31import android.system.OsConstants;
32import android.util.Log;
33import android.util.Slog;
34import android.util.SparseArray;
35
36import 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;
44import java.util.Objects;
45import java.util.Set;
46
47/**
48 * Helper class for preparing and destroying user storage
49 */
50class UserDataPreparer {
51    private static final String TAG = "UserDataPreparer";
52    private static final String XATTR_SERIAL = "user.serial";
53
54    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) {
88                enforceSerialNumber(getDataUserDeDirectory(volumeUuid, userId), userSerial);
89                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
90                    enforceSerialNumber(getDataSystemDeDirectory(userId), userSerial);
91                }
92            }
93            if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) {
94                enforceSerialNumber(getDataUserCeDirectory(volumeUuid, userId), userSerial);
95                if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) {
96                    enforceSerialNumber(getDataSystemCeDirectory(userId), userSerial);
97                }
98            }
99
100            mInstaller.createUserData(volumeUuid, userId, userSerial, flags);
101
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            }
109        } catch (Exception e) {
110            logCriticalInfo(Log.WARN, "Destroying user " + userId + " on volume " + volumeUuid
111                    + " because we failed to prepare: " + e);
112            destroyUserDataLI(volumeUuid, userId, flags);
113
114            if (allowRecover) {
115                // Try one last time; if we fail again we're really in trouble
116                prepareUserDataLI(volumeUuid, userId, userSerial,
117                    flags | StorageManager.FLAG_STORAGE_DE, false);
118            }
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) {
144                    FileUtils.deleteContentsAndDir(getUserSystemDirectory(userId));
145                    FileUtils.deleteContentsAndDir(getDataSystemDeDirectory(userId));
146                }
147                if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
148                    FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
149                }
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
161    /**
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
340}
341