public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Pavel Cahyna <pcahyna@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/tar] f44: Fix a regression with --one-top-level=/absolute
Date: Mon, 07 Sep 2026 17:57:18 GMT	[thread overview]
Message-ID: <178880383837.1.2386869887287242740.rpms-tar-f4b2efc9a092@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/tar
            Branch : f44
            Commit : f4b2efc9a09256eb3297c3db4e41e5421f8a7fc4
            Author : Pavel Cahyna <pcahyna@redhat.com>
            Date   : 2026-09-07T17:22:22+02:00
            Stats  : +807/-0 in 2 file(s)
            URL    : https://src.fedoraproject.org/rpms/tar/c/f4b2efc9a09256eb3297c3db4e41e5421f8a7fc4?branch=f44

            Log:
            Fix a regression with --one-top-level=/absolute

Introduced with the fix of bz2380007 (CVE-2025-45582)

Also, fix CVE-2026-18508.

Resolves: fedora#2498061
Resolves: CVE-2026-18508
Resolves: fedora#2509845

---
diff --git a/tar-1.35-fix-absolute-one-top-level.patch b/tar-1.35-fix-absolute-one-top-level.patch
new file mode 100644
index 0000000..da21a92
--- /dev/null
+++ b/tar-1.35-fix-absolute-one-top-level.patch
@@ -0,0 +1,793 @@
+diff --git a/NEWS b/NEWS
+index ffb4f54f..45fedbf5 100644
+--- a/NEWS
++++ b/NEWS
+@@ -1,12 +1,12 @@
+-GNU tar NEWS - User visible changes. 2026-01-30
++GNU tar NEWS - User visible changes. 2026-07-22
+ Please send GNU tar bug reports to <bug-tar@gnu.org>
+ \f
+ version 1.35.90 (git)
+ 
+ * Changes to behavior
+ 
+-** --one-top-level=DIR now requires DIR to be relative.
+-   Previously this restriction was alluded to in the manual but not enforced.
++** The --one-top-level option now requires either -x or -d mode.
++   Previously the behavior was unspecified in other operation modes.
+ 
+ * Bug fixes
+ 
+diff --git a/doc/tar.texi b/doc/tar.texi
+index 9b04b94b..91302f05 100644
+--- a/doc/tar.texi
++++ b/doc/tar.texi
+@@ -3271,14 +3271,14 @@ directory.
+ 
+ @opsummary{one-top-level}
+ @item --one-top-level[=@var{dir}]
+-Tells @command{tar} to create a new directory beneath the extraction directory
+-(or the one passed to @option{-C}) and use it to prevent @command{tar}
+-from modifying files outside that directory.
+-If @var{dir} is present, it must be a relative file name.
+-If it is absent, the name of the new directory
++Tells @command{tar} to use a directory beneath the extraction directory
++(or the one passed to @option{-C}) to prevent @command{tar}
++from accessing files outside that directory.
++If @var{dir} is absent, the name of the directory
+ is the base name of the archive minus any recognized archive suffix.
+-If multiple @option{-C} options are present,
+-each has its own subdirectory with the same name.
++If @var{dir} is an absolute file name, that is the only such directory;
++otherwise, the working directory and the directories specified by
++any @option{-C} options each have the named subdirectory.
+ Any member names that do not begin
+ with that directory name (after
+ transformations from @option{--transform} and
+@@ -3286,6 +3286,9 @@ transformations from @option{--transform} and
+ file name suffixes are @samp{.tar}, and any compression suffixes
+ recognizable by @xref{--auto-compress}.
+ 
++This option can be used only when extracting, comparing, or reading
++from the archive.
++
+ @opsummary{overwrite}
+ @item --overwrite
+ 
+diff --git a/src/common.h b/src/common.h
+index ac0e7eef..fb732f0b 100644
+--- a/src/common.h
++++ b/src/common.h
+@@ -246,7 +246,6 @@ GLOBAL bool numeric_owner_option;
+ GLOBAL bool one_file_system_option;
+ 
+ /* Create a top-level directory for extracting based on the archive name.  */
+-GLOBAL bool one_top_level_option;
+ GLOBAL char *one_top_level_dir;
+ 
+ /* Specified value to be put into tar file in place of stat () results, or
+@@ -545,6 +544,7 @@ void verify_volume (void);
+ /* Module extract.c.  */
+ 
+ void extr_init (void);
++int make_directories (char *file_name, bool *interdir_made);
+ void extract_archive (void);
+ void extract_finish (void);
+ bool rename_directory (char *src, char *dst);
+@@ -635,6 +635,20 @@ void skim_member (bool must_copy);
+ #define min(a, b) ((a) < (b) ? (a) : (b))
+ #define max(a, b) ((a) < (b) ? (b) : (a))
+ 
++/* A directory FD, and a file name BASE that matches the regexp "[^/]*[/]*".
++   If BASE is absolute (i.e., matches "/+"), FD is valid but irrelevant.
++   Otherwise if FD == AT_FDCWD, BASE is relative to the current directory.
++   Otherwise if FD == BADFD, the parent directory could not be opened
++   and BASE is merely the original file name's basename.
++   Otherwise, FD is open to a parent directory,
++   and BASE is relative to that directory.
++   BASE points to storage managed elsewhere; do not free it directly.  */
++struct fdbase
++  {
++    int fd;
++    char const *base;
++  };
++
+ char const *quote_n_colon (int n, char const *arg);
+ void assign_string_or_null (char **dest, const char *src)
+   ATTRIBUTE_NONNULL ((1));
+@@ -657,6 +671,8 @@ void namebuf_free (namebuf_t buf);
+ char *namebuf_name (namebuf_t buf, const char *name);
+ 
+ const char *tar_dirname (void);
++char *transform_top_level (const char *name)
++  _GL_ATTRIBUTE_MALLOC _GL_ATTRIBUTE_DEALLOC_FREE;
+ 
+ /* Represent N using a signed integer I such that (uintmax_t) I == N.
+    With a good optimizing compiler, this is equivalent to (intmax_t) i
+@@ -728,10 +744,10 @@ size_t blocking_write (int fd, void const *buf, size_t count);
+ enum { BADFD = AT_FDCWD == -1 ? -2 : -1 };
+ 
+ extern int chdir_current;
+-int chdir_arg (char const *dir);
+-void chdir_do (int dir);
++int chdir_arg (char const *dir, bool one_top_level);
++void chdir_do (int dir, bool create);
+ struct chdir_id { int err; dev_t st_dev; ino_t st_ino; } chdir_id (void);
+-struct fdbase { int fd; char const *base; } fdbase (char const *);
++struct fdbase fdbase (char const *);
+ struct fdbase fdbase1 (char const *);
+ void fdbase_clear (void);
+ int chdir_count (void);
+diff --git a/src/create.c b/src/create.c
+index 51c3f213..582ca4c6 100644
+--- a/src/create.c
++++ b/src/create.c
+@@ -1266,7 +1266,7 @@ ensure_slash (char **pstr)
+ /* If we just ran out of file descriptors, release a file descriptor
+    in the directory chain somewhere leading from DIR->parent->parent
+    up through the root.  Return true if successful, false (preserving
+-   errno == EMFILE) otherwise.
++   errno) otherwise.
+ 
+    Do not release DIR's file descriptor, or DIR's parent, as other
+    code assumes that they work.  On some operating systems, another
+@@ -1277,7 +1277,8 @@ ensure_slash (char **pstr)
+ static bool
+ open_failure_recover (struct tar_stat_info const *dir)
+ {
+-  if (errno == EMFILE && dir && dir->parent)
++  int err = errno;
++  if ((err == EMFILE || err == ENFILE) && dir && dir->parent)
+     {
+       struct tar_stat_info *p;
+       for (p = dir->parent->parent; p; p = p->parent)
+@@ -1286,7 +1287,7 @@ open_failure_recover (struct tar_stat_info const *dir)
+ 	    tar_stat_close (p);
+ 	    return true;
+ 	  }
+-      errno = EMFILE;
++      errno = err;
+     }
+ 
+   return false;
+diff --git a/src/extract.c b/src/extract.c
+index 4685f83c..a31f560b 100644
+--- a/src/extract.c
++++ b/src/extract.c
+@@ -698,7 +698,7 @@ fixup_delayed_set_stat (char const *src, char const *dst)
+    create all required directories.  Return zero if all the required
+    directories were created, nonzero (issuing a diagnostic) otherwise.
+    Set *INTERDIR_MADE (unless NULL) if at least one directory was created. */
+-static int
++int
+ make_directories (char *file_name, bool *interdir_made)
+ {
+   char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
+@@ -741,7 +741,7 @@ make_directories (char *file_name, bool *interdir_made)
+ 	     repair_delayed_set_stat may need to update the struct.  */
+ 	  delay_set_stat (file_name,
+ 			  0, mode & ~ current_umask, MODE_RWX,
+-			  desired_mode, AT_SYMLINK_NOFOLLOW);
++			    desired_mode, AT_SYMLINK_NOFOLLOW);
+ 	  if (interdir_made)
+ 	    *interdir_made = true;
+ 	  print_for_mkdir (file_name, desired_mode);
+@@ -974,7 +974,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links)
+ 	      && memcmp (file_name, data->file_name, data->file_name_len) == 0))
+ 	break;
+ 
+-      chdir_do (data->change_dir);
++      chdir_do (data->change_dir, false);
+ 
+       if (check_for_renamed_directories)
+ 	{
+@@ -1844,7 +1844,7 @@ extract_archive (void)
+     {
+       int dir = chdir_current;
+       apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0);
+-      chdir_do (dir);
++      chdir_do (dir, false);
+     }
+ 
+   /* Take a safety backup of a previously existing file.  */
+@@ -1864,8 +1864,35 @@ extract_archive (void)
+   typeflag = sparse_member_p (&current_stat_info) ?
+                   GNUTYPE_SPARSE : current_header->header.typeflag;
+ 
+-  bool ok = prepare_to_extract (current_stat_info.file_name, typeflag, &fun)
+-            && fun (current_stat_info.file_name, typeflag) == 0;
++  bool ok = false;
++  if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun))
++    {
++      if (one_top_level_dir)
++	{
++	  /* Create one_top_level dir if it does not exist.  */
++	  chdir_do (chdir_current, true);
++	  /* Flush delayed stat to mirror the code above that does it
++	     before extracting a new entry. Creating the one_top_level
++	     dir may have created new delayed_set_stat interdir
++	     entries, so repeat the operation. Ideally this should not
++	     be needed, but the newly-created interdir entries have
++	     st_dev/st_ino uninitialized, which would be a problem if
++	     there is a "." entry afterwards:
++	     apply_nonancestor_delayed_set_stat would use the
++	     uninitialized values. Ideally, st_dev/st_ino would be
++	     initialized by mark_metadata_set, but this one does not
++	     take chdir into account, so it stats a wrong file. */
++	  if (!delay_directory_restore_option)
++	    {
++	      int dir = chdir_current;
++	      apply_nonancestor_delayed_set_stat (current_stat_info.file_name,
++						  false);
++	      chdir_do (dir, false);
++	    }
++	}
++      if (fun (current_stat_info.file_name, typeflag) == 0)
++	ok = true;
++    }
+   skip_member ();
+   if (!ok && backup_option)
+     undo_last_backup ();
+@@ -1878,7 +1905,7 @@ apply_delayed_link (struct delayed_link *ds)
+   struct string_list *sources = ds->sources;
+   char const *valid_source = NULL;
+ 
+-  chdir_do (ds->change_dir);
++  chdir_do (ds->change_dir, false);
+ 
+   for (sources = ds->sources; sources; sources = sources->next)
+     {
+diff --git a/src/list.c b/src/list.c
+index 7d7cde5a..068ca5fb 100644
+--- a/src/list.c
++++ b/src/list.c
+@@ -128,16 +128,28 @@ enforce_one_top_level (char **pfile_name)
+       int pos = strlen (one_top_level_dir);
+       if (strncmp (p, one_top_level_dir, pos) == 0)
+ 	{
+-	  if (ISSLASH (p[pos]) || p[pos] == 0)
+-	    return;
++	  /* Remove the one_top_level_dir prefix if it ends at
++	     component boundary.  */
++	  if (ISSLASH (p[pos]))
++	    {
++	      *pfile_name = xstrdup (p[pos+1] ? &p[pos+1] : ".");
++	      free (file_name);
++	      return;
++	    }
++	  else if (p[pos] == 0)
++	    {
++	      *pfile_name = xstrdup (".");
++	      free (file_name);
++	      return;
++	    }
+ 	}
+-
+-      *pfile_name = make_file_name (one_top_level_dir, file_name);
+-      normalize_filename_x (*pfile_name);
++      /* If the prefix does not match, do nothing.  */
+     }
+   else
+-    *pfile_name = xstrdup (one_top_level_dir);
+-  free (file_name);
++    {
++      *pfile_name = xstrdup (".");
++      free (file_name);
++    }
+ }
+ 
+ void
+@@ -158,8 +170,15 @@ transform_stat_info (int typeflag, struct tar_stat_info *stat_info)
+       transform_member_name (&stat_info->link_name, XFORM_LINK);
+     }
+ 
+-  if (one_top_level_option)
+-    enforce_one_top_level (&current_stat_info.file_name);
++  if (one_top_level_dir)
++    {
++      enforce_one_top_level (&stat_info->file_name);
++      /* Hard links are interpreted relative to cwd, and --one-top-level
++	 works by means of a hidden change of cwd to the requested directory.
++	 Adjust hard link targets as well.  */
++      if (typeflag == LNKTYPE)
++	enforce_one_top_level (&stat_info->link_name);
++    }
+ }
+ 
+ /* Main loop for reading an archive.  */
+@@ -1159,9 +1178,9 @@ simple_print_header (struct tar_stat_info *st, union block *blk,
+   int sizelen;
+ 
+   if (show_transformed_names_option)
+-    temp_name = st->file_name ? st->file_name : st->orig_file_name;
++    temp_name = transform_top_level (st->file_name ? st->file_name : st->orig_file_name);
+   else
+-    temp_name = st->orig_file_name ? st->orig_file_name : st->file_name;
++    temp_name = xstrdup (st->orig_file_name ? st->orig_file_name : st->file_name);
+ 
+   if (block_number_option)
+     {
+@@ -1354,6 +1373,7 @@ simple_print_header (struct tar_stat_info *st, union block *blk,
+     }
+   fflush (stdlis);
+   xattrs_print (st);
++  free (temp_name);
+ }
+ 
+ 
+diff --git a/src/misc.c b/src/misc.c
+index 575d13d9..f6c9a819 100644
+--- a/src/misc.c
++++ b/src/misc.c
+@@ -23,6 +23,7 @@
+ #include <xgetcwd.h>
+ #include <unlinkdir.h>
+ #include <utimens.h>
++#include <assert.h>
+ 
+ #ifndef DOUBLE_SLASH_IS_DISTINCT_ROOT
+ # define DOUBLE_SLASH_IS_DISTINCT_ROOT 0
+@@ -946,6 +947,7 @@ struct wd
+      to be used.  */
+   int fd;
+ 
++  bool one_top_level;
+   /* If ID.err is zero, the directory's identity;
+      if positive, a failure indication with errno = ID.err;
+      if negative, no attempt has been made yet to get the identity.  */
+@@ -977,9 +979,17 @@ static size_t wdcache_count;
+ int
+ chdir_count (void)
+ {
+-  if (wd_count == 0)
+-    return wd_count;
+-  return wd_count - 1;
++  int count = 0;
++  if (wd_count)
++    {
++      /* Do not count the initial CWD entry -> start at 1.  */
++      for (int i = 1; i < wd_count; i++)
++	{
++	  if (! wd[i].one_top_level)
++	    count++;
++	}
++    }
++  return count;
+ }
+ 
+ /* Grow the WD table by at least one entry.  */
+@@ -996,15 +1006,27 @@ grow_wd (void)
+       wd[wd_count].abspath = NULL;
+       wd[wd_count].fd = AT_FDCWD;
+       wd[wd_count].id.err = -1;
++      wd[wd_count].one_top_level = false;
+       wd_count++;
++      if (one_top_level_dir)
++	{
++	  wd[wd_count].name = one_top_level_dir;
++	  wd[wd_count].abspath = NULL;
++	  wd[wd_count].fd = 0;
++	  wd[wd_count].id.err = -1;
++	  wd[wd_count].one_top_level = true;
++	  wd_count++;
++	}
+     }
+ }
+ 
+ /* DIR is the operand of a -C option; add it to vector of chdir targets,
+    and return the index of its location.  */
+ int
+-chdir_arg (char const *dir)
++chdir_arg (char const *dir, bool one_top_level)
+ {
++  if (one_top_level)
++    chdir_arg (dir, false);
+   if (wd_count == wd_alloc)
+     grow_wd ();
+ 
+@@ -1014,13 +1036,22 @@ chdir_arg (char const *dir)
+     {
+       dir += dotslashlen (dir);
+       if (! dir[dir[0] == '.'])
+-	return wd_count - 1;
++	{
++	  if (wd[wd_count - 1].one_top_level == one_top_level)
++	    return wd_count - 1;
++	  else
++	    return wd_count - 2;
++	}
+     }
+ 
++  if (one_top_level)
++    dir = one_top_level_dir;
++
+   wd[wd_count].name = dir;
+   wd[wd_count].abspath = NULL;
+   wd[wd_count].fd = 0;
+   wd[wd_count].id.err = -1;
++  wd[wd_count].one_top_level = one_top_level;
+   return wd_count++;
+ }
+ 
+@@ -1039,21 +1070,74 @@ static int chdir_fd = AT_FDCWD;
+    working directory; otherwise, I must be a value returned by
+    chdir_arg.  */
+ void
+-chdir_do (int i)
++chdir_do (int i, bool create)
+ {
+-  if (chdir_current != i)
+-    {
+-      struct wd *curr = &wd[i];
+-      int fd = curr->fd;
++  struct wd *curr = &wd[i];
++  int fd = curr->fd;
+ 
+-      if (! fd)
++  /* Nothing to create unless we are at the one_top_level dir that has
++     not been created yet.  */
++  create = create && curr->one_top_level && (fd == BADFD || fd == 0);
++
++  if (chdir_current != i || create)
++    {
++      if (! fd || create)
+ 	{
+ 	  if (! IS_ABSOLUTE_FILE_NAME (curr->name))
+-	    chdir_do (i - 1);
++	    {
++	      int j = i - 1;
++	      if (wd[j].one_top_level)
++		{
++		  j--;
++		  assert (! wd[j].one_top_level);
++		}
++	      chdir_do (j, false);
++	    }
+ 	  fd = openat (chdir_fd, curr->name,
+ 		       open_searchdir_how.flags & ~O_NOFOLLOW);
+ 	  if (fd < 0)
+-	    open_fatal (curr->name);
++	    {
++	      if (create)
++		{
++		  char *dir_with_dot;
++		  struct open_how saved_open_searchdir_how = open_searchdir_how;
++		  /* Don't use O_BENEATH during creation of the
++		     directory. The one-top-level directory is
++		     allowed to be given as an absolute path.  */
++		  open_searchdir_how.resolve = 0;
++		  /* Append a dot. make_directories creates
++		     directories up to and excluding the last
++		     component of the path. So, in order to create
++		     "a/b", we need to pass "a/b/." to it. */
++		  {
++		    namebuf_t nbuf = namebuf_create (curr->name);
++		    namebuf_add_dir (nbuf, ".");
++		    dir_with_dot = namebuf_finish (nbuf);
++		  }
++		  if (make_directories (dir_with_dot, NULL) == 0)
++		    /* Directory created, retry */
++		    fd = openat (chdir_fd, curr->name,
++				 open_searchdir_how.flags & ~O_NOFOLLOW);
++		  open_searchdir_how = saved_open_searchdir_how;
++		  free (dir_with_dot);
++		  /* Either the creation or open failed */
++		  if (fd < 0)
++		    open_fatal (curr->name);
++		}
++	      else if (errno == ENOENT && curr->one_top_level)
++		{
++		  /* We are requested to not create the directory now. Mark it
++		     as to be created later when called with create == true. */
++		  chdir_fd = curr->fd = BADFD;
++		  chdir_current = i;
++		  /* Do not add it to the cache */
++		  return;
++		}
++	      else
++		{
++		  open_fatal (curr->name);
++		}
++	    }
+ 
+ 	  curr->fd = fd;
+ 
+@@ -1071,7 +1155,7 @@ chdir_do (int i)
+ 	    }
+ 	}
+ 
+-      if (0 < fd)
++      if (0 < fd && /* no assumption about sign of BADFD */ fd != BADFD)
+ 	{
+ 	  /* Move the i value to the front of the cache.  This is
+ 	     O(CHDIR_CACHE_SIZE), but the cache is small.  */
+@@ -1175,6 +1259,14 @@ fdbase_opendir (char const *file_name, bool alternate)
+ {
+   char const *name = file_name;
+ 
++  if (chdir_fd == BADFD && ! IS_ABSOLUTE_FILE_NAME (file_name))
++    {
++      /* BADFD is a sentinel value meaning that the chdir directory
++	 needs to be created lazily, therefore if we encounter it, the
++	 directory does not exist yet. */
++      errno = ENOENT;
++      return (struct fdbase) { .fd = chdir_fd, .base = name };
++    }
+   /* Skip past leading "./"s,
+      but not past the last "./" if that ends the name.  */
+   idx_t dslen = dotslashlen (name);
+@@ -1219,13 +1311,13 @@ fdbase_opendir (char const *file_name, bool alternate)
+ 	  if (subfd < 0)
+ 	    {
+ 	      /* Keep the old directory cached and report open failure,
+-		 unless EMFILE means it's possible that falling
++		 unless EMFILE/ENFILE means it's possible that falling
+ 		 through to close the old directory would mean we
+ 		 could successfully retry from the chdir_fd level.
+ 	         When reporting failure, there is no need to
+ 	         null-terminate the old directory, since the code does
+ 	         not assume null termination.  */
+-	      if (errno != EMFILE)
++	      if (errno != EMFILE && errno != ENFILE)
+ 		return (struct fdbase) { .fd = BADFD, .base = base };
+ 	    }
+ 	  else
+@@ -1279,6 +1371,29 @@ tar_dirname (void)
+   return wd[chdir_current].name;
+ }
+ 
++/* Return a newly allocated string that shows NAME from the user's
++   viewpoint, given that --one-top-level may be in effect.  */
++char *
++transform_top_level (const char *name)
++{
++  if (wd[chdir_current].one_top_level)
++    {
++      if (strcmp (name, ".") == 0)
++	{
++	  /* nothing to append - .../. is the same as ... */
++	  return xstrdup (wd[chdir_current].name);
++	}
++      else
++	{
++	  namebuf_t nbuf = namebuf_create (wd[chdir_current].name);
++	  namebuf_add_dir (nbuf, name);
++	  return namebuf_finish (nbuf);
++	}
++    }
++  else
++    return xstrdup (name);
++}
++
+ /* Return the absolute path that represents the working
+    directory referenced by IDX.
+ 
+@@ -1307,12 +1422,13 @@ tar_getcdpath (int idx)
+       int save_cwdi = chdir_current;
+ 
+       for (i = idx; i >= 0; i--)
+-	if (wd[i].abspath)
++	if (wd[i].abspath && !wd[i].one_top_level)
+ 	  break;
+ 
+       while (++i <= idx)
+ 	{
+-	  chdir_do (i);
++	  if (!wd[i].one_top_level)
++	    chdir_do (i, false);
+ 	  if (i == 0)
+ 	    {
+ 	      if ((wd[i].abspath = xgetcwd ()) == NULL)
+@@ -1325,13 +1441,18 @@ tar_getcdpath (int idx)
+ 	    wd[i].abspath = xstrdup (wd[i].name);
+ 	  else
+ 	    {
+-	      namebuf_t nbuf = namebuf_create (wd[i - 1].abspath);
++	      int j = i - 1;
++	      if (wd[j].one_top_level)
++		{
++		  j--;
++		  assert (! wd[j].one_top_level);
++		}
++	      namebuf_t nbuf = namebuf_create (wd[j].abspath);
+ 	      namebuf_add_dir (nbuf, wd[i].name);
+ 	      wd[i].abspath = namebuf_finish (nbuf);
+ 	    }
+ 	}
+-
+-      chdir_do (save_cwdi);
++      chdir_do (save_cwdi, false);
+     }
+ 
+   return wd[idx].abspath;
+diff --git a/src/names.c b/src/names.c
+index 403192be..56cee63c 100644
+--- a/src/names.c
++++ b/src/names.c
+@@ -867,6 +867,7 @@ name_init (void)
+ {
+   name_buffer = xmalloc (NAME_FIELD_SIZE + 2);
+   name_buffer_length = NAME_FIELD_SIZE;
++  chdir_do (chdir_arg (".", !!one_top_level_dir), false);
+   name_list_adjust ();
+ }
+ 
+@@ -1119,7 +1120,8 @@ name_next_elt (int change_dirs)
+ 	case NELT_CHDIR:
+ 	  if (change_dirs)
+ 	    {
+-	      chdir_do (chdir_arg (xstrdup (ep->v.name)));
++	      chdir_do (chdir_arg (xstrdup (ep->v.name), !!one_top_level_dir),
++			false);
+ 	      name_list_advance ();
+ 	      break;
+ 	    }
+@@ -1182,7 +1184,7 @@ name_gather (void)
+       static int change_dir;
+ 
+       while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
+-	change_dir = chdir_arg (xstrdup (ep->v.name));
++	change_dir = chdir_arg (xstrdup (ep->v.name), !!one_top_level_dir);
+ 
+       if (ep)
+ 	{
+@@ -1211,7 +1213,7 @@ name_gather (void)
+ 	{
+ 	  int change_dir0 = change_dir;
+ 	  while ((ep = name_next_elt (0)) && ep->type == NELT_CHDIR)
+-	    change_dir = chdir_arg (xstrdup (ep->v.name));
++	    change_dir = chdir_arg (xstrdup (ep->v.name), !!one_top_level_dir);
+ 
+ 	  if (ep)
+ 	    addname (ep->v.name, change_dir, true, NULL);
+@@ -1326,7 +1328,7 @@ name_match (const char *file_name)
+ 
+       if (cursor->name[0] == 0)
+ 	{
+-	  chdir_do (cursor->change_dir);
++	  chdir_do (cursor->change_dir, false);
+ 	  namelist = NULL;
+ 	  nametail = NULL;
+ 	  return true;
+@@ -1348,7 +1350,7 @@ name_match (const char *file_name)
+ 	  if (!(ISSLASH (file_name[cursor->length]) && recursion_option)
+ 	      || cursor->found_count == 0)
+ 	    cursor->found_count++; /* remember it matched */
+-	  chdir_do (cursor->change_dir);
++	  chdir_do (cursor->change_dir, false);
+ 	  /* We got a match.  */
+ 	  return ISFOUND (cursor);
+ 	}
+@@ -1778,7 +1780,7 @@ collect_and_sort_names (void)
+       if (name->found_count || name->directory)
+ 	continue;
+ 
+-      chdir_do (name->change_dir);
++      chdir_do (name->change_dir, false);
+ 
+       if (name->name[0] == 0)
+ 	continue;
+@@ -1924,7 +1926,7 @@ name_from_list (void)
+     {
+       if (!gnu_list_name->is_wildcard)
+ 	gnu_list_name->found_count++;
+-      chdir_do (gnu_list_name->change_dir);
++      chdir_do (gnu_list_name->change_dir, false);
+       return gnu_list_name;
+     }
+   return NULL;
+diff --git a/src/tar.c b/src/tar.c
+index 9a5fbd0b..56c9ed2a 100644
+--- a/src/tar.c
++++ b/src/tar.c
+@@ -1572,7 +1572,6 @@ parse_opt (int key, char *arg, struct argp_state *state)
+ 
+     case ONE_TOP_LEVEL_OPTION:
+       optloc_save (OC_ONE_TOP_LEVEL, args->loc);
+-      one_top_level_option = true;
+       one_top_level_dir = arg;
+       break;
+ 
+@@ -2576,9 +2575,11 @@ decode_options (int argc, char **argv)
+ 	same_order_option = false;
+     }
+ 
+-  if (one_top_level_option)
++  if (optloc_lookup (OC_ONE_TOP_LEVEL))
+     {
+-      char *base;
++      if (!IS_SUBCOMMAND_CLASS (SUBCL_READ))
++	option_conflict_error ("--one-top-level",
++			       subcommand_string (subcommand_option));
+ 
+       if (absolute_names_option)
+ 	{
+@@ -2589,17 +2590,18 @@ decode_options (int argc, char **argv)
+ 
+ 	  if (optloc_eq (one_top_level_loc, absolute_names_loc))
+ 	    option_conflict_error ("--one-top-level", "--absolute-names");
+-	  else if (one_top_level_loc->source == OPTS_COMMAND_LINE)
++	  if (one_top_level_loc->source == OPTS_COMMAND_LINE)
+ 	    absolute_names_option = false;
+ 	  else
+-	    one_top_level_option = false;
++	    one_top_level_dir = NULL;
+ 	}
+ 
+-      if (!one_top_level_dir && one_top_level_option)
++      if (!absolute_names_option && !one_top_level_dir)
+ 	{
+-	  /* If the user wants to guarantee that everything is under one
+-	     directory, determine its name now and let it be created later.  */
+-	  base = base_name (archive_name_array[0]);
++	  /* Determine name now; the directory (or directories, if -C
++	     means there are multiple top-level directories) are
++	     created later if needed.  */
++	  char *base = base_name (archive_name_array[0]);
+ 	  one_top_level_dir = strip_compression_suffix (base);
+ 	  free (base);
+ 
+@@ -2609,9 +2611,7 @@ decode_options (int argc, char **argv)
+ 			    "please set it explicitly with --one-top-level=DIR")));
+ 	}
+ 
+-      if (one_top_level_dir && !IS_RELATIVE_FILE_NAME (one_top_level_dir))
+-	USAGE_ERROR ((0, 0,
+-		     _("--one-top-level=DIR must use a relative file name")));
++      normalize_filename_x (one_top_level_dir);
+     }
+ 
+   /* If ready to unlink hierarchies, so we are for simpler files.  */
+diff --git a/src/unlink.c b/src/unlink.c
+index 33d2ccd1..e199bab3 100644
+--- a/src/unlink.c
++++ b/src/unlink.c
+@@ -103,7 +103,7 @@ flush_deferred_unlinks (bool force)
+       if (force
+ 	  || records_written > p->records_written + deferred_unlink_delay)
+ 	{
+-	  chdir_do (p->dir_idx);
++	  chdir_do (p->dir_idx, false);
+ 	  if (p->is_dir)
+ 	    {
+ 	      const char *fname;
+@@ -175,11 +175,11 @@ flush_deferred_unlinks (bool force)
+ 	  struct deferred_unlink *next = p->next;
+ 	  const char *fname;
+ 
+-	  chdir_do (p->dir_idx);
++	  chdir_do (p->dir_idx, false);
+ 	  if (p->dir_idx && IS_CWD (p))
+ 	    {
+ 	      fname = tar_dirname ();
+-	      chdir_do (p->dir_idx - 1);
++	      chdir_do (p->dir_idx - 1, false);
+ 	    }
+ 	  else
+ 	    fname = p->file_name;
+@@ -195,9 +195,9 @@ flush_deferred_unlinks (bool force)
+ 	  p = next;
+ 	}
+       dunlink_head = dunlink_tail = NULL;
+-    }	  
+-	    
+-  chdir_do (saved_chdir);
++    }
++
++  chdir_do (saved_chdir, false);
+ }
+ 
+ void
+diff --git a/src/update.c b/src/update.c
+index 2bc38207..24167778 100644
+--- a/src/update.c
++++ b/src/update.c
+@@ -141,7 +141,7 @@ update_archive (void)
+ 	      {
+ 		struct stat s;
+ 
+-		chdir_do (name->change_dir);
++		chdir_do (name->change_dir, false);
+ 		if (deref_stat (current_stat_info.file_name, &s) == 0)
+ 		  {
+ 		    if (S_ISDIR (s.st_mode))

diff --git a/tar.spec b/tar.spec
index 01f3349..2e6b3a5 100644
--- a/tar.spec
+++ b/tar.spec
@@ -58,6 +58,7 @@ Patch22: tar-1.35-no-overwrite-dir-no-overwrite-even-temporarily.patch
 # d1aeb7388926e045bdec0f7934c5522c4745f02c
 # 45b6e6898d1f931bfca41d961289bd6ac33238e5
 Patch23: tar-1.35-CVE-2025-45582.patch
+# merely a preparation to apply Patch27, the change is obsoleted by it
 Patch24: tar-1.35-tar-one-top-level-DIR-must-be-relative.patch
 # Source: https://cgit.git.savannah.gnu.org/cgit/tar.git/commit/?id=08c3fc2e9337094aff01a511170fd35fdb8f1ee3
 # Fixes build with acl 2.4.0
@@ -68,6 +69,19 @@ Patch25: tar-1.35-Avoid-acl_-prefix-for-functions.patch
 # 67981bbb1587803bb1e029393d2228492cef8c4f
 # 19a3a73e8c48bd3c59cbea9b5ed6780fc6836c6d
 Patch26: tar-1.35-CVE-2026-5704.patch
+#Upstream commits
+# bfc33463942060316f70f275471ed202b0076972
+# b4fc9ca13617411c5db57286a3ff534bd40acdc1
+# 67c8dff6968aae58fcdfb05268eb1b4c07308f1a
+# 55e8233438b3c13294109df502a9b220a9a3f4f5
+# 0470c109c08f466d8332ba3326070554b4d81aa1
+# 1b91f5f66f8e6c490eef0fdee50f652cfe155844
+# 325b899214ac13519153318e66c889a31c15342d
+# 1980e032afe60c5fe0e5df573457885ece4e69d5
+# e335e2c8b102f83b63ffd373f0b88401da3ebe6c
+# part of 941f62b2
+# Also "by the way" fixes CVE-2026-18508.
+Patch27: tar-1.35-fix-absolute-one-top-level.patch
 
 BuildRequires: autoconf
 BuildRequires: automake

                 reply	other threads:[~2026-09-07 17:57 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178880383837.1.2386869887287242740.rpms-tar-f4b2efc9a092@fedoraproject.org \
    --to=pcahyna@redhat.com \
    --cc=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox