2021-07-08 23:19:31 +00:00
|
|
|
/*
|
|
|
|
* 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 <stdio.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <getopt.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
|
|
|
#include <sys/apparmor.h>
|
|
|
|
|
|
|
|
#include <libintl.h>
|
|
|
|
#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");
|
2023-06-30 10:00:29 -03:00
|
|
|
closedir(d);
|
2021-07-08 23:19:31 +00:00
|
|
|
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;
|
|
|
|
}
|