diff options
-rw-r--r-- | libs/binder/Parcel.cpp | 333 | ||||
-rw-r--r-- | libs/binder/RpcServer.cpp | 25 | ||||
-rw-r--r-- | libs/binder/RpcSession.cpp | 18 | ||||
-rw-r--r-- | libs/binder/RpcState.cpp | 277 | ||||
-rw-r--r-- | libs/binder/RpcState.h | 8 | ||||
-rw-r--r-- | libs/binder/RpcTransportRaw.cpp | 139 | ||||
-rw-r--r-- | libs/binder/RpcTransportTls.cpp | 22 | ||||
-rw-r--r-- | libs/binder/RpcWireFormat.h | 3 | ||||
-rw-r--r-- | libs/binder/Utils.h | 11 | ||||
-rw-r--r-- | libs/binder/include/binder/Parcel.h | 25 | ||||
-rw-r--r-- | libs/binder/include/binder/RpcServer.h | 12 | ||||
-rw-r--r-- | libs/binder/include/binder/RpcSession.h | 15 | ||||
-rw-r--r-- | libs/binder/include/binder/RpcTransport.h | 21 | ||||
-rw-r--r-- | libs/binder/tests/IBinderRpcTest.aidl | 4 | ||||
-rw-r--r-- | libs/binder/tests/binderRpcTest.cpp | 227 |
15 files changed, 944 insertions, 196 deletions
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp index e67dd7b5e2..8739105393 100644 --- a/libs/binder/Parcel.cpp +++ b/libs/binder/Parcel.cpp @@ -40,6 +40,7 @@ #include <binder/Status.h> #include <binder/TextOutput.h> +#include <android-base/scopeguard.h> #include <cutils/ashmem.h> #include <cutils/compiler.h> #include <utils/Flattenable.h> @@ -152,6 +153,10 @@ static void release_object(const sp<ProcessState>& proc, const flat_binder_objec ALOGE("Invalid object type 0x%08x", obj.hdr.type); } +static int toRawFd(const std::variant<base::unique_fd, base::borrowed_fd>& v) { + return std::visit([](const auto& fd) { return fd.get(); }, v); +} + Parcel::RpcFields::RpcFields(const sp<RpcSession>& session) : mSession(session) { LOG_ALWAYS_FATAL_IF(mSession == nullptr); } @@ -530,6 +535,63 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) { } } } + } else { + auto* rpcFields = maybeRpcFields(); + LOG_ALWAYS_FATAL_IF(rpcFields == nullptr); + auto* otherRpcFields = parcel->maybeRpcFields(); + if (otherRpcFields == nullptr) { + return BAD_TYPE; + } + if (rpcFields->mSession != otherRpcFields->mSession) { + return BAD_TYPE; + } + + const size_t savedDataPos = mDataPos; + base::ScopeGuard scopeGuard = [&]() { mDataPos = savedDataPos; }; + + rpcFields->mObjectPositions.reserve(otherRpcFields->mObjectPositions.size()); + if (otherRpcFields->mFds != nullptr) { + if (rpcFields->mFds == nullptr) { + rpcFields->mFds = std::make_unique<decltype(rpcFields->mFds)::element_type>(); + } + rpcFields->mFds->reserve(otherRpcFields->mFds->size()); + } + for (size_t i = 0; i < otherRpcFields->mObjectPositions.size(); i++) { + const binder_size_t objPos = otherRpcFields->mObjectPositions[i]; + if (offset <= objPos && objPos < offset + len) { + size_t newDataPos = objPos - offset + startPos; + rpcFields->mObjectPositions.push_back(newDataPos); + + mDataPos = newDataPos; + int32_t objectType; + if (status_t status = readInt32(&objectType); status != OK) { + return status; + } + if (objectType != RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR) { + continue; + } + + if (!mAllowFds) { + return FDS_NOT_ALLOWED; + } + + // Read FD, duplicate, and add to list. + int32_t fdIndex; + if (status_t status = readInt32(&fdIndex); status != OK) { + return status; + } + const auto& oldFd = otherRpcFields->mFds->at(fdIndex); + // To match kernel binder behavior, we always dup, even if the + // FD was unowned in the source parcel. + rpcFields->mFds->emplace_back( + base::unique_fd(fcntl(toRawFd(oldFd), F_DUPFD_CLOEXEC, 0))); + // Fixup the index in the data. + mDataPos = newDataPos + 4; + if (status_t status = writeInt32(rpcFields->mFds->size() - 1); status != OK) { + return status; + } + } + } } return err; @@ -584,7 +646,7 @@ void Parcel::restoreAllowFds(bool lastValue) bool Parcel::hasFileDescriptors() const { if (const auto* rpcFields = maybeRpcFields()) { - return false; + return rpcFields->mFds != nullptr && !rpcFields->mFds->empty(); } auto* kernelFields = maybeKernelFields(); if (!kernelFields->mFdsKnown) { @@ -621,34 +683,31 @@ std::vector<sp<IBinder>> Parcel::debugReadAllStrongBinders() const { std::vector<int> Parcel::debugReadAllFileDescriptors() const { std::vector<int> ret; - const auto* kernelFields = maybeKernelFields(); - if (kernelFields == nullptr) { - return ret; - } - - size_t initPosition = dataPosition(); - for (size_t i = 0; i < kernelFields->mObjectsSize; i++) { - binder_size_t offset = kernelFields->mObjects[i]; - const flat_binder_object* flat = - reinterpret_cast<const flat_binder_object*>(mData + offset); - if (flat->hdr.type != BINDER_TYPE_FD) continue; - - setDataPosition(offset); - - int fd = readFileDescriptor(); - LOG_ALWAYS_FATAL_IF(fd == -1); - ret.push_back(fd); + if (const auto* kernelFields = maybeKernelFields()) { + size_t initPosition = dataPosition(); + for (size_t i = 0; i < kernelFields->mObjectsSize; i++) { + binder_size_t offset = kernelFields->mObjects[i]; + const flat_binder_object* flat = + reinterpret_cast<const flat_binder_object*>(mData + offset); + if (flat->hdr.type != BINDER_TYPE_FD) continue; + + setDataPosition(offset); + + int fd = readFileDescriptor(); + LOG_ALWAYS_FATAL_IF(fd == -1); + ret.push_back(fd); + } + setDataPosition(initPosition); + } else if (const auto* rpcFields = maybeRpcFields(); rpcFields && rpcFields->mFds) { + for (const auto& fd : *rpcFields->mFds) { + ret.push_back(toRawFd(fd)); + } } - setDataPosition(initPosition); return ret; } status_t Parcel::hasFileDescriptorsInRange(size_t offset, size_t len, bool* result) const { - const auto* kernelFields = maybeKernelFields(); - if (kernelFields == nullptr) { - return BAD_TYPE; - } if (len > INT32_MAX || offset > INT32_MAX) { // Don't accept size_t values which may have come from an inadvertent conversion from a // negative int. @@ -659,20 +718,33 @@ status_t Parcel::hasFileDescriptorsInRange(size_t offset, size_t len, bool* resu return BAD_VALUE; } *result = false; - for (size_t i = 0; i < kernelFields->mObjectsSize; i++) { - size_t pos = kernelFields->mObjects[i]; - if (pos < offset) continue; - if (pos + sizeof(flat_binder_object) > offset + len) { - if (kernelFields->mObjectsSorted) { + if (const auto* kernelFields = maybeKernelFields()) { + for (size_t i = 0; i < kernelFields->mObjectsSize; i++) { + size_t pos = kernelFields->mObjects[i]; + if (pos < offset) continue; + if (pos + sizeof(flat_binder_object) > offset + len) { + if (kernelFields->mObjectsSorted) { + break; + } else { + continue; + } + } + const flat_binder_object* flat = + reinterpret_cast<const flat_binder_object*>(mData + pos); + if (flat->hdr.type == BINDER_TYPE_FD) { + *result = true; break; - } else { - continue; } } - const flat_binder_object* flat = reinterpret_cast<const flat_binder_object*>(mData + pos); - if (flat->hdr.type == BINDER_TYPE_FD) { - *result = true; - break; + } else if (const auto* rpcFields = maybeRpcFields()) { + for (uint32_t pos : rpcFields->mObjectPositions) { + if (offset <= pos && pos < limit) { + const auto* type = reinterpret_cast<const RpcFields::ObjectType*>(mData + pos); + if (*type == RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR) { + *result = true; + break; + } + } } } return NO_ERROR; @@ -1293,11 +1365,40 @@ status_t Parcel::writeNativeHandle(const native_handle* handle) return err; } -status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) -{ - if (isForRpc()) { - ALOGE("Cannot write file descriptor to remote binder."); - return BAD_TYPE; +status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) { + if (auto* rpcFields = maybeRpcFields()) { + std::variant<base::unique_fd, base::borrowed_fd> fdVariant; + if (takeOwnership) { + fdVariant = base::unique_fd(fd); + } else { + fdVariant = base::borrowed_fd(fd); + } + if (!mAllowFds) { + return FDS_NOT_ALLOWED; + } + switch (rpcFields->mSession->getFileDescriptorTransportMode()) { + case RpcSession::FileDescriptorTransportMode::NONE: { + return FDS_NOT_ALLOWED; + } + case RpcSession::FileDescriptorTransportMode::UNIX: { + if (rpcFields->mFds == nullptr) { + rpcFields->mFds = std::make_unique<decltype(rpcFields->mFds)::element_type>(); + } + size_t dataPos = mDataPos; + if (dataPos > UINT32_MAX) { + return NO_MEMORY; + } + if (status_t err = writeInt32(RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR); err != OK) { + return err; + } + if (status_t err = writeInt32(rpcFields->mFds->size()); err != OK) { + return err; + } + rpcFields->mObjectPositions.push_back(dataPos); + rpcFields->mFds->push_back(std::move(fdVariant)); + return OK; + } + } } flat_binder_object obj; @@ -2038,8 +2139,31 @@ native_handle* Parcel::readNativeHandle() const return h; } -int Parcel::readFileDescriptor() const -{ +int Parcel::readFileDescriptor() const { + if (const auto* rpcFields = maybeRpcFields()) { + if (!std::binary_search(rpcFields->mObjectPositions.begin(), + rpcFields->mObjectPositions.end(), mDataPos)) { + ALOGW("Attempt to read file descriptor from Parcel %p at offset %zu that is not in the " + "object list", + this, mDataPos); + return BAD_TYPE; + } + + int32_t objectType = readInt32(); + if (objectType != RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR) { + return BAD_TYPE; + } + + int32_t fdIndex = readInt32(); + if (rpcFields->mFds == nullptr || fdIndex < 0 || + static_cast<size_t>(fdIndex) >= rpcFields->mFds->size()) { + ALOGE("RPC Parcel contains invalid file descriptor index. index=%d fd_count=%zu", + fdIndex, rpcFields->mFds ? rpcFields->mFds->size() : 0); + return BAD_VALUE; + } + return toRawFd(rpcFields->mFds->at(fdIndex)); + } + const flat_binder_object* flat = readObject(true); if (flat && flat->hdr.type == BINDER_TYPE_FD) { @@ -2049,8 +2173,7 @@ int Parcel::readFileDescriptor() const return BAD_TYPE; } -int Parcel::readParcelFileDescriptor() const -{ +int Parcel::readParcelFileDescriptor() const { int32_t hasComm = readInt32(); int fd = readFileDescriptor(); if (hasComm != 0) { @@ -2270,22 +2393,22 @@ const flat_binder_object* Parcel::readObject(bool nullMetaData) const } void Parcel::closeFileDescriptors() { - auto* kernelFields = maybeKernelFields(); - if (kernelFields == nullptr) { - return; - } - size_t i = kernelFields->mObjectsSize; - if (i > 0) { - //ALOGI("Closing file descriptors for %zu objects...", i); - } - while (i > 0) { - i--; - const flat_binder_object* flat = - reinterpret_cast<flat_binder_object*>(mData + kernelFields->mObjects[i]); - if (flat->hdr.type == BINDER_TYPE_FD) { - //ALOGI("Closing fd: %ld", flat->handle); - close(flat->handle); + if (auto* kernelFields = maybeKernelFields()) { + size_t i = kernelFields->mObjectsSize; + if (i > 0) { + // ALOGI("Closing file descriptors for %zu objects...", i); + } + while (i > 0) { + i--; + const flat_binder_object* flat = + reinterpret_cast<flat_binder_object*>(mData + kernelFields->mObjects[i]); + if (flat->hdr.type == BINDER_TYPE_FD) { + // ALOGI("Closing fd: %ld", flat->handle); + close(flat->handle); + } } + } else if (auto* rpcFields = maybeRpcFields()) { + rpcFields->mFds.reset(); } } @@ -2363,8 +2486,11 @@ void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, const bin scanForFds(); } -void Parcel::rpcSetDataReference(const sp<RpcSession>& session, const uint8_t* data, - size_t dataSize, release_func relFunc) { +status_t Parcel::rpcSetDataReference(const sp<RpcSession>& session, const uint8_t* data, + size_t dataSize, const uint32_t* objectTable, + size_t objectTableSize, + std::vector<base::unique_fd> ancillaryFds, + release_func relFunc) { // this code uses 'mOwner == nullptr' to understand whether it owns memory LOG_ALWAYS_FATAL_IF(relFunc == nullptr, "must provide cleanup function"); @@ -2373,9 +2499,32 @@ void Parcel::rpcSetDataReference(const sp<RpcSession>& session, const uint8_t* d freeData(); markForRpc(session); + auto* rpcFields = maybeRpcFields(); + LOG_ALWAYS_FATAL_IF(rpcFields == nullptr); // guaranteed by markForRpc. + mData = const_cast<uint8_t*>(data); mDataSize = mDataCapacity = dataSize; mOwner = relFunc; + + if (objectTableSize != ancillaryFds.size()) { + ALOGE("objectTableSize=%zu ancillaryFds.size=%zu", objectTableSize, ancillaryFds.size()); + freeData(); // don't leak mData + return BAD_VALUE; + } + + rpcFields->mObjectPositions.reserve(objectTableSize); + for (size_t i = 0; i < objectTableSize; i++) { + rpcFields->mObjectPositions.push_back(objectTable[i]); + } + if (!ancillaryFds.empty()) { + rpcFields->mFds = std::make_unique<decltype(rpcFields->mFds)::element_type>(); + rpcFields->mFds->reserve(ancillaryFds.size()); + for (auto& fd : ancillaryFds) { + rpcFields->mFds->push_back(std::move(fd)); + } + } + + return OK; } void Parcel::print(TextOutput& to, uint32_t /*flags*/) const @@ -2558,6 +2707,9 @@ status_t Parcel::restartWrite(size_t desired) kernelFields->mObjectsSorted = false; kernelFields->mHasFds = false; kernelFields->mFdsKnown = true; + } else if (auto* rpcFields = maybeRpcFields()) { + rpcFields->mObjectPositions.clear(); + rpcFields->mFds.reset(); } mAllowFds = true; @@ -2573,17 +2725,26 @@ status_t Parcel::continueWrite(size_t desired) } auto* kernelFields = maybeKernelFields(); + auto* rpcFields = maybeRpcFields(); // If shrinking, first adjust for any objects that appear // after the new data size. - size_t objectsSize = kernelFields ? kernelFields->mObjectsSize : 0; - if (kernelFields && desired < mDataSize) { + size_t objectsSize = + kernelFields ? kernelFields->mObjectsSize : rpcFields->mObjectPositions.size(); + if (desired < mDataSize) { if (desired == 0) { objectsSize = 0; } else { - while (objectsSize > 0) { - if (kernelFields->mObjects[objectsSize - 1] < desired) break; - objectsSize--; + if (kernelFields) { + while (objectsSize > 0) { + if (kernelFields->mObjects[objectsSize - 1] < desired) break; + objectsSize--; + } + } else { + while (objectsSize > 0) { + if (rpcFields->mObjectPositions[objectsSize - 1] < desired) break; + objectsSize--; + } } } } @@ -2604,7 +2765,7 @@ status_t Parcel::continueWrite(size_t desired) } binder_size_t* objects = nullptr; - if (objectsSize) { + if (kernelFields && objectsSize) { objects = (binder_size_t*)calloc(objectsSize, sizeof(binder_size_t)); if (!objects) { free(data); @@ -2620,6 +2781,12 @@ status_t Parcel::continueWrite(size_t desired) acquireObjects(); kernelFields->mObjectsSize = oldObjectsSize; } + if (rpcFields) { + if (status_t status = truncateRpcObjects(objectsSize); status != OK) { + free(data); + return status; + } + } if (mData) { memcpy(data, mData, mDataSize < desired ? mDataSize : desired); @@ -2678,6 +2845,11 @@ status_t Parcel::continueWrite(size_t desired) kernelFields->mNextObjectHint = 0; kernelFields->mObjectsSorted = false; } + if (rpcFields) { + if (status_t status = truncateRpcObjects(objectsSize); status != OK) { + return status; + } + } // We own the data, so we can just do a realloc(). if (desired > mDataCapacity) { @@ -2734,6 +2906,35 @@ status_t Parcel::continueWrite(size_t desired) return NO_ERROR; } +status_t Parcel::truncateRpcObjects(size_t newObjectsSize) { + auto* rpcFields = maybeRpcFields(); + if (newObjectsSize == 0) { + rpcFields->mObjectPositions.clear(); + if (rpcFields->mFds) { + rpcFields->mFds->clear(); + } + return OK; + } + while (rpcFields->mObjectPositions.size() > newObjectsSize) { + uint32_t pos = rpcFields->mObjectPositions.back(); + rpcFields->mObjectPositions.pop_back(); + const auto type = *reinterpret_cast<const RpcFields::ObjectType*>(mData + pos); + if (type == RpcFields::TYPE_NATIVE_FILE_DESCRIPTOR) { + const auto fdIndex = + *reinterpret_cast<const int32_t*>(mData + pos + sizeof(RpcFields::ObjectType)); + if (rpcFields->mFds == nullptr || fdIndex < 0 || + static_cast<size_t>(fdIndex) >= rpcFields->mFds->size()) { + ALOGE("RPC Parcel contains invalid file descriptor index. index=%d fd_count=%zu", + fdIndex, rpcFields->mFds ? rpcFields->mFds->size() : 0); + return BAD_VALUE; + } + // In practice, this always removes the last element. + rpcFields->mFds->erase(rpcFields->mFds->begin() + fdIndex); + } + } + return OK; +} + void Parcel::initState() { LOG_ALLOC("Parcel %p: initState", this); diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp index 528341ed26..3bb21adde8 100644 --- a/libs/binder/RpcServer.cpp +++ b/libs/binder/RpcServer.cpp @@ -122,6 +122,14 @@ void RpcServer::setProtocolVersion(uint32_t version) { mProtocolVersion = version; } +void RpcServer::setSupportedFileDescriptorTransportModes( + const std::vector<RpcSession::FileDescriptorTransportMode>& modes) { + mSupportedFileDescriptorTransportModes.reset(); + for (RpcSession::FileDescriptorTransportMode mode : modes) { + mSupportedFileDescriptorTransportModes.set(static_cast<size_t>(mode)); + } +} + void RpcServer::setRootObject(const sp<IBinder>& binder) { std::lock_guard<std::mutex> _l(mLock); mRootObjectFactory = nullptr; @@ -292,7 +300,7 @@ void RpcServer::establishConnection(sp<RpcServer>&& server, base::unique_fd clie if (status == OK) { iovec iov{&header, sizeof(header)}; status = client->interruptableReadFully(server->mShutdownTrigger.get(), &iov, 1, - std::nullopt); + std::nullopt, /*enableAncillaryFds=*/false); if (status != OK) { ALOGE("Failed to read ID for client connecting to RPC server: %s", statusToString(status).c_str()); @@ -307,7 +315,7 @@ void RpcServer::establishConnection(sp<RpcServer>&& server, base::unique_fd clie sessionId.resize(header.sessionIdSize); iovec iov{sessionId.data(), sessionId.size()}; status = client->interruptableReadFully(server->mShutdownTrigger.get(), &iov, 1, - std::nullopt); + std::nullopt, /*enableAncillaryFds=*/false); if (status != OK) { ALOGE("Failed to read session ID for client connecting to RPC server: %s", statusToString(status).c_str()); @@ -338,7 +346,7 @@ void RpcServer::establishConnection(sp<RpcServer>&& server, base::unique_fd clie iovec iov{&response, sizeof(response)}; status = client->interruptableWriteFully(server->mShutdownTrigger.get(), &iov, 1, - std::nullopt); + std::nullopt, nullptr); if (status != OK) { ALOGE("Failed to send new session response: %s", statusToString(status).c_str()); // still need to cleanup before we can return @@ -396,6 +404,17 @@ void RpcServer::establishConnection(sp<RpcServer>&& server, base::unique_fd clie session->setMaxIncomingThreads(server->mMaxThreads); if (!session->setProtocolVersion(protocolVersion)) return; + if (server->mSupportedFileDescriptorTransportModes.test( + header.fileDescriptorTransportMode)) { + session->setFileDescriptorTransportMode( + static_cast<RpcSession::FileDescriptorTransportMode>( + header.fileDescriptorTransportMode)); + } else { + ALOGE("Rejecting connection: FileDescriptorTransportMode is not supported: %hhu", + header.fileDescriptorTransportMode); + return; + } + // if null, falls back to server root sp<IBinder> sessionSpecificRoot; if (server->mRootObjectFactory != nullptr) { diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp index 7ba08edec4..41842a7d84 100644 --- a/libs/binder/RpcSession.cpp +++ b/libs/binder/RpcSession.cpp @@ -129,6 +129,14 @@ std::optional<uint32_t> RpcSession::getProtocolVersion() { return mProtocolVersion; } +void RpcSession::setFileDescriptorTransportMode(FileDescriptorTransportMode mode) { + mFileDescriptorTransportMode = mode; +} + +RpcSession::FileDescriptorTransportMode RpcSession::getFileDescriptorTransportMode() { + return mFileDescriptorTransportMode; +} + status_t RpcSession::setupUnixDomainClient(const char* path) { return setupSocketClient(UnixSocketAddress(path)); } @@ -606,6 +614,7 @@ status_t RpcSession::initAndAddConnection(unique_fd fd, const std::vector<uint8_ RpcConnectionHeader header{ .version = mProtocolVersion.value_or(RPC_WIRE_PROTOCOL_VERSION), .options = 0, + .fileDescriptorTransportMode = static_cast<uint8_t>(mFileDescriptorTransportMode), .sessionIdSize = static_cast<uint16_t>(sessionId.size()), }; @@ -614,8 +623,8 @@ status_t RpcSession::initAndAddConnection(unique_fd fd, const std::vector<uint8_ } iovec headerIov{&header, sizeof(header)}; - auto sendHeaderStatus = - server->interruptableWriteFully(mShutdownTrigger.get(), &headerIov, 1, std::nullopt); + auto sendHeaderStatus = server->interruptableWriteFully(mShutdownTrigger.get(), &headerIov, 1, + std::nullopt, nullptr); if (sendHeaderStatus != OK) { ALOGE("Could not write connection header to socket: %s", statusToString(sendHeaderStatus).c_str()); @@ -625,8 +634,9 @@ status_t RpcSession::initAndAddConnection(unique_fd fd, const std::vector<uint8_ if (sessionId.size() > 0) { iovec sessionIov{const_cast<void*>(static_cast<const void*>(sessionId.data())), sessionId.size()}; - auto sendSessionIdStatus = server->interruptableWriteFully(mShutdownTrigger.get(), - &sessionIov, 1, std::nullopt); + auto sendSessionIdStatus = + server->interruptableWriteFully(mShutdownTrigger.get(), &sessionIov, 1, + std::nullopt, nullptr); if (sendSessionIdStatus != OK) { ALOGE("Could not write session ID ('%s') to socket: %s", base::HexString(sessionId.data(), sessionId.size()).c_str(), diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp index 419df86a4c..2b1d0e5bf6 100644 --- a/libs/binder/RpcState.cpp +++ b/libs/binder/RpcState.cpp @@ -21,6 +21,7 @@ #include <android-base/hex.h> #include <android-base/macros.h> #include <android-base/scopeguard.h> +#include <android-base/stringprintf.h> #include <binder/BpBinder.h> #include <binder/IPCThreadState.h> #include <binder/RpcServer.h> @@ -36,6 +37,7 @@ namespace android { using base::ScopeGuard; +using base::StringPrintf; #if RPC_FLAKE_PRONE void rpcMaybeWaitToFlake() { @@ -50,6 +52,15 @@ void rpcMaybeWaitToFlake() { } #endif +static bool enableAncillaryFds(RpcSession::FileDescriptorTransportMode mode) { + switch (mode) { + case RpcSession::FileDescriptorTransportMode::NONE: + return false; + case RpcSession::FileDescriptorTransportMode::UNIX: + return true; + } +} + RpcState::RpcState() {} RpcState::~RpcState() {} @@ -310,9 +321,11 @@ RpcState::CommandData::CommandData(size_t size) : mSize(size) { mData.reset(new (std::nothrow) uint8_t[size]); } -status_t RpcState::rpcSend(const sp<RpcSession::RpcConnection>& connection, - const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>>& altPoll) { +status_t RpcState::rpcSend( + const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session, + const char* what, iovec* iovs, int niovs, + const std::optional<android::base::function_ref<status_t()>>& altPoll, + const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) { for (int i = 0; i < niovs; i++) { LOG_RPC_DETAIL("Sending %s (part %d of %d) on RpcTransport %p: %s", what, i + 1, niovs, connection->rpcTransport.get(), @@ -321,7 +334,8 @@ status_t RpcState::rpcSend(const sp<RpcSession::RpcConnection>& connection, if (status_t status = connection->rpcTransport->interruptableWriteFully(session->mShutdownTrigger.get(), - iovs, niovs, altPoll); + iovs, niovs, altPoll, + ancillaryFds); status != OK) { LOG_RPC_DETAIL("Failed to write %s (%d iovs) on RpcTransport %p, error: %s", what, niovs, connection->rpcTransport.get(), statusToString(status).c_str()); @@ -334,9 +348,9 @@ status_t RpcState::rpcSend(const sp<RpcSession::RpcConnection>& connection, status_t RpcState::rpcRec(const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs) { - if (status_t status = - connection->rpcTransport->interruptableReadFully(session->mShutdownTrigger.get(), - iovs, niovs, std::nullopt); + if (status_t status = connection->rpcTransport->interruptableReadFully( + session->mShutdownTrigger.get(), iovs, niovs, std::nullopt, + enableAncillaryFds(session->getFileDescriptorTransportMode())); status != OK) { LOG_RPC_DETAIL("Failed to read %s (%d iovs) on RpcTransport %p, error: %s", what, niovs, connection->rpcTransport.get(), statusToString(status).c_str()); @@ -449,20 +463,12 @@ status_t RpcState::getSessionId(const sp<RpcSession::RpcConnection>& connection, status_t RpcState::transact(const sp<RpcSession::RpcConnection>& connection, const sp<IBinder>& binder, uint32_t code, const Parcel& data, const sp<RpcSession>& session, Parcel* reply, uint32_t flags) { - if (!data.isForRpc()) { - ALOGE("Refusing to send RPC with parcel not crafted for RPC call on binder %p code " - "%" PRIu32, - binder.get(), code); - return BAD_TYPE; - } - - if (data.objectsCount() != 0) { - ALOGE("Parcel at %p has attached objects but is being used in an RPC call on binder %p " - "code %" PRIu32, - &data, binder.get(), code); - return BAD_TYPE; + std::string errorMsg; + if (status_t status = validateParcel(session, data, &errorMsg); status != OK) { + ALOGE("Refusing to send RPC on binder %p code %" PRIu32 ": Parcel %p failed validation: %s", + binder.get(), code, &data, errorMsg.c_str()); + return status; } - uint64_t address; if (status_t status = onBinderLeaving(session, binder, &address); status != OK) return status; @@ -494,9 +500,11 @@ status_t RpcState::transactAddress(const sp<RpcSession::RpcConnection>& connecti } } - // objectTable always empty for now. Will be populated from `data` soon. - std::vector<uint32_t> objectTable; - Span<const uint32_t> objectTableSpan = {objectTable.data(), objectTable.size()}; + auto* rpcFields = data.maybeRpcFields(); + LOG_ALWAYS_FATAL_IF(rpcFields == nullptr); + + Span<const uint32_t> objectTableSpan = Span<const uint32_t>{rpcFields->mObjectPositions.data(), + rpcFields->mObjectPositions.size()}; uint32_t bodySize; LOG_ALWAYS_FATAL_IF(__builtin_add_overflow(sizeof(RpcWireTransaction), data.dataSize(), @@ -532,25 +540,25 @@ status_t RpcState::transactAddress(const sp<RpcSession::RpcConnection>& connecti {const_cast<uint8_t*>(data.data()), data.dataSize()}, objectTableSpan.toIovec(), }; - if (status_t status = rpcSend(connection, session, "transaction", iovs, arraysize(iovs), - [&] { - if (waitUs > kWaitLogUs) { - ALOGE("Cannot send command, trying to process pending " - "refcounts. Waiting %zuus. Too " - "many oneway calls?", - waitUs); - } - - if (waitUs > 0) { - usleep(waitUs); - waitUs = std::min(kWaitMaxUs, waitUs * 2); - } else { - waitUs = 1; - } - - return drainCommands(connection, session, - CommandType::CONTROL_ONLY); - }); + if (status_t status = rpcSend( + connection, session, "transaction", iovs, arraysize(iovs), + [&] { + if (waitUs > kWaitLogUs) { + ALOGE("Cannot send command, trying to process pending refcounts. Waiting " + "%zuus. Too many oneway calls?", + waitUs); + } + + if (waitUs > 0) { + usleep(waitUs); + waitUs = std::min(kWaitMaxUs, waitUs * 2); + } else { + waitUs = 1; + } + + return drainCommands(connection, session, CommandType::CONTROL_ONLY); + }, + rpcFields->mFds.get()); status != OK) { // TODO(b/167966510): need to undo onBinderLeaving - we know the // refcount isn't successfully transferred. @@ -617,18 +625,37 @@ status_t RpcState::waitForReply(const sp<RpcSession::RpcConnection>& connection, if (status_t status = rpcRec(connection, session, "reply body", iovs, arraysize(iovs)); status != OK) return status; + + // Check if the reply came with any ancillary data. + std::vector<base::unique_fd> pendingFds; + if (status_t status = connection->rpcTransport->consumePendingAncillaryData(&pendingFds); + status != OK) { + return status; + } + if (rpcReply.status != OK) return rpcReply.status; Span<const uint8_t> parcelSpan = {data.data(), data.size()}; + Span<const uint32_t> objectTableSpan; if (session->getProtocolVersion().value() >= RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE) { Span<const uint8_t> objectTableBytes = parcelSpan.splitOff(rpcReply.parcelDataSize); - LOG_ALWAYS_FATAL_IF(objectTableBytes.size > 0, "Non-empty object table not supported yet."); + std::optional<Span<const uint32_t>> maybeSpan = + objectTableBytes.reinterpret<const uint32_t>(); + if (!maybeSpan.has_value()) { + ALOGE("Bad object table size inferred from RpcWireReply. Saw bodySize=%" PRId32 + " sizeofHeader=%zu parcelSize=%" PRId32 " objectTableBytesSize=%zu. Terminating!", + command.bodySize, rpcReplyWireSize, rpcReply.parcelDataSize, + objectTableBytes.size); + return BAD_VALUE; + } + objectTableSpan = *maybeSpan; } data.release(); - reply->rpcSetDataReference(session, parcelSpan.data, parcelSpan.size, cleanup_reply_data); - return OK; + return reply->rpcSetDataReference(session, parcelSpan.data, parcelSpan.size, + objectTableSpan.data, objectTableSpan.size, + std::move(pendingFds), cleanup_reply_data); } status_t RpcState::sendDecStrongToTarget(const sp<RpcSession::RpcConnection>& connection, @@ -842,14 +869,31 @@ processTransactInternalTailCall: reply.markForRpc(session); if (replyStatus == OK) { + // Check if the transaction came with any ancillary data. + std::vector<base::unique_fd> pendingFds; + if (status_t status = connection->rpcTransport->consumePendingAncillaryData(&pendingFds); + status != OK) { + return status; + } + Span<const uint8_t> parcelSpan = {transaction->data, transactionData.size() - offsetof(RpcWireTransaction, data)}; - if (session->getProtocolVersion().value() >= + Span<const uint32_t> objectTableSpan; + if (session->getProtocolVersion().value() > RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE) { Span<const uint8_t> objectTableBytes = parcelSpan.splitOff(transaction->parcelDataSize); - LOG_ALWAYS_FATAL_IF(objectTableBytes.size > 0, - "Non-empty object table not supported yet."); + std::optional<Span<const uint32_t>> maybeSpan = + objectTableBytes.reinterpret<const uint32_t>(); + if (!maybeSpan.has_value()) { + ALOGE("Bad object table size inferred from RpcWireTransaction. Saw bodySize=%zu " + "sizeofHeader=%zu parcelSize=%" PRId32 + " objectTableBytesSize=%zu. Terminating!", + transactionData.size(), sizeof(RpcWireTransaction), + transaction->parcelDataSize, objectTableBytes.size); + return BAD_VALUE; + } + objectTableSpan = *maybeSpan; } Parcel data; @@ -857,47 +901,50 @@ processTransactInternalTailCall: // only holds onto it for the duration of this function call. Parcel will be // deleted before the 'transactionData' object. - data.rpcSetDataReference(session, parcelSpan.data, parcelSpan.size, - do_nothing_to_transact_data); + replyStatus = data.rpcSetDataReference(session, parcelSpan.data, parcelSpan.size, + objectTableSpan.data, objectTableSpan.size, + std::move(pendingFds), do_nothing_to_transact_data); - if (target) { - bool origAllowNested = connection->allowNested; - connection->allowNested = !oneway; + if (replyStatus == OK) { + if (target) { + bool origAllowNested = connection->allowNested; + connection->allowNested = !oneway; - replyStatus = target->transact(transaction->code, data, &reply, transaction->flags); + replyStatus = target->transact(transaction->code, data, &reply, transaction->flags); - connection->allowNested = origAllowNested; - } else { - LOG_RPC_DETAIL("Got special transaction %u", transaction->code); + connection->allowNested = origAllowNested; + } else { + LOG_RPC_DETAIL("Got special transaction %u", transaction->code); - switch (transaction->code) { - case RPC_SPECIAL_TRANSACT_GET_MAX_THREADS: { - replyStatus = reply.writeInt32(session->getMaxIncomingThreads()); - break; - } - case RPC_SPECIAL_TRANSACT_GET_SESSION_ID: { - // for client connections, this should always report the value - // originally returned from the server, so this is asserting - // that it exists - replyStatus = reply.writeByteVector(session->mId); - break; - } - default: { - sp<RpcServer> server = session->server(); - if (server) { - switch (transaction->code) { - case RPC_SPECIAL_TRANSACT_GET_ROOT: { - sp<IBinder> root = session->mSessionSpecificRootObject - ?: server->getRootObject(); - replyStatus = reply.writeStrongBinder(root); - break; - } - default: { - replyStatus = UNKNOWN_TRANSACTION; + switch (transaction->code) { + case RPC_SPECIAL_TRANSACT_GET_MAX_THREADS: { + replyStatus = reply.writeInt32(session->getMaxIncomingThreads()); + break; + } + case RPC_SPECIAL_TRANSACT_GET_SESSION_ID: { + // for client connections, this should always report the value + // originally returned from the server, so this is asserting + // that it exists + replyStatus = reply.writeByteVector(session->mId); + break; + } + default: { + sp<RpcServer> server = session->server(); + if (server) { + switch (transaction->code) { + case RPC_SPECIAL_TRANSACT_GET_ROOT: { + sp<IBinder> root = session->mSessionSpecificRootObject + ?: server->getRootObject(); + replyStatus = reply.writeStrongBinder(root); + break; + } + default: { + replyStatus = UNKNOWN_TRANSACTION; + } } + } else { + ALOGE("Special command sent, but no server object attached."); } - } else { - ALOGE("Special command sent, but no server object attached."); } } } @@ -969,11 +1016,22 @@ processTransactInternalTailCall: replyStatus = flushExcessBinderRefs(session, addr, target); } + std::string errorMsg; + if (status_t status = validateParcel(session, reply, &errorMsg); status != OK) { + ALOGE("Reply Parcel failed validation: %s", errorMsg.c_str()); + // Forward the error to the client of the transaction. + reply.freeData(); + reply.markForRpc(session); + replyStatus = status; + } + + auto* rpcFields = reply.maybeRpcFields(); + LOG_ALWAYS_FATAL_IF(rpcFields == nullptr); + const size_t rpcReplyWireSize = RpcWireReply::wireSize(session->getProtocolVersion().value()); - // objectTable always empty for now. Will be populated from `reply` soon. - std::vector<uint32_t> objectTable; - Span<const uint32_t> objectTableSpan = {objectTable.data(), objectTable.size()}; + Span<const uint32_t> objectTableSpan = Span<const uint32_t>{rpcFields->mObjectPositions.data(), + rpcFields->mObjectPositions.size()}; uint32_t bodySize; LOG_ALWAYS_FATAL_IF(__builtin_add_overflow(rpcReplyWireSize, reply.dataSize(), &bodySize) || @@ -998,7 +1056,8 @@ processTransactInternalTailCall: {const_cast<uint8_t*>(reply.data()), reply.dataSize()}, objectTableSpan.toIovec(), }; - return rpcSend(connection, session, "reply", iovs, arraysize(iovs), std::nullopt); + return rpcSend(connection, session, "reply", iovs, arraysize(iovs), std::nullopt, + rpcFields->mFds.get()); } status_t RpcState::processDecStrong(const sp<RpcSession::RpcConnection>& connection, @@ -1055,6 +1114,50 @@ status_t RpcState::processDecStrong(const sp<RpcSession::RpcConnection>& connect return OK; } +status_t RpcState::validateParcel(const sp<RpcSession>& session, const Parcel& parcel, + std::string* errorMsg) { + auto* rpcFields = parcel.maybeRpcFields(); + if (rpcFields == nullptr) { + *errorMsg = "Parcel not crafted for RPC call"; + return BAD_TYPE; + } + + if (rpcFields->mSession != session) { + *errorMsg = "Parcel's session doesn't match"; + return BAD_TYPE; + } + + uint32_t protocolVersion = session->getProtocolVersion().value(); + if (protocolVersion < RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE && + !rpcFields->mObjectPositions.empty()) { + *errorMsg = StringPrintf("Parcel has attached objects but the session's protocol version " + "(%" PRIu32 ") is too old, must be at least %" PRIu32, + protocolVersion, + RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE); + return BAD_VALUE; + } + + if (rpcFields->mFds && !rpcFields->mFds->empty()) { + switch (session->getFileDescriptorTransportMode()) { + case RpcSession::FileDescriptorTransportMode::NONE: + *errorMsg = + "Parcel has file descriptors, but no file descriptor transport is enabled"; + return FDS_NOT_ALLOWED; + case RpcSession::FileDescriptorTransportMode::UNIX: { + constexpr size_t kMaxFdsPerMsg = 253; + if (rpcFields->mFds->size() > kMaxFdsPerMsg) { + *errorMsg = StringPrintf("Too many file descriptors in Parcel for unix " + "domain socket: %zu (max is %zu)", + rpcFields->mFds->size(), kMaxFdsPerMsg); + return BAD_VALUE; + } + } + } + } + + return OK; +} + sp<IBinder> RpcState::tryEraseNode(std::map<uint64_t, BinderNode>::iterator& it) { sp<IBinder> ref; diff --git a/libs/binder/RpcState.h b/libs/binder/RpcState.h index 9cbe187444..08a314ebef 100644 --- a/libs/binder/RpcState.h +++ b/libs/binder/RpcState.h @@ -181,7 +181,9 @@ private: [[nodiscard]] status_t rpcSend( const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>>& altPoll); + const std::optional<android::base::function_ref<status_t()>>& altPoll, + const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds = + nullptr); [[nodiscard]] status_t rpcRec(const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs); @@ -201,6 +203,10 @@ private: const sp<RpcSession>& session, const RpcWireHeader& command); + // Whether `parcel` is compatible with `session`. + [[nodiscard]] static status_t validateParcel(const sp<RpcSession>& session, + const Parcel& parcel, std::string* errorMsg); + struct BinderNode { // Two cases: // A - local binder we are serving diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp index f9b73fce21..d9059e9a34 100644 --- a/libs/binder/RpcTransportRaw.cpp +++ b/libs/binder/RpcTransportRaw.cpp @@ -18,6 +18,7 @@ #include <log/log.h> #include <poll.h> +#include <stddef.h> #include <binder/RpcTransportRaw.h> @@ -28,6 +29,9 @@ namespace android { namespace { +// Linux kernel supports up to 253 (from SCM_MAX_FD) for unix sockets. +constexpr size_t kMaxFdsPerMsg = 253; + // RpcTransport with TLS disabled. class RpcTransportRaw : public RpcTransport { public: @@ -85,15 +89,7 @@ public: bool havePolled = false; while (true) { - msghdr msg{ - .msg_iov = iovs, - // posix uses int, glibc uses size_t. niovs is a - // non-negative int and can be cast to either. - .msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(niovs), - }; - ssize_t processSize = - TEMP_FAILURE_RETRY(sendOrReceiveFun(mSocket.get(), &msg, MSG_NOSIGNAL)); - + ssize_t processSize = sendOrReceiveFun(iovs, niovs); if (processSize < 0) { int savedErrno = errno; @@ -145,20 +141,133 @@ public: status_t interruptableWriteFully( FdTrigger* fdTrigger, iovec* iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>>& altPoll) override { - return interruptableReadOrWrite(fdTrigger, iovs, niovs, sendmsg, "sendmsg", POLLOUT, - altPoll); + const std::optional<android::base::function_ref<status_t()>>& altPoll, + const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) + override { + bool sentFds = false; + auto send = [&](iovec* iovs, int niovs) -> ssize_t { + if (ancillaryFds != nullptr && !ancillaryFds->empty() && !sentFds) { + if (ancillaryFds->size() > kMaxFdsPerMsg) { + // This shouldn't happen because we check the FD count in RpcState. + ALOGE("Saw too many file descriptors in RpcTransportCtxRaw: %zu (max is %zu). " + "Aborting session.", + ancillaryFds->size(), kMaxFdsPerMsg); + errno = EINVAL; + return -1; + } + + // CMSG_DATA is not necessarily aligned, so we copy the FDs into a buffer and then + // use memcpy. + int fds[kMaxFdsPerMsg]; + for (size_t i = 0; i < ancillaryFds->size(); i++) { + fds[i] = std::visit([](const auto& fd) { return fd.get(); }, + ancillaryFds->at(i)); + } + const size_t fdsByteSize = sizeof(int) * ancillaryFds->size(); + + alignas(struct cmsghdr) char msgControlBuf[CMSG_SPACE(sizeof(int) * kMaxFdsPerMsg)]; + + msghdr msg{ + .msg_iov = iovs, + .msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(niovs), + .msg_control = msgControlBuf, + .msg_controllen = sizeof(msgControlBuf), + }; + + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(fdsByteSize); + memcpy(CMSG_DATA(cmsg), fds, fdsByteSize); + + msg.msg_controllen = CMSG_SPACE(fdsByteSize); + + ssize_t processedSize = TEMP_FAILURE_RETRY( + sendmsg(mSocket.get(), &msg, MSG_NOSIGNAL | MSG_CMSG_CLOEXEC)); + if (processedSize > 0) { + sentFds = true; + } + return processedSize; + } + + msghdr msg{ + .msg_iov = iovs, + // posix uses int, glibc uses size_t. niovs is a + // non-negative int and can be cast to either. + .msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(niovs), + }; + return TEMP_FAILURE_RETRY(sendmsg(mSocket.get(), &msg, MSG_NOSIGNAL)); + }; + return interruptableReadOrWrite(fdTrigger, iovs, niovs, send, "sendmsg", POLLOUT, altPoll); } status_t interruptableReadFully( FdTrigger* fdTrigger, iovec* iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>>& altPoll) override { - return interruptableReadOrWrite(fdTrigger, iovs, niovs, recvmsg, "recvmsg", POLLIN, - altPoll); + const std::optional<android::base::function_ref<status_t()>>& altPoll, + bool enableAncillaryFds) override { + auto recv = [&](iovec* iovs, int niovs) -> ssize_t { + if (enableAncillaryFds) { + int fdBuffer[kMaxFdsPerMsg]; + alignas(struct cmsghdr) char msgControlBuf[CMSG_SPACE(sizeof(fdBuffer))]; + + msghdr msg{ + .msg_iov = iovs, + .msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(niovs), + .msg_control = msgControlBuf, + .msg_controllen = sizeof(msgControlBuf), + }; + ssize_t processSize = + TEMP_FAILURE_RETRY(recvmsg(mSocket.get(), &msg, MSG_NOSIGNAL)); + if (processSize < 0) { + return -1; + } + + for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + // NOTE: It is tempting to reinterpret_cast, but cmsg(3) explicitly asks + // application devs to memcpy the data to ensure memory alignment. + size_t dataLen = cmsg->cmsg_len - CMSG_LEN(0); + memcpy(fdBuffer, CMSG_DATA(cmsg), dataLen); + size_t fdCount = dataLen / sizeof(int); + for (size_t i = 0; i < fdCount; i++) { + mFdsPendingRead.emplace_back(fdBuffer[i]); + } + break; + } + } + + if (msg.msg_flags & MSG_CTRUNC) { + ALOGE("msg was truncated. Aborting session."); + errno = EPIPE; + return -1; + } + + return processSize; + } + msghdr msg{ + .msg_iov = iovs, + // posix uses int, glibc uses size_t. niovs is a + // non-negative int and can be cast to either. + .msg_iovlen = static_cast<decltype(msg.msg_iovlen)>(niovs), + }; + return TEMP_FAILURE_RETRY(recvmsg(mSocket.get(), &msg, MSG_NOSIGNAL)); + }; + return interruptableReadOrWrite(fdTrigger, iovs, niovs, recv, "recvmsg", POLLIN, altPoll); + } + + status_t consumePendingAncillaryData(std::vector<base::unique_fd>* fds) override { + fds->reserve(fds->size() + mFdsPendingRead.size()); + for (auto& fd : mFdsPendingRead) { + fds->emplace_back(std::move(fd)); + } + mFdsPendingRead.clear(); + return OK; } private: base::unique_fd mSocket; + std::vector<base::unique_fd> mFdsPendingRead; }; // RpcTransportCtx with TLS disabled. diff --git a/libs/binder/RpcTransportTls.cpp b/libs/binder/RpcTransportTls.cpp index ad5cb0fc56..77831114be 100644 --- a/libs/binder/RpcTransportTls.cpp +++ b/libs/binder/RpcTransportTls.cpp @@ -282,10 +282,18 @@ public: status_t pollRead(void) override; status_t interruptableWriteFully( FdTrigger* fdTrigger, iovec* iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>>& altPoll) override; + const std::optional<android::base::function_ref<status_t()>>& altPoll, + const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) + override; status_t interruptableReadFully( FdTrigger* fdTrigger, iovec* iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>>& altPoll) override; + const std::optional<android::base::function_ref<status_t()>>& altPoll, + bool enableAncillaryFds) override; + + status_t consumePendingAncillaryData(std::vector<base::unique_fd>* fds) override { + (void)fds; + return OK; + } private: android::base::unique_fd mSocket; @@ -313,7 +321,10 @@ status_t RpcTransportTls::pollRead(void) { status_t RpcTransportTls::interruptableWriteFully( FdTrigger* fdTrigger, iovec* iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>>& altPoll) { + const std::optional<android::base::function_ref<status_t()>>& altPoll, + const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) { + (void)ancillaryFds; + MAYBE_WAIT_IN_FLAKE_MODE; if (niovs < 0) return BAD_VALUE; @@ -356,7 +367,10 @@ status_t RpcTransportTls::interruptableWriteFully( status_t RpcTransportTls::interruptableReadFully( FdTrigger* fdTrigger, iovec* iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>>& altPoll) { + const std::optional<android::base::function_ref<status_t()>>& altPoll, + bool enableAncillaryFds) { + (void)enableAncillaryFds; + MAYBE_WAIT_IN_FLAKE_MODE; if (niovs < 0) return BAD_VALUE; diff --git a/libs/binder/RpcWireFormat.h b/libs/binder/RpcWireFormat.h index 7e2aa79ab3..13989e5bf5 100644 --- a/libs/binder/RpcWireFormat.h +++ b/libs/binder/RpcWireFormat.h @@ -45,7 +45,8 @@ static_assert(sizeof(RpcWireAddress) == sizeof(uint64_t)); struct RpcConnectionHeader { uint32_t version; // maximum supported by caller uint8_t options; - uint8_t reservered[9]; + uint8_t fileDescriptorTransportMode; + uint8_t reservered[8]; // Follows is sessionIdSize bytes. // if size is 0, this is requesting a new session. uint16_t sessionIdSize; diff --git a/libs/binder/Utils.h b/libs/binder/Utils.h index 7dcb70e6fd..37c1262c9b 100644 --- a/libs/binder/Utils.h +++ b/libs/binder/Utils.h @@ -60,6 +60,17 @@ struct Span { size = offset; return rest; } + + // Returns nullopt if the byte size of `this` isn't evenly divisible by sizeof(U). + template <typename U> + std::optional<Span<U>> reinterpret() const { + // Only allow casting from bytes for simplicity. + static_assert(std::is_same_v<std::remove_const_t<T>, uint8_t>); + if (size % sizeof(U) != 0) { + return std::nullopt; + } + return Span<U>{reinterpret_cast<U*>(data), size / sizeof(U)}; + } }; } // namespace android diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h index 0345a5d8e1..68a4aef362 100644 --- a/libs/binder/include/binder/Parcel.h +++ b/libs/binder/include/binder/Parcel.h @@ -608,8 +608,11 @@ private: size_t ipcObjectsCount() const; void ipcSetDataReference(const uint8_t* data, size_t dataSize, const binder_size_t* objects, size_t objectsCount, release_func relFunc); - void rpcSetDataReference(const sp<RpcSession>& session, const uint8_t* data, size_t dataSize, - release_func relFunc); + // Takes ownership even when an error is returned. + status_t rpcSetDataReference(const sp<RpcSession>& session, const uint8_t* data, + size_t dataSize, const uint32_t* objectTable, + size_t objectTableSize, std::vector<base::unique_fd> ancillaryFds, + release_func relFunc); status_t finishWrite(size_t len); void releaseObjects(); @@ -620,6 +623,7 @@ private: status_t restartWrite(size_t desired); // Set the capacity to `desired`, truncating the Parcel if necessary. status_t continueWrite(size_t desired); + status_t truncateRpcObjects(size_t newObjectsSize); status_t writePointer(uintptr_t val); status_t readPointer(uintptr_t *pArg) const; uintptr_t readPointer() const; @@ -1279,6 +1283,23 @@ private: // Should always be non-null. const sp<RpcSession> mSession; + + enum ObjectType : int32_t { + TYPE_BINDER_NULL = 0, + TYPE_BINDER = 1, + // FD to be passed via native transport (Trusty IPC or UNIX domain socket). + TYPE_NATIVE_FILE_DESCRIPTOR = 2, + }; + + // Sorted. + std::vector<uint32_t> mObjectPositions; + + // File descriptors referenced by the parcel data. Should be indexed + // using the offsets in the parcel data. Don't assume the list is in the + // same order as `mObjectPositions`. + // + // Boxed to save space. Lazy allocated. + std::unique_ptr<std::vector<std::variant<base::unique_fd, base::borrowed_fd>>> mFds; }; std::variant<KernelFields, RpcFields> mVariantFields; diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h index dba8dd64e9..a5bb8713a0 100644 --- a/libs/binder/include/binder/RpcServer.h +++ b/libs/binder/include/binder/RpcServer.h @@ -114,6 +114,15 @@ public: void setProtocolVersion(uint32_t version); /** + * Set the supported transports for sending and receiving file descriptors. + * + * Clients will propose a mode when connecting. If the mode is not in the + * provided list, the connection will be rejected. + */ + void setSupportedFileDescriptorTransportModes( + const std::vector<RpcSession::FileDescriptorTransportMode>& modes); + + /** * The root object can be retrieved by any client, without any * authentication. TODO(b/183988761) * @@ -193,6 +202,9 @@ private: const std::unique_ptr<RpcTransportCtx> mCtx; size_t mMaxThreads = 1; std::optional<uint32_t> mProtocolVersion; + // A mode is supported if the N'th bit is on, where N is the mode enum's value. + std::bitset<8> mSupportedFileDescriptorTransportModes = + (1 << static_cast<unsigned long>(RpcSession::FileDescriptorTransportMode::NONE)); base::unique_fd mServer; // socket we are accepting sessions on std::mutex mLock; // for below diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h index 7d5d481f83..b98b0ebea9 100644 --- a/libs/binder/include/binder/RpcSession.h +++ b/libs/binder/include/binder/RpcSession.h @@ -95,6 +95,18 @@ public: [[nodiscard]] bool setProtocolVersion(uint32_t version); std::optional<uint32_t> getProtocolVersion(); + enum class FileDescriptorTransportMode : uint8_t { + NONE = 0, + // Send file descriptors via unix domain socket ancillary data. + UNIX = 1, + }; + + /** + * Set the transport for sending and receiving file descriptors. + */ + void setFileDescriptorTransportMode(FileDescriptorTransportMode mode); + FileDescriptorTransportMode getFileDescriptorTransportMode(); + /** * This should be called once per thread, matching 'join' in the remote * process. @@ -314,7 +326,7 @@ private: // For a more complicated case, the client might itself open up a thread to // serve calls to the server at all times (e.g. if it hosts a callback) - wp<RpcServer> mForServer; // maybe null, for client sessions + wp<RpcServer> mForServer; // maybe null, for client sessions sp<WaitForShutdownListener> mShutdownListener; // used for client sessions wp<EventListener> mEventListener; // mForServer if server, mShutdownListener if client @@ -333,6 +345,7 @@ private: size_t mMaxIncomingThreads = 0; size_t mMaxOutgoingThreads = kDefaultMaxOutgoingThreads; std::optional<uint32_t> mProtocolVersion; + FileDescriptorTransportMode mFileDescriptorTransportMode = FileDescriptorTransportMode::NONE; std::condition_variable mAvailableConnectionCv; // for mWaitingThreads diff --git a/libs/binder/include/binder/RpcTransport.h b/libs/binder/include/binder/RpcTransport.h index ee4b5483be..80f5a32479 100644 --- a/libs/binder/include/binder/RpcTransport.h +++ b/libs/binder/include/binder/RpcTransport.h @@ -22,6 +22,8 @@ #include <memory> #include <optional> #include <string> +#include <variant> +#include <vector> #include <android-base/function_ref.h> #include <android-base/unique_fd.h> @@ -61,16 +63,31 @@ public: * to read/write data. If this returns an error, that error is returned from * this function. * + * ancillaryFds - FDs to be sent via UNIX domain dockets or Trusty IPC. + * + * enableAncillaryFds - Whether to check for FDs in the ancillary data and + * queue for them for use in `consumePendingAncillaryData`. If false and FDs + * are received, they will be silently dropped (and closed) by the operating + * system. + * * Return: * OK - succeeded in completely processing 'size' * error - interrupted (failure or trigger) */ [[nodiscard]] virtual status_t interruptableWriteFully( FdTrigger *fdTrigger, iovec *iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>> &altPoll) = 0; + const std::optional<android::base::function_ref<status_t()>> &altPoll, + const std::vector<std::variant<base::unique_fd, base::borrowed_fd>> *ancillaryFds) = 0; [[nodiscard]] virtual status_t interruptableReadFully( FdTrigger *fdTrigger, iovec *iovs, int niovs, - const std::optional<android::base::function_ref<status_t()>> &altPoll) = 0; + const std::optional<android::base::function_ref<status_t()>> &altPoll, + bool enableAncillaryFds) = 0; + + // Consume the ancillary data that was accumulated from previous + // `interruptableReadFully` calls. + // + // Appends to `fds`. + virtual status_t consumePendingAncillaryData(std::vector<base::unique_fd> *fds) = 0; protected: RpcTransport() = default; diff --git a/libs/binder/tests/IBinderRpcTest.aidl b/libs/binder/tests/IBinderRpcTest.aidl index 2deea82cf9..b15a2251ef 100644 --- a/libs/binder/tests/IBinderRpcTest.aidl +++ b/libs/binder/tests/IBinderRpcTest.aidl @@ -67,4 +67,8 @@ interface IBinderRpcTest { void scheduleShutdown(); void useKernelBinderCallingId(); + + ParcelFileDescriptor echoAsFile(@utf8InCpp String content); + + ParcelFileDescriptor concatFiles(in List<ParcelFileDescriptor> files); } diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp index 194553f872..3b1fc82226 100644 --- a/libs/binder/tests/binderRpcTest.cpp +++ b/libs/binder/tests/binderRpcTest.cpp @@ -54,6 +54,7 @@ #include "../RpcSocketAddress.h" // for testing preconnected clients #include "../RpcState.h" // for debugging #include "../vm_sockets.h" // for VMADDR_* +#include "utils/Errors.h" using namespace std::chrono_literals; using namespace std::placeholders; @@ -93,6 +94,21 @@ static inline std::unique_ptr<RpcTransportCtxFactory> newFactory( } } +// Create an FD that returns `contents` when read. +static base::unique_fd mockFileDescriptor(std::string contents) { + android::base::unique_fd readFd, writeFd; + CHECK(android::base::Pipe(&readFd, &writeFd)) << strerror(errno); + std::thread([writeFd = std::move(writeFd), contents = std::move(contents)]() { + signal(SIGPIPE, SIG_IGN); // ignore possible SIGPIPE from the write + if (!WriteStringToFd(contents, writeFd)) { + int savedErrno = errno; + EXPECT_EQ(EPIPE, savedErrno) + << "mockFileDescriptor write failed: " << strerror(savedErrno); + } + }).detach(); + return readFd; +} + TEST(BinderRpcParcel, EntireParcelFormatted) { Parcel p; p.writeInt32(3); @@ -329,6 +345,23 @@ public: (void)IPCThreadState::self()->getCallingPid(); return Status::ok(); } + + Status echoAsFile(const std::string& content, android::os::ParcelFileDescriptor* out) override { + out->reset(mockFileDescriptor(content)); + return Status::ok(); + } + + Status concatFiles(const std::vector<android::os::ParcelFileDescriptor>& files, + android::os::ParcelFileDescriptor* out) override { + std::string acc; + for (const auto& file : files) { + std::string result; + CHECK(android::base::ReadFdToString(file.get(), &result)); + acc.append(result); + } + out->reset(mockFileDescriptor(acc)); + return Status::ok(); + } }; sp<IBinder> MyBinderRpcTest::mHeldBinder; @@ -379,6 +412,9 @@ public: mCustomExitStatusCheck = std::move(f); } + // Kill the process. Avoid if possible. Shutdown gracefully via an RPC instead. + void terminate() { kill(mPid, SIGTERM); } + private: std::function<void(int wstatus)> mCustomExitStatusCheck; pid_t mPid = 0; @@ -448,10 +484,10 @@ struct BinderRpcTestProcessSession { BinderRpcTestProcessSession(BinderRpcTestProcessSession&&) = default; ~BinderRpcTestProcessSession() { - EXPECT_NE(nullptr, rootIface); - if (rootIface == nullptr) return; - if (!expectAlreadyShutdown) { + EXPECT_NE(nullptr, rootIface); + if (rootIface == nullptr) return; + std::vector<int32_t> remoteCounts; // calling over any sessions counts across all sessions EXPECT_OK(rootIface->countBinders(&remoteCounts)); @@ -517,8 +553,28 @@ public: size_t numSessions = 1; size_t numIncomingConnections = 0; size_t numOutgoingConnections = SIZE_MAX; + RpcSession::FileDescriptorTransportMode clientFileDescriptorTransportMode = + RpcSession::FileDescriptorTransportMode::NONE; + std::vector<RpcSession::FileDescriptorTransportMode> + serverSupportedFileDescriptorTransportModes = { + RpcSession::FileDescriptorTransportMode::NONE}; + + // If true, connection failures will result in `ProcessSession::sessions` being empty + // instead of a fatal error. + bool allowConnectFailure = false; }; + SocketType socketType() const { return std::get<0>(GetParam()); } + RpcSecurity rpcSecurity() const { return std::get<1>(GetParam()); } + uint32_t clientVersion() const { return std::get<2>(GetParam()); } + uint32_t serverVersion() const { return std::get<3>(GetParam()); } + + // Whether the test params support sending FDs in parcels. + bool supportsFdTransport() const { + return clientVersion() >= 1 && serverVersion() >= 1 && rpcSecurity() != RpcSecurity::TLS && + (socketType() == SocketType::PRECONNECTED || socketType() == SocketType::UNIX); + } + static inline std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info) { auto [type, security, clientVersion, serverVersion] = info.param; return PrintToString(type) + "_" + newFactory(security)->toCString() + "_clientV" + @@ -578,6 +634,8 @@ public: server->setProtocolVersion(serverVersion); server->setMaxThreads(options.numThreads); + server->setSupportedFileDescriptorTransportModes( + options.serverSupportedFileDescriptorTransportModes); unsigned int outPort = 0; @@ -655,6 +713,7 @@ public: CHECK(session->setProtocolVersion(clientVersion)); session->setMaxIncomingThreads(options.numIncomingConnections); session->setMaxOutgoingThreads(options.numOutgoingConnections); + session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode); switch (socketType) { case SocketType::PRECONNECTED: @@ -674,6 +733,10 @@ public: default: LOG_ALWAYS_FATAL("Unknown socket type"); } + if (options.allowConnectFailure && status != OK) { + ret.sessions.clear(); + break; + } CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status); ret.sessions.push_back({session, session->getRootObject()}); } @@ -722,7 +785,7 @@ public: }), }; - ret.rootBinder = ret.proc.sessions.at(0).root; + ret.rootBinder = ret.proc.sessions.empty() ? nullptr : ret.proc.sessions.at(0).root; ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder); return ret; @@ -973,7 +1036,13 @@ TEST_P(BinderRpc, RepeatRootObject) { } TEST_P(BinderRpc, NestedTransactions) { - auto proc = createRpcTestSocketServerProcess({}); + auto proc = createRpcTestSocketServerProcess({ + // Enable FD support because it uses more stack space and so represents + // something closer to a worst case scenario. + .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX, + .serverSupportedFileDescriptorTransportModes = + {RpcSession::FileDescriptorTransportMode::UNIX}, + }); auto nastyNester = sp<MyBinderRpcTest>::make(); EXPECT_OK(proc.rootIface->nestMe(nastyNester, 10)); @@ -1389,6 +1458,143 @@ TEST_P(BinderRpc, UseKernelBinderCallingId) { proc.expectAlreadyShutdown = true; } +TEST_P(BinderRpc, FileDescriptorTransportRejectNone) { + auto proc = createRpcTestSocketServerProcess({ + .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::NONE, + .serverSupportedFileDescriptorTransportModes = + {RpcSession::FileDescriptorTransportMode::UNIX}, + .allowConnectFailure = true, + }); + EXPECT_TRUE(proc.proc.sessions.empty()) << "session connections should have failed"; + proc.proc.host.terminate(); + proc.proc.host.setCustomExitStatusCheck([](int wstatus) { + EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGTERM) + << "server process failed incorrectly: " << WaitStatusToString(wstatus); + }); + proc.expectAlreadyShutdown = true; +} + +TEST_P(BinderRpc, FileDescriptorTransportRejectUnix) { + auto proc = createRpcTestSocketServerProcess({ + .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX, + .serverSupportedFileDescriptorTransportModes = + {RpcSession::FileDescriptorTransportMode::NONE}, + .allowConnectFailure = true, + }); + EXPECT_TRUE(proc.proc.sessions.empty()) << "session connections should have failed"; + proc.proc.host.terminate(); + proc.proc.host.setCustomExitStatusCheck([](int wstatus) { + EXPECT_TRUE(WIFSIGNALED(wstatus) && WTERMSIG(wstatus) == SIGTERM) + << "server process failed incorrectly: " << WaitStatusToString(wstatus); + }); + proc.expectAlreadyShutdown = true; +} + +TEST_P(BinderRpc, FileDescriptorTransportOptionalUnix) { + auto proc = createRpcTestSocketServerProcess({ + .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::NONE, + .serverSupportedFileDescriptorTransportModes = + {RpcSession::FileDescriptorTransportMode::NONE, + RpcSession::FileDescriptorTransportMode::UNIX}, + }); + + android::os::ParcelFileDescriptor out; + auto status = proc.rootIface->echoAsFile("hello", &out); + EXPECT_EQ(status.transactionError(), FDS_NOT_ALLOWED) << status; +} + +TEST_P(BinderRpc, ReceiveFile) { + auto proc = createRpcTestSocketServerProcess({ + .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX, + .serverSupportedFileDescriptorTransportModes = + {RpcSession::FileDescriptorTransportMode::UNIX}, + }); + + android::os::ParcelFileDescriptor out; + auto status = proc.rootIface->echoAsFile("hello", &out); + if (!supportsFdTransport()) { + EXPECT_EQ(status.transactionError(), BAD_VALUE) << status; + return; + } + ASSERT_TRUE(status.isOk()) << status; + + std::string result; + CHECK(android::base::ReadFdToString(out.get(), &result)); + EXPECT_EQ(result, "hello"); +} + +TEST_P(BinderRpc, SendFiles) { + auto proc = createRpcTestSocketServerProcess({ + .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX, + .serverSupportedFileDescriptorTransportModes = + {RpcSession::FileDescriptorTransportMode::UNIX}, + }); + + std::vector<android::os::ParcelFileDescriptor> files; + files.emplace_back(android::os::ParcelFileDescriptor(mockFileDescriptor("123"))); + files.emplace_back(android::os::ParcelFileDescriptor(mockFileDescriptor("a"))); + files.emplace_back(android::os::ParcelFileDescriptor(mockFileDescriptor("b"))); + files.emplace_back(android::os::ParcelFileDescriptor(mockFileDescriptor("cd"))); + + android::os::ParcelFileDescriptor out; + auto status = proc.rootIface->concatFiles(files, &out); + if (!supportsFdTransport()) { + EXPECT_EQ(status.transactionError(), BAD_VALUE) << status; + return; + } + ASSERT_TRUE(status.isOk()) << status; + + std::string result; + CHECK(android::base::ReadFdToString(out.get(), &result)); + EXPECT_EQ(result, "123abcd"); +} + +TEST_P(BinderRpc, SendMaxFiles) { + if (!supportsFdTransport()) { + GTEST_SKIP() << "Would fail trivially (which is tested by BinderRpc::SendFiles)"; + } + + auto proc = createRpcTestSocketServerProcess({ + .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX, + .serverSupportedFileDescriptorTransportModes = + {RpcSession::FileDescriptorTransportMode::UNIX}, + }); + + std::vector<android::os::ParcelFileDescriptor> files; + for (int i = 0; i < 253; i++) { + files.emplace_back(android::os::ParcelFileDescriptor(mockFileDescriptor("a"))); + } + + android::os::ParcelFileDescriptor out; + auto status = proc.rootIface->concatFiles(files, &out); + ASSERT_TRUE(status.isOk()) << status; + + std::string result; + CHECK(android::base::ReadFdToString(out.get(), &result)); + EXPECT_EQ(result, std::string(253, 'a')); +} + +TEST_P(BinderRpc, SendTooManyFiles) { + if (!supportsFdTransport()) { + GTEST_SKIP() << "Would fail trivially (which is tested by BinderRpc::SendFiles)"; + } + + auto proc = createRpcTestSocketServerProcess({ + .clientFileDescriptorTransportMode = RpcSession::FileDescriptorTransportMode::UNIX, + .serverSupportedFileDescriptorTransportModes = + {RpcSession::FileDescriptorTransportMode::UNIX}, + }); + + std::vector<android::os::ParcelFileDescriptor> files; + for (int i = 0; i < 254; i++) { + files.emplace_back(android::os::ParcelFileDescriptor(mockFileDescriptor("a"))); + } + + android::os::ParcelFileDescriptor out; + auto status = proc.rootIface->concatFiles(files, &out); + EXPECT_EQ(status.transactionError(), BAD_VALUE) << status; +} + TEST_P(BinderRpc, WorksWithLibbinderNdkPing) { auto proc = createRpcTestSocketServerProcess({}); @@ -1759,7 +1965,7 @@ public: std::string message(kMessage); iovec messageIov{message.data(), message.size()}; auto status = serverTransport->interruptableWriteFully(fdTrigger, &messageIov, 1, - std::nullopt); + std::nullopt, nullptr); if (status != OK) return AssertionFailure() << statusToString(status); return AssertionSuccess(); } @@ -1794,7 +2000,7 @@ public: iovec readMessageIov{readMessage.data(), readMessage.size()}; status_t readStatus = mClientTransport->interruptableReadFully(mFdTrigger.get(), &readMessageIov, 1, - std::nullopt); + std::nullopt, false); if (readStatus != OK) { return AssertionFailure() << statusToString(readStatus); } @@ -2002,8 +2208,8 @@ TEST_P(RpcTransportTest, Trigger) { auto serverPostConnect = [&](RpcTransport* serverTransport, FdTrigger* fdTrigger) { std::string message(RpcTransportTestUtils::kMessage); iovec messageIov{message.data(), message.size()}; - auto status = - serverTransport->interruptableWriteFully(fdTrigger, &messageIov, 1, std::nullopt); + auto status = serverTransport->interruptableWriteFully(fdTrigger, &messageIov, 1, + std::nullopt, nullptr); if (status != OK) return AssertionFailure() << statusToString(status); { @@ -2014,7 +2220,8 @@ TEST_P(RpcTransportTest, Trigger) { } iovec msg2Iov{msg2.data(), msg2.size()}; - status = serverTransport->interruptableWriteFully(fdTrigger, &msg2Iov, 1, std::nullopt); + status = serverTransport->interruptableWriteFully(fdTrigger, &msg2Iov, 1, std::nullopt, + nullptr); if (status != DEAD_OBJECT) return AssertionFailure() << "When FdTrigger is shut down, interruptableWriteFully " "should return DEAD_OBJECT, but it is " |