[Feat] (MG_Remote, P11 B1): the fd:<control>,<aux> client endpoint - adopt two connected unix sockets a broker opened for this process, refuse by name anything that is not a distinct connected AF_UNIX stream pair, and present the client's own PairBind pair before the Hello, over shared segments

This commit is contained in:
2026-09-29 14:47:31 -04:00
parent 76f75f3a0c
commit 53098bc57f
3 changed files with 152 additions and 9 deletions
@@ -67,6 +67,21 @@ namespace MobileGL::MG_Remote::Client {
return static_cast<std::uint32_t>(::getpid());
#endif
}
// P11 B1: `fd:<control>,<aux>` - two decimal descriptor numbers, one comma, nothing else.
// Strict on purpose: a stray space or sign is an operator's typo, and a typo that parsed
// would name some other descriptor of this process.
bool ParseFdEndpoint(const std::string& spec, int* outControl, int* outAux) {
const auto comma = spec.find(',');
if (comma == std::string::npos || spec.find(',', comma + 1) != std::string::npos) return false;
const auto number = [](const std::string& text, int* out) {
if (text.empty() || text.size() > 9 || text.find_first_not_of("0123456789") != std::string::npos)
return false;
*out = std::atoi(text.c_str());
return true;
};
return number(spec.substr(0, comma), outControl) && number(spec.substr(comma + 1), outAux);
}
} // namespace
#define MGP5_C0_STUB(what) \
@@ -843,6 +858,41 @@ namespace MobileGL::MG_Remote::Client {
return MOBILEGL_ERR_INVALID_ARGUMENT;
}
const std::string configured(MG_Config::Ipc.Control.c_str());
// P11 B1 (CONTRACT-P11 B1): a PRE-CONNECTED PAIR, fd:<control>,<aux>. A client in another
// app (or the shell) may not connect() to the server app's endpoint - SELinux `connectto`
// (B0-CROSS-APP.md) - so the server app's broker connected both sockets and an app_process
// helper passed the client ends in, inherited across its execve. This process only ADOPTS
// them: it presents its own PairBind pair (the broker wrote nothing) and says Hello. Connect
// dial, and no data endpoint - the aux socket carries the Welcome's descriptors, so the plane
// is shared segments exactly as for `unix:`.
if (configured.compare(0, 3, "fd:") == 0) {
const std::string data(MG_Config::Ipc.Data.c_str());
int control = -1;
int aux = -1;
if (!ParseFdEndpoint(configured.substr(3), &control, &aux)) {
MGLOG_E("MG_Remote: Refuse{ProtocolMismatch} MOBILEGL_IPC_CONTROL='%s' is not fd:<control>,<aux> "
"(two descriptor numbers and one comma)",
configured.c_str());
return MOBILEGL_ERR_UNSUPPORTED;
}
if (data != "auto" && data != "shm") {
MGLOG_E("MG_Remote: Refuse{ProtocolMismatch} unsupported control/data pair %s/%s - an fd: pair is "
"a unix connection and carries shared segments only",
configured.c_str(), data.c_str());
return MOBILEGL_ERR_UNSUPPORTED;
}
m_connectDial = true;
m_dataEndpoint.clear();
std::unique_ptr<Transport::SocketTransport> socket;
const auto adopted = Transport::SocketTransport::AdoptConnectedPair(control, aux, socket);
if (adopted != MOBILEGL_OK) return adopted;
const auto started = StartOverSocket(std::move(socket));
if (started == MOBILEGL_OK) {
MGLOG_I("MG_Remote client: control=fd data=shm server=fd:%d,%d pid=%u dial=connect", control, aux,
m_peerServerPid);
}
return started;
}
if (configured != "fork") {
const bool tcp = configured.compare(0, 6, "tcp://") == 0;
const bool unixSocket = configured.compare(0, 5, "unix:") == 0;
@@ -317,17 +317,13 @@ namespace MobileGL::MG_Remote::Transport {
}
}
// One more connection to `path` whose first bytes are `payload`, framed: a DataBind
// (ConnectDataConnection) or a PairBind (ConnectTo). `what` names the frame in the log.
MobileGLResult ConnectPresenting(const std::string& path, std::uint32_t timeoutMs, const void* payload,
std::uint64_t size, const char* what, int* outFd) {
*outFd = -1;
// `payload`, framed, as the first bytes on the connected socket `fd`: a DataBind or a
// PairBind. `what` names the frame in the log. The descriptor is left open either way; the
// caller owns it.
MobileGLResult SendFirstFrame(int fd, const void* payload, std::uint64_t size, const char* what) {
std::vector<std::uint8_t> framed;
const MobileGLResult appended = AppendFrame(framed, payload, size);
if (appended != MOBILEGL_OK) return appended;
int fd = -1;
const MobileGLResult connected = ConnectOne(path, timeoutMs, &fd);
if (connected != MOBILEGL_OK) return connected;
std::size_t written = 0;
while (written < framed.size()) {
const ssize_t n = ::send(fd, framed.data() + written, framed.size() - written, MSG_NOSIGNAL);
@@ -335,12 +331,47 @@ namespace MobileGL::MG_Remote::Transport {
if (n < 0 && errno == EINTR) continue;
WireLogError("MG_Remote SocketTransport: the %s could not be sent (%zu of %zu bytes): %s", what,
written, framed.size(), std::strerror(errno));
::close(fd);
return MOBILEGL_ERR_TRANSPORT_CLOSED;
}
return MOBILEGL_OK;
}
// One more connection to `path` whose first bytes are `payload`, framed: a DataBind
// (ConnectDataConnection) or a PairBind (ConnectTo). `what` names the frame in the log.
MobileGLResult ConnectPresenting(const std::string& path, std::uint32_t timeoutMs, const void* payload,
std::uint64_t size, const char* what, int* outFd) {
*outFd = -1;
int fd = -1;
const MobileGLResult connected = ConnectOne(path, timeoutMs, &fd);
if (connected != MOBILEGL_OK) return connected;
const MobileGLResult sent = SendFirstFrame(fd, payload, size, what);
if (sent != MOBILEGL_OK) {
::close(fd);
return sent;
}
*outFd = fd;
return MOBILEGL_OK;
}
// P11 B1: what an adopted descriptor must be for the `fd:` endpoint - open, a connected
// AF_UNIX stream socket. Returns null when it is, otherwise the reason, for the log line.
const char* AdoptableUnixStreamProblem(int fd) {
if (fd < 0) return "is negative";
if (::fcntl(fd, F_GETFD) < 0) return "is not an open descriptor in this process";
int type = 0;
socklen_t typeSize = sizeof(type);
if (::getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &typeSize) != 0) return "is not a socket";
if (type != SOCK_STREAM) return "is not a stream socket";
sockaddr_storage local{};
socklen_t localSize = sizeof(local);
if (::getsockname(fd, reinterpret_cast<sockaddr*>(&local), &localSize) != 0 ||
local.ss_family != AF_UNIX)
return "is not an AF_UNIX socket (a TCP pair has no descriptor channel; use tcp:// instead)";
sockaddr_storage peer{};
socklen_t peerSize = sizeof(peer);
if (::getpeername(fd, reinterpret_cast<sockaddr*>(&peer), &peerSize) != 0) return "is not connected";
return nullptr;
}
} // namespace
MobileGLResult SocketTransport::Listen(const std::string& path, int* outListenFd) {
@@ -428,6 +459,52 @@ namespace MobileGL::MG_Remote::Transport {
return MOBILEGL_OK;
}
MobileGLResult SocketTransport::AdoptConnectedPair(int controlFd, int auxFd,
std::unique_ptr<SocketTransport>& outClient) {
outClient.reset();
// Checked before a byte is written, so a stale or mistyped `fd:` names what it points at
// instead of writing a PairBind into something that is not the server.
if (controlFd == auxFd) {
WireLogError("MG_Remote: Refuse{ProtocolMismatch} fd: endpoint names descriptor %d twice; the control "
"and aux connections are two sockets", controlFd);
return MOBILEGL_ERR_INVALID_ARGUMENT;
}
const int fds[2] = {controlFd, auxFd};
const char* names[2] = {"control", "aux"};
for (int index = 0; index < 2; ++index) {
if (const char* problem = AdoptableUnixStreamProblem(fds[index])) {
WireLogError("MG_Remote: Refuse{ProtocolMismatch} fd: endpoint's %s descriptor %d %s", names[index],
fds[index], problem);
return MOBILEGL_ERR_INVALID_ARGUMENT;
}
}
// They arrived inheritable (the helper cleared FD_CLOEXEC so they would survive its execve);
// from here they are this session's, and a program's own children must not inherit them.
::fcntl(controlFd, F_SETFD, FD_CLOEXEC);
::fcntl(auxFd, F_SETFD, FD_CLOEXEC);
// P11 PAIR: whoever opened the two connections, the CLIENT presents the pair's identity -
// one fresh nonce, control then aux - exactly as ConnectTo does, before the Hello. The
// broker that connected them wrote nothing on them.
std::uint8_t nonce[kPairNonceBytes];
MobileGLResult result = MintNonce(nonce, sizeof(nonce));
if (result == MOBILEGL_OK) {
const auto controlBind = EncodePairBind(nonce, false);
result = SendFirstFrame(controlFd, controlBind.data(), controlBind.size(),
"fd: control connection's PairBind");
}
if (result == MOBILEGL_OK) {
const auto auxBind = EncodePairBind(nonce, true);
result = SendFirstFrame(auxFd, auxBind.data(), auxBind.size(), "fd: aux connection's PairBind");
}
if (result != MOBILEGL_OK) {
::close(controlFd);
::close(auxFd);
return result;
}
outClient = std::make_unique<SocketTransport>(controlFd, auxFd, TransportRole::Client);
return MOBILEGL_OK;
}
MobileGLResult SocketTransport::ConnectControl(const std::string& path, std::uint32_t timeoutMs,
std::unique_ptr<SocketTransport>& outClient) {
if (!TcpName(path)) return ConnectTo(path, timeoutMs, outClient);
@@ -816,6 +893,12 @@ namespace MobileGL::MG_Remote::Transport {
return MOBILEGL_ERR_UNSUPPORTED;
}
MobileGLResult SocketTransport::AdoptConnectedPair(int, int, std::unique_ptr<SocketTransport>& outClient) {
outClient.reset();
WireLogError("MG_Remote SocketTransport: the fd: endpoint is POSIX only (CONTRACT-P11 B1)");
return MOBILEGL_ERR_UNSUPPORTED;
}
SocketTransport::SocketTransport(int, int, TransportRole role) : m_role(role) {}
SocketTransport::~SocketTransport() = default;
int SocketTransport::TakeDataFd() { return -1; }
@@ -104,6 +104,16 @@ namespace MobileGL::MG_Remote::Transport {
static MobileGLResult ConnectTo(const std::string& path, std::uint32_t timeoutMs,
std::unique_ptr<SocketTransport>& outClient);
// P11 B1: the client's half when SOMEBODY ELSE connected - the `fd:<control>,<aux>`
// endpoint. A broker in the server's own app (the only context SELinux lets connect to its
// endpoint) opened both connections and handed the client ends over; this adopts them. Both
// must be open, connected AF_UNIX stream sockets and distinct (refused by name otherwise,
// nothing written). They become CLOEXEC, and the client then presents one PairBind pair on
// them - a fresh nonce, control then aux - exactly as ConnectTo does: the broker writes
// nothing, so the server's PairAcceptor pairs them by the client's own nonce.
static MobileGLResult AdoptConnectedPair(int controlFd, int auxFd,
std::unique_ptr<SocketTransport>& outClient);
// ---- PH-7 (4), ID-P7-3: a TCP data connection is BOUND, not paired --------
//
// The server used to pair a session's two connections by ARRIVAL ORDER inside a window