blob: cf1f2528fa0c2e07c3c122705f5aff02c2e4e76b [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/manager.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 {
auto ManagerBehavior(std::string_view binder_dir, test_helper::Poker ready) {
auto fd_and_mapping = OpenBinderAndMap(binder_dir);
// Register as the context manager
ASSERT_THAT(ioctl(fd_and_mapping.fd_.get(), BINDER_SET_CONTEXT_MGR, 0), SyscallSucceeds());
EnterLooper(fd_and_mapping.fd_);
ready.poke();
// This Binder context manager does only two things for the rest of its runtime:
// * accepts registration of the service
// * (failing if the service has ever been registered before)
// * hands out the handle to the service
// * (failing if the service has not before been registered)
std::optional<uint32_t> service_provider_handle = std::nullopt;
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: {
struct binder_transaction_data& transaction_data =
*(struct binder_transaction_data*)cursor;
switch (transaction_data.code) {
case kAddService: {
ASSERT_EQ(transaction_data.offsets_size, sizeof(binder_size_t));
ASSERT_EQ(transaction_data.data_size, sizeof(struct flat_binder_object));
binder_size_t offset = *(binder_size_t*)(transaction_data.data.ptr.offsets);
const struct flat_binder_object* obj =
(const struct flat_binder_object*)(transaction_data.data.ptr.buffer + offset);
ASSERT_EQ(obj->hdr.type, BINDER_TYPE_HANDLE);
ASSERT_EQ(service_provider_handle, std::nullopt);
service_provider_handle = obj->handle;
AcquireWriteBuffer acquire_write_buffer = {
.command = BC_ACQUIRE,
.handle = obj->handle,
};
struct binder_write_read acquire_write_read = {
.write_size = sizeof(acquire_write_buffer),
.write_buffer = (binder_uintptr_t)&acquire_write_buffer,
};
int acquire_result =
ioctl(fd_and_mapping.fd_.get(), BINDER_WRITE_READ, &acquire_write_read);
ASSERT_THAT(acquire_result, SyscallSucceeds());
ReplyWriteBuffer reply_write_buffer = {
.command = BC_REPLY,
.data =
{
.target = {.ptr = 0},
.cookie = transaction_data.cookie,
.code = transaction_data.code,
.flags = TF_STATUS_CODE,
.data_size = sizeof(int),
.offsets_size = 0,
.data =
{
.ptr =
{
.buffer = (binder_uintptr_t)&acquire_result,
.offsets = (binder_uintptr_t) nullptr,
},
},
},
};
struct binder_write_read reply_write_read = {
.write_size = sizeof(reply_write_buffer),
.write_consumed = 0,
.write_buffer = (binder_uintptr_t)&reply_write_buffer,
.read_size = 0,
.read_consumed = 0,
.read_buffer = 0,
};
ASSERT_THAT(ioctl(fd_and_mapping.fd_.get(), BINDER_WRITE_READ, &reply_write_read),
SyscallSucceeds());
break;
}
case kGetService: {
ASSERT_TRUE(service_provider_handle.has_value());
struct flat_binder_object send_handle_obj = {
.hdr =
{
.type = BINDER_TYPE_HANDLE,
},
.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS,
.handle = *service_provider_handle,
.cookie = 0,
};
binder_size_t offset = 0;
ReplyWriteBuffer reply_write_buffer = {
.command = BC_REPLY,
.data =
{
.target =
{
.handle = transaction_data.target.handle,
},
.cookie = transaction_data.cookie,
.code = transaction_data.code,
.flags = TF_ACCEPT_FDS,
.data_size = sizeof(send_handle_obj),
.offsets_size = sizeof(offset),
.data =
{
.ptr =
{
.buffer = (binder_uintptr_t)&send_handle_obj,
.offsets = (binder_uintptr_t)&offset,
},
},
},
};
struct binder_write_read reply_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, &reply_write_read),
SyscallSucceeds());
ASSERT_EQ(reply_write_read.read_consumed, 0ull);
break;
}
case kServiceSendFd:
// NOTE(https://fxbug.dev/441447806): control flow passes through here when the
// client has permission to impersonate the provider and the invocation of the
// impersonation transaction succeeds.
break;
default:
FAIL() << "Unexpected transaction_data.code " << transaction_data.code << "!";
break;
}
cursor += sizeof(transaction_data);
break;
}
case BR_REPLY:
FAIL() << "Unimplemented at this time; perhaps will be used in a future test.";
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> ManagerProcess(
std::string_view binder_dir,
fit::function<pid_t(test_helper::ForkHelper&, fit::closure)> spawn_manager,
test_helper::Poker ready) {
std::unique_ptr<test_helper::ForkHelper> fork_helper =
std::make_unique<test_helper::ForkHelper>();
fork_helper->OnlyWaitForForkedChildren();
pid_t pid = spawn_manager(*fork_helper, [binder_dir, ready = std::move(ready)]() mutable {
ManagerBehavior(binder_dir, std::move(ready));
});
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