Hello GNU tar maintainers,
I'd like to propose a small optional memory-safety hardening for GNU tar.
Summary:
- Annotate xattr_map.xm_map with optional Clang -fbounds-safety /
counted_by macros tied to xm_max (capacity), not xm_size (used count).
- Default builds unchanged (--enable-fbounds-safety OFF).
This is secure-by-design hardening (Google Patch Rewards style), not a
security bug report and not a PoC.
Unified diff attached (against savannah tar.git tip).
Thanks,
Jeff Bindel
[email protected]
diff --git a/configure.ac b/configure.ac
index a3a01700..0f2f1bb8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -32,6 +32,20 @@ AC_EXEEXT
AC_PROG_RANLIB
AC_PROG_YACC
gl_EARLY
+
+# Optional Clang -fbounds-safety. Default OFF: TAR_*COUNTED_BY* /
+# TAR_*SIZED_BY* macros in src/tar_bounds_safety.h are inert.
+AC_ARG_ENABLE([fbounds-safety],
+ [AS_HELP_STRING([--enable-fbounds-safety],
+ [enable experimental Clang -fbounds-safety annotations @<:@default=no@:>@])],
+ [enable_fbounds_safety=$enableval],
+ [enable_fbounds_safety=no])
+if test "x$enable_fbounds_safety" = xyes; then
+ AC_MSG_NOTICE([ENABLE_FBOUNDS_SAFETY enabled])
+ CFLAGS="$CFLAGS -DTAR_SUPPORT_FBOUNDS_SAFETY -fbounds-safety"
+else
+ AC_MSG_NOTICE([ENABLE_FBOUNDS_SAFETY disabled])
+fi
AC_CHECK_TOOLS([AR], [ar])
AC_CHECK_HEADERS_ONCE([linux/fd.h sys/mtio.h])
diff --git a/src/Makefile.am b/src/Makefile.am
index efda7905..2a973339 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -19,7 +19,7 @@
bin_PROGRAMS = tar
-noinst_HEADERS = arith.h common.h tar.h xattrs.h
+noinst_HEADERS = arith.h common.h tar.h tar_bounds_safety.h xattrs.h
tar_SOURCES = \
buffer.c\
checkpoint.c\
diff --git a/src/tar.h b/src/tar.h
index 390f6b24..9bd6fbba 100644
--- a/src/tar.h
+++ b/src/tar.h
@@ -17,6 +17,8 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
+#include "tar_bounds_safety.h" /* optional -fbounds-safety macros */
+
/* tar Header Block, from POSIX 1003.1-2024
<https://pubs.opengroup.org/onlinepubs/9799919799/utilities/pax.html#tagtcjh_21> */
@@ -298,9 +300,10 @@ struct xattr_array
struct xattr_map
{
- struct xattr_array *xm_map;
- idx_t xm_size; /* Size of the xattr map */
- idx_t xm_max; /* Max. number of entries in xattr_map */
+ /* Capacity is xm_max (allocated slots), NOT xm_size (used count). */
+ struct xattr_array *TAR_COUNTED_BY_OR_NULL (xm_max) xm_map;
+ idx_t xm_size; /* Used entry count (not capacity) */
+ idx_t xm_max; /* Allocated capacity (element count) */
};
struct tar_stat_info
diff --git a/src/tar_bounds_safety.h b/src/tar_bounds_safety.h
new file mode 100644
index 00000000..9ab9fbf3
--- /dev/null
+++ b/src/tar_bounds_safety.h
@@ -0,0 +1,54 @@
+/* Portability macros for optional Clang -fbounds-safety.
+
+ 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/>.
+
+ When TAR_SUPPORT_FBOUNDS_SAFETY is defined (typically via
+ -DTAR_SUPPORT_FBOUNDS_SAFETY and a Clang toolchain that implements
+ -fbounds-safety), these macros expand to Clang bounds annotations.
+ Otherwise they expand to nothing so default builds are unchanged.
+
+ Pattern matches libzip / libwebp / libpng / giflib / lz4 / zstd
+ inert-macro -fbounds-safety adoption: annotations are inert unless
+ explicitly enabled. */
+
+#ifndef TAR_BOUNDS_SAFETY_H
+#define TAR_BOUNDS_SAFETY_H
+
+#ifdef TAR_SUPPORT_FBOUNDS_SAFETY
+
+# include <ptrcheck.h>
+/* Non-ABI-breaking counted-by annotations for pointer members whose
+ * companion field is an element capacity (e.g. xattr_map.xm_max).
+ * Prefer TAR_COUNTED_BY for pointers that are non-NULL when live; use
+ * *_OR_NULL when the pointer may be NULL while the companion count is
+ * zero (xattr_map.xm_map after xattr_map_init). */
+# define TAR_COUNTED_BY(n) __counted_by(n)
+# define TAR_COUNTED_BY_OR_NULL(n) __counted_by_or_null(n)
+# define TAR_SIZED_BY(n) __sized_by(n)
+# define TAR_SIZED_BY_OR_NULL(n) __sized_by_or_null(n)
+
+#else /* !TAR_SUPPORT_FBOUNDS_SAFETY */
+
+# define TAR_COUNTED_BY(n)
+# define TAR_COUNTED_BY_OR_NULL(n)
+# define TAR_SIZED_BY(n)
+# define TAR_SIZED_BY_OR_NULL(n)
+
+#endif /* TAR_SUPPORT_FBOUNDS_SAFETY */
+
+#endif /* TAR_BOUNDS_SAFETY_H */
diff --git a/src/xattrs.c b/src/xattrs.c
index f98e8e0f..fafbb369 100644
--- a/src/xattrs.c
+++ b/src/xattrs.c
@@ -60,6 +60,10 @@ xattr_map_free (struct xattr_map *xattr_map)
free (xattr_map->xm_map[i].xval_ptr);
}
free (xattr_map->xm_map);
+ /* Pointer then capacity: counted_by invariants under -fbounds-safety. */
+ xattr_map->xm_map = NULL;
+ xattr_map->xm_size = 0;
+ xattr_map->xm_max = 0;
}
void
@@ -67,6 +71,8 @@ xattr_map_add (struct xattr_map *map,
const char *key, const char *val, idx_t len)
{
if (map->xm_size == map->xm_max)
+ /* xpalloc updates xm_max (capacity) before returning the new
+ pointer; assign then keeps counted_by(xm_max) consistent. */
map->xm_map = xpalloc (map->xm_map, &map->xm_max, 1, -1,
sizeof *map->xm_map);
struct xattr_array *p = &map->xm_map[map->xm_size];