summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/Process.java15
-rw-r--r--core/java/android/os/ZygoteProcess.java35
-rw-r--r--core/java/com/android/internal/os/Zygote.java26
-rw-r--r--core/java/com/android/internal/os/ZygoteArguments.java8
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java2
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp295
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java27
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java62
-rw-r--r--services/core/java/com/android/server/pm/Installer.java24
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java12
11 files changed, 488 insertions, 24 deletions
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index ebb2071ead7e..6ae188a8fc3d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -22,10 +22,13 @@ import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.system.Os;
import android.system.OsConstants;
+import android.util.Pair;
import android.webkit.WebViewZygote;
import dalvik.system.VMRuntime;
+import java.util.Map;
+
/**
* Tools for managing OS processes.
*/
@@ -521,6 +524,8 @@ public class Process {
* @param isTopApp whether the process starts for high priority application.
* @param disabledCompatChanges null-ok list of disabled compat changes for the process being
* started.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
@@ -541,11 +546,14 @@ public class Process {
@Nullable String packageName,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] zygoteArgs) {
return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ true, isTopApp, disabledCompatChanges, zygoteArgs);
+ /*useUsapPool=*/ true, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, zygoteArgs);
}
/** @hide */
@@ -563,10 +571,13 @@ public class Process {
@Nullable String packageName,
@Nullable long[] disabledCompatChanges,
@Nullable String[] zygoteArgs) {
+ // Webview zygote can't access app private data files, so doesn't need to know its data
+ // info.
return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, packageName,
- /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges, zygoteArgs);
+ /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges,
+ /* pkgDataInfoMap */ null, zygoteArgs);
}
/**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index d17a5e026880..d32bd26c2bb2 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -39,6 +40,7 @@ import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
/*package*/ class ZygoteStartFailedEx extends Exception {
@@ -310,6 +312,8 @@ public class ZygoteProcess {
* started.
* @param zygoteArgs Additional arguments to supply to the zygote process.
* @param isTopApp Whether the process starts for high priority application.
+ * @param pkgDataInfoMap Map from related package names to private data directory
+ * volume UUID and inode number.
*
* @return An object that describes the result of the attempt to start the process.
* @throws RuntimeException on fatal start failure
@@ -328,6 +332,8 @@ public class ZygoteProcess {
boolean useUsapPool,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] zygoteArgs) {
// TODO (chriswailes): Is there a better place to check this value?
if (fetchUsapPoolEnabledPropWithMinInterval()) {
@@ -338,7 +344,8 @@ public class ZygoteProcess {
return startViaZygote(processClass, niceName, uid, gid, gids,
runtimeFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
- packageName, useUsapPool, isTopApp, disabledCompatChanges, zygoteArgs);
+ packageName, useUsapPool, isTopApp, disabledCompatChanges,
+ pkgDataInfoMap, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
@@ -539,6 +546,8 @@ public class ZygoteProcess {
* @param packageName null-ok the name of the package this process belongs to.
* @param isTopApp Whether the process starts for high priority application.
* @param disabledCompatChanges a list of disabled compat changes for the process being started.
+ * @param pkgDataInfoMap Map from related package names to private data directory volume UUID
+ * and inode number.
* @param extraArgs Additional arguments to supply to the zygote process.
* @return An object that describes the result of the attempt to start the process.
* @throws ZygoteStartFailedEx if process start failed for any reason
@@ -559,6 +568,8 @@ public class ZygoteProcess {
boolean useUsapPool,
boolean isTopApp,
@Nullable long[] disabledCompatChanges,
+ @Nullable Map<String, Pair<String, Long>>
+ pkgDataInfoMap,
@Nullable String[] extraArgs)
throws ZygoteStartFailedEx {
ArrayList<String> argsForZygote = new ArrayList<>();
@@ -635,6 +646,24 @@ public class ZygoteProcess {
if (isTopApp) {
argsForZygote.add(Zygote.START_AS_TOP_APP_ARG);
}
+ if (pkgDataInfoMap != null && pkgDataInfoMap.size() > 0) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Zygote.PKG_DATA_INFO_MAP);
+ sb.append("=");
+ boolean started = false;
+ for (Map.Entry<String, Pair<String, Long>> entry : pkgDataInfoMap.entrySet()) {
+ if (started) {
+ sb.append(',');
+ }
+ started = true;
+ sb.append(entry.getKey());
+ sb.append(',');
+ sb.append(entry.getValue().first);
+ sb.append(',');
+ sb.append(entry.getValue().second);
+ }
+ argsForZygote.add(sb.toString());
+ }
if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
StringBuilder sb = new StringBuilder();
@@ -1182,12 +1211,14 @@ public class ZygoteProcess {
Process.ProcessStartResult result;
try {
+ // As app zygote is for generating isolated process, at the end it can't access
+ // apps data, so doesn't need to its data info.
result = startViaZygote(processClass, niceName, uid, gid,
gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo,
abi, instructionSet, null /* appDataDir */, null /* invokeWith */,
true /* startChildZygote */, null /* packageName */,
false /* useUsapPool */, false /* isTopApp */,
- null /* disabledCompatChanges */, extraArgs);
+ null /* disabledCompatChanges */, null /* pkgDataInfoMap */, extraArgs);
} catch (ZygoteStartFailedEx ex) {
throw new RuntimeException("Starting child-zygote through Zygote failed", ex);
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 2b988c155412..c390a518bb22 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -151,6 +151,9 @@ public final class Zygote {
/** Make the new process have top application priority. */
public static final String START_AS_TOP_APP_ARG = "--is-top-app";
+ /** List of packages with the same uid, and its app data info: volume uuid and inode. */
+ public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
+
/**
* An extraArg passed when a zygote process is forking a child-zygote, specifying a name
* in the abstract socket namespace. This socket name is what the new child zygote
@@ -254,6 +257,8 @@ public final class Zygote {
* @param instructionSet null-ok the instruction set to use.
* @param appDataDir null-ok the data directory of the app.
* @param isTopApp true if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * info: volume uuid and inode.
*
* @return 0 if this is the child, pid of the child
* if this is the parent, or -1 on error.
@@ -261,12 +266,13 @@ public final class Zygote {
static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
- int targetSdkVersion, boolean isTopApp) {
+ int targetSdkVersion, boolean isTopApp, String[] pkgDataInfoList) {
ZygoteHooks.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
- fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp);
+ fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Zygote.disableExecuteOnly(targetSdkVersion);
@@ -286,7 +292,7 @@ public final class Zygote {
private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
- String appDataDir, boolean isTopApp);
+ String appDataDir, boolean isTopApp, String[] pkgDataInfoList);
/**
* Specialize an unspecialized app process. The current VM must have been started
@@ -309,12 +315,16 @@ public final class Zygote {
* @param instructionSet null-ok The instruction set to use.
* @param appDataDir null-ok The data directory of the app.
* @param isTopApp True if the process is for top (high priority) application.
+ * @param pkgDataInfoList A list that stores related packages and its app data
+ * info: volume uuid and inode.
*/
private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName,
- boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp) {
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList) {
nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
- niceName, startChildZygote, instructionSet, appDataDir, isTopApp);
+ niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
+ pkgDataInfoList);
// Enable tracing as soon as possible for the child process.
Trace.setTracingEnabled(true, runtimeFlags);
@@ -336,7 +346,8 @@ public final class Zygote {
private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
- boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp);
+ boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
+ String[] pkgDataInfoList);
/**
* Called to do any initialization before starting an application.
@@ -665,7 +676,8 @@ public final class Zygote {
specializeAppProcess(args.mUid, args.mGid, args.mGids,
args.mRuntimeFlags, rlimits, args.mMountExternal,
args.mSeInfo, args.mNiceName, args.mStartChildZygote,
- args.mInstructionSet, args.mAppDataDir, args.mIsTopApp);
+ args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
+ args.mPkgDataInfoList);
disableExecuteOnly(args.mTargetSdkVersion);
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 54b2a2063451..d3499541a3a3 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -221,6 +221,12 @@ class ZygoteArguments {
long[] mDisabledCompatChanges = null;
/**
+ * A list that stores all related packages and its data info: volume uuid and inode.
+ * Null if it does need to do app data isolation.
+ */
+ String[] mPkgDataInfoList;
+
+ /**
* Constructs instance and parses args
*
* @param args zygote command-line args
@@ -437,6 +443,8 @@ class ZygoteArguments {
for (int i = 0; i < length; i++) {
mDisabledCompatChanges[i] = Long.parseLong(params[i]);
}
+ } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
+ mPkgDataInfoList = getAssignmentList(arg);
} else {
break;
}
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 3111b6ff0f72..9c6a288372c6 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -258,7 +258,7 @@ class ZygoteConnection {
parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion,
- parsedArgs.mIsTopApp);
+ parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList);
try {
if (pid == 0) {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 0ca0dc857545..df5b02c22a2d 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -31,6 +31,8 @@
// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
#include <sys/mount.h>
#include <linux/fs.h>
+#include <sys/types.h>
+#include <dirent.h>
#include <array>
#include <atomic>
@@ -40,6 +42,7 @@
#include <sstream>
#include <string>
#include <string_view>
+#include <unordered_set>
#include <android/fdsan.h>
#include <arpa/inet.h>
@@ -51,6 +54,7 @@
#include <mntent.h>
#include <paths.h>
#include <signal.h>
+#include <stdio.h>
#include <stdlib.h>
#include <sys/capability.h>
#include <sys/cdefs.h>
@@ -159,6 +163,17 @@ static std::atomic_uint32_t gUsapPoolCount = 0;
*/
static int gUsapPoolEventFD = -1;
+static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751;
+
+/**
+ * Property to control if app data isolation is enabled.
+ */
+static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+ "persist.zygote.app_data_isolation";
+
+static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000;
+static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF;
+
/**
* The maximum value that the gUSAPPoolSizeMax variable may take. This value
* is a mirror of ZygoteServer.USAP_POOL_SIZE_MAX_LIMIT
@@ -663,7 +678,7 @@ static int UnmountTree(const char* path) {
return 0;
}
-static void CreateDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
+static void PrepareDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
fail_fn_t fail_fn) {
if (fs_prepare_dir(dir.c_str(), mode, uid, gid) != 0) {
fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s: %s",
@@ -671,6 +686,16 @@ static void CreateDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
}
}
+static void PrepareDirIfNotPresent(const std::string& dir, mode_t mode, uid_t uid, gid_t gid,
+ fail_fn_t fail_fn) {
+ struct stat sb;
+ if (TEMP_FAILURE_RETRY(stat(dir.c_str(), &sb)) != -1) {
+ // Directory exists already
+ return;
+ }
+ PrepareDir(dir, mode, uid, gid, fail_fn);
+}
+
static void BindMount(const std::string& source_dir, const std::string& target_dir,
fail_fn_t fail_fn) {
if (TEMP_FAILURE_RETRY(mount(source_dir.c_str(), target_dir.c_str(), nullptr,
@@ -680,6 +705,15 @@ static void BindMount(const std::string& source_dir, const std::string& target_d
}
}
+static void MountAppDataTmpFs(const std::string& target_dir,
+ fail_fn_t fail_fn) {
+ if (TEMP_FAILURE_RETRY(mount("tmpfs", target_dir.c_str(), "tmpfs",
+ MS_NOSUID | MS_NODEV | MS_NOEXEC, "uid=0,gid=0,mode=0751")) == -1) {
+ fail_fn(CREATE_ERROR("Failed to mount tmpfs to %s: %s",
+ target_dir.c_str(), strerror(errno)));
+ }
+}
+
// Create a private mount namespace and bind mount appropriate emulated
// storage for the given user.
static void MountEmulatedStorage(uid_t uid, jint mount_mode,
@@ -712,7 +746,7 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode,
const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
bool isFuse = GetBoolProperty(kPropFuse, false);
- CreateDir(user_source, 0751, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDir(user_source, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
if (isFuse) {
if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH || mount_mode ==
@@ -1016,6 +1050,231 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server,
return pid;
}
+// Create an app data directory over tmpfs overlayed CE / DE storage, and bind mount it
+// from the actual app data directory in data mirror.
+static void createAndMountAppData(std::string_view package_name,
+ std::string_view mirror_pkg_dir_name, std::string_view mirror_data_path,
+ std::string_view actual_data_path, fail_fn_t fail_fn) {
+
+ char mirrorAppDataPath[PATH_MAX];
+ char actualAppDataPath[PATH_MAX];
+ snprintf(mirrorAppDataPath, PATH_MAX, "%s/%s", mirror_data_path.data(),
+ mirror_pkg_dir_name.data());
+ snprintf(actualAppDataPath, PATH_MAX, "%s/%s", actual_data_path.data(), package_name.data());
+
+ PrepareDir(actualAppDataPath, 0700, AID_ROOT, AID_ROOT, fail_fn);
+
+ // Bind mount from original app data directory in mirror.
+ BindMount(mirrorAppDataPath, actualAppDataPath, fail_fn);
+}
+
+// Get the directory name stored in /data/data. If device is unlocked it should be the same as
+// package name, otherwise it will be an encrypted name but with same inode number.
+static std::string getAppDataDirName(std::string_view parent_path, std::string_view package_name,
+ long long ce_data_inode, fail_fn_t fail_fn) {
+ // Check if directory exists
+ char tmpPath[PATH_MAX];
+ snprintf(tmpPath, PATH_MAX, "%s/%s", parent_path.data(), package_name.data());
+ struct stat s;
+ int err = stat(tmpPath, &s);
+ if (err == 0) {
+ // Directory exists, so return the directory name
+ return package_name.data();
+ } else {
+ if (errno != ENOENT) {
+ fail_fn(CREATE_ERROR("Unexpected error in getAppDataDirName: %s", strerror(errno)));
+ return nullptr;
+ }
+ // Directory doesn't exist, try to search the name from inode
+ DIR* dir = opendir(parent_path.data());
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
+ }
+ struct dirent* ent;
+ while ((ent = readdir(dir))) {
+ if (ent->d_ino == ce_data_inode) {
+ closedir(dir);
+ return ent->d_name;
+ }
+ }
+ closedir(dir);
+
+ // Fallback due to b/145989852, ce_data_inode stored in package manager may be corrupted
+ // if ino_t is 32 bits.
+ ino_t fixed_ce_data_inode = 0;
+ if ((ce_data_inode & UPPER_HALF_WORD_MASK) == UPPER_HALF_WORD_MASK) {
+ fixed_ce_data_inode = ce_data_inode & LOWER_HALF_WORD_MASK;
+ } else if ((ce_data_inode & LOWER_HALF_WORD_MASK) == LOWER_HALF_WORD_MASK) {
+ fixed_ce_data_inode = ((ce_data_inode >> 32) & LOWER_HALF_WORD_MASK);
+ }
+ if (fixed_ce_data_inode != 0) {
+ dir = opendir(parent_path.data());
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
+ }
+ while ((ent = readdir(dir))) {
+ if (ent->d_ino == fixed_ce_data_inode) {
+ long long d_ino = ent->d_ino;
+ ALOGW("Fallback success inode %lld -> %lld", ce_data_inode, d_ino);
+ closedir(dir);
+ return ent->d_name;
+ }
+ }
+ closedir(dir);
+ }
+ // Fallback done
+
+ fail_fn(CREATE_ERROR("Unable to find %s:%lld in %s", package_name.data(),
+ ce_data_inode, parent_path.data()));
+ return nullptr;
+ }
+}
+
+// Isolate app's data directory, by mounting a tmpfs on CE DE storage,
+// and create and bind mount app data in related_packages.
+static void isolateAppDataPerPackage(int userId, std::string_view package_name,
+ std::string_view volume_uuid, long long ce_data_inode, std::string_view actualCePath,
+ std::string_view actualDePath, fail_fn_t fail_fn) {
+
+ char mirrorCePath[PATH_MAX];
+ char mirrorDePath[PATH_MAX];
+ char mirrorCeParent[PATH_MAX];
+ snprintf(mirrorCeParent, PATH_MAX, "/data_mirror/data_ce/%s", volume_uuid.data());
+ snprintf(mirrorCePath, PATH_MAX, "%s/%d", mirrorCeParent, userId);
+ snprintf(mirrorDePath, PATH_MAX, "/data_mirror/data_de/%s/%d", volume_uuid.data(), userId);
+
+ createAndMountAppData(package_name, package_name, mirrorDePath, actualDePath, fail_fn);
+
+ std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn);
+ createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn);
+}
+
+/**
+ * Make other apps data directory not visible in CE, DE storage.
+ *
+ * Apps without app data isolation can detect if another app is installed on system,
+ * by "touching" other apps data directory like /data/data/com.whatsapp, if it returns
+ * "Permission denied" it means apps installed, otherwise it returns "File not found".
+ * Traditional file permissions or SELinux can only block accessing those directories but
+ * can't fix fingerprinting like this.
+ * We fix it by "overlaying" data directory, and only relevant app data packages exists
+ * in data directories.
+ *
+ * Steps:
+ * 1). Collect a list of all related apps (apps with same uid and whitelisted apps) data info
+ * (package name, data stored volume uuid, and inode number of its CE data directory)
+ * 2). Mount tmpfs on /data/data, /data/user(_de) and /mnt/expand, so apps no longer
+ * able to access apps data directly.
+ * 3). For each related app, create its app data directory and bind mount the actual content
+ * from apps data mirror directory. This works on both CE and DE storage, as DE storage
+ * is always available even storage is FBE locked, while we use inode number to find
+ * the encrypted DE directory in mirror so we can still bind mount it successfully.
+ *
+ * Example:
+ * 0). Assuming com.android.foo CE data is stored in /data/data and no shared uid
+ * 1). Mount a tmpfs on /data/data, /data/user, /data/user_de, /mnt/expand
+ * List = ["com.android.foo", "null" (volume uuid "null"=default),
+ * 123456 (inode number)]
+ * 2). On DE storage, we create a directory /data/user_de/0/com.com.android.foo, and bind
+ * mount (in the app's mount namespace) it from /data_mirror/data_de/0/com.android.foo.
+ * 3). We do similar for CE storage. But in direct boot mode, as /data_mirror/data_ce/0/ is
+ * encrypted, we can't find a directory with name com.android.foo on it, so we will
+ * use the inode number to find the right directory instead, which that directory content will
+ * be decrypted after storage is decrypted.
+ *
+ */
+static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
+ uid_t uid, const char* process_name, jstring managed_nice_name,
+ fail_fn_t fail_fn) {
+
+ const userid_t userId = multiuser_get_user_id(uid);
+
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+
+ int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
+ // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
+ if ((size % 3) != 0) {
+ fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size));
+ }
+
+ // Mount tmpfs on all possible data directories, so app no longer see the original apps data.
+ char internalCePath[PATH_MAX];
+ char internalLegacyCePath[PATH_MAX];
+ char internalDePath[PATH_MAX];
+ char externalPrivateMountPath[PATH_MAX];
+
+ snprintf(internalCePath, PATH_MAX, "/data/user");
+ snprintf(internalLegacyCePath, PATH_MAX, "/data/data");
+ snprintf(internalDePath, PATH_MAX, "/data/user_de");
+ snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand");
+
+ MountAppDataTmpFs(internalLegacyCePath, fail_fn);
+ MountAppDataTmpFs(internalCePath, fail_fn);
+ MountAppDataTmpFs(internalDePath, fail_fn);
+ MountAppDataTmpFs(externalPrivateMountPath, fail_fn);
+
+ for (int i = 0; i < size; i += 3) {
+ jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
+ std::string packageName = extract_fn(package_str).value();
+
+ jstring vol_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 1));
+ std::string volUuid = extract_fn(vol_str).value();
+
+ jstring inode_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 2));
+ std::string inode = extract_fn(inode_str).value();
+ std::string::size_type sz;
+ long long ceDataInode = std::stoll(inode, &sz);
+
+ std::string actualCePath, actualDePath;
+ if (volUuid.compare("null") != 0) {
+ // Volume that is stored in /mnt/expand
+ char volPath[PATH_MAX];
+ char volCePath[PATH_MAX];
+ char volDePath[PATH_MAX];
+ char volCeUserPath[PATH_MAX];
+ char volDeUserPath[PATH_MAX];
+
+ snprintf(volPath, PATH_MAX, "/mnt/expand/%s", volUuid.c_str());
+ snprintf(volCePath, PATH_MAX, "%s/user", volPath);
+ snprintf(volDePath, PATH_MAX, "%s/user_de", volPath);
+ snprintf(volCeUserPath, PATH_MAX, "%s/%d", volCePath, userId);
+ snprintf(volDeUserPath, PATH_MAX, "%s/%d", volDePath, userId);
+
+ PrepareDirIfNotPresent(volPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volCePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volDePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
+ PrepareDirIfNotPresent(volCeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+ PrepareDirIfNotPresent(volDeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+
+ actualCePath = volCeUserPath;
+ actualDePath = volDeUserPath;
+ } else {
+ // Internal volume that stored in /data
+ char internalCeUserPath[PATH_MAX];
+ char internalDeUserPath[PATH_MAX];
+ snprintf(internalCeUserPath, PATH_MAX, "/data/user/%d", userId);
+ snprintf(internalDeUserPath, PATH_MAX, "/data/user_de/%d", userId);
+ // If it's user 0, create a symlink /data/user/0 -> /data/data,
+ // otherwise create /data/user/$USER
+ if (userId == 0) {
+ symlink(internalLegacyCePath, internalCeUserPath);
+ actualCePath = internalLegacyCePath;
+ } else {
+ PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION,
+ AID_ROOT, AID_ROOT, fail_fn);
+ actualCePath = internalCeUserPath;
+ }
+ PrepareDirIfNotPresent(internalDeUserPath, DEFAULT_DATA_DIR_PERMISSION,
+ AID_ROOT, AID_ROOT, fail_fn);
+ actualDePath = internalDeUserPath;
+ }
+ isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode,
+ actualCePath, actualDePath, fail_fn);
+ }
+}
+
// Utility routine to specialize a zygote child process.
static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
@@ -1023,7 +1282,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
jint mount_external, jstring managed_se_info,
jstring managed_nice_name, bool is_system_server,
bool is_child_zygote, jstring managed_instruction_set,
- jstring managed_app_data_dir, bool is_top_app) {
+ jstring managed_app_data_dir, bool is_top_app,
+ jobjectArray pkg_data_info_list) {
const char* process_name = is_system_server ? "system_server" : "zygote";
auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1059,6 +1319,16 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids,
MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn);
+ // System services, isolated process, webview/app zygote, old target sdk app, should
+ // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation.
+ // Isolated process / webview / app zygote should be gated by SELinux and file permission
+ // so they can't even traverse CE / DE directories.
+ if (pkg_data_info_list != nullptr
+ && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+ isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
+ fail_fn);
+ }
+
// If this zygote isn't root, it won't be able to create a process group,
// since the directory is owned by root.
if (!is_system_server && getuid() == 0) {
@@ -1425,7 +1695,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
- jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
+ jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
+ jobjectArray pkg_data_info_list) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -1457,7 +1728,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE);
+ is_top_app == JNI_TRUE, pkg_data_info_list);
}
return pid;
}
@@ -1481,10 +1752,13 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer(
fds_to_ignore,
true);
if (pid == 0) {
+ // System server prcoess does not need data isolation so no need to
+ // know pkg_data_info_list.
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
permitted_capabilities, effective_capabilities,
MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
- false, nullptr, nullptr, /* is_top_app= */ false);
+ false, nullptr, nullptr, /* is_top_app= */ false,
+ /* pkg_data_info_list */ nullptr);
} else if (pid > 0) {
// The zygote process checks whether the child process has died or not.
ALOGI("System server process %d has been created", pid);
@@ -1607,14 +1881,15 @@ static void com_android_internal_os_Zygote_nativeSpecializeAppProcess(
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids,
jint runtime_flags, jobjectArray rlimits,
jint mount_external, jstring se_info, jstring nice_name,
- jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
+ jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
+ jobjectArray pkg_data_info_list) {
jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
capabilities, capabilities,
mount_external, se_info, nice_name, false,
is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
- is_top_app == JNI_TRUE);
+ is_top_app == JNI_TRUE, pkg_data_info_list);
}
/**
@@ -1775,7 +2050,7 @@ static void com_android_internal_os_Zygote_nativeBoostUsapPriority(JNIEnv* env,
static const JNINativeMethod gMethods[] = {
{ "nativeForkAndSpecialize",
- "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)I",
(void *) com_android_internal_os_Zygote_nativeForkAndSpecialize },
{ "nativeForkSystemServer", "(II[II[[IJJ)I",
(void *) com_android_internal_os_Zygote_nativeForkSystemServer },
@@ -1788,7 +2063,7 @@ static const JNINativeMethod gMethods[] = {
{ "nativeForkUsap", "(II[IZ)I",
(void *) com_android_internal_os_Zygote_nativeForkUsap },
{ "nativeSpecializeAppProcess",
- "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
+ "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)V",
(void *) com_android_internal_os_Zygote_nativeSpecializeAppProcess },
{ "nativeInitNativeState", "(Z)V",
(void *) com_android_internal_os_Zygote_nativeInitNativeState },
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 49046b244bb4..d2f1113ec5a9 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -133,6 +133,12 @@ public abstract class PackageManagerInternal {
@PackageInfoFlags int flags, int filterCallingUid, int userId);
/**
+ * Retrieve CE data directory inode number of an application.
+ * Return 0 if there's error.
+ */
+ public abstract long getCeDataInode(String packageName, int userId);
+
+ /**
* Return a List of all application packages that are installed on the
* device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been
* set, a list of all applications including those deleted with
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 5a78036911a8..22fa8ff4a0fa 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -144,6 +144,7 @@ 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.Installer;
import com.android.server.storage.AppFuseBridge;
import com.android.server.storage.StorageSessionController;
import com.android.server.storage.StorageSessionController.ExternalStorageServiceException;
@@ -367,6 +368,8 @@ class StorageManagerService extends IStorageManager.Stub
private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
+ private final Installer mInstaller;
+
/** Holding lock for AppFuse business */
private final Object mAppFuseLock = new Object();
@@ -1245,6 +1248,13 @@ class StorageManagerService extends IStorageManager.Stub
vol.state = newState;
onVolumeStateChangedLocked(vol, oldState, newState);
}
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE && state == VolumeInfo.STATE_MOUNTED) {
+ mInstaller.onPrivateVolumeMounted(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.i(TAG, "Failed when private volume mounted " + vol, e);
+ }
}
}
@@ -1290,6 +1300,13 @@ class StorageManagerService extends IStorageManager.Stub
if (vol != null) {
mStorageSessionController.onVolumeRemove(vol);
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.i(TAG, "Failed when private volume unmounted " + vol, e);
+ }
}
}
};
@@ -1601,6 +1618,9 @@ class StorageManagerService extends IStorageManager.Stub
mStorageSessionController = new StorageSessionController(mContext, mIsFuseEnabled);
+ mInstaller = new Installer(mContext);
+ mInstaller.onStart();
+
// Initialize the last-fstrim tracking if necessary
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
@@ -1974,6 +1994,13 @@ class StorageManagerService extends IStorageManager.Stub
try {
mVold.unmount(vol.id);
mStorageSessionController.onVolumeUnmount(vol);
+ try {
+ if (vol.type == VolumeInfo.TYPE_PRIVATE) {
+ mInstaller.onPrivateVolumeRemoved(vol.getFsUuid());
+ }
+ } catch (Installer.InstallerException e) {
+ Slog.e(TAG, "Failed unmount mirror data", e);
+ }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ee064191f97a..557def44dc66 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -59,6 +59,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.graphics.Point;
import android.os.AppZygote;
@@ -78,6 +79,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
+import android.provider.DeviceConfig;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -85,6 +87,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -117,6 +120,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
+import java.util.Map;
/**
* Activity manager code dealing with processes.
@@ -124,6 +128,13 @@ import java.util.List;
public final class ProcessList {
static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
+ // A device config to control the minimum target SDK to enable app data isolation
+ static final String ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+ "persist.zygote.app_data_isolation";
+
+ // A device config to control the minimum target SDK to enable app data isolation
+ static final String ANDROID_APP_DATA_ISOLATION_MIN_SDK = "android_app_data_isolation_min_sdk";
+
// The minimum time we allow between crashes, for us to consider this
// application to be bad and stop and its services and reject broadcasts.
static final int MIN_CRASH_INTERVAL = 60 * 1000;
@@ -337,6 +348,8 @@ public final class ProcessList {
private boolean mOomLevelsSet = false;
+ private boolean mAppDataIsolationEnabled = false;
+
/**
* Temporary to avoid allocations. Protected by main lock.
*/
@@ -621,6 +634,10 @@ public final class ProcessList {
mService = service;
mActiveUids = activeUids;
mPlatformCompat = platformCompat;
+ // Get this after boot, and won't be changed until it's rebooted, as we don't
+ // want some apps enabled while some apps disabled
+ mAppDataIsolationEnabled =
+ SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
@@ -1853,6 +1870,32 @@ public final class ProcessList {
}
}
+ private boolean shouldIsolateAppData(ProcessRecord app) {
+ if (!mAppDataIsolationEnabled) {
+ return false;
+ }
+ if (!UserHandle.isApp(app.uid)) {
+ return false;
+ }
+ final int minTargetSdk = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ ANDROID_APP_DATA_ISOLATION_MIN_SDK, Build.VERSION_CODES.R);
+ return app.info.targetSdkVersion >= minTargetSdk;
+ }
+
+ private Map<String, Pair<String, Long>> getPackageAppDataInfoMap(PackageManagerInternal pmInt,
+ String[] packages, int uid) {
+ Map<String, Pair<String, Long>> result = new ArrayMap<>(packages.length);
+ int userId = UserHandle.getUserId(uid);
+ for (String packageName : packages) {
+ String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
+ long inode = pmInt.getCeDataInode(packageName, userId);
+ if (inode != 0) {
+ result.put(packageName, Pair.create(volumeUuid, inode));
+ }
+ }
+ return result;
+ }
+
private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
String seInfo, String requiredAbi, String instructionSet, String invokeWith,
@@ -1869,6 +1912,21 @@ public final class ProcessList {
app.setHasForegroundActivities(true);
}
+ final Map<String, Pair<String, Long>> pkgDataInfoMap;
+
+ if (shouldIsolateAppData(app)) {
+ // Get all packages belongs to the same shared uid. sharedPackages is empty array
+ // if it doesn't have shared uid.
+ final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
+ final String sharedUserId = pmInt.getSharedUserIdForPackage(app.info.packageName);
+ final String[] sharedPackages = pmInt.getPackagesForSharedUserId(sharedUserId,
+ app.userId);
+ pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0
+ ? new String[]{app.info.packageName} : sharedPackages, uid);
+ } else {
+ pkgDataInfoMap = null;
+ }
+
final Process.ProcessStartResult startResult;
if (hostingRecord.usesWebviewZygote()) {
startResult = startWebView(entryPoint,
@@ -1884,13 +1942,13 @@ public final class ProcessList {
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, null, app.info.packageName,
/*useUsapPool=*/ false, isTopApp, app.mDisabledCompatChanges,
- new String[]{PROC_START_SEQ_IDENT + app.startSeq});
+ pkgDataInfoMap, new String[]{PROC_START_SEQ_IDENT + app.startSeq});
} else {
startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, runtimeFlags, mountExternal,
app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
app.info.dataDir, invokeWith, app.info.packageName, isTopApp,
- app.mDisabledCompatChanges,
+ app.mDisabledCompatChanges, pkgDataInfoMap,
new String[]{PROC_START_SEQ_IDENT + app.startSeq});
}
checkSlow(startTime, "startProcess: returned from zygote!");
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 26cd42daa9f8..eb4b5939aaad 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -580,6 +580,30 @@ public class Installer extends SystemService {
}
}
+ /**
+ * Bind mount private volume CE and DE mirror storage.
+ */
+ public void onPrivateVolumeMounted(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.onPrivateVolumeMounted(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ /**
+ * Unmount private volume CE and DE mirror storage.
+ */
+ public void onPrivateVolumeRemoved(String volumeUuid) throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.onPrivateVolumeRemoved(volumeUuid);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId,
String profileName, String codePath, String dexMetadataPath) throws InstallerException {
if (!checkBeforeRemote()) return false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 99d5e4a21df4..cb362b0f0a23 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -22626,6 +22626,18 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
+ public long getCeDataInode(String packageName, int userId) {
+ synchronized (mLock) {
+ final PackageSetting ps = mSettings.mPackages.get(packageName);
+ if (ps != null) {
+ return ps.getCeDataInode(userId);
+ }
+ Slog.e(TAG, "failed to find package " + packageName);
+ return 0;
+ }
+ }
+
+ @Override
public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) {
synchronized (mLock) {
final PackageSetting ps = mSettings.mPackages.get(packageName);