Commit Diff


commit - 75be285f4927f1f4a848adddfc4e222964c6d3cb
commit + 25b639dfa175a8a6684ae2c8666a270fe682523a
blob - 6898ac02358b299591e973e11de6e3084e56b19a
blob + bce946124246ed3e4d02dac9215688e76847db91
--- gotwebd/fcgi.c
+++ gotwebd/fcgi.c
@@ -257,8 +257,26 @@ urldecode(char *url)
 		q++;
 	}
 	*q = '\0';
+
+	return NULL;
+}
+
+static const struct got_error *
+validate_path(const char *path)
+{
+	size_t len = strlen(path);
+
+	if (len >= 3 && strncmp(path, "../", 3) == 0)
+		return got_error(GOT_ERR_BAD_QUERYSTRING);
+
+	if (len >= 4 && strstr(path, "/../") != NULL)
+		return got_error(GOT_ERR_BAD_QUERYSTRING);
+
+	if (len >= 3 && strcmp(path + len - 3, "/..") == 0)
+		return got_error(GOT_ERR_BAD_QUERYSTRING);
 
 	return NULL;
+
 }
 
 static const struct got_error *
@@ -296,6 +314,9 @@ assign_querystring(struct querystring *qs, char *key, 
 			}
 			break;
 		case RFILE:
+			error = validate_path(value);
+			if (error)
+				goto done;
 			if (strlcpy(qs->file, value, sizeof(qs->file)) >=
 			    sizeof(qs->file)) {
 				error = got_error_msg(GOT_ERR_NO_SPACE,
@@ -304,6 +325,9 @@ assign_querystring(struct querystring *qs, char *key, 
 			}
 			break;
 		case FOLDER:
+			error = validate_path(value);
+			if (error)
+				goto done;
 			if (strlcpy(qs->folder, value[0] ? value : "/",
 			    sizeof(qs->folder)) >= sizeof(qs->folder)) {
 				error = got_error_msg(GOT_ERR_NO_SPACE,
@@ -313,6 +337,9 @@ assign_querystring(struct querystring *qs, char *key, 
 			}
 			break;
 		case HEADREF:
+			error = validate_path(value);
+			if (error)
+				goto done;
 			if (strlcpy(qs->headref, value, sizeof(qs->headref)) >=
 			    sizeof(qs->headref)) {
 				error = got_error_msg(GOT_ERR_NO_SPACE,
@@ -331,6 +358,9 @@ assign_querystring(struct querystring *qs, char *key, 
 			}
 			break;
 		case PATH:
+			error = validate_path(value);
+			if (error)
+				goto done;
 			if (strlcpy(qs->path, value, sizeof(qs->path)) >=
 			    sizeof(qs->path)) {
 				error = got_error_msg(GOT_ERR_NO_SPACE,
blob - cd86e3c4c5f7642b3a24a00cff7fa8c3966616ac
blob + 4e02723cfa513d44a94c8ecd52378d1dba8ac7de
--- gotwebd/gotweb.c
+++ gotwebd/gotweb.c
@@ -927,7 +927,34 @@ gotweb_render_absolute_url(struct request *c, struct g
 	return gotweb_render_url(c, url);
 }
 
+/* 
+ * Ensure that a path sent in the request will not escape from the given
+ * parent directory. This matters for got-portable where we are not
+ * necessarily running in chroot and cannot be protected by unveil(2).
+ */
 static const struct got_error *
+validate_path(const char *path, const char *parent_path,
+    const char *orig_path)
+{
+	const struct got_error *error = NULL;
+	char *abspath;
+
+	abspath = realpath(path, NULL);
+	if (abspath == NULL) {
+		/* Paths which cannot be resolved are safe. */
+		if (errno == ENOENT || errno == EACCES || errno == ENOTDIR)
+			return NULL;
+		return got_error_from_errno("realpath");
+	}
+
+	if (!got_path_is_child(abspath, parent_path, strlen(parent_path)))
+		error = got_error_path(orig_path, GOT_ERR_NOT_GIT_REPO);
+
+	free(abspath);
+	return error;
+}
+
+static const struct got_error *
 gotweb_load_got_path(struct repo_dir **rp, const char *dir,
     struct request *c)
 {
@@ -947,6 +974,12 @@ gotweb_load_got_path(struct repo_dir **rp, const char 
 	    GOTWEB_GIT_DIR) == -1)
 		return got_error_from_errno("asprintf");
 
+	error = validate_path(dir_test, srv->repos_path, dir);
+	if (error) {
+		free(dir_test);
+		return error;
+	}
+
 	dt = opendir(dir_test);
 	if (dt == NULL) {
 		free(dir_test);