mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2025-02-02 13:47:13 +03:00
qemu: add support for sending QEMU stdout/stderr to virtlogd
Currently the QEMU stdout/stderr streams are written directly to a regular file (eg /var/log/libvirt/qemu/$GUEST.log). While those can be rotated by logrotate (using copytruncate option) this is not very efficient. It also leaves open a window of opportunity for a compromised/broken QEMU to DOS the host filesystem by writing lots of text to stdout/stderr. This makes it possible to connect the stdout/stderr file handles to a pipe that is provided by virtlogd. The virtlogd daemon will read from this pipe and write data to the log file, performing file rotation whenever a pre-determined size limit is reached. Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
This commit is contained in:
parent
a48539c013
commit
0d968ad715
2
cfg.mk
2
cfg.mk
@ -775,7 +775,7 @@ sc_prohibit_gettext_markup:
|
||||
# lower-level code must not include higher-level headers.
|
||||
cross_dirs=$(patsubst $(srcdir)/src/%.,%,$(wildcard $(srcdir)/src/*/.))
|
||||
cross_dirs_re=($(subst / ,/|,$(cross_dirs)))
|
||||
mid_dirs=access|conf|cpu|locking|network|node_device|rpc|security|storage
|
||||
mid_dirs=access|conf|cpu|locking|logging|network|node_device|rpc|security|storage
|
||||
sc_prohibit_cross_inclusion:
|
||||
@for dir in $(cross_dirs); do \
|
||||
case $$dir in \
|
||||
|
@ -71,6 +71,7 @@ module Libvirtd_qemu =
|
||||
| bool_entry "set_process_name"
|
||||
| int_entry "max_processes"
|
||||
| int_entry "max_files"
|
||||
| str_entry "stdio_handler"
|
||||
|
||||
let device_entry = bool_entry "mac_filter"
|
||||
| bool_entry "relaxed_acs_check"
|
||||
|
@ -515,3 +515,18 @@
|
||||
# "/usr/share/OVMF/OVMF_CODE.fd:/usr/share/OVMF/OVMF_VARS.fd",
|
||||
# "/usr/share/AAVMF/AAVMF_CODE.fd:/usr/share/AAVMF/AAVMF_VARS.fd"
|
||||
#]
|
||||
|
||||
# The backend to use for handling stdout/stderr output from
|
||||
# QEMU processes.
|
||||
#
|
||||
# 'file': QEMU writes directly to a plain file. This is the
|
||||
# historical default, but allows QEMU to inflict a
|
||||
# denial of service attack on the host by exhausting
|
||||
# filesystem space
|
||||
#
|
||||
# 'logd': QEMU writes to a pipe provided by virtlogd daemon.
|
||||
# This is the current default, providing protection
|
||||
# against denial of service by performing log file
|
||||
# rollover when a size limit is hit.
|
||||
#
|
||||
#stdio_handler = "logd"
|
||||
|
@ -454,6 +454,7 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg,
|
||||
virConfValuePtr p;
|
||||
int ret = -1;
|
||||
size_t i;
|
||||
char *stdioHandler = NULL;
|
||||
|
||||
/* Just check the file is readable before opening it, otherwise
|
||||
* libvirt emits an error.
|
||||
@ -781,6 +782,23 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg,
|
||||
GET_VALUE_ULONG("max_files", cfg->maxFiles);
|
||||
|
||||
GET_VALUE_STR("lock_manager", cfg->lockManagerName);
|
||||
GET_VALUE_STR("stdio_handler", stdioHandler);
|
||||
if (stdioHandler) {
|
||||
if (STREQ(stdioHandler, "logd")) {
|
||||
cfg->stdioLogD = true;
|
||||
} else if (STREQ(stdioHandler, "file")) {
|
||||
cfg->stdioLogD = false;
|
||||
} else {
|
||||
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
||||
_("Unknown stdio handler %s"),
|
||||
stdioHandler);
|
||||
VIR_FREE(stdioHandler);
|
||||
goto cleanup;
|
||||
}
|
||||
VIR_FREE(stdioHandler);
|
||||
} else {
|
||||
cfg->stdioLogD = true;
|
||||
}
|
||||
|
||||
GET_VALUE_ULONG("max_queued", cfg->maxQueuedJobs);
|
||||
|
||||
|
@ -173,6 +173,7 @@ struct _virQEMUDriverConfig {
|
||||
int migrationPortMax;
|
||||
|
||||
bool logTimestamp;
|
||||
bool stdioLogD;
|
||||
|
||||
/* Pairs of loader:nvram paths. The list is @nloader items long */
|
||||
char **loader;
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "virstring.h"
|
||||
#include "virthreadjob.h"
|
||||
#include "viratomic.h"
|
||||
#include "logging/log_manager.h"
|
||||
|
||||
#include "storage/storage_driver.h"
|
||||
|
||||
@ -81,8 +82,12 @@ VIR_ENUM_IMPL(qemuDomainAsyncJob, QEMU_ASYNC_JOB_LAST,
|
||||
struct _qemuDomainLogContext {
|
||||
int refs;
|
||||
int writefd;
|
||||
int readfd;
|
||||
int readfd; /* Only used if manager == NULL */
|
||||
off_t pos;
|
||||
ino_t inode; /* Only used if manager != NULL */
|
||||
unsigned char uuid[VIR_UUID_BUFLEN]; /* Only used if manager != NULL */
|
||||
char *name; /* Only used if manager != NULL */
|
||||
virLogManagerPtr manager;
|
||||
};
|
||||
|
||||
const char *
|
||||
@ -2285,10 +2290,31 @@ qemuDomainLogContextPtr qemuDomainLogContextNew(virQEMUDriverPtr driver,
|
||||
if (VIR_ALLOC(ctxt) < 0)
|
||||
goto error;
|
||||
|
||||
VIR_DEBUG("Context new %p stdioLogD=%d", ctxt, cfg->stdioLogD);
|
||||
ctxt->writefd = -1;
|
||||
ctxt->readfd = -1;
|
||||
virAtomicIntSet(&ctxt->refs, 1);
|
||||
|
||||
if (cfg->stdioLogD) {
|
||||
ctxt->manager = virLogManagerNew(virQEMUDriverIsPrivileged(driver));
|
||||
if (!ctxt->manager)
|
||||
goto error;
|
||||
|
||||
if (VIR_STRDUP(ctxt->name, vm->def->name) < 0)
|
||||
goto error;
|
||||
|
||||
memcpy(ctxt->uuid, vm->def->uuid, VIR_UUID_BUFLEN);
|
||||
|
||||
ctxt->writefd = virLogManagerDomainOpenLogFile(ctxt->manager,
|
||||
"qemu",
|
||||
vm->def->uuid,
|
||||
vm->def->name,
|
||||
0,
|
||||
&ctxt->inode,
|
||||
&ctxt->pos);
|
||||
if (ctxt->writefd < 0)
|
||||
goto error;
|
||||
} else {
|
||||
if (virAsprintf(&logfile, "%s/%s.log", cfg->logDir, vm->def->name) < 0)
|
||||
goto error;
|
||||
|
||||
@ -2325,6 +2351,7 @@ qemuDomainLogContextPtr qemuDomainLogContextNew(virQEMUDriverPtr driver,
|
||||
logfile);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if ((ctxt->pos = lseek(ctxt->writefd, 0, SEEK_END)) < 0) {
|
||||
virReportSystemError(errno, _("failed to seek in log file %s"),
|
||||
@ -2354,9 +2381,10 @@ int qemuDomainLogContextWrite(qemuDomainLogContextPtr ctxt,
|
||||
|
||||
if (virVasprintf(&message, fmt, argptr) < 0)
|
||||
goto cleanup;
|
||||
if (lseek(ctxt->writefd, 0, SEEK_END) < 0) {
|
||||
if (!ctxt->manager &&
|
||||
lseek(ctxt->writefd, 0, SEEK_END) < 0) {
|
||||
virReportSystemError(errno, "%s",
|
||||
_("Unable to see to end of domain logfile"));
|
||||
_("Unable to seek to end of domain logfile"));
|
||||
goto cleanup;
|
||||
}
|
||||
if (safewrite(ctxt->writefd, message, strlen(message)) < 0) {
|
||||
@ -2377,10 +2405,29 @@ int qemuDomainLogContextWrite(qemuDomainLogContextPtr ctxt,
|
||||
ssize_t qemuDomainLogContextRead(qemuDomainLogContextPtr ctxt,
|
||||
char **msg)
|
||||
{
|
||||
VIR_DEBUG("Context read %p manager=%p inode=%llu pos=%llu",
|
||||
ctxt, ctxt->manager,
|
||||
(unsigned long long)ctxt->inode,
|
||||
(unsigned long long)ctxt->pos);
|
||||
char *buf;
|
||||
size_t buflen = 1024 * 128;
|
||||
size_t buflen;
|
||||
if (ctxt->manager) {
|
||||
buf = virLogManagerDomainReadLogFile(ctxt->manager,
|
||||
"qemu",
|
||||
ctxt->uuid,
|
||||
ctxt->name,
|
||||
ctxt->inode,
|
||||
ctxt->pos,
|
||||
1024 * 128,
|
||||
0);
|
||||
if (!buf)
|
||||
return -1;
|
||||
buflen = strlen(buf);
|
||||
} else {
|
||||
ssize_t got;
|
||||
|
||||
buflen = 1024 * 128;
|
||||
|
||||
/* Best effort jump to start of messages */
|
||||
ignore_value(lseek(ctxt->readfd, ctxt->pos, SEEK_SET));
|
||||
|
||||
@ -2398,9 +2445,12 @@ ssize_t qemuDomainLogContextRead(qemuDomainLogContextPtr ctxt,
|
||||
buf[got] = '\0';
|
||||
|
||||
ignore_value(VIR_REALLOC_N_QUIET(buf, got + 1));
|
||||
buflen = got;
|
||||
}
|
||||
|
||||
*msg = buf;
|
||||
|
||||
return got;
|
||||
return buflen;
|
||||
}
|
||||
|
||||
|
||||
@ -2412,12 +2462,22 @@ int qemuDomainLogContextGetWriteFD(qemuDomainLogContextPtr ctxt)
|
||||
|
||||
void qemuDomainLogContextMarkPosition(qemuDomainLogContextPtr ctxt)
|
||||
{
|
||||
if (ctxt->manager)
|
||||
virLogManagerDomainGetLogFilePosition(ctxt->manager,
|
||||
"qemu",
|
||||
ctxt->uuid,
|
||||
ctxt->name,
|
||||
0,
|
||||
&ctxt->inode,
|
||||
&ctxt->pos);
|
||||
else
|
||||
ctxt->pos = lseek(ctxt->writefd, 0, SEEK_END);
|
||||
}
|
||||
|
||||
|
||||
void qemuDomainLogContextRef(qemuDomainLogContextPtr ctxt)
|
||||
{
|
||||
VIR_DEBUG("Context ref %p", ctxt);
|
||||
virAtomicIntInc(&ctxt->refs);
|
||||
}
|
||||
|
||||
@ -2430,9 +2490,12 @@ void qemuDomainLogContextFree(qemuDomainLogContextPtr ctxt)
|
||||
return;
|
||||
|
||||
lastRef = virAtomicIntDecAndTest(&ctxt->refs);
|
||||
VIR_DEBUG("Context free %p lastref=%d", ctxt, lastRef);
|
||||
if (!lastRef)
|
||||
return;
|
||||
|
||||
virLogManagerFree(ctxt->manager);
|
||||
VIR_FREE(ctxt->name);
|
||||
VIR_FORCE_CLOSE(ctxt->writefd);
|
||||
VIR_FORCE_CLOSE(ctxt->readfd);
|
||||
VIR_FREE(ctxt);
|
||||
|
@ -78,3 +78,4 @@ module Test_libvirtd_qemu =
|
||||
{ "1" = "/usr/share/OVMF/OVMF_CODE.fd:/usr/share/OVMF/OVMF_VARS.fd" }
|
||||
{ "2" = "/usr/share/AAVMF/AAVMF_CODE.fd:/usr/share/AAVMF/AAVMF_VARS.fd" }
|
||||
}
|
||||
{ "stdio_handler" = "logd" }
|
||||
|
Loading…
x
Reference in New Issue
Block a user