View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update | 
|---|---|---|---|---|---|
| 0008786 | GNUnet | util library | public | 2024-05-03 02:57 | 2024-06-08 12:03 | 
| Reporter | ulfvonbelow | Assigned To | Christian Grothoff | ||
| Priority | normal | Severity | minor | Reproducibility | always | 
| Status | closed | Resolution | fixed | ||
| Platform | x86-64 | OS | Guix System | OS Version | a1d711c92e | 
| Product Version | 0.21.1 | ||||
| Fixed in Version | 0.21.2 | ||||
| Summary | 0008786: use-after-free in finish_client_drop when GNUNET_SERVICE_stop is used | ||||
| Description | The use-after-free is triggered when calling GNUNET_SERVICE_stop on a service that has clients, which to my knowledge currently only occurs in test code, thankfully. It occurs because GNUNET_SERVICE_stop calls GNUNET_SERVICE_client_drop for each of those clients, which schedules a task to run finish_client_drop, which attempts to read from the GNUNET_SERVICE_Handle passed to GNUNET_SERVICE_stop. Immediately after scheduling these tasks, it frees that same handle. This freeing needs to happen only after finish_client_drop has been run for all the clients.  | ||||
| Steps To Reproduce | 1. ./configure --enable-sanitizer ; make ; make check 2. Observe use-after-free reported in a user of transport-testing-communicator.c  | ||||
| Additional Information | A proper fix without duplicating some code may be a bit involved, so for now I've attached a workaround that exploits the properties of the scheduler, which I've used in the process of getting the tests to pass. | ||||
| Tags | No tags attached. | ||||
| Attached Files |  0001-util-temporary-workaround-for-use-after-free-in-fini.patch (2,867 bytes)   
 
From 6accb9703f2071af6f961eb8070ba4c873652ce0 Mon Sep 17 00:00:00 2001
From: ulfvonbelow <striness@tilde.club>
Date: Wed, 1 May 2024 22:32:13 -0500
Subject: [PATCH] util: temporary workaround for use-after-free in
 finish_client_drop.
The use-after-free is triggered when calling GNUNET_SERVICE_stop on a service
that has clients, which to my knowledge currently only occurs in test code,
thankfully. It occurs because GNUNET_SERVICE_stop calls
GNUNET_SERVICE_client_drop for each of those clients, which schedules a task
to run finish_client_drop, which attempts to read from the
GNUNET_SERVICE_Handle passed to GNUNET_SERVICE_stop. Immediately after
scheduling these tasks, it frees that same handle. This freeing needs to
happen only after finish_client_drop has been run for all the clients. The
workaround enqueues a task to free the handle, which due to the scheduler's
queueing behavior should cause it to run only after all the finish_client_drop
tasks for that handle have run. Just in case in the future the
finish_client_drop tasks are scheduled in such a way that this isn't the
case (for example, if they were scheduled with a lower priority), the freeing
task is also scheduled with GNUNET_SCHEDULER_PRIORITY_IDLE.
A proper fix would probably involve making the teardown code paths for
heap-allocated (created with GNUNET_SERVICE_start()) and
stack-allocated (created with GNUNET_SERVICE_run_()) more similar, or just
making it so that both use heap allocation. It's a bit strange to have both
GNUNET_SERVICE_stop and GNUNET_SERVICE_shutdown, which both do more or less
the same thing but in different contexts.
---
 src/lib/util/service.c | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/src/lib/util/service.c b/src/lib/util/service.c
index 7aeabf687..6354718de 100644
--- a/src/lib/util/service.c
+++ b/src/lib/util/service.c
@@ -1904,6 +1904,11 @@ GNUNET_SERVICE_start (const char *service_name,
 }
 
 
+static void free_thing (void *thing)
+{
+  GNUNET_free (thing);
+}
+
 /**
  * Stops a service that was started with #GNUNET_SERVICE_start().
  *
@@ -1916,10 +1921,17 @@ GNUNET_SERVICE_stop (struct GNUNET_SERVICE_Handle *srv)
 
   GNUNET_SERVICE_suspend (srv);
   while (NULL != (client = srv->clients_head))
-    GNUNET_SERVICE_client_drop (client);
+    GNUNET_SERVICE_client_drop (client); // schedules a task that needs srv
   teardown_service (srv);
   GNUNET_free (srv->handlers);
-  GNUNET_free (srv);
+  // ... frees srv
+  /* GNUNET_free (srv); */
+  /* XXX: this relies on either the queue behavior of the scheduler or the
+     priority level of the finish_client_drop task, but should work for
+     now. */
+  GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
+                                      &free_thing,
+                                      srv);
 }
 
 
-- 
2.41.0
 | ||||
| 
		 | 
	Assigning this to grothoff for review. The fix looks ok, but as the OP correctly identifies, the logic in the service seems off... | 
| 
		 | 
	Fixed in d65ae4ba7..db2eb78ec | 
| 
		 | 
	0.21.2 released | 
| Date Modified | Username | Field | Change | 
|---|---|---|---|
| 2024-05-03 02:57 | ulfvonbelow | New Issue | |
| 2024-05-03 02:57 | ulfvonbelow | File Added: 0001-util-temporary-workaround-for-use-after-free-in-fini.patch | |
| 2024-05-05 13:54 | schanzen | Assigned To | => Christian Grothoff | 
| 2024-05-05 13:54 | schanzen | Status | new => assigned | 
| 2024-05-05 13:55 | schanzen | Note Added: 0022340 | |
| 2024-05-08 18:05 | Christian Grothoff | Status | assigned => resolved | 
| 2024-05-08 18:05 | Christian Grothoff | Resolution | open => fixed | 
| 2024-05-08 18:05 | Christian Grothoff | Fixed in Version | => 0.21.2 | 
| 2024-05-08 18:05 | Christian Grothoff | Note Added: 0022375 | |
| 2024-06-08 12:03 | schanzen | Note Added: 0022546 | |
| 2024-06-08 12:03 | schanzen | Status | resolved => closed |