[libvirt] [PATCH 0/5] Make all crashes non CVEs

Currently, whenever a crash occurs it may be very serious. It may even get its own CVE. This patch set tries to resolve the problem. Michal Privoznik (5): Introduce virConnectCrash virutil: Introduce virCrash remote: Implement virConnectCrash qemu: Implement virConnectCrash virsh: Implement virConnectCrash cfg.mk | 2 ++ daemon/remote.c | 30 +++++++++++++++++ include/libvirt/libvirt-host.h | 15 +++++++++ src/driver-hypervisor.h | 7 ++++ src/libvirt-host.c | 45 +++++++++++++++++++++++++ src/libvirt_private.syms | 1 + src/libvirt_public.syms | 1 + src/qemu/qemu_driver.c | 29 ++++++++++++++++ src/remote/remote_driver.c | 32 ++++++++++++++++++ src/remote/remote_protocol.x | 14 +++++++- src/remote_protocol-structs | 5 +++ src/util/virutil.c | 32 ++++++++++++++++++ src/util/virutil.h | 2 ++ tools/virsh-host.c | 75 ++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 9 +++++ 15 files changed, 298 insertions(+), 1 deletion(-) -- 2.7.3

Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- cfg.mk | 2 ++ include/libvirt/libvirt-host.h | 15 ++++++++++++++ src/driver-hypervisor.h | 7 +++++++ src/libvirt-host.c | 45 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 1 + 5 files changed, 70 insertions(+) diff --git a/cfg.mk b/cfg.mk index 8e8586f..6ff97e3 100644 --- a/cfg.mk +++ b/cfg.mk @@ -1133,6 +1133,8 @@ _test1=shunloadtest|virnettlscontexttest|virnettlssessiontest|vircgroupmock exclude_file_name_regexp--sc_avoid_write = \ ^(src/($(_src1))|daemon/libvirtd|tools/virsh-console|tests/($(_test1)))\.c$$ +exclude_file_name_regexp--sc_flags_debug = ^src/libvirt-host\.c$$ + exclude_file_name_regexp--sc_bindtextdomain = ^(tests|examples)/ exclude_file_name_regexp--sc_copyright_usage = \ diff --git a/include/libvirt/libvirt-host.h b/include/libvirt/libvirt-host.h index 8786fbb..80b27aa 100644 --- a/include/libvirt/libvirt-host.h +++ b/include/libvirt/libvirt-host.h @@ -872,5 +872,20 @@ int virNodeAllocPages(virConnectPtr conn, unsigned int cellCount, unsigned int flags); +typedef enum { + VIR_CONNECT_CRASH_SERVER = 0, + VIR_CONNECT_CRASH_CLIENT, +} virConnectCrashSide; + +typedef enum { + VIR_CONNECT_CRASH_WRITE_RO = 0, + VIR_CONNECT_CRASH_NULL_DEREF, + VIR_CONNECT_CRASH_STACK_OVERFLOW, +} virConnectCrashMode; + +int virConnectCrash(virConnectPtr conn, + int side, + int mode, + unsigned int flags); #endif /* __VIR_LIBVIRT_HOST_H__ */ diff --git a/src/driver-hypervisor.h b/src/driver-hypervisor.h index d11ff7f..03e7689 100644 --- a/src/driver-hypervisor.h +++ b/src/driver-hypervisor.h @@ -1238,6 +1238,12 @@ typedef int (*virDrvConnectUnregisterCloseCallback)(virConnectPtr conn, virConnectCloseFunc cb); +typedef int +(*virDrvConnectCrash)(virConnectPtr conn, + int side, + int mode, + unsigned int flags); + typedef struct _virHypervisorDriver virHypervisorDriver; typedef virHypervisorDriver *virHypervisorDriverPtr; @@ -1474,6 +1480,7 @@ struct _virHypervisorDriver { virDrvConnectRegisterCloseCallback connectRegisterCloseCallback; virDrvConnectUnregisterCloseCallback connectUnregisterCloseCallback; virDrvDomainMigrateStartPostCopy domainMigrateStartPostCopy; + virDrvConnectCrash connectCrash; }; diff --git a/src/libvirt-host.c b/src/libvirt-host.c index 24277b7..c458087 100644 --- a/src/libvirt-host.c +++ b/src/libvirt-host.c @@ -1478,3 +1478,48 @@ virNodeAllocPages(virConnectPtr conn, virDispatchError(conn); return -1; } + + +/** + * virConnectCrash: + * @conn: pointer to the hypervisor connection + * @side: which side should the crash occur on (see virConnectCrashSide) + * @mode: which technique should be used to crash (see virConnectCrashMode) + * @flags: extra flags; not used yet, so callers should always pass 0 + * + * Invoke a crash in either client or daemon. + * + * USE THIS API WITH EXTREME CAUTION AS IT MAY CRASH YOUR APPLICATION OR THE DAEMON. + * + * Returns: 0 on success, + * -1 otherwise + */ +int +virConnectCrash(virConnectPtr conn, + int side, + int mode, + unsigned int flags) +{ + VIR_DEBUG("conn=%p side=%d mode=%d flags=%x", + conn, side, mode, flags); + + virResetLastError(); + + virCheckConnectReturn(conn, -1); + virCheckReadOnlyGoto(conn->flags, error); + + if (conn->driver->connectCrash) { + int ret; + + ret = conn->driver->connectCrash(conn, side, mode, flags); + + if (ret < 0) + goto error; + return ret; + } + + virReportUnsupportedError(); + error: + virDispatchError(conn); + return -1; +} diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 1e920d6..2f73e3e 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -730,6 +730,7 @@ LIBVIRT_1.3.3 { virDomainMigrateStartPostCopy; virDomainGetPerfEvents; virDomainSetPerfEvents; + virConnectCrash; } LIBVIRT_1.2.19; # .... define new API here using predicted next version number .... -- 2.7.3

Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/libvirt_private.syms | 1 + src/util/virutil.c | 32 ++++++++++++++++++++++++++++++++ src/util/virutil.h | 2 ++ 3 files changed, 35 insertions(+) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 684f06c..afd7832 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2418,6 +2418,7 @@ virUSBDeviceSetUsedBy; # util/virutil.h +virCrash; virDoubleToStr; virEnumFromString; virEnumToString; diff --git a/src/util/virutil.c b/src/util/virutil.c index b401f8d..44e6c9b 100644 --- a/src/util/virutil.c +++ b/src/util/virutil.c @@ -2669,3 +2669,35 @@ virMemoryMaxValue(bool capped) else return LLONG_MAX; } + +static int +virRecursive(void) +{ + virRecursive(); + return 0; +} + +int +virCrash(int mode) +{ + const char *s = "Hello world!"; + char **tmp; + char *n = NULL; + + switch ((virConnectCrashMode) mode) { + case VIR_CONNECT_CRASH_WRITE_RO: + tmp = (char **) &s; + **tmp = 'h'; + break; + + case VIR_CONNECT_CRASH_NULL_DEREF: + *n = 'L'; + break; + + case VIR_CONNECT_CRASH_STACK_OVERFLOW: + virRecursive(); + break; + } + + return 0; +} diff --git a/src/util/virutil.h b/src/util/virutil.h index b121de0..e5997e4 100644 --- a/src/util/virutil.h +++ b/src/util/virutil.h @@ -265,4 +265,6 @@ unsigned long long virMemoryMaxValue(bool ulong); # define VIR_ASSIGN_IS_OVERFLOW(lvalue, rvalue) \ (((lvalue) = (rvalue)) != (rvalue)) +int virCrash(int mode); + #endif /* __VIR_UTIL_H__ */ -- 2.7.3

Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- daemon/remote.c | 30 ++++++++++++++++++++++++++++++ src/remote/remote_driver.c | 32 ++++++++++++++++++++++++++++++++ src/remote/remote_protocol.x | 14 +++++++++++++- src/remote_protocol-structs | 5 +++++ 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/daemon/remote.c b/daemon/remote.c index 9db93ff..44d92b8 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -6257,3 +6257,33 @@ remoteSerializeDomainDiskErrors(virDomainDiskErrorPtr errors, } return -1; } + + +static int +remoteDispatchConnectCrash(virNetServerPtr server ATTRIBUTE_UNUSED, + virNetServerClientPtr client, + virNetMessagePtr msg ATTRIBUTE_UNUSED, + virNetMessageErrorPtr rerr, + remote_connect_crash_args *args) +{ + int rv = -1; + struct daemonClientPrivate *priv = + virNetServerClientGetPrivateData(client); + + if (!priv->conn) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("connection not open")); + goto cleanup; + } + + if (virConnectCrash(priv->conn, + VIR_CONNECT_CRASH_SERVER, + args->mode, args->flags) < 0) + goto cleanup; + + rv = 0; + + cleanup: + if (rv < 0) + virNetMessageSaveError(rerr); + return rv; +} diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index b03c9ca..0210b0c 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -7450,6 +7450,37 @@ remoteDomainRename(virDomainPtr dom, const char *new_name, unsigned int flags) } +static int +remoteConnectCrash(virConnectPtr conn, + int side, + int mode, + unsigned int flags) +{ + int rv = -1; + struct private_data *priv = conn->privateData; + remote_connect_crash_args args; + + if (side == VIR_CONNECT_CRASH_CLIENT) + return virCrash(mode); + + remoteDriverLock(priv); + + args.mode = mode; + args.flags = flags; + + if (call(conn, priv, 0, REMOTE_PROC_CONNECT_CRASH, + (xdrproc_t)xdr_remote_connect_crash_args, (char *)&args, + (xdrproc_t)xdr_void, (char *)NULL) == -1) { + goto done; + } + + rv = 0; + + done: + remoteDriverUnlock(priv); + return rv; +} + /* get_nonnull_domain and get_nonnull_network turn an on-wire * (name, uuid) pair into virDomainPtr or virNetworkPtr object. * These can return NULL if underlying memory allocations fail, @@ -7805,6 +7836,7 @@ static virHypervisorDriver hypervisor_driver = { .connectRegisterCloseCallback = remoteConnectRegisterCloseCallback, /* 1.3.2 */ .connectUnregisterCloseCallback = remoteConnectUnregisterCloseCallback, /* 1.3.2 */ .domainMigrateStartPostCopy = remoteDomainMigrateStartPostCopy, /* 1.3.3 */ + .connectCrash = remoteConnectCrash, /* 1.3.3 */ }; static virNetworkDriver network_driver = { diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index 8bda792..40ae43f 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -3261,6 +3261,11 @@ struct remote_domain_migrate_start_post_copy_args { unsigned int flags; }; +struct remote_connect_crash_args { + int mode; + unsigned int flags; +}; + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -5781,5 +5786,12 @@ enum remote_procedure { * @generate: both * @acl: domain:write */ - REMOTE_PROC_DOMAIN_SET_PERF_EVENTS = 366 + REMOTE_PROC_DOMAIN_SET_PERF_EVENTS = 366, + + /** + * @generate: none + * @priority: high + * @acl: connect:write + */ + REMOTE_PROC_CONNECT_CRASH = 367 }; diff --git a/src/remote_protocol-structs b/src/remote_protocol-structs index 6dddd52..f564365 100644 --- a/src/remote_protocol-structs +++ b/src/remote_protocol-structs @@ -2730,6 +2730,10 @@ struct remote_domain_migrate_start_post_copy_args { remote_nonnull_domain dom; u_int flags; }; +struct remote_connect_crash_args { + int mode; + u_int flags; +}; enum remote_procedure { REMOTE_PROC_CONNECT_OPEN = 1, REMOTE_PROC_CONNECT_CLOSE = 2, @@ -3097,4 +3101,5 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_MIGRATE_START_POST_COPY = 364, REMOTE_PROC_DOMAIN_GET_PERF_EVENTS = 365, REMOTE_PROC_DOMAIN_SET_PERF_EVENTS = 366, + REMOTE_PROC_CONNECT_CRASH = 367, }; -- 2.7.3

Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- src/qemu/qemu_driver.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 4aa1625..4b394db 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -20131,6 +20131,34 @@ static int qemuDomainRename(virDomainPtr dom, return ret; } +static int qemuConnectCrash(virConnectPtr conn, + int side, + int mode, + unsigned int flags) +{ + virCheckFlags(0, -1); + + if (virConnectCrashEnsureACL(conn) < 0) + return -1; + + if (side != VIR_CONNECT_CRASH_SERVER) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("Only server crashing is supported")); + return -1; + } + + if (mode != VIR_CONNECT_CRASH_WRITE_RO && + mode != VIR_CONNECT_CRASH_NULL_DEREF && + mode != VIR_CONNECT_CRASH_STACK_OVERFLOW) { + virReportError(VIR_ERR_ARGUMENT_UNSUPPORTED, "%s", + _("Invalid mode for crash")); + return -1; + } + + return virCrash(mode); +} + + static virHypervisorDriver qemuHypervisorDriver = { .name = QEMU_DRIVER_NAME, .connectOpen = qemuConnectOpen, /* 0.2.0 */ @@ -20342,6 +20370,7 @@ static virHypervisorDriver qemuHypervisorDriver = { .domainSetUserPassword = qemuDomainSetUserPassword, /* 1.2.16 */ .domainRename = qemuDomainRename, /* 1.2.19 */ .domainMigrateStartPostCopy = qemuDomainMigrateStartPostCopy, /* 1.3.3 */ + .connectCrash = qemuConnectCrash, /* 1.3.3 */ }; -- 2.7.3

Signed-off-by: Michal Privoznik <mprivozn@redhat.com> --- tools/virsh-host.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools/virsh.pod | 9 +++++++ 2 files changed, 84 insertions(+) diff --git a/tools/virsh-host.c b/tools/virsh-host.c index 80ac4bd..8635386 100644 --- a/tools/virsh-host.c +++ b/tools/virsh-host.c @@ -1137,6 +1137,75 @@ cmdCPUModelNames(vshControl *ctl, const vshCmd *cmd) } /* + * "crash" command + */ +static const vshCmdInfo info_crash[] = { + {.name = "help", + .data = N_("Crash either daemon or client") + }, + {.name = "desc", + .data = N_("Crash either daemon or client") + }, + {.name = NULL} +}; + +static const vshCmdOptDef opts_crash[] = { + {.name = "mode", + .type = VSH_OT_STRING, + .help = N_("mode of crash (write, null, stack)") + }, + {.name = "side", + .type = VSH_OT_STRING, + .help = N_("side of crash (client, server)") + }, + {.name = NULL} +}; + +static bool +cmdCrash(vshControl *ctl, const vshCmd *cmd) +{ + const char *modeStr = NULL, *sideStr = NULL; + int mode = VIR_CONNECT_CRASH_NULL_DEREF; + int side = VIR_CONNECT_CRASH_SERVER; + virshControlPtr priv = ctl->privData; + const unsigned int flags = 0; + + if (vshCommandOptStringReq(ctl, cmd, "mode", &modeStr) < 0 || + vshCommandOptStringReq(ctl, cmd, "side", &sideStr) < 0) + return false; + + if (modeStr) { + if (STREQ(modeStr, "write")) { + mode = VIR_CONNECT_CRASH_WRITE_RO; + } else if (STREQ(modeStr, "null")) { + mode = VIR_CONNECT_CRASH_NULL_DEREF; + } else if (STREQ(modeStr, "stack")) { + mode = VIR_CONNECT_CRASH_STACK_OVERFLOW; + } else { + vshError(ctl, _("Unknown crash mode: %s"), modeStr); + return false; + } + } + + if (sideStr) { + if (STREQ(sideStr, "server")) { + side = VIR_CONNECT_CRASH_SERVER; + } else if (STREQ(sideStr, "client")) { + side = VIR_CONNECT_CRASH_CLIENT; + } else { + vshError(ctl, _("Unknown crash side: %s"), sideStr); + return false; + } + } + + if (virConnectCrash(priv->conn, side, mode, flags) < 0) + return false; + + return true; +} + + +/* * "version" command */ static const vshCmdInfo info_version[] = { @@ -1372,6 +1441,12 @@ const vshCmdDef hostAndHypervisorCmds[] = { .info = info_cpu_models, .flags = 0 }, + {.name = "crash", + .handler = cmdCrash, + .opts = opts_crash, + .info = info_crash, + .flags = 0 + }, {.name = "domcapabilities", .handler = cmdDomCapabilities, .opts = opts_domcapabilities, diff --git a/tools/virsh.pod b/tools/virsh.pod index d2cc5b2..bc28d3a 100644 --- a/tools/virsh.pod +++ b/tools/virsh.pod @@ -243,6 +243,15 @@ This command is only available in interactive mode. Will print the current directory. +=item B<crash> [I<mode>] [I<side>] + +Crash either daemon or client. Depending on I<mode> selected +preferred way of crashing can be performed: "write" for +attempting to write to read-only memory, "null" for dereferencing +a NULL pointer, or "stack" for stack overflow. Moreover, I<side> +on which the crash occurs can be chosen from "server" and +"client". B<Use this command with extreme caution!> + =item B<connect> [I<URI>] [I<--readonly>] (Re)-Connect to the hypervisor. When the shell is first started, this -- 2.7.3

On Fri, Apr 01, 2016 at 09:08:05 +0200, Michal Privoznik wrote:
Currently, whenever a crash occurs it may be very serious. It may even get its own CVE. This patch set tries to resolve the problem.
Nice, but I think you could have integrated some interesting ideas from the previous attempt: https://www.redhat.com/archives/libvir-list/2014-April/msg00005.html Jirka

On Fri, Apr 01, 2016 at 09:44:50AM +0200, Jiri Denemark wrote:
On Fri, Apr 01, 2016 at 09:08:05 +0200, Michal Privoznik wrote:
Currently, whenever a crash occurs it may be very serious. It may even get its own CVE. This patch set tries to resolve the problem.
Nice, but I think you could have integrated some interesting ideas from the previous attempt: https://www.redhat.com/archives/libvir-list/2014-April/msg00005.html
Moreover until there is ACL check, the ACL required is connect:write and it's not possible to do this from read-only connection, it won't help us much =D But nice waste of time^W^W^Wtry ;)
Jirka
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list
participants (3)
-
Jiri Denemark
-
Martin Kletzander
-
Michal Privoznik