diff options
author | Philip Guenther <guenther@cvs.openbsd.org> | 2015-02-12 23:44:58 +0000 |
---|---|---|
committer | Philip Guenther <guenther@cvs.openbsd.org> | 2015-02-12 23:44:58 +0000 |
commit | 555bfd186e9dbe63e01634b53f74086383d9ea05 (patch) | |
tree | 51e30e98216f0bbb8876a2a7fc570a32c2298121 | |
parent | eef4f64fcf2ad649e744938cbaed3211c769b708 (diff) |
Prevent an archive from esacaping the current directory by itself:
when extracting a symlink whose value is absolute or contains ".."
components, just create a zero-length normal file (with additional
tracking of the mode and hardlinks to the symlink) until everything
else is extracted, then go back and replace it with the requested
link (if its still that zero-length placeholder).
This and previous symlink and ".." path fixes prompted by a report
from Daniel Cegielka (daniel.cegielka (at) gmail.com)
ok millert@
-rw-r--r-- | bin/pax/ar_subs.c | 8 | ||||
-rw-r--r-- | bin/pax/extern.h | 7 | ||||
-rw-r--r-- | bin/pax/file_subs.c | 36 | ||||
-rw-r--r-- | bin/pax/pat_rep.c | 21 | ||||
-rw-r--r-- | bin/pax/pax.c | 3 | ||||
-rw-r--r-- | bin/pax/tables.c | 327 | ||||
-rw-r--r-- | bin/pax/tables.h | 3 |
7 files changed, 394 insertions, 11 deletions
diff --git a/bin/pax/ar_subs.c b/bin/pax/ar_subs.c index 48493bd17ab..6c2a9c8abca 100644 --- a/bin/pax/ar_subs.c +++ b/bin/pax/ar_subs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ar_subs.c,v 1.39 2014/05/23 19:47:49 guenther Exp $ */ +/* $OpenBSD: ar_subs.c,v 1.40 2015/02/12 23:44:57 guenther Exp $ */ /* $NetBSD: ar_subs.c,v 1.5 1995/03/21 09:07:06 cgd Exp $ */ /*- @@ -165,6 +165,8 @@ extract(void) int fd; time_t now; + sltab_start(); + arcn = &archd; /* * figure out archive type; pass any format specific options to the @@ -360,6 +362,7 @@ popd: (void)(*frmt->end_rd)(); (void)sigprocmask(SIG_BLOCK, &s_mask, NULL); ar_close(0); + sltab_process(0); proc_dir(0); pat_chk(); } @@ -758,6 +761,8 @@ copy(void) ARCHD archd; char dirbuf[PAXPATHLEN+1]; + sltab_start(); + arcn = &archd; /* * set up the destination dir path and make sure it is a directory. We @@ -969,6 +974,7 @@ copy(void) */ (void)sigprocmask(SIG_BLOCK, &s_mask, NULL); ar_close(0); + sltab_process(0); proc_dir(0); ftree_chk(); } diff --git a/bin/pax/extern.h b/bin/pax/extern.h index 00ead23398f..22912087c10 100644 --- a/bin/pax/extern.h +++ b/bin/pax/extern.h @@ -1,4 +1,4 @@ -/* $OpenBSD: extern.h,v 1.46 2015/02/12 01:30:47 guenther Exp $ */ +/* $OpenBSD: extern.h,v 1.47 2015/02/12 23:44:57 guenther Exp $ */ /* $NetBSD: extern.h,v 1.5 1996/03/26 23:54:16 mrg Exp $ */ /*- @@ -202,6 +202,7 @@ int pat_sel(ARCHD *); int pat_match(ARCHD *); int mod_name(ARCHD *); int set_dest(ARCHD *, char *, int); +int has_dotdot(const char *); /* * pax.c @@ -263,6 +264,10 @@ void purg_lnk(ARCHD *); void lnk_end(void); int ftime_start(void); int chk_ftime(ARCHD *); +int sltab_start(void); +int sltab_add_sym(const char *_path, const char *_value, mode_t _mode); +int sltab_add_link(const char *, const struct stat *); +void sltab_process(int _in_sig); int name_start(void); int add_name(char *, int, char *); void sub_name(char *, int *, size_t); diff --git a/bin/pax/file_subs.c b/bin/pax/file_subs.c index ca546c080a6..2c5475b963c 100644 --- a/bin/pax/file_subs.c +++ b/bin/pax/file_subs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: file_subs.c,v 1.41 2015/02/11 23:14:46 guenther Exp $ */ +/* $OpenBSD: file_subs.c,v 1.42 2015/02/12 23:44:57 guenther Exp $ */ /* $NetBSD: file_subs.c,v 1.4 1995/03/21 09:07:18 cgd Exp $ */ /*- @@ -166,6 +166,7 @@ int lnk_creat(ARCHD *arcn) { struct stat sb; + int res; /* * we may be running as root, so we have to be sure that link target @@ -183,7 +184,18 @@ lnk_creat(ARCHD *arcn) return(-1); } - return(mk_link(arcn->ln_name, &sb, arcn->name, 0)); + res = mk_link(arcn->ln_name, &sb, arcn->name, 0); + if (res == 0) { + /* check for a hardlink to a placeholder symlink */ + res = sltab_add_link(arcn->name, &sb); + + if (res < 0) { + /* arrgh, it failed, clean up */ + unlink(arcn->name); + } + } + + return (res); } /* @@ -340,7 +352,7 @@ node_creat(ARCHD *arcn) struct stat sb; char target[PATH_MAX]; char *nm = arcn->name; - int len; + int len, defer_pmode = 0; /* * create node based on type, if that fails try to unlink the node and @@ -400,7 +412,21 @@ badlink: nm); return(-1); case PAX_SLK: - res = symlink(arcn->ln_name, nm); + if (arcn->ln_name[0] != '/' && + !has_dotdot(arcn->ln_name)) + res = symlink(arcn->ln_name, nm); + else { + /* + * absolute symlinks and symlinks with ".." + * have to be deferred to prevent the archive + * from bootstrapping itself to outside the + * working directory. + */ + res = sltab_add_sym(nm, arcn->ln_name, + arcn->sb.st_mode); + if (res == 0) + defer_pmode = 1; + } break; case PAX_CTG: case PAX_HLK: @@ -454,7 +480,7 @@ badlink: */ if (!pmode || res) arcn->sb.st_mode &= ~(SETBITS); - if (pmode) + if (pmode && !defer_pmode) set_pmode(nm, arcn->sb.st_mode); if (arcn->type == PAX_DIR && strcmp(NM_CPIO, argv0) != 0) { diff --git a/bin/pax/pat_rep.c b/bin/pax/pat_rep.c index 6f3eb1bf98b..8094ee3a3e8 100644 --- a/bin/pax/pat_rep.c +++ b/bin/pax/pat_rep.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pat_rep.c,v 1.35 2015/02/12 23:01:58 guenther Exp $ */ +/* $OpenBSD: pat_rep.c,v 1.36 2015/02/12 23:44:57 guenther Exp $ */ /* $NetBSD: pat_rep.c,v 1.4 1995/03/21 09:07:33 cgd Exp $ */ /*- @@ -583,6 +583,25 @@ range_match(char *pattern, int test) } /* + * has_dotdot() + * Returns true iff the supplied path contains a ".." component. + */ + +int +has_dotdot(const char *path) +{ + const char *p = path; + + while ((p = strstr(p, "..")) != NULL) { + if ((p == path || p[-1] == '/') && + (p[2] == '/' || p[2] == '\0')) + return (1); + p += 2; + } + return (0); +} + +/* * mod_name() * modify a selected file name. first attempt to apply replacement string * expressions, then apply interactive file rename. We apply replacement diff --git a/bin/pax/pax.c b/bin/pax/pax.c index 49508254ef1..67a1c47bbd8 100644 --- a/bin/pax/pax.c +++ b/bin/pax/pax.c @@ -1,4 +1,4 @@ -/* $OpenBSD: pax.c,v 1.38 2014/11/23 05:28:12 guenther Exp $ */ +/* $OpenBSD: pax.c,v 1.39 2015/02/12 23:44:57 guenther Exp $ */ /* $NetBSD: pax.c,v 1.5 1996/03/26 23:54:20 mrg Exp $ */ /*- @@ -311,6 +311,7 @@ sig_cleanup(int which_sig) (void) write(STDERR_FILENO, errbuf, strlen(errbuf)); ar_close(1); + sltab_process(1); proc_dir(1); if (tflag) atdir_end(); diff --git a/bin/pax/tables.c b/bin/pax/tables.c index f61aeb165bc..b99dbbe4534 100644 --- a/bin/pax/tables.c +++ b/bin/pax/tables.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tables.c,v 1.41 2015/02/12 01:30:47 guenther Exp $ */ +/* $OpenBSD: tables.c,v 1.42 2015/02/12 23:44:57 guenther Exp $ */ /* $NetBSD: tables.c,v 1.4 1995/03/21 09:07:45 cgd Exp $ */ /*- @@ -37,6 +37,7 @@ #include <sys/types.h> #include <sys/time.h> #include <sys/stat.h> +#include <fcntl.h> #include <limits.h> #include <signal.h> #include <stdio.h> @@ -461,6 +462,330 @@ chk_ftime(ARCHD *arcn) } /* + * escaping (absolute or w/"..") symlink table routines + * + * By default, an archive shouldn't be able extract to outside of the + * current directory. What should we do if the archive contains a symlink + * whose value is either absolute or contains ".." components? What we'll + * do is initially create the path as an empty file (to block attempts to + * reference _through_ it) and instead record its path and desired + * final value and mode. Then once all the other archive + * members are created (but before the pass to set timestamps on + * directories) we'll process those records, replacing the placeholder with + * the correct symlink and setting them to the correct mode, owner, group, + * and timestamps. + * + * Note: we also need to handle hardlinks to symlinks (barf) as well as + * hardlinks whose target is replaced by a later entry in the archive (barf^2). + * + * So we track things by dev+ino of the placeholder file, associating with + * that the value and mode of the final symlink and a list of paths that + * should all be hardlinks of that. We'll 'store' the symlink's desired + * timestamps, owner, and group by setting them on the placeholder file. + * + * The operations are: + * a) create an escaping symlink: create the placeholder file and add an entry + * for the new link + * b) create a hardlink: do the link. If the target turns out to be a + * zero-length file whose dev+ino are in the symlink table, then add this + * path to the list of names for that link + * c) perform deferred processing: for each entry, check each associated path: + * if it's a zero-length file with the correct dev+ino then recreate it as + * the specified symlink or hardlink to the first such + */ + +struct slpath { + char *sp_path; + struct slpath *sp_next; +}; +struct slinode { + ino_t sli_ino; + char *sli_value; + struct slpath sli_paths; + struct slinode *sli_fow; /* hash table chain */ + dev_t sli_dev; + mode_t sli_mode; +}; + +static struct slinode **slitab = NULL; + +/* + * sltab_start() + * create the hash table + * Return: + * 0 if the table and file was created ok, -1 otherwise + */ + +int +sltab_start(void) +{ + + if ((slitab = calloc(SL_TAB_SZ, sizeof *slitab)) == NULL) { + syswarn(1, errno, "symlink table"); + return(-1); + } + + return(0); +} + +/* + * sltab_add_sym() + * Create the placeholder and tracking info for an escaping symlink. + * Return: + * 0 on success, -1 otherwise + */ + +int +sltab_add_sym(const char *path0, const char *value0, mode_t mode) +{ + struct stat sb; + struct slinode *s; + struct slpath *p; + char *path, *value; + u_int indx; + int fd; + + /* create the placeholder */ + fd = open(path0, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600); + if (fd == -1) + return (-1); + if (fstat(fd, &sb) == -1) { + unlink(path0); + close(fd); + return (-1); + } + close(fd); + + if ((path = strdup(path0)) == NULL) { + syswarn(1, errno, "defered symlink path"); + unlink(path0); + return (-1); + } + if ((value = strdup(value0)) == NULL) { + syswarn(1, errno, "defered symlink value"); + unlink(path); + free(path); + return (-1); + } + + /* now check the hash table for conflicting entry */ + indx = (sb.st_ino ^ sb.st_dev) % SL_TAB_SZ; + for (s = slitab[indx]; s != NULL; s = s->sli_fow) { + if (s->sli_ino != sb.st_ino || s->sli_dev != sb.st_dev) + continue; + + /* + * One of our placeholders got removed behind our back and + * we've reused the inode. Weird, but clean up the mess. + */ + free(s->sli_value); + free(s->sli_paths.sp_path); + p = s->sli_paths.sp_next; + while (p != NULL) { + struct slpath *next_p = p->sp_next; + + free(p->sp_path); + free(p); + p = next_p; + } + goto set_value; + } + + /* Normal case: create a new node */ + if ((s = malloc(sizeof *s)) == NULL) { + syswarn(1, errno, "defered symlink"); + unlink(path); + free(path); + free(value); + return (-1); + } + s->sli_ino = sb.st_ino; + s->sli_dev = sb.st_dev; + s->sli_fow = slitab[indx]; + slitab[indx] = s; + +set_value: + s->sli_paths.sp_path = path; + s->sli_paths.sp_next = NULL; + s->sli_value = value; + s->sli_mode = mode; + return (0); +} + +/* + * sltab_add_link() + * A hardlink was created; if it looks like a placeholder, handle the + * tracking. + * Return: + * 0 if things are ok, -1 if something went wrong + */ + +int +sltab_add_link(const char *path, const struct stat *sb) +{ + struct slinode *s; + struct slpath *p; + u_int indx; + + if (!S_ISREG(sb->st_mode) || sb->st_size != 0) + return (1); + + /* find the hash table entry for this hardlink */ + indx = (sb->st_ino ^ sb->st_dev) % SL_TAB_SZ; + for (s = slitab[indx]; s != NULL; s = s->sli_fow) { + if (s->sli_ino != sb->st_ino || s->sli_dev != sb->st_dev) + continue; + + if ((p = malloc(sizeof *p)) == NULL) { + syswarn(1, errno, "deferred symlink hardlink"); + return (-1); + } + if ((p->sp_path = strdup(path)) == NULL) { + syswarn(1, errno, "defered symlink hardlink path"); + free(p); + return (-1); + } + + /* link it in */ + p->sp_next = s->sli_paths.sp_next; + s->sli_paths.sp_next = p; + return (0); + } + + /* not found */ + return (1); +} + + +static int +sltab_process_one(struct slinode *s, struct slpath *p, const char *first, + int in_sig) +{ + struct stat sb; + char *path = p->sp_path; + mode_t mode; + int err; + + /* + * is it the expected placeholder? This can fail legimately + * if the archive overwrote the link with another, later entry, + * so don't warn. + */ + if (stat(path, &sb) != 0 || !S_ISREG(sb.st_mode) || sb.st_size != 0 || + sb.st_ino != s->sli_ino || sb.st_dev != s->sli_dev) + return (0); + + if (unlink(path) && errno != ENOENT) { + if (!in_sig) + syswarn(1, errno, "deferred symlink removal"); + return (0); + } + + err = 0; + if (first != NULL) { + /* add another hardlink to the existing symlink */ + if (linkat(AT_FDCWD, first, AT_FDCWD, path, 0) == 0) + return (0); + + /* + * Couldn't hardlink the symlink for some reason, so we'll + * try creating it as its own symlink, but save the error + * for reporting if that fails. + */ + err = errno; + } + + if (symlink(s->sli_value, path)) { + if (!in_sig) { + const char *qualifier = ""; + if (err) + qualifier = " hardlink"; + else + err = errno; + + syswarn(1, err, "deferred symlink%s: %s", + qualifier, path); + } + return (0); + } + + /* success, so set the id, mode, and times */ + mode = s->sli_mode; + if (pids) { + /* if can't set the ids, force the set[ug]id bits off */ + if (set_ids(path, sb.st_uid, sb.st_gid)) + mode &= ~(SETBITS); + } + + if (pmode) + set_pmode(path, mode); + + if (patime || pmtime) + set_ftime(path, sb.st_mtime, sb.st_atime, 0); + + /* + * If we tried to link to first but failed, then this new symlink + * might be a better one to try in the future. Guess from the errno. + */ + if (err == 0 || err == ENOENT || err == EMLINK || err == EOPNOTSUPP) + return (1); + return (0); +} + +/* + * sltab_process() + * Do all the delayed process for escape symlinks + */ + +void +sltab_process(int in_sig) +{ + struct slinode *s; + struct slpath *p; + char *first; + u_int indx; + + if (slitab == NULL) + return; + + /* walk across the entire hash table */ + for (indx = 0; indx < SL_TAB_SZ; indx++) { + while ((s = slitab[indx]) != NULL) { + /* pop this entry */ + slitab[indx] = s->sli_fow; + + first = NULL; + p = &s->sli_paths; + while (1) { + struct slpath *next_p; + + if (sltab_process_one(s, p, first, in_sig)) { + if (!in_sig) + free(first); + first = p->sp_path; + } else if (!in_sig) + free(p->sp_path); + + if ((next_p = p->sp_next) == NULL) + break; + *p = *next_p; + if (!in_sig) + free(next_p); + } + if (!in_sig) { + free(first); + free(s->sli_value); + free(s); + } + } + } + if (!in_sig) + free(slitab); + slitab = NULL; +} + + +/* * Interactive rename table routines * * The interactive rename table keeps track of the new names that the user diff --git a/bin/pax/tables.h b/bin/pax/tables.h index f4ac151f911..45dc40aed60 100644 --- a/bin/pax/tables.h +++ b/bin/pax/tables.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tables.h,v 1.12 2015/02/11 23:14:46 guenther Exp $ */ +/* $OpenBSD: tables.h,v 1.13 2015/02/12 23:44:57 guenther Exp $ */ /* $NetBSD: tables.h,v 1.3 1995/03/21 09:07:47 cgd Exp $ */ /*- @@ -50,6 +50,7 @@ #define N_TAB_SZ 541 /* interactive rename hash table */ #define D_TAB_SZ 317 /* unique device mapping table */ #define A_TAB_SZ 317 /* ftree dir access time reset table */ +#define SL_TAB_SZ 317 /* escape symlink tables */ #define MAXKEYLEN 64 /* max number of chars for hash */ #define DIRP_SIZE 64 /* initial size of created dir table */ |