qemusecuritytest: Test SELinux too

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@redhat.com>
Reviewed-by: Andrea Bolognani <abologna@redhat.com>
This commit is contained in:
Michal Privoznik 2020-11-02 22:31:03 +01:00
parent d337543f06
commit df8ff46a16
4 changed files with 240 additions and 10 deletions

View File

@ -0,0 +1,2 @@
system_u:system_r:svirt_t:s0
system_u:system_r:svirt_tcg_t:s0

View File

@ -0,0 +1,2 @@
system_u:object_r:svirt_image_t:s0
system_u:object_r:virt_content_t:s0

View File

@ -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 is 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

View File

@ -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;
}