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

Commit 33a90988 authored by Tetiana Meronyk's avatar Tetiana Meronyk
Browse files

Fix security vulnerability that creates user with no restrictions when accountOptions are too long.

Bug: 293602970
Test: atest UserManagerTest#testAddUserAccountData_validStringValuesAreSaved_validBundleIsSaved && atest UserManagerTest#testAddUserAccountData_invalidStringValuesAreTruncated_invalidBundleIsDropped
Change-Id: I23c971f671546ac085060add89485cfac6691ca3
Merged-In: I23c971f671546ac085060add89485cfac6691ca3
parent 5ee95d20
Loading
Loading
Loading
Loading
+37 −0
Original line number Diff line number Diff line
@@ -294,6 +294,43 @@ public final class PersistableBundle extends BaseBundle implements Cloneable, Pa
        XmlUtils.writeMapXml(mMap, out, this);
    }

    /**
     * Checks whether all keys and values are within the given character limit.
     * Note: Maximum character limit of String that can be saved to XML as part of bundle is 65535.
     * Otherwise IOException is thrown.
     * @param limit length of String keys and values in the PersistableBundle, including nested
     *                    PersistableBundles to check against.
     *
     * @hide
     */
    public boolean isBundleContentsWithinLengthLimit(int limit) {
        unparcel();
        if (mMap == null) {
            return true;
        }
        for (int i = 0; i < mMap.size(); i++) {
            if (mMap.keyAt(i) != null && mMap.keyAt(i).length() > limit) {
                return false;
            }
            final Object value = mMap.valueAt(i);
            if (value instanceof String && ((String) value).length() > limit) {
                return false;
            } else if (value instanceof String[]) {
                String[] stringArray =  (String[]) value;
                for (int j = 0; j < stringArray.length; j++) {
                    if (stringArray[j] != null
                            && stringArray[j].length() > limit) {
                        return false;
                    }
                }
            } else if (value instanceof PersistableBundle
                    && !((PersistableBundle) value).isBundleContentsWithinLengthLimit(limit)) {
                return false;
            }
        }
        return true;
    }

    /** @hide */
    static class MyReadMapCallback implements  XmlUtils.ReadMapCallback {
        @Override
+19 −4
Original line number Diff line number Diff line
@@ -103,6 +103,21 @@ public class UserManager {
    /** Whether the device is in headless system user mode; null until cached. */
    private static Boolean sIsHeadlessSystemUser = null;

    /** Maximum length of username.
     * @hide
     */
    public static final int MAX_USER_NAME_LENGTH = 100;

    /** Maximum length of user property String value.
     * @hide
     */
    public static final int MAX_ACCOUNT_STRING_LENGTH = 500;

    /** Maximum length of account options String values.
     * @hide
     */
    public static final int MAX_ACCOUNT_OPTIONS_LENGTH = 1000;

    /**
     * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
     * This type of user cannot be created; it can only pre-exist on first boot.
@@ -4204,15 +4219,15 @@ public class UserManager {
     * This API should only be called if the current user is an {@link #isAdminUser() admin} user,
     * as otherwise the returned intent will not be able to create a user.
     *
     * @param userName Optional name to assign to the user.
     * @param userName Optional name to assign to the user. Character limit is 100.
     * @param accountName Optional account name that will be used by the setup wizard to initialize
     *                    the user.
     *                    the user. Character limit is 500.
     * @param accountType Optional account type for the account to be created. This is required
     *                    if the account name is specified.
     *                    if the account name is specified. Character limit is 500.
     * @param accountOptions Optional bundle of data to be passed in during account creation in the
     *                       new user via {@link AccountManager#addAccount(String, String, String[],
     *                       Bundle, android.app.Activity, android.accounts.AccountManagerCallback,
     *                       Handler)}.
     *                       Handler)}. Character limit is 1000.
     * @return An Intent that can be launched from an Activity.
     * @see #USER_CREATION_FAILED_NOT_PERMITTED
     * @see #USER_CREATION_FAILED_NO_MORE_USERS
+12 −0
Original line number Diff line number Diff line
@@ -116,6 +116,14 @@ public class ConfirmUserCreationActivity extends AlertActivity
        if (cantCreateUser) {
            setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
            return null;
        } else if (!(isUserPropertyWithinLimit(mUserName, UserManager.MAX_USER_NAME_LENGTH)
                && isUserPropertyWithinLimit(mAccountName, UserManager.MAX_ACCOUNT_STRING_LENGTH)
                && isUserPropertyWithinLimit(mAccountType, UserManager.MAX_ACCOUNT_STRING_LENGTH))
                || (mAccountOptions != null && !mAccountOptions.isBundleContentsWithinLengthLimit(
                UserManager.MAX_ACCOUNT_OPTIONS_LENGTH))) {
            setResult(UserManager.USER_CREATION_FAILED_NOT_PERMITTED);
            Log.i(TAG, "User properties must not exceed their character limits");
            return null;
        } else if (cantCreateAnyMoreUsers) {
            setResult(UserManager.USER_CREATION_FAILED_NO_MORE_USERS);
            return null;
@@ -144,4 +152,8 @@ public class ConfirmUserCreationActivity extends AlertActivity
        }
        finish();
    }

    private boolean isUserPropertyWithinLimit(String property, int limit) {
        return property == null || property.length() <= limit;
    }
}
+17 −13
Original line number Diff line number Diff line
@@ -284,8 +284,6 @@ public class UserManagerService extends IUserManager.Stub {

    private static final int USER_VERSION = 11;

    private static final int MAX_USER_STRING_LENGTH = 500;

    private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms

    static final int WRITE_USER_MSG = 1;
@@ -4261,16 +4259,18 @@ public class UserManagerService extends IUserManager.Stub {
        if (userData.persistSeedData) {
            if (userData.seedAccountName != null) {
                serializer.attribute(null, ATTR_SEED_ACCOUNT_NAME,
                        truncateString(userData.seedAccountName));
                        truncateString(userData.seedAccountName,
                                UserManager.MAX_ACCOUNT_STRING_LENGTH));
            }
            if (userData.seedAccountType != null) {
                serializer.attribute(null, ATTR_SEED_ACCOUNT_TYPE,
                        truncateString(userData.seedAccountType));
                        truncateString(userData.seedAccountType,
                                UserManager.MAX_ACCOUNT_STRING_LENGTH));
            }
        }
        if (userInfo.name != null) {
            serializer.startTag(null, TAG_NAME);
            serializer.text(truncateString(userInfo.name));
            serializer.text(truncateString(userInfo.name, UserManager.MAX_USER_NAME_LENGTH));
            serializer.endTag(null, TAG_NAME);
        }
        synchronized (mRestrictionsLock) {
@@ -4319,11 +4319,11 @@ public class UserManagerService extends IUserManager.Stub {
        serializer.endDocument();
    }

    private String truncateString(String original) {
        if (original == null || original.length() <= MAX_USER_STRING_LENGTH) {
    private String truncateString(String original, int limit) {
        if (original == null || original.length() <= limit) {
            return original;
        }
        return original.substring(0, MAX_USER_STRING_LENGTH);
        return original.substring(0, limit);
    }

    /*
@@ -4771,8 +4771,7 @@ public class UserManagerService extends IUserManager.Stub {
            @UserIdInt int parentId, boolean preCreate, @Nullable String[] disallowedPackages,
            @NonNull TimingsTraceAndSlog t, @Nullable Object token)
            throws UserManager.CheckedUserOperationException {

        String truncatedName = truncateString(name);
        String truncatedName = truncateString(name, UserManager.MAX_USER_NAME_LENGTH);
        final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
        if (userTypeDetails == null) {
            throwCheckedUserOperationException(
@@ -6373,9 +6372,14 @@ public class UserManagerService extends IUserManager.Stub {
                    Slog.e(LOG_TAG, "No such user for settings seed data u=" + userId);
                    return;
                }
                userData.seedAccountName = truncateString(accountName);
                userData.seedAccountType = truncateString(accountType);
                userData.seedAccountName = truncateString(accountName,
                        UserManager.MAX_ACCOUNT_STRING_LENGTH);
                userData.seedAccountType = truncateString(accountType,
                        UserManager.MAX_ACCOUNT_STRING_LENGTH);
                if (accountOptions != null && accountOptions.isBundleContentsWithinLengthLimit(
                        UserManager.MAX_ACCOUNT_OPTIONS_LENGTH)) {
                    userData.seedAccountOptions = accountOptions;
                }
                userData.persistSeedData = persist;
            }
            if (persist) {
+1 −1
Original line number Diff line number Diff line
@@ -331,7 +331,7 @@ public final class UserManagerServiceTest {
    @Test
    public void testCreateUserWithLongName_TruncatesName() {
        UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
        assertThat(user.name.length()).isEqualTo(500);
        assertThat(user.name.length()).isEqualTo(UserManager.MAX_USER_NAME_LENGTH);
        UserInfo user1 = mUms.createUserWithThrow("Test", USER_TYPE_FULL_SECONDARY, 0);
        assertThat(user1.name.length()).isEqualTo(4);
    }
Loading