commit 25b639dfa175a8a6684ae2c8666a270fe682523a from: Stefan Sperling via: Thomas Adam date: Mon Sep 08 16:29:32 2025 UTC block gotwebd requests with paths that point outside the repository directory ok op@ 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);