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 android.content.Context;
20import android.content.pm.UserInfo;
21import android.os.Environment;
22import android.os.FileUtils;
23import android.os.storage.StorageManager;
24import android.os.storage.VolumeInfo;
25import android.system.ErrnoException;
26import android.system.Os;
27import android.system.OsConstants;
28import android.util.Log;
29import android.util.Slog;
30import android.util.SparseArray;
31
32import com.android.internal.annotations.VisibleForTesting;
33
34import java.io.File;
35import java.io.IOException;
36import java.nio.charset.StandardCharsets;
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.List;
40import java.util.Objects;
41import java.util.Set;
42
43import static com.android.server.pm.PackageManagerService.logCriticalInfo;
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                    FileUtils.deleteContentsAndDir(getDataMiscDeDirectory(userId));
136                }
137                if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
138                    FileUtils.deleteContentsAndDir(getDataSystemCeDirectory(userId));
139                    FileUtils.deleteContentsAndDir(getDataMiscCeDirectory(userId));
140                }
141            }
142
143            // Data with special labels is now gone, so finish the job
144            storage.destroyUserStorage(volumeUuid, userId, flags);
145
146        } catch (Exception e) {
147            logCriticalInfo(Log.WARN,
148                    "Failed to destroy user " + userId + " on volume " + volumeUuid + ": " + e);
149        }
150    }
151
152    /**
153     * Examine all users present on given mounted volume, and destroy data
154     * belonging to users that are no longer valid, or whose user ID has been
155     * recycled.
156     */
157    void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList) {
158        final List<File> files = new ArrayList<>();
159        Collections.addAll(files, FileUtils
160                .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid)));
161        Collections.addAll(files, FileUtils
162                .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid)));
163        Collections.addAll(files, FileUtils
164                .listFilesOrEmpty(Environment.getDataSystemDeDirectory()));
165        Collections.addAll(files, FileUtils
166                .listFilesOrEmpty(Environment.getDataSystemCeDirectory()));
167        Collections.addAll(files, FileUtils
168                .listFilesOrEmpty(Environment.getDataMiscCeDirectory()));
169        reconcileUsers(volumeUuid, validUsersList, files);
170    }
171
172    @VisibleForTesting
173    void reconcileUsers(String volumeUuid, List<UserInfo> validUsersList, List<File> files) {
174        final int userCount = validUsersList.size();
175        SparseArray<UserInfo> users = new SparseArray<>(userCount);
176        for (int i = 0; i < userCount; i++) {
177            UserInfo user = validUsersList.get(i);
178            users.put(user.id, user);
179        }
180        for (File file : files) {
181            if (!file.isDirectory()) {
182                continue;
183            }
184
185            final int userId;
186            final UserInfo info;
187            try {
188                userId = Integer.parseInt(file.getName());
189                info = users.get(userId);
190            } catch (NumberFormatException e) {
191                Slog.w(TAG, "Invalid user directory " + file);
192                continue;
193            }
194
195            boolean destroyUser = false;
196            if (info == null) {
197                logCriticalInfo(Log.WARN, "Destroying user directory " + file
198                        + " because no matching user was found");
199                destroyUser = true;
200            } else if (!mOnlyCore) {
201                try {
202                    enforceSerialNumber(file, info.serialNumber);
203                } catch (IOException e) {
204                    logCriticalInfo(Log.WARN, "Destroying user directory " + file
205                            + " because we failed to enforce serial number: " + e);
206                    destroyUser = true;
207                }
208            }
209
210            if (destroyUser) {
211                synchronized (mInstallLock) {
212                    destroyUserDataLI(volumeUuid, userId,
213                            StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
214                }
215            }
216        }
217    }
218
219    @VisibleForTesting
220    protected File getDataMiscCeDirectory(int userId) {
221        return Environment.getDataMiscCeDirectory(userId);
222    }
223
224    @VisibleForTesting
225    protected File getDataSystemCeDirectory(int userId) {
226        return Environment.getDataSystemCeDirectory(userId);
227    }
228
229    @VisibleForTesting
230    protected File getDataMiscDeDirectory(int userId) {
231        return Environment.getDataMiscDeDirectory(userId);
232    }
233
234    @VisibleForTesting
235    protected File getUserSystemDirectory(int userId) {
236        return Environment.getUserSystemDirectory(userId);
237    }
238
239    @VisibleForTesting
240    protected File getDataUserCeDirectory(String volumeUuid, int userId) {
241        return Environment.getDataUserCeDirectory(volumeUuid, userId);
242    }
243
244    @VisibleForTesting
245    protected File getDataSystemDeDirectory(int userId) {
246        return Environment.getDataSystemDeDirectory(userId);
247    }
248
249    @VisibleForTesting
250    protected File getDataUserDeDirectory(String volumeUuid, int userId) {
251        return Environment.getDataUserDeDirectory(volumeUuid, userId);
252    }
253
254    @VisibleForTesting
255    protected boolean isFileEncryptedEmulatedOnly() {
256        return StorageManager.isFileEncryptedEmulatedOnly();
257    }
258
259    /**
260     * Enforce that serial number stored in user directory inode matches the
261     * given expected value. Gracefully sets the serial number if currently
262     * undefined.
263     *
264     * @throws IOException when problem extracting serial number, or serial
265     *             number is mismatched.
266     */
267    void enforceSerialNumber(File file, int serialNumber) throws IOException {
268        if (isFileEncryptedEmulatedOnly()) {
269            // When we're emulating FBE, the directory may have been chmod
270            // 000'ed, meaning we can't read the serial number to enforce it;
271            // instead of destroying the user, just log a warning.
272            Slog.w(TAG, "Device is emulating FBE; assuming current serial number is valid");
273            return;
274        }
275
276        final int foundSerial = getSerialNumber(file);
277        Slog.v(TAG, "Found " + file + " with serial number " + foundSerial);
278
279        if (foundSerial == -1) {
280            Slog.d(TAG, "Serial number missing on " + file + "; assuming current is valid");
281            try {
282                setSerialNumber(file, serialNumber);
283            } catch (IOException e) {
284                Slog.w(TAG, "Failed to set serial number on " + file, e);
285            }
286
287        } else if (foundSerial != serialNumber) {
288            throw new IOException("Found serial number " + foundSerial
289                    + " doesn't match expected " + serialNumber);
290        }
291    }
292
293    /**
294     * Set serial number stored in user directory inode.
295     *
296     * @throws IOException if serial number was already set
297     */
298    private static void setSerialNumber(File file, int serialNumber) throws IOException {
299        try {
300            final byte[] buf = Integer.toString(serialNumber).getBytes(StandardCharsets.UTF_8);
301            Os.setxattr(file.getAbsolutePath(), XATTR_SERIAL, buf, OsConstants.XATTR_CREATE);
302        } catch (ErrnoException e) {
303            throw e.rethrowAsIOException();
304        }
305    }
306
307    /**
308     * Return serial number stored in user directory inode.
309     *
310     * @return parsed serial number, or -1 if not set
311     */
312    @VisibleForTesting
313    static int getSerialNumber(File file) throws IOException {
314        try {
315            final byte[] buf = Os.getxattr(file.getAbsolutePath(), XATTR_SERIAL);
316            final String serial = new String(buf);
317            try {
318                return Integer.parseInt(serial);
319            } catch (NumberFormatException e) {
320                throw new IOException("Bad serial number: " + serial);
321            }
322        } catch (ErrnoException e) {
323            if (e.errno == OsConstants.ENODATA) {
324                return -1;
325            } else {
326                throw e.rethrowAsIOException();
327            }
328        }
329    }
330
331}
332