defer free of thread-local dlerror buffers from inconsistent context
authorRich Felker <dalias@aerifal.cx>
Fri, 15 Feb 2019 19:20:49 +0000 (14:20 -0500)
committerRich Felker <dalias@aerifal.cx>
Fri, 15 Feb 2019 19:20:49 +0000 (14:20 -0500)
__dl_thread_cleanup is called from the context of an exiting thread
that is not in a consistent state valid for calling application code.
since commit c9f415d7ea2dace5bf77f6518b6afc36bb7a5732, it's possible
(and supported usage) for the allocator to have been replaced by the
application, so __dl_thread_cleanup can no longer call free. instead,
reuse the message buffer as a linked-list pointer, and queue it to be
freed the next time any dynamic linker error message is generated.

src/ldso/dlerror.c

index 06ed8542ca475e8f72b232e6dde9c6868c81427a..3fcc7779530234fa7734c464876a70b8f2e9bab4 100644 (file)
@@ -3,6 +3,7 @@
 #include <stdarg.h>
 #include "pthread_impl.h"
 #include "dynlink.h"
+#include "lock.h"
 
 char *dlerror()
 {
@@ -16,21 +17,38 @@ char *dlerror()
                return s;
 }
 
+static volatile int freebuf_queue_lock[1];
+static void **freebuf_queue;
+
 void __dl_thread_cleanup(void)
 {
        pthread_t self = __pthread_self();
-       if (self->dlerror_buf != (void *)-1)
-               free(self->dlerror_buf);
+       if (self->dlerror_buf && self->dlerror_buf != (void *)-1) {
+               LOCK(freebuf_queue_lock);
+               void **p = (void **)self->dlerror_buf;
+               *p = freebuf_queue;
+               freebuf_queue = p;
+               UNLOCK(freebuf_queue_lock);
+       }
 }
 
 hidden void __dl_vseterr(const char *fmt, va_list ap)
 {
+       LOCK(freebuf_queue_lock);
+       while (freebuf_queue) {
+               void **p = freebuf_queue;
+               freebuf_queue = *p;
+               free(p);
+       }
+       UNLOCK(freebuf_queue_lock);
+
        va_list ap2;
        va_copy(ap2, ap);
        pthread_t self = __pthread_self();
        if (self->dlerror_buf != (void *)-1)
                free(self->dlerror_buf);
        size_t len = vsnprintf(0, 0, fmt, ap2);
+       if (len < sizeof(void *)) len = sizeof(void *);
        va_end(ap2);
        char *buf = malloc(len+1);
        if (buf) {