Merge branch 'jc/apply-beyond-symlink'

"git apply" was not very careful about reading from, removing,
updating and creating paths outside the working tree (under
--index/--cached) or the current directory (when used as a
replacement for GNU patch).

* jc/apply-beyond-symlink:
  apply: do not touch a file beyond a symbolic link
  apply: do not read from beyond a symbolic link
  apply: do not read from the filesystem under --index
  apply: reject input that touches outside the working area
This commit is contained in:
Junio C Hamano
2015-03-03 14:37:01 -08:00
4 changed files with 399 additions and 2 deletions

View File

@ -51,6 +51,7 @@ static int apply_verbosely;
static int allow_overlap;
static int no_add;
static int threeway;
static int unsafe_paths;
static const char *fake_ancestor;
static int line_termination = '\n';
static unsigned int p_context = UINT_MAX;
@ -3221,7 +3222,7 @@ static int load_patch_target(struct strbuf *buf,
const char *name,
unsigned expected_mode)
{
if (cached) {
if (cached || check_index) {
if (read_file_or_gitlink(ce, buf))
return error(_("read of %s failed"), name);
} else if (name) {
@ -3230,6 +3231,8 @@ static int load_patch_target(struct strbuf *buf,
return read_file_or_gitlink(ce, buf);
else
return SUBMODULE_PATCH_WITHOUT_INDEX;
} else if (has_symlink_leading_path(name, strlen(name))) {
return error(_("reading from '%s' beyond a symbolic link"), name);
} else {
if (read_old_data(st, name, buf))
return error(_("read of %s failed"), name);
@ -3569,6 +3572,121 @@ static int check_to_create(const char *new_name, int ok_if_exists)
return 0;
}
/*
* We need to keep track of how symlinks in the preimage are
* manipulated by the patches. A patch to add a/b/c where a/b
* is a symlink should not be allowed to affect the directory
* the symlink points at, but if the same patch removes a/b,
* it is perfectly fine, as the patch removes a/b to make room
* to create a directory a/b so that a/b/c can be created.
*/
static struct string_list symlink_changes;
#define SYMLINK_GOES_AWAY 01
#define SYMLINK_IN_RESULT 02
static uintptr_t register_symlink_changes(const char *path, uintptr_t what)
{
struct string_list_item *ent;
ent = string_list_lookup(&symlink_changes, path);
if (!ent) {
ent = string_list_insert(&symlink_changes, path);
ent->util = (void *)0;
}
ent->util = (void *)(what | ((uintptr_t)ent->util));
return (uintptr_t)ent->util;
}
static uintptr_t check_symlink_changes(const char *path)
{
struct string_list_item *ent;
ent = string_list_lookup(&symlink_changes, path);
if (!ent)
return 0;
return (uintptr_t)ent->util;
}
static void prepare_symlink_changes(struct patch *patch)
{
for ( ; patch; patch = patch->next) {
if ((patch->old_name && S_ISLNK(patch->old_mode)) &&
(patch->is_rename || patch->is_delete))
/* the symlink at patch->old_name is removed */
register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY);
if (patch->new_name && S_ISLNK(patch->new_mode))
/* the symlink at patch->new_name is created or remains */
register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT);
}
}
static int path_is_beyond_symlink_1(struct strbuf *name)
{
do {
unsigned int change;
while (--name->len && name->buf[name->len] != '/')
; /* scan backwards */
if (!name->len)
break;
name->buf[name->len] = '\0';
change = check_symlink_changes(name->buf);
if (change & SYMLINK_IN_RESULT)
return 1;
if (change & SYMLINK_GOES_AWAY)
/*
* This cannot be "return 0", because we may
* see a new one created at a higher level.
*/
continue;
/* otherwise, check the preimage */
if (check_index) {
struct cache_entry *ce;
ce = cache_file_exists(name->buf, name->len, ignore_case);
if (ce && S_ISLNK(ce->ce_mode))
return 1;
} else {
struct stat st;
if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode))
return 1;
}
} while (1);
return 0;
}
static int path_is_beyond_symlink(const char *name_)
{
int ret;
struct strbuf name = STRBUF_INIT;
assert(*name_ != '\0');
strbuf_addstr(&name, name_);
ret = path_is_beyond_symlink_1(&name);
strbuf_release(&name);
return ret;
}
static void die_on_unsafe_path(struct patch *patch)
{
const char *old_name = NULL;
const char *new_name = NULL;
if (patch->is_delete)
old_name = patch->old_name;
else if (!patch->is_new && !patch->is_copy)
old_name = patch->old_name;
if (!patch->is_delete)
new_name = patch->new_name;
if (old_name && !verify_path(old_name))
die(_("invalid path '%s'"), old_name);
if (new_name && !verify_path(new_name))
die(_("invalid path '%s'"), new_name);
}
/*
* Check and apply the patch in-core; leave the result in patch->result
* for the caller to write it out to the final destination.
@ -3656,6 +3774,22 @@ static int check_patch(struct patch *patch)
}
}
if (!unsafe_paths)
die_on_unsafe_path(patch);
/*
* An attempt to read from or delete a path that is beyond a
* symbolic link will be prevented by load_patch_target() that
* is called at the beginning of apply_data() so we do not
* have to worry about a patch marked with "is_delete" bit
* here. We however need to make sure that the patch result
* is not deposited to a path that is beyond a symbolic link
* here.
*/
if (!patch->is_delete && path_is_beyond_symlink(patch->new_name))
return error(_("affected file '%s' is beyond a symbolic link"),
patch->new_name);
if (apply_data(patch, &st, ce) < 0)
return error(_("%s: patch does not apply"), name);
patch->rejected = 0;
@ -3666,6 +3800,7 @@ static int check_patch_list(struct patch *patch)
{
int err = 0;
prepare_symlink_changes(patch);
prepare_fn_table(patch);
while (patch) {
if (apply_verbosely)
@ -4404,6 +4539,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
N_("make sure the patch is applicable to the current index")),
OPT_BOOL(0, "cached", &cached,
N_("apply a patch without touching the working tree")),
OPT_BOOL(0, "unsafe-paths", &unsafe_paths,
N_("accept a patch that touches outside the working area")),
OPT_BOOL(0, "apply", &force_apply,
N_("also apply the patch (use with --stat/--summary/--check)")),
OPT_BOOL('3', "3way", &threeway,
@ -4476,6 +4613,9 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
die(_("--cached outside a repository"));
check_index = 1;
}
if (check_index)
unsafe_paths = 0;
for (i = 0; i < argc; i++) {
const char *arg = argv[i];
int fd;