Thanks for reporting the bug; I installed the attached somewhat-fancier patch
to fix it.
Thanks also for the test case; I sent you separately instructions for the
copyright paperwork.
From cfdb891bbd68c920ea75210977593f3cd08f0418 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Tue, 13 Oct 2026 08:56:40 -0700
Subject: [PATCH] tar: fix two-digit octal escapes
Problem reported by Nexory in:
https://lists.gnu.org/r/bug-tar/2026-10/msg00005.html
* src/common.h (is_octal_digit): Make inline and move here ...
* src/list.c: ... from here.
* src/misc.c (unquote_string): Simplify code and fix
octal-escape handling.
---
src/common.h | 6 ++++++
src/list.c | 6 ------
src/misc.c | 17 ++++++-----------
3 files changed, 12 insertions(+), 17 deletions(-)
diff --git a/src/common.h b/src/common.h
index 38f9958e..cbb7906d 100644
--- a/src/common.h
+++ b/src/common.h
@@ -494,6 +494,12 @@ void buffer_write_global_xheader (void);
const char *first_decompress_program (int *pstate);
const char *next_decompress_program (int *pstate);
+COMMON_INLINE bool
+is_octal_digit (char c)
+{
+ return '0' <= c && c <= '7';
+}
+
/* Sum values returned by printf to estimate the total bytes output.
Estimate -1 if there was a problem, e.g., int overflow or I/O error. */
COMMON_INLINE intmax_t
diff --git a/src/list.c b/src/list.c
index b0da0cec..6d4dc7ae 100644
--- a/src/list.c
+++ b/src/list.c
@@ -630,12 +630,6 @@ read_header (union block **return_block, struct tar_stat_info *info,
return status;
}
-static bool
-is_octal_digit (char c)
-{
- return '0' <= c && c <= '7';
-}
-
/* Decode things from a file HEADER block into STAT_INFO, also setting
*FORMAT_POINTER depending on the header block format. If
DO_USER_GROUP, decode the user/group information (this is useful
diff --git a/src/misc.c b/src/misc.c
index 499ea3e1..6135fff9 100644
--- a/src/misc.c
+++ b/src/misc.c
@@ -244,19 +244,14 @@ unquote_string (char *string)
case '7':
{
unsigned char value = *source++ - '0';
-
- if (*source < '0' || *source > '7')
+ if (is_octal_digit (*source))
{
- *destination++ = value;
- break;
- }
- unsigned char val1 = value * 8 + (*source++ - '0'), val2;
- if (*source < '0' || *source > '7' || ckd_mul (&val2, val1, 8))
- {
- *destination++ = value;
- break;
+ value = value * 8 + (*source++ - '0');
+ unsigned char val;
+ if (is_octal_digit (*source) && !ckd_mul (&val, value, 8))
+ value = val + (*source++ - '0');
}
- *destination++ = val2 + (*source++ - '0');
+ *destination++ = value;
break;
}
--
2.53.0