|
| 1 | +From 8074d64dc2eca846d6a61efe1a9b7428a0ce1dd1 Mon Sep 17 00:00:00 2001 |
| 2 | +From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= <berrange@redhat.com> |
| 3 | +Date: Tue, 30 Apr 2024 11:51:15 +0100 |
| 4 | +Subject: [PATCH] rpc: ensure temporary GSource is removed from client event |
| 5 | + loop |
| 6 | +MIME-Version: 1.0 |
| 7 | +Content-Type: text/plain; charset=UTF-8 |
| 8 | +Content-Transfer-Encoding: 8bit |
| 9 | + |
| 10 | +Users are seeing periodic segfaults from libvirt client apps, |
| 11 | +especially thread heavy ones like virt-manager. A typical |
| 12 | +stack trace would end up in the virNetClientIOEventFD method, |
| 13 | +with illegal access to stale stack data. eg |
| 14 | + |
| 15 | +==238721==ERROR: AddressSanitizer: stack-use-after-return on address 0x75cd18709788 at pc 0x75cd3111f907 bp 0x75cd181ff550 sp 0x75cd181ff548 |
| 16 | +WRITE of size 4 at 0x75cd18709788 thread T11 |
| 17 | + #0 0x75cd3111f906 in virNetClientIOEventFD /usr/src/debug/libvirt/libvirt-10.2.0/build/../src/rpc/virnetclient.c:1634:15 |
| 18 | + #1 0x75cd3210d198 (/usr/lib/libglib-2.0.so.0+0x5a198) (BuildId: 0a2311dfbbc6c215dc36f4b6bdd2b4b6fbae55a2) |
| 19 | + #2 0x75cd3216c3be (/usr/lib/libglib-2.0.so.0+0xb93be) (BuildId: 0a2311dfbbc6c215dc36f4b6bdd2b4b6fbae55a2) |
| 20 | + #3 0x75cd3210ddc6 in g_main_loop_run (/usr/lib/libglib-2.0.so.0+0x5adc6) (BuildId: 0a2311dfbbc6c215dc36f4b6bdd2b4b6fbae55a2) |
| 21 | + #4 0x75cd3111a47c in virNetClientIOEventLoop /usr/src/debug/libvirt/libvirt-10.2.0/build/../src/rpc/virnetclient.c:1722:9 |
| 22 | + #5 0x75cd3111a47c in virNetClientIO /usr/src/debug/libvirt/libvirt-10.2.0/build/../src/rpc/virnetclient.c:2002:10 |
| 23 | + #6 0x75cd3111a47c in virNetClientSendInternal /usr/src/debug/libvirt/libvirt-10.2.0/build/../src/rpc/virnetclient.c:2170:11 |
| 24 | + #7 0x75cd311198a8 in virNetClientSendWithReply /usr/src/debug/libvirt/libvirt-10.2.0/build/../src/rpc/virnetclient.c:2198:11 |
| 25 | + #8 0x75cd31111653 in virNetClientProgramCall /usr/src/debug/libvirt/libvirt-10.2.0/build/../src/rpc/virnetclientprogram.c:318:9 |
| 26 | + #9 0x75cd31241c8f in callFull /usr/src/debug/libvirt/libvirt-10.2.0/build/../src/remote/remote_driver.c:6054:10 |
| 27 | + #10 0x75cd31241c8f in call /usr/src/debug/libvirt/libvirt-10.2.0/build/../src/remote/remote_driver.c:6076:12 |
| 28 | + #11 0x75cd31241c8f in remoteNetworkGetXMLDesc /usr/src/debug/libvirt/libvirt-10.2.0/build/src/remote/remote_client_bodies.h:5959:9 |
| 29 | + #12 0x75cd31410ff7 in virNetworkGetXMLDesc /usr/src/debug/libvirt/libvirt-10.2.0/build/../src/libvirt-network.c:952:15 |
| 30 | + |
| 31 | +The root cause is a bad assumption in the virNetClientIOEventLoop |
| 32 | +method. This method is run by whichever thread currently owns the |
| 33 | +buck, and is responsible for handling I/O. Inside a for(;;) loop, |
| 34 | +this method creates a temporary GSource, adds it to the event loop |
| 35 | +and runs g_main_loop_run(). When I/O is ready, the GSource callback |
| 36 | +(virNetClientIOEventFD) will fire and call g_main_loop_quit(), and |
| 37 | +return G_SOURCE_REMOVE which results in the temporary GSource being |
| 38 | +destroyed. A g_autoptr() will then remove the last reference. |
| 39 | + |
| 40 | +What was overlooked, is that a second thread can come along and |
| 41 | +while it can't enter virNetClientIOEventLoop, it will register an |
| 42 | +idle source that uses virNetClientIOWakeup to interrupt the |
| 43 | +original thread's 'g_main_loop_run' call. When this happens the |
| 44 | +virNetClientIOEventFD callback never runs, and so the temporary |
| 45 | +GSource is not destroyed. The g_autoptr() will remove a reference, |
| 46 | +but by virtue of still being attached to the event context, there |
| 47 | +is an extra reference held causing GSource to be leaked. The |
| 48 | +next time 'g_main_loop_run' is called, the original GSource will |
| 49 | +trigger its callback, and access data that was allocated on the |
| 50 | +stack by the previous thread, and likely SEGV. |
| 51 | + |
| 52 | +To solve this, the thread calling 'g_main_loop_run' must call |
| 53 | +g_source_destroy, immediately upon return, to guarantee that |
| 54 | +the temporary GSource is removed. |
| 55 | + |
| 56 | +CVE-2024-4418 |
| 57 | +Reviewed-by: Ján Tomko <jtomko@redhat.com> |
| 58 | +Reported-by: Martin Shirokov <shirokovmartin@gmail.com> |
| 59 | +Tested-by: Martin Shirokov <shirokovmartin@gmail.com> |
| 60 | +Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> |
| 61 | +--- |
| 62 | + src/rpc/virnetclient.c | 14 +++++++++++++- |
| 63 | + 1 file changed, 13 insertions(+), 1 deletion(-) |
| 64 | + |
| 65 | +diff --git a/src/rpc/virnetclient.c b/src/rpc/virnetclient.c |
| 66 | +index 68098b1c8d..147b0d661a 100644 |
| 67 | +--- a/src/rpc/virnetclient.c |
| 68 | ++++ b/src/rpc/virnetclient.c |
| 69 | +@@ -1657,7 +1657,7 @@ static int virNetClientIOEventLoop(virNetClient *client, |
| 70 | + #endif /* !WIN32 */ |
| 71 | + int timeout = -1; |
| 72 | + virNetMessage *msg = NULL; |
| 73 | +- g_autoptr(GSource) G_GNUC_UNUSED source = NULL; |
| 74 | ++ g_autoptr(GSource) source = NULL; |
| 75 | + GIOCondition ev = 0; |
| 76 | + struct virNetClientIOEventData data = { |
| 77 | + .client = client, |
| 78 | +@@ -1721,6 +1721,18 @@ static int virNetClientIOEventLoop(virNetClient *client, |
| 79 | + |
| 80 | + g_main_loop_run(client->eventLoop); |
| 81 | + |
| 82 | ++ /* |
| 83 | ++ * If virNetClientIOEventFD ran, this GSource will already be |
| 84 | ++ * destroyed due to G_SOURCE_REMOVE. It is harmless to re-destroy |
| 85 | ++ * it, since we still own a reference. |
| 86 | ++ * |
| 87 | ++ * If virNetClientIOWakeup ran, it will have interrupted the |
| 88 | ++ * g_main_loop_run call, before virNetClientIOEventFD could |
| 89 | ++ * run, and thus the GSource is still registered, and we need |
| 90 | ++ * to destroy it since it is referencing stack memory for 'data' |
| 91 | ++ */ |
| 92 | ++ g_source_destroy(source); |
| 93 | ++ |
| 94 | + #ifndef WIN32 |
| 95 | + ignore_value(pthread_sigmask(SIG_SETMASK, &oldmask, NULL)); |
| 96 | + #endif /* !WIN32 */ |
| 97 | +-- |
| 98 | +GitLab |
0 commit comments