From 531937cf6f3c08021c325ab882171dbea96e5838 Mon Sep 17 00:00:00 2001
From: T-X <linus.luessing@c0d3.blue>
Date: Mon, 4 Oct 2021 21:23:29 +0200
Subject: [PATCH] gluon-neighbour-info: fix broken output with large results
 (#2322)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Currently a buffer with a fixed size of 8192 bytes is used. However the
result can potentially be larger, which leads to a truncated JSON
output on stdout. UDP packets, without compression and with IP
fragmentation, can be up to 64KiB large.

Instead of using a fixed size buffer on the stack ask the kernel first
about the size of the UDP data and allocate a buffer of appropriate size
on the heap before receiving the UDP data.

The issue was observed with a custom respondd provider.

Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
---
 .../src/gluon-neighbour-info.c                | 43 ++++++++++++++++---
 1 file changed, 36 insertions(+), 7 deletions(-)

diff --git a/package/gluon-neighbour-info/src/gluon-neighbour-info.c b/package/gluon-neighbour-info/src/gluon-neighbour-info.c
index 6470508c..119aaddc 100644
--- a/package/gluon-neighbour-info/src/gluon-neighbour-info.c
+++ b/package/gluon-neighbour-info/src/gluon-neighbour-info.c
@@ -69,8 +69,23 @@ void tv_subtract (struct timeval *r, const struct timeval *a, const struct timev
 	}
 }
 
-ssize_t recvtimeout(int socket, void *buffer, size_t length, int flags, const struct timeval *timeout) {
+void resize_recvbuffer(char **recvbuffer, size_t *recvbuffer_len, size_t recvlen)
+{
+	free(*recvbuffer);
+	*recvbuffer = malloc(recvlen);
+
+	if (!(*recvbuffer)) {
+		perror("Could not resize recvbuffer");
+		exit(EXIT_FAILURE);
+	}
+
+	*recvbuffer_len = recvlen;
+}
+
+ssize_t recvtimeout(int socket, char **recvbuffer, size_t *recvbuffer_len,
+		    const struct timeval *timeout) {
 	struct timeval now, timeout_left;
+	ssize_t recvlen;
 
 	getclock(&now);
 	tv_subtract(&timeout_left, timeout, &now);
@@ -79,18 +94,28 @@ ssize_t recvtimeout(int socket, void *buffer, size_t length, int flags, const st
 		return -1;
 
 	setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout_left, sizeof(timeout_left));
-	return recv(socket, buffer, length, flags);
+
+	recvlen = recv(socket, NULL, 0, MSG_PEEK | MSG_TRUNC);
+	if (recvlen < 0)
+		return recvlen;
+
+	if (recvlen > *recvbuffer_len)
+		resize_recvbuffer(recvbuffer, recvbuffer_len, recvlen);
+
+	return recv(socket, *recvbuffer, *recvbuffer_len, 0);
 }
 
-int request(const int sock, const struct sockaddr_in6 *client_addr, const char *request, const char *sse, double timeout, unsigned int max_count) {
+int request(const int sock, char **recvbuffer, size_t *recvbuffer_len,
+	    const struct sockaddr_in6 *client_addr, const char *request,
+	    const char *sse, double timeout, unsigned int max_count) {
 	ssize_t ret;
-	char buffer[8192];
 	unsigned int count = 0;
 
 	ret = sendto(sock, request, strlen(request), 0, (struct sockaddr *)client_addr, sizeof(struct sockaddr_in6));
 
 	if (ret < 0) {
 		perror("Error in sendto()");
+		free(*recvbuffer);
 		exit(EXIT_FAILURE);
 	}
 
@@ -105,7 +130,7 @@ int request(const int sock, const struct sockaddr_in6 *client_addr, const char *
 	}
 
 	do {
-		ret = recvtimeout(sock, buffer, sizeof(buffer), 0, &tv_timeout);
+		ret = recvtimeout(sock, recvbuffer, recvbuffer_len, &tv_timeout);
 
 		if (ret < 0)
 			break;
@@ -116,7 +141,7 @@ int request(const int sock, const struct sockaddr_in6 *client_addr, const char *
 			fputs("data: ", stdout);
 		}
 
-		fwrite(buffer, sizeof(char), ret, stdout);
+		fwrite(*recvbuffer, sizeof(char), ret, stdout);
 
 		if (sse)
 			fputs("\n\n", stdout);
@@ -137,6 +162,8 @@ int main(int argc, char **argv) {
 	int sock;
 	struct sockaddr_in6 client_addr = {};
 	char *request_string = NULL;
+	char *recvbuffer = NULL;
+	size_t recvbuffer_len = 0;
 
 	sock = socket(PF_INET6, SOCK_DGRAM, 0);
 
@@ -243,11 +270,13 @@ int main(int argc, char **argv) {
 	}
 
 	do {
-		ret = request(sock, &client_addr, request_string, sse, timeout, max_count);
+		ret = request(sock, &recvbuffer, &recvbuffer_len, &client_addr,
+			      request_string, sse, timeout, max_count);
 	} while(loop);
 
 	if (sse)
 		fputs("event: eot\ndata: null\n\n", stdout);
 
+	free(recvbuffer);
 	return ret;
 }
-- 
GitLab