asLody/VirtualApp · error · SecurityException

You need MANAGE_USERS permission to:

Error message

You need MANAGE_USERS permission to: 

What it means

checkManageUsersPermission in VUserManagerService guards user-management operations (rename, icon, guest mode, wipe, create user). It throws SecurityException unless the caller's UID equals VirtualCore's own UID. In other words, only the virtual engine process itself (system/root-equivalent inside the virtualized environment) may manage users; any outside binder caller is rejected.

Solutions

  1. Invoke user management through the official VirtualCore facade from within the engine process, not via direct binder calls from a client process.
  2. If you must call remotely, run the call inside the process that hosts VirtualCore so the calling uid matches myUid().
  3. Wrap the call in try-catch for SecurityException and fall back to a request-passing mechanism (e.g. an intent/IPC to the engine) that executes the operation in-process.
  4. Verify the client was properly installed/initialized by VirtualApp so its identity resolves to the engine uid.

Example fix

// before
VUserManagerService.get().createUser("name", 0);
// after
try {
    VUserManagerService.get().createUser("name", 0);
} catch (SecurityException e) {
    // route through the engine process instead
    VirtualCore.get().createUser("name", 0);
}
Defensive patterns

Strategy: try-catch

Validate before calling

boolean canManageUsers = VBinder.getCallingUid() == VirtualCore.get().myUid();
if (!canManageUsers) { /* route request through the engine process instead */ }

Type guard

static boolean isEngineProcess() {
    return VBinder.getCallingUid() == VirtualCore.get().myUid();
}

Try / catch

try {
    userManager.createUser(name, flags);
} catch (SecurityException e) {
    Log.w(TAG, "MANAGE_USERS required, routing through engine", e);
    engineProxy.createUser(name, flags);
}

Prevention

When it happens

Trigger: Calling setUserName, setUserIcon, setGuestEnabled, wipeUser, makeInitialized, or createUser via binder from a process whose VBinder.getCallingUid() != VirtualCore.get().myUid(). Any cross-process caller other than the engine's own process triggers it.

Common situations: Calling VUserManagerService directly from an app inside the virtual container instead of going through VirtualCore/IPackageManager on the engine side; re-implementing binder stubs that pass a foreign calling uid; testing user management from an external shell or another app.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of asLody/VirtualApp@666fefcb5d (2026-09-09). Data as JSON: /api/errors/678102ad07af152e. Report an issue: GitHub.

Appendix: source

Thrown at VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/VUserManagerService.java:168

    public static VUserManagerService get() {
        synchronized (VUserManagerService.class) {
            return sInstance;
        }
    }

    /**
     * Enforces that only the system UID or root's UID or apps that have the
     * {android.Manifest.permission.MANAGE_USERS MANAGE_USERS}
     * permission can make certain calls to the VUserManager.
     *
     * @param message used as message if SecurityException is thrown
     * @throws SecurityException if the caller is not system or root
     */
    private static void checkManageUsersPermission(String message) {
        final int uid = VBinder.getCallingUid();
        if (uid != VirtualCore.get().myUid()) {
            throw new SecurityException("You need MANAGE_USERS permission to: " + message);
        }
    }

    @Override
    public List<VUserInfo> getUsers(boolean excludeDying) {
        //checkManageUsersPermission("query users");
        synchronized (mPackagesLock) {
            ArrayList<VUserInfo> users = new ArrayList<VUserInfo>(mUsers.size());
            for (int i = 0; i < mUsers.size(); i++) {
                VUserInfo ui = mUsers.valueAt(i);
                if (ui.partial) {
                    continue;
                }
                if (!excludeDying || !mRemovingUserIds.contains(ui.id)) {
                    users.add(ui);
                }
            }
            return users;

View on GitHub (pinned to 666fefcb5d)