From a655d8dc9a1c641b8d55312cc827e50e3e8cb2de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Je=C5=BEek?= <lukas.jezek@nic.cz>
Date: Fri, 4 Dec 2020 06:57:26 +0100
Subject: [PATCH] doh2: restricts endpoints to doh and dns-query

---
 NEWS                       |  1 +
 daemon/http.c              | 73 +++++++++++++++++++++++++++++++++++++-
 tests/config/doh2.test.lua |  2 +-
 3 files changed, 74 insertions(+), 2 deletions(-)

diff --git a/NEWS b/NEWS
index 9d8cde991..b0d4ed670 100644
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,7 @@ Knot Resolver X.Y.X (yyyy-mm-dd)
 Bugfixes
 --------
 - fix map() command on 32-bit platforms; regressed in 5.2.0 (!1093)
+- doh2: restrict endpoints to doh and dns-query (#636, !1104)
 
 
 Knot Resolver 5.2.0 (2020-11-11)
diff --git a/daemon/http.c b/daemon/http.c
index beccac202..a44ecdd06 100644
--- a/daemon/http.c
+++ b/daemon/http.c
@@ -19,6 +19,7 @@
 #include "lib/layer/iterate.h" /* kr_response_classify */
 #include "lib/cache/util.h"
 
+#include "contrib/cleanup.h"
 #include "contrib/base64url.h"
 
 #define MAKE_NV(K, KS, V, VS) \
@@ -130,6 +131,72 @@ static int send_data_callback(nghttp2_session *h2, nghttp2_frame *frame, const u
 	return 0;
 }
 
+/*
+ * Check endpoint and uri path
+ */
+static int check_uri(const char* uri_path)
+{
+	static const char key[] = "dns=";
+	static const char *delim = "&";
+	static const char *endpoins[] = {"dns-query", "doh"};
+	char *beg;
+	char *end_prev;
+	ssize_t endpoint_len;
+	ssize_t ret;
+
+	if (!uri_path)
+		return kr_error(EINVAL);
+
+	auto_free char *path = malloc(sizeof(*path) * (strlen(uri_path) + 1));
+	if (!path)
+		return kr_error(ENOMEM);
+
+	memcpy(path, uri_path, strlen(uri_path));
+	path[strlen(uri_path)] = '\0';
+
+	char *query_mark = strstr(path, "?");
+
+	/* calculating of endpoint_len - for POST or GET method */
+	endpoint_len = (query_mark) ? query_mark - path - 1 : strlen(path) - 1;
+
+	/* check endpoint */
+	ret = -1;
+	for(int i = 0; i < sizeof(endpoins)/sizeof(*endpoins); i++)
+	{
+		if (strlen(endpoins[i]) != endpoint_len)
+			continue;
+		ret = strncmp(path + 1, endpoins[i], strlen(endpoins[i]));
+		if (!ret)
+			break;
+	}
+
+	if (ret) /* no endpoint found */
+		return -1;
+	if (endpoint_len == strlen(path) - 1) /* done for POST method */
+		return 0;
+
+	/* go over key:value pair */
+	beg = strtok(query_mark + 1, delim);
+	if (beg) {
+		while (beg != NULL) {
+			if (!strncmp(beg, key, 4)) { /* dns variable in path found */
+				break;
+			}
+			end_prev = beg + strlen(beg);
+			beg = strtok(NULL, delim);
+			if (beg-1 != end_prev) { /* detect && */
+				return -1;
+			}
+		}
+
+		if (!beg) { /* no dns variable in path */
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
 /*
  * Process a query from URI path if there's base64url encoded dns variable.
  */
@@ -217,7 +284,6 @@ static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame,
 	struct http_ctx *ctx = (struct http_ctx *)user_data;
 	int32_t stream_id = frame->hd.stream_id;
 
-
 	if (frame->hd.type != NGHTTP2_HEADERS)
 		return 0;
 
@@ -229,6 +295,11 @@ static int header_callback(nghttp2_session *h2, const nghttp2_frame *frame,
 	}
 
 	if (!strcasecmp(":path", (const char *)name)) {
+		if (check_uri((const char *)value) < 0) {
+			refuse_stream(h2, stream_id);
+			return 0;
+		}
+
 		ctx->uri_path = malloc(sizeof(*ctx->uri_path) * (valuelen + 1));
 		if (!ctx->uri_path)
 			return kr_error(ENOMEM);
diff --git a/tests/config/doh2.test.lua b/tests/config/doh2.test.lua
index a97fc0692..2c4779e90 100644
--- a/tests/config/doh2.test.lua
+++ b/tests/config/doh2.test.lua
@@ -96,7 +96,7 @@ else
 	local function start_server()
 		local request = require('http.request')
 		local ssl_ctx = require('openssl.ssl.context')
-		uri_templ = string.format('https://%s:%d/dns_query', host, port)
+		uri_templ = string.format('https://%s:%d/dns-query', host, port)
 		req_templ = assert(request.new_from_uri(uri_templ))
 		req_templ.headers:upsert('content-type', 'application/dns-message')
 		req_templ.ctx = ssl_ctx.new()
-- 
GitLab