diff options
8 files changed, 315 insertions, 40 deletions
diff --git a/core/java/android/os/AppZygote.java b/core/java/android/os/AppZygote.java index 22756934abc0..40cbaf75c02d 100644 --- a/core/java/android/os/AppZygote.java +++ b/core/java/android/os/AppZygote.java @@ -34,6 +34,8 @@ import com.android.internal.annotations.GuardedBy; public class AppZygote { private static final String LOG_TAG = "AppZygote"; + private final int mZygoteUid; + private final Object mLock = new Object(); /** @@ -45,8 +47,9 @@ public class AppZygote { private final ApplicationInfo mAppInfo; - public AppZygote(ApplicationInfo appInfo) { + public AppZygote(ApplicationInfo appInfo, int zygoteUid) { mAppInfo = appInfo; + mZygoteUid = zygoteUid; } /** @@ -94,8 +97,8 @@ public class AppZygote { mZygote = Process.zygoteProcess.startChildZygote( "com.android.internal.os.AppZygoteInit", mAppInfo.processName + "_zygote", - mAppInfo.uid, - mAppInfo.uid, + mZygoteUid, + mZygoteUid, null, // gids 0, // runtimeFlags "app_zygote", // seInfo diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 938b23ca733f..ee56e3d0ad16 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -205,6 +205,24 @@ public class Process { public static final int LAST_APPLICATION_UID = 19999; /** + * First uid used for fully isolated sandboxed processes spawned from an app zygote + * @hide + */ + public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; + + /** + * Number of UIDs we allocate per application zygote + * @hide + */ + public static final int NUM_UIDS_PER_APP_ZYGOTE = 100; + + /** + * Last uid used for fully isolated sandboxed processes spawned from an app zygote + * @hide + */ + public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999; + + /** * First uid used for fully isolated sandboxed processes (with no permissions of their own) * @hide */ @@ -650,7 +668,8 @@ public class Process { /** {@hide} */ public static final boolean isIsolated(int uid) { uid = UserHandle.getAppId(uid); - return uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID; + return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID) + || (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID); } /** diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index f8feb7b4a693..ad8a4d5645bc 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -138,8 +138,7 @@ public final class UserHandle implements Parcelable { */ public static boolean isIsolated(int uid) { if (uid > 0) { - final int appId = getAppId(uid); - return appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID; + return Process.isIsolated(uid); } else { return false; } @@ -294,9 +293,14 @@ public final class UserHandle implements Parcelable { sb.append('u'); sb.append(getUserId(uid)); final int appId = getAppId(uid); - if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) { - sb.append('i'); - sb.append(appId - Process.FIRST_ISOLATED_UID); + if (isIsolated(appId)) { + if (appId > Process.FIRST_ISOLATED_UID) { + sb.append('i'); + sb.append(appId - Process.FIRST_ISOLATED_UID); + } else { + sb.append("ai"); + sb.append(appId - Process.FIRST_APP_ZYGOTE_ISOLATED_UID); + } } else if (appId >= Process.FIRST_APPLICATION_UID) { sb.append('a'); sb.append(appId - Process.FIRST_APPLICATION_UID); @@ -330,9 +334,14 @@ public final class UserHandle implements Parcelable { pw.print('u'); pw.print(getUserId(uid)); final int appId = getAppId(uid); - if (appId >= Process.FIRST_ISOLATED_UID && appId <= Process.LAST_ISOLATED_UID) { - pw.print('i'); - pw.print(appId - Process.FIRST_ISOLATED_UID); + if (isIsolated(appId)) { + if (appId > Process.FIRST_ISOLATED_UID) { + pw.print('i'); + pw.print(appId - Process.FIRST_ISOLATED_UID); + } else { + pw.print("ai"); + pw.print(appId - Process.FIRST_APP_ZYGOTE_ISOLATED_UID); + } } else if (appId >= Process.FIRST_APPLICATION_UID) { pw.print('a'); pw.print(appId - Process.FIRST_APPLICATION_UID); diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java index 2c8e66d010f9..0b329d70f7af 100644 --- a/core/java/com/android/internal/os/WebViewZygoteInit.java +++ b/core/java/com/android/internal/os/WebViewZygoteInit.java @@ -121,7 +121,6 @@ class WebViewZygoteInit { public static void main(String argv[]) { Log.i(TAG, "Starting WebViewZygoteInit"); - WebViewZygoteServer server = new WebViewZygoteServer(); ChildZygoteInit.runZygoteServer(server, argv); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e171736bd9cc..bc21610dd602 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1943,7 +1943,8 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { ProcessRecord app = mProcessList.newProcessRecordLocked(info, info.processName, false, - 0); + 0, + false); app.setPersistent(true); app.pid = MY_PID; app.getWindowProcessController().setPid(MY_PID); @@ -7407,7 +7408,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (app == null) { - app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0); + app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0, false); mProcessList.updateLruProcessLocked(app, false, null); updateOomAdjLocked(); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 896452b4ad62..9898d06837be 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -19,8 +19,6 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; import static android.app.ActivityThread.PROC_START_SEQ_IDENT; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO; -import static android.os.Process.FIRST_ISOLATED_UID; -import static android.os.Process.LAST_ISOLATED_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; import static android.os.Process.getFreeMemory; @@ -83,6 +81,7 @@ import android.util.EventLog; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.util.StatsLog; import android.view.Display; @@ -112,6 +111,7 @@ import java.io.PrintWriter; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.List; /** @@ -368,11 +368,126 @@ public final class ProcessList { final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses = new ArrayMap<AppZygote, ArrayList<ProcessRecord>>(); + final class IsolatedUidRange { + @VisibleForTesting + public final int mFirstUid; + @VisibleForTesting + public final int mLastUid; + + @GuardedBy("ProcessList.this.mService") + private final SparseBooleanArray mUidUsed = new SparseBooleanArray(); + + @GuardedBy("ProcessList.this.mService") + private int mNextUid; + + IsolatedUidRange(int firstUid, int lastUid) { + mFirstUid = firstUid; + mLastUid = lastUid; + mNextUid = firstUid; + } + + @GuardedBy("ProcessList.this.mService") + int allocateIsolatedUidLocked(int userId) { + int uid; + int stepsLeft = (mLastUid - mFirstUid + 1); + for (int i = 0; i < stepsLeft; ++i) { + if (mNextUid < mFirstUid || mNextUid > mLastUid) { + mNextUid = mFirstUid; + } + uid = UserHandle.getUid(userId, mNextUid); + mNextUid++; + if (!mUidUsed.get(uid, false)) { + mUidUsed.put(uid, true); + return uid; + } + } + return -1; + } + + @GuardedBy("ProcessList.this.mService") + void freeIsolatedUidLocked(int uid) { + // Strip out userId + final int appId = UserHandle.getAppId(uid); + mUidUsed.delete(appId); + } + }; + /** - * Counter for assigning isolated process uids, to avoid frequently reusing the - * same ones. + * A class that allocates ranges of isolated UIDs per application, and keeps track of them. */ - int mNextIsolatedProcessUid = 0; + final class IsolatedUidRangeAllocator { + private final int mFirstUid; + private final int mNumUidRanges; + private final int mNumUidsPerRange; + /** + * We map the uid range [mFirstUid, mFirstUid + mNumUidRanges * mNumUidsPerRange) + * back to an underlying bitset of [0, mNumUidRanges) and allocate out of that. + */ + @GuardedBy("ProcessList.this.mService") + private final BitSet mAvailableUidRanges; + @GuardedBy("ProcessList.this.mService") + private final ProcessMap<IsolatedUidRange> mAppRanges = new ProcessMap<IsolatedUidRange>(); + + IsolatedUidRangeAllocator(int firstUid, int lastUid, int numUidsPerRange) { + mFirstUid = firstUid; + mNumUidsPerRange = numUidsPerRange; + mNumUidRanges = (lastUid - firstUid + 1) / numUidsPerRange; + mAvailableUidRanges = new BitSet(mNumUidRanges); + // Mark all as available + mAvailableUidRanges.set(0, mNumUidRanges); + } + + @GuardedBy("ProcessList.this.mService") + IsolatedUidRange getIsolatedUidRangeLocked(ApplicationInfo info) { + return mAppRanges.get(info.processName, info.uid); + } + + @GuardedBy("ProcessList.this.mService") + IsolatedUidRange getOrCreateIsolatedUidRangeLocked(ApplicationInfo info) { + IsolatedUidRange range = getIsolatedUidRangeLocked(info); + if (range == null) { + int uidRangeIndex = mAvailableUidRanges.nextSetBit(0); + if (uidRangeIndex < 0) { + // No free range + return null; + } + mAvailableUidRanges.clear(uidRangeIndex); + int actualUid = mFirstUid + uidRangeIndex * mNumUidsPerRange; + range = new IsolatedUidRange(actualUid, actualUid + mNumUidsPerRange - 1); + mAppRanges.put(info.processName, info.uid, range); + } + return range; + } + + @GuardedBy("ProcessList.this.mService") + void freeUidRangeLocked(ApplicationInfo info) { + // Find the UID range + IsolatedUidRange range = mAppRanges.get(info.processName, info.uid); + if (range != null) { + // Map back to starting uid + final int uidRangeIndex = (range.mFirstUid - mFirstUid) / mNumUidsPerRange; + // Mark it as available in the underlying bitset + mAvailableUidRanges.set(uidRangeIndex); + // And the map + mAppRanges.remove(info.processName, info.uid); + } + } + } + + /** + * The available isolated UIDs for processes that are not spawned from an application zygote. + */ + @VisibleForTesting + IsolatedUidRange mGlobalIsolatedUids = new IsolatedUidRange(Process.FIRST_ISOLATED_UID, + Process.LAST_ISOLATED_UID); + + /** + * An allocator for isolated UID ranges for apps that use an application zygote. + */ + @VisibleForTesting + IsolatedUidRangeAllocator mAppIsolatedUidRangeAllocator = + new IsolatedUidRangeAllocator(Process.FIRST_APP_ZYGOTE_ISOLATED_UID, + Process.LAST_APP_ZYGOTE_ISOLATED_UID, Process.NUM_UIDS_PER_APP_ZYGOTE); /** * Processes that are being forcibly torn down. @@ -1527,12 +1642,20 @@ public final class ProcessList { if (zygoteProcesses.size() == 0) { // Only remove if no longer in use now mAppZygotes.remove(appInfo.processName, appInfo.uid); mAppZygoteProcesses.remove(appZygote); + mAppIsolatedUidRangeAllocator.freeUidRangeLocked(appInfo); appZygote.stopZygote(); } } @GuardedBy("mService") private void removeProcessFromAppZygoteLocked(final ProcessRecord app) { + // Free the isolated uid for this process + final IsolatedUidRange appUidRange = + mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked(app.info); + if (appUidRange != null) { + appUidRange.freeIsolatedUidLocked(app.uid); + } + final AppZygote appZygote = mAppZygotes.get(app.info.processName, app.info.uid); if (appZygote != null) { ArrayList<ProcessRecord> zygoteProcesses = mAppZygoteProcesses.get(appZygote); @@ -1550,7 +1673,12 @@ public final class ProcessList { AppZygote appZygote = mAppZygotes.get(app.info.processName, app.info.uid); final ArrayList<ProcessRecord> zygoteProcessList; if (appZygote == null) { - appZygote = new AppZygote(app.info); + final int userId = UserHandle.getUserId(app.info.uid); + final IsolatedUidRange uidRange = + mAppIsolatedUidRangeAllocator.getIsolatedUidRangeLocked(app.info); + // Allocate an isolated UID out of this range for the Zygote itself + final int zygoteIsolatedUid = uidRange.allocateIsolatedUidLocked(userId); + appZygote = new AppZygote(app.info, zygoteIsolatedUid); mAppZygotes.put(app.info.processName, app.info.uid, appZygote); zygoteProcessList = new ArrayList<ProcessRecord>(); mAppZygoteProcesses.put(appZygote, zygoteProcessList); @@ -1701,8 +1829,9 @@ public final class ProcessList { ? hostingName.flattenToShortString() : null; if (app == null) { + final boolean fromAppZygote = "app_zygote".equals(hostingType); checkSlow(startTime, "startProcess: creating new process record"); - app = newProcessRecordLocked(info, processName, isolated, isolatedUid); + app = newProcessRecordLocked(info, processName, isolated, isolatedUid, fromAppZygote); if (app == null) { Slog.w(TAG, "Failed making new process record for " + processName + "/" + info.uid + " isolated=" + isolated); @@ -2075,29 +2204,31 @@ public final class ProcessList { } @GuardedBy("mService") + private IsolatedUidRange getOrCreateIsolatedUidRangeLocked(ApplicationInfo info, + boolean fromAppZygote) { + if (!fromAppZygote) { + // Allocate an isolated UID from the global range + return mGlobalIsolatedUids; + } else { + return mAppIsolatedUidRangeAllocator.getOrCreateIsolatedUidRangeLocked(info); + } + } + + @GuardedBy("mService") final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, - boolean isolated, int isolatedUid) { + boolean isolated, int isolatedUid, boolean fromAppZygote) { String proc = customProcess != null ? customProcess : info.processName; final int userId = UserHandle.getUserId(info.uid); int uid = info.uid; if (isolated) { if (isolatedUid == 0) { - int stepsLeft = LAST_ISOLATED_UID - FIRST_ISOLATED_UID + 1; - while (true) { - if (mNextIsolatedProcessUid < FIRST_ISOLATED_UID - || mNextIsolatedProcessUid > LAST_ISOLATED_UID) { - mNextIsolatedProcessUid = FIRST_ISOLATED_UID; - } - uid = UserHandle.getUid(userId, mNextIsolatedProcessUid); - mNextIsolatedProcessUid++; - if (mIsolatedProcesses.indexOfKey(uid) < 0) { - // No process for this uid, use it. - break; - } - stepsLeft--; - if (stepsLeft <= 0) { - return null; - } + IsolatedUidRange uidRange = getOrCreateIsolatedUidRangeLocked(info, fromAppZygote); + if (uidRange == null) { + return null; + } + uid = uidRange.allocateIsolatedUidLocked(userId); + if (uid == -1) { + return null; } } else { // Special case for startIsolatedProcess (internal only), where @@ -2165,9 +2296,10 @@ public final class ProcessList { old.uidRecord = null; } mIsolatedProcesses.remove(uid); + mGlobalIsolatedUids.freeIsolatedUidLocked(uid); // Remove the (expected) ProcessRecord from the app zygote final ProcessRecord record = expecting != null ? expecting : old; - if (record != null && record.isolated) { + if (record != null && record.appZygote) { removeProcessFromAppZygoteLocked(record); } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 6f0a562eb819..0d0824a74cdd 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -76,6 +76,7 @@ final class ProcessRecord implements WindowProcessListener { private final ActivityManagerService mService; // where we came from final ApplicationInfo info; // all about the first app in the process final boolean isolated; // true if this is a special isolated process + final boolean appZygote; // true if this is forked from the app zygote final int uid; // uid of process; may be different from 'info' if isolated final int userId; // user of process. final String processName; // name of the process @@ -559,6 +560,8 @@ final class ProcessRecord implements WindowProcessListener { mService = _service; info = _info; isolated = _info.uid != _uid; + appZygote = (UserHandle.getAppId(_uid) >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID + && UserHandle.getAppId(_uid) <= Process.LAST_APP_ZYGOTE_ISOLATED_UID); uid = _uid; userId = UserHandle.getUserId(_uid); processName = _processName; diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java index ea0e095cfc1f..6a10ff487d2d 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -71,6 +71,8 @@ import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import com.android.server.AppOpsService; +import com.android.server.am.ProcessList.IsolatedUidRange; +import com.android.server.am.ProcessList.IsolatedUidRangeAllocator; import com.android.server.wm.ActivityTaskManagerService; import org.junit.After; @@ -294,6 +296,113 @@ public class ActivityManagerServiceTest { } } + private void validateAppZygoteIsolatedUidRange(IsolatedUidRange uidRange) { + assertNotNull(uidRange); + assertTrue(uidRange.mFirstUid >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID + && uidRange.mFirstUid <= Process.LAST_APP_ZYGOTE_ISOLATED_UID); + assertTrue(uidRange.mLastUid >= Process.FIRST_APP_ZYGOTE_ISOLATED_UID + && uidRange.mLastUid <= Process.LAST_APP_ZYGOTE_ISOLATED_UID); + assertTrue(uidRange.mLastUid > uidRange.mFirstUid + && ((uidRange.mLastUid - uidRange.mFirstUid + 1) + == Process.NUM_UIDS_PER_APP_ZYGOTE)); + } + + private void verifyUidRangesNoOverlap(IsolatedUidRange uidRange1, IsolatedUidRange uidRange2) { + IsolatedUidRange lowRange = uidRange1.mFirstUid <= uidRange2.mFirstUid ? uidRange1 : uidRange2; + IsolatedUidRange highRange = lowRange == uidRange1 ? uidRange2 : uidRange1; + + assertTrue(highRange.mFirstUid > lowRange.mLastUid); + } + + @Test + public void testIsolatedUidRangeAllocator() { + final IsolatedUidRangeAllocator allocator = mAms.mProcessList.mAppIsolatedUidRangeAllocator; + + // Create initial range + ApplicationInfo appInfo = new ApplicationInfo(); + appInfo.processName = "com.android.test.app"; + appInfo.uid = 10000; + final IsolatedUidRange range = allocator.getOrCreateIsolatedUidRangeLocked(appInfo); + validateAppZygoteIsolatedUidRange(range); + verifyIsolatedUidAllocator(range); + + // Create a second range + ApplicationInfo appInfo2 = new ApplicationInfo(); + appInfo2.processName = "com.android.test.app2"; + appInfo2.uid = 10001; + IsolatedUidRange range2 = allocator.getOrCreateIsolatedUidRangeLocked(appInfo2); + validateAppZygoteIsolatedUidRange(range2); + verifyIsolatedUidAllocator(range2); + + // Verify ranges don't overlap + verifyUidRangesNoOverlap(range, range2); + + // Free range, reallocate and verify + allocator.freeUidRangeLocked(appInfo2); + range2 = allocator.getOrCreateIsolatedUidRangeLocked(appInfo2); + validateAppZygoteIsolatedUidRange(range2); + verifyUidRangesNoOverlap(range, range2); + verifyIsolatedUidAllocator(range2); + + // Free both, then try to allocate the maximum number of UID ranges + allocator.freeUidRangeLocked(appInfo); + allocator.freeUidRangeLocked(appInfo2); + + int maxNumUidRanges = (Process.LAST_APP_ZYGOTE_ISOLATED_UID + - Process.FIRST_APP_ZYGOTE_ISOLATED_UID + 1) / Process.NUM_UIDS_PER_APP_ZYGOTE; + for (int i = 0; i < maxNumUidRanges; i++) { + appInfo = new ApplicationInfo(); + appInfo.uid = 10000 + i; + appInfo.processName = "com.android.test.app" + Integer.toString(i); + IsolatedUidRange uidRange = allocator.getOrCreateIsolatedUidRangeLocked(appInfo); + validateAppZygoteIsolatedUidRange(uidRange); + verifyIsolatedUidAllocator(uidRange); + } + + // Try to allocate another one and make sure it fails + appInfo = new ApplicationInfo(); + appInfo.uid = 9000; + appInfo.processName = "com.android.test.app.failed"; + IsolatedUidRange failedRange = allocator.getOrCreateIsolatedUidRangeLocked(appInfo); + + assertNull(failedRange); + } + + public void verifyIsolatedUid(ProcessList.IsolatedUidRange range, int uid) { + assertTrue(uid >= range.mFirstUid && uid <= range.mLastUid); + } + + public void verifyIsolatedUidAllocator(ProcessList.IsolatedUidRange range) { + int uid = range.allocateIsolatedUidLocked(0); + verifyIsolatedUid(range, uid); + + int uid2 = range.allocateIsolatedUidLocked(0); + verifyIsolatedUid(range, uid2); + assertTrue(uid2 != uid); + + // Free both + range.freeIsolatedUidLocked(uid); + range.freeIsolatedUidLocked(uid2); + + // Allocate the entire range + for (int i = 0; i < (range.mLastUid - range.mFirstUid + 1); ++i) { + uid = range.allocateIsolatedUidLocked(0); + verifyIsolatedUid(range, uid); + } + + // Ensure the next one fails + uid = range.allocateIsolatedUidLocked(0); + assertEquals(uid, -1); + } + + @Test + public void testGlobalIsolatedUidAllocator() { + final IsolatedUidRange globalUidRange = mAms.mProcessList.mGlobalIsolatedUids; + assertEquals(globalUidRange.mFirstUid, Process.FIRST_ISOLATED_UID); + assertEquals(globalUidRange.mLastUid, Process.LAST_ISOLATED_UID); + verifyIsolatedUidAllocator(globalUidRange); + } + @Test public void testBlockStateForUid() { final UidRecord uidRec = new UidRecord(TEST_UID, null /* atmInternal */); |