Extend restorecon to handle app data directories correctly.
App data directories are labeled by installd at creation time
based on package information, seapp_contexts, and file_contexts.
Prior to this change, restorecon and restorecon_recursive did not
know how to label app data directories and would merely label
them based on file_contexts, causing them to be (mis)labeled with
a single security context if restorecon was applied to /data/data/<pkgname>
or /data/user/N/<pkgname>. Extend restorecon to correctly handle app data
directories based on all of the relevant information.
After applying this change and its dependencies (including rewriting
toolbox restorecon to use libselinux), a restorecon -Rv /data/data
only relabels the lib symlinks in the app data directories, which I
believe is harmless and arguably is a bug in installd. Originally
when the lib subdirectories were created in each app data directory
we labeled them with system_data_file to distinguish them from
app data, prevent direct writes by apps, and allow execute by apps.
However, when the lib directories were moved under /data/app-lib and only the
symlink was left behind, it continued to be labeled system_data_file
as a side effect of the fact that it is created before installd
calls selinux_android_setfilecon2() on the package directory and
thus inherits the original parent directory security context. Offhand,
I don't see a real reason to not just label the symlinks with the app data
directory context even though the symlinks do have a different UID (install)
since the containing directory is owned by the app UID so apps
can already unlink and re-create the symlink at will. So I think this
change by restorecon is harmless and we could switch installd to
applying the setfilecon2 first before creating the symlinks so that
they are originally labeled this way.
Change-Id: I698b1b2c3f00f31fbb2015edf23d33b51aa5bba1
Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
diff --git a/src/android.c b/src/android.c
index aca5adb..dbe903d 100644
--- a/src/android.c
+++ b/src/android.c
@@ -363,6 +363,8 @@
uid_t userid;
uid_t appid;
+ __selinux_once(once, seapp_context_init);
+
userid = uid / AID_USER;
appid = uid % AID_USER;
if (appid < AID_APP) {
@@ -509,8 +511,6 @@
if (is_selinux_enabled() <= 0)
return 0;
- __selinux_once(once, seapp_context_init);
-
rc = getfilecon(pkgdir, &ctx_str);
if (rc < 0)
goto err;
@@ -575,8 +575,6 @@
if (is_selinux_enabled() <= 0)
return 0;
- __selinux_once(once, seapp_context_init);
-
rc = getcon(&ctx_str);
if (rc)
goto err;
@@ -706,20 +704,246 @@
static pthread_once_t fc_once = PTHREAD_ONCE_INIT;
+struct pkgInfo {
+ char *name;
+ uid_t uid;
+ bool debuggable;
+ char *dataDir;
+ char *seinfo;
+ struct pkgInfo *next;
+};
+
+#define PKGTAB_SIZE 256
+static struct pkgInfo *pkgTab[PKGTAB_SIZE];
+
+static unsigned int pkghash(const char *pkgname)
+{
+ unsigned int h = 7;
+ for (; *pkgname; pkgname++) {
+ h = h * 31 + *pkgname;
+ }
+ return h & (PKGTAB_SIZE - 1);
+}
+
+/* The file containing the list of installed packages on the system */
+#define PACKAGES_LIST_FILE "/data/system/packages.list"
+
+static void package_info_init(void)
+{
+ char *buf = NULL;
+ size_t buflen = 0;
+ ssize_t bytesread;
+ FILE *fp;
+ char *cur, *next;
+ struct pkgInfo *pkgInfo = NULL;
+ unsigned int hash;
+ unsigned long lineno = 1;
+
+ fp = fopen(PACKAGES_LIST_FILE, "r");
+ if (!fp) {
+ selinux_log(SELINUX_ERROR, "SELinux: Could not open %s: %s.\n",
+ PACKAGES_LIST_FILE, strerror(errno));
+ return;
+ }
+ while ((bytesread = getline(&buf, &buflen, fp)) > 0) {
+ pkgInfo = calloc(1, sizeof(*pkgInfo));
+ if (!pkgInfo)
+ goto err;
+ next = buf;
+ cur = strsep(&next, " \t\n");
+ if (!cur)
+ goto err;
+ pkgInfo->name = strdup(cur);
+ if (!pkgInfo->name)
+ goto err;
+ cur = strsep(&next, " \t\n");
+ if (!cur)
+ goto err;
+ pkgInfo->uid = atoi(cur);
+ if (!pkgInfo->uid)
+ goto err;
+ cur = strsep(&next, " \t\n");
+ if (!cur)
+ goto err;
+ pkgInfo->debuggable = atoi(cur);
+ cur = strsep(&next, " \t\n");
+ if (!cur)
+ goto err;
+ pkgInfo->dataDir = strdup(cur);
+ if (!pkgInfo->dataDir)
+ goto err;
+ cur = strsep(&next, " \t\n");
+ if (!cur)
+ goto err;
+ pkgInfo->seinfo = strdup(cur);
+ if (!pkgInfo->seinfo)
+ goto err;
+
+ hash = pkghash(pkgInfo->name);
+ if (pkgTab[hash])
+ pkgInfo->next = pkgTab[hash];
+ pkgTab[hash] = pkgInfo;
+
+ lineno++;
+ }
+
+#if DEBUG
+ {
+ unsigned int buckets, entries, chainlen, longestchain;
+
+ buckets = entries = longestchain = 0;
+ for (hash = 0; hash < PKGTAB_SIZE; hash++) {
+ if (pkgTab[hash]) {
+ buckets++;
+ chainlen = 0;
+ for (pkgInfo = pkgTab[hash]; pkgInfo; pkgInfo = pkgInfo->next) {
+ chainlen++;
+ selinux_log(SELINUX_INFO, "%s: name=%s uid=%u debuggable=%s dataDir=%s seinfo=%s\n",
+ __FUNCTION__,
+ pkgInfo->name, pkgInfo->uid, pkgInfo->debuggable ? "true" : "false", pkgInfo->dataDir, pkgInfo->seinfo);
+ }
+ entries += chainlen;
+ if (longestchain < chainlen)
+ longestchain = chainlen;
+ }
+ }
+ selinux_log(SELINUX_INFO, "SELinux: %d pkg entries and %d/%d buckets used, longest chain %d\n", entries, buckets, PKGTAB_SIZE, longestchain);
+ }
+#endif
+
+out:
+ free(buf);
+ fclose(fp);
+ return;
+
+err:
+ selinux_log(SELINUX_ERROR, "SELinux: Error reading %s on line %lu.\n",
+ PACKAGES_LIST_FILE, lineno);
+ if (pkgInfo) {
+ free(pkgInfo->name);
+ free(pkgInfo->dataDir);
+ free(pkgInfo->seinfo);
+ free(pkgInfo);
+ }
+ goto out;
+}
+
+static pthread_once_t pkg_once = PTHREAD_ONCE_INIT;
+
+struct pkgInfo *package_info_lookup(const char *name)
+{
+ struct pkgInfo *pkgInfo;
+ unsigned int hash;
+
+ __selinux_once(pkg_once, package_info_init);
+
+ hash = pkghash(name);
+ for (pkgInfo = pkgTab[hash]; pkgInfo; pkgInfo = pkgInfo->next) {
+ if (!strcmp(name, pkgInfo->name))
+ return pkgInfo;
+ }
+ return NULL;
+}
+
+/* The path prefixes of package data directories. */
+#define DATA_DATA_PREFIX "/data/data/"
+#define DATA_USER_PREFIX "/data/user/"
+
+static int pkgdir_selabel_lookup(const char *pathname, char **secontextp)
+{
+ char *pkgname = NULL, *end = NULL;
+ struct pkgInfo *pkgInfo = NULL;
+ char *secontext = *secontextp;
+ context_t ctx = NULL;
+ int rc = 0;
+
+ /* Skip directory prefix before package name. */
+ if (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1)) {
+ pathname += sizeof(DATA_DATA_PREFIX) - 1;
+ } else if (!strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1)) {
+ pathname += sizeof(DATA_USER_PREFIX) - 1;
+ while (isdigit(*pathname))
+ pathname++;
+ if (*pathname == '/')
+ pathname++;
+ else
+ return 0;
+ } else
+ return 0;
+
+ if (!(*pathname))
+ return 0;
+
+ pkgname = strdup(pathname);
+ if (!pkgname)
+ return -1;
+
+ for (end = pkgname; *end && *end != '/'; end++)
+ ;
+ *end = '\0';
+
+ pkgInfo = package_info_lookup(pkgname);
+ if (!pkgInfo) {
+ free(pkgname);
+ return 0;
+ }
+
+ ctx = context_new(secontext);
+ if (!ctx)
+ goto err;
+
+ rc = seapp_context_lookup(SEAPP_TYPE, pkgInfo->uid, 0,
+ pkgInfo->seinfo, pkgInfo->name, ctx);
+ if (rc < 0)
+ goto err;
+
+ secontext = context_str(ctx);
+ if (!secontext)
+ goto err;
+
+ if (!strcmp(secontext, *secontextp))
+ goto out;
+
+ rc = security_check_context(secontext);
+ if (rc < 0)
+ goto err;
+
+ freecon(*secontextp);
+ *secontextp = strdup(secontext);
+ if (!(*secontextp))
+ goto err;
+
+ rc = 0;
+
+out:
+ free(pkgname);
+ context_free(ctx);
+ return rc;
+err:
+ selinux_log(SELINUX_ERROR, "%s: Error looking up context for path %s, pkgname %s, seinfo %s, uid %u: %s\n",
+ __FUNCTION__, pathname, pkgname, pkgInfo->seinfo, pkgInfo->uid, strerror(errno));
+ rc = -1;
+ goto out;
+}
+
#define RESTORECON_LAST "security.restorecon_last"
static int restorecon_sb(const char *pathname, const struct stat *sb, bool setrestoreconlast, bool nochange, bool verbose)
{
char *secontext = NULL;
char *oldsecontext = NULL;
- int i;
+ int rc = 0;
if (selabel_lookup(sehandle, &secontext, pathname, sb->st_mode) < 0)
return -1;
- if (lgetfilecon(pathname, &oldsecontext) < 0) {
- freecon(secontext);
- return -1;
+ if (lgetfilecon(pathname, &oldsecontext) < 0)
+ goto err;
+
+ if (!strncmp(pathname, DATA_DATA_PREFIX, sizeof(DATA_DATA_PREFIX)-1) ||
+ !strncmp(pathname, DATA_USER_PREFIX, sizeof(DATA_USER_PREFIX)-1)) {
+ if (pkgdir_selabel_lookup(pathname, &secontext) < 0)
+ goto err;
}
if (strcmp(oldsecontext, secontext) != 0) {
@@ -727,23 +951,27 @@
selinux_log(SELINUX_INFO,
"SELinux: Relabeling %s from %s to %s.\n", pathname, oldsecontext, secontext);
if (!nochange) {
- if (lsetfilecon(pathname, secontext) < 0) {
- selinux_log(SELINUX_ERROR,
- "SELinux: Could not set context for %s to %s: %s\n",
- pathname, secontext, strerror(errno));
- freecon(oldsecontext);
- freecon(secontext);
- return -1;
- }
+ if (lsetfilecon(pathname, secontext) < 0)
+ goto err;
}
}
- freecon(oldsecontext);
- freecon(secontext);
if (setrestoreconlast && !nochange && S_ISDIR(sb->st_mode))
setxattr(pathname, RESTORECON_LAST, fc_digest, sizeof fc_digest, 0);
- return 0;
+ rc = 0;
+
+out:
+ freecon(oldsecontext);
+ freecon(secontext);
+ return rc;
+
+err:
+ selinux_log(SELINUX_ERROR,
+ "SELinux: Could not set context for %s: %s\n",
+ pathname, strerror(errno));
+ rc = -1;
+ goto out;
}
int selinux_android_restorecon_flags(const char* pathname, unsigned int flags)