Commit Diff


commit - c3d3a336ffa1b625a5ddcff77bd6aaec86638279
commit + dcb89595705170326a44de80a246452329cfc0a1
blob - beabb467f8b0a218c66f84aa75b3a377276a55c2
blob + 30774393e8a5bd14695eeda37cf4105e6c11f907
--- gotwebd/fcgi.c
+++ gotwebd/fcgi.c
@@ -255,8 +255,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 *
@@ -294,6 +312,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,
@@ -302,6 +323,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,
@@ -311,6 +335,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,
@@ -329,6 +356,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 - cdd5b253e75c190fd7f3c710e9eadeefea473c8a
blob + d4eecc1c2d5aecc314dd3ab30ca5cf784947b3fc
--- gotwebd/gotweb.c
+++ gotwebd/gotweb.c
@@ -929,7 +929,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)
 {
@@ -949,6 +976,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);