From d5f2af7b95b738b25272a98319b09540a0606d14 Mon Sep 17 00:00:00 2001 From: Greg Kurz Date: Tue, 21 Mar 2017 09:12:47 +0100 Subject: [PATCH 1/2] 9pfs: don't try to flush self and avoid QEMU hang on reset According to the 9P spec [*], when a client wants to cancel a pending I/O request identified by a given tag (uint16), it must send a Tflush message and wait for the server to respond with a Rflush message before reusing this tag for another I/O. The server may still send a completion message for the I/O if it wasn't actually cancelled but the Rflush message must arrive after that. QEMU hence waits for the flushed PDU to complete before sending the Rflush message back to the client. If a client sends 'Tflush tag oldtag' and tag == oldtag, QEMU will then allocate a PDU identified by tag, find it in the PDU list and wait for this same PDU to complete... i.e. wait for a completion that will never happen. This causes a tag and ring slot leak in the guest, and a PDU leak in QEMU, all of them limited by the maximal number of PDUs (128). But, worse, this causes QEMU to hang on device reset since v9fs_reset() wants to drain all pending I/O. This insane behavior is likely to denote a bug in the client, and it would deserve an Rerror message to be sent back. Unfortunately, the protocol allows it and requires all flush requests to suceed (only a Tflush response is expected). The only option is to detect when we have to handle a self-referencing flush request and report success to the client right away. [*] http://man.cat-v.org/plan_9/5/flush Reported-by: Al Viro Signed-off-by: Greg Kurz --- hw/9pfs/9p.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/hw/9pfs/9p.c b/hw/9pfs/9p.c index 76c9247c77..b8c0b99358 100644 --- a/hw/9pfs/9p.c +++ b/hw/9pfs/9p.c @@ -2353,7 +2353,7 @@ static void coroutine_fn v9fs_flush(void *opaque) ssize_t err; int16_t tag; size_t offset = 7; - V9fsPDU *cancel_pdu; + V9fsPDU *cancel_pdu = NULL; V9fsPDU *pdu = opaque; V9fsState *s = pdu->s; @@ -2364,9 +2364,13 @@ static void coroutine_fn v9fs_flush(void *opaque) } trace_v9fs_flush(pdu->tag, pdu->id, tag); - QLIST_FOREACH(cancel_pdu, &s->active_list, next) { - if (cancel_pdu->tag == tag) { - break; + if (pdu->tag == tag) { + error_report("Warning: the guest sent a self-referencing 9P flush request"); + } else { + QLIST_FOREACH(cancel_pdu, &s->active_list, next) { + if (cancel_pdu->tag == tag) { + break; + } } } if (cancel_pdu) { From 262169abe74b4c2d8b299b7499904cfc3c1902ea Mon Sep 17 00:00:00 2001 From: Greg Kurz Date: Tue, 21 Mar 2017 09:12:47 +0100 Subject: [PATCH 2/2] 9pfs: proxy: assert if unmarshal fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replies from the virtfs proxy are made up of a fixed-size header (8 bytes) and a payload of variable size (maximum 64kb). When receiving a reply, the proxy backend first reads the whole header and then unmarshals it. If the header is okay, it then does the same operation with the payload. Since the proxy backend uses a pre-allocated buffer which has enough room for a header and the maximum payload size, marshalling should never fail with fixed size arguments. Any error here is likely to result from a more serious corruption in QEMU and we'd better dump core right away. This patch adds error checks where they are missing and converts the associated error paths into assertions. This should also address Coverity's complaints CID 1348519 and CID 1348520, about not always checking the return value of proxy_unmarshal(). Signed-off-by: Greg Kurz Reviewed-by: Philippe Mathieu-Daudé --- hw/9pfs/9p-proxy.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/hw/9pfs/9p-proxy.c b/hw/9pfs/9p-proxy.c index f4aa7a9d70..28b20a7c3d 100644 --- a/hw/9pfs/9p-proxy.c +++ b/hw/9pfs/9p-proxy.c @@ -165,7 +165,8 @@ static int v9fs_receive_response(V9fsProxy *proxy, int type, return retval; } reply->iov_len = PROXY_HDR_SZ; - proxy_unmarshal(reply, 0, "dd", &header.type, &header.size); + retval = proxy_unmarshal(reply, 0, "dd", &header.type, &header.size); + assert(retval == 4 * 2); /* * if response size > PROXY_MAX_IO_SZ, read the response but ignore it and * return -ENOBUFS @@ -194,9 +195,7 @@ static int v9fs_receive_response(V9fsProxy *proxy, int type, if (header.type == T_ERROR) { int ret; ret = proxy_unmarshal(reply, PROXY_HDR_SZ, "d", status); - if (ret < 0) { - *status = ret; - } + assert(ret == 4); return 0; } @@ -213,6 +212,7 @@ static int v9fs_receive_response(V9fsProxy *proxy, int type, &prstat.st_atim_sec, &prstat.st_atim_nsec, &prstat.st_mtim_sec, &prstat.st_mtim_nsec, &prstat.st_ctim_sec, &prstat.st_ctim_nsec); + assert(retval == 8 * 3 + 4 * 3 + 8 * 10); prstat_to_stat(response, &prstat); break; } @@ -225,6 +225,7 @@ static int v9fs_receive_response(V9fsProxy *proxy, int type, &prstfs.f_files, &prstfs.f_ffree, &prstfs.f_fsid[0], &prstfs.f_fsid[1], &prstfs.f_namelen, &prstfs.f_frsize); + assert(retval == 8 * 11); prstatfs_to_statfs(response, &prstfs); break; } @@ -246,7 +247,8 @@ static int v9fs_receive_response(V9fsProxy *proxy, int type, break; } case T_GETVERSION: - proxy_unmarshal(reply, PROXY_HDR_SZ, "q", response); + retval = proxy_unmarshal(reply, PROXY_HDR_SZ, "q", response); + assert(retval == 8); break; default: return -1; @@ -274,18 +276,16 @@ static int v9fs_receive_status(V9fsProxy *proxy, return retval; } reply->iov_len = PROXY_HDR_SZ; - proxy_unmarshal(reply, 0, "dd", &header.type, &header.size); - if (header.size != sizeof(int)) { - *status = -ENOBUFS; - return 0; - } + retval = proxy_unmarshal(reply, 0, "dd", &header.type, &header.size); + assert(retval == 4 * 2); retval = socket_read(proxy->sockfd, reply->iov_base + PROXY_HDR_SZ, header.size); if (retval < 0) { return retval; } reply->iov_len += header.size; - proxy_unmarshal(reply, PROXY_HDR_SZ, "d", status); + retval = proxy_unmarshal(reply, PROXY_HDR_SZ, "d", status); + assert(retval == 4); return 0; }