/* * Copyright (C) 2020 Canonical Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License published by the Free Software Foundation. */ #define _GNU_SOURCE /* for asprintf() */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _(s) gettext(s) /* TODO: implement config locations - value can change */ #define DEFAULT_CONFIG_LOCATIONS "/etc/apparmor/parser.conf" #define DEFAULT_POLICY_LOCATIONS "/var/cache/apparmor:/etc/apparmor.d/cache.d:/etc/apparmor.d/cache" #define CACHE_FEATURES_FILE ".features" bool opt_debug = false; bool opt_verbose = false; bool opt_dryrun = false; bool opt_force = false; bool opt_config = false; #define warning(fmt, args...) _error(_("aa-load: WARN: " fmt "\n"), ## args) #define error(fmt, args...) _error(_("aa-load: ERROR: " fmt "\n"), ## args) static void _error(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } #define verbose(fmt, args...) _debug(opt_verbose, _(fmt "\n"), ## args) #define debug(fmt, args...) _debug(opt_debug, _("aa-load: DEBUG: " fmt "\n"), ## args) static void _debug(bool opt_displayit, const char *fmt, ...) { va_list args; if (!opt_displayit) return; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } static int have_enough_privilege(const char *command) { uid_t uid, euid; uid = getuid(); euid = geteuid(); if (uid != 0 && euid != 0) { error("%s: Sorry. You need root privileges to run this program.\n", command); return EPERM; } if (uid != 0 && euid == 0) { error("%s: Aborting! You've set this program setuid root.\n" "Anybody who can run this program can update " "your AppArmor profiles.\n", command); exit(EXIT_FAILURE); } return 0; } static int load_config(const char *file) { /* TODO */ return ENOENT; } /** * load a single policy cache file to the kernel */ static int load_policy_file(const char *file) { int rc = 0; struct aa_kernel_interface *kernel_interface; if (aa_kernel_interface_new(&kernel_interface, NULL, NULL)) { rc = -errno; error("Failed to open kernel interface '%s': %m", file); return rc; } if (!opt_dryrun && aa_kernel_interface_replace_policy_from_file(kernel_interface, AT_FDCWD, file)) { rc = -errno; error("Failed to load policy into kernel '%s': %m", file); } aa_kernel_interface_unref(kernel_interface); return rc; } static void validate_features(const char *dir_path) { aa_features *kernel_features; if (aa_features_new_from_kernel(&kernel_features) == -1) { error("Failed to obtain features: %m"); return; } if (aa_features_check(AT_FDCWD, dir_path, kernel_features) == -1) { if (errno == ENOENT) { /* features file does not exist * not an issue when loading cache policies from dir */ } else if (errno == EEXIST) { warning("Overlay features do not match kernel features"); } } aa_features_unref(kernel_features); } /** * load a directory of policy cache files to the kernel * This does not do a subdir search to find the kernel match but * tries to load the dir regardless of whether its features match * * The hierarchy looks like * * dir/ * .features * profile1 * ... */ static int load_policy_dir(const char *dir_path) { DIR *d; struct dirent *dir; int rc = 0; char *file; size_t len; validate_features(dir_path); d = opendir(dir_path); if (!d) { rc = -errno; error("Failed to open directory '%s': %m", dir_path); return rc; } while ((dir = readdir(d)) != NULL) { /* Only check regular files for now */ if (dir->d_type == DT_REG) { len = strnlen(dir->d_name, PATH_MAX); /* Ignores .features */ if (strncmp(dir->d_name, CACHE_FEATURES_FILE, len) == 0) { continue; } if (asprintf(&file, "%s/%s", dir_path, dir->d_name) == -1) { error("Failure allocating memory"); closedir(d); return -1; } load_policy_file(file); free(file); file = NULL; } } closedir(d); return 0; } /** * load_hashed_policy - find policy hashed dir and load it * * load/replace all policy from a policy hierarchy directory * * Returns: 0 on success < -errno * * It will find the subdir that matches the kernel and load all * precompiled policy files from it. * * The hierarchy looks something like * * location/ * kernel_hash1.0/ * .features * profile1 * ... * kernel_hash2.0/ * .features * profile1 * ... */ static int load_policy_by_hash(const char *location) { aa_policy_cache *policy_cache = NULL; int rc; if ((rc = aa_policy_cache_new(&policy_cache, NULL, AT_FDCWD, location, 0))) { rc = -errno; error("Failed to open policy cache '%s': %m", location); return rc; } if (opt_debug) { /* show hash directory under location that matches the * current kernel */ char *cache_loc = aa_policy_cache_dir_path_preview(NULL, AT_FDCWD, location); if (!cache_loc) { rc = -errno; error("Failed to find cache location '%s': %m", location); goto out; } debug("Loading cache from '%s'\n", cache_loc); free(cache_loc); } if (!opt_dryrun) { if ((rc = aa_policy_cache_replace_all(policy_cache, NULL)) < 0) { error("Failed to load policy cache '%s': %m", location); } else { verbose("Success - Loaded policy cache '%s'", location); } } out: aa_policy_cache_unref(policy_cache); return rc; } /** * load_arg - calls specific load functions for files and directories * * load/replace all policy files/dir in arg * * Returns: 0 on success, 1 on failure. * * It will load by hash subtree first, and fallback to a cache dir * If not a directory, it will try to load it as a cache file */ static int load_arg(char *arg) { char **location = NULL; int i, n, rc = 0; /* arg can specify an overlay of multiple cache locations */ if ((n = aa_split_overlay_str(arg, &location, 0, true)) == -1) { error("Failed to parse overlay locations: %m"); return 1; } for (i = 0; i < n; i++) { struct stat st; debug("Trying to open %s", location[i]); if (stat(location[i], &st) == -1) { error("Failed stat of '%s': %m", location[i]); rc = 1; continue; } if (S_ISDIR(st.st_mode)) { /* try hash dir subtree first */ if (load_policy_by_hash(location[i]) < 0) { error("Failed load policy by hash '%s': %m", location[i]); rc = 1; } /* fall back to cache dir */ if (load_policy_dir(location[i]) < 0) { error("Failed load policy by directory '%s': %m", location[i]); rc = 1; } } else if (load_policy_file(location[i]) < 0) { rc = 1; } } for (i = 0; i < n; i++) free(location[i]); free(location); return rc; } static void print_usage(const char *command) { printf("Usage: %s [OPTIONS] (cache file|cache dir|cache base dir)]*\n" "Load Precompiled AppArmor policy from a cache location or \n" "locations.\n\n" "Options:\n" " -f, --force load policy even if abi does not match the kernel\n" " -d, --debug display debug messages\n" " -v, --verbose display progress and error messages\n" " -n, --dry-run do everything except actual load\n" " -h, --help this message\n", command); } static const char *short_options = "c:dfvnh"; struct option long_options[] = { {"config", 1, 0, 'c'}, {"debug", 0, 0, 'd'}, {"force", 0, 0, 'f'}, {"verbose", 0, 0, 'v'}, {"dry-run", 0, 0, 'n'}, {"help", 0, 0, 'h'}, {NULL, 0, 0, 0}, }; static int process_args(int argc, char **argv) { int c, o; opterr = 1; while ((c = getopt_long(argc, argv, short_options, long_options, &o)) != -1) { switch(c) { case 0: error("error in argument processing\n"); exit(1); break; case 'd': opt_debug = true; break; case 'f': opt_force = true; break; case 'v': opt_verbose = true; break; case 'n': opt_dryrun = true; break; case 'h': print_usage(argv[0]); exit(0); break; case 'c': /* TODO: reserved config location, * act as a bad arg for now, when added update usage */ //opt_config = true; uncomment when implemented /* Fall through */ default: error("unknown argument: '%s'\n\n", optarg); print_usage(argv[1]); exit(1); break; } } return optind; } int main(int argc, char **argv) { int i, rc = 0; optind = process_args(argc, argv); if (!opt_dryrun && have_enough_privilege(argv[0])) return 1; /* if no location use the default one */ if (optind == argc) { if (!opt_config && load_config(DEFAULT_CONFIG_LOCATIONS) == 0) { verbose("Loaded policy config"); } if ((rc = load_arg(DEFAULT_POLICY_LOCATIONS))) verbose("Loading policy from default location '%s'", DEFAULT_POLICY_LOCATIONS); else debug("No policy specified, and no policy config or policy in default locations"); } for (i = optind; i < argc; i++) { /* Try to load all policy locations even if one fails * but always return an error if any fail */ int tmp = load_arg(argv[i]); if (!rc) rc = tmp; } return rc; }