epee: fix fragmented Levin messages on big endian
What changed, and why it matters
This commit fixes a bug in Monero's network message handling that only affects computers using big-endian processors (a rare type of CPU architecture). The bug caused the program to misread the sizes and flags of split-up network messages, which could make the software reject valid network traffic or behave incorrectly. It does not appear to be a security exploit against typical users, but it is a correctness fix for an important networking component.
Treat as a routine correctness/portability fix. No urgent security response is indicated by the commit itself. If running Monero on big-endian hardware, ensure this patch is applied to avoid P2P message handling failures.
Security signals we found
Byte-order conversion added for network protocol header fields on big-endian hosts
Fragmented message reassembly logic corrected
Unit tests adjusted to use little-endian header construction helpers
Evidence from the diff
The patch corrects byte-order handling for fragmented Levin protocol messages on big-endian systems. In levin_protocol_handler_async.h, when reassembling a fragmented message, the code now byte-swaps all fields of bucket_head2 (signature, m_cb, m_command, m_return_code, m_flags, m_protocol_version) if the host is not little-endian, rather than only swapping m_cb. In levin_base.cpp, the final fragment’s m_flags is now written in little-endian byte order. Unit tests are updated to construct headers in little-endian form and to use the make_header helper. The change is a portability/correctness fix; there is no direct evidence in the commit of a security vulnerability or remote exploit.
Changed components
contrib/epee/include/net/levin_protocol_handler_async.hcontrib/epee/src/levin_base.cpptests/unit_tests/epee_levin_protocol_handler_async.cpptests/unit_tests/levin.cppInspect captured patch +23 / −19
diff --git a/contrib/epee/include/net/levin_protocol_handler_async.h b/contrib/epee/include/net/levin_protocol_handler_async.h
index d3cb795..24381f0 100644
--- a/contrib/epee/include/net/levin_protocol_handler_async.h
+++ b/contrib/epee/include/net/levin_protocol_handler_async.h
@@ -489,7 +489,15 @@ public:
temp.swap(m_fragment_buffer);
std::memcpy(std::addressof(m_current_head), std::addressof(temp[0]), sizeof(bucket_head2));
- const std::uint64_t inner_size = SWAP64LE(m_current_head.m_cb);
+#if BYTE_ORDER != LITTLE_ENDIAN
+ m_current_head.m_signature = SWAP64LE(m_current_head.m_signature);
+ m_current_head.m_cb = SWAP64LE(m_current_head.m_cb);
+ m_current_head.m_command = SWAP32LE(m_current_head.m_command);
+ m_current_head.m_return_code = SWAP32LE(m_current_head.m_return_code);
+ m_current_head.m_flags = SWAP32LE(m_current_head.m_flags);
+ m_current_head.m_protocol_version = SWAP32LE(m_current_head.m_protocol_version);
+#endif
+ const std::uint64_t inner_size = m_current_head.m_cb;
buff_to_invoke = {reinterpret_cast<const uint8_t*>(temp.data()) + sizeof(bucket_head2), temp.size() - sizeof(bucket_head2)};
if (buff_to_invoke.size() < inner_size)
{
diff --git a/contrib/epee/src/levin_base.cpp b/contrib/epee/src/levin_base.cpp
index 963e33f..47cf3a8 100644
--- a/contrib/epee/src/levin_base.cpp
+++ b/contrib/epee/src/levin_base.cpp
@@ -120,7 +120,7 @@ namespace levin
copy_size = payload.remove_prefix(payload_space);
if (payload.empty())
- head.m_flags = LEVIN_PACKET_END;
+ head.m_flags = SWAP32LE(LEVIN_PACKET_END);
buffer.write(as_byte_span(head));
buffer.write(payload.data() - copy_size, copy_size);
diff --git a/tests/unit_tests/epee_levin_protocol_handler_async.cpp b/tests/unit_tests/epee_levin_protocol_handler_async.cpp
index d7146ce..6ee989b 100644
--- a/tests/unit_tests/epee_levin_protocol_handler_async.cpp
+++ b/tests/unit_tests/epee_levin_protocol_handler_async.cpp
@@ -560,7 +560,7 @@ TEST_F(test_levin_protocol_handler__hanle_recv_with_invalid_data, handles_invali
TEST_F(test_levin_protocol_handler__hanle_recv_with_invalid_data, handles_big_cb)
{
- m_req_head.m_cb = max_packet_size + 1;
+ m_req_head.m_cb = SWAP64LE(max_packet_size + 1);
prepare_buf();
ASSERT_FALSE(m_conn->m_protocol_handler.handle_recv(m_buf.data(), m_buf.size()));
@@ -636,15 +636,15 @@ TEST_F(test_levin_protocol_handler__hanle_recv_with_invalid_data, handles_unexpe
TEST_F(test_levin_protocol_handler__hanle_recv_with_invalid_data, handles_short_fragment)
{
- m_req_head.m_cb = 1;
- m_req_head.m_flags = LEVIN_PACKET_BEGIN;
- m_req_head.m_command = 0;
+ m_req_head.m_cb = SWAP64LE(1);
+ m_req_head.m_flags = SWAP32LE(LEVIN_PACKET_BEGIN);
+ m_req_head.m_command = SWAP32LE(0);
m_in_data.resize(1);
prepare_buf();
ASSERT_TRUE(m_conn->m_protocol_handler.handle_recv(m_buf.data(), m_buf.size()));
- m_req_head.m_flags = LEVIN_PACKET_END;
+ m_req_head.m_flags = SWAP32LE(LEVIN_PACKET_END);
prepare_buf();
ASSERT_FALSE(m_conn->m_protocol_handler.handle_recv(m_buf.data(), m_buf.size()));
@@ -652,16 +652,16 @@ TEST_F(test_levin_protocol_handler__hanle_recv_with_invalid_data, handles_short_
TEST_F(test_levin_protocol_handler__hanle_recv_with_invalid_data, handles_bad_cb)
{
- m_req_head.m_cb = sizeof(epee::levin::bucket_head2);
- m_req_head.m_flags = LEVIN_PACKET_BEGIN;
- m_req_head.m_command = 0;
+ m_req_head.m_cb = SWAP64LE(sizeof(epee::levin::bucket_head2));
+ m_req_head.m_flags = SWAP32LE(LEVIN_PACKET_BEGIN);
+ m_req_head.m_command = SWAP32LE(0);
m_in_data.resize(sizeof(epee::levin::bucket_head2));
prepare_buf();
ASSERT_TRUE(m_conn->m_protocol_handler.handle_recv(m_buf.data(), m_buf.size()));
- m_req_head.m_cb = 1;
- m_req_head.m_flags = LEVIN_PACKET_END;
+ m_req_head.m_cb = SWAP64LE(1);
+ m_req_head.m_flags = SWAP32LE(LEVIN_PACKET_END);
m_in_data.resize(1);
prepare_buf();
diff --git a/tests/unit_tests/levin.cpp b/tests/unit_tests/levin.cpp
index 4d890ef..f3fe5a2 100644
--- a/tests/unit_tests/levin.cpp
+++ b/tests/unit_tests/levin.cpp
@@ -567,9 +567,7 @@ TEST(make_fragment, multiple)
EXPECT_TRUE(std::memcmp(std::addressof(header), fragment.data(), sizeof(header)) == 0);
fragment.take_slice(sizeof(header));
- header.m_flags = LEVIN_PACKET_REQUEST;
- header.m_cb = bytes.size();
- header.m_command = 114;
+ header = epee::levin::make_header(114, bytes.size(), LEVIN_PACKET_REQUEST, false);
ASSERT_LE(sizeof(header), fragment.size());
EXPECT_TRUE(std::memcmp(std::addressof(header), fragment.data(), sizeof(header)) == 0);
@@ -581,9 +579,7 @@ TEST(make_fragment, multiple)
bytes.erase(0, 1024 - sizeof(header) * 2);
fragment.take_slice(1024 - sizeof(header) * 2);
- header.m_flags = 0;
- header.m_cb = 1024 - sizeof(header);
- header.m_command = 0;
+ header = epee::levin::make_header(0, 1024 - sizeof(header), 0, false);
ASSERT_LE(sizeof(header), fragment.size());
EXPECT_TRUE(std::memcmp(std::addressof(header), fragment.data(), sizeof(header)) == 0);
@@ -595,7 +591,7 @@ TEST(make_fragment, multiple)
bytes.erase(0, 1024 - sizeof(header));
fragment.take_slice(1024 - sizeof(header));
- header.m_flags = LEVIN_PACKET_END;
+ header = epee::levin::make_header(0, 1024 - sizeof(header), LEVIN_PACKET_END, false);
ASSERT_LE(sizeof(header), fragment.size());
EXPECT_TRUE(std::memcmp(std::addressof(header), fragment.data(), sizeof(header)) == 0);
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.