Jeff King <peff@xxxxxxxx> writes: > diff --git a/environment.c b/environment.c > index bb518c61cd..7c233e0e0e 100644 > --- a/environment.c > +++ b/environment.c > @@ -73,6 +73,7 @@ int merge_log_config = -1; > int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ > unsigned long pack_size_limit_cfg; > enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; > +int allow_external_symlinks = 1; OK, so by default it is not blocked... > +static int symlink_leaves_repo(const char *target, const char *linkpath) > +{ > + /* > + * Absolute paths are always considered to leave the repository (even > + * if they happen to point to the working tree path). > + */ > + if (is_absolute_path(target)) > + return 1; Very sensible. > + /* > + * Allow relative paths that start with a sequence of "../", > + * as long as they do not break out of the symlink's root. > + * This loop will detect break-out cases and return; otherwise, at the > + * end of the loop "target" will point to the first non-".." component. > + * > + * We count the depth of linkpath by eating up directory components left > + * to right. Technically the symlink would resolve right-to-left, but > + * we don't care about the actual values, only the number. > + */ > + while (target[0] == '.') { > + if (!target[1]) { > + /* trailing "." -- ignore */ > + target++; > + } else if (is_dir_sep(target[1])) { > + /* "./" -- ignore */ > + target += 2; > + } else if (target[1] == '.' && > + (!target[2] || is_dir_sep(target[2]))) { > + /* ".." or "../" -- drop one from linkpath depth */ > + while (!is_dir_sep(*linkpath)) { > + /* end-of-string; target exceeded our depth */ > + if (!*linkpath) > + return 1; > + linkpath++; > + } > + /* skip final "/" */ > + linkpath++; > + > + /* skip past ".." */ > + target += 2; > + /* and "/" if present */ > + if (is_dir_sep(*target)) > + target++; > + } > + } > + > + /* > + * Now we have a path in "target" that only go down into the tree. > + * Disallow any interior "../", like "foo/../bar". These might be > + * OK, but we cannot know unless we know whether "foo" is itself a > + * symlink. So err on the side of caution. > + */ > + while (*target) { > + const char *v; > + if (skip_prefix(target, "..", &v) && (!*v || is_dir_sep(*v))) > + return 1; > + target++; > + } > + > + return 0; > +} > + > +int safe_symlink(const char *target, const char *linkpath) > +{ > + if (!allow_external_symlinks && > + symlink_leaves_repo(target, linkpath)) { > + errno = EPERM; > + return -1; > + } > + > + return symlink(target, linkpath); > +} OK. This is only about blocking creation of new symbolic links that goes outside the working tree. It obviously is a good thing to do. We have some "symlink safety" in various parts of the system [*1*], and I wonder if we can somehow consolidate the support to a more central place. Thanks. [Footnote] *1* For example, apply tries to be careful not to take the "path" recorded in the incoming patch blindly, and instead checks if any path component in it is a symbolic link before touching. Similarly, callers of has_symlink_leading_path() all try to be careful when the "path" they want to use to access a filesystem entity has a symbolic link in the middle on the filesystem.