5 Commits

Author SHA1 Message Date
ThePedroo
3688df6450 fix: *AppSpecialize inconsistency for root manager process; improve: allow to inject into root manager
This commit fixes an issue where modules would have their "postAppSpecialize" called for root manager processes, but not "preAppSpecialize". The approach taken to fix that was to completely allow to inject into root manager, which instead of making it not call "postAppSpecialize" either, now it allows both.
2025-08-15 21:30:18 -03:00
ThePedroo
0c7a756030 update: LSPlt submodule
This commit updates the LSPlt submodule used in ReZygisk to the latest commit.
2025-08-15 06:30:48 -03:00
ThePedroo
70805bb390 fix: misinterpreted state.json Zygote injection information
This commit fixes the incorrect interpretation of the "state.json"'s Zygote injection information.
2025-08-13 22:06:04 -03:00
ThePedroo
e6344d2e12 add: r_debug_tail trace hiding for undlclosed modules
This commit adds support for hiding "r_debug_tail" trace when a module is not "dlclose"d in the app's process.
2025-08-13 21:43:46 -03:00
ThePedroo
d2ebb2bfed improve: ReZygisk state in module description
This commit improves how ReZygisk state is shown in the module description, reducing its size in favor of the same amount of information but in the WebUI, as all root implementations have WebUI available, even if it's through external applications due to incompetency, such as Magisk.

closes #28
2025-08-13 21:34:13 -03:00
11 changed files with 609 additions and 468 deletions

View File

@@ -57,7 +57,6 @@ android {
defaultConfig {
externalNativeBuild.cmake {
arguments += "-DANDROID_STL=none"
arguments += "-DLSPLT_STANDALONE=ON"
arguments += "-DCMAKE_BUILD_PARALLEL_LEVEL=${Runtime.getRuntime().availableProcessors()}"
cFlags("-std=c18", *defaultCFlags)
cppFlags("-std=c++20", *defaultCFlags)

View File

@@ -15,7 +15,7 @@ target_link_libraries(common log)
aux_source_directory(injector INJECTOR_SRC_LIST)
add_library(zygisk SHARED ${INJECTOR_SRC_LIST})
target_include_directories(zygisk PRIVATE include)
target_link_libraries(zygisk cxx::cxx log common lsplt_static phmap)
target_link_libraries(zygisk cxx::cxx log common lsplt_static)
aux_source_directory(ptracer PTRACER_SRC_LIST)
add_executable(libzygisk_ptrace.so ${PTRACER_SRC_LIST})

View File

@@ -8,6 +8,50 @@
#include "socket_utils.h"
ssize_t write_loop(int fd, const void *buf, size_t count) {
ssize_t written = 0;
while (written < (ssize_t)count) {
ssize_t ret = write(fd, (const char *)buf + written, count - written);
if (ret == -1) {
if (errno == EINTR || errno == EAGAIN) continue;
PLOGE("write");
}
if (ret == 0) {
LOGE("write: 0 bytes written");
return -1;
}
written += ret;
}
return written;
}
ssize_t read_loop(int fd, void *buf, size_t count) {
ssize_t read_bytes = 0;
while (read_bytes < (ssize_t)count) {
ssize_t ret = read(fd, (char *)buf + read_bytes, count - read_bytes);
if (ret == -1) {
if (errno == EINTR || errno == EAGAIN) continue;
PLOGE("read");
}
if (ret == 0) {
LOGE("read: 0 bytes read");
return -1;
}
read_bytes += ret;
}
return read_bytes;
}
/* TODO: Standardize how to log errors */
int read_fd(int fd) {
char cmsgbuf[CMSG_SPACE(sizeof(int))];
@@ -47,14 +91,14 @@ int read_fd(int fd) {
ssize_t write_string(int fd, const char *str) {
size_t str_len = strlen(str);
ssize_t write_bytes = TEMP_FAILURE_RETRY(write(fd, &str_len, sizeof(size_t)));
ssize_t write_bytes = write_loop(fd, &str_len, sizeof(size_t));
if (write_bytes != (ssize_t)sizeof(size_t)) {
LOGE("Failed to write string length: Not all bytes were written (%zd != %zu).\n", write_bytes, sizeof(size_t));
return -1;
}
write_bytes = TEMP_FAILURE_RETRY(write(fd, str, str_len));
write_bytes = write_loop(fd, str, str_len);
if (write_bytes != (ssize_t)str_len) {
LOGE("Failed to write string: Promised bytes doesn't exist (%zd != %zu).\n", write_bytes, str_len);
@@ -66,7 +110,7 @@ ssize_t write_string(int fd, const char *str) {
char *read_string(int fd) {
size_t str_len = 0;
ssize_t read_bytes = TEMP_FAILURE_RETRY(read(fd, &str_len, sizeof(size_t)));
ssize_t read_bytes = read_loop(fd, &str_len, sizeof(size_t));
if (read_bytes != (ssize_t)sizeof(size_t)) {
LOGE("Failed to read string length: Not all bytes were read (%zd != %zu).\n", read_bytes, sizeof(size_t));
@@ -80,7 +124,7 @@ char *read_string(int fd) {
return NULL;
}
read_bytes = TEMP_FAILURE_RETRY(read(fd, buf, str_len));
read_bytes = read_loop(fd, buf, str_len);
if (read_bytes != (ssize_t)str_len) {
LOGE("Failed to read string: Promised bytes doesn't exist (%zd != %zu).\n", read_bytes, str_len);
@@ -94,14 +138,14 @@ char *read_string(int fd) {
return buf;
}
#define write_func(type) \
ssize_t write_## type(int fd, type val) { \
return TEMP_FAILURE_RETRY(write(fd, &val, sizeof(type))); \
#define write_func(type) \
ssize_t write_## type(int fd, type val) { \
return write_loop(fd, &val, sizeof(type)); \
}
#define read_func(type) \
ssize_t read_## type(int fd, type *val) { \
return TEMP_FAILURE_RETRY(read(fd, val, sizeof(type))); \
#define read_func(type) \
ssize_t read_## type(int fd, type *val) { \
return read_loop(fd, val, sizeof(type)); \
}
write_func(uint8_t)

View File

@@ -2,7 +2,3 @@ project(external)
OPTION(LSPLT_BUILD_SHARED OFF)
add_subdirectory(lsplt/lsplt/src/main/jni)
add_library(phmap INTERFACE)
target_include_directories(phmap INTERFACE parallel-hashmap)
target_compile_options(phmap INTERFACE -Wno-unused-value)

View File

@@ -3,6 +3,10 @@
#include <stdint.h>
ssize_t write_loop(int fd, const void *buf, size_t count);
ssize_t read_loop(int fd, void *buf, size_t count);
int read_fd(int fd);
ssize_t write_string(int fd, const char *str);

View File

@@ -822,52 +822,52 @@ void ZygiskContext::app_specialize_pre() {
if Zygisk is enabled.
*/
setenv("ZYGISK_ENABLED", "1", 1);
} else {
/* INFO: Because we load directly from the file, we need to do it before we umount
the mounts, or else it won't have access to /data/adb anymore.
*/
if (!load_modules_only()) {
LOGE("Failed to load modules");
return;
}
/* INFO: Modules only have two "start off" points from Zygisk, preSpecialize and
postSpecialize. In preSpecialize, the process still has privileged
permissions, and therefore can execute mount/umount/setns functions.
If we update the mount namespace AFTER executing them, any mounts made
will be lost, and the process will not have access to them anymore.
In postSpecialize, while still could have its mounts modified with the
assistance of a Zygisk companion, it will already have the mount
namespace switched by then, so there won't be issues.
Knowing this, we update the mns before execution, so that they can still
make changes to mounts in DenyListed processes without being reverted.
*/
bool in_denylist = (info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST;
if (in_denylist) {
flags[DO_REVERT_UNMOUNT] = true;
update_mnt_ns(Clean, false);
}
/* INFO: Executed after setns to ensure a module can update the mounts of an
application without worrying about it being overwritten by setns.
*/
run_modules_pre();
/* INFO: The modules may request that although the process is NOT in
the DenyList, it has its mount namespace switched to the clean
one.
So to ensure this behavior happens, we must also check after the
modules are loaded and executed, so that the modules can have
the chance to request it.
*/
if (!in_denylist && flags[DO_REVERT_UNMOUNT])
update_mnt_ns(Clean, false);
}
/* INFO: Because we load directly from the file, we need to do it before we umount
the mounts, or else it won't have access to /data/adb anymore.
*/
if (!load_modules_only()) {
LOGE("Failed to load modules");
return;
}
/* INFO: Modules only have two "start off" points from Zygisk, preSpecialize and
postSpecialize. In preSpecialize, the process still has privileged
permissions, and therefore can execute mount/umount/setns functions.
If we update the mount namespace AFTER executing them, any mounts made
will be lost, and the process will not have access to them anymore.
In postSpecialize, while still could have its mounts modified with the
assistance of a Zygisk companion, it will already have the mount
namespace switched by then, so there won't be issues.
Knowing this, we update the mns before execution, so that they can still
make changes to mounts in DenyListed processes without being reverted.
*/
bool in_denylist = (info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST;
if (in_denylist) {
flags[DO_REVERT_UNMOUNT] = true;
update_mnt_ns(Clean, false);
}
/* INFO: Executed after setns to ensure a module can update the mounts of an
application without worrying about it being overwritten by setns.
*/
run_modules_pre();
/* INFO: The modules may request that although the process is NOT in
the DenyList, it has its mount namespace switched to the clean
one.
So to ensure this behavior happens, we must also check after the
modules are loaded and executed, so that the modules can have
the chance to request it.
*/
if (!in_denylist && flags[DO_REVERT_UNMOUNT])
update_mnt_ns(Clean, false);
}

View File

@@ -24,6 +24,7 @@ static const char *(*get_realpath_sym)(SoInfo *) = NULL;
static void (*soinfo_free)(SoInfo *) = NULL;
static SoInfo *(*find_containing_library)(const void *p) = NULL;
static void (*purge_unused_memory)(void) = NULL;
struct link_map *r_debug_tail = NULL;
static inline const char *get_path(SoInfo *self) {
if (get_realpath_sym)
@@ -150,6 +151,17 @@ static bool solist_init() {
LOGD("%p is purge_unused_memory", (void *)purge_unused_memory);
r_debug_tail = (struct link_map *)getSymbValueByPrefix(linker, "__dl__ZL12r_debug_tail");
if (r_debug_tail == NULL) {
LOGE("Failed to find r_debug_tail __dl__ZL10r_debug_tail");
ElfImg_destroy(linker);
somain = NULL;
return false;
}
g_module_load_counter = (size_t *)getSymbAddress(linker, "__dl__ZL21g_module_load_counter");
if (g_module_load_counter != NULL) LOGD("found symbol g_module_load_counter");
@@ -173,6 +185,58 @@ static bool solist_init() {
return true;
}
/* INFO: This is an AOSP function to remove a link map from
the link map list.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker_gdb_support.cpp#63
*/
static void remove_link_map_from_debug_map(struct link_map *map) {
if (r_debug_tail == map) {
r_debug_tail = map->l_prev;
}
if (map->l_prev) {
map->l_prev->l_next = map->l_next;
}
if (map->l_next) {
map->l_next->l_prev = map->l_prev;
}
}
static struct link_map *find_link_map(SoInfo *si) {
const char *path = get_path(si);
if (path == NULL) {
LOGE("Failed to get path for SoInfo %p", (void *)si);
return NULL;
}
LOGD("Searching for link_map for %s", path);
struct link_map *map = r_debug_tail;
while (map) {
/* INFO: l_name uses the same pointer as realpath function of SoInfo, allowing us
to directly compare the pointers instead of the strings.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/heads/android15-release/linker/linker.cpp#283
*/
if (map->l_name && (uintptr_t)map->l_name == (uintptr_t)path) {
LOGD("Found link_map for %s: %p", path, (void *)map);
return map;
}
map = map->l_next;
}
LOGE("Failed to find link_map for %s", path);
return NULL;
}
/* INFO: find_containing_library returns the SoInfo for the library that contains
that memory inside its limits, hence why named "lib_memory" in ReZygisk. */
bool solist_drop_so_path(void *lib_memory, bool unload) {
@@ -219,11 +283,24 @@ bool solist_drop_so_path(void *lib_memory, bool unload) {
} else if (!unload) {
LOGD("Not unloading so path for %s, only dropping it", path);
/* TODO: call notify_gdb_of_unload(found); (it is static) to avoid leaving traces in
r_debug_tail.
SOURCES:
- https://android.googlesource.com/platform/bionic/+/refs/heads/main/linker/linker_gdb_support.cpp#94
/*
INFO: If the link map is not removed from the list, it gets inconsistent, resulting
in a loop when listing through it, which can be detected. To fix that, we
can remove the map, like expected.
We cannot use the notify_gdb_of_unload function as it is static, and not available
in all linker binaries.
*/
struct link_map *map = find_link_map(found);
if (!map) {
LOGE("Failed to find link map for %s", path);
pdg_protect();
return false;
}
remove_link_map_from_debug_map(map);
/* INFO: unregister_soinfo_tls cannot be used since module might use JNI which may
require TLS, so we cannot remove it. */
soinfo_free(found);

View File

@@ -17,6 +17,7 @@
#include "utils.h"
#include "daemon.h"
#include "misc.h"
#include "socket_utils.h"
#include "monitor.h"
@@ -29,6 +30,15 @@ static bool update_status(const char *message);
char monitor_stop_reason[32];
struct environment_information {
char *root_impl;
char **modules;
uint32_t modules_len;
};
static struct environment_information environment_information64;
static struct environment_information environment_information32;
enum ptracer_tracing_state {
TRACING,
STOPPING,
@@ -158,57 +168,26 @@ bool rezygiskd_listener_init() {
return true;
}
struct __attribute__((__packed__)) MsgHead {
unsigned int cmd;
int length;
};
void rezygiskd_listener_callback() {
while (1) {
struct MsgHead msg = { 0 };
uint8_t cmd;
ssize_t nread = TEMP_FAILURE_RETRY(read(monitor_sock_fd, &cmd, sizeof(cmd)));
if (nread == -1) {
PLOGE("read socket");
size_t nread;
again:
nread = read(monitor_sock_fd, &msg, sizeof(msg));
if ((int)nread == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) goto again;
PLOGE("read socket");
continue;
}
char *msg_data = NULL;
if (msg.length != 0) {
msg_data = malloc(msg.length);
if (!msg_data) {
LOGE("malloc msg data failed");
continue;
}
again_msg_data:
nread = read(monitor_sock_fd, msg_data, msg.length);
if ((int)nread == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) goto again_msg_data;
PLOGE("read socket");
free(msg_data);
continue;
}
continue;
}
switch (msg.cmd) {
switch (cmd) {
case START: {
if (tracing_state == STOPPING) tracing_state = TRACING;
else if (tracing_state == STOPPED) {
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
if (tracing_state == STOPPING) {
LOGI("Continue tracing init");
LOGI("start tracing init");
tracing_state = TRACING;
} else if (tracing_state == STOPPED) {
LOGI("Start tracing init");
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
tracing_state = TRACING;
}
@@ -219,7 +198,7 @@ void rezygiskd_listener_callback() {
}
case STOP: {
if (tracing_state == TRACING) {
LOGI("stop tracing requested");
LOGI("Stop tracing requested");
tracing_state = STOPPING;
strcpy(monitor_stop_reason, "user requested");
@@ -231,7 +210,7 @@ void rezygiskd_listener_callback() {
break;
}
case EXIT: {
LOGI("prepare for exit ...");
LOGI("Prepare for exit ...");
tracing_state = EXITING;
strcpy(monitor_stop_reason, "user requested");
@@ -241,97 +220,167 @@ void rezygiskd_listener_callback() {
break;
}
case ZYGOTE64_INJECTED: {
status64.zygote_injected = true;
update_status(NULL);
break;
}
case ZYGOTE64_INJECTED:
case ZYGOTE32_INJECTED: {
status32.zygote_injected = true;
update_status(NULL);
break;
}
case DAEMON64_SET_INFO: {
LOGD("received daemon64 info %s", msg_data);
/* Will only happen if somehow the daemon restarts */
if (status64.daemon_info) {
free(status64.daemon_info);
status64.daemon_info = NULL;
}
status64.daemon_info = strdup(msg_data);
if (!status64.daemon_info) {
PLOGE("malloc daemon64 info");
break;
}
LOGI("Received Zygote%s injected command", cmd == ZYGOTE64_INJECTED ? "64" : "32");
struct rezygiskd_status *status = cmd == ZYGOTE64_INJECTED ? &status64 : &status32;
status->zygote_injected = true;
update_status(NULL);
break;
}
case DAEMON64_SET_INFO:
case DAEMON32_SET_INFO: {
LOGD("received daemon32 info %s", msg_data);
LOGD("Received ReZygiskd%s info", cmd == DAEMON64_SET_INFO ? "64" : "32");
if (status32.daemon_info) {
free(status32.daemon_info);
status32.daemon_info = NULL;
}
status32.daemon_info = strdup(msg_data);
if (!status32.daemon_info) {
PLOGE("malloc daemon32 info");
break;
}
update_status(NULL);
break;
}
case DAEMON64_SET_ERROR_INFO: {
LOGD("received daemon64 error info %s", msg_data);
status64.daemon_running = false;
if (status64.daemon_error_info) {
free(status64.daemon_error_info);
status64.daemon_error_info = NULL;
}
status64.daemon_error_info = strdup(msg_data);
if (!status64.daemon_error_info) {
PLOGE("malloc daemon64 error info");
uint32_t root_impl_len;
if (read_uint32_t(monitor_sock_fd, &root_impl_len) != sizeof(root_impl_len)) {
LOGE("read ReZygiskd%s root impl len", cmd == DAEMON64_SET_INFO ? "64" : "32");
break;
}
struct environment_information *environment_information = cmd == DAEMON64_SET_INFO ? &environment_information64 : &environment_information32;
if (environment_information->root_impl) {
LOGD("freeing old ReZygiskd%s root impl", cmd == DAEMON64_SET_INFO ? "64" : "32");
free((void *)environment_information->root_impl);
environment_information->root_impl = NULL;
}
environment_information->root_impl = malloc(root_impl_len + 1);
if (environment_information->root_impl == NULL) {
PLOGE("malloc ReZygiskd%s root impl", cmd == DAEMON64_SET_INFO ? "64" : "32");
break;
}
if (read_loop(monitor_sock_fd, (void *)environment_information->root_impl, root_impl_len) != (ssize_t)root_impl_len) {
LOGE("read ReZygiskd%s root impl", cmd == DAEMON64_SET_INFO ? "64" : "32");
free((void *)environment_information->root_impl);
environment_information->root_impl = NULL;
break;
}
environment_information->root_impl[root_impl_len] = '\0';
LOGD("ReZygiskd%s root impl: %s", cmd == DAEMON64_SET_INFO ? "64" : "32", environment_information->root_impl);
if (read_uint32_t(monitor_sock_fd, &environment_information->modules_len) != sizeof(environment_information->modules_len)) {
LOGE("read ReZygiskd%s modules len", cmd == DAEMON64_SET_INFO ? "64" : "32");
free((void *)environment_information->root_impl);
environment_information->root_impl = NULL;
break;
}
if (environment_information->modules) {
LOGD("freeing old ReZygiskd%s modules", cmd == DAEMON64_SET_INFO ? "64" : "32");
for (size_t i = 0; i < environment_information->modules_len; i++) {
free((void *)environment_information->modules[i]);
}
free((void *)environment_information->modules);
environment_information->modules = NULL;
}
environment_information->modules = malloc(environment_information->modules_len * sizeof(char *));
if (environment_information->modules == NULL) {
PLOGE("malloc ReZygiskd%s modules", cmd == DAEMON64_SET_INFO ? "64" : "32");
free((void *)environment_information->root_impl);
environment_information->root_impl = NULL;
break;
}
for (size_t i = 0; i < environment_information->modules_len; i++) {
uint32_t module_name_len;
if (read_uint32_t(monitor_sock_fd, &module_name_len) != sizeof(module_name_len)) {
LOGE("read ReZygiskd%s module name len", cmd == DAEMON64_SET_INFO ? "64" : "32");
goto rezygiskd64_set_info_modules_cleanup;
}
environment_information->modules[i] = malloc(module_name_len + 1);
if (environment_information->modules[i] == NULL) {
PLOGE("malloc ReZygiskd%s module name", cmd == DAEMON64_SET_INFO ? "64" : "32");
goto rezygiskd64_set_info_modules_cleanup;
}
if (read_loop(monitor_sock_fd, (void *)environment_information->modules[i], module_name_len) != (ssize_t)module_name_len) {
LOGE("read ReZygiskd%s module name", cmd == DAEMON64_SET_INFO ? "64" : "32");
goto rezygiskd64_set_info_modules_cleanup;
}
environment_information->modules[i][module_name_len] = '\0';
LOGD("ReZygiskd%s module %zu: %s", cmd == DAEMON64_SET_INFO ? "64" : "32", i, environment_information->modules[i]);
continue;
rezygiskd64_set_info_modules_cleanup:
free((void *)environment_information->root_impl);
environment_information->root_impl = NULL;
for (size_t j = 0; j < i; j++) {
free((void *)environment_information->modules[j]);
}
free((void *)environment_information->modules);
environment_information->modules = NULL;
break;
}
update_status(NULL);
break;
}
case DAEMON64_SET_ERROR_INFO:
case DAEMON32_SET_ERROR_INFO: {
LOGD("received daemon32 error info %s", msg_data);
LOGD("Received ReZygiskd%s error info", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
status32.daemon_running = false;
if (status32.daemon_error_info) {
free(status32.daemon_error_info);
status32.daemon_error_info = NULL;
}
status32.daemon_error_info = strdup(msg_data);
if (!status32.daemon_error_info) {
PLOGE("malloc daemon32 error info");
uint32_t error_info_len;
if (read_uint32_t(monitor_sock_fd, &error_info_len) != sizeof(error_info_len)) {
LOGE("read ReZygiskd%s error info len", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
break;
}
struct rezygiskd_status *status = cmd == DAEMON64_SET_ERROR_INFO ? &status64 : &status32;
if (status->daemon_error_info) {
LOGD("freeing old ReZygiskd%s error info", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
free(status->daemon_error_info);
status->daemon_error_info = NULL;
}
status->daemon_error_info = malloc(error_info_len + 1);
if (status->daemon_error_info == NULL) {
PLOGE("malloc ReZygiskd%s error info", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
break;
}
if (read_loop(monitor_sock_fd, status->daemon_error_info, error_info_len) != (ssize_t)error_info_len) {
LOGE("read ReZygiskd%s error info", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32");
free(status->daemon_error_info);
status->daemon_error_info = NULL;
break;
}
status->daemon_error_info[error_info_len] = '\0';
LOGD("ReZygiskd%s error info: %s", cmd == DAEMON64_SET_ERROR_INFO ? "64" : "32", status->daemon_error_info);
update_status(NULL);
break;
@@ -347,8 +396,6 @@ void rezygiskd_listener_callback() {
}
}
if (msg_data) free(msg_data);
break;
}
}
@@ -423,51 +470,49 @@ static bool ensure_daemon_created(bool is_64bit) {
return true;
}
#define CHECK_DAEMON_EXIT(abi) \
if (status##abi.supported && pid == status##abi.daemon_pid) { \
char status_str[64]; \
parse_status(sigchld_status, status_str, sizeof(status_str)); \
\
LOGW("daemon" #abi " pid %d exited: %s", pid, status_str); \
status##abi.daemon_running = false; \
\
if (!status##abi.daemon_error_info) { \
status##abi.daemon_error_info = (char *)malloc(strlen(status_str) + 1); \
if (!status##abi.daemon_error_info) { \
LOGE("malloc daemon" #abi " error info failed"); \
\
return; \
} \
\
memcpy(status##abi.daemon_error_info, status_str, strlen(status_str) + 1); \
} \
\
update_status(NULL); \
continue; \
#define CHECK_DAEMON_EXIT(abi) \
if (status##abi.supported && pid == status##abi.daemon_pid) { \
char status_str[64]; \
parse_status(sigchld_status, status_str, sizeof(status_str)); \
\
LOGW("daemon" #abi " pid %d exited: %s", pid, status_str); \
status##abi.daemon_running = false; \
\
if (!status##abi.daemon_error_info) { \
status##abi.daemon_error_info = strdup(status_str); \
if (!status##abi.daemon_error_info) { \
LOGE("malloc daemon" #abi " error info failed"); \
\
return; \
} \
} \
\
continue; \
}
#define PRE_INJECT(abi, is_64) \
if (strcmp(program, "/system/bin/app_process" # abi) == 0) { \
tracer = "./bin/zygisk-ptrace" # abi; \
\
if (should_stop_inject ## abi()) { \
LOGW("zygote" # abi " restart too much times, stop injecting"); \
\
tracing_state = STOPPING; \
memcpy(monitor_stop_reason, "zygote crashed", sizeof("zygote crashed")); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
} \
if (!ensure_daemon_created(is_64)) { \
LOGW("daemon" #abi " not running, stop injecting"); \
\
tracing_state = STOPPING; \
memcpy(monitor_stop_reason, "daemon not running", sizeof("daemon not running")); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
} \
#define PRE_INJECT(abi, is_64) \
if (strcmp(program, "/system/bin/app_process" # abi) == 0) { \
tracer = "./bin/zygisk-ptrace" # abi; \
\
if (should_stop_inject ## abi()) { \
LOGW("Zygote" # abi " restart too much times, stop injecting"); \
\
tracing_state = STOPPING; \
strcpy(monitor_stop_reason, "Zygote crashed"); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
} \
\
if (!ensure_daemon_created(is_64)) { \
LOGW("ReZygiskd " #abi "-bit not running, stop injecting"); \
\
tracing_state = STOPPING; \
strcpy(monitor_stop_reason, "ReZygiskd not running"); \
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
\
break; \
} \
}
int sigchld_signal_fd;
@@ -661,8 +706,6 @@ void sigchld_listener_callback() {
}
}
} while (false);
update_status(NULL);
} else {
char status_str[64];
parse_status(sigchld_status, status_str, sizeof(status_str));
@@ -702,27 +745,20 @@ static char post_section[1024];
#define WRITE_STATUS_ABI(suffix) \
if (status ## suffix.supported) { \
strcat(status_text, " zygote" # suffix ": "); \
if (tracing_state != TRACING) strcat(status_text, "❓ unknown, "); \
else if (status ## suffix.zygote_injected) strcat(status_text, "😋 injected, "); \
else strcat(status_text, "❌ not injected, "); \
strcat(status_text, ", ReZygisk " # suffix "-bit: "); \
\
strcat(status_text, "daemon" # suffix ": "); \
if (status ## suffix.daemon_running) { \
strcat(status_text, "😋 running "); \
if (tracing_state != TRACING) strcat(status_text, "❌"); \
else if (status ## suffix.zygote_injected && status ## suffix.daemon_running) \
strcat(status_text, "✅"); \
else strcat(status_text, "⚠️"); \
\
if (status ## suffix.daemon_info != NULL) { \
strcat(status_text, "("); \
strcat(status_text, status ## suffix.daemon_info); \
strcat(status_text, ")"); \
} \
} else { \
strcat(status_text, "❌ crashed "); \
\
if (status ## suffix.daemon_error_info != NULL) { \
strcat(status_text, "("); \
if (!status ## suffix.daemon_running) { \
if (status ## suffix.daemon_error_info) { \
strcat(status_text, "(ReZygiskd: "); \
strcat(status_text, status ## suffix.daemon_error_info); \
strcat(status_text, ")"); \
} else { \
strcat(status_text, "(ReZygiskd: not running)"); \
} \
} \
}
@@ -742,41 +778,106 @@ static bool update_status(const char *message) {
return true;
}
char status_text[1024] = "monitor: ";
char status_text[256] = "Monitor: ";
switch (tracing_state) {
case TRACING: {
strcat(status_text, "😋 tracing");
strcat(status_text, "");
break;
}
case STOPPING: [[fallthrough]];
case STOPPED: {
strcat(status_text, "❌ stopped");
strcat(status_text, "");
break;
}
case EXITING: {
strcat(status_text, " exited");
strcat(status_text, "");
break;
}
}
if (tracing_state != TRACING && monitor_stop_reason[0] != '\0') {
strcat(status_text, " (");
strcat(status_text, monitor_stop_reason);
strcat(status_text, ")");
}
strcat(status_text, ",");
WRITE_STATUS_ABI(64)
WRITE_STATUS_ABI(32)
fprintf(prop, "%s[%s] %s", pre_section, status_text, post_section);
fclose(prop);
if (environment_information64.root_impl || environment_information32.root_impl) {
FILE *json = fopen("/data/adb/rezygisk/state.json", "w");
if (json == NULL) {
PLOGE("failed to open state.json");
return false;
}
fprintf(json, "{\n");
fprintf(json, " \"root\": \"%s\",\n", environment_information64.root_impl ? environment_information64.root_impl : environment_information32.root_impl);
fprintf(json, " \"monitor\": {\n");
fprintf(json, " \"state\": \"%d\"", tracing_state);
if (monitor_stop_reason[0] != '\0') fprintf(json, ",\n \"reason\": \"%s\",\n", monitor_stop_reason);
else fprintf(json, "\n");
fprintf(json, " },\n");
fprintf(json, " \"rezygiskd\": {\n");
if (status64.supported) {
fprintf(json, " \"64\": {\n");
fprintf(json, " \"state\": %d,\n", status64.daemon_running);
if (status64.daemon_error_info) fprintf(json, " \"reason\": \"%s\",\n", status64.daemon_error_info);
fprintf(json, " \"modules\": [");
if (environment_information64.modules) for (uint32_t i = 0; i < environment_information64.modules_len; i++) {
if (i > 0) fprintf(json, ", ");
fprintf(json, "\"%s\"", environment_information64.modules[i]);
}
fprintf(json, "]\n");
fprintf(json, " }");
if (status32.supported) fprintf(json, ",\n");
else fprintf(json, "\n");
}
if (status32.supported) {
fprintf(json, " \"32\": {\n");
fprintf(json, " \"state\": %d,\n", status32.daemon_running);
if (status32.daemon_error_info) fprintf(json, " \"reason\": \"%s\",\n", status32.daemon_error_info);
fprintf(json, " \"modules\": [");
if (environment_information32.modules) for (uint32_t i = 0; i < environment_information32.modules_len; i++) {
if (i > 0) fprintf(json, ", ");
fprintf(json, "\"%s\"", environment_information32.modules[i]);
}
fprintf(json, "]\n");
fprintf(json, " }\n");
}
fprintf(json, " },\n");
fprintf(json, " \"zygote\": {\n");
if (status64.supported) {
fprintf(json, " \"64\": %d", status64.zygote_injected);
if (status32.zygote_injected) fprintf(json, ",\n");
else fprintf(json, "\n");
}
if (status32.zygote_injected) {
fprintf(json, " \"32\": %d\n", status32.zygote_injected);
}
fprintf(json, " }\n");
fprintf(json, "}\n");
fclose(json);
} else {
if (remove("/data/adb/rezygisk/state.json") == -1) {
PLOGE("failed to remove state.json");
}
}
LOGI("status updated: %s", status_text);
return true;
}
@@ -821,7 +922,7 @@ static bool prepare_environment() {
return false;
}
return update_status(NULL);
return true;
}
void init_monitor() {
@@ -864,7 +965,23 @@ void init_monitor() {
if (status32.daemon_info) free(status32.daemon_info);
if (status32.daemon_error_info) free(status32.daemon_error_info);
LOGI("exit");
if (environment_information64.root_impl) free((void *)environment_information64.root_impl);
if (environment_information64.modules) {
for (uint32_t i = 0; i < environment_information64.modules_len; i++) {
free((void *)environment_information64.modules[i]);
}
free((void *)environment_information64.modules);
}
if (environment_information32.root_impl) free((void *)environment_information32.root_impl);
if (environment_information32.modules) {
for (uint32_t i = 0; i < environment_information32.modules_len; i++) {
free((void *)environment_information32.modules[i]);
}
free((void *)environment_information32.modules);
}
LOGI("Terminating ReZygisk monitor");
}
int send_control_command(enum rezygiskd_command cmd) {
@@ -880,9 +997,10 @@ int send_control_command(enum rezygiskd_command cmd) {
socklen_t socklen = sizeof(sa_family_t) + sun_path_len;
ssize_t nsend = sendto(sockfd, (void *)&cmd, sizeof(cmd), 0, (struct sockaddr *)&addr, socklen);
uint8_t cmd_op = cmd;
ssize_t nsend = sendto(sockfd, (void *)&cmd_op, sizeof(cmd_op), 0, (struct sockaddr *)&addr, socklen);
close(sockfd);
return nsend != sizeof(cmd) ? -1 : 0;
return nsend != sizeof(cmd_op) ? -1 : 0;
}

View File

@@ -91,101 +91,106 @@ async function getModuleNames(modules) {
const unameCmd = await exec('/system/bin/uname -r')
if (unameCmd.errno !== 0) return setError('WebUI', unameCmd.stderr)
document.getElementById('kernel_version_div').innerHTML = unameCmd.stdout
console.log('[rezygisk.js] Kernel version: ', unameCmd.stdout)
document.getElementById('kernel_version_div').innerHTML = unameCmd.stdout.trim()
console.log('[rezygisk.js] Kernel version: ', unameCmd.stdout.trim())
const catCmd = await exec('/system/bin/cat /data/adb/rezygisk/module.prop')
console.log(`[rezygisk.js] ReZygisk module infomation:\n${catCmd.stdout}`)
let expectedWorking = 0
let actuallyWorking = 0
if (catCmd.errno !== 0) {
console.error('[rezygisk.js] Failed to retrieve ReZygisk module information:', catCmd.stderr)
const ReZygiskInfo = {
rootImpl: null,
monitor: null,
zygotes: [],
daemons: []
}
rezygisk_state.innerHTML = translations.page.home.status.notWorking
rezygisk_icon_state.innerHTML = '<img class="dimc" src="assets/cross.svg">'
if (catCmd.errno === 0) {
/* INFO: Just ensure that they won't appear unless there's info */
rootCss.style.setProperty('--bright', '#766000')
/* INFO: Hide zygote divs */
zygote_divs.forEach((zygote_div) => {
zygote_div.style.display = 'none'
})
version.innerHTML = catCmd.stdout.split('\n').find((line) => line.startsWith('version=')).substring('version='.length).trim()
loading_screen.style.display = 'none'
bottom_nav.style.display = 'flex'
let moduleInfo = catCmd.stdout.split('\n').find((line) => line.startsWith('description=')).substring('description='.length).split('[')[1].split(']')[0]
return;
}
const daemonModules = []
moduleInfo.match(/\(([^)]+)\)/g).forEach((area) => {
moduleInfo = moduleInfo.replace(area, ',')
/* INFO: Just ensure that they won't appear unless there's info */
zygote_divs.forEach((zygote_div) => {
zygote_div.style.display = 'none'
})
const info = area.substring(1, area.length - 1).split(', ')
if (info.length === 1) return; /* INFO: undefined as object */
version.innerHTML = catCmd.stdout.split('\n').find((line) => line.startsWith('version=')).substring('version='.length).trim()
const rootImpl = info[0].substring('Root: '.length)
const stateCmd = await exec('/system/bin/cat /data/adb/rezygisk/state.json')
if (stateCmd.errno !== 0) {
console.error('[rezygisk.js] Failed to retrieve ReZygisk state information:', stateCmd.stderr)
info[1] = info[1].substring('Modules: '.length)
const modules = info.slice(1, info.length)
rezygisk_state.innerHTML = translations.page.home.status.notWorking
rezygisk_icon_state.innerHTML = '<img class="dimc" src="assets/cross.svg">'
ReZygiskInfo.rootImpl = rootImpl
if (modules[0] !== 'None') daemonModules.push(modules)
rootCss.style.setProperty('--bright', '#766000')
/* INFO: Hide zygote divs */
zygote_divs.forEach((zygote_div) => {
zygote_div.style.display = 'none'
})
const infoArea = moduleInfo.split(', ')
infoArea.forEach((info) => {
if (info.startsWith('monitor:')) {
ReZygiskInfo.monitor = info.substring('monitor: X '.length).trim()
}
loading_screen.style.display = 'none'
bottom_nav.style.display = 'flex'
if (info.startsWith('zygote')) {
ReZygiskInfo.zygotes.push({
bits: info.substring('zygote'.length, 'zygote'.length + 'XX'.length),
state: info.substring('zygoteXX: X '.length).trim()
})
}
return;
}
if (info.startsWith('daemon')) {
ReZygiskInfo.daemons.push({
bits: info.substring('daemon'.length, 'daemon'.length + 'XX'.length),
state: info.substring('daemonXX: X '.length).trim(),
modules: daemonModules[ReZygiskInfo.daemons.length] || []
})
}
})
const ReZygiskState = JSON.parse(stateCmd.stdout)
switch (ReZygiskInfo.monitor) {
case 'tracing': monitor_status.innerHTML = translations.page.actions.status.tracing; break;
case 'stopping': monitor_status.innerHTML = translations.page.actions.status.stopping; break;
case 'stopped': monitor_status.innerHTML = translations.page.actions.status.stopped; break;
case 'exiting': monitor_status.innerHTML = translations.page.actions.status.exiting; break;
default: monitor_status.innerHTML = translations.page.actions.status.unknown;
root_impl.innerHTML = ReZygiskState.root
switch (ReZygiskState.monitor.state) {
case 0: monitor_status.innerHTML = translations.page.actions.status.tracing; break;
case 1: monitor_status.innerHTML = translations.page.actions.status.stopping; break;
case 2: monitor_status.innerHTML = translations.page.actions.status.stopped; break;
case 3: monitor_status.innerHTML = translations.page.actions.status.exiting; break;
default: monitor_status.innerHTML = translations.page.actions.status.unknown;
}
const expectedWorking = (ReZygiskState.zygote['64'] !== undefined ? 1 : 0) + (ReZygiskState.zygote['32'] !== undefined ? 1 : 0)
let actuallyWorking = 0
if (ReZygiskState.zygote['64'] !== undefined) {
const zygote64 = ReZygiskState.zygote['64']
zygote_divs[0].style.display = 'block'
switch (zygote64) {
case 1: {
zygote_status_divs[0].innerHTML = translations.page.home.info.zygote.injected
actuallyWorking++
break
}
case 0: zygote_status_divs[0].innerHTML = translations.page.home.info.zygote.notInjected; break
default: zygote_status_divs[0].innerHTML = translations.page.home.info.zygote.unknown
}
}
expectedWorking = ReZygiskInfo.zygotes.length
if (ReZygiskState.zygote['32'] !== undefined) {
const zygote32 = ReZygiskState.zygote['32']
for (let i = 0; i < ReZygiskInfo.zygotes.length; i++) {
const zygote = ReZygiskInfo.zygotes[i]
/* INFO: Not used ATM */
/* const daemon = ReZygiskInfo.daemons[i] */
zygote_divs[1].style.display = 'block'
const zygoteDiv = zygote_divs[zygote.bits === '64' ? 0 : 1]
const zygoteStatusDiv = zygote_status_divs[zygote.bits === '64' ? 0 : 1]
switch (zygote32) {
case 1: {
zygote_status_divs[1].innerHTML = translations.page.home.info.zygote.injected
zygoteDiv.style.display = 'block'
actuallyWorking++
switch (zygote.state) {
case 'injected': {
zygoteStatusDiv.innerHTML = translations.page.home.info.zygote.injected;
actuallyWorking++
break;
}
case 'not injected': zygoteStatusDiv.innerHTML = translations.page.home.info.zygote.notInjected; break;
default: zygoteStatusDiv.innerHTML = translations.page.home.info.zygote.unknown;
break
}
case 0: zygote_status_divs[1].innerHTML = translations.page.home.info.zygote.notInjected; break
default: zygote_status_divs[1].innerHTML = translations.page.home.info.zygote.unknown
}
}
@@ -203,24 +208,24 @@ async function getModuleNames(modules) {
rezygisk_icon_state.innerHTML = '<img class="brightc" src="assets/warn.svg">'
}
if (ReZygiskInfo.rootImpl)
root_impl.innerHTML = ReZygiskInfo.rootImpl
const all_modules = []
ReZygiskInfo.daemons.forEach((daemon) => {
daemon.modules.forEach((module_id) => {
const module = all_modules.find((mod) => mod.id === module_id)
Object.keys(ReZygiskState.rezygiskd).forEach((daemon_bit) => {
const daemon = ReZygiskState.rezygiskd[daemon_bit]
if (module) {
module.bitsUsed.push(daemon.bits)
} else {
all_modules.push({
id: module_id,
name: null,
bitsUsed: [ daemon.bits ]
})
}
})
if (daemon.modules && daemon.modules.length > 0) {
daemon.modules.forEach((module_id) => {
const module = all_modules.find((mod) => mod.id === module_id)
if (module) {
module.bitsUsed.push(daemon_bit)
} else {
all_modules.push({
id: module_id,
name: null,
bitsUsed: [ daemon_bit ]
})
}
})
}
})
if (all_modules.length !== 0) {
@@ -251,7 +256,6 @@ async function getModuleNames(modules) {
loading_screen.style.display = 'none'
bottom_nav.style.display = 'flex'
const start_time = Number(localStorage.getItem('/system/boot-time'))
console.log('[rezygisk.js] boot time: ', Date.now() - start_time, 'ms')
localStorage.removeItem('/system/boot_time')

View File

@@ -261,11 +261,6 @@ static int spawn_companion(char *restrict argv[], char *restrict name, int lib_f
exit(0);
}
struct __attribute__((__packed__)) MsgHead {
unsigned int cmd;
int length;
};
/* WARNING: Dynamic memory based */
void zygiskd_start(char *restrict argv[]) {
/* INFO: When implementation is None or Multiple, it won't set the values
@@ -276,118 +271,42 @@ void zygiskd_start(char *restrict argv[]) {
struct root_impl impl;
get_impl(&impl);
if (impl.impl == None || impl.impl == Multiple) {
char *msg_data = NULL;
unix_datagram_sendto(CONTROLLER_SOCKET, &(uint8_t){ DAEMON_SET_ERROR_INFO }, sizeof(uint8_t));
if (impl.impl == None) msg_data = "Unsupported environment: Unknown root implementation";
else msg_data = "Unsupported environment: Multiple root implementations found";
const char *msg = NULL;
if (impl.impl == None) msg = "Unsupported environment: Unknown root implementation";
else msg = "Unsupported environment: Multiple root implementations found";
struct MsgHead msg = {
.cmd = DAEMON_SET_ERROR_INFO,
.length = (int)strlen(msg_data) + 1
};
LOGE("%s", msg);
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
unix_datagram_sendto(CONTROLLER_SOCKET, msg_data, (size_t)msg.length);
uint32_t msg_len = (uint32_t)strlen(msg);
unix_datagram_sendto(CONTROLLER_SOCKET, &msg_len, sizeof(msg_len));
unix_datagram_sendto(CONTROLLER_SOCKET, (void *)msg, msg_len);
exit(EXIT_FAILURE);
} else {
enum Architecture arch = get_arch();
load_modules(arch, &context);
char *module_list = NULL;
size_t module_list_len = 0;
if (context.len == 0) {
module_list = strdup("None");
module_list_len = strlen("None");
} else {
for (size_t i = 0; i < context.len; i++) {
if (i != context.len - 1) {
char *tmp_module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + strlen(", ") + 1);
if (tmp_module_list == NULL) {
LOGE("Failed reallocating memory for module list.\n");
char *kmsg_failure = "Failed reallocating memory for module list";
struct MsgHead msg = {
.cmd = DAEMON_SET_ERROR_INFO,
.length = (int)strlen(kmsg_failure) + 1
};
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
unix_datagram_sendto(CONTROLLER_SOCKET, kmsg_failure, (size_t)msg.length);
free(module_list);
free_modules(&context);
exit(EXIT_FAILURE);
}
module_list = tmp_module_list;
strcpy(module_list + module_list_len, context.modules[i].name);
module_list_len += strlen(context.modules[i].name);
strcpy(module_list + module_list_len, ", ");
module_list_len += strlen(", ");
} else {
char *tmp_module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + 1);
if (tmp_module_list == NULL) {
LOGE("Failed reallocating memory for module list.\n");
char *kmsg_failure = "Failed reallocating memory for module list";
struct MsgHead msg = {
.cmd = DAEMON_SET_ERROR_INFO,
.length = (int)strlen(kmsg_failure) + 1
};
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
unix_datagram_sendto(CONTROLLER_SOCKET, kmsg_failure, (size_t)msg.length);
free(module_list);
free_modules(&context);
exit(EXIT_FAILURE);
}
module_list = tmp_module_list;
strcpy(module_list + module_list_len, context.modules[i].name);
module_list_len += strlen(context.modules[i].name);
}
}
}
unix_datagram_sendto(CONTROLLER_SOCKET, &(uint8_t){ DAEMON_SET_INFO }, sizeof(uint8_t));
char impl_name[LONGEST_ROOT_IMPL_NAME];
stringify_root_impl_name(impl, impl_name);
size_t msg_length = strlen("Root: , Modules: ") + strlen(impl_name) + module_list_len + 1;
uint32_t root_impl_len = (uint32_t)strlen(impl_name);
unix_datagram_sendto(CONTROLLER_SOCKET, &root_impl_len, sizeof(root_impl_len));
unix_datagram_sendto(CONTROLLER_SOCKET, impl_name, root_impl_len);
struct MsgHead msg = {
.cmd = DAEMON_SET_INFO,
.length = (int)msg_length
};
uint32_t modules_len = (uint32_t)context.len;
unix_datagram_sendto(CONTROLLER_SOCKET, &modules_len, sizeof(modules_len));
char *msg_data = malloc(msg_length);
if (msg_data == NULL) {
LOGE("Failed allocating memory for message data.\n");
char *kmsg_failure = "Failed allocating memory for message data";
msg.cmd = DAEMON_SET_ERROR_INFO;
msg.length = (int)strlen(kmsg_failure) + 1;
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
unix_datagram_sendto(CONTROLLER_SOCKET, kmsg_failure, (size_t)msg.length);
free(module_list);
free_modules(&context);
exit(EXIT_FAILURE);
for (size_t i = 0; i < context.len; i++) {
uint32_t module_name_len = (uint32_t)strlen(context.modules[i].name);
unix_datagram_sendto(CONTROLLER_SOCKET, &module_name_len, sizeof(module_name_len));
unix_datagram_sendto(CONTROLLER_SOCKET, context.modules[i].name, module_name_len);
}
snprintf(msg_data, msg_length, "Root: %s, Modules: %s", impl_name, module_list);
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
unix_datagram_sendto(CONTROLLER_SOCKET, msg_data, msg_length);
free(msg_data);
free(module_list);
LOGI("Sent root implementation and modules information to controller socket");
}
int socket_fd = create_daemon_socket();
@@ -427,12 +346,7 @@ void zygiskd_start(char *restrict argv[]) {
switch (action) {
case PingHeartbeat: {
struct MsgHead msg = {
.cmd = ZYGOTE_INJECTED,
.length = 0
};
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
unix_datagram_sendto(CONTROLLER_SOCKET, &(uint8_t){ ZYGOTE_INJECTED }, sizeof(uint8_t));
break;
}
@@ -447,22 +361,7 @@ void zygiskd_start(char *restrict argv[]) {
break;
}
case SystemServerStarted: {
struct MsgHead msg = {
.cmd = SYSTEM_SERVER_STARTED,
.length = 0
};
unix_datagram_sendto(CONTROLLER_SOCKET, &msg, sizeof(struct MsgHead));
if (impl.impl == None || impl.impl == Multiple) {
LOGI("Unsupported environment detected. Exiting.\n");
close(client_fd);
close(socket_fd);
free_modules(&context);
exit(1);
}
unix_datagram_sendto(CONTROLLER_SOCKET, &(uint8_t){ SYSTEM_SERVER_STARTED }, sizeof(uint8_t));
break;
}