Merge pull request #1080 from SirCmpwn/ipc-security

Revise IPC security configuration
This commit is contained in:
Drew DeVault 2017-02-21 05:18:42 -05:00 committed by GitHub
commit f68d2fb33c
13 changed files with 228 additions and 115 deletions

View file

@ -203,7 +203,6 @@ enum secure_feature {
FEATURE_FULLSCREEN = 16, FEATURE_FULLSCREEN = 16,
FEATURE_KEYBOARD = 32, FEATURE_KEYBOARD = 32,
FEATURE_MOUSE = 64, FEATURE_MOUSE = 64,
FEATURE_IPC = 128,
}; };
struct feature_policy { struct feature_policy {
@ -225,7 +224,17 @@ enum ipc_feature {
IPC_FEATURE_EVENT_MODE = 1024, IPC_FEATURE_EVENT_MODE = 1024,
IPC_FEATURE_EVENT_WINDOW = 2048, IPC_FEATURE_EVENT_WINDOW = 2048,
IPC_FEATURE_EVENT_BINDING = 4096, IPC_FEATURE_EVENT_BINDING = 4096,
IPC_FEATURE_EVENT_INPUT = 8192 IPC_FEATURE_EVENT_INPUT = 8192,
IPC_FEATURE_ALL_COMMANDS = 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
IPC_FEATURE_ALL_EVENTS = 256 | 512 | 1024 | 2048 | 4096 | 8192,
IPC_FEATURE_ALL = IPC_FEATURE_ALL_COMMANDS | IPC_FEATURE_ALL_EVENTS,
};
struct ipc_policy {
char *program;
uint32_t features;
}; };
/** /**
@ -300,7 +309,7 @@ struct sway_config {
// Security // Security
list_t *command_policies; list_t *command_policies;
list_t *feature_policies; list_t *feature_policies;
uint32_t ipc_policy; list_t *ipc_policies;
}; };
void pid_workspace_add(struct pid_workspace *pw); void pid_workspace_add(struct pid_workspace *pw);
@ -331,6 +340,8 @@ void free_config(struct sway_config *config);
*/ */
char *do_var_replacement(char *str); char *do_var_replacement(char *str);
struct cmd_results *check_security_config();
int input_identifier_cmp(const void *item, const void *data); int input_identifier_cmp(const void *item, const void *data);
void merge_input_config(struct input_config *dst, struct input_config *src); void merge_input_config(struct input_config *dst, struct input_config *src);
void apply_input_config(struct input_config *ic, struct libinput_device *dev); void apply_input_config(struct input_config *ic, struct libinput_device *dev);

View file

@ -3,12 +3,14 @@
#include <unistd.h> #include <unistd.h>
#include "sway/config.h" #include "sway/config.h"
enum secure_feature get_feature_policy(pid_t pid); uint32_t get_feature_policy(pid_t pid);
enum command_context get_command_policy(const char *cmd); uint32_t get_ipc_policy(pid_t pid);
uint32_t get_command_policy(const char *cmd);
const char *command_policy_str(enum command_context context); const char *command_policy_str(enum command_context context);
struct feature_policy *alloc_feature_policy(const char *program); struct feature_policy *alloc_feature_policy(const char *program);
struct ipc_policy *alloc_ipc_policy(const char *program);
struct command_policy *alloc_command_policy(const char *command); struct command_policy *alloc_command_policy(const char *command);
#endif #endif

View file

@ -5,36 +5,42 @@
# You MUST read this man page if you intend to attempt to secure your sway # You MUST read this man page if you intend to attempt to secure your sway
# installation. # installation.
# #
# This file should live at __SYSCONFDIR__/sway/security and will be # DO NOT CHANGE THIS FILE. Override these defaults by writing new files in
# automatically read by sway. # __SYSCONFDIR__/sway/security.d/*
# Configures which programs are allowed to use which sway features # Configures enabled compositor features for specific programs
permit * fullscreen keyboard mouse ipc permit * fullscreen keyboard mouse
permit __PREFIX__/bin/swaylock lock permit __PREFIX__/bin/swaylock lock
permit __PREFIX__/bin/swaybar panel
permit __PREFIX__/bin/swaybg background permit __PREFIX__/bin/swaybg background
permit __PREFIX__/bin/swaygrab screenshot permit __PREFIX__/bin/swaygrab screenshot
permit __PREFIX__/bin/swaybar panel
# Configures which IPC features are enabled # Configures enabled IPC features for specific programs
ipc { ipc __PREFIX__/bin/swaymsg {
command enabled * enabled
events {
* disabled
}
}
ipc __PREFIX__/bin/swaybar {
bar-config enabled
outputs enabled outputs enabled
workspaces enabled workspaces enabled
tree enabled command enabled
marks enabled
bar-config enabled
inputs enabled
events { events {
workspace enabled workspace enabled
output enabled
mode enabled mode enabled
window enabled
input enabled
binding disabled
} }
} }
ipc __PREFIX__/bin/swaygrab {
outputs enabled
tree enabled
}
# Limits the contexts from which certain commands are permitted # Limits the contexts from which certain commands are permitted
commands { commands {
* all * all

View file

@ -91,7 +91,7 @@ function(add_config name source destination)
endfunction() endfunction()
add_config(config config sway) add_config(config config sway)
add_config(security security sway) add_config(00-defaults security.d/00-defaults sway/security.d)
add_manpage(sway 1) add_manpage(sway 1)
add_manpage(sway 5) add_manpage(sway 5)

View file

@ -297,6 +297,7 @@ static struct cmd_handler bar_colors_handlers[] = {
}; };
static struct cmd_handler ipc_handlers[] = { static struct cmd_handler ipc_handlers[] = {
{ "*", cmd_ipc_cmd },
{ "bar-config", cmd_ipc_cmd }, { "bar-config", cmd_ipc_cmd },
{ "command", cmd_ipc_cmd }, { "command", cmd_ipc_cmd },
{ "events", cmd_ipc_events }, { "events", cmd_ipc_events },
@ -308,6 +309,7 @@ static struct cmd_handler ipc_handlers[] = {
}; };
static struct cmd_handler ipc_event_handlers[] = { static struct cmd_handler ipc_event_handlers[] = {
{ "*", cmd_ipc_event_cmd },
{ "binding", cmd_ipc_event_cmd }, { "binding", cmd_ipc_event_cmd },
{ "input", cmd_ipc_event_cmd }, { "input", cmd_ipc_event_cmd },
{ "mode", cmd_ipc_event_cmd }, { "mode", cmd_ipc_event_cmd },

View file

@ -10,6 +10,9 @@ struct cmd_results *cmd_commands(int argc, char **argv) {
if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) { if ((error = checkarg(argc, "commands", EXPECTED_EQUAL_TO, 1))) {
return error; return error;
} }
if ((error = check_security_config())) {
return error;
}
if (strcmp(argv[0], "{") != 0) { if (strcmp(argv[0], "{") != 0) {
return cmd_results_new(CMD_FAILURE, "commands", "Expected block declaration"); 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."); 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); return cmd_results_new(CMD_BLOCK_COMMANDS, NULL, NULL);
} }

View file

@ -1,18 +1,26 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include "sway/security.h"
#include "sway/commands.h" #include "sway/commands.h"
#include "sway/config.h" #include "sway/config.h"
#include "ipc.h" #include "ipc.h"
#include "log.h" #include "log.h"
#include "util.h" #include "util.h"
static struct ipc_policy *current_policy = NULL;
struct cmd_results *cmd_ipc(int argc, char **argv) { struct cmd_results *cmd_ipc(int argc, char **argv) {
struct cmd_results *error = NULL; struct cmd_results *error = NULL;
if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 1))) { if ((error = checkarg(argc, "ipc", EXPECTED_EQUAL_TO, 2))) {
return error;
}
if ((error = check_security_config())) {
return error; return error;
} }
if (config->reading && strcmp("{", argv[0]) != 0) { const char *program = argv[0];
if (config->reading && strcmp("{", argv[1]) != 0) {
return cmd_results_new(CMD_INVALID, "ipc", return cmd_results_new(CMD_INVALID, "ipc",
"Expected '{' at start of IPC config definition."); "Expected '{' at start of IPC config definition.");
} }
@ -21,10 +29,8 @@ struct cmd_results *cmd_ipc(int argc, char **argv) {
return cmd_results_new(CMD_FAILURE, "ipc", "Can only be used in config file."); 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) { current_policy = alloc_ipc_policy(program);
return cmd_results_new(CMD_INVALID, "permit", list_add(config->ipc_policies, current_policy);
"This command is only permitted to run from " SYSCONFDIR "/sway/security");
}
return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL); return cmd_results_new(CMD_BLOCK_IPC, NULL, NULL);
} }
@ -67,6 +73,7 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
char *name; char *name;
enum ipc_feature type; enum ipc_feature type;
} types[] = { } types[] = {
{ "*", IPC_FEATURE_ALL_COMMANDS },
{ "command", IPC_FEATURE_COMMAND }, { "command", IPC_FEATURE_COMMAND },
{ "workspaces", IPC_FEATURE_GET_WORKSPACES }, { "workspaces", IPC_FEATURE_GET_WORKSPACES },
{ "outputs", IPC_FEATURE_GET_OUTPUTS }, { "outputs", IPC_FEATURE_GET_OUTPUTS },
@ -86,10 +93,10 @@ struct cmd_results *cmd_ipc_cmd(int argc, char **argv) {
} }
if (enabled) { if (enabled) {
config->ipc_policy |= type; current_policy->features |= type;
sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]); sway_log(L_DEBUG, "Enabled IPC %s feature", argv[-1]);
} else { } else {
config->ipc_policy &= ~type; current_policy->features &= ~type;
sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]); sway_log(L_DEBUG, "Disabled IPC %s feature", argv[-1]);
} }
@ -116,6 +123,7 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
char *name; char *name;
enum ipc_feature type; enum ipc_feature type;
} types[] = { } types[] = {
{ "*", IPC_FEATURE_ALL_EVENTS },
{ "workspace", IPC_FEATURE_EVENT_WORKSPACE }, { "workspace", IPC_FEATURE_EVENT_WORKSPACE },
{ "output", IPC_FEATURE_EVENT_OUTPUT }, { "output", IPC_FEATURE_EVENT_OUTPUT },
{ "mode", IPC_FEATURE_EVENT_MODE }, { "mode", IPC_FEATURE_EVENT_MODE },
@ -134,10 +142,10 @@ struct cmd_results *cmd_ipc_event_cmd(int argc, char **argv) {
} }
if (enabled) { if (enabled) {
config->ipc_policy |= type; current_policy->features |= type;
sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]); sway_log(L_DEBUG, "Enabled IPC %s event", argv[-1]);
} else { } else {
config->ipc_policy &= ~type; current_policy->features &= ~type;
sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]); sway_log(L_DEBUG, "Disabled IPC %s event", argv[-1]);
} }

View file

@ -19,7 +19,6 @@ static enum secure_feature get_features(int argc, char **argv,
{ "fullscreen", FEATURE_FULLSCREEN }, { "fullscreen", FEATURE_FULLSCREEN },
{ "keyboard", FEATURE_KEYBOARD }, { "keyboard", FEATURE_KEYBOARD },
{ "mouse", FEATURE_MOUSE }, { "mouse", FEATURE_MOUSE },
{ "ipc", FEATURE_IPC },
}; };
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
@ -63,19 +62,13 @@ struct cmd_results *cmd_permit(int argc, char **argv) {
if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) { if ((error = checkarg(argc, "permit", EXPECTED_MORE_THAN, 1))) {
return error; return error;
} }
if ((error = check_security_config())) {
if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) { return error;
return cmd_results_new(CMD_INVALID, "permit",
"This command is only permitted to run from " SYSCONFDIR "/sway/security");
} }
struct feature_policy *policy = get_policy(argv[0]); struct feature_policy *policy = get_policy(argv[0]);
policy->features |= get_features(argc, argv, &error); policy->features |= get_features(argc, argv, &error);
if (error) {
return error;
}
sway_log(L_DEBUG, "Permissions granted to %s for features %d", sway_log(L_DEBUG, "Permissions granted to %s for features %d",
policy->program, policy->features); policy->program, policy->features);
@ -87,19 +80,13 @@ struct cmd_results *cmd_reject(int argc, char **argv) {
if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) { if ((error = checkarg(argc, "reject", EXPECTED_MORE_THAN, 1))) {
return error; return error;
} }
if ((error = check_security_config())) {
if (!current_config_path || strcmp(SYSCONFDIR "/sway/security", current_config_path) != 0) { return error;
return cmd_results_new(CMD_INVALID, "permit",
"This command is only permitted to run from " SYSCONFDIR "/sway/security");
} }
struct feature_policy *policy = get_policy(argv[0]); struct feature_policy *policy = get_policy(argv[0]);
policy->features &= ~get_features(argc, argv, &error); policy->features &= ~get_features(argc, argv, &error);
if (error) {
return error;
}
sway_log(L_DEBUG, "Permissions granted to %s for features %d", sway_log(L_DEBUG, "Permissions granted to %s for features %d",
policy->program, policy->features); policy->program, policy->features);

View file

@ -11,6 +11,7 @@
#include <libinput.h> #include <libinput.h>
#include <limits.h> #include <limits.h>
#include <float.h> #include <float.h>
#include <dirent.h>
#include "wayland-desktop-shell-server-protocol.h" #include "wayland-desktop-shell-server-protocol.h"
#include "sway/commands.h" #include "sway/commands.h"
#include "sway/config.h" #include "sway/config.h"
@ -379,7 +380,7 @@ static void config_defaults(struct sway_config *config) {
// Security // Security
if (!(config->command_policies = create_list())) goto cleanup; if (!(config->command_policies = create_list())) goto cleanup;
if (!(config->feature_policies = create_list())) goto cleanup; if (!(config->feature_policies = create_list())) goto cleanup;
config->ipc_policy = UINT32_MAX; if (!(config->ipc_policies = create_list())) goto cleanup;
return; return;
cleanup: cleanup:
@ -485,6 +486,10 @@ static bool load_config(const char *path, struct sway_config *config) {
return true; 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) { bool load_main_config(const char *file, bool is_active) {
input_init(); input_init();
@ -512,7 +517,43 @@ bool load_main_config(const char *file, bool is_active) {
list_add(config->config_chain, path); list_add(config->config_chain, path);
config->reading = true; 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); success = success && load_config(path, config);
if (is_active) { if (is_active) {
@ -620,6 +661,15 @@ bool load_include_configs(const char *path, struct sway_config *config) {
return true; 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 read_config(FILE *file, struct sway_config *config) {
bool success = true; bool success = true;
enum cmd_status block = CMD_BLOCK_END; enum cmd_status block = CMD_BLOCK_END;

View file

@ -35,6 +35,7 @@ struct ipc_client {
struct wlc_event_source *event_source; struct wlc_event_source *event_source;
int fd; int fd;
uint32_t payload_length; uint32_t payload_length;
uint32_t security_policy;
enum ipc_command_type current_command; enum ipc_command_type current_command;
enum ipc_command_type subscribed_events; enum ipc_command_type subscribed_events;
}; };
@ -159,17 +160,6 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
return 0; return 0;
} }
pid_t pid = get_client_pid(client_fd);
if (!(get_feature_policy(pid) & FEATURE_IPC)) {
sway_log(L_INFO, "Permission to connect to IPC socket denied to %d", pid);
const char *error = "{\"success\": false, \"message\": \"Permission denied\"}";
if (write(client_fd, &error, sizeof(error)) < (int)sizeof(error)) {
sway_log(L_DEBUG, "Failed to write entire error");
}
close(client_fd);
return 0;
}
struct ipc_client* client = malloc(sizeof(struct ipc_client)); struct ipc_client* client = malloc(sizeof(struct ipc_client));
if (!client) { if (!client) {
sway_log(L_ERROR, "Unable to allocate ipc client"); sway_log(L_ERROR, "Unable to allocate ipc client");
@ -181,6 +171,9 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
client->subscribed_events = 0; client->subscribed_events = 0;
client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client); client->event_source = wlc_event_loop_add_fd(client_fd, WLC_EVENT_READABLE, ipc_client_handle_readable, client);
pid_t pid = get_client_pid(client->fd);
client->security_policy = get_ipc_policy(pid);
list_add(ipc_client_list, client); list_add(ipc_client_list, client);
return 0; return 0;
@ -248,8 +241,7 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
return 0; 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")) { if (!sway_assert(client != NULL, "client != NULL")) {
return; return;
} }
@ -333,8 +325,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
ipc_client_disconnect(client); ipc_client_disconnect(client);
return; return;
} }
if (client->payload_length > 0) if (client->payload_length > 0) {
{
ssize_t received = recv(client->fd, buf, client->payload_length, 0); ssize_t received = recv(client->fd, buf, client->payload_length, 0);
if (received == -1) if (received == -1)
{ {
@ -351,7 +342,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
switch (client->current_command) { switch (client->current_command) {
case IPC_COMMAND: case IPC_COMMAND:
{ {
if (!(config->ipc_policy & IPC_FEATURE_COMMAND)) { if (!(client->security_policy & IPC_FEATURE_COMMAND)) {
goto exit_denied; goto exit_denied;
} }
struct cmd_results *results = handle_command(buf, CONTEXT_IPC); struct cmd_results *results = handle_command(buf, CONTEXT_IPC);
@ -365,6 +356,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_SUBSCRIBE: case IPC_SUBSCRIBE:
{ {
// TODO: Check if they're permitted to use these events
struct json_object *request = json_tokener_parse(buf); struct json_object *request = json_tokener_parse(buf);
if (request == NULL) { if (request == NULL) {
ipc_send_reply(client, "{\"success\": false}", 18); ipc_send_reply(client, "{\"success\": false}", 18);
@ -403,7 +395,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_WORKSPACES: case IPC_GET_WORKSPACES:
{ {
if (!(config->ipc_policy & IPC_FEATURE_GET_WORKSPACES)) { if (!(client->security_policy & IPC_FEATURE_GET_WORKSPACES)) {
goto exit_denied; goto exit_denied;
} }
json_object *workspaces = json_object_new_array(); json_object *workspaces = json_object_new_array();
@ -416,7 +408,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_INPUTS: case IPC_GET_INPUTS:
{ {
if (!(config->ipc_policy & IPC_FEATURE_GET_INPUTS)) { if (!(client->security_policy & IPC_FEATURE_GET_INPUTS)) {
goto exit_denied; goto exit_denied;
} }
json_object *inputs = json_object_new_array(); json_object *inputs = json_object_new_array();
@ -442,7 +434,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_OUTPUTS: case IPC_GET_OUTPUTS:
{ {
if (!(config->ipc_policy & IPC_FEATURE_GET_OUTPUTS)) { if (!(client->security_policy & IPC_FEATURE_GET_OUTPUTS)) {
goto exit_denied; goto exit_denied;
} }
json_object *outputs = json_object_new_array(); json_object *outputs = json_object_new_array();
@ -455,7 +447,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_TREE: case IPC_GET_TREE:
{ {
if (!(config->ipc_policy & IPC_FEATURE_GET_TREE)) { if (!(client->security_policy & IPC_FEATURE_GET_TREE)) {
goto exit_denied; goto exit_denied;
} }
json_object *tree = ipc_json_describe_container_recursive(&root_container); json_object *tree = ipc_json_describe_container_recursive(&root_container);
@ -522,7 +514,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
case IPC_GET_BAR_CONFIG: case IPC_GET_BAR_CONFIG:
{ {
if (!(config->ipc_policy & IPC_FEATURE_GET_BAR_CONFIG)) { if (!(client->security_policy & IPC_FEATURE_GET_BAR_CONFIG)) {
goto exit_denied; goto exit_denied;
} }
if (!buf[0]) { if (!buf[0]) {
@ -567,6 +559,7 @@ void ipc_client_handle_command(struct ipc_client *client) {
exit_denied: exit_denied:
ipc_send_reply(client, error_denied, (uint32_t)strlen(error_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: exit_cleanup:
client->payload_length = 0; client->payload_length = 0;
@ -594,6 +587,8 @@ bool ipc_send_reply(struct ipc_client *client, const char *payload, uint32_t pay
return false; return false;
} }
sway_log(L_DEBUG, "Send IPC reply: %s", payload);
return true; return true;
} }
@ -616,10 +611,33 @@ void ipc_get_outputs_callback(swayc_t *container, void *data) {
} }
void ipc_send_event(const char *json_string, enum ipc_command_type event) { void ipc_send_event(const char *json_string, enum ipc_command_type event) {
static struct {
enum ipc_command_type event;
enum ipc_feature feature;
} security_mappings[] = {
{ IPC_EVENT_WORKSPACE, IPC_FEATURE_EVENT_WORKSPACE },
{ IPC_EVENT_OUTPUT, IPC_FEATURE_EVENT_OUTPUT },
{ IPC_EVENT_MODE, IPC_FEATURE_EVENT_MODE },
{ IPC_EVENT_WINDOW, IPC_FEATURE_EVENT_WINDOW },
{ IPC_EVENT_BINDING, IPC_FEATURE_EVENT_BINDING },
{ IPC_EVENT_INPUT, IPC_FEATURE_EVENT_INPUT }
};
uint32_t security_mask = 0;
for (size_t i = 0; i < sizeof(security_mappings) / sizeof(security_mappings[0]); ++i) {
if (security_mappings[i].event == event) {
security_mask = security_mappings[i].feature;
break;
}
}
int i; int i;
struct ipc_client *client; struct ipc_client *client;
for (i = 0; i < ipc_client_list->length; i++) { for (i = 0; i < ipc_client_list->length; i++) {
client = ipc_client_list->items[i]; client = ipc_client_list->items[i];
if (!(client->security_policy & security_mask)) {
continue;
}
if ((client->subscribed_events & event_mask(event)) == 0) { if ((client->subscribed_events & event_mask(event)) == 0) {
continue; continue;
} }
@ -632,9 +650,6 @@ void ipc_send_event(const char *json_string, enum ipc_command_type event) {
} }
void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) { void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
if (!(config->ipc_policy & IPC_FEATURE_EVENT_WORKSPACE)) {
return;
}
sway_log(L_DEBUG, "Sending workspace::%s event", change); sway_log(L_DEBUG, "Sending workspace::%s event", change);
json_object *obj = json_object_new_object(); json_object *obj = json_object_new_object();
json_object_object_add(obj, "change", json_object_new_string(change)); json_object_object_add(obj, "change", json_object_new_string(change));
@ -659,9 +674,6 @@ void ipc_event_workspace(swayc_t *old, swayc_t *new, const char *change) {
} }
void ipc_event_window(swayc_t *window, const char *change) { void ipc_event_window(swayc_t *window, const char *change) {
if (!(config->ipc_policy & IPC_FEATURE_EVENT_WINDOW)) {
return;
}
sway_log(L_DEBUG, "Sending window::%s event", change); sway_log(L_DEBUG, "Sending window::%s event", change);
json_object *obj = json_object_new_object(); json_object *obj = json_object_new_object();
json_object_object_add(obj, "change", json_object_new_string(change)); json_object_object_add(obj, "change", json_object_new_string(change));
@ -687,9 +699,6 @@ void ipc_event_barconfig_update(struct bar_config *bar) {
} }
void ipc_event_mode(const char *mode) { void ipc_event_mode(const char *mode) {
if (!(config->ipc_policy & IPC_FEATURE_EVENT_MODE)) {
return;
}
sway_log(L_DEBUG, "Sending mode::%s event", mode); sway_log(L_DEBUG, "Sending mode::%s event", mode);
json_object *obj = json_object_new_object(); json_object *obj = json_object_new_object();
json_object_object_add(obj, "change", json_object_new_string(mode)); json_object_object_add(obj, "change", json_object_new_string(mode));
@ -715,9 +724,6 @@ void ipc_event_modifier(uint32_t modifier, const char *state) {
} }
static void ipc_event_binding(json_object *sb_obj) { static void ipc_event_binding(json_object *sb_obj) {
if (!(config->ipc_policy & IPC_FEATURE_EVENT_BINDING)) {
return;
}
sway_log(L_DEBUG, "Sending binding::run event"); sway_log(L_DEBUG, "Sending binding::run event");
json_object *obj = json_object_new_object(); json_object *obj = json_object_new_object();
json_object_object_add(obj, "change", json_object_new_string("run")); json_object_object_add(obj, "change", json_object_new_string("run"));

View file

@ -175,13 +175,6 @@ static void security_sanity_check() {
cap_free(cap); cap_free(cap);
} }
#endif #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) { int main(int argc, char **argv) {

View file

@ -27,6 +27,29 @@ struct feature_policy *alloc_feature_policy(const char *program) {
return policy; return policy;
} }
struct ipc_policy *alloc_ipc_policy(const char *program) {
uint32_t default_policy = 0;
for (int i = 0; i < config->ipc_policies->length; ++i) {
struct ipc_policy *policy = config->ipc_policies->items[i];
if (strcmp(policy->program, "*") == 0) {
default_policy = policy->features;
break;
}
}
struct ipc_policy *policy = malloc(sizeof(struct ipc_policy));
if (!policy) {
return NULL;
}
policy->program = strdup(program);
if (!policy->program) {
free(policy);
return NULL;
}
policy->features = default_policy;
return policy;
}
struct command_policy *alloc_command_policy(const char *command) { struct command_policy *alloc_command_policy(const char *command) {
struct command_policy *policy = malloc(sizeof(struct command_policy)); struct command_policy *policy = malloc(sizeof(struct command_policy));
if (!policy) { if (!policy) {
@ -41,7 +64,7 @@ struct command_policy *alloc_command_policy(const char *command) {
return policy; return policy;
} }
enum secure_feature get_feature_policy(pid_t pid) { static const char *get_pid_exe(pid_t pid) {
#ifdef __FreeBSD__ #ifdef __FreeBSD__
const char *fmt = "/proc/%d/file"; const char *fmt = "/proc/%d/file";
#else #else
@ -52,9 +75,8 @@ enum secure_feature get_feature_policy(pid_t pid) {
if (path) { if (path) {
snprintf(path, pathlen + 1, fmt, pid); snprintf(path, pathlen + 1, fmt, pid);
} }
static char link[2048];
uint32_t default_policy = 0; static char link[2048];
ssize_t len = !path ? -1 : readlink(path, link, sizeof(link)); ssize_t len = !path ? -1 : readlink(path, link, sizeof(link));
if (len < 0) { if (len < 0) {
@ -67,6 +89,13 @@ enum secure_feature get_feature_policy(pid_t pid) {
} }
free(path); free(path);
return link;
}
uint32_t get_feature_policy(pid_t pid) {
uint32_t default_policy = 0;
const char *link = get_pid_exe(pid);
for (int i = 0; i < config->feature_policies->length; ++i) { for (int i = 0; i < config->feature_policies->length; ++i) {
struct feature_policy *policy = config->feature_policies->items[i]; struct feature_policy *policy = config->feature_policies->items[i];
if (strcmp(policy->program, "*") == 0) { if (strcmp(policy->program, "*") == 0) {
@ -80,7 +109,24 @@ enum secure_feature get_feature_policy(pid_t pid) {
return default_policy; return default_policy;
} }
enum command_context get_command_policy(const char *cmd) { uint32_t get_ipc_policy(pid_t pid) {
uint32_t default_policy = 0;
const char *link = get_pid_exe(pid);
for (int i = 0; i < config->ipc_policies->length; ++i) {
struct ipc_policy *policy = config->ipc_policies->items[i];
if (strcmp(policy->program, "*") == 0) {
default_policy = policy->features;
}
if (strcmp(policy->program, link) == 0) {
return policy->features;
}
}
return default_policy;
}
uint32_t get_command_policy(const char *cmd) {
uint32_t default_policy = 0; uint32_t default_policy = 0;
for (int i = 0; i < config->command_policies->length; ++i) { for (int i = 0; i < config->command_policies->length; ++i) {

View file

@ -19,8 +19,13 @@ usually best suited to a distro maintainer who wants to ship a secure sway
environment in their distro. Sway provides a number of means of securing it but environment in their distro. Sway provides a number of means of securing it but
you must make a few changes external to sway first. you must make a few changes external to sway first.
Security-related configuration is only valid in /etc/sway/config (or whatever path Configuration of security features is limited to files in the security directory
is appropriate for your system). (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 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
this directory.
Environment security Environment security
-------------------- --------------------
@ -160,22 +165,20 @@ Setting a command policy overwrites any previous policy that was in place.
IPC policies IPC policies
------------ ------------
You may whitelist IPC access like so: Disabling IPC access via swaymsg is encouraged if you intend to secure the IPC
socket, because any program that can execute swaymsg could circumvent its own
security policy by simply invoking swaymsg.
permit /usr/bin/swaybar ipc You can configure which features of IPC are available for particular clients:
permit /usr/bin/swaygrab ipc
# etc
Note that it's suggested you do not enable swaymsg to access IPC if you intend to ipc <executable> {
secure your IPC socket, because any program could just run swaymsg itself instead
of connecting to IPC directly.
You can also configure which features of IPC are available with an IPC block:
ipc {
... ...
} }
You may use * for <executable> to configure the default policy for all clients.
Configuring IPC policies for specific executables is not supported on FreeBSD, and
the default policy will be applied to all IPC connections.
The following commands are available within this block: The following commands are available within this block:
**bar-config** <enabled|disabled>:: **bar-config** <enabled|disabled>::
@ -201,7 +204,7 @@ The following commands are available within this block:
You can also control which IPC events can be raised with an events block: You can also control which IPC events can be raised with an events block:
ipc { ipc <executable> {
events { events {
... ...
} }
@ -227,7 +230,8 @@ The following commands are vaild within an ipc events block:
**workspace** <enabled|disabled>:: **workspace** <enabled|disabled>::
Controls workspace notifications. Controls workspace notifications.
Disabling some of these may cause swaybar to behave incorrectly. In each of these blocks, you may use * (as in "* enabled" or "* disabled") to
control access to every feature at once.
Authors Authors
------- -------