Hi Sergey, thanks for installing the symlink fix in August. While reading read_in_tar_header I noticed that the AREGTYPE case can no longer recognize a directory.
In the old tar format a directory is a member with type flag '\0' whose name ends in a slash. The code looks for that slash at c_name[c_namesize - 1], but c_namesize counts the terminating NUL (cpio.h, cpio_set_c_name), so that byte is always NUL and every such member is taken for a regular file. With an archive holding olddir/ and oldfile, both with type flag '\0': cpio -ntv: -rwxr-xr-x ... olddir/ cpio -id: cpio: olddir/: Cannot open: Is a directory GNU tar 1.35 lists olddir/ in the same archive as a directory. The index came in with d36ec5f, which replaced strlen (c_name) by c_namesize; before that the line read c_name[strlen (c_name) - 1]. The patch looks at c_namesize - 2 and checks that the name is not empty first, since c_namesize is 1 then. Current GNU tar writes directories with type flag '5' in the v7, oldgnu, gnu and ustar formats, so archives it writes are not affected. The patch includes an autotest case, with the archive embedded in base64 as in symlink-bad-length.at. It fails before the change and passes after it. Full suite here: 18 of 18 before, 19 of 19 after. As last time, if the test case needs copyright paperwork, please just take the src/tar.c part. Regards, Nexory
From 7ff8ac7a90914123f2c22b4489b12daf4acc270e Mon Sep 17 00:00:00 2001 From: Nexory <[email protected]> Date: Sat, 10 Oct 2026 01:43:39 +0200 Subject: [PATCH] tar: recognize old-format directories again In the old tar format a directory is an AREGTYPE member whose name ends in a slash. read_in_tar_header looks for that slash at c_name[c_namesize - 1], but c_namesize counts the terminating NUL (see cpio.h and cpio_set_c_name), so that byte is always NUL and every such member is taken for a regular file: cpio -t lists it with mode -rwxr-xr-x and cpio -i fails with "Cannot open: Is a directory". The index came in with commit d36ec5f, which replaced strlen (c_name) by c_namesize; the old code read c_name[strlen (c_name) - 1]. Check c_namesize first, since it is 1 for an empty name. * src/tar.c (read_in_tar_header): Look for the slash at c_namesize - 2, if the name is not empty. * tests/aregtype-dir.at: New file. * tests/Makefile.am: Add new test case. * tests/testsuite.at: Likewise. --- src/tar.c | 6 ++- tests/Makefile.am | 3 +- tests/aregtype-dir.at | 90 +++++++++++++++++++++++++++++++++++++++++++ tests/testsuite.at | 1 + 4 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 tests/aregtype-dir.at diff --git a/src/tar.c b/src/tar.c index fc9bde9..207675f 100644 --- a/src/tar.c +++ b/src/tar.c @@ -433,8 +433,10 @@ read_in_tar_header (struct cpio_file_stat *file_hdr, int in_des) case AREGTYPE: /* Old tar format; if the last char in filename is '/' then it is - a directory, otherwise it's a regular file. */ - if (file_hdr->c_name[file_hdr->c_namesize - 1] == '/') + a directory, otherwise it's a regular file. C_NAMESIZE counts + the terminating NUL, so the last char is at C_NAMESIZE - 2. */ + if (file_hdr->c_namesize > 1 + && file_hdr->c_name[file_hdr->c_namesize - 2] == '/') file_hdr->c_mode |= CP_IFDIR; else file_hdr->c_mode |= CP_IFREG; diff --git a/tests/Makefile.am b/tests/Makefile.am index e5096e5..0ff4469 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -58,7 +58,8 @@ TESTSUITE_AT = \ CVE-2019-14866.at\ linktime.at\ linktime01.at\ - symlink-tar-abs.at + symlink-tar-abs.at\ + aregtype-dir.at TESTSUITE = $(srcdir)/testsuite diff --git a/tests/aregtype-dir.at b/tests/aregtype-dir.at new file mode 100644 index 0000000..d65d0d8 --- /dev/null +++ b/tests/aregtype-dir.at @@ -0,0 +1,90 @@ +# Process this file with autom4te to create testsuite. -*- Autotest -*- +# Copyright (C) 2026 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# Old (pre-POSIX) tar archives mark a directory by a trailing `/' in +# the member name and leave the type flag at '\0' (AREGTYPE). The +# archive below holds such a directory, olddir/, and a regular file, +# oldfile, both with type flag '\0'. + +AT_SETUP([old tar format: trailing slash marks a directory]) +AT_KEYWORDS([aregtype-dir]) + +AT_DATA([ARCHIVE.base64], +[b2xkZGlyLwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA3NTUAMDAwMDAw +MAAwMDAwMDAwADAwMDAwMDAwMDAwADE0NTI0NzcwNDAwADAwNTc2MAAgAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABv +bGRmaWxlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDY0NAAwMDAwMDAw +ADAwMDAwMDAAMDAwMDAwMDAwMDYAMTQ1MjQ3NzA0MDAAMDA2MDQ1ACAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhl +bGxvCgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== +]) + +AT_CHECK([ +base64 -d ARCHIVE.base64 > ARCHIVE || AT_SKIP_TEST +cpio -ntv < ARCHIVE | sed 's/ .* / /' +mkdir out +cd out +cpio -id < ../ARCHIVE +test -d olddir && echo olddir is a directory +cat oldfile +], +[0], +[drwxr-xr-x olddir/ +-rw-r--r-- oldfile +olddir is a directory +hello +], +[ignore]) + +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at index 099c1e9..8604243 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -49,3 +49,4 @@ m4_include([linktime01.at]) m4_include([symlink-tar-abs.at]) +m4_include([aregtype-dir.at]) -- 2.47.3
