diff --git a/build/cmake/CMakeModules/ZstdOptions.cmake b/build/cmake/CMakeModules/ZstdOptions.cmake index dc7813444e2..641bba50cc9 100644 --- a/build/cmake/CMakeModules/ZstdOptions.cmake +++ b/build/cmake/CMakeModules/ZstdOptions.cmake @@ -66,3 +66,13 @@ endif() # Set global definitions add_definitions(-DXXH_NAMESPACE=ZSTD_) + +# Optional experimental Clang -fbounds-safety (OFF by default; macros inert otherwise) +option(ZSTD_ENABLE_FBOUNDS_SAFETY + "Enable experimental Clang -fbounds-safety annotations (OFF by default)" + OFF) +if(ZSTD_ENABLE_FBOUNDS_SAFETY) + message(STATUS "ZSTD_ENABLE_FBOUNDS_SAFETY enabled") +else() + message(STATUS "ZSTD_ENABLE_FBOUNDS_SAFETY disabled") +endif() diff --git a/build/cmake/lib/CMakeLists.txt b/build/cmake/lib/CMakeLists.txt index 82f4f50869c..28a73fe9f5f 100644 --- a/build/cmake/lib/CMakeLists.txt +++ b/build/cmake/lib/CMakeLists.txt @@ -266,6 +266,16 @@ configure_file("${LIBRARY_DIR}/libzstd.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/libzs install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libzstd.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") # install target +# Optional -fbounds-safety (inert macros unless enabled) +if(ZSTD_ENABLE_FBOUNDS_SAFETY) + foreach(_zstd_tgt IN ITEMS libzstd_shared libzstd_static) + if(TARGET ${_zstd_tgt}) + target_compile_definitions(${_zstd_tgt} PUBLIC ZSTD_SUPPORT_FBOUNDS_SAFETY) + target_compile_options(${_zstd_tgt} PUBLIC -fbounds-safety) + endif() + endforeach() +endif() + install(FILES ${PublicHeaders} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") foreach(target_suffix IN ITEMS "_shared" "_static" "") diff --git a/lib/Makefile b/lib/Makefile index 569bd6090c9..816a5f81253 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -371,6 +371,7 @@ install-includes: [ -e $(DESTDIR)$(INCLUDEDIR) ] || $(INSTALL) -d -m 755 $(DESTDIR)$(INCLUDEDIR)/ @echo Installing includes $(INSTALL_DATA) zstd.h $(DESTDIR)$(INCLUDEDIR) + $(INSTALL_DATA) zstd_bounds_safety.h $(DESTDIR)$(INCLUDEDIR) $(INSTALL_DATA) zstd_errors.h $(DESTDIR)$(INCLUDEDIR) $(INSTALL_DATA) zdict.h $(DESTDIR)$(INCLUDEDIR) @@ -382,6 +383,7 @@ uninstall: $(RM) $(DESTDIR)$(LIBDIR)/$(LIBZSTD) $(RM) $(DESTDIR)$(PKGCONFIGDIR)/libzstd.pc $(RM) $(DESTDIR)$(INCLUDEDIR)/zstd.h + $(RM) $(DESTDIR)$(INCLUDEDIR)/zstd_bounds_safety.h $(RM) $(DESTDIR)$(INCLUDEDIR)/zstd_errors.h $(RM) $(DESTDIR)$(INCLUDEDIR)/zdict.h @echo zstd libraries successfully uninstalled diff --git a/lib/decompress/zstd_decompress.c b/lib/decompress/zstd_decompress.c index 7e6e01b4759..ffc825ea1e6 100644 --- a/lib/decompress/zstd_decompress.c +++ b/lib/decompress/zstd_decompress.c @@ -2394,11 +2394,12 @@ size_t ZSTD_decompressStream_simpleArgs ( { ZSTD_outBuffer output; ZSTD_inBuffer input; - output.dst = dst; + /* Capacity before pointer so sized_by invariants hold under -fbounds-safety. */ output.size = dstCapacity; + output.dst = dst; output.pos = *dstPos; - input.src = src; input.size = srcSize; + input.src = src; input.pos = *srcPos; { size_t const cErr = ZSTD_decompressStream(dctx, &output, &input); *dstPos = output.pos; diff --git a/lib/libzstd.mk b/lib/libzstd.mk index 60928b0ee95..8fc15f33ec1 100644 --- a/lib/libzstd.mk +++ b/lib/libzstd.mk @@ -52,6 +52,13 @@ endif # Assembly support ZSTD_NO_ASM ?= 0 +# Optional Clang -fbounds-safety (OFF by default; inert macros otherwise) +ENABLE_FBOUNDS_SAFETY ?= 0 +ifeq ($(ENABLE_FBOUNDS_SAFETY),1) + CPPFLAGS += -DZSTD_SUPPORT_FBOUNDS_SAFETY + CFLAGS += -fbounds-safety +endif + ZSTD_LIB_EXCLUDE_COMPRESSORS_DFAST_AND_UP ?= 0 ZSTD_LIB_EXCLUDE_COMPRESSORS_GREEDY_AND_UP ?= 0 diff --git a/lib/zstd.h b/lib/zstd.h index 97fef316fd6..8205af01950 100644 --- a/lib/zstd.h +++ b/lib/zstd.h @@ -16,6 +16,7 @@ #include /* size_t */ #include "zstd_errors.h" /* list of errors */ +#include "zstd_bounds_safety.h" /* optional -fbounds-safety macros */ #if defined(ZSTD_STATIC_LINKING_ONLY) && !defined(ZSTD_H_ZSTD_STATIC_LINKING_ONLY) #include /* INT_MAX */ #endif /* ZSTD_STATIC_LINKING_ONLY */ @@ -699,13 +700,21 @@ ZSTDLIB_API size_t ZSTD_DCtx_reset(ZSTD_DCtx* dctx, ZSTD_ResetDirective reset); ****************************/ typedef struct ZSTD_inBuffer_s { - const void* src; /**< start of input buffer */ + /* size is the capacity companion for src (bytes). + * Field order is preserved (pointer before size) for public ABI; + * update sites assign capacity before the pointer so sized-by + * invariants hold under optional -fbounds-safety builds. + */ + const void* ZSTD_SIZED_BY_OR_NULL(size) src; /**< start of input buffer */ size_t size; /**< size of input buffer */ size_t pos; /**< position where reading stopped. Will be updated. Necessarily 0 <= pos <= size */ } ZSTD_inBuffer; typedef struct ZSTD_outBuffer_s { - void* dst; /**< start of output buffer */ + /* size is the capacity companion for dst (bytes). Same ABI / + * capacity-first assignment notes as ZSTD_inBuffer. + */ + void* ZSTD_SIZED_BY_OR_NULL(size) dst; /**< start of output buffer */ size_t size; /**< size of output buffer */ size_t pos; /**< position where writing stopped. Will be updated. Necessarily 0 <= pos <= size */ } ZSTD_outBuffer; diff --git a/lib/zstd_bounds_safety.h b/lib/zstd_bounds_safety.h new file mode 100644 index 00000000000..3a0a569b668 --- /dev/null +++ b/lib/zstd_bounds_safety.h @@ -0,0 +1,44 @@ +/* + * zstd_bounds_safety.h - portability macros for optional -fbounds-safety + * + * Copyright (c) 2026 Jeff Bindel + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + * + * When ZSTD_SUPPORT_FBOUNDS_SAFETY is defined (typically via + * -DZSTD_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 libwebp / libpng / giflib / lz4 inert-macro + * -fbounds-safety adoption: annotations are inert unless explicitly enabled. + */ +#ifndef ZSTD_BOUNDS_SAFETY_H_235446 +#define ZSTD_BOUNDS_SAFETY_H_235446 + +#ifdef ZSTD_SUPPORT_FBOUNDS_SAFETY + +# include +/* Non-ABI-breaking sized-by annotations for byte buffers whose companion + * field / argument is a capacity in bytes (ZSTD_inBuffer.size / + * ZSTD_outBuffer.size). Use *_OR_NULL when the pointer may be NULL while + * the companion size is zero. + */ +# define ZSTD_SIZED_BY(n) __sized_by(n) +# define ZSTD_SIZED_BY_OR_NULL(n) __sized_by_or_null(n) +# define ZSTD_COUNTED_BY(n) __counted_by(n) +# define ZSTD_COUNTED_BY_OR_NULL(n) __counted_by_or_null(n) + +#else /* !ZSTD_SUPPORT_FBOUNDS_SAFETY */ + +# define ZSTD_SIZED_BY(n) +# define ZSTD_SIZED_BY_OR_NULL(n) +# define ZSTD_COUNTED_BY(n) +# define ZSTD_COUNTED_BY_OR_NULL(n) + +#endif /* ZSTD_SUPPORT_FBOUNDS_SAFETY */ + +#endif /* ZSTD_BOUNDS_SAFETY_H_235446 */