This is an automated email from the ASF dual-hosted git repository.
moonchen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push:
new 1f34fdafd9 Reject over-long unix socket paths in server_ports (#13356)
1f34fdafd9 is described below
commit 1f34fdafd96f0506edc3bd18098fc54c0800345a
Author: Mo Chen <[email protected]>
AuthorDate: Tue Jul 14 09:14:27 2026 -0500
Reject over-long unix socket paths in server_ports (#13356)
* Reject over-long unix socket paths in server_ports
A path longer than sun_path (108 bytes including the terminator) was
silently truncated, and left unterminated, since strncpy writes no
terminator when it truncates, so ATS bound a listener on the wrong
filesystem path with nothing telling the operator the configured path
was too long.
Reject the path at configuration parse with a Warning naming the
limit, and make UnAddr's string constructors always null-terminate.
---
include/tscore/ink_inet.h | 22 +++++++++++++-----
src/records/RecHttp.cc | 7 ++++++
src/records/unit_tests/test_RecHttp.cc | 41 ++++++++++++++++++++++++++++++++++
src/tscore/unit_tests/test_ink_inet.cc | 32 ++++++++++++++++++++++++++
4 files changed, 96 insertions(+), 6 deletions(-)
diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h
index 18319b1353..d0cc243364 100644
--- a/include/tscore/ink_inet.h
+++ b/include/tscore/ink_inet.h
@@ -34,6 +34,7 @@
#include "tscore/ink_memory.h"
#include "tscore/ink_apidefs.h"
+#include "tscore/ink_string.h"
#include "swoc/bwf_fwd.h"
#if !TS_HAS_IN6_IS_ADDR_UNSPECIFIED
@@ -1610,16 +1611,21 @@ struct UnAddr {
UnAddr() { _path[0] = 0; }
- UnAddr(self const &addr) { strncpy(_path, addr._path, TS_UNIX_SIZE); }
- explicit UnAddr(const char *path) { strncpy(_path, path, TS_UNIX_SIZE - 1); }
- explicit UnAddr(const std::string &path) { strncpy(_path, path.c_str(),
TS_UNIX_SIZE); }
+ UnAddr(self const &addr) { ink_strlcpy(_path, addr._path, TS_UNIX_SIZE); }
+ explicit UnAddr(const char *path) { ink_strlcpy(_path, path, TS_UNIX_SIZE); }
+ explicit UnAddr(const std::string &path) { ink_strlcpy(_path, path.c_str(),
TS_UNIX_SIZE); }
explicit UnAddr(sockaddr const *addr) { this->assign(addr); }
explicit UnAddr(sockaddr_un const *addr) {
this->assign(ats_ip_sa_cast(addr)); }
/// Construct from @c IpEndpoint.
explicit UnAddr(IpEndpoint const &addr) { this->assign(&addr.sa); }
/// Construct from @c IpEndpoint.
- explicit UnAddr(IpEndpoint const *addr) { this->assign(&addr->sa); }
+ explicit UnAddr(IpEndpoint const *addr) : UnAddr()
+ {
+ if (addr) {
+ this->assign(&addr->sa);
+ }
+ }
/// Assign sockaddr storage.
self &assign(sockaddr const *addr);
@@ -1639,7 +1645,7 @@ struct UnAddr {
operator=(self const &addr)
{
if (this != &addr) {
- strncpy(_path, addr._path, TS_UNIX_SIZE);
+ ink_strlcpy(_path, addr._path, TS_UNIX_SIZE);
}
return *this;
}
@@ -1651,7 +1657,11 @@ inline UnAddr &
UnAddr::assign(sockaddr const *addr)
{
if (addr) {
- strncpy(_path, ats_unix_cast(addr)->sun_path, TS_UNIX_SIZE);
+ // A kernel sockaddr_un may carry a full, unterminated sun_path.
+ strncpy(_path, ats_unix_cast(addr)->sun_path, TS_UNIX_SIZE - 1);
+ _path[TS_UNIX_SIZE - 1] = '\0';
+ } else {
+ _path[0] = '\0';
}
return *this;
}
diff --git a/src/records/RecHttp.cc b/src/records/RecHttp.cc
index 57cb822c99..6feade5dd1 100644
--- a/src/records/RecHttp.cc
+++ b/src/records/RecHttp.cc
@@ -368,6 +368,13 @@ HttpProxyPort::processOptions(const char *opts)
for (auto item : values) {
if (item[0] == '/') {
+ if (size_t len = strlen(item); len >= TS_UNIX_SIZE) {
+ // A longer path would be silently truncated and the listener bound to
the wrong
+ // filesystem path. Reject the whole descriptor so a later token (e.g.
a numeric
+ // port) can't silently turn this into an INET listener.
+ Warning("Unix path '%s' in port configuration '%s' is too long (%zu
bytes, max %zu)", item, opts, len, TS_UNIX_SIZE - 1);
+ return false;
+ }
m_family = AF_UNIX;
m_unix_path = UnAddr(item);
af_set_p = true;
diff --git a/src/records/unit_tests/test_RecHttp.cc
b/src/records/unit_tests/test_RecHttp.cc
index 9f0190d51f..4558631993 100644
--- a/src/records/unit_tests/test_RecHttp.cc
+++ b/src/records/unit_tests/test_RecHttp.cc
@@ -97,6 +97,47 @@ TEST_CASE("RecHttp", "[librecords][RecHttp]")
REQUIRE(view.find(":ssl") != TextView::npos);
REQUIRE(view.find(":proto") == TextView::npos); // it's default, should
not have this.
}
+
+ SECTION("unix-path")
+ {
+ HttpProxyPort::loadValue(ports, "/run/ats/uds.socket");
+ REQUIRE(ports.size() == 1);
+ REQUIRE(ports[0].m_family == AF_UNIX);
+ REQUIRE(std::string_view(ports[0].m_unix_path._path) ==
"/run/ats/uds.socket");
+ }
+
+ SECTION("unix-path-max")
+ {
+ // Exactly TS_UNIX_SIZE - 1 bytes: the longest path that fits sun_path
with its terminator.
+ std::string path = "/" + std::string(TS_UNIX_SIZE - 2, 'x');
+ HttpProxyPort::loadValue(ports, path.c_str());
+ REQUIRE(ports.size() == 1);
+ REQUIRE(ports[0].m_family == AF_UNIX);
+ REQUIRE(std::string_view(ports[0].m_unix_path._path) == path);
+ }
+
+ SECTION("unix-path-too-long")
+ {
+ // One byte past what sun_path can hold.
+ std::string path = "/" + std::string(TS_UNIX_SIZE - 1, 'x');
+ HttpProxyPort::loadValue(ports, path.c_str());
+ REQUIRE(ports.size() == 0);
+ // The reject itself, then loadValue's "No valid definition" for the
dropped descriptor.
+ REQUIRE(cdiag->messages.size() == 2);
+ REQUIRE(cdiag->messages[0].find("too long") != std::string::npos);
+ }
+
+ SECTION("unix-path-too-long-with-port")
+ {
+ // An over-long unix path followed by a numeric port must reject the whole
descriptor,
+ // not silently fall back to an INET port listener.
+ std::string path = "/" + std::string(TS_UNIX_SIZE - 1, 'x') + ":8080";
+ HttpProxyPort::loadValue(ports, path.c_str());
+ REQUIRE(ports.size() == 0);
+ // The reject itself, then loadValue's "No valid definition" for the
dropped descriptor.
+ REQUIRE(cdiag->messages.size() == 2);
+ REQUIRE(cdiag->messages[0].find("too long") != std::string::npos);
+ }
}
struct ConvertAlpnToWireFormatTestCase {
diff --git a/src/tscore/unit_tests/test_ink_inet.cc
b/src/tscore/unit_tests/test_ink_inet.cc
index b16234041a..0dd2304b10 100644
--- a/src/tscore/unit_tests/test_ink_inet.cc
+++ b/src/tscore/unit_tests/test_ink_inet.cc
@@ -280,4 +280,36 @@ TEST_CASE("ink_inet_unix", "[libts][inet][unix]")
#if HAVE_STRUCT_SOCKADDR_UN_SUN_LEN
REQUIRE(ep.sun.sun_len == SUN_LEN(&ep.sun));
#endif
+
+ // An over-long path must still yield a null terminated _path.
+ std::string long_path(TS_UNIX_SIZE + 10, 'y');
+ UnAddr from_cstr(long_path.c_str());
+ REQUIRE(from_cstr._path[TS_UNIX_SIZE - 1] == '\0');
+ REQUIRE(strlen(from_cstr._path) == TS_UNIX_SIZE - 1);
+ UnAddr from_string(long_path);
+ REQUIRE(from_string._path[TS_UNIX_SIZE - 1] == '\0');
+ REQUIRE(strlen(from_string._path) == TS_UNIX_SIZE - 1);
+
+ // A kernel sockaddr_un may carry a full, unterminated sun_path; assign()
and the copy
+ // paths must still yield a terminated _path.
+ IpEndpoint raw;
+ raw.sa.sa_family = AF_UNIX;
+ memset(raw.sun.sun_path, 'z', TS_UNIX_SIZE); // deliberately no terminator
+ UnAddr from_sockaddr(&raw.sa);
+ REQUIRE(from_sockaddr._path[TS_UNIX_SIZE - 1] == '\0');
+ REQUIRE(strlen(from_sockaddr._path) == TS_UNIX_SIZE - 1);
+ UnAddr copied(from_sockaddr);
+ REQUIRE(copied._path[TS_UNIX_SIZE - 1] == '\0');
+ UnAddr assigned;
+ assigned = from_sockaddr;
+ REQUIRE(assigned._path[TS_UNIX_SIZE - 1] == '\0');
+
+ // Constructing from a null address must leave _path an empty, terminated
string so the copy
+ // paths don't read uninitialized memory.
+ UnAddr from_null_sa{static_cast<sockaddr const *>(nullptr)};
+ REQUIRE(from_null_sa._path[0] == '\0');
+ UnAddr from_null_ep{static_cast<IpEndpoint const *>(nullptr)};
+ REQUIRE(from_null_ep._path[0] == '\0');
+ UnAddr copied_null(from_null_sa);
+ REQUIRE(copied_null._path[0] == '\0');
}