diff --git a/libraries/libapparmor/include/sys/apparmor.h b/libraries/libapparmor/include/sys/apparmor.h index 20b199d60..da64a8921 100644 --- a/libraries/libapparmor/include/sys/apparmor.h +++ b/libraries/libapparmor/include/sys/apparmor.h @@ -197,6 +197,10 @@ extern int aa_policy_cache_replace_all(aa_policy_cache *policy_cache, aa_kernel_interface *kernel_interface); extern int aa_policy_cache_no_dirs(aa_policy_cache *policy_cache); extern char *aa_policy_cache_dir_path(aa_policy_cache *policy_cache, int n); +extern int aa_policy_cache_dirfd(aa_policy_cache *policy_cache, int dir); +extern int aa_policy_cache_open(aa_policy_cache *policy_cache, const char *name, + int flags); +extern char *aa_policy_cache_filename(aa_policy_cache *policy_cache, const char *name); extern char *aa_policy_cache_dir_path_preview(aa_features *kernel_features, int dirfd, const char *path); diff --git a/libraries/libapparmor/include/sys/apparmor_private.h b/libraries/libapparmor/include/sys/apparmor_private.h index 00bbee4f8..a1a0a0772 100644 --- a/libraries/libapparmor/include/sys/apparmor_private.h +++ b/libraries/libapparmor/include/sys/apparmor_private.h @@ -34,6 +34,8 @@ int _aa_asprintf(char **strp, const char *fmt, ...); int _aa_dirat_for_each(int dirfd, const char *name, void *data, int (* cb)(int, const char *, struct stat *, void *)); +int _aa_overlaydirat_for_each(int dirfd[], int n, void *data, + int (* cb)(int, const char *, struct stat *, void *)); #ifdef __cplusplus } diff --git a/libraries/libapparmor/src/libapparmor.map b/libraries/libapparmor/src/libapparmor.map index 6cd814a20..ef061a722 100644 --- a/libraries/libapparmor/src/libapparmor.map +++ b/libraries/libapparmor/src/libapparmor.map @@ -95,10 +95,14 @@ APPARMOR_2.11 { *; } APPARMOR_2.10; -APPARMOR_2.12 { +APPARMOR_2.15 { global: aa_policy_cache_dir_path; aa_policy_cache_dir_path_preview; + aa_policy_cache_no_dirs; + aa_policy_cache_dirfd; + aa_policy_cache_open; + aa_policy_cache_filename; aa_features_id; local: *; diff --git a/libraries/libapparmor/src/policy_cache.c b/libraries/libapparmor/src/policy_cache.c index d020eb101..f981bc5ab 100644 --- a/libraries/libapparmor/src/policy_cache.c +++ b/libraries/libapparmor/src/policy_cache.c @@ -67,17 +67,29 @@ struct replace_all_cb_data { aa_kernel_interface *kernel_interface; }; -static int replace_all_cb(int dirfd unused, const char *name, struct stat *st, +static int replace_all_cb(int dirfd, const char *name, struct stat *st, void *cb_data) { int retval = 0; - if (!S_ISDIR(st->st_mode) && !_aa_is_blacklisted(name)) { + if (S_ISLNK(st->st_mode)) { + /* + * symlinks in that cache are used to track file merging. + * In the scanned overlay situation they can be skipped + * as the combined entry will be one of none skipped + * entries + */ + } else if (S_ISREG(st->st_mode) && st->st_size == 0) { + /* + * empty file in the cache dir is used as a whiteout + * to hide files in a lower layer. skip + */ + } else if (!S_ISDIR(st->st_mode) && !_aa_is_blacklisted(name)) { struct replace_all_cb_data *data; data = (struct replace_all_cb_data *) cb_data; retval = aa_kernel_interface_replace_policy_from_file(data->kernel_interface, - data->policy_cache->dirfd[0], + dirfd, name); } @@ -515,8 +527,8 @@ int aa_policy_cache_replace_all(aa_policy_cache *policy_cache, cb_data.policy_cache = policy_cache; cb_data.kernel_interface = kernel_interface; - retval = _aa_dirat_for_each(policy_cache->dirfd[0], ".", &cb_data, - replace_all_cb); + retval = _aa_overlaydirat_for_each(policy_cache->dirfd, policy_cache->n, + &cb_data, replace_all_cb); aa_kernel_interface_unref(kernel_interface); @@ -559,6 +571,56 @@ char *aa_policy_cache_dir_path(aa_policy_cache *policy_cache, int dir) return path; } +/** + * aa_policy_cache_dirfd - returns the dirfd for a aa_policy_cache directory + * @policy_cache: the policy_cache + * @dir: which dir in the policy cache to return the dirfd of + * + * Returns: The dirfd to the @dir policy cache directory on success, -1 on + * error with errno set. + * + * caller is responsible for closing the returned dirfd + */ +int aa_policy_cache_dirfd(aa_policy_cache *policy_cache, int dir) +{ + if (dir < 0 || dir >= policy_cache->n) { + PERROR("aa_policy_cache directory: %d does not exist\n", dir); + errno = ERANGE; + return -1; + } + + return dup(policy_cache->dirfd[dir]); +} + +/* open cache file corresponding to name */ +int aa_policy_cache_open(aa_policy_cache *policy_cache, const char *name, + int flags) +{ + int i, fd; + + for (i = 0; i < policy_cache->n; i++) { + fd = openat(policy_cache->dirfd[i], name, flags); + if (fd != -1) + return fd; + } + + return -1; +} + +char *aa_policy_cache_filename(aa_policy_cache *policy_cache, const char *name) +{ + char *path; + autoclose int fd = aa_policy_cache_open(policy_cache, name, O_RDONLY); + + if (fd == -1) + return NULL; + path = path_from_fd(fd); + if (!path) + PERROR("Can't return the path to the aa_policy_cache cachename: %m\n"); + + return path; +} + /** * aa_policy_cache_dir_path_preview - returns the path to the aa_policy_cache directory * @kernel_features: features representing a kernel (may be NULL if you want to diff --git a/libraries/libapparmor/src/private.c b/libraries/libapparmor/src/private.c index 75d22b749..bece09d19 100644 --- a/libraries/libapparmor/src/private.c +++ b/libraries/libapparmor/src/private.c @@ -235,6 +235,128 @@ out: return rc; } + +#define max(a, b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) +#define CHUNK 32 +struct overlaydir { + int dirfd; + struct dirent *dent; +}; + +static int insert(struct overlaydir **overlayptr, int *max_size, int *size, + int pos, int remaining, int dirfd, struct dirent *ent) +{ + struct overlaydir *overlay = *overlayptr; + int i, chunk = max(remaining, CHUNK); + + if (size + 1 >= max_size) { + struct overlaydir *tmp = reallocarray(overlay, + *max_size + chunk, + sizeof(*overlay)); + if (tmp == NULL) + return -1; + overlay = tmp; + } + *max_size += chunk; + (*size)++; + for (i = *size; i > pos; i--) + overlay[i] = overlay[i - 1]; + overlay[pos].dirfd = dirfd; + overlay[pos].dent = ent; + + *overlayptr = overlay; + return 0; +} + +#define merge(overlay, n_overlay, max_size, list, n_list, dirfd) \ +({ \ + int i, j; \ + int rc = 0; \ + \ + for (i = 0, j = 0; i < n_overlay && j < n_list; ) { \ + int res = strcmp(overlay[i].dent->d_name, list[j]->d_name);\ + if (res < 0) { \ + i++; \ + continue; \ + } else if (res == 0) { \ + free(list[j]); \ + list[j] = NULL; \ + i++; \ + j++; \ + } else { \ + if ((rc = insert(&overlay, &max_size, &n_overlay, i,\ + n_list - j, dirfd, list[j]))) \ + goto fail; \ + i++; \ + list[j++] = NULL; \ + } \ + } \ + while (j < n_list) { \ + if ((rc = insert(&overlay, &max_size, &n_overlay, i, \ + n_list - j, dirfd,list[j]))) \ + goto fail; \ + i++; \ + list[j++] = NULL; \ + } \ + \ +fail: \ + rc; \ +}) + +int _aa_overlaydirat_for_each(int dirfd[], int n, void *data, + int (* cb)(int, const char *, struct stat *, void *)) +{ + autofree struct dirent **list = NULL; + autofree struct overlaydir *overlay = NULL; + int i, k; + int n_list, size = 0, max_size = 0; + int rc = 0; + + for (i = 0; i < n; i++) { + n_list = scandirat(dirfd[i], ".", &list, dot_or_dot_dot_filter, + alphasort); + if (n_list == -1) { + PDEBUG("scandirat of dirfd[%d] failed: %m\n", i); + return -1; + } + if (merge(overlay, size, max_size, list, n_list, dirfd[i])) { + for (k = 0; k < n_list; k++) + free(list[i]); + for (k = 0; k < size; k++) + free(overlay[k].dent); + return -1; + } + } + + for (rc = 0, i = 0; i < size; i++) { + /* Must cycle through all dirs so that each one is autofreed */ + autofree struct dirent *dent = overlay[i].dent; + struct stat my_stat; + + if (rc) + continue; + + if (fstatat(overlay[i].dirfd, dent->d_name, &my_stat, + AT_SYMLINK_NOFOLLOW)) { + PDEBUG("stat failed for '%s': %m\n", dent->d_name); + rc = -1; + continue; + } + + if (cb(overlay[i].dirfd, dent->d_name, &my_stat, data)) { + PDEBUG("dir_for_each callback failed for '%s'\n", + dent->d_name); + rc = -1; + continue; + } + } + + return rc; +} + /** * _aa_dirat_for_each: iterate over a directory calling cb for each entry * @dirfd: already opened directory diff --git a/parser/parser_main.c b/parser/parser_main.c index 6d7b59f68..3f0294520 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -101,7 +101,6 @@ struct timespec cache_tstamp, mru_policy_tstamp; static char *apparmorfs = NULL; static char *cacheloc = NULL; -static char *cachedir = NULL; static bool print_cache_dir = false; static aa_features *features = NULL; @@ -760,10 +759,11 @@ int test_for_dir_mode(const char *basename, const char *linkdir) } int process_profile(int option, aa_kernel_interface *kernel_interface, - const char *profilename, const char *cachedir) + const char *profilename, aa_policy_cache *pc) { int retval = 0; autofree const char *cachename = NULL; + autofree const char *writecachename = NULL; autofree const char *cachetmpname = NULL; autoclose int cachetmp = -1; const char *basename = NULL; @@ -803,9 +803,12 @@ int process_profile(int option, aa_kernel_interface *kernel_interface, } /* setup cachename and tstamp */ - if (!force_complain && !skip_cache) { - cachename = cache_filename(cachedir, basename); - valid_read_cache(cachename); + if (!force_complain && pc) { + cachename = aa_policy_cache_filename(pc, basename); + if (!cachename) { + PERROR("Could not get cachename for '%s'\n", basename); + } else + valid_read_cache(cachename); } } @@ -837,8 +840,6 @@ int process_profile(int option, aa_kernel_interface *kernel_interface, if (!retval || skip_bad_cache_rebuild) return retval; } - - cachetmp = setup_cache_tmp(&cachetmpname, cachename); } if (show_cache) @@ -874,6 +875,18 @@ int process_profile(int option, aa_kernel_interface *kernel_interface, goto out; } + if (pc && write_cache) { + writecachename = cache_filename(pc, 0, basename); + if (!writecachename) { + PERROR("Cache write disabled: Cannot create cache file name '%s': %m\n", basename); + write_cache = 0; + } + cachetmp = setup_cache_tmp(&cachetmpname, writecachename); + if (cachetmp == -1) { + PERROR("Cache write disabled: Cannot create setup tmp cache file '%s': %m\n", writecachename); + write_cache = 0; + } + } /* cache file generated by load_policy */ retval = load_policy(option, kernel_interface, cachetmp); if (retval == 0 && write_cache) { @@ -882,7 +895,7 @@ int process_profile(int option, aa_kernel_interface *kernel_interface, PERROR("Warning failed to create cache: %s\n", basename); } else { - install_cache(cachetmpname, cachename); + install_cache(cachetmpname, writecachename); } } out: @@ -1024,7 +1037,7 @@ static void setup_parallel_compile(void) struct dir_cb_data { aa_kernel_interface *kernel_interface; const char *dirname; /* name of the parent dir */ - const char *cachedir; /* path to the cache sub directory */ + aa_policy_cache *policy_cache; /* policy_cache to use */ }; /* data - pointer to a dir_cb_data */ @@ -1039,7 +1052,7 @@ static int profile_dir_cb(int dirfd unused, const char *name, struct stat *st, if (asprintf(&path, "%s/%s", cb_data->dirname, name) < 0) PERROR(_("Out of memory")); work_spawn(process_profile(option, cb_data->kernel_interface, - path, cb_data->cachedir), + path, cb_data->policy_cache), handle_work_result); } return rc; @@ -1163,14 +1176,6 @@ int main(int argc, char *argv[]) write_cache = 0; skip_read_cache = 1; - } else { - cachedir = aa_policy_cache_dir_path(policy_cache, 0); - if (!cachedir) { - PERROR("Policy cache disabled: Cannot locate the policy cache directory: %m\n"); - - write_cache = 0; - skip_read_cache = 1; - } } } @@ -1201,7 +1206,7 @@ int main(int argc, char *argv[]) memset(&cb_data, 0, sizeof(struct dir_cb_data)); cb_data.dirname = profilename; - cb_data.cachedir = cachedir; + cb_data.policy_cache = policy_cache; cb_data.kernel_interface = kernel_interface; cb = binary_input ? binary_dir_cb : profile_dir_cb; if ((retval = dirat_for_each(AT_FDCWD, profilename, @@ -1215,7 +1220,7 @@ int main(int argc, char *argv[]) handle_work_result); } else { work_spawn(process_profile(option, kernel_interface, - profilename, cachedir), + profilename, policy_cache), handle_work_result); } diff --git a/parser/policy_cache.c b/parser/policy_cache.c index 3454cc0dc..2864eec85 100644 --- a/parser/policy_cache.c +++ b/parser/policy_cache.c @@ -95,13 +95,15 @@ void update_mru_tstamp(FILE *file, const char *name) } } -char *cache_filename(const char *cachedir, const char *basename) +char *cache_filename(aa_policy_cache *pc, int dir, const char *basename) { char *cachename; + autofree char *path; - if (asprintf(&cachename, "%s/%s", cachedir, basename) < 0) { + path = aa_policy_cache_dir_path(pc, dir); + if (!path || asprintf(&cachename, "%s/%s", path, basename) < 0) { PERROR("Memory allocation error."); - exit(1); + return NULL; } return cachename; diff --git a/parser/policy_cache.h b/parser/policy_cache.h index 693370d00..7925f3677 100644 --- a/parser/policy_cache.h +++ b/parser/policy_cache.h @@ -41,7 +41,7 @@ extern int debug_cache; void set_cache_tstamp(struct timespec t); void update_mru_tstamp(FILE *file, const char *path); bool valid_cached_file_version(const char *cachename); -char *cache_filename(const char *cachedir, const char *basename); +char *cache_filename(aa_policy_cache *pc, int dir, const char *basename); void valid_read_cache(const char *cachename); int cache_hit(const char *cachename); int setup_cache_tmp(const char **cachetmpname, const char *cachename);