2
0
mirror of https://github.com/checkpoint-restore/criu synced 2025-08-22 09:58:09 +00:00

check: add a feature test for apparmor_stacking

Signed-off-by: Tycho Andersen <tycho.andersen@canonical.com>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
This commit is contained in:
Tycho Andersen 2021-04-09 11:55:13 +02:00 committed by Andrei Vagin
parent 8d992a680e
commit 8723e3f998
6 changed files with 369 additions and 102 deletions

View File

@ -4,6 +4,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/mman.h>
#include <unistd.h> #include <unistd.h>
#include <ftw.h> #include <ftw.h>
@ -45,7 +46,7 @@ static AaNamespace *new_namespace(char *name, AaNamespace *parent)
void *m; void *m;
AaNamespace *ret; AaNamespace *ret;
ret = xmalloc(sizeof(*ret)); ret = xzalloc(sizeof(*ret));
if (!ret) if (!ret)
return NULL; return NULL;
aa_namespace__init(ret); aa_namespace__init(ret);
@ -350,6 +351,267 @@ err:
return -1; return -1;
} }
/* An AA profile that allows everything that the parasite needs to do */
#define PARASITE_PROFILE \
("profile %s {\n" \
" /** rwmlkix,\n" \
" unix,\n" \
" capability,\n" \
" signal,\n" \
"}\n")
char policydir[PATH_MAX] = ".criu.temp-aa-policy.XXXXXX";
static void *get_suspend_policy(char *name, off_t *len)
{
char policy[1024], file[PATH_MAX], cache[PATH_MAX], clean_name[PATH_MAX];
void *ret = NULL;
int n, fd, policy_len, i;
struct stat sb;
char *cmd[] = {
"apparmor_parser", "-QWL", cache, file, NULL,
};
*len = 0;
policy_len = snprintf(policy, sizeof(policy), PARASITE_PROFILE, name);
if (policy_len < 0 || policy_len >= sizeof(policy)) {
pr_err("policy name %s too long\n", name);
return NULL;
}
/* policy names can have /s, but file paths can't */
for (i = 0; name[i]; i++) {
if (i == PATH_MAX) {
pr_err("name %s too long\n", name);
return NULL;
}
clean_name[i] = name[i] == '/' ? '.' : name[i];
}
clean_name[i] = 0;
n = snprintf(file, sizeof(file), "%s/%s", policydir, clean_name);
if (n < 0 || n >= sizeof(policy)) {
pr_err("policy name %s too long\n", clean_name);
return NULL;
}
n = snprintf(cache, sizeof(cache), "%s/cache", policydir);
if (n < 0 || n >= sizeof(policy)) {
pr_err("policy dir too long\n");
return NULL;
}
fd = open(file, O_CREAT | O_WRONLY, 0600);
if (fd < 0) {
pr_perror("couldn't create %s", file);
return NULL;
}
n = write(fd, policy, policy_len);
close(fd);
if (n < 0 || n != policy_len) {
pr_perror("couldn't write policy for %s", file);
return NULL;
}
n = cr_system(-1, -1, -1, cmd[0], cmd, 0);
if (n < 0 || !WIFEXITED(n) || WEXITSTATUS(n)) {
pr_err("apparmor parsing failed %d\n", n);
return NULL;
}
n = snprintf(file, sizeof(file), "%s/cache/%s", policydir, clean_name);
if (n < 0 || n >= sizeof(policy)) {
pr_err("policy name %s too long\n", clean_name);
return NULL;
}
fd = open(file, O_RDONLY);
if (fd < 0) {
pr_perror("couldn't open %s", file);
return NULL;
}
if (fstat(fd, &sb) < 0) {
pr_perror("couldn't stat fd");
goto out;
}
ret = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (ret == MAP_FAILED) {
pr_perror("mmap of %s failed", file);
goto out;
}
*len = sb.st_size;
out:
close(fd);
return ret;
}
#define NEXT_AA_TOKEN(pos) \
while (*pos) { \
if (*pos == '/' && *(pos + 1) && *(pos + 1) == '/' && *(pos + 2) && *(pos + 2) == '&') { \
pos += 3; \
break; \
} \
if (*pos == ':' && *(pos + 1) && *(pos + 1) == '/' && *(pos + 2) && *(pos + 2) == '/') { \
pos += 3; \
break; \
} \
pos++; \
}
static int write_aa_policy(AaNamespace *ns, char *path, int offset, char *rewrite, bool suspend)
{
int i, my_offset, ret;
char *rewrite_pos = rewrite, namespace[PATH_MAX];
if (rewrite && suspend) {
pr_err("requesting aa rewriting and suspension at the same time is not supported\n");
return -1;
}
if (!rewrite) {
strncpy(namespace, ns->name, sizeof(namespace) - 1);
} else {
NEXT_AA_TOKEN(rewrite_pos);
switch (*rewrite_pos) {
case ':': {
char tmp, *end;
end = strchr(rewrite_pos + 1, ':');
if (!end) {
pr_err("invalid namespace %s\n", rewrite_pos);
return -1;
}
tmp = *end;
*end = 0;
strlcpy(namespace, rewrite_pos + 1, sizeof(namespace));
*end = tmp;
break;
}
default:
strlcpy(namespace, ns->name, sizeof(namespace));
for (i = 0; i < ns->n_policies; i++) {
if (strcmp(ns->policies[i]->name, rewrite_pos))
pr_warn("binary rewriting of apparmor policies not supported right now, not renaming %s to %s\n",
ns->policies[i]->name, rewrite_pos);
}
}
}
my_offset = snprintf(path + offset, PATH_MAX - offset, "/namespaces/%s", ns->name);
if (my_offset < 0 || my_offset >= PATH_MAX - offset) {
pr_err("snprintf'd too many characters\n");
return -1;
}
if (!suspend && mkdir(path, 0755) < 0 && errno != EEXIST) {
pr_perror("failed to create namespace %s", path);
goto fail;
}
for (i = 0; i < ns->n_namespaces; i++) {
if (write_aa_policy(ns, path, offset + my_offset, rewrite_pos, suspend) < 0)
goto fail;
}
ret = snprintf(path + offset + my_offset, sizeof(path) - offset - my_offset, "/.replace");
if (ret < 0 || ret >= sizeof(path) - offset - my_offset) {
pr_err("snprintf failed\n");
goto fail;
}
for (i = 0; i < ns->n_policies; i++) {
AaPolicy *p = ns->policies[i];
void *data = p->blob.data;
int fd, n;
off_t len = p->blob.len;
fd = open(path, O_WRONLY);
if (fd < 0) {
pr_perror("couldn't open apparmor load file %s", path);
goto fail;
}
if (suspend) {
pr_info("suspending policy %s\n", p->name);
data = get_suspend_policy(p->name, &len);
if (!data) {
close(fd);
goto fail;
}
}
n = write(fd, data, len);
close(fd);
if (suspend && munmap(data, len) < 0) {
pr_perror("failed to munmap");
goto fail;
}
if (n != len) {
pr_perror("write AA policy %s in %s failed", p->name, namespace);
goto fail;
}
if (!suspend)
pr_info("wrote aa policy %s: %s %d\n", path, p->name, n);
}
return 0;
fail:
if (!suspend) {
path[offset + my_offset] = 0;
rmdir(path);
}
pr_err("failed to write policy in AA namespace %s\n", namespace);
return -1;
}
static int do_suspend(bool suspend)
{
int i;
for (i = 0; i < n_namespaces; i++) {
AaNamespace *ns = namespaces[i];
char path[PATH_MAX] = AA_SECURITYFS_PATH "/policy";
if (write_aa_policy(ns, path, strlen(path), opts.lsm_profile, false) < 0)
return -1;
}
return 0;
}
int suspend_aa(void)
{
int ret;
if (!mkdtemp(policydir)) {
pr_perror("failed to make AA policy dir");
return -1;
}
ret = do_suspend(true);
if (rm_rf(policydir) < 0)
pr_err("failed removing policy dir %s\n", policydir);
return ret;
}
int unsuspend_aa(void)
{
return do_suspend(false);
}
int dump_aa_namespaces(void) int dump_aa_namespaces(void)
{ {
ApparmorEntry *ae = NULL; ApparmorEntry *ae = NULL;
@ -411,97 +673,6 @@ bool check_aa_ns_dumping(void)
return major >= 1 && minor >= 2; return major >= 1 && minor >= 2;
} }
static int restore_aa_namespace(AaNamespace *ns, char *path, int offset)
{
pid_t pid;
int status;
pid = fork();
if (pid < 0) {
pr_perror("fork failed");
return -1;
}
if (!pid) {
int i, my_offset, ret, fd;
char buf[1024];
ret = snprintf(buf, sizeof(buf), "changeprofile :%s:", ns->name);
if (ret < 0 || ret >= sizeof(buf)) {
pr_err("profile %s too big\n", ns->name);
exit(1);
}
my_offset = snprintf(path + offset, PATH_MAX - offset, "/namespaces/%s", ns->name);
if (my_offset < 0 || my_offset >= PATH_MAX - offset) {
pr_err("snprintf'd too many characters\n");
exit(1);
}
if (mkdir(path, 0755) < 0) {
if (errno == EEXIST) {
pr_warn("apparmor namespace %s already exists, restoring into it\n", path);
} else {
pr_perror("failed to create namespace %s", path);
exit(1);
}
}
fd = open_proc_rw(PROC_SELF, "attr/current");
if (fd < 0) {
pr_perror("couldn't open attr/current");
goto fail;
}
errno = 0;
ret = write(fd, buf, strlen(buf));
close(fd);
if (ret != strlen(buf)) {
pr_perror("failed to change aa namespace %s", buf);
goto fail;
}
for (i = 0; i < ns->n_namespaces; i++) {
if (restore_aa_namespace(ns, path, offset + my_offset) < 0)
goto fail;
}
for (i = 0; i < ns->n_policies; i++) {
int fd, n;
AaPolicy *p = ns->policies[i];
fd = open(AA_SECURITYFS_PATH "/.replace", O_WRONLY);
if (fd < 0) {
pr_perror("couldn't open apparmor load file");
goto fail;
}
n = write(fd, p->blob.data, p->blob.len);
close(fd);
if (n != p->blob.len) {
pr_perror("write AA policy failed");
goto fail;
}
}
exit(0);
fail:
rmdir(path);
exit(1);
}
if (waitpid(pid, &status, 0) < 0) {
pr_perror("waitpid failed");
return -1;
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
return 0;
pr_err("failed to restore aa namespace, worker exited: %d\n", status);
return -1;
}
int prepare_apparmor_namespaces(void) int prepare_apparmor_namespaces(void)
{ {
struct cr_img *img; struct cr_img *img;
@ -528,7 +699,7 @@ int prepare_apparmor_namespaces(void)
for (i = 0; i < ae->n_namespaces; i++) { for (i = 0; i < ae->n_namespaces; i++) {
char path[PATH_MAX] = AA_SECURITYFS_PATH "/policy"; char path[PATH_MAX] = AA_SECURITYFS_PATH "/policy";
if (restore_aa_namespace(ae->namespaces[i], path, strlen(path)) < 0) { if (write_aa_policy(ae->namespaces[i], path, strlen(path), opts.lsm_profile, false) < 0) {
ret = -1; ret = -1;
goto out; goto out;
} }
@ -539,3 +710,74 @@ out:
apparmor_entry__free_unpacked(ae, NULL); apparmor_entry__free_unpacked(ae, NULL);
return ret; return ret;
} }
int render_aa_profile(char **out, const char *cur)
{
const char *pos;
int n_namespaces = 0, n_profiles = 0;
bool last_namespace = false;
/* no rewriting necessary */
if (!opts.lsm_supplied) {
*out = xsprintf("changeprofile %s", cur);
if (!*out)
return -1;
return 0;
}
/* user asked to re-write to an unconfined profile */
if (!opts.lsm_profile) {
*out = NULL;
return 0;
}
pos = opts.lsm_profile;
while (*pos) {
switch (*pos) {
case ':':
n_namespaces++;
break;
default:
n_profiles++;
}
NEXT_AA_TOKEN(pos);
}
/* special case: there is no namespacing or stacking; we can just
* changeprofile to the rewritten string
*/
if (n_profiles == 1 && n_namespaces == 0) {
*out = xsprintf("changeprofile %s", opts.lsm_profile);
if (!*out)
return -1;
pr_info("rewrote apparmor profile from %s to %s\n", cur, *out);
return 0;
}
pos = cur;
while (*pos) {
switch (*pos) {
case ':':
n_namespaces--;
last_namespace = true;
break;
default:
n_profiles--;
}
NEXT_AA_TOKEN(pos);
if (n_profiles == 0 && n_namespaces == 0)
break;
}
*out = xsprintf("changeprofile %s//%s%s", opts.lsm_profile, last_namespace ? "" : "&", pos);
if (!*out)
return -1;
pr_info("rewrote apparmor profile from %s to %s\n", cur, *out);
return 0;
}

View File

@ -45,12 +45,16 @@
#include "tun.h" #include "tun.h"
#include "namespaces.h" #include "namespaces.h"
#include "pstree.h" #include "pstree.h"
#include "lsm.h"
#include "apparmor.h"
#include "cr_options.h" #include "cr_options.h"
#include "libnetlink.h" #include "libnetlink.h"
#include "net.h" #include "net.h"
#include "restorer.h" #include "restorer.h"
#include "uffd.h" #include "uffd.h"
#include "images/inventory.pb-c.h"
static char *feature_name(int (*func)(void)); static char *feature_name(int (*func)(void));
static int check_tty(void) static int check_tty(void)
@ -98,6 +102,14 @@ out:
return ret; return ret;
} }
static int check_apparmor_stacking(void)
{
if (!check_aa_ns_dumping())
return -1;
return 0;
}
static int check_map_files(void) static int check_map_files(void)
{ {
int ret; int ret;
@ -1466,6 +1478,7 @@ int cr_check(void)
ret |= check_newifindex(); ret |= check_newifindex();
ret |= check_pidfd_store(); ret |= check_pidfd_store();
ret |= check_ns_pid(); ret |= check_ns_pid();
ret |= check_apparmor_stacking();
} }
/* /*
@ -1576,6 +1589,7 @@ static struct feature_list feature_list[] = {
{ "has_ipt_legacy", check_ipt_legacy }, { "has_ipt_legacy", check_ipt_legacy },
{ "pidfd_store", check_pidfd_store }, { "pidfd_store", check_pidfd_store },
{ "ns_pid", check_ns_pid }, { "ns_pid", check_ns_pid },
{ "apparmor_stacking", check_apparmor_stacking },
{ NULL, NULL }, { NULL, NULL },
}; };

View File

@ -3236,8 +3236,6 @@ static struct thread_creds_args *rst_prep_creds_args(CredsEntry *ce, unsigned lo
char *rendered = NULL, *profile; char *rendered = NULL, *profile;
profile = ce->lsm_profile; profile = ce->lsm_profile;
if (opts.lsm_supplied)
profile = opts.lsm_profile;
if (validate_lsm(profile) < 0) if (validate_lsm(profile) < 0)
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);

View File

@ -28,7 +28,8 @@ int validate_lsm(char *profile);
/* /*
* Render the profile name in the way that the LSM wants it written to * Render the profile name in the way that the LSM wants it written to
* /proc/<pid>/attr/current. * /proc/<pid>/attr/current, according to whatever is in the images and
* specified by --lsm-profile.
*/ */
int render_lsm_profile(char *profile, char **val); int render_lsm_profile(char *profile, char **val);

View File

@ -318,6 +318,10 @@ int collect_and_suspend_lsm(void)
/* now, suspend the LSM; this is where code that implements something /* now, suspend the LSM; this is where code that implements something
* like PTRACE_O_SUSPEND_LSM should live. */ * like PTRACE_O_SUSPEND_LSM should live. */
switch (kdat.lsm) { switch (kdat.lsm) {
case LSMTYPE__APPARMOR:
if (suspend_aa() < 0)
return -1;
break;
default: default:
pr_warn("don't know how to suspend LSM %d\n", kdat.lsm); pr_warn("don't know how to suspend LSM %d\n", kdat.lsm);
} }
@ -327,6 +331,9 @@ int collect_and_suspend_lsm(void)
int unsuspend_lsm(void) int unsuspend_lsm(void)
{ {
if (kdat.lsm == LSMTYPE__APPARMOR && unsuspend_aa())
return -1;
return 0; return 0;
} }
@ -357,12 +364,7 @@ int render_lsm_profile(char *profile, char **val)
switch (kdat.lsm) { switch (kdat.lsm) {
case LSMTYPE__APPARMOR: case LSMTYPE__APPARMOR:
if (strcmp(profile, "unconfined") != 0 && asprintf(val, "changeprofile %s", profile) < 0) { return render_aa_profile(val, profile);
pr_err("allocating lsm profile failed\n");
*val = NULL;
return -1;
}
break;
case LSMTYPE__SELINUX: case LSMTYPE__SELINUX:
if (asprintf(val, "%s", profile) < 0) { if (asprintf(val, "%s", profile) < 0) {
*val = NULL; *val = NULL;

View File

@ -4,8 +4,18 @@
int collect_aa_namespace(char *profile); int collect_aa_namespace(char *profile);
int dump_aa_namespaces(void); int dump_aa_namespaces(void);
/*
* This is an operation similar to PTRACE_O_SUSPEND_SECCOMP but for apparmor,
* done entirely from userspace. All the namespaces to be dumped should be
* collected via collect_aa_namespaces() before calling this.
*/
int suspend_aa(void);
int unsuspend_aa(void);
bool check_aa_ns_dumping(void); bool check_aa_ns_dumping(void);
int prepare_apparmor_namespaces(void); int prepare_apparmor_namespaces(void);
int render_aa_profile(char **out, const char *cur);
#endif /* __CR_APPARMOR_H__ */ #endif /* __CR_APPARMOR_H__ */