security_manager: Rework metadata locking

Trying to use virlockd to lock metadata turns out to be too big
gun. Since we will always spawn a separate process for relabeling
we are safe to use thread unsafe POSIX locks and take out
virtlockd completely out of the picture.

Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: John Ferlan <jferlan@redhat.com>
This commit is contained in:
Michal Privoznik 2018-10-02 14:47:20 +02:00
parent a2f0b97ab7
commit 207860927a
4 changed files with 141 additions and 124 deletions

View File

@ -205,6 +205,7 @@ virSecurityDACTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
void *opaque) void *opaque)
{ {
virSecurityDACChownListPtr list = opaque; virSecurityDACChownListPtr list = opaque;
virSecurityManagerMetadataLockStatePtr state;
const char **paths = NULL; const char **paths = NULL;
size_t npaths = 0; size_t npaths = 0;
size_t i; size_t i;
@ -218,14 +219,10 @@ virSecurityDACTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
for (i = 0; i < list->nItems; i++) { for (i = 0; i < list->nItems; i++) {
const char *p = list->items[i]->path; const char *p = list->items[i]->path;
if (!p ||
virFileIsDir(p))
continue;
VIR_APPEND_ELEMENT_COPY_INPLACE(paths, npaths, p); VIR_APPEND_ELEMENT_COPY_INPLACE(paths, npaths, p);
} }
if (virSecurityManagerMetadataLock(list->manager, paths, npaths) < 0) if (!(state = virSecurityManagerMetadataLock(list->manager, paths, npaths)))
goto cleanup; goto cleanup;
} }
@ -249,9 +246,8 @@ virSecurityDACTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
break; break;
} }
if (list->lock && if (list->lock)
virSecurityManagerMetadataUnlock(list->manager, paths, npaths) < 0) virSecurityManagerMetadataUnlock(list->manager, &state);
goto cleanup;
if (rv < 0) if (rv < 0)
goto cleanup; goto cleanup;

View File

@ -21,6 +21,10 @@
*/ */
#include <config.h> #include <config.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "security_driver.h" #include "security_driver.h"
#include "security_stack.h" #include "security_stack.h"
#include "security_dac.h" #include "security_dac.h"
@ -30,14 +34,11 @@
#include "virlog.h" #include "virlog.h"
#include "locking/lock_manager.h" #include "locking/lock_manager.h"
#include "virfile.h" #include "virfile.h"
#include "virtime.h"
#define VIR_FROM_THIS VIR_FROM_SECURITY #define VIR_FROM_THIS VIR_FROM_SECURITY
VIR_LOG_INIT("security.security_manager"); VIR_LOG_INIT("security.security_manager");
virMutex lockManagerMutex = VIR_MUTEX_INITIALIZER;
struct _virSecurityManager { struct _virSecurityManager {
virObjectLockable parent; virObjectLockable parent;
@ -47,10 +48,6 @@ struct _virSecurityManager {
void *privateData; void *privateData;
virLockManagerPluginPtr lockPlugin; virLockManagerPluginPtr lockPlugin;
/* This is a FD that represents a connection to virtlockd so
* that connection is kept open in between MetdataLock() and
* MetadataUnlock() calls. */
int clientfd;
}; };
static virClassPtr virSecurityManagerClass; static virClassPtr virSecurityManagerClass;
@ -66,7 +63,6 @@ void virSecurityManagerDispose(void *obj)
mgr->drv->close(mgr); mgr->drv->close(mgr);
virObjectUnref(mgr->lockPlugin); virObjectUnref(mgr->lockPlugin);
VIR_FORCE_CLOSE(mgr->clientfd);
VIR_FREE(mgr->privateData); VIR_FREE(mgr->privateData);
} }
@ -119,7 +115,6 @@ virSecurityManagerNewDriver(virSecurityDriverPtr drv,
mgr->flags = flags; mgr->flags = flags;
mgr->virtDriver = virtDriver; mgr->virtDriver = virtDriver;
VIR_STEAL_PTR(mgr->privateData, privateData); VIR_STEAL_PTR(mgr->privateData, privateData);
mgr->clientfd = -1;
if (drv->open(mgr) < 0) if (drv->open(mgr) < 0)
goto error; goto error;
@ -1281,129 +1276,153 @@ virSecurityManagerRestoreTPMLabels(virSecurityManagerPtr mgr,
} }
static virLockManagerPtr struct _virSecurityManagerMetadataLockState {
virSecurityManagerNewLockManager(virSecurityManagerPtr mgr, size_t nfds;
const char * const *paths, int *fds;
size_t npaths) };
static int
cmpstringp(const void *p1, const void *p2)
{ {
virLockManagerPtr lock; const char *s1 = *(char * const *) p1;
virLockManagerParam params[] = { const char *s2 = *(char * const *) p2;
{ .type = VIR_LOCK_MANAGER_PARAM_TYPE_UUID,
.key = "uuid",
},
{ .type = VIR_LOCK_MANAGER_PARAM_TYPE_STRING,
.key = "name",
.value = { .cstr = "libvirtd-sec" },
},
{ .type = VIR_LOCK_MANAGER_PARAM_TYPE_UINT,
.key = "pid",
.value = { .iv = getpid() },
},
};
const unsigned int flags = 0;
size_t i;
if (virGetHostUUID(params[0].value.uuid) < 0) if (!s1 && !s2)
return NULL; return 0;
if (!(lock = virLockManagerNew(virLockManagerPluginGetDriver(mgr->lockPlugin), if (!s1 || !s2)
VIR_LOCK_MANAGER_OBJECT_TYPE_DAEMON, return s2 ? -1 : 1;
ARRAY_CARDINALITY(params),
params,
flags)))
return NULL;
for (i = 0; i < npaths; i++) { /* from man 3 qsort */
if (virLockManagerAddResource(lock, return strcmp(s1, s2);
VIR_LOCK_MANAGER_RESOURCE_TYPE_METADATA,
paths[i], 0, NULL, 0) < 0)
goto error;
}
return lock;
error:
virLockManagerFree(lock);
return NULL;
} }
#define METADATA_OFFSET 1
#define METADATA_LEN 1
/* How many seconds should we try to acquire the lock before /**
* giving up. */ * virSecurityManagerMetadataLock:
#define LOCK_ACQUIRE_TIMEOUT 60 * @mgr: security manager object
* @paths: paths to lock
int * @npaths: number of items in @paths array
virSecurityManagerMetadataLock(virSecurityManagerPtr mgr, *
const char * const *paths, * Lock passed @paths for metadata change. The returned state
* should be passed to virSecurityManagerMetadataUnlock.
*
* NOTE: this function is not thread safe (because of usage of
* POSIX locks).
*
* Returns: state on success,
* NULL on failure.
*/
virSecurityManagerMetadataLockStatePtr
virSecurityManagerMetadataLock(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
const char **paths,
size_t npaths) size_t npaths)
{ {
virLockManagerPtr lock; size_t i = 0;
virTimeBackOffVar timebackoff; size_t nfds = 0;
int fd = -1; int *fds = NULL;
int rv = -1; virSecurityManagerMetadataLockStatePtr ret = NULL;
int ret = -1;
virMutexLock(&lockManagerMutex); if (VIR_ALLOC_N(fds, npaths) < 0)
return NULL;
if (!(lock = virSecurityManagerNewLockManager(mgr, paths, npaths))) /* Sort paths to lock in order to avoid deadlocks. */
goto cleanup; qsort(paths, npaths, sizeof(*paths), cmpstringp);
if (virTimeBackOffStart(&timebackoff, 1, LOCK_ACQUIRE_TIMEOUT * 1000) < 0) for (i = 0; i < npaths; i++) {
goto cleanup; const char *p = paths[i];
while (virTimeBackOffWait(&timebackoff)) { struct stat sb;
rv = virLockManagerAcquire(lock, NULL, int retries = 10 * 1000;
VIR_LOCK_MANAGER_ACQUIRE_ROLLBACK, int fd;
VIR_DOMAIN_LOCK_FAILURE_DEFAULT, &fd);
if (rv >= 0) if (!p || stat(p, &sb) < 0)
break;
if (virGetLastErrorCode() == VIR_ERR_RESOURCE_BUSY)
continue; continue;
goto cleanup; if (S_ISDIR(sb.st_mode)) {
/* Directories can't be locked */
continue;
}
if ((fd = open(p, O_RDWR)) < 0) {
if (S_ISSOCK(sb.st_mode)) {
/* Sockets can be opened only if there exists the
* other side that listens. */
continue;
}
virReportSystemError(errno,
_("unable to open %s"),
p);
goto cleanup;
}
do {
if (virFileLock(fd, false,
METADATA_OFFSET, METADATA_LEN, false) < 0) {
if (retries && (errno == EACCES || errno == EAGAIN)) {
/* File is locked. Try again. */
retries--;
usleep(1000);
continue;
} else {
virReportSystemError(errno,
_("unable to lock %s for metadata change"),
p);
VIR_FORCE_CLOSE(fd);
goto cleanup;
}
}
break;
} while (1);
VIR_APPEND_ELEMENT_COPY_INPLACE(fds, nfds, fd);
} }
if (rv < 0) if (VIR_ALLOC(ret) < 0)
goto cleanup; goto cleanup;
mgr->clientfd = fd; VIR_STEAL_PTR(ret->fds, fds);
fd = -1; ret->nfds = nfds;
nfds = 0;
ret = 0;
cleanup: cleanup:
virLockManagerFree(lock); for (i = nfds; i > 0; i--)
VIR_FORCE_CLOSE(fd); VIR_FORCE_CLOSE(fds[i - 1]);
if (ret < 0) VIR_FREE(fds);
virMutexUnlock(&lockManagerMutex);
return ret; return ret;
} }
int void
virSecurityManagerMetadataUnlock(virSecurityManagerPtr mgr, virSecurityManagerMetadataUnlock(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
const char * const *paths, virSecurityManagerMetadataLockStatePtr *state)
size_t npaths)
{ {
virLockManagerPtr lock; size_t i;
int fd;
int ret = -1;
/* lockManagerMutex acquired from previous if (!state)
* virSecurityManagerMetadataLock() call. */ return;
fd = mgr->clientfd; for (i = 0; i < (*state)->nfds; i++) {
mgr->clientfd = -1; char ebuf[1024];
int fd = (*state)->fds[i];
if (!(lock = virSecurityManagerNewLockManager(mgr, paths, npaths))) /* Technically, unlock is not needed because it will
goto cleanup; * happen on VIR_CLOSE() anyway. But let's play it nice. */
if (virFileUnlock(fd, METADATA_OFFSET, METADATA_LEN) < 0) {
VIR_WARN("Unable to unlock fd %d: %s",
fd, virStrerror(errno, ebuf, sizeof(ebuf)));
}
if (virLockManagerRelease(lock, NULL, 0) < 0) if (VIR_CLOSE(fd) < 0) {
goto cleanup; VIR_WARN("Unable to close fd %d: %s",
fd, virStrerror(errno, ebuf, sizeof(ebuf)));
}
}
ret = 0; VIR_FREE((*state)->fds);
cleanup: VIR_FREE(*state);
virLockManagerFree(lock);
VIR_FORCE_CLOSE(fd);
virMutexUnlock(&lockManagerMutex);
return ret;
} }

View File

@ -200,11 +200,16 @@ int virSecurityManagerSetTPMLabels(virSecurityManagerPtr mgr,
int virSecurityManagerRestoreTPMLabels(virSecurityManagerPtr mgr, int virSecurityManagerRestoreTPMLabels(virSecurityManagerPtr mgr,
virDomainDefPtr vm); virDomainDefPtr vm);
int virSecurityManagerMetadataLock(virSecurityManagerPtr mgr, typedef struct _virSecurityManagerMetadataLockState virSecurityManagerMetadataLockState;
const char * const *paths, typedef virSecurityManagerMetadataLockState *virSecurityManagerMetadataLockStatePtr;
size_t npaths);
int virSecurityManagerMetadataUnlock(virSecurityManagerPtr mgr, virSecurityManagerMetadataLockStatePtr
const char * const *paths, virSecurityManagerMetadataLock(virSecurityManagerPtr mgr,
size_t npaths); const char **paths,
size_t npaths);
void
virSecurityManagerMetadataUnlock(virSecurityManagerPtr mgr,
virSecurityManagerMetadataLockStatePtr *state);
#endif /* VIR_SECURITY_MANAGER_H__ */ #endif /* VIR_SECURITY_MANAGER_H__ */

View File

@ -215,6 +215,7 @@ virSecuritySELinuxTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
void *opaque) void *opaque)
{ {
virSecuritySELinuxContextListPtr list = opaque; virSecuritySELinuxContextListPtr list = opaque;
virSecurityManagerMetadataLockStatePtr state;
bool privileged = virSecurityManagerGetPrivileged(list->manager); bool privileged = virSecurityManagerGetPrivileged(list->manager);
const char **paths = NULL; const char **paths = NULL;
size_t npaths = 0; size_t npaths = 0;
@ -229,13 +230,10 @@ virSecuritySELinuxTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
for (i = 0; i < list->nItems; i++) { for (i = 0; i < list->nItems; i++) {
const char *p = list->items[i]->path; const char *p = list->items[i]->path;
if (virFileIsDir(p))
continue;
VIR_APPEND_ELEMENT_COPY_INPLACE(paths, npaths, p); VIR_APPEND_ELEMENT_COPY_INPLACE(paths, npaths, p);
} }
if (virSecurityManagerMetadataLock(list->manager, paths, npaths) < 0) if (!(state = virSecurityManagerMetadataLock(list->manager, paths, npaths)))
goto cleanup; goto cleanup;
} }
@ -253,9 +251,8 @@ virSecuritySELinuxTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
} }
} }
if (list->lock && if (list->lock)
virSecurityManagerMetadataUnlock(list->manager, paths, npaths) < 0) virSecurityManagerMetadataUnlock(list->manager, &state);
goto cleanup;
if (rv < 0) if (rv < 0)
goto cleanup; goto cleanup;