From bf74686c290e5fc328444501e6393d9da260aab0 Mon Sep 17 00:00:00 2001 From: trim21 Date: Thu, 18 Jun 2026 15:56:41 +0800 Subject: [PATCH] fix: replace reinterpret_cast UB in command_base with aligned placement new Replace the union-based reinterpret_cast type erasure in command_base with an alignas char buffer + typed copy/destroy helper pointers. set_function() placement-news the correct std::function type at the buffer address, and stores per-type copy/destroy helpers so that the copy ctor, assignment, and destructor always operate on the actual type rather than assuming base_function. _reinterpret_cast access of t_pod remains zero-overhead and is now well-defined because the object was constructed at that address as T via placement new. Fixes #1818 --- src/rpc/command.h | 104 +++++++++++++++++++++++++++++----------------- 1 file changed, 66 insertions(+), 38 deletions(-) diff --git a/src/rpc/command.h b/src/rpc/command.h index ba90bcea..d177da9c 100644 --- a/src/rpc/command.h +++ b/src/rpc/command.h @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -28,7 +29,6 @@ struct target_wrapper { typedef no_type* cleaned_type; }; -// Since c++0x isn't out yet... template struct rt_triple : private std::pair { typedef std::pair base_type; @@ -55,9 +55,6 @@ struct rt_triple : private std::pair { base_type(src.first, src.second), third(src.third) {} }; -// Since it gets used so many places we might as well put it in the -// rpc namespace. -//typedef std::pair target_type; typedef rt_triple target_type; class command_base; @@ -114,10 +111,48 @@ public: char buffer[sizeof(torrent::Object) * max_arguments]; }; - command_base() { new (&_pod()) base_function(); } - command_base(const command_base& src) { new (&_pod()) base_function(src._pod()); } + command_base() : m_copy_helper(nullptr), m_dest_helper(nullptr) {} - ~command_base() { _pod().~base_function(); } + command_base(const command_base& src) { + m_copy_helper = src.m_copy_helper; + m_dest_helper = src.m_dest_helper; + if (src.m_copy_helper) + src.m_copy_helper(t_pod, src.t_pod); + } + + command_base& operator=(const command_base& src) { + if (this != &src) { + if (m_dest_helper) m_dest_helper(t_pod); + m_copy_helper = src.m_copy_helper; + m_dest_helper = src.m_dest_helper; + if (src.m_copy_helper) + src.m_copy_helper(t_pod, src.t_pod); + } + return *this; + } + + command_base(command_base&& src) noexcept { + m_copy_helper = src.m_copy_helper; + m_dest_helper = src.m_dest_helper; + if (src.m_copy_helper) + src.m_copy_helper(t_pod, src.t_pod); + } + + command_base& operator=(command_base&& src) noexcept { + if (this != &src) { + if (m_dest_helper) m_dest_helper(t_pod); + m_copy_helper = src.m_copy_helper; + m_dest_helper = src.m_dest_helper; + if (src.m_copy_helper) + src.m_copy_helper(t_pod, src.t_pod); + } + return *this; + } + + ~command_base() { + if (m_dest_helper) + m_dest_helper(t_pod); + } static torrent::Object* argument(unsigned int index) { return current_stack.begin() + index; } static torrent::Object& argument_ref(unsigned int index) { return *(current_stack.begin() + index); } @@ -132,55 +167,50 @@ public: static void pop_stack(stack_type* stack, torrent::Object* last_stack); template - void set_function(T s, [[maybe_unused]] int value = command_base_is_valid::value) { _pod() = s; } + void set_function(T s, [[maybe_unused]] int value = command_base_is_valid::value) { + static_assert(sizeof(T) <= sizeof(t_pod), "t_pod storage overflow"); + static_assert(alignof(t_pod) % alignof(T) == 0, "t_pod alignment violation"); + + if (m_dest_helper) m_dest_helper(t_pod); + + ::new (t_pod) T(std::move(s)); + + m_copy_helper = [](void* dest, const void* src) { + ::new (dest) T(*static_cast(src)); + }; + m_dest_helper = [](void* ptr) { + static_cast(ptr)->~T(); + }; + } template void set_function_2(typename command_base_is_type::type s, [[maybe_unused]] int value = command_base_is_valid::type>::value) { - _pod::type>() = s; + set_function::type>(std::move(s)); } - // The std::function object in GCC is castable between types with a - // pointer to a struct of ctor/dtor/calls for non-POD slots. As such - // it should be safe to cast between different std::function - // template types, yet what the C++0x standard will say about this I - // have no idea atm. template tmpl& _pod() { return reinterpret_cast(t_pod); } template const tmpl& _pod() const { return reinterpret_cast(t_pod); } template static const torrent::Object _call(command_base* cmd, target_type target, Args args); - command_base& operator = (const command_base& src) { - _pod() = src._pod(); - return *this; - } - protected: - // For use by functions that need to use placeholders to arguments - // within commands. E.d. callable command strings where one of the - // arguments within the command needs to be supplied by the caller. + using copy_fn_t = void (*)(void* dest, const void* src); + using dest_fn_t = void (*)(void* ptr); -#ifdef HAVE_CXX11 - union { - base_function t_pod; - // char t_pod[sizeof(base_function)]; - }; -#else - union { - char t_pod[sizeof(base_function)]; - }; -#endif + alignas(std::max_align_t) char t_pod[sizeof(base_function)]; + + copy_fn_t m_copy_helper; + dest_fn_t m_dest_helper; }; template struct target_type_id { - // Nothing here, so we cause an error. }; template inline bool is_target_compatible(const target_type& target) { return target.first == target_type_id::value; } -// Splitting pairs into separate targets. inline bool is_target_pair(const target_type& target) { return target.first >= command_base::target_download_pair; } template inline T @@ -203,7 +233,7 @@ command_base::_call(command_base* cmd, target_type target, Args args) { #define COMMAND_BASE_TEMPLATE_TYPE(func_type, func_parm) \ template ::proper_type> struct func_type { typedef std::function type; }; \ - \ + \ template <> struct command_base_is_valid::type> { static const int value = 1; }; \ template <> struct command_base_is_valid::type> { static const int value = 1; }; \ template <> struct command_base_is_valid::type> { static const int value = 1; }; \ @@ -211,8 +241,6 @@ command_base::_call(command_base* cmd, target_type target, Args args) { template <> struct command_base_is_valid::type> { static const int value = 1; }; \ template <> struct command_base_is_valid::type> { static const int value = 1; }; -// template struct command_base_is_valid::type > { static const int value = 1; }; - COMMAND_BASE_TEMPLATE_TYPE(command_function, torrent::Object (T, const torrent::Object&)); COMMAND_BASE_TEMPLATE_TYPE(command_value_function, torrent::Object (T, const torrent::Object::value_type&)); COMMAND_BASE_TEMPLATE_TYPE(command_string_function, torrent::Object (T, const std::string&)); @@ -220,7 +248,7 @@ COMMAND_BASE_TEMPLATE_TYPE(command_list_function, torrent::Object (T, const to #define COMMAND_BASE_TEMPLATE_CALL(func_name, func_type) \ template const torrent::Object func_name(command_base* rawCommand, target_type target, const torrent::Object& args); \ - \ + \ template <> struct command_base_is_type > { static const int value = 1; typedef func_type::type type; }; \ template <> struct command_base_is_type > { static const int value = 1; typedef func_type::type type; }; \ template <> struct command_base_is_type > { static const int value = 1; typedef func_type::type type; }; \