summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/PackageManagerInternal.java17
-rw-r--r--core/java/android/os/storage/IStorageManager.aidl2
-rw-r--r--core/java/android/os/storage/StorageManager.java30
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java122
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/StorageManagerServiceTest.java209
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));
+ }
+}