commit 427120b41a141626dbb40a752c848f199fc9f7a8
Author: Stefan Bühler <stbuehler@web.de>
Date:   Thu May 28 15:47:14 2015 +0000

    escape all strings for logging (fixes #2646 log file injection, reported by Jaanus Kääp)
    
    From: Stefan Bühler <stbuehler@web.de>
    
    git-svn-id: svn://svn.lighttpd.net/lighttpd/branches/lighttpd-1.4.x@2989 152afb58-edef-0310-8abb-c4023f1b3aa9

Index: lighttpd-1.4.20/src/buffer.c
===================================================================
--- lighttpd-1.4.20.orig/src/buffer.c	2017-03-06 18:16:49.318501717 +0100
+++ lighttpd-1.4.20/src/buffer.c	2017-03-07 11:18:44.531828046 +0100
@@ -870,6 +870,168 @@ int buffer_append_string_encoded(buffer
 	return 0;
 }
 
+static size_t buffer_align_size(size_t size) {
+	size_t align = BUFFER_PIECE_SIZE - (size % BUFFER_PIECE_SIZE);
+	/* overflow on unsinged size_t is defined to wrap around */
+	if (size + align < size) return size;
+	return size + align;
+}
+
+int buffer_string_is_empty(const buffer *b) {
+	return 0 == buffer_string_length(b);
+}
+
+/* make sure buffer is at least "size" big. keep old data */
+static void buffer_realloc(buffer *b, size_t size) {
+	force_assert(NULL != b);
+	if (0 == size) size = 1;
+
+	if (size <= b->size) return;
+
+	b->size = buffer_align_size(size);
+	b->ptr = realloc(b->ptr, b->size);
+
+	force_assert(NULL != b->ptr);
+}
+
+static void buffer_alloc(buffer *b, size_t size) {
+	force_assert(NULL != b);
+	if (0 == size) size = 1;
+
+	if (size <= b->size) return;
+
+	if (NULL != b->ptr) free(b->ptr);
+
+	b->used = 0;
+	b->size = buffer_align_size(size);
+	b->ptr = malloc(b->size);
+
+	force_assert(NULL != b->ptr);
+}
+
+char* buffer_string_prepare_copy(buffer *b, size_t size) {
+	force_assert(NULL != b);
+	force_assert(size + 1 > size);
+
+	buffer_alloc(b, size + 1);
+
+	b->used = 1;
+	b->ptr[0] = '\0';
+
+	return b->ptr;
+}
+
+char* buffer_string_prepare_append(buffer *b, size_t size) {
+	force_assert(NULL !=  b);
+
+	if (buffer_string_is_empty(b)) {
+		return buffer_string_prepare_copy(b, size);
+	} else {
+		size_t req_size = b->used + size;
+
+		/* not empty, b->used already includes a terminating 0 */
+		force_assert(req_size >= b->used);
+
+		/* check for overflow: unsigned overflow is defined to wrap around */
+		force_assert(req_size >= b->used);
+
+		buffer_realloc(b, req_size);
+
+		return b->ptr + b->used - 1;
+	}
+}
+
+void buffer_commit(buffer *b, size_t size)
+{
+	force_assert(NULL != b);
+	force_assert(b->size > 0);
+
+	if (0 == b->used) b->used = 1;
+
+	if (size > 0) {
+		/* check for overflow: unsigned overflow is defined to wrap around */
+		force_assert(b->used + size > b->used);
+
+		force_assert(b->used + size <= b->size);
+		b->used += size;
+	}
+
+	b->ptr[b->used - 1] = '\0';
+}
+
+
+void buffer_append_string_c_escaped(buffer *b, const char *s, size_t s_len) {
+	unsigned char *ds, *d;
+	size_t d_len, ndx;
+
+	force_assert(NULL != b);
+	force_assert(NULL != s || 0 == s_len);
+
+	if (0 == s_len) return;
+
+	/* count to-be-encoded-characters */
+	for (ds = (unsigned char *)s, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) {
+		if ((*ds < 0x20) /* control character */
+				|| (*ds >= 0x7f)) { /* DEL + non-ASCII characters */
+			switch (*ds) {
+			case '\t':
+			case '\r':
+			case '\n':
+				d_len += 2;
+				break;
+			default:
+				d_len += 4; /* \xCC */
+				break;
+			}
+		} else {
+			d_len++;
+		}
+	}
+
+	d = (unsigned char*) buffer_string_prepare_append(b, d_len);
+	buffer_commit(b, d_len); /* fill below */
+	force_assert('\0' == *d);
+
+	for (ds = (unsigned char *)s, d_len = 0, ndx = 0; ndx < s_len; ds++, ndx++) {
+		if ((*ds < 0x20) /* control character */
+				|| (*ds >= 0x7f)) { /* DEL + non-ASCII characters */
+			d[d_len++] = '\\';
+			switch (*ds) {
+			case '\t':
+				d[d_len++] = 't';
+				break;
+			case '\r':
+				d[d_len++] = 'r';
+				break;
+			case '\n':
+				d[d_len++] = 'n';
+				break;
+			default:
+				d[d_len++] = 'x';
+				d[d_len++] = hex_chars[((*ds) >> 4) & 0x0F];
+				d[d_len++] = hex_chars[(*ds) & 0x0F];
+				break;
+			}
+		} else {
+			d[d_len++] = *ds;
+		}
+	}
+}
+
+int buffer_is_equal_caseless_string(const buffer *a, const char *s, size_t b_len) {
+	force_assert(NULL != a);
+	if (a->used != b_len + 1) return 0;
+	force_assert('\0' == a->ptr[a->used - 1]);
+
+	return (0 == strcasecmp(a->ptr, s));
+}
+
+void log_failed_assert(const char *filename, unsigned int line, const char *msg) {
+	/* can't use buffer here; could lead to recursive assertions */
+	fprintf(stderr, "%s.%u: %s\n", filename, line, msg);
+	fflush(stderr);
+	abort();
+}
 
 /* decodes url-special-chars inplace.
  * replaces non-printable characters with '_'
Index: lighttpd-1.4.20/src/log.c
===================================================================
--- lighttpd-1.4.20.orig/src/log.c	2017-03-06 18:16:58.950512277 +0100
+++ lighttpd-1.4.20/src/log.c	2017-03-06 18:19:43.222692385 +0100
@@ -208,12 +208,12 @@ int log_error_write(server *srv, const c
 		switch(*fmt) {
 		case 's':           /* string */
 			s = va_arg(ap, char *);
-			buffer_append_string(srv->errorlog_buf, s);
+			buffer_append_string_c_escaped(srv->errorlog_buf, s, (NULL != s) ? strlen(s) : 0);
 			buffer_append_string_len(srv->errorlog_buf, CONST_STR_LEN(" "));
 			break;
 		case 'b':           /* buffer */
 			b = va_arg(ap, buffer *);
-			buffer_append_string_buffer(srv->errorlog_buf, b);
+			buffer_append_string_c_escaped(srv->errorlog_buf, CONST_BUF_LEN(b));
 			buffer_append_string_len(srv->errorlog_buf, CONST_STR_LEN(" "));
 			break;
 		case 'd':           /* int */
@@ -234,11 +234,11 @@ int log_error_write(server *srv, const c
 			break;
 		case 'S':           /* string */
 			s = va_arg(ap, char *);
-			buffer_append_string(srv->errorlog_buf, s);
+			buffer_append_string_c_escaped(srv->errorlog_buf, s, (NULL != s) ? strlen(s) : 0);
 			break;
 		case 'B':           /* buffer */
 			b = va_arg(ap, buffer *);
-			buffer_append_string_buffer(srv->errorlog_buf, b);
+			buffer_append_string_c_escaped(srv->errorlog_buf, CONST_BUF_LEN(b));
 			break;
 		case 'D':           /* int */
 			d = va_arg(ap, int);
Index: lighttpd-1.4.20/src/buffer.h
===================================================================
--- lighttpd-1.4.20.orig/src/buffer.h	2017-03-06 18:16:49.366501769 +0100
+++ lighttpd-1.4.20/src/buffer.h	2017-03-07 11:18:14.819801631 +0100
@@ -33,6 +33,38 @@ typedef struct {
 	size_t size;
 } read_buffer;
 
+/* reset b. if NULL != b && NULL != src, move src content to b. reset src. */
+void buffer_move(buffer *b, buffer *src);
+
+/* make sure buffer is large enough to store a string of given size
+ * and a terminating zero.
+ * sets b to an empty string, and may drop old content.
+ * @return b->ptr
+ */
+char* buffer_string_prepare_copy(buffer *b, size_t size);
+
+/* allocate buffer large enough to be able to append a string of given size
+ * if b was empty (used == 0) it will contain an empty string (used == 1)
+ * afterwards
+ * "used" data is preserved; if not empty buffer must contain a
+ * zero terminated string.
+ */
+char* buffer_string_prepare_append(buffer *b, size_t size);
+
+/* use after prepare_(copy,append) when you have written data to the buffer
+ * to increase the buffer length by size. also sets the terminating zero.
+ * requires enough space is present for the terminating zero (prepare with the
+ * same size to be sure).
+ */
+void buffer_commit(buffer *b, size_t size);
+int buffer_string_is_empty(const buffer *b);
+
+static inline size_t buffer_string_length(const buffer *b); /* buffer string length without terminating 0 */
+static inline size_t buffer_string_length(const buffer *b) {
+	return NULL != b && 0 != b->used ? b->used - 1 : 0;
+}
+
+
 buffer_array* buffer_array_init(void);
 void buffer_array_free(buffer_array *b);
 void buffer_array_reset(buffer_array *b);
@@ -81,6 +113,7 @@ int buffer_is_empty(buffer *b);
 int buffer_is_equal(buffer *a, buffer *b);
 int buffer_is_equal_right_len(buffer *a, buffer *b, size_t len);
 int buffer_is_equal_string(buffer *a, const char *s, size_t b_len);
+int buffer_is_equal_caseless_string(const buffer *a, const char *s, size_t b_len);
 int buffer_caseless_compare(const char *a, size_t a_len, const char *b, size_t b_len);
 
 typedef enum {
@@ -94,6 +127,8 @@ typedef enum {
 } buffer_encoding_t;
 
 int buffer_append_string_encoded(buffer *b, const char *s, size_t s_len, buffer_encoding_t encoding);
+/* escape non-printable characters; simple escapes for \t, \r, \n; fallback to \xCC */
+void buffer_append_string_c_escaped(buffer *b, const char *s, size_t s_len);
 
 int buffer_urldecode_path(buffer *url);
 int buffer_urldecode_query(buffer *url);
@@ -122,9 +157,18 @@ int light_isalnum(int c);
 	if (x->used > 1 && x->ptr[x->used - 2] != '/') { BUFFER_APPEND_STRING_CONST(x, "/"); }
 
 #define CONST_STR_LEN(x) x, x ? sizeof(x) - 1 : 0
-#define CONST_BUF_LEN(x) x->ptr, x->used ? x->used - 1 : 0
+// #define CONST_BUF_LEN(x) x->ptr, x->used ? x->used - 1 : 0
+#define CONST_BUF_LEN(x) ((x) ? (x)->ptr : NULL), buffer_string_length(x)
 
 
+#ifdef __GNUC__
+# define LI_NORETURN __attribute__((noreturn))
+#else
+# define LI_NORETURN
+#endif
+
+void log_failed_assert(const char *filename, unsigned int line, const char *msg) LI_NORETURN;
+#define force_assert(x) do { if (!(x)) log_failed_assert(__FILE__, __LINE__, "assertion failed: " #x); } while(0)
 #define SEGFAULT() do { fprintf(stderr, "%s.%d: aborted\n", __FILE__, __LINE__); abort(); } while(0)
 #define UNUSED(x) ( (void)(x) )
 
