Hello,

current master decodes a two-digit octal escape wrongly when no third
octal digit follows.  Since commit f96aff3c ("Prefer other types to
int in misc.c"), the second branch of the octal case in unquote_string
(src/misc.c) stores value, the first digit, although the second digit
has already been consumed.  So \53 becomes \5 and \12 becomes \1.
Before that commit the code stored the two-digit value, and the manual
says an escape has up to 3 octal digits.

For example, with a file named a+b and a list file holding the line
a\53b, tar cf archive -T list fails with

  tar: a\005b: Cannot stat: No such file or directory

The same branch is taken when the third digit would overflow a byte,
as in \400, which gives \4 instead of \40 followed by 0.  The patch
stores val1 in that branch and leaves the overflow check from f96aff3c
as it is, rather than restructuring the loop.  Escapes with one or
three digits, including \377 and \1011, decode as before.

tar 1.35 has the old code, and no release tag contains f96aff3c, so
only master is affected.

The patch adds an autotest case that reads names with one, two and
three digit escapes from a -T list and as operands.  It fails before
the change and passes after it.  Full suite here: 229 passed and 37
skipped before the change, 230 passed and 37 skipped after it.  I
built and tested only on GNU/Linux (Debian 13, gcc).

The change in src/misc.c is one line.  If the test needs a copyright
assignment, I am happy to do the FSF paperwork.

Regards,
Nexory
From ee2f3d80e0c84e8b80ac4a630fbf27ce1fc93096 Mon Sep 17 00:00:00 2001
From: Nexory <[email protected]>
Date: Sun, 11 Oct 2026 04:38:01 +0200
Subject: [PATCH] tar: fix two-digit octal escapes

Problem introduced by commit f96aff3ce92f ("Prefer other types to
int in misc.c").  With an escape like \12 that is not followed by
another octal digit, unquote_string stored the first digit alone
(\1) instead of the two-digit value.  The same happened with \400,
where the third digit would overflow a byte.
* src/misc.c (unquote_string): Store val1, not value.
* tests/unquote.at: New file.
* tests/Makefile.am (TESTSUITE_AT), tests/testsuite.at: Add it.
---
 src/misc.c         |  2 +-
 tests/Makefile.am  |  1 +
 tests/testsuite.at |  1 +
 tests/unquote.at   | 62 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 65 insertions(+), 1 deletion(-)
 create mode 100644 tests/unquote.at

diff --git a/src/misc.c b/src/misc.c
index 499ea3e1..00da4e7f 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -253,7 +253,7 @@ unquote_string (char *string)
 	    unsigned char val1 = value * 8 + (*source++ - '0'), val2;
 	    if (*source < '0' || *source > '7' || ckd_mul (&val2, val1, 8))
 	      {
-		*destination++ = value;
+		*destination++ = val1;
 		break;
 	      }
 	    *destination++ = val2 + (*source++ - '0');
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 81ac3075..7103dcc6 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -286,6 +286,7 @@ TESTSUITE_AT = \
  time01.at\
  time02.at\
  truncate.at\
+ unquote.at\
  update.at\
  update01.at\
  update02.at\
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 6243f0d8..9e3eaac5 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -266,6 +266,7 @@ m4_include([T-zfile.at])
 m4_include([T-nonl.at])
 m4_include([T-dir00.at])
 m4_include([T-dir01.at])
+m4_include([unquote.at])
 
 AT_BANNER([Various options])
 m4_include([indexfile.at])
diff --git a/tests/unquote.at b/tests/unquote.at
new file mode 100644
index 00000000..d2a8729a
--- /dev/null
+++ b/tests/unquote.at
@@ -0,0 +1,62 @@
+# Test suite for GNU tar.                             -*- Autotest -*-
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This file is part of GNU tar.
+#
+# GNU tar 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 of the License, or
+# (at your option) any later version.
+#
+# GNU tar 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/>.
+
+# Octal escapes of one, two and three digits in file names given in a
+# --files-from list or as operands.  A two-digit escape such as \53
+# not followed by another octal digit was decoded as its first digit
+# alone.  A third digit that would overflow a byte, as in \400, ends
+# the escape after two digits.
+
+AT_SETUP([octal escapes in file names])
+AT_KEYWORDS([files-from unquote octal])
+
+AT_DATA([file-list],
+[a\53b
+a\53
+cA\101
+cA\1011
+e\400
+f\7g
+])
+
+AT_TAR_CHECK([
+genfile --file 'a+b'
+genfile --file 'a+'
+genfile --file 'cAA'
+genfile --file 'cAA1'
+genfile --file 'e 0'
+genfile --file "`printf 'f\007g'`"
+
+tar cvf archive -T ../file-list
+echo ==
+tar cvf archive 'a\53b' 'cA\101'
+],
+[0],
+[a+b
+a+
+cAA
+cAA1
+e 0
+f\ag
+==
+a+b
+cAA
+],
+[],[],[],[ustar]) # Testing one format is enough
+
+AT_CLEANUP
-- 
2.47.3

Reply via email to