Skip to content

Fix clang UBSan trigger in sbo_resource.hpp#275

Open
lzulberti wants to merge 1 commit into
boostorg:developfrom
lzulberti:fix-ubsan
Open

Fix clang UBSan trigger in sbo_resource.hpp#275
lzulberti wants to merge 1 commit into
boostorg:developfrom
lzulberti:fix-ubsan

Conversation

@lzulberti

Copy link
Copy Markdown

Running my application with UB sanitizers enabled, catched this line computing the offset from nullptr and then adding buffer + padding to nullptr again:

buffer_.p = static_cast<void*>(static_cast<char*>(nullptr) + buffer + padding);

The UBSan error:

/home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/cobalt/detail/sbo_resource.hpp:49:66: runtime error: applying non-zero offset 124542660792792 to null pointer
    #0 0x5e31f7feaccb in boost::cobalt::detail::sbo_resource::align_as_max_() /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/cobalt/detail/sbo_resource.hpp:49:66
    #1 0x5e31f7feaccb in boost::cobalt::detail::sbo_resource::sbo_resource(void*, unsigned long, std::pmr::memory_resource*) /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/cobalt/detail/sbo_resource.hpp:74:5
    #2 0x5e31f7feaccb in boost::cobalt::op<boost::system::error_code>::awaitable::awaitable(boost::cobalt::op<boost::system::error_code>*) /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/cobalt/op.hpp:112:34
    #3 0x5e31f7feaccb in boost::cobalt::op<boost::system::error_code>::operator co_await() /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/cobalt/op.hpp:130:12
    #4 0x5e31f7feaccb in boost::cobalt::promise<void> (anonymous namespace)::push_worker_lock<std::shared_ptr<ingeniars::cobalt::Mutex>>(std::shared_ptr<ingeniars::cobalt::Mutex>, std::shared_ptr<std::vector<int, std::allocator<int>>>, std::chrono::duration<long, std::ratio<1l, 1000l>>)  <OMISSIS>
    #5 0x5e31f7ff16c3 in MutexTest_LockSerializesStress_Test::TestBody()::$_0::operator()() const (.resume) <OMISSIS>
    #6 0x5e31f804f1ef in std::__n4861::coroutine_handle<boost::cobalt::detail::task_promise<void>>::resume() const /usr/lib/gcc/x86_64-linux-gnu/15/../../../../include/c++/15/coroutine:247:29
    #7 0x5e31f804f1ef in boost::cobalt::unique_handle<boost::cobalt::detail::task_promise<void>>::operator()() && /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/cobalt/unique_handle.hpp:76:15
    #8 0x5e31f804f1ef in boost::asio::detail::binder0<boost::cobalt::unique_handle<boost::cobalt::detail::task_promise<void>>>::operator()() /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/bind_handler.hpp:57:5
    #9 0x5e31f804f1ef in void boost::asio::detail::executor_function::complete<boost::asio::detail::binder0<boost::cobalt::unique_handle<boost::cobalt::detail::task_promise<void>>>, std::allocator<void>>(boost::asio::detail::executor_function::impl_base*, bool) /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/executor_function.hpp:114:7
    #10 0x5e31f8040516 in boost::asio::detail::executor_function::operator()() /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/executor_function.hpp:62:7
    #11 0x5e31f8040516 in void boost::asio::io_context::basic_executor_type<std::allocator<void>, 4ul>::execute<boost::asio::detail::executor_function>(boost::asio::detail::executor_function&&) const /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/impl/io_context.hpp:218:7
    #12 0x5e31f804e99f in void boost::asio::execution::detail::any_executor_base::execute<boost::asio::detail::binder0<boost::cobalt::unique_handle<boost::cobalt::detail::task_promise<void>>>>(boost::asio::detail::binder0<boost::cobalt::unique_handle<boost::cobalt::detail::task_promise<void>>>&&) const /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/execution/any_executor.hpp:682:9
    #13 0x5e31f804e5bb in boost::asio::detail::work_dispatcher<boost::asio::detail::empty_work_function, boost::cobalt::unique_handle<boost::cobalt::detail::task_promise<void>>, boost::asio::any_io_executor, void>::operator()() /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/work_dispatcher.hpp:238:65
    #14 0x5e31f804f7fc in void boost::asio::detail::executor_function::complete<boost::asio::detail::work_dispatcher<boost::asio::detail::empty_work_function, boost::cobalt::unique_handle<boost::cobalt::detail::task_promise<void>>, boost::asio::any_io_executor, void>, std::allocator<void>>(boost::asio::detail::executor_function::impl_base*, bool) /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/executor_function.hpp:114:7
    #15 0x5e31f803e39f in boost::asio::detail::executor_function::operator()() /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/executor_function.hpp:62:7
    #16 0x5e31f803e39f in boost::asio::detail::executor_op<boost::asio::detail::executor_function, std::allocator<void>, boost::asio::detail::scheduler_operation>::do_complete(void*, boost::asio::detail::scheduler_operation*, boost::system::error_code const&, unsigned long) /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/executor_op.hpp:71:7
    #17 0x5e31f8039f72 in boost::asio::detail::scheduler_operation::complete(void*, boost::system::error_code const&, unsigned long) /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/scheduler_operation.hpp:41:5
    #18 0x5e31f8039f72 in boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex<boost::asio::detail::posix_mutex>::scoped_lock&, boost::asio::detail::scheduler_thread_info&, boost::system::error_code const&) /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/impl/scheduler.ipp:502:12
    #19 0x5e31f80393e2 in boost::asio::detail::scheduler::run(boost::system::error_code&) /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/detail/impl/scheduler.ipp:218:10
    #20 0x5e31f7ffafe2 in boost::asio::io_context::run() /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/asio/impl/io_context.ipp:64:24
    #21 0x5e31f7ffafe2 in void boost::cobalt::run<void>(boost::cobalt::task<void>) /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/cobalt/run.hpp:54:11
    #22 0x5e31f7fe7af3 in MutexTest_LockSerializesStress_Test::TestBody() <OMISSIS>
    #23 0x5e31f8223023 in void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/b/src/googletest/src/gtest.cc:2664:10
    #24 0x5e31f8202325 in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/b/src/googletest/src/gtest.cc:2700:14
    #25 0x5e31f81dfbe6 in testing::Test::Run() /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/b/src/googletest/src/gtest.cc:2739:5
    #26 0x5e31f81e063e in testing::TestInfo::Run() /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/b/src/googletest/src/gtest.cc:2885:11
    #27 0x5e31f81e0e59 in testing::TestSuite::Run() /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/b/src/googletest/src/gtest.cc:3063:30
    #28 0x5e31f81f21d1 in testing::internal::UnitTestImpl::RunAllTests() /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/b/src/googletest/src/gtest.cc:6054:44
    #29 0x5e31f82253d3 in bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/b/src/googletest/src/gtest.cc:2664:10
    #30 0x5e31f8204805 in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/b/src/googletest/src/gtest.cc:2700:14
    #31 0x5e31f81f1aa5 in testing::UnitTest::Run() /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/b/src/googletest/src/gtest.cc:5594:10
    #32 0x5e31f7fe43b0 in RUN_ALL_TESTS() /home/zulberti/.conan2/p/b/gtest579ad47de4fe4/p/include/gtest/gtest.h:2334:73
    
...

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /home/zulberti/.conan2/p/b/boostbc3d9ea54563f/p/include/boost/cobalt/detail/sbo_resource.hpp:49:6

@klemens-morgenstern

Copy link
Copy Markdown
Collaborator

Thanks. Can you change the PR so it only changes the affected lines?

@lzulberti lzulberti force-pushed the fix-ubsan branch 2 times, most recently from c9e28ff to 0e9f7fe Compare May 21, 2026 06:32
@lzulberti

lzulberti commented May 21, 2026

Copy link
Copy Markdown
Author

Sure, done ;-)

Is the offset to nullptr needed?

@klemens-morgenstern

Copy link
Copy Markdown
Collaborator

You can't use a reinterpret_cast in a constexpr function :)

@lzulberti

Copy link
Copy Markdown
Author

I see, if contstexpr is needed and the function needs to perform that UB operation, I do not see solutions. Do you?

Should the alignment check be done in the constexpr function? Can it be enforced to programmer doing something like

alignas(std::max_align_t) unsigned char storage[1024];

constexpr auto make_resource()
{
  return boost::cobalt::detail::sbo_resource(
      storage,
      sizeof(storage));
}

Constexpr buffer should be already aligned, while runtime behavior can be preserved.

reinterpret_cast cannot be evaluated during constant evaluation, so the
plain cast made align_as_max_ ill-formed as a constexpr function. Guard
it with std::is_constant_evaluated() and return early at compile time;
no real buffer is ever aligned during constant evaluation. Runtime
behaviour is unchanged and UBSan-clean under gcc and clang.
@lzulberti

Copy link
Copy Markdown
Author

Hi @klemens-morgenstern, you're right that reinterpret_cast can't be evaluated during constant evaluation, that's exactly why the bare-cast version is rejected. With -Winvalid-constexpr (an error by default on recent clang) it fails to compile even when the function is never called at compile time:

error: constexpr function never produces a constant expression [-Winvalid-constexpr]
note: reinterpret_cast is not allowed in a constant expression

But a constexpr function is only ill-formed when no invocation can be a constant expression. Guarding the cast with std::is_constant_evaluated() and returning early restores that. During constant evaluation the function just returns, and at runtime it does the normal integer-arithmetic alignment:

constexpr void align_as_max_()
{
  if (std::is_constant_evaluated())
    return;

  const auto buffer = reinterpret_cast<std::uintptr_t>(buffer_.p);
  ...
}

Skipping alignment at compile time is fine here: no real buffer is ever aligned during constant evaluation. The only ctor that calls align_as_max_() takes a (void*, size) and its sole users construct it at runtime (op::awaitable lives in a coroutine frame, which can't be constexpr; composition gets its resource from handler.get_allocator()); the default ctor used by get_null_sbo_resource() doesn't touch it.

Verified compile + run clean, and UBSan + ASan clean, under both gcc 15 and clang 21 in C++20 — covering the misaligned-start (the originally-flagged branch), too-small, and empty-buffer paths. Also confirmed valid constexpr under clang -Werror=invalid-constexpr and g++.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants