From 7d4a56f84d0589c535baa50afceeb70d8a04b856 Mon Sep 17 00:00:00 2001 From: xunchang Date: Fri, 1 Mar 2019 12:32:28 -0800 Subject: Restorecon: save digest of all partial matches for directory We used to hash the file_context and skip the restorecon on the top level directory if the hash doesn't change. But the file_context might change after an OTA update; and some users experienced long restorecon time as they have lots of files under directories like /data/media. This CL tries to hash all the partial match entries in the file_context for each directory; and skips the restorecon if that digest stays the same, regardless of the changes to the other parts of file_context. Bug: 62302954 Test: visited directory skips correctly during restorecon. Change-Id: Ia0668629a260b9b7a049bb68f6a8cc901c6cc46b --- libselinux/src/android/android_platform.c | 113 +++++++++++++++++++++++++----- 1 file changed, 95 insertions(+), 18 deletions(-) diff --git a/libselinux/src/android/android_platform.c b/libselinux/src/android/android_platform.c index 56e2cc5f..7b39b793 100644 --- a/libselinux/src/android/android_platform.c +++ b/libselinux/src/android/android_platform.c @@ -1443,7 +1443,7 @@ err: goto out; } -#define RESTORECON_LAST "security.restorecon_last" +#define RESTORECON_PARTIAL_MATCH_DIGEST "security.sehash" static int restorecon_sb(const char *pathname, const struct stat *sb, bool nochange, bool verbose, @@ -1502,6 +1502,57 @@ err: #define SYS_PATH "/sys" #define SYS_PREFIX SYS_PATH "/" +struct dir_hash_node { + char* path; + uint8_t digest[SHA1_HASH_SIZE]; + struct dir_hash_node *next; +}; + +// Returns true if the digest of all partial matched contexts is the same as the one +// saved by setxattr. Otherwise returns false and constructs a dir_hash_node with the +// newly calculated digest. +static bool check_context_match_for_dir(const char *pathname, struct dir_hash_node **new_node, + bool force, int error) { + uint8_t read_digest[SHA1_HASH_SIZE]; + ssize_t read_size = getxattr(pathname, RESTORECON_PARTIAL_MATCH_DIGEST, + read_digest, SHA1_HASH_SIZE); + uint8_t calculated_digest[SHA1_HASH_SIZE]; + bool status = selabel_hash_all_partial_matches(fc_sehandle, pathname, + calculated_digest); + + if (!new_node) { + return false; + } + *new_node = NULL; + if (!force && status && read_size == SHA1_HASH_SIZE && + memcmp(read_digest, calculated_digest, SHA1_HASH_SIZE) == 0) { + return true; + } + + // Save the digest of all matched contexts for the current directory. + if (!error && status) { + *new_node = calloc(1, sizeof(struct dir_hash_node)); + if (*new_node == NULL) { + selinux_log(SELINUX_ERROR, + "SELinux: %s: Out of memory\n", __func__); + return false; + } + + (*new_node)->path = strdup(pathname); + if ((*new_node)->path == NULL) { + selinux_log(SELINUX_ERROR, + "SELinux: %s: Out of memory\n", __func__); + free(*new_node); + *new_node = NULL; + return false; + } + memcpy((*new_node)->digest, calculated_digest, SHA1_HASH_SIZE); + (*new_node)->next = NULL; + } + + return false; +} + static int selinux_android_restorecon_common(const char* pathname_orig, const char *seinfo, uid_t uid, @@ -1524,8 +1575,8 @@ static int selinux_android_restorecon_common(const char* pathname_orig, char * paths[2] = { NULL , NULL }; int ftsflags = FTS_NOCHDIR | FTS_PHYSICAL; int error, sverrno; - char xattr_value[FC_DIGEST_SIZE]; - ssize_t size; + struct dir_hash_node *current = NULL; + struct dir_hash_node *head = NULL; if (!cross_filesystems) { ftsflags |= FTS_XDEV; @@ -1576,7 +1627,7 @@ static int selinux_android_restorecon_common(const char* pathname_orig, } /* - * Ignore restorecon_last on /data/data or /data/user + * Ignore saved partial match digest on /data/data or /data/user * since their labeling is based on seapp_contexts and seinfo * assignments rather than file_contexts and is managed by * installd rather than init. @@ -1598,17 +1649,6 @@ static int selinux_android_restorecon_common(const char* pathname_orig, setrestoreconlast = false; } - if (setrestoreconlast) { - size = getxattr(pathname, RESTORECON_LAST, xattr_value, sizeof fc_digest); - if (!force && size == sizeof fc_digest && memcmp(fc_digest, xattr_value, sizeof fc_digest) == 0) { - selinux_log(SELINUX_INFO, - "SELinux: Skipping restorecon_recursive(%s)\n", - pathname); - error = 0; - goto cleanup; - } - } - fts = fts_open(paths, ftsflags, NULL); if (!fts) { error = -1; @@ -1647,6 +1687,26 @@ static int selinux_android_restorecon_common(const char* pathname_orig, continue; } + if (setrestoreconlast) { + struct dir_hash_node* new_node = NULL; + if (check_context_match_for_dir(ftsent->fts_path, &new_node, force, error)) { + selinux_log(SELINUX_INFO, + "SELinux: Skipping restorecon on directory(%s)\n", + ftsent->fts_path); + fts_set(fts, ftsent, FTS_SKIP); + continue; + } + if (new_node) { + if (!current) { + current = new_node; + head = current; + } else { + current->next = new_node; + current = current->next; + } + } + } + if (skipce && (!strncmp(ftsent->fts_path, DATA_SYSTEM_CE_PREFIX, sizeof(DATA_SYSTEM_CE_PREFIX)-1) || !strncmp(ftsent->fts_path, DATA_MISC_CE_PREFIX, sizeof(DATA_MISC_CE_PREFIX)-1))) { @@ -1672,10 +1732,20 @@ static int selinux_android_restorecon_common(const char* pathname_orig, } } - // Labeling successful. Mark the top level directory as completed. + // Labeling successful. Write the partial match digests for subdirectories. + // TODO: Write the digest upon FTS_DP if no error occurs in its descents. if (setrestoreconlast && !nochange && !error) { - if (setxattr(pathname, RESTORECON_LAST, fc_digest, sizeof fc_digest, 0) < 0) - selinux_log(SELINUX_ERROR, "SELinux: setxattr failed: %s: %s\n", pathname, strerror(errno)); + current = head; + while (current != NULL) { + if (setxattr(current->path, RESTORECON_PARTIAL_MATCH_DIGEST, current->digest, + SHA1_HASH_SIZE, 0) < 0) { + selinux_log(SELINUX_ERROR, + "SELinux: setxattr failed: %s: %s\n", + current->path, + strerror(errno)); + } + current = current->next; + } } out: @@ -1685,6 +1755,13 @@ out: cleanup: free(pathdnamer); free(pathname); + current = head; + while (current != NULL) { + struct dir_hash_node *next = current->next; + free(current->path); + free(current); + current = next; + } return error; oom: sverrno = errno; -- cgit v1.2.3