diff --git a/include/sway/config.h b/include/sway/config.h index ba49b9a0..d77fbd51 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -340,6 +340,8 @@ void free_config(struct sway_config *config); */ char *do_var_replacement(char *str); +struct cmd_results *check_security_config(); + int input_identifier_cmp(const void *item, const void *data); void merge_input_config(struct input_config *dst, struct input_config *src); void apply_input_config(struct input_config *ic, struct libinput_device *dev); diff --git a/security.d/00-defaults.in b/security.d/00-defaults.in index 99859edd..f319e446 100644 --- a/security.d/00-defaults.in +++ b/security.d/00-defaults.in @@ -29,6 +29,11 @@ ipc __PREFIX__/bin/swaybar { outputs enabled workspaces enabled command enabled + + events { + workspace enabled + mode enabled + } } ipc __PREFIX__/bin/swaygrab { diff --git a/sway/commands/commands.c b/sway/commands/commands.c index 8c7ed487..0c64970c 100644 --- a/sway/commands/commands.c +++ b/sway/commands/commands.c @@ -10,6 +10,9 @@ struct cmd_results *cmd_commands(int argc, char **argv) { if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) { return error; } + if ((error = check_security_config())) { + return error; + } if (strcmp(argv[0], "{") != 0) { return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration"); @@ -19,10 +22,5 @@ struct cmd_results *cmd_commands(int argc, char **argv) { return cmd_results_new(CMD_FAILURE, "commands", "Can only be used in config file."); } - if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) { - return cmd_results_new(CMD_INVALID, "permit", - "This command is only permitted to run from " SYSCONFDIR "/sway/security"); - } - return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL); } diff --git a/sway/commands/ipc.c b/sway/commands/ipc.c index d49aab64..8a7b849f 100644 --- a/sway/commands/ipc.c +++ b/sway/commands/ipc.c @@ -14,6 +14,9 @@ struct cmd_results *cmd_ipc(int argc, char **argv) { if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 2))) { return error; } + if ((error = check_security_config())) { + return error; + } const char *program = argv[0]; @@ -26,11 +29,6 @@ struct cmd_results *cmd_ipc(int argc, char **argv) { return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file."); } - if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) { - return cmd_results_new(CMD_INVALID, "permit", - "This command is only permitted to run from " SYSCONFDIR "/sway/security"); - } - current_policy = alloc_ipc_policy(program); list_add(config->ipc_policies, current_policy); diff --git a/sway/commands/permit.c b/sway/commands/permit.c index 6eb71816..e2bec2e2 100644 --- a/sway/commands/permit.c +++ b/sway/commands/permit.c @@ -62,19 +62,13 @@ struct cmd_results *cmd_permit(int argc, char **argv) { if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) { return error; } - - if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) { - return cmd_results_new(CMD_INVALID, "permit", - "This command is only permitted to run from " SYSCONFDIR "/sway/security"); + if ((error = check_security_config())) { + return error; } struct feature_policy *policy = get_policy(argv[0]); policy->features |= get_features(argc, argv, &error); - if (error) { - return error; - } - sway_log(L_DEBUG, "Permissions granted to %s for features %d", policy->program, policy->features); @@ -86,19 +80,13 @@ struct cmd_results *cmd_reject(int argc, char **argv) { if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) { return error; } - - if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) { - return cmd_results_new(CMD_INVALID, "permit", - "This command is only permitted to run from " SYSCONFDIR "/sway/security"); + if ((error = check_security_config())) { + return error; } struct feature_policy *policy = get_policy(argv[0]); policy->features &= ~get_features(argc, argv, &error); - if (error) { - return error; - } - sway_log(L_DEBUG, "Permissions granted to %s for features %d", policy->program, policy->features); diff --git a/sway/config.c b/sway/config.c index 4a3c953d..88e6fad1 100644 --- a/sway/config.c +++ b/sway/config.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "wayland-desktop-shell-server-protocol.h" #include "sway/commands.h" #include "sway/config.h" @@ -485,6 +486,10 @@ static bool load_config(const char *path, struct sway_config *config) { return true; } +static int qstrcmp(const void* a, const void* b) { + return strcmp(*((char**) a), *((char**) b)); +} + bool load_main_config(const char *file, bool is_active) { input_init(); @@ -512,7 +517,43 @@ bool load_main_config(const char *file, bool is_active) { list_add(config->config_chain, path); config->reading = true; - bool success = load_config(SYSCONFDIR "/sway/security", config); + + // Read security configs + bool success = true; + DIR *dir = opendir(SYSCONFDIR "/sway/security.d"); + if (!dir) { + sway_log(L_ERROR, "%s does not exist, sway will have no security configuration" + " and will probably be broken", SYSCONFDIR "/sway/security.d"); + } else { + list_t *secconfigs = create_list(); + char *base = SYSCONFDIR "/sway/security.d/"; + struct dirent *ent = readdir(dir); + while (ent != NULL) { + if (ent->d_type == DT_REG) { + char *_path = malloc(strlen(ent->d_name) + strlen(base) + 1); + strcpy(_path, base); + strcat(_path, ent->d_name); + list_add(secconfigs, _path); + } + ent = readdir(dir); + } + closedir(dir); + + list_qsort(secconfigs, qstrcmp); + for (int i = 0; i < secconfigs->length; ++i) { + char *_path = secconfigs->items[i]; + struct stat s; + if (stat(_path, &s) || s.st_uid != 0 || s.st_gid != 0 || (s.st_mode & 0777) != 0644) { + sway_log(L_ERROR, "Refusing to load %s - it must be owned by root and mode 644", _path); + success = false; + } else { + success = success && load_config(_path, config); + } + } + + free_flat_list(secconfigs); + } + success = success && load_config(path, config); if (is_active) { @@ -620,6 +661,15 @@ bool load_include_configs(const char *path, struct sway_config *config) { return true; } +struct cmd_results *check_security_config() { + if (!current_config_path || strncmp(SYSCONFDIR "/sway/security.d/", current_config_path, + strlen(SYSCONFDIR "/sway/security.d/")) != 0) { + return cmd_results_new(CMD_INVALID, "permit", + "This command is only permitted to run from " SYSCONFDIR "/sway/security.d/*"); + } + return NULL; +} + bool read_config(FILE *file, struct sway_config *config) { bool success = true; enum cmd_status block = CMD_BLOCK_END; diff --git a/sway/ipc-server.c b/sway/ipc-server.c index 20a19b44..eddae461 100644 --- a/sway/ipc-server.c +++ b/sway/ipc-server.c @@ -241,8 +241,7 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) { return 0; } -void ipc_client_disconnect(struct ipc_client *client) -{ +void ipc_client_disconnect(struct ipc_client *client) { if (!sway_assert(client != NULL, "client != NULL")) { return; } @@ -326,8 +325,7 @@ void ipc_client_handle_command(struct ipc_client *client) { ipc_client_disconnect(client); return; } - if (client->payload_length > 0) - { + if (client->payload_length > 0) { ssize_t received = recv(client->fd, buf, client->payload_length, 0); if (received == -1) { @@ -397,7 +395,7 @@ void ipc_client_handle_command(struct ipc_client *client) { case IPC_GET_WORKSPACES: { - if (!(client->security_policy & IPC_FEATURE_GET_TREE)) { + if (!(client->security_policy & IPC_FEATURE_GET_WORKSPACES)) { goto exit_denied; } json_object *workspaces = json_object_new_array(); @@ -410,7 +408,7 @@ void ipc_client_handle_command(struct ipc_client *client) { case IPC_GET_INPUTS: { - if (!(client->security_policy & IPC_FEATURE_GET_TREE)) { + if (!(client->security_policy & IPC_FEATURE_GET_INPUTS)) { goto exit_denied; } json_object *inputs = json_object_new_array(); @@ -436,7 +434,7 @@ void ipc_client_handle_command(struct ipc_client *client) { case IPC_GET_OUTPUTS: { - if (!(client->security_policy & IPC_FEATURE_GET_TREE)) { + if (!(client->security_policy & IPC_FEATURE_GET_OUTPUTS)) { goto exit_denied; } json_object *outputs = json_object_new_array(); @@ -561,6 +559,7 @@ void ipc_client_handle_command(struct ipc_client *client) { exit_denied: ipc_send_reply(client, error_denied, (uint32_t)strlen(error_denied)); + sway_log(L_DEBUG, "Denied IPC client access to %i", client->current_command); exit_cleanup: client->payload_length = 0; @@ -588,6 +587,8 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay return false; } + sway_log(L_DEBUG, "Send IPC reply: %s", payload); + return true; } diff --git a/sway/main.c b/sway/main.c index 1c4c56c0..0151e078 100644 --- a/sway/main.c +++ b/sway/main.c @@ -175,13 +175,6 @@ static void security_sanity_check() { cap_free(cap); } #endif - if (!stat(SYSCONFDIR "/sway", &s)) { - if (s.st_uid != 0 || s.st_gid != 0 - || (s.st_mode & S_IWGRP) || (s.st_mode & S_IWOTH)) { - sway_log(L_ERROR, - "!! DANGER !! " SYSCONFDIR "/sway is not secure! It should be owned by root and set to 0755 at the minimum"); - } - } } int main(int argc, char **argv) { diff --git a/sway/sway-security.7.txt b/sway/sway-security.7.txt index 98e3f5ac..fb47ffcf 100644 --- a/sway/sway-security.7.txt +++ b/sway/sway-security.7.txt @@ -21,7 +21,7 @@ you must make a few changes external to sway first. Configuration of security features is limited to files in the security directory (this is likely /etc/sway/security.d/*, but depends on your installation prefix). -Files in this directory must be owned by root:root and chmod 600. The default +Files in this directory must be owned by root:root and chmod 644. The default security configuration is installed to /etc/sway/security.d/00-defaults, and should not be modified - it will be updated with the latest recommended security defaults between releases. To override the defaults, you should add more files to