diff options
6 files changed, 362 insertions, 29 deletions
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index a2a483280123..823d9951862c 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -31,6 +31,7 @@ import android.util.SparseArray; import com.android.internal.util.function.TriFunction; +import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -680,4 +681,20 @@ public abstract class PackageManagerInternal { * @return a SparseArray mapping from appId to it's sharedUserId. */ public abstract SparseArray<String> getAppsWithSharedUserIds(); + + /** + * Return if device is currently in a "core" boot environment, typically + * used to support full-disk encryption. Only apps marked with + * {@code coreApp} attribute are available. + */ + public abstract boolean isOnlyCoreApps(); + + /** + * Make a best-effort attempt to provide the requested free disk space by + * deleting cached files. + * + * @throws IOException if the request was unable to be fulfilled. + */ + public abstract void freeStorage(String volumeUuid, long bytes, int storageFlags) + throws IOException; } diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 55a202fd3a66..5c2d41e1aebc 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -187,4 +187,6 @@ interface IStorageManager { void allocateBytes(String volumeUuid, long bytes, int flags, String callingPackage) = 78; void runIdleMaintenance() = 79; void abortIdleMaintenance() = 80; + String translateAppToSystem(String path, String packageName, int userId) = 81; + String translateSystemToApp(String path, String packageName, int userId) = 82; } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index b24dda002caa..5a1ea68b65da 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1486,6 +1486,36 @@ public class StorageManager { return path; } + /** + * Translate given shared storage path from a path in an app sandbox + * namespace to a path in the system namespace. + * + * @hide + */ + public File translateAppToSystem(File file, String packageName) { + try { + return new File(mStorageManager.translateAppToSystem(file.getAbsolutePath(), + packageName, mContext.getUserId())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Translate given shared storage path from a path in the system namespace + * to a path in an app sandbox namespace. + * + * @hide + */ + public File translateSystemToApp(File file, String packageName) { + try { + return new File(mStorageManager.translateSystemToApp(file.getAbsolutePath(), + packageName, mContext.getUserId())); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** {@hide} */ @VisibleForTesting public @NonNull ParcelFileDescriptor openProxyFileDescriptor( diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 42157cc562b9..30a8557f6971 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -24,12 +24,14 @@ import static android.os.storage.OnObbStateChangeListener.ERROR_NOT_MOUNTED; import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED; import static android.os.storage.OnObbStateChangeListener.MOUNTED; import static android.os.storage.OnObbStateChangeListener.UNMOUNTED; + import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; import static com.android.internal.util.XmlUtils.readStringAttribute; import static com.android.internal.util.XmlUtils.writeIntAttribute; import static com.android.internal.util.XmlUtils.writeLongAttribute; import static com.android.internal.util.XmlUtils.writeStringAttribute; + import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; import static org.xmlpull.v1.XmlPullParser.START_TAG; @@ -112,6 +114,7 @@ import android.util.TimeUtils; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IMediaContainerService; import com.android.internal.os.AppFuseMount; import com.android.internal.os.BackgroundThread; @@ -125,11 +128,13 @@ import com.android.internal.util.HexDump; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.widget.LockPatternUtils; -import com.android.server.pm.PackageManagerService; import com.android.server.storage.AppFuseBridge; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal.ScreenObserver; +import libcore.io.IoUtils; +import libcore.util.EmptyArray; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -159,14 +164,13 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; -import libcore.io.IoUtils; -import libcore.util.EmptyArray; - /** * Service responsible for various storage media. Connects to {@code vold} to * watch for and manage dynamically added storage, such as SD cards and USB mass @@ -182,8 +186,8 @@ class StorageManagerService extends IStorageManager.Stub private static final String ZRAM_ENABLED_PROPERTY = "persist.sys.zram_enabled"; - private static final String ISOLATED_STORAGE_PROPERTY = - "persist.sys.isolated_storage"; + private static final boolean ENABLE_ISOLATED_STORAGE = SystemProperties + .getBoolean(StorageManager.PROP_ISOLATED_STORAGE, false); private static final String SHARED_SANDBOX_ID_PREFIX = "shared:"; @@ -448,8 +452,8 @@ class StorageManagerService extends IStorageManager.Stub private volatile boolean mDaemonConnected = false; private volatile boolean mSecureKeyguardShowing = true; - private PackageManagerService mPms; private PackageManagerInternal mPmInternal; + private UserManagerInternal mUmInternal; private final Callbacks mCallbacks; private final LockPatternUtils mLockPatternUtils; @@ -824,8 +828,8 @@ class StorageManagerService extends IStorageManager.Stub // System user does not have media provider, so skip. if (user.isSystemOnly()) continue; - final ProviderInfo provider = mPms.resolveContentProvider(MediaStore.AUTHORITY, - PackageManager.MATCH_DIRECT_BOOT_AWARE + final ProviderInfo provider = mPmInternal.resolveContentProvider( + MediaStore.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, user.id); if (provider != null) { @@ -1180,7 +1184,7 @@ class StorageManagerService extends IStorageManager.Stub @GuardedBy("mLock") private void onVolumeCreatedLocked(VolumeInfo vol) { - if (mPms.isOnlyCoreApps()) { + if (mPmInternal.isOnlyCoreApps()) { Slog.d(TAG, "System booted in core-only mode; ignoring volume " + vol.getId()); return; } @@ -1428,9 +1432,8 @@ class StorageManagerService extends IStorageManager.Stub mCallbacks = new Callbacks(FgThread.get().getLooper()); mLockPatternUtils = new LockPatternUtils(mContext); - // XXX: This will go away soon in favor of IMountServiceObserver - mPms = (PackageManagerService) ServiceManager.getService("package"); mPmInternal = LocalServices.getService(PackageManagerInternal.class); + mUmInternal = LocalServices.getService(UserManagerInternal.class); HandlerThread hthread = new HandlerThread(TAG); hthread.start(); @@ -1484,14 +1487,13 @@ class StorageManagerService extends IStorageManager.Stub connect(); } - private void collectPackagesInfo() { - if (!SystemProperties.getBoolean(ISOLATED_STORAGE_PROPERTY, false)) { - return; - } + @VisibleForTesting + void collectPackagesInfo() { + if (!ENABLE_ISOLATED_STORAGE) return; + resetPackageData(); final SparseArray<String> sharedUserIds = mPmInternal.getAppsWithSharedUserIds(); - final int[] userIds = LocalServices.getService( - UserManagerInternal.class).getUserIds(); + final int[] userIds = mUmInternal.getUserIds(); for (int userId : userIds) { final List<ApplicationInfo> appInfos = mContext.getPackageManager().getInstalledApplicationsAsUser( @@ -1525,9 +1527,8 @@ class StorageManagerService extends IStorageManager.Stub return sharedUserId == null ? packageName : SHARED_SANDBOX_ID_PREFIX + sharedUserId; } private void pushPackagesInfo() throws RemoteException { - if (!SystemProperties.getBoolean(ISOLATED_STORAGE_PROPERTY, false)) { - return; - } + if (!ENABLE_ISOLATED_STORAGE) return; + // Arrays to fill up from {@link #mAppIds} final String[] allPackageNames; final int[] appIdsForPackages; @@ -1565,9 +1566,8 @@ class StorageManagerService extends IStorageManager.Stub } private String[] getPackagesArrayForUser(int userId) { - if (!SystemProperties.getBoolean(ISOLATED_STORAGE_PROPERTY, false)) { - return new String[0]; - } + if (!ENABLE_ISOLATED_STORAGE) return EmptyArray.STRING; + synchronized (mPackagesLock) { return getPackagesForUserPL(userId).toArray(new String[0]); } @@ -2267,7 +2267,7 @@ class StorageManagerService extends IStorageManager.Stub return false; } - final int packageUid = mPms.getPackageUid(packageName, + final int packageUid = mPmInternal.getPackageUid(packageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callerUid)); if (DEBUG_OBB) { @@ -3091,7 +3091,7 @@ class StorageManagerService extends IStorageManager.Stub bytes += storage.getStorageLowBytes(path); } - mPms.freeStorage(volumeUuid, bytes, flags); + mPmInternal.freeStorage(volumeUuid, bytes, flags); } catch (IOException e) { throw new ParcelableException(e); } finally { @@ -3099,6 +3099,72 @@ class StorageManagerService extends IStorageManager.Stub } } + private static final Pattern PATTERN_TRANSLATE = Pattern.compile( + "(?i)^(/storage/[^/]+/(?:[0-9]+/)?)(.*)"); + + @Override + public String translateAppToSystem(String path, String packageName, int userId) { + return translateInternal(path, packageName, userId, true); + } + + @Override + public String translateSystemToApp(String path, String packageName, int userId) { + return translateInternal(path, packageName, userId, false); + } + + private String translateInternal(String path, String packageName, int userId, + boolean toSystem) { + if (!ENABLE_ISOLATED_STORAGE) return path; + + if (path.contains("/../")) { + throw new SecurityException("Shady looking path " + path); + } + + final int uid = mPmInternal.getPackageUid(packageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); + final String sandboxId; + synchronized (mPackagesLock) { + sandboxId = mSandboxIds.get(UserHandle.getAppId(uid)); + } + if (uid < 0 || sandboxId == null) { + throw new IllegalArgumentException("Unknown package " + packageName); + } + + final Matcher m = PATTERN_TRANSLATE.matcher(path); + if (m.matches()) { + final String device = m.group(1); + final String devicePath = m.group(2); + + // Does path belong to any packages belonging to this UID? If so, + // they get to go straight through to legacy paths. + final String[] pkgs = mContext.getPackageManager().getPackagesForUid(uid); + for (String pkg : pkgs) { + if (devicePath.startsWith("Android/data/" + pkg + "/") || + devicePath.startsWith("Android/media/" + pkg + "/") || + devicePath.startsWith("Android/obb/" + pkg + "/")) { + return path; + } + } + + if (toSystem) { + // Everything else goes into sandbox. + return device + "Android/sandbox/" + sandboxId.replace(':', '/') + "/" + devicePath; + } else { + // Does path belong to this sandbox? If so, leave sandbox. + final String sandboxPrefix = "Android/sandbox/" + sandboxId.replace(':', '/') + "/"; + if (devicePath.startsWith(sandboxPrefix)) { + return device + devicePath.substring(sandboxPrefix.length()); + } + + // Path isn't valid inside sandbox! + throw new SecurityException( + "Path " + path + " isn't valid inside sandbox " + sandboxId); + } + } + + return path; + } + private void addObbStateLocked(ObbState obbState) throws RemoteException { final IBinder binder = obbState.getBinder(); List<ObbState> obbStates = mObbMounts.get(binder); @@ -3764,9 +3830,7 @@ class StorageManagerService extends IStorageManager.Stub @Override public void onExternalStoragePolicyChanged(int uid, String packageName) { // No runtime storage permissions in isolated storage world, so nothing to do here. - if (SystemProperties.getBoolean(ISOLATED_STORAGE_PROPERTY, false)) { - return; - } + if (!ENABLE_ISOLATED_STORAGE) return; final int mountMode = getExternalStorageMountMode(uid, packageName); remountUidExternalStorage(uid, mountMode); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6df030893184..8247e4317cf9 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -24451,6 +24451,17 @@ public class PackageManagerService extends IPackageManager.Stub return getAppsWithSharedUserIdsLocked(); } } + + @Override + public boolean isOnlyCoreApps() { + return PackageManagerService.this.isOnlyCoreApps(); + } + + @Override + public void freeStorage(String volumeUuid, long bytes, int storageFlags) + throws IOException { + PackageManagerService.this.freeStorage(volumeUuid, bytes, storageFlags); + } } private SparseArray<String> getAppsWithSharedUserIdsLocked() { diff --git a/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java new file mode 100644 index 000000000000..43438b99edef --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; +import android.os.UserHandle; +import android.os.UserManagerInternal; +import android.os.storage.StorageManagerInternal; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.SparseArray; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class StorageManagerServiceTest { + + private StorageManagerService mService; + + @Mock private Context mContext; + @Mock private PackageManager mPm; + @Mock private PackageManagerInternal mPmi; + @Mock private UserManagerInternal mUmi; + + private static final String PKG_GREY = "com.grey"; + private static final String PKG_RED = "com.red"; + private static final String PKG_BLUE = "com.blue"; + + private static final int UID_GREY = 10000; + private static final int UID_COLORS = 10001; + + private static final String NAME_COLORS = "colors"; + + private static ApplicationInfo buildApplicationInfo(String packageName, int uid) { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = packageName; + ai.uid = uid; + return ai; + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + LocalServices.removeServiceForTest(StorageManagerInternal.class); + + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mPmi); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mUmi); + + when(mContext.getPackageManager()).thenReturn(mPm); + + when(mUmi.getUserIds()).thenReturn(new int[] { 0 }); + + { + final SparseArray<String> res = new SparseArray<>(); + res.put(UID_COLORS, NAME_COLORS); + when(mPmi.getAppsWithSharedUserIds()).thenReturn(res); + } + + { + final List<ApplicationInfo> res = new ArrayList<>(); + res.add(buildApplicationInfo(PKG_GREY, UID_GREY)); + res.add(buildApplicationInfo(PKG_RED, UID_COLORS)); + res.add(buildApplicationInfo(PKG_BLUE, UID_COLORS)); + when(mPm.getInstalledApplicationsAsUser(anyInt(), anyInt())).thenReturn(res); + } + + when(mPmi.getPackageUid(eq(PKG_GREY), anyInt(), anyInt())).thenReturn(UID_GREY); + when(mPmi.getPackageUid(eq(PKG_RED), anyInt(), anyInt())).thenReturn(UID_COLORS); + when(mPmi.getPackageUid(eq(PKG_BLUE), anyInt(), anyInt())).thenReturn(UID_COLORS); + + when(mPm.getPackagesForUid(eq(UID_GREY))).thenReturn(new String[] { PKG_GREY }); + when(mPm.getPackagesForUid(eq(UID_COLORS))).thenReturn(new String[] { PKG_RED, PKG_BLUE }); + + mService = new StorageManagerService(mContext); + mService.collectPackagesInfo(); + } + + @Test + public void testNone() throws Exception { + assertTranslation( + "/dev/null", + "/dev/null", PKG_GREY); + assertTranslation( + "/dev/null", + "/dev/null", PKG_RED); + } + + @Test + public void testPrimary() throws Exception { + assertTranslation( + "/storage/emulated/0/Android/sandbox/com.grey/foo.jpg", + "/storage/emulated/0/foo.jpg", PKG_GREY); + assertTranslation( + "/storage/emulated/0/Android/sandbox/shared/colors/foo.jpg", + "/storage/emulated/0/foo.jpg", PKG_RED); + } + + @Test + public void testSecondary() throws Exception { + assertTranslation( + "/storage/0000-0000/Android/sandbox/com.grey/foo/bar.jpg", + "/storage/0000-0000/foo/bar.jpg", PKG_GREY); + assertTranslation( + "/storage/0000-0000/Android/sandbox/shared/colors/foo/bar.jpg", + "/storage/0000-0000/foo/bar.jpg", PKG_RED); + } + + @Test + public void testLegacy() throws Exception { + // Accessing their own paths goes straight through + assertTranslation( + "/storage/emulated/0/Android/data/com.grey/foo.jpg", + "/storage/emulated/0/Android/data/com.grey/foo.jpg", PKG_GREY); + + // Accessing other package paths goes into sandbox + assertTranslation( + "/storage/emulated/0/Android/sandbox/shared/colors/" + + "Android/data/com.grey/foo.jpg", + "/storage/emulated/0/Android/data/com.grey/foo.jpg", PKG_RED); + } + + @Test + public void testLegacyShared() throws Exception { + // Accessing their own paths goes straight through + assertTranslation( + "/storage/emulated/0/Android/data/com.red/foo.jpg", + "/storage/emulated/0/Android/data/com.red/foo.jpg", PKG_RED); + assertTranslation( + "/storage/emulated/0/Android/data/com.red/foo.jpg", + "/storage/emulated/0/Android/data/com.red/foo.jpg", PKG_BLUE); + + // Accessing other package paths goes into sandbox + assertTranslation( + "/storage/emulated/0/Android/sandbox/com.grey/" + + "Android/data/com.red/foo.jpg", + "/storage/emulated/0/Android/data/com.red/foo.jpg", PKG_GREY); + } + + @Test + public void testSecurity() throws Exception { + // Shady paths should throw + try { + mService.translateAppToSystem( + "/storage/emulated/0/../foo.jpg", + PKG_GREY, UserHandle.USER_SYSTEM); + fail(); + } catch (SecurityException expected) { + } + + // Sandboxes can't see system paths + try { + mService.translateSystemToApp( + "/storage/emulated/0/foo.jpg", + PKG_GREY, UserHandle.USER_SYSTEM); + fail(); + } catch (SecurityException expected) { + } + + // Sandboxes can't see paths in other sandboxes + try { + mService.translateSystemToApp( + "/storage/emulated/0/Android/sandbox/shared/colors/foo.jpg", + PKG_GREY, UserHandle.USER_SYSTEM); + fail(); + } catch (SecurityException expected) { + } + } + + private void assertTranslation(String system, String sandbox, String packageName) + throws Exception { + assertEquals(system, + mService.translateAppToSystem(sandbox, packageName, UserHandle.USER_SYSTEM)); + assertEquals(sandbox, + mService.translateSystemToApp(system, packageName, UserHandle.USER_SYSTEM)); + } +} |