Add a new tool for debugging darlingserver

This new tool (`dserverdbg`) runs on the host but connects to
darlingserver and makes unmanaged calls to retrieve debugging
information.

The initial set of subcommands available in this tool are `ps`,
`lsport`, `lspset`, and `lsmsg`:
  * `ps` lists processes currently registered with the server and how
    many Mach ports they have
  * `lsport` lists the ports of a given process (via PID) and their
    rights and messages counts (for receive rights)
  * `lspset` lists the members of a given portset (via PID and port
    name) and provides the same information about each port as `lsport`
  * `lsmsg` lists the messages of a given port (via PID and port name),
    providing sender PID (if available) and size

This tool may be expanded later to allow e.g. modifying logging settings
while darlingserver is running or perhaps searching through and
filtering the logs.
This commit is contained in:
Ariel Abreu 2023-10-04 00:23:56 -04:00
parent 5a3e170bf6
commit db65a1b009
No known key found for this signature in database
GPG Key ID: 5B88AAAF4280706F
11 changed files with 852 additions and 0 deletions

View File

@ -2,9 +2,14 @@ project(darlingserver)
cmake_minimum_required(VERSION 3.13)
option(DSERVER_TOOLS "Build darlingserver tools (for debugging)" OFF)
option(DSERVER_ASAN "Build darlingserver with ASAN" OFF)
option(DSERVER_UBSAN "Build darlingserver with UBSAN" OFF)
if (DSERVER_TOOLS)
add_subdirectory(tools)
endif()
if (DSERVER_ASAN)
add_compile_definitions(
DSERVER_ASAN=1

View File

@ -269,6 +269,7 @@ add_library(darlingserver_duct_tape
src/semaphore.c
src/psynch.c
src/condvar.c
src/debug.c
xnu/libkern/os/refcnt.c
xnu/libkern/gen/OSAtomicOperations.c

View File

@ -105,6 +105,11 @@ void dtape_semaphore_up(dtape_semaphore_t* semaphore);
dtape_semaphore_wait_result_t dtape_semaphore_down(dtape_semaphore_t* semaphore);
bool dtape_semaphore_down_simple(dtape_semaphore_t* semaphore);
uint64_t dtape_debug_task_port_count(dtape_task_t* task);
uint64_t dtape_debug_task_list_ports(dtape_task_t* task, dtape_debug_task_list_ports_iterator_f iterator, void* context);
uint64_t dtape_debug_portset_list_members(dtape_task_t* task, uint32_t portset, dtape_debug_portset_list_members_iterator_f iterator, void* context);
uint64_t dtape_debug_port_list_messages(dtape_task_t* task, uint32_t port, dtape_debug_port_list_messages_iterator_f iterator, void* context);
#ifdef __cplusplus
};
#endif

View File

@ -75,6 +75,23 @@ typedef struct dtape_load_info {
uint64_t thread_count;
} dtape_load_info_t;
typedef struct dtape_debug_port {
uint32_t name;
uint32_t rights;
uint64_t refs;
uint64_t messages;
} dtape_debug_port_t;
typedef bool (*dtape_debug_task_list_ports_iterator_f)(void* context, const dtape_debug_port_t* port);
typedef bool (*dtape_debug_portset_list_members_iterator_f)(void* context, const dtape_debug_port_t* port);
typedef struct dtape_debug_message {
uint32_t sender;
uint64_t size;
} dtape_debug_message_t;
typedef bool (*dtape_debug_port_list_messages_iterator_f)(void* context, const dtape_debug_message_t* message);
#ifdef __cplusplus
};
#endif

120
duct-tape/src/debug.c Normal file
View File

@ -0,0 +1,120 @@
#include <darlingserver/duct-tape.h>
#include <darlingserver/duct-tape/task.h>
uint64_t dtape_debug_task_port_count(dtape_task_t* task) {
return task->xnu_task.itk_space->is_table_hashed;
};
uint64_t dtape_debug_task_list_ports(dtape_task_t* task, dtape_debug_task_list_ports_iterator_f iterator, void* context) {
uint64_t port_count = 0;
bool call_it = true;
for (mach_port_index_t index = 0; index < task->xnu_task.itk_space->is_table_size; ++index) {
ipc_entry_t entry = &task->xnu_task.itk_space->is_table[index];
dtape_debug_port_t debug_port;
ipc_port_t port = NULL;
if (IE_BITS_TYPE(entry->ie_bits) == MACH_PORT_TYPE_NONE) {
continue;
}
port = ip_object_to_port(entry->ie_object);
debug_port.name = MACH_PORT_MAKE(index, IE_BITS_GEN(entry->ie_bits));
debug_port.refs = IE_BITS_UREFS(entry->ie_bits);
debug_port.rights = IE_BITS_TYPE(entry->ie_bits);
debug_port.messages = port->ip_messages.imq_msgcount;
if (call_it) {
call_it = iterator(context, &debug_port);
}
++port_count;
}
return port_count;
};
uint64_t dtape_debug_portset_list_members(dtape_task_t* task, uint32_t portset, dtape_debug_portset_list_members_iterator_f iterator, void* context) {
ipc_object_t object = NULL;
ipc_mqueue_t mqueue = NULL;
ipc_entry_num_t member_count = 0;
mach_port_name_t* names = NULL;
ipc_entry_num_t actual_count = 0;
bool call_it = true;
if (ipc_mqueue_copyin(task->xnu_task.itk_space, portset, &mqueue, &object) != KERN_SUCCESS) {
return 0;
}
do {
if (names) {
kfree(names, sizeof(*names) * member_count);
}
names = kalloc(sizeof(*names) * actual_count);
member_count = actual_count;
ipc_mqueue_set_gather_member_names(task->xnu_task.itk_space, mqueue, member_count, names, &actual_count);
} while (member_count != actual_count);
for (ipc_entry_num_t i = 0; i < member_count; ++i) {
mach_port_name_t* name = &names[i];
dtape_debug_port_t debug_port;
ipc_entry_t entry = NULL;
ipc_port_t port = NULL;
entry = ipc_entry_lookup(task->xnu_task.itk_space, *name);
port = ip_object_to_port(entry->ie_object);
debug_port.name = *name;
debug_port.refs = IE_BITS_UREFS(entry->ie_bits);
debug_port.rights = IE_BITS_TYPE(entry->ie_bits);
debug_port.messages = port->ip_messages.imq_msgcount;
if (call_it) {
call_it = iterator(context, &debug_port);
}
}
if (names) {
kfree(names, sizeof(*names) * member_count);
}
io_release(object);
return member_count;
};
uint64_t dtape_debug_port_list_messages(dtape_task_t* task, uint32_t port, dtape_debug_port_list_messages_iterator_f iterator, void* context) {
ipc_object_t object = NULL;
ipc_mqueue_t mqueue = NULL;
uint64_t message_count = 0;
bool call_it = true;
if (ipc_mqueue_copyin(task->xnu_task.itk_space, port, &mqueue, &object) != KERN_SUCCESS) {
return 0;
}
for (ipc_kmsg_t kmsg = ipc_kmsg_queue_first(&mqueue->imq_messages); kmsg != NULL; kmsg = ipc_kmsg_queue_next(&mqueue->imq_messages, kmsg)) {
dtape_debug_message_t debug_message;
debug_message.sender = 0;
if (kmsg->ikm_header->msgh_remote_port && kmsg->ikm_header->msgh_remote_port->ip_receiver) {
debug_message.sender = dtape_task_for_xnu_task(kmsg->ikm_header->msgh_remote_port->ip_receiver->is_task)->saved_pid;
}
debug_message.size = kmsg->ikm_size;
if (call_it) {
call_it = iterator(context, &debug_message);
}
++message_count;
}
io_release(object);
return message_count;
};

View File

@ -254,6 +254,17 @@ namespace DarlingServer {
return (*it3).second;
};
std::vector<std::shared_ptr<Entry>> copyEntries() {
std::shared_lock lock(_rwlock);
std::vector<std::shared_ptr<Entry>> entries;
for (const auto& [eid, entry]: _emap) {
entries.push_back(entry);
}
return entries;
};
/**
* Locks the registry, preventing new entries from being added and old ones from being removed.
*

View File

@ -611,6 +611,38 @@ calls = [
], [
('retval', 'uint32_t'),
], XNU_BSD_TRAP_CALL | XNU_TRAP_NO_DTAPE_DEF | ALLOW_INTERRUPTIONS),
#
# debug calls
#
('debug_list_processes', [], [
('process_count', 'uint64_t'),
('fd', '@fd'),
], UNMANAGED_CALL),
('debug_list_ports', [
('process', 'uint32_t'),
], [
('port_count', 'uint64_t'),
('fd', '@fd'),
], UNMANAGED_CALL),
('debug_list_members', [
('process', 'uint32_t'),
('portset', 'uint32_t'),
], [
('port_count', 'uint64_t'),
('fd', '@fd'),
], UNMANAGED_CALL),
('debug_list_messages', [
('process', 'uint32_t'),
('port', 'uint32_t'),
], [
('message_count', 'uint64_t'),
('fd', '@fd'),
], UNMANAGED_CALL),
]
ALLOWED_PRIVATE_TYPES = [
@ -775,6 +807,27 @@ typedef struct dserver_rpc_call_push_reply {
uint64_t reply_size;
} dserver_rpc_call_push_reply_t;
//
// Debug calls
//
typedef struct dserver_debug_process {
uint32_t pid;
uint64_t port_count;
} dserver_debug_process_t;
typedef struct dserver_debug_port {
uint32_t port_name;
uint32_t rights;
uint64_t refs;
uint64_t messages;
} dserver_debug_port_t;
typedef struct dserver_debug_message {
uint32_t sender;
uint64_t size;
} dserver_debug_message_t;
""")
library_source.write("""\

View File

@ -380,6 +380,10 @@ void DarlingServer::Call::Checkout::processCall() {
code = -ESRCH;
}
// clear the thread pointer so that the reply will be sent directly through the server
// (otherwise, we would attempt to send it through the thread, which is now dead)
_thread.reset();
_sendReply(code);
};
@ -1071,4 +1075,115 @@ void DarlingServer::Call::Groups::processCall() {
_sendReply(code, oldGroups.size());
};
void DarlingServer::Call::DebugListProcesses::processCall() {
int code = 0;
auto processes = processRegistry().copyEntries();
int pipes[2] = {-1, -1};
code = pipe(pipes);
if (code == 0) {
for (const auto& process: processes) {
dserver_debug_process_t debugProcess;
debugProcess.pid = process->nsid();
debugProcess.port_count = dtape_debug_task_port_count(process->_dtapeTask);
write(pipes[1], &debugProcess, sizeof(debugProcess));
}
close(pipes[1]);
}
_sendReply(code, processes.size(), pipes[0]);
};
void DarlingServer::Call::DebugListPorts::processCall() {
int code = 0;
uint64_t portCount = 0;
int pipes[2] = {-1, -1};
if (auto maybeProcess = processRegistry().lookupEntryByNSID(_body.process)) {
auto process = *maybeProcess;
code = pipe(pipes);
if (code == 0) {
portCount = dtape_debug_task_list_ports(process->_dtapeTask, [](void* context, const dtape_debug_port_t* port) {
int& writeFD = *(int*)context;
dserver_debug_port_t debugPort;
debugPort.port_name = port->name;
debugPort.rights = port->rights;
debugPort.refs = port->refs;
debugPort.messages = port->messages;
write(writeFD, &debugPort, sizeof(debugPort));
return true;
}, &pipes[1]);
}
} else {
code = -ESRCH;
}
_sendReply(code, portCount, pipes[0]);
};
void DarlingServer::Call::DebugListMembers::processCall() {
int code = 0;
uint64_t portCount = 0;
int pipes[2] = {-1, -1};
if (auto maybeProcess = processRegistry().lookupEntryByNSID(_body.process)) {
auto process = *maybeProcess;
code = pipe(pipes);
if (code == 0) {
portCount = dtape_debug_portset_list_members(process->_dtapeTask, _body.portset, [](void* context, const dtape_debug_port_t* port) {
int& writeFD = *(int*)context;
dserver_debug_port_t debugPort;
debugPort.port_name = port->name;
debugPort.rights = port->rights;
debugPort.refs = port->refs;
debugPort.messages = port->messages;
write(writeFD, &debugPort, sizeof(debugPort));
return true;
}, &pipes[1]);
}
} else {
code = -ESRCH;
}
_sendReply(code, portCount, pipes[0]);
};
void DarlingServer::Call::DebugListMessages::processCall() {
int code = 0;
uint64_t portCount = 0;
int pipes[2] = {-1, -1};
if (auto maybeProcess = processRegistry().lookupEntryByNSID(_body.process)) {
auto process = *maybeProcess;
code = pipe(pipes);
if (code == 0) {
portCount = dtape_debug_port_list_messages(process->_dtapeTask, _body.port, [](void* context, const dtape_debug_message_t* port) {
int& writeFD = *(int*)context;
dserver_debug_message_t debugMessage;
debugMessage.sender = port->sender;
debugMessage.size = port->size;
write(writeFD, &debugMessage, sizeof(debugMessage));
return true;
}, &pipes[1]);
}
} else {
code = -ESRCH;
}
_sendReply(code, portCount, pipes[0]);
};
DSERVER_CLASS_SOURCE_DEFS;

35
tools/CMakeLists.txt Normal file
View File

@ -0,0 +1,35 @@
project(darlingserver-tools)
cmake_minimum_required(VERSION 3.13)
set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/../src/rpc.c" PROPERTIES
GENERATED TRUE
)
add_library(dserverdbg_dserver_rpc STATIC "${CMAKE_CURRENT_BINARY_DIR}/../src/rpc.c")
add_dependencies(dserverdbg_dserver_rpc generate_dserver_rpc_wrappers)
target_compile_options(dserverdbg_dserver_rpc PRIVATE -include "${CMAKE_CURRENT_SOURCE_DIR}/dserverdbg-rpc-defs.h")
add_dependencies(dserverdbg_dserver_rpc rtsig_h)
target_include_directories(dserverdbg_dserver_rpc PRIVATE
"${CMAKE_BINARY_DIR}/src/startup" # for `rtsig.h`
)
target_include_directories(dserverdbg_dserver_rpc PUBLIC
"${CMAKE_CURRENT_BINARY_DIR}/../include"
../include
)
add_executable(dserverdbg dserverdbg.c)
target_link_libraries(dserverdbg PRIVATE dserverdbg_dserver_rpc)
install(
TARGETS dserverdbg
DESTINATION bin
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
SETUID
)

112
tools/dserverdbg-rpc-defs.h Normal file
View File

@ -0,0 +1,112 @@
#pragma once
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <darlingserver/rpc-supplement.h>
#include <rtsig.h>
#define dserver_rpc_hooks_msghdr_t struct msghdr
#define dserver_rpc_hooks_iovec_t struct iovec
#define dserver_rpc_hooks_cmsghdr_t struct cmsghdr
#define DSERVER_RPC_HOOKS_CMSG_SPACE CMSG_SPACE
#define DSERVER_RPC_HOOKS_CMSG_FIRSTHDR CMSG_FIRSTHDR
#define DSERVER_RPC_HOOKS_SOL_SOCKET SOL_SOCKET
#define DSERVER_RPC_HOOKS_SCM_RIGHTS SCM_RIGHTS
#define DSERVER_RPC_HOOKS_CMSG_LEN CMSG_LEN
#define DSERVER_RPC_HOOKS_CMSG_DATA CMSG_DATA
#define DSERVER_RPC_HOOKS_ATTRIBUTE static
#define dserver_rpc_hooks_get_pid getpid
#define dserver_rpc_hooks_get_tid() ((pid_t)syscall(SYS_gettid))
#if __x86_64__
#define dserver_rpc_hooks_get_architecture() dserver_rpc_architecture_x86_64
#elif __i386__
#define dserver_rpc_hooks_get_architecture() dserver_rpc_architecture_i386
#elif __aarch64__
#define dserver_rpc_hooks_get_architecture() dserver_rpc_architecture_arm64
#elif __arm__
#define dserver_rpc_hooks_get_architecture() dserver_rpc_architecture_arm32
#else
#define dserver_rpc_hooks_get_architecture() dserver_rpc_architecture_invalid
#endif
extern struct sockaddr_un __dserver_socket_address_data;
#define dserver_rpc_hooks_get_server_address() ((void*)&__dserver_socket_address_data)
#define dserver_rpc_hooks_get_server_address_length() sizeof(__dserver_socket_address_data)
#define dserver_rpc_hooks_memcpy memcpy
static long int dserver_rpc_hooks_send_message(int socket, const dserver_rpc_hooks_msghdr_t* message) {
ssize_t ret = sendmsg(socket, message, 0);
if (ret < 0) {
return -errno;
}
return ret;
};
static long int dserver_rpc_hooks_receive_message(int socket, dserver_rpc_hooks_msghdr_t* out_message) {
ssize_t ret = recvmsg(socket, out_message, 0);
if (ret < 0) {
return -errno;
}
if (ret >= sizeof(dserver_s2c_callhdr_t)) {
dserver_s2c_callhdr_t* callhdr = out_message->msg_iov->iov_base;
if (callhdr->call_number == 0x52cca11) {
// this is an S2C call
// dserverdbg shouldn't need to be doing S2C calls
fprintf(stderr, "dserverdbg darlingserver RPC hooks received S2C call\n");
abort();
}
}
return ret;
};
#define dserver_rpc_hooks_get_bad_message_status() (-EBADMSG)
#define dserver_rpc_hooks_get_communication_error_status() (-ECOMM)
#define dserver_rpc_hooks_get_broken_pipe_status() (-EPIPE)
#define dserver_rpc_hooks_close_fd close
extern int __dserver_main_thread_socket_fd;
#define dserver_rpc_hooks_get_socket() __dserver_main_thread_socket_fd
#define dserver_rpc_hooks_printf(...) fprintf(stderr, ## __VA_ARGS__)
#define dserver_rpc_hooks_atomic_save_t sigset_t
static void dserver_rpc_hooks_atomic_begin(dserver_rpc_hooks_atomic_save_t* atomic_save) {
sigset_t set;
sigfillset(&set);
sigdelset(&set, LINUX_SIGRTMIN);
sigdelset(&set, LINUX_SIGRTMIN + 1);
pthread_sigmask(SIG_BLOCK, &set, atomic_save);
};
static void dserver_rpc_hooks_atomic_end(dserver_rpc_hooks_atomic_save_t* atomic_save) {
pthread_sigmask(SIG_SETMASK, atomic_save, NULL);
};
#define dserver_rpc_hooks_get_interrupt_status() (-EINTR)
static void dserver_rpc_hooks_push_reply(int socket, const dserver_rpc_hooks_msghdr_t* reply, size_t size) {
// we shouldn't need to push any replies in dserverdbg
abort();
};

378
tools/dserverdbg.c Normal file
View File

@ -0,0 +1,378 @@
#define _GNU_SOURCE
#include <darlingserver/rpc.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <fcntl.h>
#include <sched.h>
#include <errno.h>
#include <signal.h>
#include <sys/un.h>
#include <sys/socket.h>
typedef uint32_t mach_port_type_t;
typedef uint32_t mach_port_right_t;
#define MACH_PORT_RIGHT_SEND ((mach_port_right_t) 0)
#define MACH_PORT_RIGHT_RECEIVE ((mach_port_right_t) 1)
#define MACH_PORT_RIGHT_SEND_ONCE ((mach_port_right_t) 2)
#define MACH_PORT_RIGHT_PORT_SET ((mach_port_right_t) 3)
#define MACH_PORT_RIGHT_DEAD_NAME ((mach_port_right_t) 4)
#define MACH_PORT_RIGHT_LABELH ((mach_port_right_t) 5) /* obsolete right */
#define MACH_PORT_RIGHT_NUMBER ((mach_port_right_t) 6) /* right not implemented */
#define MACH_PORT_TYPE(right) \
((mach_port_type_t)(((mach_port_type_t) 1) \
<< ((right) + ((mach_port_right_t) 16))))
#define MACH_PORT_TYPE_NONE ((mach_port_type_t) 0L)
#define MACH_PORT_TYPE_SEND MACH_PORT_TYPE(MACH_PORT_RIGHT_SEND)
#define MACH_PORT_TYPE_RECEIVE MACH_PORT_TYPE(MACH_PORT_RIGHT_RECEIVE)
#define MACH_PORT_TYPE_SEND_ONCE MACH_PORT_TYPE(MACH_PORT_RIGHT_SEND_ONCE)
#define MACH_PORT_TYPE_PORT_SET MACH_PORT_TYPE(MACH_PORT_RIGHT_PORT_SET)
#define MACH_PORT_TYPE_DEAD_NAME MACH_PORT_TYPE(MACH_PORT_RIGHT_DEAD_NAME)
#define MACH_PORT_TYPE_LABELH MACH_PORT_TYPE(MACH_PORT_RIGHT_LABELH) /* obsolete */
// borrowed from `src/startup/darling.c`
// ---
// Between Linux 4.9 and 4.11, a strange bug has been introduced
// which prevents connecting to Unix sockets if the socket was
// created in a different mount namespace or under overlayfs
// (dunno which one is really responsible for this).
#define USE_LINUX_4_11_HACK 1
typedef enum dserverdbg_command {
dserverdbg_command_ps,
dserverdbg_command_lsport,
dserverdbg_command_lspset,
dserverdbg_command_lsmsg,
} dserverdbg_command_t;
struct sockaddr_un __dserver_socket_address_data = {0};
int __dserver_main_thread_socket_fd = -1;
static char* default_prefix_path(uid_t original_uid) {
struct passwd* info = getpwuid(original_uid);
char* result = NULL;
if (asprintf(&result, "%s/.darling", info->pw_dir) < 0) {
return NULL;
}
return result;
};
static char* get_prefix_path(uid_t original_uid) {
char* env = getenv("DPREFIX");
if (env) {
return strdup(env);
}
return default_prefix_path(original_uid);
};
// borrowed from `src/startup/darling.c`
static void joinNamespace(pid_t pid, int type, const char* typeName)
{
int fdNS;
char pathNS[4096];
snprintf(pathNS, sizeof(pathNS), "/proc/%d/ns/%s", pid, typeName);
fdNS = open(pathNS, O_RDONLY);
if (fdNS < 0)
{
fprintf(stderr, "Cannot open %s namespace file: %s\n", typeName, strerror(errno));
exit(1);
}
// Calling setns() with a PID namespace doesn't move our process into it,
// but our child process will be spawned inside the namespace
if (setns(fdNS, type) != 0)
{
fprintf(stderr, "Cannot join %s namespace: %s\n", typeName, strerror(errno));
exit(1);
}
close(fdNS);
}
// borrowed from `src/startup/darling.c`, with the UID/GID check removed
static pid_t getInitProcess(const char* prefix)
{
const char pidFile[] = "/.init.pid";
char* pidPath;
pid_t pid;
int pid_i;
FILE *fp;
char procBuf[100];
char *exeBuf;
pidPath = (char*) alloca(strlen(prefix) + sizeof(pidFile));
strcpy(pidPath, prefix);
strcat(pidPath, pidFile);
fp = fopen(pidPath, "r");
if (fp == NULL)
return 0;
if (fscanf(fp, "%d", &pid_i) != 1)
{
fclose(fp);
unlink(pidPath);
return 0;
}
fclose(fp);
pid = (pid_t) pid_i;
// Does the process exist?
if (kill(pid, 0) == -1)
{
unlink(pidPath);
return 0;
}
// Is it actually an init process?
snprintf(procBuf, sizeof(procBuf), "/proc/%d/comm", pid);
fp = fopen(procBuf, "r");
if (fp == NULL)
{
unlink(pidPath);
return 0;
}
if (fscanf(fp, "%ms", &exeBuf) != 1)
{
fclose(fp);
unlink(pidPath);
return 0;
}
fclose(fp);
if (strcmp(exeBuf, "darlingserver") != 0)
{
unlink(pidPath);
return 0;
}
free(exeBuf);
return pid;
}
static int setup_socket(void) {
int fd = -1;
fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (fd < 0) {
goto err_out;
}
int fd_flags = fcntl(fd, F_GETFD);
if (fd_flags < 0) {
goto err_out;
}
if (fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) < 0) {
goto err_out;
}
sa_family_t family = AF_UNIX;
if (bind(fd, (const struct sockaddr*)&family, sizeof(family)) < 0) {
goto err_out;
}
out:
return fd;
err_out:
if (fd >= 0) {
close(fd);
}
return -1;
};
// borrowed from `src/startup/darling.c`
static void missingSetuidRoot(void)
{
char path[4096];
int len;
len = readlink("/proc/self/exe", path, sizeof(path)-1);
if (len < 0)
strcpy(path, "darling");
else
path[len] = '\0';
fprintf(stderr, "Sorry, the `%s' binary is not setuid root, which is mandatory.\n", path);
fprintf(stderr, "Darling needs this in order to create mount and PID namespaces and to perform mounts.\n");
}
int main(int argc, char** argv) {
char* prefix_path = NULL;
dserverdbg_command_t command = dserverdbg_command_ps;
pid_t command_pid = 0;
uint32_t command_port = 0;
int output_fd = -1;
int status = 0;
uint64_t count = 0;
uint64_t elmsize = 0;
char* data = 0;
uid_t original_uid = -1;
gid_t original_gid = -1;
#if USE_LINUX_4_11_HACK
pid_t pidInit = 0;
#endif
if (geteuid() != 0) {
missingSetuidRoot();
return 1;
}
original_uid = getuid();
original_gid = getgid();
setuid(0);
setgid(0);
prefix_path = get_prefix_path(original_uid);
if (!prefix_path) {
fprintf(stderr, "Failed to determine prefix path\n");
return 1;
}
__dserver_socket_address_data.sun_family = AF_UNIX;
snprintf(__dserver_socket_address_data.sun_path, sizeof(__dserver_socket_address_data.sun_path), "%s/.darlingserver.sock", prefix_path);
#if USE_LINUX_4_11_HACK
pidInit = getInitProcess(prefix_path);
joinNamespace(pidInit, CLONE_NEWNS, "mnt");
#endif
__dserver_main_thread_socket_fd = setup_socket();
if (__dserver_main_thread_socket_fd < 0) {
fprintf(stderr, "Failed to set up darlingserver client socket\n");
return 1;
}
if (argc > 1) {
if (strcmp(argv[1], "ps") == 0 || strcmp(argv[1], "lsproc") == 0) {
command = dserverdbg_command_ps;
} else if (strcmp(argv[1], "lsport") == 0) {
command = dserverdbg_command_lsport;
} else if (strcmp(argv[1], "lspset") == 0) {
command = dserverdbg_command_lspset;
} else if (strcmp(argv[1], "lsmsg") == 0) {
command = dserverdbg_command_lsmsg;
} else {
fprintf(stderr, "Unknown subcommand: %s\n", argv[1]);
return 1;
}
}
switch (command) {
case dserverdbg_command_ps:
if (argc > 2) {
fprintf(stderr, "Expected 1 argument (subcommand); got %d arguments\n", argc);
return 1;
}
break;
case dserverdbg_command_lsport:
if (argc != 3) {
fprintf(stderr, "Expected 2 arguments (subcommand and PID); got %d arguments\n", argc);
return 1;
}
command_pid = atoi(argv[2]);
break;
case dserverdbg_command_lspset:
case dserverdbg_command_lsmsg:
if (argc != 4) {
fprintf(stderr, "Expected 3 arguments (subcommand, PID, and port name); got %d arguments\n", argc);
return 1;
}
command_pid = atoi(argv[2]);
command_port = atoi(argv[3]);
break;
}
switch (command) {
case dserverdbg_command_ps:
status = dserver_rpc_debug_list_processes(&count, &output_fd);
elmsize = sizeof(dserver_debug_process_t);
break;
case dserverdbg_command_lsport:
status = dserver_rpc_debug_list_ports(command_pid, &count, &output_fd);
elmsize = sizeof(dserver_debug_port_t);
break;
case dserverdbg_command_lspset:
status = dserver_rpc_debug_list_members(command_pid, command_port, &count, &output_fd);
elmsize = sizeof(dserver_debug_port_t);
break;
case dserverdbg_command_lsmsg:
status = dserver_rpc_debug_list_messages(command_pid, command_port, &count, &output_fd);
elmsize = sizeof(dserver_debug_message_t);
break;
}
if (status != 0) {
fprintf(stderr, "Subcommand failed: server replied with error: %d (%s)\n", status, strerror(status));
return 1;
}
for (uint64_t i = 0; i < count; ++i) {
char buffer[elmsize];
if (read(output_fd, buffer, elmsize) != elmsize) {
status = errno;
fprintf(stderr, "Failed to read from output pipe: %d (%s)\n", status, strerror(status));
return 1;
}
switch (command) {
case dserverdbg_command_ps: {
dserver_debug_process_t* data = (void*)buffer;
printf("pid %u - %lu ports\n", data->pid, data->port_count);
} break;
case dserverdbg_command_lsport:
case dserverdbg_command_lspset: {
dserver_debug_port_t* data = (void*)buffer;
const char* right_name = "<unknown>";
if (data->rights == MACH_PORT_TYPE_SEND) {
right_name = "send";
} else if (data->rights == MACH_PORT_TYPE_RECEIVE) {
right_name = "receive";
} else if (data->rights == MACH_PORT_TYPE_SEND_ONCE) {
right_name = "send-once";
} else if (data->rights == MACH_PORT_TYPE_PORT_SET) {
right_name = "port set";
} else if (data->rights == MACH_PORT_TYPE_DEAD_NAME) {
right_name = "dead name";
} else if (data->rights == MACH_PORT_TYPE_LABELH) {
right_name = "labelh";
}
printf("port %d (%s), %lu refs - %lu messages\n", data->port_name, right_name, data->refs, data->messages);
} break;
case dserverdbg_command_lsmsg: {
dserver_debug_message_t* data = (void*)buffer;
printf("message %lu (from %u); %lu bytes\n", i, data->sender, data->size);
} break;
}
}
if (output_fd >= 0) {
close(output_fd);
}
if (prefix_path) {
free(prefix_path);
}
return 0;
};