The qemusecuritytest checks for random domain XMLs from qemuxml2argvdata/ whether set+restore seclabels leaves something behind. It can be an XATTR that we forgot to remove or a file that the owner was not restored on. But so far only DAC driver is checked. Implement missing pieces and enable SELinux testing too. This is done by mocking some libselinux APIs and following the same logic used for DAC - everything is implemented in memory, there is new hash table introduced that holds SELinux labels for paths that were setfilecon_raw()-ed and in the end the hash table is checked for entries that don't have the default SELinux label (i.e. were not restored). Signed-off-by: Michal Privoznik <mprivozn@xxxxxxxxxx> --- tests/qemusecuritydata/virtual_domain_context | 2 + tests/qemusecuritydata/virtual_image_context | 2 + tests/qemusecuritymock.c | 195 +++++++++++++++++- tests/qemusecuritytest.c | 51 ++++- 4 files changed, 240 insertions(+), 10 deletions(-) create mode 100644 tests/qemusecuritydata/virtual_domain_context create mode 100644 tests/qemusecuritydata/virtual_image_context diff --git a/tests/qemusecuritydata/virtual_domain_context b/tests/qemusecuritydata/virtual_domain_context new file mode 100644 index 0000000000..150f281d10 --- /dev/null +++ b/tests/qemusecuritydata/virtual_domain_context @@ -0,0 +1,2 @@ +system_u:system_r:svirt_t:s0 +system_u:system_r:svirt_tcg_t:s0 diff --git a/tests/qemusecuritydata/virtual_image_context b/tests/qemusecuritydata/virtual_image_context new file mode 100644 index 0000000000..8ab1e27ea2 --- /dev/null +++ b/tests/qemusecuritydata/virtual_image_context @@ -0,0 +1,2 @@ +system_u:object_r:svirt_image_t:s0 +system_u:object_r:virt_content_t:s0 diff --git a/tests/qemusecuritymock.c b/tests/qemusecuritymock.c index 543a5f7f3f..db03572dbe 100644 --- a/tests/qemusecuritymock.c +++ b/tests/qemusecuritymock.c @@ -24,6 +24,11 @@ #include <unistd.h> #include <fcntl.h> +#ifdef WITH_SELINUX +# include <selinux/selinux.h> +# include <selinux/label.h> +#endif + #include "virmock.h" #include "virfile.h" #include "virthread.h" @@ -41,7 +46,8 @@ * work as expected. Therefore there is a lot we have to mock * (chown, stat, XATTR APIs, etc.). Since the test won't run as * root chown() would fail, therefore we have to keep everything - * in memory. By default, all files are owned by 1:2. + * in memory. By default, all files are owned by 1:2 and have a + * SELinux label. * By the way, since there are some cases where real stat needs * to be called, the mocked functions are effective only if * $ENVVAR is set. @@ -49,11 +55,16 @@ #define DEFAULT_UID 1 #define DEFAULT_GID 2 +#define DEFAULT_SELINUX_LABEL "system_u:object_r:default_t:s0" static int (*real_chown)(const char *path, uid_t uid, gid_t gid); static int (*real_open)(const char *path, int flags, ...); static int (*real_close)(int fd); +#ifdef WITH_SELINUX +static int (*real_setfilecon_raw)(const char *path, const char *context); +static int (*real_getfilecon_raw)(const char *path, char **context); +#endif /* Global mutex to avoid races */ @@ -71,6 +82,10 @@ virHashTablePtr xattr_paths = NULL; * the lower half is UID and the higher is GID. */ virHashTablePtr chown_paths = NULL; +/* The SELinux label is stored in a hash table. For simplicity, + * the path os the key and the value is the label. */ +virHashTablePtr selinux_paths = NULL; + static void init_hash(void) @@ -94,6 +109,11 @@ init_hash(void) fprintf(stderr, "Unable to create hash table for chowned paths\n"); abort(); } + + if (!(selinux_paths = virHashNew(g_free))) { + fprintf(stderr, "Unable to create hash table for selinux labels\n"); + abort(); + } } @@ -106,6 +126,10 @@ init_syms(void) VIR_MOCK_REAL_INIT(chown); VIR_MOCK_REAL_INIT(open); VIR_MOCK_REAL_INIT(close); +#ifdef WITH_SELINUX + VIR_MOCK_REAL_INIT(setfilecon_raw); + VIR_MOCK_REAL_INIT(getfilecon_raw); +#endif /* Intentionally not calling init_hash() here */ } @@ -376,9 +400,30 @@ typedef struct _checkOwnerData checkOwnerData; struct _checkOwnerData { const char **paths; bool chown_fail; + bool selinux_fail; }; +static int +checkSELinux(void *payload, + const char *name, + void *opaque) +{ + checkOwnerData *data = opaque; + char *label = payload; + + if (STRNEQ(label, DEFAULT_SELINUX_LABEL) && + !virStringListHasString(data->paths, name)) { + fprintf(stderr, + "Path %s wasn't restored back to its original SELinux label\n", + name); + data->selinux_fail = true; + } + + return 0; +} + + static int checkOwner(void *payload, const char *name, @@ -431,7 +476,7 @@ printXATTR(void *payload, int checkPaths(const char **paths) { int ret = -1; - checkOwnerData data = { .paths = paths, .chown_fail = false }; + checkOwnerData data = { .paths = paths, .chown_fail = false, .selinux_fail = false }; bool xattr_fail = false; size_t i; @@ -445,13 +490,16 @@ int checkPaths(const char **paths) } } - if ((virHashForEach(chown_paths, checkOwner, &data)) < 0) + if (virHashForEach(selinux_paths, checkSELinux, &data) < 0) goto cleanup; - if ((virHashForEach(xattr_paths, printXATTR, &xattr_fail)) < 0) + if (virHashForEach(chown_paths, checkOwner, &data) < 0) goto cleanup; - if (data.chown_fail || xattr_fail) + if (virHashForEach(xattr_paths, printXATTR, &xattr_fail) < 0) + goto cleanup; + + if (data.chown_fail || data.selinux_fail || xattr_fail) goto cleanup; ret = 0; @@ -466,9 +514,10 @@ void freePaths(void) virMutexLock(&m); init_hash(); + virHashFree(selinux_paths); virHashFree(chown_paths); virHashFree(xattr_paths); - chown_paths = xattr_paths = NULL; + selinux_paths = chown_paths = xattr_paths = NULL; virMutexUnlock(&m); } @@ -490,3 +539,137 @@ virHostGetBootTime(unsigned long long *when) *when = 1234567890; return 0; } + + +#ifdef WITH_SELINUX +int +is_selinux_enabled(void) +{ + return 1; +} + + +struct selabel_handle * +selabel_open(unsigned int backend G_GNUC_UNUSED, + const struct selinux_opt *opts G_GNUC_UNUSED, + unsigned nopts G_GNUC_UNUSED) +{ + return (void*)((intptr_t) 0x1); +} + + +void +selabel_close(struct selabel_handle *rec G_GNUC_UNUSED) +{ + /* nada */ +} + + +const char * +selinux_virtual_domain_context_path(void) +{ + return abs_srcdir "/qemusecuritydata/virtual_domain_context"; +} + + +const char * +selinux_virtual_image_context_path(void) +{ + return abs_srcdir "/qemusecuritydata/virtual_image_context"; +} + + +int getcon_raw(char **context) +{ + *context = g_strdup("system_u:system_r:virtd_t:s0-s0:c0.c1023"); + return 0; +} + + +static int +mock_setfilecon_raw(const char *path, + const char *context) +{ + g_autofree char *val = g_strdup(context); + int ret = -1; + + virMutexLock(&m); + init_hash(); + + if (virHashUpdateEntry(selinux_paths, path, val) < 0) + goto cleanup; + val = NULL; + + ret = 0; + cleanup: + virMutexUnlock(&m); + return ret; +} + + +static int +mock_getfilecon_raw(const char *path, + char **context) +{ + const char *val; + + virMutexLock(&m); + init_hash(); + + val = virHashLookup(selinux_paths, path); + if (!val) + val = DEFAULT_SELINUX_LABEL; + + *context = g_strdup(val); + virMutexUnlock(&m); + return 0; +} + + +int +setfilecon_raw(const char *path, + const char *context) +{ + int ret; + + init_syms(); + + if (getenv(ENVVAR)) + ret = mock_setfilecon_raw(path, context); + else + ret = real_setfilecon_raw(path, context); + + return ret; +} + + +int +getfilecon_raw(const char *path, + char **context) +{ + int ret; + + init_syms(); + + if (getenv(ENVVAR)) + ret = mock_getfilecon_raw(path, context); + else + ret = real_getfilecon_raw(path, context); + + return ret; +} + + +int +selabel_lookup_raw(struct selabel_handle *hnd G_GNUC_UNUSED, + char **context, + const char *key G_GNUC_UNUSED, + int type G_GNUC_UNUSED) +{ + /* This function will be called only if we haven't found original label in + * XATTRs. Return something else than DEFAULT_SELINUX_LABEL so that it is + * considered as error. */ + *context = g_strdup("system_u:object_r:default_t:s1"); + return 0; +} +#endif diff --git a/tests/qemusecuritytest.c b/tests/qemusecuritytest.c index 297200d9ad..7ee1ccd1b6 100644 --- a/tests/qemusecuritytest.c +++ b/tests/qemusecuritytest.c @@ -73,6 +73,9 @@ prepareObjects(virQEMUDriverPtr driver, 0))) return -1; + if (virSecurityManagerGenLabel(driver->securityManager, vm->def) < 0) + return -1; + *vm_ret = g_steal_pointer(&vm); return 0; } @@ -134,6 +137,11 @@ static int mymain(void) { virQEMUDriver driver; + virSecurityManagerPtr stack = NULL; + virSecurityManagerPtr dac = NULL; +#ifdef WITH_SELINUX + virSecurityManagerPtr selinux = NULL; +#endif int ret = 0; if (virInitialize() < 0 || @@ -142,15 +150,45 @@ mymain(void) /* Now fix the secdriver */ virObjectUnref(driver.securityManager); - if (!(driver.securityManager = virSecurityManagerNewDAC("test", 1000, 1000, - VIR_SECURITY_MANAGER_PRIVILEGED | - VIR_SECURITY_MANAGER_DYNAMIC_OWNERSHIP, - NULL))) { + + if (!(dac = virSecurityManagerNewDAC("test", 1000, 1000, + VIR_SECURITY_MANAGER_PRIVILEGED | + VIR_SECURITY_MANAGER_DYNAMIC_OWNERSHIP, + NULL))) { fprintf(stderr, "Cannot initialize DAC security driver"); ret = -1; goto cleanup; } + if (!(stack = virSecurityManagerNewStack(dac))) { + fprintf(stderr, "Cannot initialize stack security driver"); + ret = -1; + goto cleanup; + } + dac = NULL; + +#if WITH_SELINUX + selinux = virSecurityManagerNew("selinux", "test", + VIR_SECURITY_MANAGER_PRIVILEGED | + VIR_SECURITY_MANAGER_DEFAULT_CONFINED | + VIR_SECURITY_MANAGER_REQUIRE_CONFINED); + if (!selinux) { + fprintf(stderr, "Cannot initialize selinux security driver"); + ret = -1; + goto cleanup; + } + + if (virSecurityManagerStackAddNested(stack, selinux) < 0) { + fprintf(stderr, "Cannot add selinux security driver onto stack"); + ret = -1; + goto cleanup; + } + selinux = NULL; +#endif + + driver.securityManager = g_steal_pointer(&stack); + + #define DO_TEST_DOMAIN(f) \ do { \ struct testData data = {.driver = &driver, .file = f}; \ @@ -214,6 +252,11 @@ mymain(void) cleanup: qemuTestDriverFree(&driver); +#ifdef WITH_SELINUX + virObjectUnref(selinux); +#endif + virObjectUnref(dac); + virObjectUnref(stack); return ret; } -- 2.26.2