302 lines
9.6 KiB
Diff
302 lines
9.6 KiB
Diff
|
From 512c7e92808dff66779f7421f1c17a081f18d7e6 Mon Sep 17 00:00:00 2001
|
||
|
From: Laurent Vivier <lvivier@redhat.com>
|
||
|
Date: Thu, 29 Jul 2021 04:56:46 -0400
|
||
|
Subject: [PATCH 13/14] net: check if the file descriptor is valid before using
|
||
|
it
|
||
|
MIME-Version: 1.0
|
||
|
Content-Type: text/plain; charset=UTF-8
|
||
|
Content-Transfer-Encoding: 8bit
|
||
|
|
||
|
RH-Author: Laurent Vivier <lvivier@redhat.com>
|
||
|
Message-id: <20210726102337.6359-2-lvivier@redhat.com>
|
||
|
Patchwork-id: 101924
|
||
|
O-Subject: [RHEL-8.5.0 qemu-kvm PATCH 1/2] net: check if the file descriptor is valid before using it
|
||
|
Bugzilla: 1982134
|
||
|
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
|
||
|
RH-Acked-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
|
||
|
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
|
||
|
|
||
|
BZ: https://bugzilla.redhat.com/show_bug.cgi?id=1982134
|
||
|
BRANCH: rhel-8.5.0
|
||
|
UPSTREAM: Merged
|
||
|
BREW: https://brewweb.engineering.redhat.com/brew/taskinfo?taskID=38380653
|
||
|
|
||
|
qemu_set_nonblock() checks that the file descriptor can be used and, if
|
||
|
not, crashes QEMU. An assert() is used for that. The use of assert() is
|
||
|
used to detect programming error and the coredump will allow to debug
|
||
|
the problem.
|
||
|
|
||
|
But in the case of the tap device, this assert() can be triggered by
|
||
|
a misconfiguration by the user. At startup, it's not a real problem, but it
|
||
|
can also happen during the hot-plug of a new device, and here it's a
|
||
|
problem because we can crash a perfectly healthy system.
|
||
|
|
||
|
For instance:
|
||
|
# ip link add link virbr0 name macvtap0 type macvtap mode bridge
|
||
|
# ip link set macvtap0 up
|
||
|
# TAP=/dev/tap$(ip -o link show macvtap0 | cut -d: -f1)
|
||
|
# qemu-system-x86_64 -machine q35 -device pcie-root-port,id=pcie-root-port-0 -monitor stdio 9<> $TAP
|
||
|
(qemu) netdev_add type=tap,id=hostnet0,vhost=on,fd=9
|
||
|
(qemu) device_add driver=virtio-net-pci,netdev=hostnet0,id=net0,bus=pcie-root-port-0
|
||
|
(qemu) device_del net0
|
||
|
(qemu) netdev_del hostnet0
|
||
|
(qemu) netdev_add type=tap,id=hostnet1,vhost=on,fd=9
|
||
|
qemu-system-x86_64: .../util/oslib-posix.c:247: qemu_set_nonblock: Assertion `f != -1' failed.
|
||
|
Aborted (core dumped)
|
||
|
|
||
|
To avoid that, add a function, qemu_try_set_nonblock(), that allows to report the
|
||
|
problem without crashing.
|
||
|
|
||
|
In the same way, we also update the function for vhostfd in net_init_tap_one() and
|
||
|
for fd in net_init_socket() (both descriptors are provided by the user and can
|
||
|
be wrong).
|
||
|
|
||
|
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
|
||
|
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
|
||
|
Signed-off-by: Jason Wang <jasowang@redhat.com>
|
||
|
(cherry picked from commit 894022e616016fe81745753f14adfbd680a1c7ee)
|
||
|
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
|
||
|
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
|
||
|
---
|
||
|
include/qemu/sockets.h | 1 +
|
||
|
net/socket.c | 9 +++++--
|
||
|
net/tap.c | 25 +++++++++++++++---
|
||
|
util/oslib-posix.c | 26 +++++++++++++------
|
||
|
util/oslib-win32.c | 57 ++++++++++++++++++++++++------------------
|
||
|
5 files changed, 79 insertions(+), 39 deletions(-)
|
||
|
|
||
|
diff --git a/include/qemu/sockets.h b/include/qemu/sockets.h
|
||
|
index 57cd049d6e..7d1f813576 100644
|
||
|
--- a/include/qemu/sockets.h
|
||
|
+++ b/include/qemu/sockets.h
|
||
|
@@ -18,6 +18,7 @@ int qemu_accept(int s, struct sockaddr *addr, socklen_t *addrlen);
|
||
|
int socket_set_cork(int fd, int v);
|
||
|
int socket_set_nodelay(int fd);
|
||
|
void qemu_set_block(int fd);
|
||
|
+int qemu_try_set_nonblock(int fd);
|
||
|
void qemu_set_nonblock(int fd);
|
||
|
int socket_set_fast_reuse(int fd);
|
||
|
|
||
|
diff --git a/net/socket.c b/net/socket.c
|
||
|
index c92354049b..2d21fddd9c 100644
|
||
|
--- a/net/socket.c
|
||
|
+++ b/net/socket.c
|
||
|
@@ -725,13 +725,18 @@ int net_init_socket(const Netdev *netdev, const char *name,
|
||
|
}
|
||
|
|
||
|
if (sock->has_fd) {
|
||
|
- int fd;
|
||
|
+ int fd, ret;
|
||
|
|
||
|
fd = monitor_fd_param(cur_mon, sock->fd, errp);
|
||
|
if (fd == -1) {
|
||
|
return -1;
|
||
|
}
|
||
|
- qemu_set_nonblock(fd);
|
||
|
+ ret = qemu_try_set_nonblock(fd);
|
||
|
+ if (ret < 0) {
|
||
|
+ error_setg_errno(errp, -ret, "%s: Can't use file descriptor %d",
|
||
|
+ name, fd);
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
if (!net_socket_fd_init(peer, "socket", name, fd, 1, sock->mcast,
|
||
|
errp)) {
|
||
|
return -1;
|
||
|
diff --git a/net/tap.c b/net/tap.c
|
||
|
index 6207f61f84..41a20102fd 100644
|
||
|
--- a/net/tap.c
|
||
|
+++ b/net/tap.c
|
||
|
@@ -689,6 +689,8 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
|
||
|
}
|
||
|
|
||
|
if (vhostfdname) {
|
||
|
+ int ret;
|
||
|
+
|
||
|
vhostfd = monitor_fd_param(cur_mon, vhostfdname, &err);
|
||
|
if (vhostfd == -1) {
|
||
|
if (tap->has_vhostforce && tap->vhostforce) {
|
||
|
@@ -698,7 +700,12 @@ static void net_init_tap_one(const NetdevTapOptions *tap, NetClientState *peer,
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
- qemu_set_nonblock(vhostfd);
|
||
|
+ ret = qemu_try_set_nonblock(vhostfd);
|
||
|
+ if (ret < 0) {
|
||
|
+ error_setg_errno(errp, -ret, "%s: Can't use file descriptor %d",
|
||
|
+ name, fd);
|
||
|
+ return;
|
||
|
+ }
|
||
|
} else {
|
||
|
vhostfd = open("/dev/vhost-net", O_RDWR);
|
||
|
if (vhostfd < 0) {
|
||
|
@@ -766,6 +773,7 @@ int net_init_tap(const Netdev *netdev, const char *name,
|
||
|
Error *err = NULL;
|
||
|
const char *vhostfdname;
|
||
|
char ifname[128];
|
||
|
+ int ret = 0;
|
||
|
|
||
|
assert(netdev->type == NET_CLIENT_DRIVER_TAP);
|
||
|
tap = &netdev->u.tap;
|
||
|
@@ -795,7 +803,12 @@ int net_init_tap(const Netdev *netdev, const char *name,
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
- qemu_set_nonblock(fd);
|
||
|
+ ret = qemu_try_set_nonblock(fd);
|
||
|
+ if (ret < 0) {
|
||
|
+ error_setg_errno(errp, -ret, "%s: Can't use file descriptor %d",
|
||
|
+ name, fd);
|
||
|
+ return -1;
|
||
|
+ }
|
||
|
|
||
|
vnet_hdr = tap_probe_vnet_hdr(fd);
|
||
|
|
||
|
@@ -810,7 +823,6 @@ int net_init_tap(const Netdev *netdev, const char *name,
|
||
|
char **fds;
|
||
|
char **vhost_fds;
|
||
|
int nfds = 0, nvhosts = 0;
|
||
|
- int ret = 0;
|
||
|
|
||
|
if (tap->has_ifname || tap->has_script || tap->has_downscript ||
|
||
|
tap->has_vnet_hdr || tap->has_helper || tap->has_queues ||
|
||
|
@@ -843,7 +855,12 @@ int net_init_tap(const Netdev *netdev, const char *name,
|
||
|
goto free_fail;
|
||
|
}
|
||
|
|
||
|
- qemu_set_nonblock(fd);
|
||
|
+ ret = qemu_try_set_nonblock(fd);
|
||
|
+ if (ret < 0) {
|
||
|
+ error_setg_errno(errp, -ret, "%s: Can't use file descriptor %d",
|
||
|
+ name, fd);
|
||
|
+ goto free_fail;
|
||
|
+ }
|
||
|
|
||
|
if (i == 0) {
|
||
|
vnet_hdr = tap_probe_vnet_hdr(fd);
|
||
|
diff --git a/util/oslib-posix.c b/util/oslib-posix.c
|
||
|
index 8f88e4dbe1..db70416dbb 100644
|
||
|
--- a/util/oslib-posix.c
|
||
|
+++ b/util/oslib-posix.c
|
||
|
@@ -240,25 +240,35 @@ void qemu_set_block(int fd)
|
||
|
assert(f != -1);
|
||
|
}
|
||
|
|
||
|
-void qemu_set_nonblock(int fd)
|
||
|
+int qemu_try_set_nonblock(int fd)
|
||
|
{
|
||
|
int f;
|
||
|
f = fcntl(fd, F_GETFL);
|
||
|
- assert(f != -1);
|
||
|
- f = fcntl(fd, F_SETFL, f | O_NONBLOCK);
|
||
|
-#ifdef __OpenBSD__
|
||
|
if (f == -1) {
|
||
|
+ return -errno;
|
||
|
+ }
|
||
|
+ if (fcntl(fd, F_SETFL, f | O_NONBLOCK) == -1) {
|
||
|
+#ifdef __OpenBSD__
|
||
|
/*
|
||
|
* Previous to OpenBSD 6.3, fcntl(F_SETFL) is not permitted on
|
||
|
* memory devices and sets errno to ENODEV.
|
||
|
* It's OK if we fail to set O_NONBLOCK on devices like /dev/null,
|
||
|
* because they will never block anyway.
|
||
|
*/
|
||
|
- assert(errno == ENODEV);
|
||
|
- }
|
||
|
-#else
|
||
|
- assert(f != -1);
|
||
|
+ if (errno == ENODEV) {
|
||
|
+ return 0;
|
||
|
+ }
|
||
|
#endif
|
||
|
+ return -errno;
|
||
|
+ }
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+void qemu_set_nonblock(int fd)
|
||
|
+{
|
||
|
+ int f;
|
||
|
+ f = qemu_try_set_nonblock(fd);
|
||
|
+ assert(f == 0);
|
||
|
}
|
||
|
|
||
|
int socket_set_fast_reuse(int fd)
|
||
|
diff --git a/util/oslib-win32.c b/util/oslib-win32.c
|
||
|
index 3b49d27297..7eedbe5859 100644
|
||
|
--- a/util/oslib-win32.c
|
||
|
+++ b/util/oslib-win32.c
|
||
|
@@ -132,31 +132,6 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
|
||
|
}
|
||
|
#endif /* CONFIG_LOCALTIME_R */
|
||
|
|
||
|
-void qemu_set_block(int fd)
|
||
|
-{
|
||
|
- unsigned long opt = 0;
|
||
|
- WSAEventSelect(fd, NULL, 0);
|
||
|
- ioctlsocket(fd, FIONBIO, &opt);
|
||
|
-}
|
||
|
-
|
||
|
-void qemu_set_nonblock(int fd)
|
||
|
-{
|
||
|
- unsigned long opt = 1;
|
||
|
- ioctlsocket(fd, FIONBIO, &opt);
|
||
|
- qemu_fd_register(fd);
|
||
|
-}
|
||
|
-
|
||
|
-int socket_set_fast_reuse(int fd)
|
||
|
-{
|
||
|
- /* Enabling the reuse of an endpoint that was used by a socket still in
|
||
|
- * TIME_WAIT state is usually performed by setting SO_REUSEADDR. On Windows
|
||
|
- * fast reuse is the default and SO_REUSEADDR does strange things. So we
|
||
|
- * don't have to do anything here. More info can be found at:
|
||
|
- * http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621.aspx */
|
||
|
- return 0;
|
||
|
-}
|
||
|
-
|
||
|
-
|
||
|
static int socket_error(void)
|
||
|
{
|
||
|
switch (WSAGetLastError()) {
|
||
|
@@ -233,6 +208,38 @@ static int socket_error(void)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
+void qemu_set_block(int fd)
|
||
|
+{
|
||
|
+ unsigned long opt = 0;
|
||
|
+ WSAEventSelect(fd, NULL, 0);
|
||
|
+ ioctlsocket(fd, FIONBIO, &opt);
|
||
|
+}
|
||
|
+
|
||
|
+int qemu_try_set_nonblock(int fd)
|
||
|
+{
|
||
|
+ unsigned long opt = 1;
|
||
|
+ if (ioctlsocket(fd, FIONBIO, &opt) != NO_ERROR) {
|
||
|
+ return -socket_error();
|
||
|
+ }
|
||
|
+ qemu_fd_register(fd);
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+void qemu_set_nonblock(int fd)
|
||
|
+{
|
||
|
+ (void)qemu_try_set_nonblock(fd);
|
||
|
+}
|
||
|
+
|
||
|
+int socket_set_fast_reuse(int fd)
|
||
|
+{
|
||
|
+ /* Enabling the reuse of an endpoint that was used by a socket still in
|
||
|
+ * TIME_WAIT state is usually performed by setting SO_REUSEADDR. On Windows
|
||
|
+ * fast reuse is the default and SO_REUSEADDR does strange things. So we
|
||
|
+ * don't have to do anything here. More info can be found at:
|
||
|
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/ms740621.aspx */
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
int inet_aton(const char *cp, struct in_addr *ia)
|
||
|
{
|
||
|
uint32_t addr = inet_addr(cp);
|
||
|
--
|
||
|
2.27.0
|
||
|
|