diff options
Diffstat (limited to 'bin')
-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 */ |