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:
parent
8d992a680e
commit
8723e3f998
428
criu/apparmor.c
428
criu/apparmor.c
@ -4,6 +4,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <ftw.h>
|
||||
|
||||
@ -45,7 +46,7 @@ static AaNamespace *new_namespace(char *name, AaNamespace *parent)
|
||||
void *m;
|
||||
AaNamespace *ret;
|
||||
|
||||
ret = xmalloc(sizeof(*ret));
|
||||
ret = xzalloc(sizeof(*ret));
|
||||
if (!ret)
|
||||
return NULL;
|
||||
aa_namespace__init(ret);
|
||||
@ -350,6 +351,267 @@ err:
|
||||
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)
|
||||
{
|
||||
ApparmorEntry *ae = NULL;
|
||||
@ -411,97 +673,6 @@ bool check_aa_ns_dumping(void)
|
||||
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)
|
||||
{
|
||||
struct cr_img *img;
|
||||
@ -528,7 +699,7 @@ int prepare_apparmor_namespaces(void)
|
||||
for (i = 0; i < ae->n_namespaces; i++) {
|
||||
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;
|
||||
goto out;
|
||||
}
|
||||
@ -539,3 +710,74 @@ out:
|
||||
apparmor_entry__free_unpacked(ae, NULL);
|
||||
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;
|
||||
}
|
||||
|
@ -45,12 +45,16 @@
|
||||
#include "tun.h"
|
||||
#include "namespaces.h"
|
||||
#include "pstree.h"
|
||||
#include "lsm.h"
|
||||
#include "apparmor.h"
|
||||
#include "cr_options.h"
|
||||
#include "libnetlink.h"
|
||||
#include "net.h"
|
||||
#include "restorer.h"
|
||||
#include "uffd.h"
|
||||
|
||||
#include "images/inventory.pb-c.h"
|
||||
|
||||
static char *feature_name(int (*func)(void));
|
||||
|
||||
static int check_tty(void)
|
||||
@ -98,6 +102,14 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int check_apparmor_stacking(void)
|
||||
{
|
||||
if (!check_aa_ns_dumping())
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_map_files(void)
|
||||
{
|
||||
int ret;
|
||||
@ -1466,6 +1478,7 @@ int cr_check(void)
|
||||
ret |= check_newifindex();
|
||||
ret |= check_pidfd_store();
|
||||
ret |= check_ns_pid();
|
||||
ret |= check_apparmor_stacking();
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1576,6 +1589,7 @@ static struct feature_list feature_list[] = {
|
||||
{ "has_ipt_legacy", check_ipt_legacy },
|
||||
{ "pidfd_store", check_pidfd_store },
|
||||
{ "ns_pid", check_ns_pid },
|
||||
{ "apparmor_stacking", check_apparmor_stacking },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
|
@ -3236,8 +3236,6 @@ static struct thread_creds_args *rst_prep_creds_args(CredsEntry *ce, unsigned lo
|
||||
char *rendered = NULL, *profile;
|
||||
|
||||
profile = ce->lsm_profile;
|
||||
if (opts.lsm_supplied)
|
||||
profile = opts.lsm_profile;
|
||||
|
||||
if (validate_lsm(profile) < 0)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
@ -28,7 +28,8 @@ int validate_lsm(char *profile);
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
|
14
criu/lsm.c
14
criu/lsm.c
@ -318,6 +318,10 @@ int collect_and_suspend_lsm(void)
|
||||
/* now, suspend the LSM; this is where code that implements something
|
||||
* like PTRACE_O_SUSPEND_LSM should live. */
|
||||
switch (kdat.lsm) {
|
||||
case LSMTYPE__APPARMOR:
|
||||
if (suspend_aa() < 0)
|
||||
return -1;
|
||||
break;
|
||||
default:
|
||||
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)
|
||||
{
|
||||
if (kdat.lsm == LSMTYPE__APPARMOR && unsuspend_aa())
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -357,12 +364,7 @@ int render_lsm_profile(char *profile, char **val)
|
||||
|
||||
switch (kdat.lsm) {
|
||||
case LSMTYPE__APPARMOR:
|
||||
if (strcmp(profile, "unconfined") != 0 && asprintf(val, "changeprofile %s", profile) < 0) {
|
||||
pr_err("allocating lsm profile failed\n");
|
||||
*val = NULL;
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
return render_aa_profile(val, profile);
|
||||
case LSMTYPE__SELINUX:
|
||||
if (asprintf(val, "%s", profile) < 0) {
|
||||
*val = NULL;
|
||||
|
@ -4,8 +4,18 @@
|
||||
int collect_aa_namespace(char *profile);
|
||||
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);
|
||||
|
||||
int prepare_apparmor_namespaces(void);
|
||||
|
||||
int render_aa_profile(char **out, const char *cur);
|
||||
|
||||
#endif /* __CR_APPARMOR_H__ */
|
||||
|
Loading…
x
Reference in New Issue
Block a user