mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 11:04:59 +00:00

Instead of getting credentials from Process::current(), we now require that they be provided as input to the various VFS functions. This ensures that an atomic set of credentials is used throughout an entire VFS operation.
527 lines
16 KiB
C++
527 lines
16 KiB
C++
/*
|
|
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Singleton.h>
|
|
#include <AK/StringBuilder.h>
|
|
#include <Kernel/API/POSIX/errno.h>
|
|
#include <Kernel/Debug.h>
|
|
#include <Kernel/FileSystem/OpenFileDescription.h>
|
|
#include <Kernel/FileSystem/VirtualFileSystem.h>
|
|
#include <Kernel/Locking/Mutex.h>
|
|
#include <Kernel/Locking/MutexProtected.h>
|
|
#include <Kernel/Net/LocalSocket.h>
|
|
#include <Kernel/Process.h>
|
|
#include <Kernel/StdLib.h>
|
|
#include <Kernel/UnixTypes.h>
|
|
#include <LibC/sys/ioctl_numbers.h>
|
|
|
|
namespace Kernel {
|
|
|
|
static Singleton<MutexProtected<LocalSocket::List>> s_list;
|
|
|
|
static MutexProtected<LocalSocket::List>& all_sockets()
|
|
{
|
|
return *s_list;
|
|
}
|
|
|
|
void LocalSocket::for_each(Function<void(LocalSocket const&)> callback)
|
|
{
|
|
all_sockets().for_each_shared([&](auto const& socket) {
|
|
callback(socket);
|
|
});
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::try_for_each(Function<ErrorOr<void>(LocalSocket const&)> callback)
|
|
{
|
|
return all_sockets().with_shared([&](auto const& sockets) -> ErrorOr<void> {
|
|
for (auto& socket : sockets)
|
|
TRY(callback(socket));
|
|
return {};
|
|
});
|
|
}
|
|
|
|
ErrorOr<NonnullLockRefPtr<LocalSocket>> LocalSocket::try_create(int type)
|
|
{
|
|
auto client_buffer = TRY(DoubleBuffer::try_create("LocalSocket: Client buffer"sv));
|
|
auto server_buffer = TRY(DoubleBuffer::try_create("LocalSocket: Server buffer"sv));
|
|
return adopt_nonnull_lock_ref_or_enomem(new (nothrow) LocalSocket(type, move(client_buffer), move(server_buffer)));
|
|
}
|
|
|
|
ErrorOr<SocketPair> LocalSocket::try_create_connected_pair(int type)
|
|
{
|
|
auto socket = TRY(LocalSocket::try_create(type));
|
|
auto description1 = TRY(OpenFileDescription::try_create(*socket));
|
|
|
|
TRY(socket->try_set_path("[socketpair]"sv));
|
|
|
|
socket->set_acceptor(Process::current());
|
|
socket->set_connected(true);
|
|
socket->set_connect_side_role(Role::Connected);
|
|
socket->set_role(Role::Accepted);
|
|
|
|
auto description2 = TRY(OpenFileDescription::try_create(*socket));
|
|
|
|
return SocketPair { move(description1), move(description2) };
|
|
}
|
|
|
|
LocalSocket::LocalSocket(int type, NonnullOwnPtr<DoubleBuffer> client_buffer, NonnullOwnPtr<DoubleBuffer> server_buffer)
|
|
: Socket(AF_LOCAL, type, 0)
|
|
, m_for_client(move(client_buffer))
|
|
, m_for_server(move(server_buffer))
|
|
{
|
|
auto& current_process = Process::current();
|
|
auto current_process_credentials = current_process.credentials();
|
|
m_prebind_uid = current_process_credentials->euid();
|
|
m_prebind_gid = current_process_credentials->egid();
|
|
m_prebind_mode = 0666;
|
|
|
|
m_for_client->set_unblock_callback([this]() {
|
|
evaluate_block_conditions();
|
|
});
|
|
m_for_server->set_unblock_callback([this]() {
|
|
evaluate_block_conditions();
|
|
});
|
|
|
|
all_sockets().with_exclusive([&](auto& list) {
|
|
list.append(*this);
|
|
});
|
|
|
|
dbgln_if(LOCAL_SOCKET_DEBUG, "LocalSocket({}) created with type={}", this, type);
|
|
}
|
|
|
|
LocalSocket::~LocalSocket()
|
|
{
|
|
all_sockets().with_exclusive([&](auto& list) {
|
|
list.remove(*this);
|
|
});
|
|
}
|
|
|
|
void LocalSocket::get_local_address(sockaddr* address, socklen_t* address_size)
|
|
{
|
|
auto& address_un = *reinterpret_cast<sockaddr_un*>(address);
|
|
address_un = {
|
|
.sun_family = AF_UNIX,
|
|
.sun_path = {},
|
|
};
|
|
|
|
if (!m_path || m_path->is_empty()) {
|
|
*address_size = sizeof(address_un.sun_family);
|
|
return;
|
|
}
|
|
|
|
size_t bytes_to_copy = min(m_path->length() + 1, min(static_cast<size_t>(*address_size), sizeof(address_un.sun_path)));
|
|
memcpy(address_un.sun_path, m_path->characters(), bytes_to_copy);
|
|
*address_size = sizeof(address_un.sun_family) + bytes_to_copy;
|
|
}
|
|
|
|
void LocalSocket::get_peer_address(sockaddr* address, socklen_t* address_size)
|
|
{
|
|
get_local_address(address, address_size);
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::bind(Userspace<sockaddr const*> user_address, socklen_t address_size)
|
|
{
|
|
VERIFY(setup_state() == SetupState::Unstarted);
|
|
if (address_size > sizeof(sockaddr_un))
|
|
return set_so_error(EINVAL);
|
|
|
|
sockaddr_un address = {};
|
|
SOCKET_TRY(copy_from_user(&address, user_address, address_size));
|
|
|
|
if (address.sun_family != AF_LOCAL)
|
|
return set_so_error(EINVAL);
|
|
|
|
auto path = SOCKET_TRY(KString::try_create(StringView { address.sun_path, strnlen(address.sun_path, sizeof(address.sun_path)) }));
|
|
dbgln_if(LOCAL_SOCKET_DEBUG, "LocalSocket({}) bind({})", this, path);
|
|
|
|
mode_t mode = S_IFSOCK | (m_prebind_mode & 0777);
|
|
UidAndGid owner { m_prebind_uid, m_prebind_gid };
|
|
auto result = VirtualFileSystem::the().open(Process::current().credentials(), path->view(), O_CREAT | O_EXCL | O_NOFOLLOW_NOERROR, mode, Process::current().current_directory(), owner);
|
|
if (result.is_error()) {
|
|
if (result.error().code() == EEXIST)
|
|
return set_so_error(EADDRINUSE);
|
|
return result.release_error();
|
|
}
|
|
|
|
auto file = move(result.value());
|
|
auto inode = file->inode();
|
|
|
|
VERIFY(inode);
|
|
if (!inode->bind_socket(*this))
|
|
return set_so_error(EADDRINUSE);
|
|
|
|
m_inode = inode;
|
|
|
|
m_path = move(path);
|
|
m_bound = true;
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::connect(OpenFileDescription& description, Userspace<sockaddr const*> user_address, socklen_t address_size)
|
|
{
|
|
VERIFY(!m_bound);
|
|
|
|
if (address_size > sizeof(sockaddr_un))
|
|
return set_so_error(EINVAL);
|
|
|
|
sockaddr_un address = {};
|
|
SOCKET_TRY(copy_from_user(&address, user_address, address_size));
|
|
|
|
if (address.sun_family != AF_LOCAL)
|
|
return set_so_error(EINVAL);
|
|
|
|
if (is_connected())
|
|
return set_so_error(EISCONN);
|
|
|
|
auto path = SOCKET_TRY(KString::try_create(StringView { address.sun_path, strnlen(address.sun_path, sizeof(address.sun_path)) }));
|
|
dbgln_if(LOCAL_SOCKET_DEBUG, "LocalSocket({}) connect({})", this, *path);
|
|
|
|
auto file = SOCKET_TRY(VirtualFileSystem::the().open(Process::current().credentials(), path->view(), O_RDWR, 0, Process::current().current_directory()));
|
|
auto inode = file->inode();
|
|
m_inode = inode;
|
|
|
|
VERIFY(inode);
|
|
|
|
auto peer = inode->bound_socket();
|
|
if (!peer)
|
|
return set_so_error(ECONNREFUSED);
|
|
|
|
m_path = move(path);
|
|
|
|
VERIFY(m_connect_side_fd == &description);
|
|
set_connect_side_role(Role::Connecting);
|
|
|
|
auto result = peer->queue_connection_from(*this);
|
|
if (result.is_error()) {
|
|
set_connect_side_role(Role::None);
|
|
return result;
|
|
}
|
|
|
|
if (is_connected()) {
|
|
set_connect_side_role(Role::Connected);
|
|
return {};
|
|
}
|
|
|
|
auto unblock_flags = Thread::OpenFileDescriptionBlocker::BlockFlags::None;
|
|
if (Thread::current()->block<Thread::ConnectBlocker>({}, description, unblock_flags).was_interrupted()) {
|
|
set_connect_side_role(Role::None);
|
|
return set_so_error(EINTR);
|
|
}
|
|
|
|
dbgln_if(LOCAL_SOCKET_DEBUG, "LocalSocket({}) connect({}) status is {}", this, *m_path, to_string(setup_state()));
|
|
|
|
if (!has_flag(unblock_flags, Thread::OpenFileDescriptionBlocker::BlockFlags::Connect)) {
|
|
set_connect_side_role(Role::None);
|
|
return set_so_error(ECONNREFUSED);
|
|
}
|
|
set_connect_side_role(Role::Connected);
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::listen(size_t backlog)
|
|
{
|
|
MutexLocker locker(mutex());
|
|
if (type() != SOCK_STREAM)
|
|
return set_so_error(EOPNOTSUPP);
|
|
set_backlog(backlog);
|
|
auto previous_role = m_role;
|
|
set_role(Role::Listener);
|
|
set_connect_side_role(Role::Listener, previous_role != m_role);
|
|
|
|
dbgln_if(LOCAL_SOCKET_DEBUG, "LocalSocket({}) listening with backlog={}", this, backlog);
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::attach(OpenFileDescription& description)
|
|
{
|
|
VERIFY(!m_accept_side_fd_open);
|
|
if (m_connect_side_role == Role::None) {
|
|
VERIFY(m_connect_side_fd == nullptr);
|
|
m_connect_side_fd = &description;
|
|
} else {
|
|
VERIFY(m_connect_side_fd != &description);
|
|
m_accept_side_fd_open = true;
|
|
}
|
|
|
|
evaluate_block_conditions();
|
|
return {};
|
|
}
|
|
|
|
void LocalSocket::detach(OpenFileDescription& description)
|
|
{
|
|
if (m_connect_side_fd == &description) {
|
|
m_connect_side_fd = nullptr;
|
|
} else {
|
|
VERIFY(m_accept_side_fd_open);
|
|
m_accept_side_fd_open = false;
|
|
|
|
if (m_bound) {
|
|
auto inode = m_inode.strong_ref();
|
|
if (inode)
|
|
inode->unbind_socket();
|
|
}
|
|
}
|
|
|
|
evaluate_block_conditions();
|
|
}
|
|
|
|
bool LocalSocket::can_read(OpenFileDescription const& description, u64) const
|
|
{
|
|
auto role = this->role(description);
|
|
if (role == Role::Listener)
|
|
return can_accept();
|
|
if (role == Role::Accepted)
|
|
return !has_attached_peer(description) || !m_for_server->is_empty();
|
|
if (role == Role::Connected)
|
|
return !has_attached_peer(description) || !m_for_client->is_empty();
|
|
return false;
|
|
}
|
|
|
|
bool LocalSocket::has_attached_peer(OpenFileDescription const& description) const
|
|
{
|
|
auto role = this->role(description);
|
|
if (role == Role::Accepted)
|
|
return m_connect_side_fd != nullptr;
|
|
if (role == Role::Connected)
|
|
return m_accept_side_fd_open;
|
|
return false;
|
|
}
|
|
|
|
bool LocalSocket::can_write(OpenFileDescription const& description, u64) const
|
|
{
|
|
auto role = this->role(description);
|
|
if (role == Role::Accepted)
|
|
return !has_attached_peer(description) || m_for_client->space_for_writing();
|
|
if (role == Role::Connected)
|
|
return !has_attached_peer(description) || m_for_server->space_for_writing();
|
|
return false;
|
|
}
|
|
|
|
ErrorOr<size_t> LocalSocket::sendto(OpenFileDescription& description, UserOrKernelBuffer const& data, size_t data_size, int, Userspace<sockaddr const*>, socklen_t)
|
|
{
|
|
if (!has_attached_peer(description))
|
|
return set_so_error(EPIPE);
|
|
auto* socket_buffer = send_buffer_for(description);
|
|
if (!socket_buffer)
|
|
return set_so_error(EINVAL);
|
|
auto nwritten_or_error = socket_buffer->write(data, data_size);
|
|
if (!nwritten_or_error.is_error() && nwritten_or_error.value() > 0)
|
|
Thread::current()->did_unix_socket_write(nwritten_or_error.value());
|
|
return nwritten_or_error;
|
|
}
|
|
|
|
DoubleBuffer* LocalSocket::receive_buffer_for(OpenFileDescription& description)
|
|
{
|
|
auto role = this->role(description);
|
|
if (role == Role::Accepted)
|
|
return m_for_server.ptr();
|
|
if (role == Role::Connected)
|
|
return m_for_client.ptr();
|
|
return nullptr;
|
|
}
|
|
|
|
DoubleBuffer* LocalSocket::send_buffer_for(OpenFileDescription& description)
|
|
{
|
|
auto role = this->role(description);
|
|
if (role == Role::Connected)
|
|
return m_for_server.ptr();
|
|
if (role == Role::Accepted)
|
|
return m_for_client.ptr();
|
|
return nullptr;
|
|
}
|
|
|
|
ErrorOr<size_t> LocalSocket::recvfrom(OpenFileDescription& description, UserOrKernelBuffer& buffer, size_t buffer_size, int, Userspace<sockaddr*>, Userspace<socklen_t*>, Time&)
|
|
{
|
|
auto* socket_buffer = receive_buffer_for(description);
|
|
if (!socket_buffer)
|
|
return set_so_error(EINVAL);
|
|
if (!description.is_blocking()) {
|
|
if (socket_buffer->is_empty()) {
|
|
if (!has_attached_peer(description))
|
|
return 0;
|
|
return set_so_error(EAGAIN);
|
|
}
|
|
} else if (!can_read(description, 0)) {
|
|
auto unblock_flags = Thread::OpenFileDescriptionBlocker::BlockFlags::None;
|
|
if (Thread::current()->block<Thread::ReadBlocker>({}, description, unblock_flags).was_interrupted())
|
|
return set_so_error(EINTR);
|
|
}
|
|
if (!has_attached_peer(description) && socket_buffer->is_empty())
|
|
return 0;
|
|
VERIFY(!socket_buffer->is_empty());
|
|
auto nread_or_error = socket_buffer->read(buffer, buffer_size);
|
|
if (!nread_or_error.is_error() && nread_or_error.value() > 0)
|
|
Thread::current()->did_unix_socket_read(nread_or_error.value());
|
|
return nread_or_error;
|
|
}
|
|
|
|
StringView LocalSocket::socket_path() const
|
|
{
|
|
if (!m_path)
|
|
return {};
|
|
return m_path->view();
|
|
}
|
|
|
|
ErrorOr<NonnullOwnPtr<KString>> LocalSocket::pseudo_path(OpenFileDescription const& description) const
|
|
{
|
|
StringBuilder builder;
|
|
TRY(builder.try_append("socket:"sv));
|
|
TRY(builder.try_append(socket_path()));
|
|
|
|
switch (role(description)) {
|
|
case Role::Listener:
|
|
TRY(builder.try_append(" (listening)"sv));
|
|
break;
|
|
case Role::Accepted:
|
|
TRY(builder.try_appendff(" (accepted from pid {})", origin_pid()));
|
|
break;
|
|
case Role::Connected:
|
|
TRY(builder.try_appendff(" (connected to pid {})", acceptor_pid()));
|
|
break;
|
|
case Role::Connecting:
|
|
TRY(builder.try_append(" (connecting)"sv));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return KString::try_create(builder.string_view());
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::getsockopt(OpenFileDescription& description, int level, int option, Userspace<void*> value, Userspace<socklen_t*> value_size)
|
|
{
|
|
if (level != SOL_SOCKET)
|
|
return Socket::getsockopt(description, level, option, value, value_size);
|
|
|
|
MutexLocker locker(mutex());
|
|
|
|
socklen_t size;
|
|
TRY(copy_from_user(&size, value_size.unsafe_userspace_ptr()));
|
|
|
|
switch (option) {
|
|
case SO_SNDBUF:
|
|
return ENOTSUP;
|
|
case SO_RCVBUF:
|
|
return ENOTSUP;
|
|
case SO_PEERCRED: {
|
|
if (size < sizeof(ucred))
|
|
return EINVAL;
|
|
switch (role(description)) {
|
|
case Role::Accepted:
|
|
TRY(copy_to_user(static_ptr_cast<ucred*>(value), &m_origin));
|
|
size = sizeof(ucred);
|
|
TRY(copy_to_user(value_size, &size));
|
|
return {};
|
|
case Role::Connected:
|
|
TRY(copy_to_user(static_ptr_cast<ucred*>(value), &m_acceptor));
|
|
size = sizeof(ucred);
|
|
TRY(copy_to_user(value_size, &size));
|
|
return {};
|
|
case Role::Connecting:
|
|
return ENOTCONN;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
default:
|
|
return Socket::getsockopt(description, level, option, value, value_size);
|
|
}
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::ioctl(OpenFileDescription& description, unsigned request, Userspace<void*> arg)
|
|
{
|
|
switch (request) {
|
|
case FIONREAD: {
|
|
int readable = receive_buffer_for(description)->immediately_readable();
|
|
return copy_to_user(static_ptr_cast<int*>(arg), &readable);
|
|
}
|
|
}
|
|
|
|
return EINVAL;
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::chmod(OpenFileDescription&, mode_t mode)
|
|
{
|
|
auto inode = m_inode.strong_ref();
|
|
if (inode)
|
|
return inode->chmod(mode);
|
|
|
|
m_prebind_mode = mode & 0777;
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::chown(OpenFileDescription&, UserID uid, GroupID gid)
|
|
{
|
|
auto inode = m_inode.strong_ref();
|
|
if (inode)
|
|
return inode->chown(uid, gid);
|
|
|
|
auto& current_process = Process::current();
|
|
if (!current_process.is_superuser() && (current_process.euid() != uid || !current_process.in_group(gid)))
|
|
return set_so_error(EPERM);
|
|
|
|
m_prebind_uid = uid;
|
|
m_prebind_gid = gid;
|
|
return {};
|
|
}
|
|
|
|
NonnullLockRefPtrVector<OpenFileDescription>& LocalSocket::recvfd_queue_for(OpenFileDescription const& description)
|
|
{
|
|
auto role = this->role(description);
|
|
if (role == Role::Connected)
|
|
return m_fds_for_client;
|
|
if (role == Role::Accepted)
|
|
return m_fds_for_server;
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
NonnullLockRefPtrVector<OpenFileDescription>& LocalSocket::sendfd_queue_for(OpenFileDescription const& description)
|
|
{
|
|
auto role = this->role(description);
|
|
if (role == Role::Connected)
|
|
return m_fds_for_server;
|
|
if (role == Role::Accepted)
|
|
return m_fds_for_client;
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::sendfd(OpenFileDescription const& socket_description, NonnullLockRefPtr<OpenFileDescription> passing_description)
|
|
{
|
|
MutexLocker locker(mutex());
|
|
auto role = this->role(socket_description);
|
|
if (role != Role::Connected && role != Role::Accepted)
|
|
return set_so_error(EINVAL);
|
|
auto& queue = sendfd_queue_for(socket_description);
|
|
// FIXME: Figure out how we should limit this properly.
|
|
if (queue.size() > 128)
|
|
return set_so_error(EBUSY);
|
|
SOCKET_TRY(queue.try_append(move(passing_description)));
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<NonnullLockRefPtr<OpenFileDescription>> LocalSocket::recvfd(OpenFileDescription const& socket_description)
|
|
{
|
|
MutexLocker locker(mutex());
|
|
auto role = this->role(socket_description);
|
|
if (role != Role::Connected && role != Role::Accepted)
|
|
return set_so_error(EINVAL);
|
|
auto& queue = recvfd_queue_for(socket_description);
|
|
if (queue.is_empty()) {
|
|
// FIXME: Figure out the perfect error code for this.
|
|
return set_so_error(EAGAIN);
|
|
}
|
|
return queue.take_first();
|
|
}
|
|
|
|
ErrorOr<void> LocalSocket::try_set_path(StringView path)
|
|
{
|
|
m_path = TRY(KString::try_create(path));
|
|
return {};
|
|
}
|
|
|
|
}
|