Skip to content

Commit af1a2cc

Browse files
committed
[BUG] Fix misaligned sockaddr_in access in SocketAddr
SocketAddr(u_long, uint16_t) bound a sockaddr_in reference to the sockaddr storage through a reinterpret_cast. sockaddr is not aligned for a sockaddr_in on every ABI (alignment 1 on macOS and 2 on Linux versus 4), so the access is undefined and UBSan reports it: socket_tools.h:180:29: runtime error: reference binding to misaligned address 0x00016bda6551 for type 'sockaddr_in', which requires 4 byte alignment Store the address in a sockaddr_storage, which is the socket API's type for an address of any family and is aligned for all of them, and build the IPv4 address in a local sockaddr_in that is copied into the storage, as the string constructor already does. Replace the sockaddr conversion operators with addr() and addr_len(): the storage is larger than a sockaddr_in and passing its full size as the address length to bind() or connect() is rejected on some platforms, so addr_len() is the length of a sockaddr_in, the only family stored. getsockname() and accept() keep passing the full storage size, which is the buffer capacity for those output parameters. The default constructor sets AF_UNSPEC explicitly, and the tests read the family through ss_family. New tests cover the integer constructor at an address only sockaddr_storage alignment can guarantee, the accessors, and a bind/getsockname/connect/accept round trip on the loopback interface. Fixes #4307
1 parent c7b64ec commit af1a2cc

3 files changed

Lines changed: 109 additions & 32 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Increment the:
1515

1616
## [Unreleased]
1717

18+
* [BUG] Store `SocketAddr` in a `sockaddr_storage` and pass `addr()` and
19+
`addr_len()` to the socket API, fixing a misaligned `sockaddr_in` access
20+
[#4307](https://github.com/open-telemetry/opentelemetry-cpp/issues/4307)
21+
1822
## [1.30.0] 2026-10-22
1923

2024
This release contains:

‎ext/include/opentelemetry/ext/http/server/socket_tools.h‎

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -167,20 +167,25 @@ struct SocketAddr
167167
{
168168
static u_long const Loopback = 0x7F000001;
169169

170-
sockaddr m_data{};
170+
// sockaddr_storage is the socket API's type for holding an address of any family: it is large
171+
// enough and aligned strictly enough for a sockaddr_in, which a plain sockaddr is not on every
172+
// ABI. Addresses are copied in and out of it with memcpy; see addr() and addr_len() for how it
173+
// is passed to the socket API.
174+
sockaddr_storage m_data{};
171175

172176
/// <summary>
173177
/// SocketAddr constructor
174178
/// </summary>
175179
/// <returns>SocketAddr</returns>
176-
SocketAddr() {}
180+
SocketAddr() { m_data.ss_family = AF_UNSPEC; }
177181

178182
SocketAddr(u_long addr, uint16_t port)
179183
{
180-
sockaddr_in &inet4 = reinterpret_cast<sockaddr_in &>(m_data);
184+
sockaddr_in inet4{};
181185
inet4.sin_family = AF_INET;
182186
inet4.sin_port = htons(port);
183187
inet4.sin_addr.s_addr = htonl(addr);
188+
std::memcpy(&m_data, &inet4, sizeof(inet4));
184189
}
185190

186191
/// Parses an IPv4 address in "host" or "host:port" form. Host parsing follows the platform's
@@ -264,13 +269,18 @@ struct SocketAddr
264269
}
265270
}
266271

267-
operator sockaddr *() { return &m_data; }
272+
/// The stored address as the socket API takes it. Only AF_INET addresses are stored, so
273+
/// addr_len() is the length of a sockaddr_in rather than of the whole storage: bind() and
274+
/// connect() reject a length that is too large for the address family on some platforms.
275+
sockaddr *addr() { return reinterpret_cast<sockaddr *>(&m_data); }
268276

269-
operator const sockaddr *() const { return &m_data; }
277+
const sockaddr *addr() const { return reinterpret_cast<const sockaddr *>(&m_data); }
278+
279+
socklen_t addr_len() const { return static_cast<socklen_t>(sizeof(sockaddr_in)); }
270280

271281
int port() const
272282
{
273-
switch (m_data.sa_family)
283+
switch (m_data.ss_family)
274284
{
275285
case AF_INET: {
276286
// Copy out rather than binding a sockaddr_in glvalue to sockaddr storage, which is an
@@ -289,7 +299,7 @@ struct SocketAddr
289299
{
290300
std::ostringstream os;
291301

292-
switch (m_data.sa_family)
302+
switch (m_data.ss_family)
293303
{
294304
case AF_INET: {
295305
sockaddr_in inet4{};
@@ -302,23 +312,22 @@ struct SocketAddr
302312
}
303313

304314
default:
305-
os << "[?AF?" << m_data.sa_family << ']';
315+
os << "[?AF?" << m_data.ss_family << ']';
306316
}
307317
return os.str();
308318
}
309319
};
310320

311-
// The parser memcpys a sockaddr_in into m_data, and the socket syscalls pass sizeof(SocketAddr)
312-
// as the address length. This wrapper is IPv4-only, so require sockaddr and sockaddr_in to have
313-
// the exact same size rather than trusting every ABI: passing an address length that is too large
314-
// for the family is a documented EINVAL for connect()/bind(). Exact equality also keeps the memcpy
315-
// safe. Together with the assertion below, sizeof(SocketAddr) == sizeof(sockaddr_in).
316-
static_assert(sizeof(sockaddr) == sizeof(sockaddr_in),
317-
"SocketAddr is IPv4-only: sockaddr and sockaddr_in must have identical size");
318-
static_assert(offsetof(sockaddr, sa_family) == offsetof(sockaddr_in, sin_family),
319-
"sockaddr and sockaddr_in must place the address family at the same offset");
320-
static_assert(sizeof(SocketAddr) == sizeof(sockaddr),
321-
"SocketAddr must add no storage beyond its sockaddr, since syscalls use its size");
321+
// The constructors memcpy a sockaddr_in into m_data, port() and toString() memcpy it back out, and
322+
// the address family is read through ss_family. Those are only well defined if the storage is
323+
// large enough and aligned strictly enough for a sockaddr_in and places the address family where
324+
// a sockaddr_in does.
325+
static_assert(sizeof(sockaddr_storage) >= sizeof(sockaddr_in),
326+
"sockaddr_storage must be large enough to hold a sockaddr_in");
327+
static_assert(alignof(sockaddr_storage) >= alignof(sockaddr_in),
328+
"sockaddr_storage must be aligned strictly enough for a sockaddr_in");
329+
static_assert(offsetof(sockaddr_storage, ss_family) == offsetof(sockaddr_in, sin_family),
330+
"sockaddr_storage and sockaddr_in must place the address family at the same offset");
322331

323332
/// <summary>
324333
/// Encapsulation of a socket (non-exclusive ownership)
@@ -388,7 +397,7 @@ struct Socket
388397
bool connect(SocketAddr const &addr)
389398
{
390399
assert(m_sock != Invalid);
391-
return (::connect(m_sock, addr, sizeof(addr)) == 0);
400+
return (::connect(m_sock, addr.addr(), addr.addr_len()) == 0);
392401
}
393402

394403
void close()
@@ -418,18 +427,18 @@ struct Socket
418427
bool bind(SocketAddr const &addr)
419428
{
420429
assert(m_sock != Invalid);
421-
return (::bind(m_sock, addr, sizeof(addr)) == 0);
430+
return (::bind(m_sock, addr.addr(), addr.addr_len()) == 0);
422431
}
423432

424433
bool getsockname(SocketAddr &addr) const
425434
{
426435
assert(m_sock != Invalid);
427436
#ifdef _WIN32
428-
int addrlen = sizeof(addr);
437+
int addrlen = sizeof(addr.m_data);
429438
#else
430-
socklen_t addrlen = sizeof(addr);
439+
socklen_t addrlen = sizeof(addr.m_data);
431440
#endif
432-
return (::getsockname(m_sock, addr, &addrlen) == 0);
441+
return (::getsockname(m_sock, addr.addr(), &addrlen) == 0);
433442
}
434443

435444
bool listen(int backlog)
@@ -442,11 +451,11 @@ struct Socket
442451
{
443452
assert(m_sock != Invalid);
444453
#ifdef _WIN32
445-
int addrlen = sizeof(caddr);
454+
int addrlen = sizeof(caddr.m_data);
446455
#else
447-
socklen_t addrlen = sizeof(caddr);
456+
socklen_t addrlen = sizeof(caddr.m_data);
448457
#endif
449-
csock = ::accept(m_sock, caddr, &addrlen);
458+
csock = ::accept(m_sock, caddr.addr(), &addrlen);
450459
return !csock.invalid();
451460
}
452461

‎ext/test/http/socket_tools_test.cc‎

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
#include <gtest/gtest.h>
5+
#include <stddef.h>
6+
#include <cstdint>
57
#include <string>
68
#ifndef _WIN32
7-
# include <sys/socket.h> // for sockaddr, AF_UNSPEC, AF_INET
9+
# include <netinet/in.h> // for sockaddr_in, IPPROTO_TCP
10+
# include <sys/socket.h> // for sockaddr, AF_UNSPEC, AF_INET, SOCK_STREAM
811
#endif
912

1013
#include "opentelemetry/ext/http/server/socket_tools.h"
@@ -17,14 +20,75 @@ namespace
1720
// corrupted while port() happens to still return -1.
1821
void ExpectInvalid(const SocketTools::SocketAddr &addr)
1922
{
20-
EXPECT_EQ(addr.m_data.sa_family, AF_UNSPEC);
23+
EXPECT_EQ(addr.m_data.ss_family, AF_UNSPEC);
2124
EXPECT_EQ(addr.port(), -1);
2225
}
2326

27+
TEST(SocketAddrTest, DefaultsToUnspecified)
28+
{
29+
SocketTools::SocketAddr addr;
30+
EXPECT_EQ(addr.m_data.ss_family, AF_UNSPEC);
31+
EXPECT_EQ(addr.port(), -1);
32+
}
33+
34+
// The storage must be aligned for every address type copied into it. Placing the address after a
35+
// single byte lets the compiler put it wherever its own alignment allows; with a sockaddr
36+
// (alignment 1 or 2) as the storage that was an address a sockaddr_in (alignment 4) cannot live
37+
// at, which UBSan's alignment check reported for this constructor.
38+
TEST(SocketAddrTest, ConstructsFromHostOrderAddressAndPort)
39+
{
40+
struct Padded
41+
{
42+
char pad = 0;
43+
SocketTools::SocketAddr addr{SocketTools::SocketAddr::Loopback, 8800};
44+
} padded;
45+
EXPECT_EQ(reinterpret_cast<std::uintptr_t>(&padded.addr.m_data) % alignof(sockaddr_in), 0u);
46+
EXPECT_EQ(padded.addr.m_data.ss_family, AF_INET);
47+
EXPECT_EQ(padded.addr.port(), 8800);
48+
EXPECT_EQ(padded.addr.toString(), "127.0.0.1:8800");
49+
}
50+
51+
// bind() and connect() take addr() and addr_len(). The storage is larger than an IPv4 address, and
52+
// passing its full size as the address length is rejected with EINVAL on some platforms.
53+
TEST(SocketAddrTest, ExposesAddressAndLengthForTheSocketApi)
54+
{
55+
SocketTools::SocketAddr addr(SocketTools::SocketAddr::Loopback, 8800);
56+
EXPECT_EQ(static_cast<const void *>(addr.addr()), static_cast<const void *>(&addr.m_data));
57+
EXPECT_EQ(static_cast<size_t>(addr.addr_len()), sizeof(sockaddr_in));
58+
}
59+
60+
TEST(SocketTest, BindsConnectsAndAcceptsOnLoopback)
61+
{
62+
SocketTools::Socket listener(AF_INET, SOCK_STREAM, IPPROTO_TCP);
63+
ASSERT_FALSE(listener.invalid());
64+
ASSERT_TRUE(listener.bind(SocketTools::SocketAddr(SocketTools::SocketAddr::Loopback, 0)));
65+
ASSERT_TRUE(listener.listen(1));
66+
67+
SocketTools::SocketAddr bound;
68+
ASSERT_TRUE(listener.getsockname(bound));
69+
EXPECT_EQ(bound.m_data.ss_family, AF_INET);
70+
ASSERT_GT(bound.port(), 0);
71+
72+
SocketTools::Socket client(AF_INET, SOCK_STREAM, IPPROTO_TCP);
73+
ASSERT_FALSE(client.invalid());
74+
ASSERT_TRUE(client.connect(SocketTools::SocketAddr(SocketTools::SocketAddr::Loopback,
75+
static_cast<uint16_t>(bound.port()))));
76+
77+
SocketTools::Socket accepted;
78+
SocketTools::SocketAddr peer;
79+
ASSERT_TRUE(listener.accept(accepted, peer));
80+
EXPECT_EQ(peer.m_data.ss_family, AF_INET);
81+
EXPECT_EQ(peer.toString().substr(0, 10), "127.0.0.1:");
82+
83+
accepted.close();
84+
client.close();
85+
listener.close();
86+
}
87+
2488
TEST(SocketAddrTest, ParsesHostAndPort)
2589
{
2690
SocketTools::SocketAddr addr("127.0.0.1:8800");
27-
EXPECT_EQ(addr.m_data.sa_family, AF_INET);
91+
EXPECT_EQ(addr.m_data.ss_family, AF_INET);
2892
EXPECT_EQ(addr.port(), 8800);
2993
EXPECT_EQ(addr.toString(), "127.0.0.1:8800");
3094
}
@@ -57,7 +121,7 @@ TEST(SocketAddrTest, ParsesHostWithoutPort)
57121
TEST(SocketAddrTest, ParsesLegitimateZeroPort)
58122
{
59123
SocketTools::SocketAddr addr("127.0.0.1:0");
60-
EXPECT_EQ(addr.m_data.sa_family, AF_INET);
124+
EXPECT_EQ(addr.m_data.ss_family, AF_INET);
61125
EXPECT_EQ(addr.port(), 0);
62126
EXPECT_EQ(addr.toString(), "127.0.0.1:0");
63127
}
@@ -67,7 +131,7 @@ TEST(SocketAddrTest, ParsesLegitimateZeroPort)
67131
TEST(SocketAddrTest, AcceptsLeadingZeroPort)
68132
{
69133
SocketTools::SocketAddr addr("127.0.0.1:080");
70-
EXPECT_EQ(addr.m_data.sa_family, AF_INET);
134+
EXPECT_EQ(addr.m_data.ss_family, AF_INET);
71135
EXPECT_EQ(addr.port(), 80);
72136
}
73137

0 commit comments

Comments
 (0)