diff --git a/MobileGL/MG_Remote/Client/ClientSession.cpp b/MobileGL/MG_Remote/Client/ClientSession.cpp index ca917153..2d9cfc1f 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.cpp +++ b/MobileGL/MG_Remote/Client/ClientSession.cpp @@ -67,6 +67,21 @@ namespace MobileGL::MG_Remote::Client { return static_cast(::getpid()); #endif } + + // P11 B1: `fd:,` - 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:,. 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:, " + "(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 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; diff --git a/MobileGL/MG_Remote/Transport/SocketTransport.cpp b/MobileGL/MG_Remote/Transport/SocketTransport.cpp index 498b9375..a8c52f3b 100644 --- a/MobileGL/MG_Remote/Transport/SocketTransport.cpp +++ b/MobileGL/MG_Remote/Transport/SocketTransport.cpp @@ -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 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(&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(&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& 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(controlFd, auxFd, TransportRole::Client); + return MOBILEGL_OK; + } + MobileGLResult SocketTransport::ConnectControl(const std::string& path, std::uint32_t timeoutMs, std::unique_ptr& 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& 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; } diff --git a/MobileGL/MG_Remote/Transport/SocketTransport.h b/MobileGL/MG_Remote/Transport/SocketTransport.h index 3f9c619a..b5e44e53 100644 --- a/MobileGL/MG_Remote/Transport/SocketTransport.h +++ b/MobileGL/MG_Remote/Transport/SocketTransport.h @@ -104,6 +104,16 @@ namespace MobileGL::MG_Remote::Transport { static MobileGLResult ConnectTo(const std::string& path, std::uint32_t timeoutMs, std::unique_ptr& outClient); + // P11 B1: the client's half when SOMEBODY ELSE connected - the `fd:,` + // 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& 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