Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 241529cb authored by Alex Buynytskyy's avatar Alex Buynytskyy
Browse files

Protect userlist.xml and <user>.xml from corruption.

Bug: 269072872
Bug: 253568736
Test: atest UserManagerServiceTest UserManagerServiceUserInfoTest
Change-Id: I7aa9858731f60f80d1538f40fec765316b691c2d
parent 69b03326
Loading
Loading
Loading
Loading
+177 −117
Original line number Diff line number Diff line
@@ -278,6 +278,7 @@ public class UserManagerService extends IUserManager.Stub {
    private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms

    static final int WRITE_USER_MSG = 1;
    static final int WRITE_USER_LIST_MSG = 2;
    static final int WRITE_USER_DELAY = 2*1000;  // 2 seconds

    private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
@@ -321,7 +322,6 @@ public class UserManagerService extends IUserManager.Stub {
    private final Handler mHandler;

    private final File mUsersDir;
    @GuardedBy("mPackagesLock")
    private final File mUserListFile;

    private final IBinder mUserRestrictionToken = new Binder();
@@ -3623,17 +3623,33 @@ public class UserManagerService extends IUserManager.Stub {
        mUpdatingSystemUserMode = true;
    }


    private ResilientAtomicFile getUserListFile() {
        File tempBackup = new File(mUserListFile.getParent(), mUserListFile.getName() + ".backup");
        File reserveCopy = new File(mUserListFile.getParent(),
                mUserListFile.getName() + ".reservecopy");
        int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
        return new ResilientAtomicFile(mUserListFile, tempBackup, reserveCopy, fileMode,
                "user list", (priority, msg) -> {
            Slog.e(LOG_TAG, msg);
            // Something went wrong, schedule full rewrite.
            scheduleWriteUserList();
        });
    }

    @GuardedBy({"mPackagesLock"})
    private void readUserListLP() {
        if (!mUserListFile.exists()) {
        try (ResilientAtomicFile file = getUserListFile()) {
            FileInputStream fin = null;
            try {
                fin = file.openRead();
                if (fin == null) {
                    Slog.e(LOG_TAG, "userlist.xml not found, fallback to single user");
                    fallbackToSingleUserLP();
                    return;
                }
        FileInputStream fis = null;
        AtomicFile userListFile = new AtomicFile(mUserListFile);
        try {
            fis = userListFile.openRead();
            final TypedXmlPullParser parser = Xml.resolvePullParser(fis);

                final TypedXmlPullParser parser = Xml.resolvePullParser(fin);
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG
                        && type != XmlPullParser.END_DOCUMENT) {
@@ -3690,10 +3706,12 @@ public class UserManagerService extends IUserManager.Stub {

                updateUserIds();
                upgradeIfNecessaryLP();
        } catch (IOException | XmlPullParserException e) {
            fallbackToSingleUserLP();
        } finally {
            IoUtils.closeQuietly(fis);
            } catch (Exception e) {
                // Remove corrupted file and retry.
                file.failRead(fin, e);
                readUserListLP();
                return;
            }
        }

        synchronized (mUsersLock) {
@@ -4099,6 +4117,18 @@ public class UserManagerService extends IUserManager.Stub {
        }
    }

    private void scheduleWriteUserList() {
        if (DBG) {
            debug("scheduleWriteUserList");
        }
        // No need to wrap it within a lock -- worst case, we'll just post the same message
        // twice.
        if (!mHandler.hasMessages(WRITE_USER_LIST_MSG)) {
            Message msg = mHandler.obtainMessage(WRITE_USER_LIST_MSG);
            mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
        }
    }

    private void scheduleWriteUser(UserData userData) {
        if (DBG) {
            debug("scheduleWriteUser");
@@ -4111,13 +4141,29 @@ public class UserManagerService extends IUserManager.Stub {
        }
    }

    private ResilientAtomicFile getUserFile(int userId) {
        File file = new File(mUsersDir, userId + XML_SUFFIX);
        File tempBackup = new File(mUsersDir, userId + XML_SUFFIX + ".backup");
        File reserveCopy = new File(mUsersDir, userId + XML_SUFFIX + ".reservecopy");
        int fileMode = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH;
        return new ResilientAtomicFile(file, tempBackup, reserveCopy, fileMode,
                "user info", (priority, msg) -> {
            Slog.e(LOG_TAG, msg);
            // Something went wrong, schedule full rewrite.
            UserData userData = getUserDataNoChecks(userId);
            if (userData != null) {
                scheduleWriteUser(userData);
            }
        });
    }

    @GuardedBy({"mPackagesLock"})
    private void writeUserLP(UserData userData) {
        if (DBG) {
            debug("writeUserLP " + userData);
        }
        try (ResilientAtomicFile userFile = getUserFile(userData.info.id)) {
            FileOutputStream fos = null;
        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userData.info.id + XML_SUFFIX));
            try {
                fos = userFile.startWrite();
                writeUserLP(userData, fos);
@@ -4127,6 +4173,7 @@ public class UserManagerService extends IUserManager.Stub {
                userFile.failWrite(fos);
            }
        }
    }

    /*
     * Writes the user file in this format:
@@ -4253,13 +4300,16 @@ public class UserManagerService extends IUserManager.Stub {
        if (DBG) {
            debug("writeUserList");
        }

        try (ResilientAtomicFile file = getUserListFile()) {
            FileOutputStream fos = null;
        AtomicFile userListFile = new AtomicFile(mUserListFile);
            try {
            fos = userListFile.startWrite();
                fos = file.startWrite();

                final TypedXmlSerializer serializer = Xml.resolveSerializer(fos);
                serializer.startDocument(null, true);
            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
                serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output",
                        true);

                serializer.startTag(null, TAG_USERS);
                serializer.attributeInt(null, ATTR_NEXT_SERIAL_NO, mNextSerialNumber);
@@ -4289,29 +4339,32 @@ public class UserManagerService extends IUserManager.Stub {
                serializer.endTag(null, TAG_USERS);

                serializer.endDocument();
            userListFile.finishWrite(fos);
                file.finishWrite(fos);
            } catch (Exception e) {
            userListFile.failWrite(fos);
            Slog.e(LOG_TAG, "Error writing user list");
                Slog.e(LOG_TAG, "Error writing user list", e);
                file.failWrite(fos);
            }
        }
    }

    @GuardedBy({"mPackagesLock"})
    private UserData readUserLP(int id) {
        try (ResilientAtomicFile file = getUserFile(id)) {
            FileInputStream fis = null;
            try {
            AtomicFile userFile =
                    new AtomicFile(new File(mUsersDir, Integer.toString(id) + XML_SUFFIX));
            fis = userFile.openRead();
                fis = file.openRead();
                if (fis == null) {
                    Slog.e(LOG_TAG, "User info not found, returning null, user id: " + id);
                    return null;
                }
                return readUserLP(id, fis);
        } catch (IOException ioe) {
            Slog.e(LOG_TAG, "Error reading user list");
        } catch (XmlPullParserException pe) {
            Slog.e(LOG_TAG, "Error reading user list");
        } finally {
            IoUtils.closeQuietly(fis);
            } catch (Exception e) {
                // Remove corrupted file and retry.
                Slog.e(LOG_TAG, "Error reading user info, user id: " + id);
                file.failRead(fis, e);
                return readUserLP(id);
            }
        }
        return null;
    }

    @GuardedBy({"mPackagesLock"})
@@ -5805,9 +5858,8 @@ public class UserManagerService extends IUserManager.Stub {
        synchronized (mPackagesLock) {
            writeUserListLP();
        }
        // Remove user file
        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userId + XML_SUFFIX));
        userFile.delete();
        // Remove user file(s)
        getUserFile(userId).delete();
        updateUserIds();
        if (RELEASE_DELETED_USER_ID) {
            synchronized (mUsersLock) {
@@ -6770,6 +6822,13 @@ public class UserManagerService extends IUserManager.Stub {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case WRITE_USER_LIST_MSG: {
                    removeMessages(WRITE_USER_LIST_MSG);
                    synchronized (mPackagesLock) {
                        writeUserListLP();
                    }
                    break;
                }
                case WRITE_USER_MSG:
                    removeMessages(WRITE_USER_MSG, msg.obj);
                    synchronized (mPackagesLock) {
@@ -6782,6 +6841,7 @@ public class UserManagerService extends IUserManager.Stub {
                                    + ", it was probably removed before handler could handle it");
                        }
                    }
                    break;
            }
        }
    }