Repository navigation
UCT/TCP: Validate AM receive length #12081
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1280,6 +1280,16 @@ static inline void uct_tcp_ep_handle_recv_err(uct_tcp_ep_t *ep, | |
| } | ||
| } | ||
|
|
||
| static UCS_F_NOINLINE void | ||
| uct_tcp_ep_handle_invalid_am_length(uct_tcp_ep_t *ep, uint32_t length, | ||
| size_t max_length) | ||
| { | ||
| ucs_diag("tcp_ep %p (state %s): received AM length %u exceeds maximum %zu", | ||
| ep, uct_tcp_ep_cm_state[ep->conn_state].name, length, max_length); | ||
| uct_tcp_ep_ctx_reset(&ep->rx); | ||
| uct_tcp_ep_handle_disconnected(ep, UCS_ERR_CONNECTION_RESET); | ||
| } | ||
|
|
||
| static inline unsigned uct_tcp_ep_recv(uct_tcp_ep_t *ep, size_t recv_length) | ||
| { | ||
| uct_tcp_iface_t UCS_V_UNUSED *iface = ucs_derived_of(ep->super.super.iface, | ||
|
|
@@ -1435,6 +1445,8 @@ static unsigned uct_tcp_ep_progress_am_rx(uct_tcp_ep_t *ep) | |
| { | ||
| uct_tcp_iface_t *iface = ucs_derived_of(ep->super.super.iface, | ||
| uct_tcp_iface_t); | ||
| size_t max_am_length = iface->config.rx_seg_size - | ||
| sizeof(uct_tcp_am_hdr_t); | ||
| unsigned handled = 0; | ||
| uct_tcp_am_hdr_t *hdr; | ||
| size_t recv_length; | ||
|
|
@@ -1485,10 +1497,11 @@ static unsigned uct_tcp_ep_progress_am_rx(uct_tcp_ep_t *ep) | |
| } | ||
|
|
||
| hdr = UCS_PTR_BYTE_OFFSET(ep->rx.buf, ep->rx.offset); | ||
| ucs_assertv(hdr->length <= (iface->config.rx_seg_size - sizeof(*hdr)), | ||
| "tcp_ep %p (conn state - %s): %u vs %zu", | ||
| ep, uct_tcp_ep_cm_state[ep->conn_state].name, hdr->length, | ||
| (iface->config.rx_seg_size - sizeof(*hdr))); | ||
| if (ucs_unlikely(hdr->length > max_am_length)) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we validate the length of the service AM ids here as well? |
||
| uct_tcp_ep_handle_invalid_am_length(ep, hdr->length, | ||
| max_am_length); | ||
| goto out; | ||
| } | ||
|
|
||
| if (remaining < (sizeof(*hdr) + hdr->length)) { | ||
| handled++; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -259,6 +259,30 @@ UCS_TEST_P(test_uct_tcp, listener_flood_connect_and_close) { | |
| test_listener_flood(*m_ent, max_conn, 0); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Residual coverage gap: the new test only covers the single-header case in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test intentionally sends only the oversized header. Without the fix it enters the partial-message path and subsequent progress calls attempt the second receive, but no payload is sent, so it fails safely by leaving the endpoint active instead of corrupting memory. The exact second-receive overwrite was reproduced separately under ASan. With the fix, every complete header is rejected before it can be retained, independent of TCP segmentation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Residual coverage gap: no coverage for a TX-capable EP hitting this path (error-handler flow via
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new check only selects the existing uct_tcp_ep_handle_disconnected() path. The TX-capable branch and uct_iface_handle_ep_err() are existing peer-failure plumbing covered by the generic peer-failure tests, while this focused regression covers the new oversized-header decision and the RX-only destruction path. I do not think a duplicate TX-capable fixture is needed here. |
||
| } | ||
|
|
||
| UCS_TEST_P(test_uct_tcp, reject_oversized_am) | ||
| { | ||
| constexpr uint64_t magic_number = UCT_TCP_MAGIC_NUMBER; | ||
| const uct_tcp_am_hdr_t hdr = { | ||
| 0, std::numeric_limits<uint32_t>::max() | ||
| }; | ||
| std::vector<char> buf(sizeof(magic_number) + sizeof(hdr)); | ||
| std::vector<int> fds; | ||
|
|
||
| memcpy(&buf[0], &magic_number, sizeof(magic_number)); | ||
| memcpy(&buf[sizeof(magic_number)], &hdr, sizeof(hdr)); | ||
|
|
||
| setup_conns_to_entity(*m_ent, 1, fds); | ||
| post_send(fds.front(), buf); | ||
|
|
||
| wait_for_cond([this]() { | ||
| return ucs_list_is_empty(&m_tcp_iface->ep_list); | ||
| }, | ||
| [this]() { progress(); }, 1.0); | ||
| EXPECT_TRUE(ucs_list_is_empty(&m_tcp_iface->ep_list)); | ||
|
|
||
| close(fds.front()); | ||
| } | ||
|
|
||
| UCS_TEST_P(test_uct_tcp, check_addr_len) | ||
| { | ||
| uct_iface_attr_t iface_attr; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe use
ucs_error()here?diagis below the defaultwarnlog level so it is not printed by default, and this path also triggers for a legit peer configured with a biggerUCX_TCP_RX_SEG_SIZE(the sender bounds AM zcopy by its ownrx_seg_size), so the user would see only a connection reset without the reason.