diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ee17ba7..4179126f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,16 @@ include(CMakePackageConfigHelpers) # Build options option(BUILD_SHARED_LIBS "Build shared library" OFF) + +# Optional Clang -fbounds-safety. Default OFF: OGG_*COUNTED_BY* macros in +# ogg_bounds_safety.h are inert and the ABI/build is unchanged. When ON, +# requires a Clang that provides -fbounds-safety / . +option(OGG_ENABLE_FBOUNDS_SAFETY + "Enable Clang -fbounds-safety annotations (experimental toolchain)" OFF) +if(OGG_ENABLE_FBOUNDS_SAFETY) + add_definitions(-DOGG_SUPPORT_FBOUNDS_SAFETY) + add_compile_options(-fbounds-safety) +endif() if(APPLE) option(BUILD_FRAMEWORK "Build Framework bundle for OSX" OFF) endif() @@ -69,6 +79,7 @@ configure_file(include/ogg/config_types.h.in include/ogg/config_types.h @ONLY) set(OGG_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/include/ogg/config_types.h include/ogg/ogg.h + include/ogg/ogg_bounds_safety.h include/ogg/os_types.h ) diff --git a/include/ogg/Makefile.am b/include/ogg/Makefile.am index 142699d3..da8faf48 100644 --- a/include/ogg/Makefile.am +++ b/include/ogg/Makefile.am @@ -2,5 +2,5 @@ oggincludedir = $(includedir)/ogg -ogginclude_HEADERS = ogg.h os_types.h +ogginclude_HEADERS = ogg.h ogg_bounds_safety.h os_types.h nodist_ogginclude_HEADERS = config_types.h diff --git a/include/ogg/ogg.h b/include/ogg/ogg.h index c4325aa7..136a51ad 100644 --- a/include/ogg/ogg.h +++ b/include/ogg/ogg.h @@ -22,6 +22,7 @@ extern "C" { #include #include +#include typedef struct { void *iov_base; @@ -50,7 +51,12 @@ typedef struct { Ogg bitstream **********************************************************/ typedef struct { - unsigned char *body_data; /* bytes from packet bodies */ + /* body_storage is the capacity companion for body_data. + * Field order is preserved (pointer before size) for public ABI; + * update sites assign capacity before the pointer so counted-by + * invariants hold under optional -fbounds-safety builds. + */ + unsigned char * OGG_COUNTED_BY_OR_NULL(body_storage) body_data; /* bytes from packet bodies */ long body_storage; /* storage elements allocated */ long body_fill; /* elements stored; fill mark */ long body_returned; /* elements of fill returned */ diff --git a/include/ogg/ogg_bounds_safety.h b/include/ogg/ogg_bounds_safety.h new file mode 100644 index 00000000..75d1b2c1 --- /dev/null +++ b/include/ogg/ogg_bounds_safety.h @@ -0,0 +1,39 @@ +/* ogg_bounds_safety.h - portability macros for optional -fbounds-safety + * + * Copyright (c) 2026 Jeff Bindel + * + * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS + * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE + * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. + * + * When OGG_SUPPORT_FBOUNDS_SAFETY is defined (typically via + * -DOGG_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 inert-macro + * -fbounds-safety adoption: annotations are inert unless explicitly enabled. + */ + +#ifndef OGG_BOUNDS_SAFETY_H +#define OGG_BOUNDS_SAFETY_H + +#ifdef OGG_SUPPORT_FBOUNDS_SAFETY + +# include +/* Non-ABI-breaking counted-by annotations for struct pointer members. + * Prefer OGG_COUNTED_BY_OR_NULL for pointers that may be NULL while the + * companion size field is zero (ogg_stream_state.body_data pattern). + */ +# define OGG_COUNTED_BY(n) __counted_by(n) +# define OGG_COUNTED_BY_OR_NULL(n) __counted_by_or_null(n) + +#else /* !OGG_SUPPORT_FBOUNDS_SAFETY */ + +# define OGG_COUNTED_BY(n) +# define OGG_COUNTED_BY_OR_NULL(n) + +#endif /* OGG_SUPPORT_FBOUNDS_SAFETY */ + +#endif /* OGG_BOUNDS_SAFETY_H */ diff --git a/src/framing.c b/src/framing.c index 4034eb53..f36abe93 100644 --- a/src/framing.c +++ b/src/framing.c @@ -133,6 +133,7 @@ static void _ogg_crc_init(){ int ogg_stream_init(ogg_stream_state *os,int serialno){ if(os){ memset(os,0,sizeof(*os)); + /* counted_by: set capacity before pointer */ os->body_storage=16*1024; os->lacing_storage=1024; @@ -162,6 +163,9 @@ int ogg_stream_check(ogg_stream_state *os){ int ogg_stream_clear(ogg_stream_state *os){ if(os){ if(os->body_data)_ogg_free(os->body_data); + /* counted_by clear order: drop pointer then capacity */ + os->body_data=NULL; + os->body_storage=0; if(os->lacing_vals)_ogg_free(os->lacing_vals); if(os->granule_vals)_ogg_free(os->granule_vals); @@ -196,6 +200,7 @@ static int _os_body_expand(ogg_stream_state *os,long needed){ ogg_stream_clear(os); return -1; } + /* counted_by: set capacity before pointer */ os->body_storage=body_storage; os->body_data=ret; }