Skip to content

Commit 82a6298

Browse files
dsjohns2facebook-github-bot
authored andcommitted
Make IpcRemHandle trivially destructible to prevent secondary segfaults (meta-pytorch#2068)
Summary: IpcRemHandle::peerId is changed from std::string to char[kMaxPeerIdLen]. When an IB request double-completion causes refCount_ to go negative, commInternalError propagates up through FB_COMMCHECKTHROW_EX in AllReduceRing, and stack unwinding destroys the local vector<unique_ptr<CtranMapperRequest>>. The destruction chain reaches IpcRemHandle::~IpcRemHandle(), where ~std::string() tries to free a corrupted heap pointer and segfaults — masking the real IB bug. With char[], the destructor is trivial (no-op), so the error path completes cleanly and the actual commInternalError is reported. Reviewed By: elvinlife Differential Revision: D97314372
1 parent e4bcf85 commit 82a6298

5 files changed

Lines changed: 32 additions & 9 deletions

File tree

comms/ctran/mapper/tests/CtranDistMapperUT.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,8 @@ TEST_P(CtranDistMapperBufExportParam, BufExportCtrl) {
813813
if (remoteAccessKeys.backend == CtranMapperBackend::IB) {
814814
ASSERT_NE(remoteAccessKeys.ibKey.rkeys[0], 0);
815815
} else if (remoteAccessKeys.backend == CtranMapperBackend::NVL) {
816-
ASSERT_EQ(remoteAccessKeys.nvlKey.peerId, statex->gPid(recvPeer));
816+
ASSERT_STREQ(
817+
remoteAccessKeys.nvlKey.peerId, statex->gPid(recvPeer).c_str());
817818
ASSERT_NE(remoteAccessKeys.nvlKey.basePtr, nullptr);
818819
}
819820

comms/ctran/mapper/tests/CtranMapperUT.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1273,7 +1273,8 @@ TEST_F(CtranMapperTest, RemoteAccessKeyToString) {
12731273
rkey1.ibKey.rkeys[i] = 291 + i;
12741274
}
12751275
rkey1.ibKey.nKeys = CTRAN_MAX_IB_DEVICES_PER_RANK;
1276-
rkey1.nvlKey.peerId = "host1:1234";
1276+
std::strncpy(
1277+
rkey1.nvlKey.peerId, "host1:1234", ctran::regcache::kMaxPeerIdLen);
12771278
rkey1.nvlKey.basePtr = (void*)0x4567890;
12781279
EXPECT_EQ(rkey1.toString(), "backend=IB, ibKey=[291, 292]");
12791280

comms/ctran/regcache/IpcRegCache.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ commResult_t ctran::IpcRegCache::importMem(
108108
// import from baseAddr of a remote segment, return buf at offset from
109109
// baseAddr
110110
*buf = reinterpret_cast<char*>(basePtr) + ipcDesc.offset;
111-
remKey->peerId = peerId;
111+
std::snprintf(remKey->peerId, regcache::kMaxPeerIdLen, "%s", peerId.c_str());
112112
remKey->basePtr = ipcDesc.desc.base;
113113
remKey->uid = ipcDesc.uid;
114114
CLOGF_TRACE(

comms/ctran/regcache/IpcRegCacheBase.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,14 @@ struct IpcRemRegElem {
129129
}
130130
};
131131

132+
// Maximum length for peer ID string (including null terminator)
133+
// Format: "hostname:pid" - hostname can be up to 255 chars (DNS limit)
134+
constexpr size_t kMaxPeerIdLen = 272;
135+
132136
struct IpcRemHandle {
133137
// use peerId, basePtr and uid on peer to lookup the imported memory handle
134-
// in local cache
135-
std::string peerId;
138+
// in local cache.
139+
char peerId[kMaxPeerIdLen]{};
136140
void* basePtr;
137141
uint32_t uid;
138142

@@ -148,10 +152,6 @@ enum class IpcReqType : uint8_t {
148152
kRelease = 1, // Release notification
149153
};
150154

151-
// Maximum length for peer ID string (including null terminator)
152-
// Format: "hostname:pid" - hostname can be up to 255 chars (DNS limit)
153-
constexpr size_t kMaxPeerIdLen = 272;
154-
155155
// Unified IPC request structure sent over the network.
156156
// Used for both memory export (IpcDesc) and release (IpcRelease) requests.
157157
// The peer checks IpcReqType to determine which callback to invoke.

comms/ctran/regcache/tests/RegCacheUT.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <gtest/gtest.h>
55

66
#include <cstdlib>
7+
#include <cstring>
78
#include <memory>
89

910
#include "comms/ctran/backends/ib/CtranIbSingleton.h"
@@ -1365,6 +1366,26 @@ TEST_F(RegCacheTest, ImportMemWithExtraSegments) {
13651366
COMMCHECK_TEST(ctran::commMemFreeDisjoint(buf, segSizes));
13661367
}
13671368

1369+
// Verify IpcRemHandle is trivially destructible and survives heap corruption.
1370+
// Simulates the scenario from the IB double-completion bug: construct a valid
1371+
// IpcRemHandle, corrupt its memory, then destroy it. With std::string peerId,
1372+
// the destructor tries to free a corrupted pointer and segfaults. With a
1373+
// fixed-size char[], the destructor is trivial and this is safe.
1374+
TEST(IpcRemHandleTest, CorruptedDestroyDoesNotCrash) {
1375+
alignas(ctran::regcache::IpcRemHandle) char
1376+
buf[sizeof(ctran::regcache::IpcRemHandle)];
1377+
auto* handle = new (buf) ctran::regcache::IpcRemHandle();
1378+
1379+
// Corrupt the entire struct — simulates heap corruption
1380+
std::memset(buf, 0xAB, sizeof(ctran::regcache::IpcRemHandle));
1381+
// Prevent the compiler from optimizing away the corruption or destructor
1382+
asm volatile("" ::: "memory");
1383+
1384+
// Destructor must not crash. With std::string this segfaults;
1385+
// with char[] this is a no-op.
1386+
handle->~IpcRemHandle();
1387+
}
1388+
13681389
int main(int argc, char* argv[]) {
13691390
::testing::InitGoogleTest(&argc, argv);
13701391
return RUN_ALL_TESTS();

0 commit comments

Comments
 (0)