Commit 5fe3a138 authored by Tycho Andersen's avatar Tycho Andersen Committed by Pavel Emelyanov

lsm: add support for c/ring LSM profiles

This patch adds support for checkpoint and restore of two linux security
modules (apparmor and selinux). The actual checkpoint or restore code isn't
that interesting, other than that we have to do the LSM restore in the restorer
blob since it may block any number of things that we want to do as part of the
restore process.

I tried originally to get this to work using libraries in the restorer blob,
but I could _not_ get things to work correctly (I assume I was doing something
wrong with all the static linking, you can see my draft attempts here:
https://github.com/tych0/criu/commits/apparmor-using-libraries ). I can try to
resurrect this if it makes more sense, to do it that way, though.

v2: lsm_profile lives in creds.proto instead of the task core, look in a more
    canonical place for selinuxfs and don't try to special case any selinux
    profile names.
v3: only allow unconfined selinux profiles
Signed-off-by: 's avatarTycho Andersen <tycho.andersen@canonical.com>
Signed-off-by: 's avatarPavel Emelyanov <xemul@parallels.com>
parent a8b7e53b
......@@ -8,6 +8,11 @@ ifeq ($(call try-cc,$(LIBBSD_DEV_TEST),-lbsd),y)
DEFINES += -DCONFIG_HAS_LIBBSD
endif
ifeq ($(call pkg-config-check,libselinux),y)
LIBS := -lselinux $(LIBS)
DEFINES += -DCONFIG_HAS_SELINUX
endif
$(CONFIG): scripts/utilities.mak scripts/feature-tests.mak include/config-base.h
$(E) " GEN " $@
$(Q) @echo '#ifndef __CR_CONFIG_H__' > $@
......
......@@ -64,6 +64,7 @@ obj-y += timerfd.o
obj-y += aio.o
obj-y += string.o
obj-y += sigframe.o
obj-y += lsm.o
ifeq ($(VDSO),y)
obj-y += $(ARCH_DIR)/vdso.o
endif
......
......@@ -74,6 +74,7 @@
#include "action-scripts.h"
#include "aio.h"
#include "security.h"
#include "lsm.h"
#include "asm/dump.h"
......@@ -485,6 +486,9 @@ static int dump_task_creds(struct parasite_ctl *ctl,
if (parasite_dump_creds(ctl, &ce) < 0)
return -1;
if (collect_lsm_profile(ctl->pid.real, &ce) < 0)
return -1;
return pb_write_one(img_from_set(fds, CR_FD_CREDS), &ce, PB_CREDS);
}
......
......@@ -74,6 +74,7 @@
#include "action-scripts.h"
#include "aio.h"
#include "security.h"
#include "lsm.h"
#include "parasite-syscall.h"
......@@ -2248,7 +2249,7 @@ static inline int verify_cap_size(CredsEntry *ce)
(ce->n_cap_prm == CR_CAP_SIZE) && (ce->n_cap_bnd == CR_CAP_SIZE));
}
static int prepare_creds(int pid, struct task_restore_args *args)
static int prepare_creds(int pid, struct task_restore_args *args, char **lsm_profile)
{
int ret;
struct cr_img *img;
......@@ -2295,6 +2296,17 @@ static int prepare_creds(int pid, struct task_restore_args *args)
return -1;
}
*lsm_profile = NULL;
if (ce->lsm_profile) {
if (validate_lsm(ce) < 0)
return -1;
*lsm_profile = xstrdup(ce->lsm_profile);
if (!*lsm_profile)
return -1;
}
creds_entry__free_unpacked(ce, NULL);
args->cap_last_cap = kdat.last_cap;
......@@ -2631,6 +2643,10 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core)
unsigned long aio_rings;
MmEntry *mm = rsti(current)->mm;
char *lsm = NULL;
int lsm_profile_len = 0;
unsigned long lsm_pos = 0;
struct vm_area_list self_vmas;
struct vm_area_list *vmas = &rsti(current)->vmas;
int i;
......@@ -2783,6 +2799,32 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core)
task_args = mem;
thread_args = (struct thread_restore_args *)(task_args + 1);
ret = prepare_creds(pid, task_args, &lsm);
if (ret < 0)
goto err;
if (lsm) {
char *rendered;
int ret;
ret = render_lsm_profile(lsm, &rendered);
xfree(lsm);
if (ret < 0) {
goto err_nv;
}
lsm_pos = rst_mem_cpos(RM_PRIVATE);
lsm_profile_len = strlen(rendered);
lsm = rst_mem_alloc(lsm_profile_len + 1, RM_PRIVATE);
if (!lsm) {
xfree(rendered);
goto err_nv;
}
strncpy(lsm, rendered, lsm_profile_len);
xfree(rendered);
}
/*
* Get a reference to shared memory area which is
* used to signal if shmem restoration complete
......@@ -2837,6 +2879,19 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core)
else
task_args->helpers = NULL;
if (lsm) {
task_args->proc_attr_current = open_proc_rw(PROC_SELF, "attr/current");
if (task_args->proc_attr_current < 0) {
pr_perror("Can't open attr/current");
goto err;
}
task_args->lsm_profile = rst_mem_remap_ptr(lsm_pos, RM_PRIVATE);
task_args->lsm_profile_len = lsm_profile_len;
} else {
task_args->lsm_profile = NULL;
}
/*
* Arguments for task restoration.
*/
......@@ -2937,10 +2992,6 @@ static int sigreturn_restore(pid_t pid, CoreEntry *core)
if (ret < 0)
goto err;
ret = prepare_creds(pid, task_args);
if (ret < 0)
goto err;
ret = prepare_mm(pid, task_args);
if (ret < 0)
goto err;
......
......@@ -8,6 +8,7 @@
#include "pstree.h"
#include "stats.h"
#include "cgroup.h"
#include "lsm.h"
#include "protobuf.h"
#include "protobuf/inventory.pb-c.h"
#include "protobuf/pagemap.pb-c.h"
......@@ -17,6 +18,7 @@ bool ns_per_id = false;
bool img_common_magic = true;
TaskKobjIdsEntry *root_ids;
u32 root_cg_set;
Lsmtype image_lsm;
int check_img_inventory(void)
{
......@@ -51,6 +53,8 @@ int check_img_inventory(void)
root_cg_set = he->root_cg_set;
}
image_lsm = he->lsmtype;
switch (he->img_version) {
case CRTOOLS_IMAGES_V1:
/* good old images. OK */
......@@ -93,6 +97,7 @@ int write_img_inventory(void)
he.has_fdinfo_per_id = true;
he.ns_per_id = true;
he.has_ns_per_id = true;
he.lsmtype = host_lsm_type();
crt.i.state = TASK_ALIVE;
crt.i.pid.real = getpid();
......
#ifndef __CR_LSM_H__
#define __CR_LSM_H__
#include "protobuf/inventory.pb-c.h"
#include "protobuf/creds.pb-c.h"
/*
* Get the Lsmtype for the current host.
*/
extern Lsmtype host_lsm_type();
/*
* Read the LSM profile for the pstree item
*/
extern int collect_lsm_profile(pid_t, CredsEntry *);
/*
* Validate that the LSM profiles can be correctly applied (must happen after
* pstree is set up).
*/
extern int validate_lsm();
/*
* Render the profile name in the way that the LSM wants it written to
* /proc/<pid>/attr/current.
*/
int render_lsm_profile(char *profile, char **val);
#endif /* __CR_LSM_H__ */
......@@ -154,6 +154,10 @@ struct task_restore_args {
pid_t *helpers /* the TASK_HELPERS to wait on at the end of restore */;
int n_helpers;
int proc_attr_current;
char *lsm_profile;
int lsm_profile_len;
#ifdef CONFIG_VDSO
unsigned long vdso_rt_size;
struct vdso_symtable vdso_sym_rt; /* runtime vdso symbols */
......
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include "config.h"
#include "pstree.h"
#include "util.h"
#include "protobuf.h"
#include "protobuf/inventory.pb-c.h"
#include "protobuf/creds.pb-c.h"
#ifdef CONFIG_HAS_SELINUX
#include <selinux/selinux.h>
#endif
static Lsmtype lsmtype;
static int (*get_label)(pid_t, char **) = NULL;
static char *name = NULL;
static int apparmor_get_label(pid_t pid, char **profile_name)
{
FILE *f;
char *space;
f = fopen_proc(pid, "attr/current");
if (!f)
return -1;
if (fscanf(f, "%ms", profile_name) != 1) {
fclose(f);
pr_perror("err scanfing");
return -1;
}
fclose(f);
/*
* A profile name can be followed by an enforcement mode, e.g.
* lxc-default-with-nesting (enforced)
* but the profile name is just the part before the space.
*/
space = strstr(*profile_name, " ");
if (space)
*space = 0;
/*
* An "unconfined" value means there is no profile, so we don't need to
* worry about trying to restore one.
*/
if (strcmp(*profile_name, "unconfined") == 0)
*profile_name = NULL;
return 0;
}
#ifdef CONFIG_HAS_SELINUX
static int selinux_get_label(pid_t pid, char **output)
{
security_context_t ctx;
char *pos, *last;
int i;
if (getpidcon_raw(pid, &ctx) < 0) {
pr_perror("getting selinux profile failed");
return -1;
}
*output = NULL;
/*
* Since SELinux attributes can be finer grained than at the task
* level, and we currently don't try to dump any of these other bits,
* let's only allow unconfined profiles, which look something like:
*
* unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
*/
pos = (char*)ctx;
for (i = 0; i < 3; i++) {
last = pos;
pos = strstr(pos, ":");
if (!pos) {
pr_err("Invalid selinux context %s\n", (char *)ctx);
freecon(ctx);
return -1;
}
*pos = 0;
if (!strstartswith(last, "unconfined_")) {
pr_err("Non unconfined selinux contexts not supported %s\n", last);
freecon(ctx);
return -1;
}
pos++;
}
freecon(ctx);
return 0;
}
#endif
static void get_host_lsm()
{
if (access("/sys/kernel/security/apparmor", F_OK) == 0) {
get_label = apparmor_get_label;
lsmtype = LSMTYPE__APPARMOR;
name = "apparmor";
return;
}
#ifdef CONFIG_HAS_SELINUX
/*
* This seems to be the canonical place to mount this fs if it is
* enabled, although we may (?) want to check /selinux for posterity as
* well.
*/
if (access("/sys/fs/selinux", F_OK) == 0) {
get_label = selinux_get_label;
lsmtype = LSMTYPE__SELINUX;
name = "selinux";
return;
}
#endif
get_label = NULL;
lsmtype = LSMTYPE__NO_LSM;
name = "none";
}
Lsmtype host_lsm_type()
{
if (name == NULL)
get_host_lsm();
return lsmtype;
}
int collect_lsm_profile(pid_t pid, CredsEntry *ce)
{
if (name == NULL)
get_host_lsm();
ce->lsm_profile = NULL;
if (lsmtype == LSMTYPE__NO_LSM)
return 0;
if (get_label(pid, &ce->lsm_profile) < 0)
return -1;
if (ce->lsm_profile)
pr_info("%d has lsm profile %s\n", pid, ce->lsm_profile);
return 0;
}
// in inventory.c
extern Lsmtype image_lsm;
int validate_lsm(CredsEntry *ce)
{
if (name == NULL)
get_host_lsm();
if (image_lsm == LSMTYPE__NO_LSM || image_lsm == lsmtype)
return 0;
/*
* This is really only a problem if the processes have actually
* specified an LSM profile. If not, we won't restore anything anyway,
* so it's fine.
*/
if (ce->lsm_profile) {
pr_err("mismatched lsm types and lsm profile specified\n");
return -1;
}
return 0;
}
int render_lsm_profile(char *profile, char **val)
{
*val = NULL;
switch (lsmtype) {
case LSMTYPE__APPARMOR:
if (strcmp(profile, "unconfined") != 0 && asprintf(val, "changeprofile %s", profile) < 0) {
*val = NULL;
return -1;
}
break;
case LSMTYPE__SELINUX:
if (asprintf(val, "%s", profile) < 0) {
*val = NULL;
return -1;
}
break;
default:
return -1;
}
return 0;
}
......@@ -740,6 +740,25 @@ static int wait_helpers(struct task_restore_args *task_args)
return 0;
}
static int lsm_set_label(struct task_restore_args *args)
{
int ret = -1;
if (!args->lsm_profile)
return 0;
pr_info("restoring lsm profile %s\n", args->lsm_profile);
ret = sys_write(args->proc_attr_current, args->lsm_profile, args->lsm_profile_len);
sys_close(args->proc_attr_current);
if (ret < 0) {
pr_err("can't write lsm profile\n");
return -1;
}
return ret;
}
/*
* The main routine to restore task via sigreturn.
* This one is very special, we never return there
......@@ -1160,6 +1179,11 @@ long __export_restore_task(struct task_restore_args *args)
ret = ret || restore_dumpable_flag(&args->mm);
ret = ret || restore_pdeath_sig(args->t);
if (lsm_set_label(args) < 0) {
pr_err("lsm_set_label failed\n");
goto core_restore_end;
}
futex_set_and_wake(&thread_inprogress, args->nr_threads);
restore_finish_stage(CR_STATE_RESTORE_CREDS);
......
......@@ -16,4 +16,6 @@ message creds_entry {
required uint32 secbits = 13;
repeated uint32 groups = 14;
optional string lsm_profile = 15;
}
import "core.proto";
enum lsmtype {
NO_LSM = 0;
SELINUX = 1;
APPARMOR = 2;
}
message inventory_entry {
required uint32 img_version = 1;
optional bool fdinfo_per_id = 2;
optional task_kobj_ids_entry root_ids = 3;
optional bool ns_per_id = 4;
optional uint32 root_cg_set = 5;
optional lsmtype lsmtype = 6;
}
......@@ -5,3 +5,7 @@ try-cc = $(shell sh -c \
echo "$(1)" | \
$(CC) $(DEFINES) -x c - $(2) $(3) -o "$$TMP" > /dev/null 2>&1 && echo y; \
rm -f "$$TMP"')
# pkg-config-check
# Usage: ifeq ($(call pkg-config-check, library),y)
pkg-config-check = $(shell sh -c 'pkg-config $(1) && echo y')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment