CVE-2018-25103: Insufficient Information (NVD-CWE-noinfo)
value: There exists use-after-free vulnerabilities in lighttpd <= 1.4.50 request parsing which might read from invalid pointers to memory used in the same request, not from other requests.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-06-17T18:15Z
Repository: https://github.com/lighttpd/lighttpd1.4
Language: C
Files changed: src/request.c
Commit date: 2018-08-26 18:44:46 +0200
Commit message: [core,security] process headers after combining folded headers

- this fixes various use-after-free scenarios (reported by Or Peles of
  VDOO): when parse_single_header stores pointers to header values in
  con->request, those pointers are not updated if the header value is
  reallocated when folded header lines are appended.
- also remove trailing white-space from folded lines

Diff: diff --git a/src/request.c b/src/request.c
index 2b7fca39..e94d8591 100644
--- a/src/request.c
+++ b/src/request.c
@@ -442,10 +442,15 @@ static void init_parse_header_state(parse_header_state* state) {
  * returns 0 on error
  */
 static int parse_single_header(server *srv, connection *con, parse_header_state *state, data_string *ds) {
 	int cmp = 0;
 
+	/* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
+	if (buffer_string_is_empty(ds->value)) {
+		goto drop_header;
+	}
+
 	/* retreive values
 	 *
 	 *
 	 * the list of options is sorted to simplify the search
 	 */
@@ -573,10 +578,11 @@ invalid_header:
 
 int http_request_parse(server *srv, connection *con) {
 	char *uri = NULL, *proto = NULL, *method = NULL;
 	int is_key = 1, key_len = 0, is_ws_after_key = 0, in_folding;
 	char *value = NULL, *key = NULL;
+	data_string *current_header = NULL;
 
 	int line = 0;
 
 	int request_line_stage = 0;
 	size_t i, first, ilen;
@@ -1022,11 +1028,12 @@ int http_request_parse(server *srv, connection *con) {
 		} else {
 			switch(*cur) {
 			case '\r':
 			case '\n':
 				if (*cur == '\n' || con->parse_request->ptr[i+1] == '\n') {
-					data_string *ds = NULL;
+					int value_len;
+
 					if (*cur == '\n') {
 						if (http_header_strict) {
 							http_request_missing_CR_before_LF(srv, con);
 							goto failure;
 						}
@@ -1036,21 +1043,19 @@ int http_request_parse(server *srv, connection *con) {
 					}
 
 					/* End of Headerline */
 					con->parse_request->ptr[i] = '\0';
 
+					value_len = cur - value;
+
+					/* strip trailing white-spaces */
+					while (value_len > 0 && (value[value_len - 1] == ' ' || value[value_len - 1] == '\t')) {
+						--value_len;
+					}
+
 					if (in_folding) {
-						/**
-						 * we use a evil hack to handle the line-folding
-						 * 
-						 * As array_insert_unique() deletes 'ds' in the case of a duplicate
-						 * ds points somewhere and we get a evil crash. As a solution we keep the old
-						 * "key" and get the current value from the hash and append us
-						 *
-						 * */
-
-						if (!key || !key_len) {
+						if (!current_header) {
 							/* 400 */
 
 							if (srv->srvconf.log_request_header_on_error) {
 								log_error_write(srv, __FILE__, __LINE__, "s", "WS at the start of first line -> 400");
 
@@ -1060,51 +1065,36 @@ int http_request_parse(server *srv, connection *con) {
 							}
 
 							goto failure;
 						}
 
-						if (NULL != (ds = (data_string *)array_get_element_klen(con->request.headers, key, key_len))) {
-							buffer_append_string(ds->value, value);
-						}
+						buffer_append_string_len(current_header->value, value, value_len);
 					} else {
-						int s_len;
-						key = con->parse_request->ptr + first;
-
-						s_len = cur - value;
-
-						/* strip trailing white-spaces */
-						for (; s_len > 0 && 
-								(value[s_len - 1] == ' ' || 
-									value[s_len - 1] == '\t'); s_len--);
-
-						value[s_len] = '\0';
-
-						if (s_len > 0) {
-							if (NULL == (ds = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
-								ds = data_string_init();
-							}
-							buffer_copy_string_len(ds->key, key, key_len);
-							buffer_copy_string_len(ds->value, value, s_len);
-
+						/* process previous header */
+						if (current_header) {
+							data_string *ds = current_header;
+							current_header = NULL;
 							if (!parse_single_header(srv, con, &state, ds)) {
 								/* parse_single_header should already have logged it */
 								goto failure;
 							}
-						} else {
-							/* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
 						}
+
+						key = con->parse_request->ptr + first;
+
+						if (NULL == (current_header = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
+							current_header = data_string_init();
+						}
+
+						buffer_copy_string_len(current_header->key, key, key_len);
+						buffer_copy_string_len(current_header->value, value, value_len);
 					}
 
 					first = i+1;
 					is_key = 1;
 					value = NULL;
-#if 0
-					/**
-					 * for Bug 1230 keep the key_len a live
-					 */
 					key_len = 0; 
-#endif
 					in_folding = 0;
 				} else {
 					if (srv->srvconf.log_request_header_on_error) {
 						log_error_write(srv, __FILE__, __LINE__, "sbs",
 								"CR without LF", con->request.request, "-> 400");
@@ -1130,10 +1120,20 @@ int http_request_parse(server *srv, connection *con) {
 				break;
 			}
 		}
 	}
 
+	/* process last header */
+	if (current_header) {
+		data_string* ds = current_header;
+		current_header = NULL;
+		if (!parse_single_header(srv, con, &state, ds)) {
+			/* parse_single_header should already have logged it */
+			goto failure;
+		}
+	}
+
 	con->header_len = i;
 
 	/* do some post-processing */
 
 	if (con->request.http_version == HTTP_VERSION_1_1) {
@@ -1250,10 +1250,12 @@ int http_request_parse(server *srv, connection *con) {
 	}
 
 	return 0;
 
 failure:
+	if (current_header) current_header->free((data_unset *)current_header);
+
 	con->keep_alive = 0;
 	con->response.keep_alive = 0;
 	if (!con->http_status) con->http_status = 400;
 
 	return 0;

Vulnerable code: 					data_string *ds = NULL;
						/**
						 * we use a evil hack to handle the line-folding
						 * 
						 * As array_insert_unique() deletes 'ds' in the case of a duplicate
						 * ds points somewhere and we get a evil crash. As a solution we keep the old
						 * "key" and get the current value from the hash and append us
						 *
						 * */

						if (!key || !key_len) {
						if (NULL != (ds = (data_string *)array_get_element_klen(con->request.headers, key, key_len))) {
							buffer_append_string(ds->value, value);
						}
						int s_len;
						key = con->parse_request->ptr + first;

						s_len = cur - value;

						/* strip trailing white-spaces */
						for (; s_len > 0 && 
								(value[s_len - 1] == ' ' || 
									value[s_len - 1] == '\t'); s_len--);

						value[s_len] = '\0';

						if (s_len > 0) {
							if (NULL == (ds = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
								ds = data_string_init();
							}
							buffer_copy_string_len(ds->key, key, key_len);
							buffer_copy_string_len(ds->value, value, s_len);

						} else {
							/* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
#if 0
					/**
					 * for Bug 1230 keep the key_len a live
					 */
#endif
Fixed code: 	/* empty header-fields are not allowed by HTTP-RFC, we just ignore them */
	if (buffer_string_is_empty(ds->value)) {
		goto drop_header;
	}

	data_string *current_header = NULL;
					int value_len;

					value_len = cur - value;

					/* strip trailing white-spaces */
					while (value_len > 0 && (value[value_len - 1] == ' ' || value[value_len - 1] == '\t')) {
						--value_len;
					}

						if (!current_header) {
						buffer_append_string_len(current_header->value, value, value_len);
						/* process previous header */
						if (current_header) {
							data_string *ds = current_header;
							current_header = NULL;

						key = con->parse_request->ptr + first;

						if (NULL == (current_header = (data_string *)array_get_unused_element(con->request.headers, TYPE_STRING))) {
							current_header = data_string_init();
						}

						buffer_copy_string_len(current_header->key, key, key_len);
						buffer_copy_string_len(current_header->value, value, value_len);
	/* process last header */
	if (current_header) {
		data_string* ds = current_header;
		current_header = NULL;
		if (!parse_single_header(srv, con, &state, ds)) {
			/* parse_single_header should already have logged it */
			goto failure;
		}
	}

	if (current_header) current_header->free((data_unset *)current_header);