blob: a847ed5592e6af81fcac92b9e7ea2dd0eb4be5b3 [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/starnix/tests/syscalls/cpp/binder/provider.h"
#include <fcntl.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include <linux/android/binder.h>
#include "src/starnix/tests/syscalls/cpp/binder/common.h"
#include "src/starnix/tests/syscalls/cpp/binder_helper.h"
#include "src/starnix/tests/syscalls/cpp/syscall_matchers.h"
#include "src/starnix/tests/syscalls/cpp/test_helper.h"
namespace starnix_binder {
namespace {
void ProviderBehavior(
std::string_view binder_dir, test_helper::Poker ready,
fit::function<void(std::string_view)> validate_client_secctx_seen_by_provider) {
auto fd_and_mapping = OpenBinderAndMap(binder_dir);
struct flat_binder_object add_service_object = {
.hdr =
{
.type = BINDER_TYPE_BINDER,
},
.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS | FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
.binder = (binder_uintptr_t) nullptr,
.cookie = 0,
};
binder_size_t add_service_offset = 0;
TransactionWriteBuffer add_service_write_buffer = {
.command = BC_TRANSACTION,
.data =
{
.target =
{
.handle = kServiceManagerHandle,
},
.cookie = 0,
.code = kAddService,
.flags = TF_ROOT_OBJECT,
.data_size = sizeof(add_service_object),
.offsets_size = sizeof(add_service_offset),
.data =
{
.ptr =
{
.buffer = (binder_uintptr_t)&add_service_object,
.offsets = (binder_uintptr_t)&add_service_offset,
},
},
},
};
std::array<int32_t, 32> add_service_read_buffer = {};
struct binder_write_read add_service_write_read = {
.write_size = sizeof(add_service_write_buffer),
.write_consumed = 0,
.write_buffer = (binder_uintptr_t)&add_service_write_buffer,
.read_size = sizeof(add_service_read_buffer),
.read_consumed = 0,
.read_buffer = (binder_uintptr_t)add_service_read_buffer.data(),
};
ASSERT_THAT(ioctl(fd_and_mapping.fd_.get(), BINDER_WRITE_READ, &add_service_write_read),
SyscallSucceeds());
// TODO: https://fxbug.dev/441451502 - it looks like Linux and Starnix may differ in what
// they populate into add_service_read_buffer at this point; Linux looks like it puts in
// BR_NOOP, BR_INCREFS, BR_ACQUIRE, and BR_TRANSACTION_COMPLETE, but Starnix looks like it
// puts in just BR_ACQUIRE. This is worth a deeper look.
EnterLooper(fd_and_mapping.fd_);
ready.poke();
while (true) {
std::array<uint32_t, 32> read_buffer = {};
struct binder_write_read write_read = {
.read_size = sizeof(read_buffer),
.read_consumed = 0,
.read_buffer = (binder_uintptr_t)read_buffer.data(),
};
ASSERT_THAT(ioctl(fd_and_mapping.fd_.get(), BINDER_WRITE_READ, &write_read), SyscallSucceeds());
binder_uintptr_t cursor = (binder_uintptr_t)read_buffer.data();
binder_uintptr_t limit = cursor + write_read.read_consumed;
while (cursor < limit) {
binder_driver_return_protocol returned = *(binder_driver_return_protocol*)(cursor);
cursor += sizeof(binder_driver_return_protocol);
switch (returned) {
case BR_NOOP:
case BR_TRANSACTION_COMPLETE:
break;
case BR_INCREFS:
case BR_ACQUIRE:
case BR_RELEASE:
case BR_DECREFS:
cursor += sizeof(struct binder_ptr_cookie);
break;
case BR_TRANSACTION:
FAIL() << "Unimplemented - maybe we will need this in a future test?";
break;
case BR_TRANSACTION_SEC_CTX: {
const struct binder_transaction_data_secctx& transaction_data_secctx =
*(struct binder_transaction_data_secctx*)cursor;
validate_client_secctx_seen_by_provider((const char*)transaction_data_secctx.secctx);
if (transaction_data_secctx.transaction_data.code == kServiceSendFd &&
transaction_data_secctx.transaction_data.flags != TF_ONE_WAY) {
struct binder_fd_object send_fd_obj = {
.hdr =
{
.type = BINDER_TYPE_FD,
},
.pad_flags =
0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS | FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
.fd = static_cast<uint32_t>(fd_and_mapping.fd_.get()),
.cookie = transaction_data_secctx.transaction_data.cookie,
};
binder_size_t send_fd_offset = 0;
ReplyWriteBuffer reply_write_buffer = {
.command = BC_REPLY,
.data =
{
.target =
{
.handle = transaction_data_secctx.transaction_data.target.handle,
},
.cookie = transaction_data_secctx.transaction_data.cookie,
.code = transaction_data_secctx.transaction_data.code,
.flags = TF_ACCEPT_FDS,
.data_size = sizeof(send_fd_obj),
.offsets_size = sizeof(send_fd_offset),
.data =
{
.ptr =
{
.buffer = (binder_uintptr_t)&send_fd_obj,
.offsets = (binder_uintptr_t)&send_fd_offset,
},
},
},
};
struct binder_write_read send_fd_write_read = {
.write_size = sizeof(reply_write_buffer),
.write_buffer = (binder_uintptr_t)&reply_write_buffer,
};
ASSERT_THAT(ioctl(fd_and_mapping.fd_.get(), BINDER_WRITE_READ, &send_fd_write_read),
SyscallSucceeds());
}
cursor += sizeof(transaction_data_secctx);
break;
}
case BR_REPLY:
cursor += sizeof(struct binder_transaction_data);
break;
case BR_DEAD_BINDER:
case BR_FAILED_REPLY:
case BR_DEAD_REPLY:
break;
case BR_ERROR:
cursor += sizeof(uint32_t);
break;
default:
FAIL() << "Unexpected binder_driver_return_protocol value " << returned << "!";
break;
}
}
}
}
} // namespace
fit::deferred_action<fit::closure> ProviderProcess(
std::string_view binder_dir,
fit::function<pid_t(test_helper::ForkHelper&, fit::closure)> spawn_provider,
test_helper::Poker ready,
fit::function<void(std::string_view)> validate_client_secctx_seen_by_provider) {
std::unique_ptr<test_helper::ForkHelper> fork_helper =
std::make_unique<test_helper::ForkHelper>();
fork_helper->OnlyWaitForForkedChildren();
pid_t pid = spawn_provider(*fork_helper,
[binder_dir, ready = std::move(ready),
validate_client_secctx_seen_by_provider =
std::move(validate_client_secctx_seen_by_provider)]() mutable {
ProviderBehavior(binder_dir, std::move(ready),
std::move(validate_client_secctx_seen_by_provider));
});
return fit::defer(fit::closure([pid, fork_helper = std::move(fork_helper)] {
ASSERT_THAT(kill(pid, SIGKILL), SyscallSucceeds());
fork_helper->ExpectSignal(SIGKILL);
ASSERT_TRUE(fork_helper->WaitForChildren());
}));
}
} // namespace starnix_binder