Skip to content

Commit df6a298

Browse files
committed
Fix use-after-free in in_forward conn handler via ref counting
Add refcount mechanism to prevent event handlers from accessing freed connections. Connection destruction is deferred until all event handler references are released. Signed-off-by: Cameron Sparr <[email protected]>
1 parent 8579d63 commit df6a298

File tree

2 files changed

+102
-3
lines changed

2 files changed

+102
-3
lines changed

plugins/in_forward/fw_conn.c

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ int fw_conn_event(void *data)
4545

4646
conn = connection->user_data;
4747

48+
/* Check if connection is being deleted, and increment reference count */
49+
pthread_mutex_lock(&conn->refcount_mutex);
50+
if (conn->pending_deletion) {
51+
/* Connection is being deleted, don't process this event */
52+
pthread_mutex_unlock(&conn->refcount_mutex);
53+
return -1;
54+
}
55+
conn->refcount++;
56+
pthread_mutex_unlock(&conn->refcount_mutex);
57+
4858
ctx = conn->ctx;
4959

5060
event = &connection->event;
@@ -56,12 +66,17 @@ int fw_conn_event(void *data)
5666
ret = fw_prot_secure_forward_handshake(ctx->ins, conn);
5767
if (ret == -1) {
5868
flb_plg_trace(ctx->ins, "fd=%i closed connection", event->fd);
69+
pthread_mutex_lock(&conn->refcount_mutex);
70+
conn->pending_deletion = 1;
71+
pthread_mutex_unlock(&conn->refcount_mutex);
5972
fw_conn_del(conn);
60-
6173
return -1;
6274
}
6375

6476
conn->handshake_status = FW_HANDSHAKE_ESTABLISHED;
77+
78+
/* Release event handler's reference */
79+
fw_conn_del(conn);
6580
return 0;
6681
}
6782

@@ -72,6 +87,9 @@ int fw_conn_event(void *data)
7287
if (conn->buf_size >= ctx->buffer_max_size) {
7388
flb_plg_warn(ctx->ins, "fd=%i incoming data exceed limit (%lu bytes)",
7489
event->fd, (ctx->buffer_max_size));
90+
pthread_mutex_lock(&conn->refcount_mutex);
91+
conn->pending_deletion = 1;
92+
pthread_mutex_unlock(&conn->refcount_mutex);
7593
fw_conn_del(conn);
7694
return -1;
7795
}
@@ -86,6 +104,10 @@ int fw_conn_event(void *data)
86104
tmp = flb_realloc(conn->buf, size);
87105
if (!tmp) {
88106
flb_errno();
107+
pthread_mutex_lock(&conn->refcount_mutex);
108+
conn->pending_deletion = 1;
109+
pthread_mutex_unlock(&conn->refcount_mutex);
110+
fw_conn_del(conn);
89111
return -1;
90112
}
91113
flb_plg_trace(ctx->ins, "fd=%i buffer realloc %i -> %i",
@@ -106,24 +128,40 @@ int fw_conn_event(void *data)
106128
conn->buf_len += bytes;
107129

108130
ret = fw_prot_process(ctx->ins, conn);
131+
109132
if (ret == -1) {
133+
pthread_mutex_lock(&conn->refcount_mutex);
134+
conn->pending_deletion = 1;
135+
pthread_mutex_unlock(&conn->refcount_mutex);
110136
fw_conn_del(conn);
111137
return -1;
112138
}
139+
140+
/* Release event handler's reference */
141+
fw_conn_del(conn);
113142
return bytes;
114143
}
115144
else {
116145
flb_plg_trace(ctx->ins, "fd=%i closed connection", event->fd);
146+
pthread_mutex_lock(&conn->refcount_mutex);
147+
conn->pending_deletion = 1;
148+
pthread_mutex_unlock(&conn->refcount_mutex);
117149
fw_conn_del(conn);
118150
return -1;
119151
}
120152
}
121153

122154
if (event->mask & MK_EVENT_CLOSE) {
123155
flb_plg_trace(ctx->ins, "fd=%i hangup", event->fd);
156+
pthread_mutex_lock(&conn->refcount_mutex);
157+
conn->pending_deletion = 1;
158+
pthread_mutex_unlock(&conn->refcount_mutex);
124159
fw_conn_del(conn);
125160
return -1;
126161
}
162+
163+
/* Release event handler's reference */
164+
fw_conn_del(conn);
127165
return 0;
128166
}
129167

@@ -203,6 +241,20 @@ struct fw_conn *fw_conn_add(struct flb_connection *connection, struct flb_in_fw_
203241
conn->compression_type = FLB_COMPRESSION_ALGORITHM_NONE;
204242
conn->d_ctx = NULL;
205243

244+
/* Initialize reference count to 0 (only counts temporary event handler references) */
245+
conn->refcount = 0;
246+
conn->pending_deletion = 0;
247+
ret = pthread_mutex_init(&conn->refcount_mutex, NULL);
248+
if (ret != 0) {
249+
flb_errno();
250+
if (conn->helo != NULL) {
251+
flb_free(conn->helo);
252+
}
253+
flb_free(conn->buf);
254+
flb_free(conn);
255+
return NULL;
256+
}
257+
206258
/* Register instance into the event loop */
207259
ret = mk_event_add(flb_engine_evl_get(),
208260
connection->fd,
@@ -223,8 +275,16 @@ struct fw_conn *fw_conn_add(struct flb_connection *connection, struct flb_in_fw_
223275
return conn;
224276
}
225277

226-
int fw_conn_del(struct fw_conn *conn)
278+
/* Helper function to destroy connection resources */
279+
static void fw_conn_destroy(struct fw_conn *conn)
227280
{
281+
/* Unregister from event loop first, before freeing anything.
282+
* This prevents any queued events from trying to execute fw_conn_event
283+
* after the connection has been freed, which would cause a use-after-free
284+
* when trying to lock conn->refcount_mutex on line 48 of fw_conn_event.
285+
*/
286+
mk_event_del(flb_engine_evl_get(), &conn->connection->event);
287+
228288
/* The downstream unregisters the file descriptor from the event-loop
229289
* so there's nothing to be done by the plugin
230290
*/
@@ -247,8 +307,27 @@ int fw_conn_del(struct fw_conn *conn)
247307
}
248308
flb_free(conn->helo);
249309
}
310+
311+
/* Destroy the mutex before freeing the connection */
312+
pthread_mutex_destroy(&conn->refcount_mutex);
313+
250314
flb_free(conn->buf);
251315
flb_free(conn);
316+
}
317+
318+
int fw_conn_del(struct fw_conn *conn)
319+
{
320+
int should_free;
321+
322+
/* Decrement reference count and check if we should free */
323+
pthread_mutex_lock(&conn->refcount_mutex);
324+
conn->refcount--;
325+
should_free = (conn->pending_deletion && conn->refcount == 0);
326+
pthread_mutex_unlock(&conn->refcount_mutex);
327+
328+
if (should_free) {
329+
fw_conn_destroy(conn);
330+
}
252331

253332
return 0;
254333
}
@@ -258,10 +337,26 @@ int fw_conn_del_all(struct flb_in_fw_config *ctx)
258337
struct mk_list *tmp;
259338
struct mk_list *head;
260339
struct fw_conn *conn;
340+
int should_free;
261341

262342
mk_list_foreach_safe(head, tmp, &ctx->connections) {
263343
conn = mk_list_entry(head, struct fw_conn, _head);
264-
fw_conn_del(conn);
344+
345+
/* Mark for deletion and check if we should free immediately */
346+
pthread_mutex_lock(&conn->refcount_mutex);
347+
if (conn->pending_deletion) {
348+
pthread_mutex_unlock(&conn->refcount_mutex);
349+
continue;
350+
}
351+
conn->pending_deletion = 1;
352+
should_free = (conn->refcount == 0);
353+
pthread_mutex_unlock(&conn->refcount_mutex);
354+
355+
if (should_free) {
356+
/* No event handler is currently processing, free immediately */
357+
fw_conn_destroy(conn);
358+
}
359+
/* Otherwise, event handler will see pending_deletion and free when done */
265360
}
266361

267362
return 0;

plugins/in_forward/fw_conn.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#ifndef FLB_IN_FW_CONN_H
2121
#define FLB_IN_FW_CONN_H
2222

23+
#include <fluent-bit/flb_pthread.h>
2324
#include <fluent-bit/flb_compression.h>
2425

2526
#define FLB_IN_FW_CHUNK_SIZE "1024000" /* 1MB */
@@ -41,6 +42,9 @@ struct flb_in_fw_helo;
4142

4243
/* Respresents a connection */
4344
struct fw_conn {
45+
int refcount; /* Counts temporary event handler references (starts at 0) */
46+
pthread_mutex_t refcount_mutex; /* Mutex protecting refcount and pending_deletion */
47+
int pending_deletion; /* Tombstone flag: when set, connection will be freed when refcount reaches 0 */
4448
int status; /* Connection status */
4549
int handshake_status; /* handshake status */
4650

0 commit comments

Comments
 (0)