This is an automated email from the ASF dual-hosted git repository.
swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git
The following commit(s) were added to refs/heads/master by this push:
new b3d9d651 Adopt Safe Buffers Model in hexdump() using std::span and add
unsafe-buffer regression checks (#716)
b3d9d651 is described below
commit b3d9d651c4f078df87be4fdd0ee36af063000d1c
Author: sadd amr3e <[email protected]>
AuthorDate: Wed Jun 10 09:14:54 2026 +0530
Adopt Safe Buffers Model in hexdump() using std::span and add unsafe-buffer
regression checks (#716)
Co-authored-by: metsw24-max <[email protected]>
---
.../compiler-features/check-compiler-support.cmake | 7 +++
src/main/cpp/CMakeLists.txt | 31 +++++++++++++
src/main/cpp/hexdump.cpp | 53 ++++++++++++++++++----
src/test/cpp/hexdumptestcase.cpp | 10 ++++
4 files changed, 92 insertions(+), 9 deletions(-)
diff --git a/src/cmake/compiler-features/check-compiler-support.cmake
b/src/cmake/compiler-features/check-compiler-support.cmake
index efa29f81..c2e86f73 100644
--- a/src/cmake/compiler-features/check-compiler-support.cmake
+++ b/src/cmake/compiler-features/check-compiler-support.cmake
@@ -1,5 +1,12 @@
# This module checks compiler and standard library support
#
+include(CheckCXXCompilerFlag)
+
+# Does the compiler support the Safe Buffers diagnostic -Wunsafe-buffer-usage?
+# (Clang >= 16.) Used to enforce, on a per-file basis, that translation units
+# migrated to the Safe Buffers Programming Model stay free of unchecked raw
+# pointer/array arithmetic. See LOG4CXX_ENABLE_SAFE_BUFFERS.
+check_cxx_compiler_flag("-Wunsafe-buffer-usage"
LOG4CXX_HAS_WUNSAFE_BUFFER_USAGE)
# Does the compiler support thread_local?
if(MINGW)
diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt
index 5a2d3d22..44e2b4ba 100644
--- a/src/main/cpp/CMakeLists.txt
+++ b/src/main/cpp/CMakeLists.txt
@@ -264,3 +264,34 @@ endif()
if(${ENABLE_FMT_LAYOUT})
target_link_libraries(log4cxx PUBLIC fmt::fmt)
endif()
+
+# --- Safe Buffers Programming Model enforcement ------------------------------
+# Translation units below have been migrated to the Safe Buffers Programming
+# Model: raw C buffers and pointer arithmetic over untrusted input are replaced
+# by bounds-aware views/containers (e.g. std::span). Compiling them with
+# Clang's -Wunsafe-buffer-usage prevents the unsafe patterns from being
+# reintroduced. Append a file to this list once it has been migrated.
+set(LOG4CXX_SAFE_BUFFERS_SOURCES
+ hexdump.cpp
+)
+
+option(LOG4CXX_ENABLE_SAFE_BUFFERS
+ "Compile Safe-Buffers-migrated sources with -Wunsafe-buffer-usage (Clang)"
OFF)
+option(LOG4CXX_SAFE_BUFFERS_AS_ERROR
+ "Treat -Wunsafe-buffer-usage as an error in Safe-Buffers-migrated sources"
OFF)
+
+if(LOG4CXX_ENABLE_SAFE_BUFFERS)
+ if(LOG4CXX_HAS_WUNSAFE_BUFFER_USAGE)
+ set(_safe_buffers_flags -Wunsafe-buffer-usage)
+ if(LOG4CXX_SAFE_BUFFERS_AS_ERROR)
+ list(APPEND _safe_buffers_flags -Werror=unsafe-buffer-usage)
+ endif()
+ set_source_files_properties(${LOG4CXX_SAFE_BUFFERS_SOURCES}
+ PROPERTIES COMPILE_OPTIONS "${_safe_buffers_flags}")
+ message(STATUS "Safe Buffers enforcement enabled for:
${LOG4CXX_SAFE_BUFFERS_SOURCES}")
+ else()
+ message(WARNING
+ "LOG4CXX_ENABLE_SAFE_BUFFERS is ON but the compiler does not support "
+ "-Wunsafe-buffer-usage; Safe Buffers enforcement is disabled.")
+ endif()
+endif()
diff --git a/src/main/cpp/hexdump.cpp b/src/main/cpp/hexdump.cpp
index f535d184..69f89bfb 100644
--- a/src/main/cpp/hexdump.cpp
+++ b/src/main/cpp/hexdump.cpp
@@ -26,14 +26,49 @@
#include <ios>
#include <iomanip>
#include <cctype>
+#include <cstddef>
+#include <cstdint>
+#if defined(__cpp_lib_span) && __cpp_lib_span >= 202002L
+ #include <span>
+#endif
using namespace LOG4CXX_NS;
typedef std::basic_stringstream<logchar> LogStream;
+namespace {
+
+// Safe Buffers Programming Model: hexdump() receives a raw (pointer, length)
+// pair describing an untrusted byte buffer (typically bytes just read from a
+// socket or file). Indexing that pointer directly with computed offsets is
+// exactly the unsafe pattern that Clang's -Wunsafe-buffer-usage flags, because
+// a single off-by-one in the bookkeeping below would read out of bounds.
+//
+// To remove that hazard the formatting code is written once against a
+// bounds-aware contiguous view. When std::span is available (C++20) it is
+// used directly so -Wunsafe-buffer-usage accepts the body; on older toolchains
+// a minimal drop-in view provides the same subset of the API.
+#if defined(__cpp_lib_span) && __cpp_lib_span >= 202002L
+using ByteView = std::span<const uint8_t>;
+#else
+class ByteView
+{
+ public:
+ ByteView(const uint8_t* data, std::size_t size) noexcept
+ : m_data(data), m_size(size) {}
+ std::size_t size() const noexcept { return m_size; }
+ uint8_t operator[](std::size_t index) const noexcept { return
m_data[index]; }
+ private:
+ const uint8_t* m_data;
+ std::size_t m_size;
+};
+#endif
+
+} // namespace
+
LogString LOG4CXX_NS::hexdump(const void* bytes, uint32_t len, HexdumpFlags
flags){
LogString ret;
- const uint8_t* bytes_u8 = static_cast<const uint8_t*>(bytes);
+ const ByteView data{ static_cast<const uint8_t*>(bytes), len };
LogStream sstream;
#if LOG4CXX_LOGCHAR_IS_WCHAR
const wchar_t fill_char = L'0';
@@ -47,7 +82,7 @@ LogString LOG4CXX_NS::hexdump(const void* bytes, uint32_t
len, HexdumpFlags flag
sstream << LOG4CXX_EOL;
}
- for(uint32_t offset = 0; offset < len; offset += 16){
+ for(uint32_t offset = 0; offset < data.size(); offset += 16){
if(offset != 0){
sstream << LOG4CXX_EOL;
}
@@ -59,7 +94,7 @@ LogString LOG4CXX_NS::hexdump(const void* bytes, uint32_t
len, HexdumpFlags flag
// Print out the first 8 bytes
for(int byte = 0; byte < 8; byte++){
- if(offset + byte >= len){
+ if(offset + byte >= data.size()){
sstream << LOG4CXX_STR(" ");
if(byte != 8){
sstream << LOG4CXX_STR(" ");
@@ -67,7 +102,7 @@ LogString LOG4CXX_NS::hexdump(const void* bytes, uint32_t
len, HexdumpFlags flag
continue;
}
- sstream << std::hex << std::setw(2) <<
std::setfill(fill_char) << static_cast<int>(bytes_u8[offset + byte]) <<
std::resetiosflags(std::ios_base::fmtflags(0));
+ sstream << std::hex << std::setw(2) <<
std::setfill(fill_char) << static_cast<int>(data[offset + byte]) <<
std::resetiosflags(std::ios_base::fmtflags(0));
sstream << std::setfill(space_fill_char);
if(byte != 8){
sstream << LOG4CXX_STR(" ");
@@ -78,7 +113,7 @@ LogString LOG4CXX_NS::hexdump(const void* bytes, uint32_t
len, HexdumpFlags flag
// Print out the last 8 bytes
for(int byte = 8; byte < 16; byte++){
- if(offset + byte >= len){
+ if(offset + byte >= data.size()){
sstream << LOG4CXX_STR(" ");
if(byte != 15){
sstream << LOG4CXX_STR(" ");
@@ -86,7 +121,7 @@ LogString LOG4CXX_NS::hexdump(const void* bytes, uint32_t
len, HexdumpFlags flag
continue;
}
- sstream << std::hex << std::setw(2) <<
std::setfill(fill_char) << static_cast<int>(bytes_u8[offset + byte]) <<
std::resetiosflags(std::ios_base::fmtflags(0));
+ sstream << std::hex << std::setw(2) <<
std::setfill(fill_char) << static_cast<int>(data[offset + byte]) <<
std::resetiosflags(std::ios_base::fmtflags(0));
if(byte != 15){
sstream << LOG4CXX_STR(" ");
}
@@ -95,11 +130,11 @@ LogString LOG4CXX_NS::hexdump(const void* bytes, uint32_t
len, HexdumpFlags flag
// Print out the ASCII text
sstream << LOG4CXX_STR(" |");
for(int byte = 0; byte < 16; byte++){
- if(offset + byte >= len){
+ if(offset + byte >= data.size()){
break;
}
- if(std::isprint(bytes_u8[offset + byte])){
- logchar to_append = bytes_u8[offset + byte];
+ if(std::isprint(static_cast<unsigned char>(data[offset
+ byte]))){
+ logchar to_append = data[offset + byte];
sstream << to_append;
}else{
sstream << LOG4CXX_STR(".");
diff --git a/src/test/cpp/hexdumptestcase.cpp b/src/test/cpp/hexdumptestcase.cpp
index 66624c23..2f7b806b 100644
--- a/src/test/cpp/hexdumptestcase.cpp
+++ b/src/test/cpp/hexdumptestcase.cpp
@@ -28,6 +28,7 @@ LOGUNIT_CLASS(HexdumpTestCase)
LOGUNIT_TEST(test2);
LOGUNIT_TEST(test_newline);
LOGUNIT_TEST(test_newline2);
+ LOGUNIT_TEST(test_empty);
LOGUNIT_TEST_SUITE_END();
public:
@@ -100,6 +101,15 @@ public:
LOGUNIT_ASSERT_EQUAL(expectedOutput, dumped);
}
+ // A zero-length buffer must not read any bytes and produces no rows.
+ // This exercises the lower bound of the Safe Buffers view used
internally.
+ void test_empty()
+ {
+ LogString expectedOutput;
+ LogString dumped = log4cxx::hexdump(nullptr, 0);
+ LOGUNIT_ASSERT_EQUAL(expectedOutput, dumped);
+ }
+
};