Required information
Operating system:
Ubuntu 22.04 (also reproducible on any Linux with Clang/GCC + ASan)
Compiler version:
Clang 14+ / GCC 11+ with -fsanitize=address
Eclipse iceoryx version:
Reproduced on v2.0.6. The bug is present on main (commit 8c614d1) — the offending line in iceoryx_posh/source/version/version_info.cpp:111 is unchanged.
Observed result or behaviour:
AddressSanitizer reports a global-buffer-overflow inside iox::version::VersionInfo::getCurrentVersion() whenever iceoryx is built from a release tarball (i.e. without a .git directory) with -fsanitize=address.
iceoryx_posh/cmake/iceoryxversions.cmake runs git describe to populate ICEORYX_SHA1. When the source tree has no .git (release tarball, vendored copy, distro package, Bazel http_archive, etc.), the command fails silently and ICEORYX_SHA1 is defined as the empty string literal "" — that's 1 byte in the binary, including the trailing NUL.
getCurrentVersion() then constructs a CommitIdString_t like this:
CommitIdString_t shortCommitIdString(TruncateToCapacity,
ICEORYX_SHA1,
COMMIT_ID_STRING_SIZE /*=12*/);
The matching cxx::string TruncateToCapacity ctor in iceoryx_hoofs/include/iceoryx_hoofs/internal/cxx/string.inl (~line 90) does not check the actual length of the source pointer when an explicit count argument is supplied — it memcpy()s count bytes unconditionally. So the ctor reads 12 bytes from a 1-byte literal.
ASan output:
==X==ERROR: AddressSanitizer: global-buffer-overflow on address ...
READ of size 12 at 0x... thread T0
#0 __asan_memcpy
#1 iox::cxx::string<12u>::string<...>(... ICEORYX_SHA1, 12)
iceoryx_hoofs/include/iceoryx_hoofs/internal/cxx/string.inl:90
#2 iox::version::VersionInfo::getCurrentVersion()
iceoryx_posh/source/version/version_info.cpp:111
#3 iox::roudi::RouDi setup / iox::PoshRuntime::PoshRuntime
0x... is located 0 bytes to the left of global variable ""...
ICEORYX_SHA1 ... of size 1
Expected result or behaviour:
VersionInfo::getCurrentVersion() should not read past the end of ICEORYX_SHA1, regardless of how the build environment defined that literal (full 40-char SHA, short SHA, or empty string).
Conditions where it occurred / Performed steps:
- Build iceoryx from a release tarball (no
.git/) — for example via Bazel http_archive of v2.0.6, distro package, or tar xf v2.0.6.tar.gz && cmake ....
- Compile the consuming binary with
-fsanitize=address.
- Initialize
iox::PoshRuntime (or call VersionInfo::getCurrentVersion() directly).
In our case the consumer is a ROS 2 workspace using rmw_cyclonedds_cpp + iceoryx. The first thing iox::PoshRuntime does at startup is call getCurrentVersion() to compare its version info against RouDi's. With ASan enabled, every node aborts inside rmw_create_node() before user code ever runs. Without a fix, an entire AV / robotics stack built with --config=asan cannot bring up a single node.
Additional helpful information
This is a real out-of-bounds read (not just an ASan annotation issue): memcpy is reading bytes from the rodata section past the end of the "" literal. It happens to be benign on non-instrumented builds because the read lands in adjacent rodata padding/symbols and the resulting iox::string is then truncated at the first NUL — but it is still UB, and ASan correctly reports it.
A fix is straightforward: cap the byte count to the actual length of the source via strnlen(ICEORYX_SHA1, COMMIT_ID_STRING_SIZE). This is a no-op on real git builds (where ICEORYX_SHA1 is the full 40-char SHA) since strnlen is bounded by COMMIT_ID_STRING_SIZE, so the truncation behavior is identical for any source string of length ≥ 12.
I have a PR ready with the fix and two regression tests. Will link once I have the issue number.
Required information
Operating system:
Ubuntu 22.04 (also reproducible on any Linux with Clang/GCC + ASan)
Compiler version:
Clang 14+ / GCC 11+ with
-fsanitize=addressEclipse iceoryx version:
Reproduced on
v2.0.6. The bug is present onmain(commit 8c614d1) — the offending line iniceoryx_posh/source/version/version_info.cpp:111is unchanged.Observed result or behaviour:
AddressSanitizer reports a
global-buffer-overflowinsideiox::version::VersionInfo::getCurrentVersion()whenever iceoryx is built from a release tarball (i.e. without a.gitdirectory) with-fsanitize=address.iceoryx_posh/cmake/iceoryxversions.cmakerunsgit describeto populateICEORYX_SHA1. When the source tree has no.git(release tarball, vendored copy, distro package, Bazelhttp_archive, etc.), the command fails silently andICEORYX_SHA1is defined as the empty string literal""— that's 1 byte in the binary, including the trailing NUL.getCurrentVersion()then constructs aCommitIdString_tlike this:The matching
cxx::stringTruncateToCapacityctor iniceoryx_hoofs/include/iceoryx_hoofs/internal/cxx/string.inl(~line 90) does not check the actual length of the source pointer when an explicitcountargument is supplied — itmemcpy()scountbytes unconditionally. So the ctor reads 12 bytes from a 1-byte literal.ASan output:
Expected result or behaviour:
VersionInfo::getCurrentVersion()should not read past the end ofICEORYX_SHA1, regardless of how the build environment defined that literal (full 40-char SHA, short SHA, or empty string).Conditions where it occurred / Performed steps:
.git/) — for example via Bazelhttp_archiveofv2.0.6, distro package, ortar xf v2.0.6.tar.gz && cmake ....-fsanitize=address.iox::PoshRuntime(or callVersionInfo::getCurrentVersion()directly).In our case the consumer is a ROS 2 workspace using
rmw_cyclonedds_cpp+ iceoryx. The first thingiox::PoshRuntimedoes at startup is callgetCurrentVersion()to compare its version info against RouDi's. With ASan enabled, every node aborts insidermw_create_node()before user code ever runs. Without a fix, an entire AV / robotics stack built with--config=asancannot bring up a single node.Additional helpful information
This is a real out-of-bounds read (not just an ASan annotation issue):
memcpyis reading bytes from the rodata section past the end of the""literal. It happens to be benign on non-instrumented builds because the read lands in adjacent rodata padding/symbols and the resultingiox::stringis then truncated at the first NUL — but it is still UB, and ASan correctly reports it.A fix is straightforward: cap the byte count to the actual length of the source via
strnlen(ICEORYX_SHA1, COMMIT_ID_STRING_SIZE). This is a no-op on real git builds (whereICEORYX_SHA1is the full 40-char SHA) sincestrnlenis bounded byCOMMIT_ID_STRING_SIZE, so the truncation behavior is identical for any source string of length ≥ 12.I have a PR ready with the fix and two regression tests. Will link once I have the issue number.