Hello LEDE developers,

I found a bug in procd that gets triggered when long lines are printed
by services whose stdout/stderr are being logged. The bug itself is
explained in the attached patch.

However, when I was testing the fix, I found out that the bug is present
in other places, mostly those that call "ustream_get_read_buf" function.
Some examples:

- ubox/log/logread.c:logread_fb_data_cb() - when buffer passed on the
descriptor is larger than 4096, reception stops
- netifd/main.c:netifd_process_log_read_cb - this is a place that seems
to have this bug fixed, but still has incorrect handling of NUL bytes
- libubox/examples/ustream-example.c:client_read_cb - this is probably
the place that originated this broken bit of code
- uhttpd/relay.c:relay_process_headers - another place that seems broken

I've attached an init script (that goes to /etc/init.d/flood) and three
Lua programs (flood[123].lua) that trigger this behavior:
- flood1.lua writes long message to stdout, that triggers this behavior
in procd
- flood2.lua writes message that gets correctly processed by procd, but
triggers the bug in logread
- flood3.lua writes message with embedded zeros

I don't post patches to mailing lists very often, so I apologize if I'm
sending this in a wrong format or in a too broken english.

Best regards,
Jakub Horak
#!/bin/sh /etc/rc.common

START=99

USE_PROCD=1
PROG=/bin/flood1.lua

start_service()
{
        config_load cgminer

        procd_open_instance
        procd_set_param command "$PROG" 
        procd_set_param respawn ${respawn_threshold:-3600} 
${respawn_timeout:-5} ${respawn_retry:-0}
        procd_set_param stdout 1
        procd_set_param stderr 1
        procd_close_instance
}

stop_service()
{
        echo stop
}

service_triggers()
{
        procd_add_reload_trigger "flood"
}
#!/usr/bin/lua
require 'nixio'

print('flood 1 starting')
print(('x'):rep(4096))
print('flood 1 done')
io.stdout:flush()

while true do
	print('flood 1 tick')
	nixio.nanosleep(1)
end
#!/usr/bin/lua
require 'nixio'

print('flood 2 starting')
print(('x'):rep(4050))
print('flood 2 done')
io.stdout:flush()

while true do
	nixio.nanosleep(1)
end
#!/usr/bin/lua
require 'nixio'

print('flood 3 starting')
io.write('hello\0world')
io.stdout:flush()
print('flood 3 done')
io.stdout:flush()

while true do
	nixio.nanosleep(1)
end
From c4ea27994bfa81e38dff9ccf9a92a33b5c612407 Mon Sep 17 00:00:00 2001
From: Jakub Horak <jakub.ho...@braiins.cz>
Date: Thu, 11 Jan 2018 16:47:50 +0100
Subject: [PATCH] procd: Fix behavior when parsing long lines

Processing of service stdout/stderr is silently stopped when long lines (>4096 bytes)
are encountered. Moreover, when NUL byte (0) is received on input, the
processing is stopped as well.

When reading long lines procd waits until a newline character is received
even though receive buffer is full. At that moment the file-descriptor is
removed from epoll and communication stopson that descriptor stops. Similar
thing happens with NUL byte, only that the newline is not found because "NUL"
stops strchr() from processing further down the string.

This patch corrects the behavior: long lines are broken into smaller ones with
forced newline and NUL bytes are skipped when searching for a newline.

Signed-off-by: Jakub Horak <jakub.ho...@braiins.cz>
---
 service/instance.c | 26 ++++++++++++++++++--------
 1 file changed, 18 insertions(+), 8 deletions(-)

diff --git a/service/instance.c b/service/instance.c
index a968a0b..0293099 100644
--- a/service/instance.c
+++ b/service/instance.c
@@ -461,24 +461,34 @@ instance_stdio(struct ustream *s, int prio, struct service_instance *in)
 {
 	char *newline, *str, *arg0, ident[32];
 	int len;
+	int buflen;
 
 	arg0 = basename(blobmsg_data(blobmsg_data(in->command)));
 	snprintf(ident, sizeof(ident), "%s[%d]", arg0, in->proc.pid);
 	ulog_open(ULOG_SYSLOG, LOG_DAEMON, ident);
 
 	do {
-		str = ustream_get_read_buf(s, NULL);
+		str = ustream_get_read_buf(s, &buflen);
 		if (!str)
 			break;
 
-		newline = strchr(str, '\n');
-		if (!newline)
-			break;
-
-		*newline = 0;
+		/* search for '\n', take into account NUL bytes */
+		newline = memchr(str, '\n', buflen);
+		if (!newline) {
+			/* is there a room in buffer? */
+			if (buflen < s->r.buffer_len) {
+				/* yes - bailout, newline may still come */
+				break;
+			} else {
+				/* no - force newline */
+				len = buflen;
+			}
+		} else {
+			*newline = 0;
+			len = newline + 1 - str;
+		}
+		/* "str" is NUL terminated by ustream_get_read_buf */
 		ulog(prio, "%s\n", str);
-
-		len = newline + 1 - str;
 		ustream_consume(s, len);
 	} while (1);
 
-- 
2.7.4

Attachment: signature.asc
Description: OpenPGP digital signature

_______________________________________________
Lede-dev mailing list
Lede-dev@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/lede-dev

Reply via email to