diff options
author | dec05eba <dec05eba@protonmail.com> | 2025-03-08 16:22:51 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2025-03-08 16:22:51 +0100 |
commit | 49c4d673661e968831390fabfe0ce69716e5e1c7 (patch) | |
tree | 427c1ea7db5af5d8705b609ed28fb3c449508b8f | |
parent | 616b174b818f26f41a3e114cfe3deb38259398b7 (diff) |
Fix global hotkeys not working on some systems (filesystem is mounted with nosuid
-rw-r--r-- | main.c | 201 |
1 files changed, 163 insertions, 38 deletions
@@ -32,16 +32,23 @@ static bool readlink_realpath(const char *filepath, char *buffer) { return true; } -static bool file_has_sys_admin_capability(const char *filepath) { +static bool file_has_capabilities(const char *filepath, const cap_value_t *caps, int num_caps) { cap_t cap = cap_get_file(filepath); if(!cap) return false; - cap_flag_value_t res = CAP_CLEAR; - cap_get_flag(cap, CAP_SYS_ADMIN, CAP_PERMITTED, &res); - bool cap_set = res == CAP_SET; + bool has_caps = true; + for(int i = 0; i < num_caps; ++i) { + cap_flag_value_t res = CAP_CLEAR; + cap_get_flag(cap, caps[i], CAP_PERMITTED, &res); + if(res != CAP_SET) { + has_caps = false; + break; + } + } + cap_free(cap); - return cap_set; + return has_caps; } static bool file_set_capabilities(const char *filepath, const cap_value_t *caps_to_set, int num_caps_to_set) { @@ -64,6 +71,89 @@ static bool file_set_capabilities(const char *filepath, const cap_value_t *caps_ return res == 0; } +static const char* get_column(const char *str, int size, int *column_size, int column) { + *column_size = 0; + int current_column = 0; + int i = 0; + for(; column > 0 && i < size; ++i) { + if(str[i] == ' ') { + ++current_column; + if(current_column == column) { + ++i; + break; + } + } + } + + const int column_start = i; + for(; i < size; ++i) { + if(str[i] == ' ') { + *column_size = i - column_start; + return str + column_start; + } + } + + *column_size = size; + return str; +} + +static bool mount_has_flag(const char *flags, int flags_size, const char *flag_to_find) { + if(flags_size == 0) + return false; + + const int flag_to_find_size = strlen(flag_to_find); + int offset = 0; + for(;;) { + const char *p = memmem(flags + offset, flags_size - offset, flag_to_find, flag_to_find_size); + if(!p) + return false; + + offset = p - flags; + const char prev_char = offset > 0 ? flags[offset - 1] : ','; + const char next_char = offset + flag_to_find_size < flags_size ? flags[offset + flag_to_find_size] : ','; + if((prev_char == ',' || prev_char == '\n') && (next_char == ',' || next_char == '\n')) + return true; + } + + return false; +} + +static bool mount_has_setuid_permission(const char *path) { + FILE *f = fopen("/proc/mounts", "rb"); + if(!f) { + fprintf(stderr, "Error: failed to open /proc/mounts\n"); + // Assume it's ok and just continue + return true; + } + + // Assume there is permission if it can't be found + bool has_permission = true; + const int path_size = strlen(path); + char line[1024]; + while(fgets(line, sizeof(line), f)) { + const int line_size = strlen(line); + int mount_point_size = 0; + const char *mount_point = get_column(line, line_size, &mount_point_size, 1); + if(!mount_point) + continue; + + if(mount_point && mount_point_size == path_size && memcmp(mount_point, path, mount_point_size) == 0) { + int flags_size = 0; + const char *flags = get_column(line, line_size, &flags_size, 3); + if(!flags) + continue; + + const bool has_rw = mount_has_flag(flags, flags_size, "rw"); + const bool has_nosuid = mount_has_flag(flags, flags_size, "nosuid"); + has_permission = has_rw && !has_nosuid; + break; + } + } + + fclose(f); + return has_permission; +} + static bool create_local_kms_server_proxy_directory(const char *home) { char path[PATH_MAX]; int err; @@ -139,14 +229,7 @@ static bool copy_file_atomic(const char *source_path, const char *dest_path) { return res; } -static void usage(void) { - fprintf(stderr, "usage alt.1: kms-server-proxy <initial_socket_path> <card_path> <user_homepath>\n"); - fprintf(stderr, "usage alt.2: kms-server-proxy setup-gsr-ui [user_homepath]\n"); - fprintf(stderr, "usage alt.3: kms-server-proxy launch-gsr-global-hotkeys <user_homepath> [args...]\n"); - exit(1); -} - -static bool set_gsr_files_set_permissions_and_capabilities(const char *kms_server_proxy_local_filepath) { +static bool gsr_files_set_permissions_and_capabilities(const char *kms_server_proxy_local_filepath) { /* owner: read/write/execute, group: read/execute, public: read/execute */ if(chmod(kms_server_proxy_local_filepath, 0755) != 0) { fprintf(stderr, "Error: failed to set %s permissions\n", kms_server_proxy_local_filepath); @@ -158,7 +241,7 @@ static bool set_gsr_files_set_permissions_and_capabilities(const char *kms_serve return false; } - if(!file_set_capabilities(kms_server_proxy_local_filepath, (const cap_value_t[]){ CAP_SYS_ADMIN, CAP_SETFCAP, CAP_SETUID }, 3)) { + if(!file_set_capabilities(kms_server_proxy_local_filepath, (const cap_value_t[]){ CAP_SYS_ADMIN, CAP_SETUID }, 2)) { fprintf(stderr, "Error: failed to set kms-server-proxy capabilities\n"); return false; } @@ -167,17 +250,13 @@ static bool set_gsr_files_set_permissions_and_capabilities(const char *kms_serve } /* |gsr_global_hotkeys_local_filepath| can be NULL */ -static bool setup_local_gsr_files(const char *user_homepath, const char *kms_server_proxy_local_filepath, const char *kms_server_proxy_home) { +static bool setup_local_gsr_files(const char *user_homepath, const char *kms_server_proxy_home) { if(!create_local_kms_server_proxy_directory(user_homepath)) { fprintf(stderr, "Error: failed to create ~/.local/share/gpu-screen-recorder directory\n"); return false; } - if(!copy_file_atomic(KMS_SERVER_PROXY_FILEPATH, kms_server_proxy_local_filepath)) { - fprintf(stderr, "Error: failed to copy kms-server-proxy to %s\n", kms_server_proxy_local_filepath); - return false; - } - + remove(kms_server_proxy_home); if(!copy_file_atomic(KMS_SERVER_PROXY_FILEPATH, kms_server_proxy_home)) { fprintf(stderr, "Error: failed to copy kms-server-proxy to %s\n", kms_server_proxy_home); return false; @@ -186,13 +265,20 @@ static bool setup_local_gsr_files(const char *user_homepath, const char *kms_ser return true; } -static int setup_gsr_ui(const char *user_homepath) { - char self_path[PATH_MAX]; - if(!readlink_realpath("/proc/self/exe", self_path)) { - fprintf(stderr, "failed to resolve /proc/self/exe\n"); - return 1; +static bool get_local_kms_server_proxy_filepath(char *output, size_t output_size, const char *user_homepath) { + if(mount_has_setuid_permission(user_homepath)) { + snprintf(output, output_size, "%s/.local/share/gpu-screen-recorder/kms-server-proxy-2", user_homepath); + return true; + } else if(mount_has_setuid_permission("/")) { + snprintf(output, output_size, "/usr/bin/kms-server-proxy-2"); + return true; + } else { + fprintf(stderr, "Error: neither %s nor / has both read-write permission and setuid permission\n", user_homepath); + return false; } +} +static int setup_gsr_ui(const char *user_homepath) { if(!user_homepath) { user_homepath = getenv("HOME"); if(!user_homepath) @@ -200,8 +286,7 @@ static int setup_gsr_ui(const char *user_homepath) { } char kms_server_proxy_local_filepath[PATH_MAX]; - /* Update kms-server-proxy-N to kms-server-proxy-N+1 on update (update that needs to run before this program launches itself) */ - snprintf(kms_server_proxy_local_filepath, sizeof(kms_server_proxy_local_filepath), "%s/.local/share/gpu-screen-recorder/kms-server-proxy-2", user_homepath); + get_local_kms_server_proxy_filepath(kms_server_proxy_local_filepath, sizeof(kms_server_proxy_local_filepath), user_homepath); /* Weird-ass distros like openSUSE only allow pkexec for files in $HOME!!! */ char kms_server_proxy_home[PATH_MAX]; @@ -209,12 +294,18 @@ static int setup_gsr_ui(const char *user_homepath) { if(geteuid() == 0) { /* is current user root? */ remove(kms_server_proxy_home); - if(!set_gsr_files_set_permissions_and_capabilities(kms_server_proxy_local_filepath)) + remove(kms_server_proxy_local_filepath); + if(!copy_file_atomic(KMS_SERVER_PROXY_FILEPATH, kms_server_proxy_local_filepath)) { + fprintf(stderr, "Error: failed to copy kms-server-proxy to %s\n", kms_server_proxy_local_filepath); + return 1; + } + + if(!gsr_files_set_permissions_and_capabilities(kms_server_proxy_local_filepath)) return 1; return 0; } else { - if(!setup_local_gsr_files(user_homepath, kms_server_proxy_local_filepath, kms_server_proxy_home)) + if(!setup_local_gsr_files(user_homepath, kms_server_proxy_home)) return 1; const char *args[] = { "pkexec", kms_server_proxy_home, "setup-gsr-ui", user_homepath, NULL }; @@ -225,6 +316,24 @@ static int setup_gsr_ui(const char *user_homepath) { } } +static bool is_setup(const char *user_homepath) { + if(!user_homepath) { + user_homepath = getenv("HOME"); + if(!user_homepath) + user_homepath = "/tmp"; + } + + char kms_server_proxy_local_filepath[PATH_MAX]; + get_local_kms_server_proxy_filepath(kms_server_proxy_local_filepath, sizeof(kms_server_proxy_local_filepath), user_homepath); + + if(!file_has_capabilities(kms_server_proxy_local_filepath, (const cap_value_t[]){ CAP_SYS_ADMIN, CAP_SETUID }, 2)) { + fprintf(stderr, "Error: kms-server-proxy (%s) is either not installed or is missing capabilities\n", kms_server_proxy_local_filepath); + return false; + } + + return true; +} + static int launch_gsr_kms_server(const char *initial_socket_path, const char *card_path, const char *user_homepath) { char self_path[PATH_MAX]; if(!readlink_realpath("/proc/self/exe", self_path)) { @@ -233,14 +342,13 @@ static int launch_gsr_kms_server(const char *initial_socket_path, const char *ca } char kms_server_proxy_local_filepath[PATH_MAX]; - /* Update kms-server-proxy-N to kms-server-proxy-N+1 on update (update that needs to run before this program launches itself) */ - snprintf(kms_server_proxy_local_filepath, sizeof(kms_server_proxy_local_filepath), "%s/.local/share/gpu-screen-recorder/kms-server-proxy-2", user_homepath); + get_local_kms_server_proxy_filepath(kms_server_proxy_local_filepath, sizeof(kms_server_proxy_local_filepath), user_homepath); /* Weird-ass distros like openSUSE only allow pkexec for files in $HOME!!! */ char kms_server_proxy_home[PATH_MAX]; snprintf(kms_server_proxy_home, sizeof(kms_server_proxy_home), "%s/kms-server-proxy", user_homepath); - if(file_has_sys_admin_capability(kms_server_proxy_local_filepath)) { + if(file_has_capabilities(kms_server_proxy_local_filepath, (const cap_value_t[]){ CAP_SYS_ADMIN }, 1)) { /* Need to resolve kms_server_proxy_local_filepath because /home can be a symlink to another location */ char kms_server_proxy_local_filepath_full[PATH_MAX]; if(!readlink_realpath(kms_server_proxy_local_filepath, kms_server_proxy_local_filepath_full)) { @@ -266,13 +374,19 @@ static int launch_gsr_kms_server(const char *initial_socket_path, const char *ca return execvp(args[0], (char *const*)args); } else if(geteuid() == 0) { /* is current user root? */ remove(kms_server_proxy_home); - if(!set_gsr_files_set_permissions_and_capabilities(kms_server_proxy_local_filepath)) + remove(kms_server_proxy_local_filepath); + if(!copy_file_atomic(KMS_SERVER_PROXY_FILEPATH, kms_server_proxy_local_filepath)) { + fprintf(stderr, "Error: failed to copy kms-server-proxy to %s\n", kms_server_proxy_local_filepath); + return 1; + } + + if(!gsr_files_set_permissions_and_capabilities(kms_server_proxy_local_filepath)) return 1; const char *args[] = { GSR_KMS_SERVER_FILEPATH, initial_socket_path, card_path, NULL }; return execv(args[0], (char *const*)args); } else { - if(!setup_local_gsr_files(user_homepath, kms_server_proxy_local_filepath, kms_server_proxy_home)) + if(!setup_local_gsr_files(user_homepath, kms_server_proxy_home)) return 1; const char *args[] = { "pkexec", kms_server_proxy_home, initial_socket_path, card_path, user_homepath, NULL }; @@ -293,11 +407,10 @@ static int launch_gsr_global_hotkeys(char **argv) { const char *user_homepath = argv[2]; char kms_server_proxy_local_filepath[PATH_MAX]; - /* Update kms-server-proxy-N to kms-server-proxy-N+1 on update (update that needs to run before this program launches itself) */ - snprintf(kms_server_proxy_local_filepath, sizeof(kms_server_proxy_local_filepath), "%s/.local/share/gpu-screen-recorder/kms-server-proxy-2", user_homepath); + get_local_kms_server_proxy_filepath(kms_server_proxy_local_filepath, sizeof(kms_server_proxy_local_filepath), user_homepath); - if(!file_has_sys_admin_capability(kms_server_proxy_local_filepath)) { - fprintf(stderr, "Error: kms-server-proxy is missing cap sys admin capability\n"); + if(!file_has_capabilities(kms_server_proxy_local_filepath, (const cap_value_t[]){ CAP_SETUID }, 1)) { + fprintf(stderr, "Error: kms-server-proxy (%s) is either not installed or is missing capabilities\n", kms_server_proxy_local_filepath); return 1; } @@ -328,6 +441,14 @@ static int launch_gsr_global_hotkeys(char **argv) { return result; } +static void usage(void) { + fprintf(stderr, "usage alt.1: kms-server-proxy <initial_socket_path> <card_path> <user_homepath>\n"); + fprintf(stderr, "usage alt.2: kms-server-proxy setup-gsr-ui [user_homepath]\n"); + fprintf(stderr, "usage alt.3: kms-server-proxy launch-gsr-global-hotkeys <user_homepath> [args...]\n"); + fprintf(stderr, "usage alt.4: kms-server-proxy is-setup [user_homepath]\n"); + exit(1); +} + int main(int argc, char **argv) { if(argc < 2) usage(); @@ -342,6 +463,10 @@ int main(int argc, char **argv) { fprintf(stderr, "Error: missing user_homepath argument to launch-gsr-global-hotkeys\n"); usage(); } + } else if(strcmp(argv[1], "is-setup") == 0) { + const char *user_homepath = argc == 3 ? argv[2] : NULL; + const bool s = is_setup(user_homepath); + return s ? 0 : 1; } else if(argc == 4) { return launch_gsr_kms_server(argv[1], argv[2], argv[3]); } else { |