commit dcb89595705170326a44de80a246452329cfc0a1 from: Stefan Sperling date: Mon Sep 08 13:03:29 2025 UTC block gotwebd requests with paths that point outside the repository directory ok op@ 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);