untrusted comment: signature from openbsd 5.7 base secret key
RWSvUZXnw9gUb9+47vPMyTiirxqsHUcYDiiXEt6SXOBcjbsj5N1VDmGwE0d/o63ktPMErEqGkHQscRZj770tpnABLVegjbrfxAA=

OpenBSD 5.7 errata 7, Apr 30, 2015:

tar/pax/cpio had multiple issues:
* extracting a malicious archive could create files outside of the
  current directory without using pre-existing symlinks to 'escape',
  and could change the timestamps and modes on preexisting files

* tar without -P would permit extraction of paths with ".." components

* there was a buffer overflow in the handling of pax extension headers,

Apply by doing:
   cd /usr/src
   signify -Vep /etc/signify/openbsd-57-base.pub -x 007_tar.patch.sig -m - | \
       patch -p0

Then build and install pax:

   cd /usr/src/bin/pax
   make obj
   make
   make install


Index: bin/pax/ar_subs.c
===================================================================
RCS file: /cvs/src/bin/pax/ar_subs.c,v
retrieving revision 1.41
diff -u -p -r1.41 ar_subs.c
--- bin/pax/ar_subs.c   21 Feb 2015 22:48:23 -0000      1.41
+++ bin/pax/ar_subs.c   30 Apr 2015 05:13:05 -0000
@@ -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();
}
Index: bin/pax/extern.h
===================================================================
RCS file: /cvs/src/bin/pax/extern.h,v
retrieving revision 1.49
diff -u -p -r1.49 extern.h
--- bin/pax/extern.h    21 Feb 2015 22:48:23 -0000      1.49
+++ bin/pax/extern.h    30 Apr 2015 05:13:05 -0000
@@ -147,6 +147,8 @@ int set_ids(char *, uid_t, gid_t);
int fset_ids(char *, int, uid_t, gid_t);
void set_pmode(char *, mode_t);
void fset_pmode(char *, int, mode_t);
+int set_attr(const struct file_times *, int _force_times, mode_t, int _do_mode,
+    int _in_sig);
int file_write(int, char *, int, int *, int *, int, char *);
void file_flush(int, char *, int);
void rdfile_close(ARCHD *, int *);
@@ -200,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
@@ -261,18 +264,29 @@ 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);
+#ifndef NOCPIO
int dev_start(void);
int add_dev(ARCHD *);
int map_dev(ARCHD *, u_long, u_long);
+#else
+# define dev_start()   0
+# define add_dev(x)    0
+# define map_dev(x,y,z)        0
+#endif /* NOCPIO */
int atdir_start(void);
void atdir_end(void);
void add_atdir(char *, dev_t, ino_t, time_t, time_t);
-int get_atdir(dev_t, ino_t, time_t *, time_t *);
+int do_atdir(const char *, dev_t, ino_t);
int dir_start(void);
void add_dir(char *, struct stat *, int);
+void delete_dir(dev_t, ino_t);
void proc_dir(int _in_sig);
u_int st_hash(const char *, int, int);

Index: bin/pax/file_subs.c
===================================================================
RCS file: /cvs/src/bin/pax/file_subs.c,v
retrieving revision 1.44
diff -u -p -r1.44 file_subs.c
--- bin/pax/file_subs.c 21 Feb 2015 22:48:23 -0000      1.44
+++ bin/pax/file_subs.c 30 Apr 2015 05:13:06 -0000
@@ -56,10 +56,6 @@ mk_link(char *, struct stat *, char *, i
 * and setting access modes, uid/gid and times of files
 */

-#define FILEBITS               (S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
-#define SETBITS                        (S_ISUID | S_ISGID)
-#define ABITS                  (FILEBITS | SETBITS)
-
/*
 * file_creat()
 *     Create and open a file.
@@ -170,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
@@ -187,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);
}

/*
@@ -291,6 +299,7 @@ mk_link(char *to, struct stat *to_sb, ch
                               syswarn(1, errno, "Unable to remove %s", from);
                               return(-1);
                       }
+                       delete_dir(sb.st_dev, sb.st_ino);
               } else if (unlink(from) < 0) {
                       if (!ign) {
                               syswarn(1, errno, "Unable to remove %s", from);
@@ -344,7 +353,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
@@ -404,7 +413,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:
@@ -458,7 +481,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) {
@@ -469,33 +492,36 @@ badlink:
                * rights. This allows nodes in the archive that are children
                * of this directory to be extracted without failure. Both time
                * and modes will be fixed after the entire archive is read and
-                * before pax exits.
+                * before pax exits.  To do that safely, we want the dev+ino
+                * of the directory we created.
                */
-               if (access(nm, R_OK | W_OK | X_OK) < 0) {
-                       if (lstat(nm, &sb) < 0) {
-                               syswarn(0, errno,"Could not access %s (stat)",
-                                   arcn->name);
-                               set_pmode(nm,file_mode | S_IRWXU);
-                       } else {
-                               /*
-                                * We have to add rights to the dir, so we make
-                                * sure to restore the mode. The mode must be
-                                * restored AS CREATED and not as stored if
-                                * pmode is not set.
-                                */
-                               set_pmode(nm,
-                                   ((sb.st_mode & FILEBITS) | S_IRWXU));
-                               if (!pmode)
-                                       arcn->sb.st_mode = sb.st_mode;
-                       }
+               if (lstat(nm, &sb) < 0) {
+                       syswarn(0, errno,"Could not access %s (stat)", nm);
+               } else if (access(nm, R_OK | W_OK | X_OK) < 0) {
+                       /*
+                        * We have to add rights to the dir, so we make
+                        * sure to restore the mode. The mode must be
+                        * restored AS CREATED and not as stored if
+                        * pmode is not set.
+                        */
+                       set_pmode(nm,
+                           ((sb.st_mode & FILEBITS) | S_IRWXU));
+                       if (!pmode)
+                               arcn->sb.st_mode = sb.st_mode;

                       /*
-                        * we have to force the mode to what was set here,
-                        * since we changed it from the default as created.
+                        * we have to force the mode to what was set
+                        * here, since we changed it from the default
+                        * as created.
                        */
+                       arcn->sb.st_dev = sb.st_dev;
+                       arcn->sb.st_ino = sb.st_ino;
                       add_dir(nm, &(arcn->sb), 1);
-               } else if (pmode || patime || pmtime)
+               } else if (pmode || patime || pmtime) {
+                       arcn->sb.st_dev = sb.st_dev;
+                       arcn->sb.st_ino = sb.st_ino;
                       add_dir(nm, &(arcn->sb), 0);
+               }
       }

       if (patime || pmtime)
@@ -539,6 +565,7 @@ unlnk_exist(char *name, int type)
                       syswarn(1,errno,"Unable to remove directory %s", name);
                       return(-1);
               }
+               delete_dir(sb.st_dev, sb.st_ino);
               return(0);
       }

@@ -766,6 +793,60 @@ fset_pmode(char *fnm, int fd, mode_t mod
}

/*
+ * set_attr()
+ *     Given a DIRDATA, restore the mode and times as indicated, but
+ *     only after verifying that it's the directory that we wanted.
+ */
+int
+set_attr(const struct file_times *ft, int force_times, mode_t mode,
+    int do_mode, int in_sig)
+{
+       struct stat sb;
+       int fd, r;
+
+       if (!do_mode && !force_times && !patime && !pmtime)
+               return (0);
+
+       /*
+        * We could legitimately go through a symlink here,
+        * so do *not* use O_NOFOLLOW.  The dev+ino check will
+        * protect us from evil.
+        */
+       fd = open(ft->ft_name, O_RDONLY | O_DIRECTORY);
+       if (fd == -1) {
+               if (!in_sig)
+                       syswarn(1, errno, "Unable to restore mode and times"
+                           " for directory: %s", ft->ft_name);
+               return (-1);
+       }
+
+       if (fstat(fd, &sb) == -1) {
+               if (!in_sig)
+                       syswarn(1, errno, "Unable to stat directory: %s",
+                           ft->ft_name);
+               r = -1;
+       } else if (ft->ft_ino != sb.st_ino || ft->ft_dev != sb.st_dev) {
+               if (!in_sig)
+                       paxwarn(1, "Directory vanished before restoring"
+                           " mode and times: %s", ft->ft_name);
+               r = -1;
+       } else {
+               /* Whew, it's a match!  Is there anything to change? */
+               if (do_mode && (mode & ABITS) != (sb.st_mode & ABITS))
+                       fset_pmode(ft->ft_name, fd, mode);
+               if (((force_times || patime) && ft->ft_atime != sb.st_atime) ||
+                   ((force_times || pmtime) && ft->ft_mtime != sb.st_mtime))
+                       fset_ftime(ft->ft_name, fd, ft->ft_mtime,
+                           ft->ft_atime, force_times);
+               r = 0;
+       }
+       close(fd);
+
+       return (r);
+}
+
+
+/*
 * file_write()
 *     Write/copy a file (during copy or archive extract). This routine knows
 *     how to copy files with lseek holes in it. (Which are read as file
@@ -957,15 +1038,15 @@ rdfile_close(ARCHD *arcn, int *fd)
       if (*fd < 0)
               return;

-       (void)close(*fd);
-       *fd = -1;
-       if (!tflag)
-               return;
-
       /*
        * user wants last access time reset
        */
-       set_ftime(arcn->org_name, arcn->sb.st_mtime, arcn->sb.st_atime, 1);
+       if (tflag)
+               fset_ftime(arcn->org_name, *fd, arcn->sb.st_mtime,
+                   arcn->sb.st_atime, 1);
+
+       (void)close(*fd);
+       *fd = -1;
}

/*
Index: bin/pax/ftree.c
===================================================================
RCS file: /cvs/src/bin/pax/ftree.c,v
retrieving revision 1.36
diff -u -p -r1.36 ftree.c
--- bin/pax/ftree.c     21 Feb 2015 22:48:23 -0000      1.36
+++ bin/pax/ftree.c     30 Apr 2015 05:13:06 -0000
@@ -337,8 +337,6 @@ int
next_file(ARCHD *arcn)
{
       int cnt;
-       time_t atime;
-       time_t mtime;

       /*
        * ftree_sel() might have set the ftree_skip flag if the user has the
@@ -393,10 +391,10 @@ next_file(ARCHD *arcn)
                        * remember to force the time (this is -t on a read
                        * directory, not a created directory).
                        */
-                       if (!tflag || (get_atdir(ftent->fts_statp->st_dev,
-                           ftent->fts_statp->st_ino, &mtime, &atime) < 0))
+                       if (!tflag)
                               continue;
-                       set_ftime(ftent->fts_path, mtime, atime, 1);
+                       do_atdir(ftent->fts_path, ftent->fts_statp->st_dev,
+                           ftent->fts_statp->st_ino);
                       continue;
               case FTS_DC:
                       /*
Index: bin/pax/pat_rep.c
===================================================================
RCS file: /cvs/src/bin/pax/pat_rep.c,v
retrieving revision 1.37
diff -u -p -r1.37 pat_rep.c
--- bin/pax/pat_rep.c   21 Feb 2015 22:48:23 -0000      1.37
+++ bin/pax/pat_rep.c   30 Apr 2015 05:13:06 -0000
@@ -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
@@ -630,6 +649,30 @@ mod_name(ARCHD *arcn)
               if (rmleadslash < 2) {
                       rmleadslash = 2;
                       paxwarn(0, "Removing leading / from absolute path names in the archive");
+               }
+       }
+       if (rmleadslash) {
+               const char *last = NULL;
+               const char *p = arcn->name;
+
+               while ((p = strstr(p, "..")) != NULL) {
+                       if ((p == arcn->name || p[-1] == '/') &&
+                           (p[2] == '/' || p[2] == '\0'))
+                               last = p + 2;
+                       p += 2;
+               }
+               if (last != NULL) {
+                       last++;
+                       paxwarn(1, "Removing leading \"%.*s\"",
+                           (int)(last - arcn->name), arcn->name);
+                       arcn->nlen = strlen(last);
+                       if (arcn->nlen > 0)
+                               memmove(arcn->name, last, arcn->nlen + 1);
+                       else {
+                               arcn->name[0] = '.';
+                               arcn->name[1] = '\0';
+                               arcn->nlen = 1;
+                       }
               }
       }

Index: bin/pax/pax.c
===================================================================
RCS file: /cvs/src/bin/pax/pax.c,v
retrieving revision 1.40
diff -u -p -r1.40 pax.c
--- bin/pax/pax.c       21 Feb 2015 22:48:23 -0000      1.40
+++ bin/pax/pax.c       30 Apr 2015 05:13:06 -0000
@@ -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();
Index: bin/pax/pax.h
===================================================================
RCS file: /cvs/src/bin/pax/pax.h,v
retrieving revision 1.24
diff -u -p -r1.24 pax.h
--- bin/pax/pax.h       21 Feb 2015 22:48:23 -0000      1.24
+++ bin/pax/pax.h       30 Apr 2015 05:13:06 -0000
@@ -211,6 +211,20 @@ typedef struct {
} FSUB;

/*
+ * Time data for a given file.  This is usually embedded in a structure
+ * indexed by dev+ino, by name, by order in the archive, etc.  set_attr()
+ * takes one of these and will only change the times or mode if the file
+ * at the given name has the indicated dev+ino.
+ */
+struct file_times {
+       ino_t   ft_ino;         /* inode number to verify */
+       time_t  ft_mtime;       /* times to set */
+       time_t  ft_atime;
+       char    *ft_name;       /* name of file to set the times on */
+       dev_t   ft_dev;         /* device number to verify */
+};
+
+/*
 * Format Specific Options List
 *
 * Used to pass format options to the format options handler
@@ -228,6 +242,10 @@ typedef struct oplist {
#define MAJOR(x)       major(x)
#define MINOR(x)       minor(x)
#define TODEV(x, y)    makedev((x), (y))
+
+#define FILEBITS               (S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO)
+#define SETBITS                        (S_ISUID | S_ISGID)
+#define ABITS                  (FILEBITS | SETBITS)

/*
 * General Defines
Index: bin/pax/tables.c
===================================================================
RCS file: /cvs/src/bin/pax/tables.c,v
retrieving revision 1.44
diff -u -p -r1.44 tables.c
--- bin/pax/tables.c    21 Feb 2015 22:48:23 -0000      1.44
+++ bin/pax/tables.c    30 Apr 2015 05:13:07 -0000
@@ -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>
@@ -73,8 +74,6 @@ static size_t dirsize;                /* size of dirp
static size_t dircnt = 0;      /* entries in dir time/mode storage */
static int ffd = -1;           /* tmp file for file time table name storage */

-static DEVT *chk_dev(dev_t, int);
-
/*
 * hard link table routines
 *
@@ -463,6 +462,343 @@ 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 (havechd && *path0 != '/') {
+               if ((path = realpath(path0, NULL)) == NULL) {
+                       syswarn(1, errno, "Cannot canonicalize %s", path0);
+                       unlink(path0);
+                       return (-1);
+               }
+       } else 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 (havechd && *path != '/') {
+                       if ((p->sp_path = realpath(path, NULL)) == NULL) {
+                               syswarn(1, errno, "Cannot canonicalize %s",
+                                   path);
+                               free(p);
+                               return (-1);
+                       }
+               } else 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
@@ -607,6 +943,7 @@ sub_name(char *oname, int *onamelen, siz
        */
}

+#ifndef NOCPIO
/*
 * device/inode mapping table routines
 * (used with formats that store device and inodes fields)
@@ -647,6 +984,8 @@ sub_name(char *oname, int *onamelen, siz
 * (for more info see table.h for the data structures involved).
 */

+static DEVT *chk_dev(dev_t, int);
+
/*
 * dev_start()
 *     create the device mapping table
@@ -872,6 +1211,7 @@ map_dev(ARCHD *arcn, u_long dev_mask, u_
       paxwarn(0, "Archive may create improper hard links when extracted");
       return(0);
}
+#endif /* NOCPIO */

/*
 * directory access/mod time reset table routines (for directories READ by pax)
@@ -938,7 +1278,7 @@ atdir_end(void)
                * not read by pax. Read time reset is controlled by -t.
                */
               for (; pt != NULL; pt = pt->fow)
-                       set_ftime(pt->name, pt->mtime, pt->atime, 1);
+                       set_attr(&pt->ft, 1, 0, 0, 0);
       }
}

@@ -968,7 +1308,7 @@ add_atdir(char *fname, dev_t dev, ino_t
       indx = ((unsigned)ino) % A_TAB_SZ;
       if ((pt = atab[indx]) != NULL) {
               while (pt != NULL) {
-                       if ((pt->ino == ino) && (pt->dev == dev))
+                       if ((pt->ft.ft_ino == ino) && (pt->ft.ft_dev == dev))
                               break;
                       pt = pt->fow;
               }
@@ -986,11 +1326,11 @@ add_atdir(char *fname, dev_t dev, ino_t
       sigfillset(&allsigs);
       sigprocmask(SIG_BLOCK, &allsigs, &savedsigs);
       if ((pt = malloc(sizeof *pt)) != NULL) {
-               if ((pt->name = strdup(fname)) != NULL) {
-                       pt->dev = dev;
-                       pt->ino = ino;
-                       pt->mtime = mtime;
-                       pt->atime = atime;
+               if ((pt->ft.ft_name = strdup(fname)) != NULL) {
+                       pt->ft.ft_dev = dev;
+                       pt->ft.ft_ino = ino;
+                       pt->ft.ft_mtime = mtime;
+                       pt->ft.ft_atime = atime;
                       pt->fow = atab[indx];
                       atab[indx] = pt;
                       sigprocmask(SIG_SETMASK, &savedsigs, NULL);
@@ -1015,7 +1355,7 @@ add_atdir(char *fname, dev_t dev, ino_t
 */

int
-get_atdir(dev_t dev, ino_t ino, time_t *mtime, time_t *atime)
+do_atdir(const char *name, dev_t dev, ino_t ino)
{
       ATDIR *pt;
       ATDIR **ppt;
@@ -1033,7 +1373,7 @@ get_atdir(dev_t dev, ino_t ino, time_t *

       ppt = &(atab[indx]);
       while (pt != NULL) {
-               if ((pt->ino == ino) && (pt->dev == dev))
+               if ((pt->ft.ft_ino == ino) && (pt->ft.ft_dev == dev))
                       break;
               /*
                * no match, go to next one
@@ -1045,19 +1385,19 @@ get_atdir(dev_t dev, ino_t ino, time_t *
       /*
        * return if we did not find it.
        */
-       if (pt == NULL)
+       if (pt == NULL || pt->ft.ft_name == NULL ||
+           strcmp(name, pt->ft.ft_name) == 0)
               return(-1);

       /*
-        * found it. return the times and remove the entry from the table.
+        * found it. set the times and remove the entry from the table.
        */
+       set_attr(&pt->ft, 1, 0, 0, 0);
       sigfillset(&allsigs);
       sigprocmask(SIG_BLOCK, &allsigs, &savedsigs);
       *ppt = pt->fow;
       sigprocmask(SIG_SETMASK, &savedsigs, NULL);
-       *mtime = pt->mtime;
-       *atime = pt->atime;
-       free(pt->name);
+       free(pt->ft.ft_name);
       free(pt);
       return(0);
}
@@ -1077,12 +1417,8 @@ get_atdir(dev_t dev, ino_t ino, time_t *
 * times and file permissions specified by the archive are stored. After all
 * files have been extracted (or copied), these directories have their times
 * and file modes reset to the stored values. The directory info is restored in
- * reverse order as entries were added to the data file from root to leaf. To
- * restore atime properly, we must go backwards. The data file consists of
- * records with two parts, the file name followed by a DIRDATA trailer. The
- * fixed sized trailer contains the size of the name plus the off_t location in
- * the file. To restore we work backwards through the file reading the trailer
- * then the file name.
+ * reverse order as entries were added from root to leaf: to restore atime
+ * properly, we must go backwards.
 */

/*
@@ -1150,14 +1486,16 @@ add_dir(char *name, struct stat *psb, in
               sigprocmask(SIG_SETMASK, &savedsigs, NULL);
       }
       dblk = &dirp[dircnt];
-       if ((dblk->name = strdup(name)) == NULL) {
+       if ((dblk->ft.ft_name = strdup(name)) == NULL) {
               paxwarn(1, "Unable to store mode and times for created"
                   " directory: %s", name);
               return;
       }
-       dblk->mode = psb->st_mode & 0xffff;
-       dblk->mtime = psb->st_mtime;
-       dblk->atime = psb->st_atime;
+       dblk->ft.ft_mtime = psb->st_mtime;
+       dblk->ft.ft_atime = psb->st_atime;
+       dblk->ft.ft_ino = psb->st_ino;
+       dblk->ft.ft_dev = psb->st_dev;
+       dblk->mode = psb->st_mode & ABITS;
       dblk->frc_mode = frc_mode;
       sigprocmask(SIG_BLOCK, &allsigs, &savedsigs);
       ++dircnt;
@@ -1165,6 +1503,35 @@ add_dir(char *name, struct stat *psb, in
}

/*
+ * delete_dir()
+ *     When we rmdir a directory, we may want to make sure we don't
+ *     later warn about being unable to set its mode and times.
+ */
+
+void
+delete_dir(dev_t dev, ino_t ino)
+{
+       DIRDATA *dblk;
+       char *name;
+       size_t i;
+
+       if (dirp == NULL)
+               return;
+       for (i = 0; i < dircnt; i++) {
+               dblk = &dirp[i];
+
+               if (dblk->ft.ft_name == NULL)
+                       continue;
+               if (dblk->ft.ft_dev == dev && dblk->ft.ft_ino == ino) {
+                       name = dblk->ft.ft_name;
+                       dblk->ft.ft_name = NULL;
+                       free(name);
+                       break;
+               }
+       }
+}
+
+/*
 * proc_dir(int in_sig)
 *     process all file modes and times stored for directories CREATED
 *     by pax.  If in_sig is set, we're in a signal handler and can't
@@ -1184,17 +1551,22 @@ proc_dir(int in_sig)
        */
       cnt = dircnt;
       while (cnt-- > 0) {
+               dblk = &dirp[cnt];
+               /*
+                * If we remove a directory we created, we replace the
+                * ft_name with NULL.  Ignore those.
+                */
+               if (dblk->ft.ft_name == NULL)
+                       continue;
+
               /*
                * frc_mode set, make sure we set the file modes even if
                * the user didn't ask for it (see file_subs.c for more info)
                */
-               dblk = &dirp[cnt];
-               if (pmode || dblk->frc_mode)
-                       set_pmode(dblk->name, dblk->mode);
-               if (patime || pmtime)
-                       set_ftime(dblk->name, dblk->mtime, dblk->atime, 0);
+               set_attr(&dblk->ft, 0, dblk->mode, pmode || dblk->frc_mode,
+                   in_sig);
               if (!in_sig)
-                       free(dblk->name);
+                       free(dblk->ft.ft_name);
       }

       if (!in_sig)
Index: bin/pax/tables.h
===================================================================
RCS file: /cvs/src/bin/pax/tables.h,v
retrieving revision 1.14
diff -u -p -r1.14 tables.h
--- bin/pax/tables.h    21 Feb 2015 22:48:23 -0000      1.14
+++ bin/pax/tables.h    30 Apr 2015 05:13:07 -0000
@@ -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 */

@@ -143,12 +144,8 @@ typedef struct dlist {
 */

typedef struct atdir {
-       ino_t ino;
-       time_t mtime;   /* access and mod time to reset to */
-       time_t atime;
-       char *name;     /* name of directory to reset */
+       struct file_times ft;
       struct atdir *fow;
-       dev_t dev;      /* dev and inode for fast lookup */
} ATDIR;

/*
@@ -162,9 +159,7 @@ typedef struct atdir {
 */

typedef struct dirdata {
-       time_t mtime;   /* mtime to set */
-       time_t atime;   /* atime to set */
-       char *name;     /* file name */
-       u_int16_t mode; /* file mode to restore */
+       struct file_times ft;
+       u_int16_t mode;         /* file mode to restore */
       u_int16_t frc_mode;     /* do we force mode settings? */
} DIRDATA;
Index: bin/pax/tar.c
===================================================================
RCS file: /cvs/src/bin/pax/tar.c,v
retrieving revision 1.55
diff -u -p -r1.55 tar.c
--- bin/pax/tar.c       21 Feb 2015 22:48:23 -0000      1.55
+++ bin/pax/tar.c       30 Apr 2015 05:13:07 -0000
@@ -58,7 +58,7 @@ static char *name_split(char *, int);
static int ul_oct(u_long, char *, int, int);
static int uqd_oct(u_quad_t, char *, int, int);
#ifndef SMALL
-static int rd_xheader(ARCHD *, char *, off_t, char);
+static int rd_xheader(ARCHD *arcn, int, off_t);
#endif

static uid_t uid_nobody;
@@ -734,7 +734,7 @@ ustar_id(char *blk, int size)
int
ustar_rd(ARCHD *arcn, char *buf)
{
-       HD_USTAR *hd;
+       HD_USTAR *hd = (HD_USTAR *)buf;
       char *dest;
       int cnt = 0;
       dev_t devmajor;
@@ -746,18 +746,30 @@ ustar_rd(ARCHD *arcn, char *buf)
        */
       if (ustar_id(buf, BLKMULT) < 0)
               return(-1);
+
+#ifndef SMALL
+reset:
+#endif
       memset(arcn, 0, sizeof(*arcn));
       arcn->org_name = arcn->name;
       arcn->sb.st_nlink = 1;
-       hd = (HD_USTAR *)buf;

#ifndef SMALL
-       /* Process the Extended header. */
+       /* Process Extended headers. */
       if (hd->typeflag == XHDRTYPE || hd->typeflag == GHDRTYPE) {
-               if (rd_xheader(arcn, buf,
-                   (off_t)asc_ul(hd->size, sizeof(hd->size), OCT),
-                   hd->typeflag) < 0)
+               if (rd_xheader(arcn, hd->typeflag == GHDRTYPE,
+                   (off_t)asc_ul(hd->size, sizeof(hd->size), OCT)) < 0)
                       return (-1);
+
+               /* Update and check the ustar header. */
+               if (rd_wrbuf(buf, BLKMULT) != BLKMULT)
+                       return (-1);
+               if (ustar_id(buf, BLKMULT) < 0)
+                       return(-1);
+
+               /* if the next block is another extension, reset the values */
+               if (hd->typeflag == XHDRTYPE || hd->typeflag == GHDRTYPE)
+                       goto reset;
       }
#endif

@@ -1193,51 +1205,84 @@ expandname(char *buf, size_t len, char *

#ifndef SMALL

-#define MINXHDRSZ      6
+/* shortest possible extended record: "5 a=\n" */
+#define MINXHDRSZ      5
+
+/* longest record we'll accept */
+#define MAXXHDRSZ      BLKMULT

static int
-rd_xheader(ARCHD *arcn, char *buf, off_t size, char typeflag)
+rd_xheader(ARCHD *arcn, int global, off_t size)
{
-       off_t len;
+       char buf[MAXXHDRSZ];
+       unsigned long len;
       char *delim, *keyword;
-       char *nextp, *p;
+       char *nextp, *p, *end;
+       int pad, ret = 0;

-       if (size < MINXHDRSZ) {
-               paxwarn(1, "Invalid extended header length");
-               return (-1);
-       }
-       if (rd_wrbuf(buf, size) != size)
-               return (-1);
-       if (rd_skip((off_t)BLKMULT - size) < 0)
-               return (-1);
+       /* before we alter size, make note of how much we have to skip */
+       pad = TAR_PAD((unsigned)size);

-       for (p = buf; size > 0; size -= len, p = nextp) {
-               if (!isdigit((unsigned char)*p)) {
+       p = end = buf;
+       while (size > 0 || p < end) {
+               if (size > 0) {
+                       int rdlen;
+
+                       /* shift stuff down */
+                       if (p > buf) {
+                               memmove(buf, p, end - p);
+                               end -= p - buf;
+                               p = buf;
+                       }
+
+                       /* fill starting at end */
+                       rdlen = MINIMUM(size, (buf + sizeof buf) - end);
+                       if (rd_wrbuf(end, rdlen) != rdlen) {
+                               ret = -1;
+                               break;
+                       }
+                       size -= rdlen;
+                       end += rdlen;
+               }
+
+               /* [p, end) is good */
+               if (memchr(p, ' ', end - p) == NULL ||
+                   !isdigit((unsigned char)*p)) {
                       paxwarn(1, "Invalid extended header record");
-                       return (-1);
+                       ret = -1;
+                       break;
               }
               errno = 0;
-               len = strtoll(p, &delim, 10);
-               if (*delim != ' ' || (errno == ERANGE &&
-                   (len == LLONG_MIN || len == LLONG_MAX)) ||
+               len = strtoul(p, &delim, 10);
+               if (*delim != ' ' || (errno == ERANGE && len == ULONG_MAX) ||
                   len < MINXHDRSZ) {
                       paxwarn(1, "Invalid extended header record length");
-                       return (-1);
+                       ret = -1;
+                       break;
               }
-               if (len > size) {
-                       paxwarn(1, "Extended header record length %lld is "
-                           "out of range", (long long)len);
-                       return (-1);
+               if (len > end - p) {
+                       paxwarn(1, "Extended header record length %lu is "
+                           "out of range", len);
+                       /* if we can just toss this record, do so */
+                       len -= end - p;
+                       if (len <= size && rd_skip(len) == 0) {
+                               size -= len;
+                               p = end = buf;
+                               continue;
+                       }
+                       ret = -1;
+                       break;
               }
               nextp = p + len;
               keyword = p = delim + 1;
               p = memchr(p, '=', len);
               if (!p || nextp[-1] != '\n') {
                       paxwarn(1, "Malformed extended header record");
-                       return (-1);
+                       ret = -1;
+                       break;
               }
               *p++ = nextp[-1] = '\0';
-               if (typeflag == XHDRTYPE) {
+               if (!global) {
                       if (!strcmp(keyword, "path")) {
                               arcn->nlen = strlcpy(arcn->name, p,
                                   sizeof(arcn->name));
@@ -1246,11 +1291,11 @@ rd_xheader(ARCHD *arcn, char *buf, off_t
                                   sizeof(arcn->ln_name));
                       }
               }
+               p = nextp;
       }

-       /* Update the ustar header. */
-       if (rd_wrbuf(buf, BLKMULT) != BLKMULT)
+       if (rd_skip(size + pad) < 0)
               return (-1);
-       return (0);
+       return (ret);
}
#endif