mirror of
https://github.com/swaywm/sway.git
synced 2024-12-01 02:49:26 +00:00
sway: prevent endless loop with broken clients
The server loops endlessly in following scenarios: - client sends less bytes than IPC header requires - client sends less bytes than defined by payload size - client sends more payload data than buffered by operating system This happens because the server relies on the buffering in sockets by the operating system. The server only retrieves bytes from buffer when enough bytes are available. To prevent this, store data in heap. Also check supplied payload length before working with that value. Proof of Concept client in Python (you will notice that sway process consumes a lot of CPU while the client is running): ``` import os import socket swaysock=os.environ['SWAYSOCK'] client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) client.connect(swaysock) client.send(b'\x69\x33\x2D\x69\x70\x63\x00\x00\x00\x00\xFF\xFF\xFF\xFF\x00') input('Press enter to quit.') ```
This commit is contained in:
parent
edcdb5552d
commit
06ab0d166a
|
@ -3,6 +3,9 @@
|
||||||
|
|
||||||
#define event_mask(ev) (1 << (ev & 0x7F))
|
#define event_mask(ev) (1 << (ev & 0x7F))
|
||||||
|
|
||||||
|
// maximum size of payload is 4 MB
|
||||||
|
#define IPC_MAX_SIZE 4e6
|
||||||
|
|
||||||
enum ipc_command_type {
|
enum ipc_command_type {
|
||||||
// i3 command types - see i3's I3_REPLY_TYPE constants
|
// i3 command types - see i3's I3_REPLY_TYPE constants
|
||||||
IPC_COMMAND = 0,
|
IPC_COMMAND = 0,
|
||||||
|
|
|
@ -48,6 +48,9 @@ struct ipc_client {
|
||||||
struct sway_server *server;
|
struct sway_server *server;
|
||||||
int fd;
|
int fd;
|
||||||
enum ipc_command_type subscribed_events;
|
enum ipc_command_type subscribed_events;
|
||||||
|
size_t read_buffer_len;
|
||||||
|
size_t read_buffer_size;
|
||||||
|
char *read_buffer;
|
||||||
size_t write_buffer_len;
|
size_t write_buffer_len;
|
||||||
size_t write_buffer_size;
|
size_t write_buffer_size;
|
||||||
char *write_buffer;
|
char *write_buffer;
|
||||||
|
@ -187,11 +190,23 @@ int ipc_handle_connection(int fd, uint32_t mask, void *data) {
|
||||||
client_fd, WL_EVENT_READABLE, ipc_client_handle_readable, client);
|
client_fd, WL_EVENT_READABLE, ipc_client_handle_readable, client);
|
||||||
client->writable_event_source = NULL;
|
client->writable_event_source = NULL;
|
||||||
|
|
||||||
|
client->read_buffer_size = 128;
|
||||||
|
client->read_buffer_len = 0;
|
||||||
|
client->read_buffer = malloc(client->read_buffer_size);
|
||||||
|
if (!client->read_buffer) {
|
||||||
|
sway_log(SWAY_ERROR, "Unable to allocate ipc client read buffer");
|
||||||
|
free(client);
|
||||||
|
close(client_fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
client->write_buffer_size = 128;
|
client->write_buffer_size = 128;
|
||||||
client->write_buffer_len = 0;
|
client->write_buffer_len = 0;
|
||||||
client->write_buffer = malloc(client->write_buffer_size);
|
client->write_buffer = malloc(client->write_buffer_size);
|
||||||
if (!client->write_buffer) {
|
if (!client->write_buffer) {
|
||||||
sway_log(SWAY_ERROR, "Unable to allocate ipc client write buffer");
|
sway_log(SWAY_ERROR, "Unable to allocate ipc client write buffer");
|
||||||
|
free(client->read_buffer);
|
||||||
|
free(client);
|
||||||
close(client_fd);
|
close(client_fd);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -224,49 +239,67 @@ int ipc_client_handle_readable(int client_fd, uint32_t mask, void *data) {
|
||||||
ipc_client_disconnect(client);
|
ipc_client_disconnect(client);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if ((size_t)read_available + 1 > SIZE_MAX - client->read_buffer_len) {
|
||||||
|
sway_log_errno(SWAY_INFO, "Receiving too much data from IPC client");
|
||||||
|
ipc_client_disconnect(client);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for the rest of the command payload in case the header has already been read
|
if (client->read_buffer_len + read_available >= client->read_buffer_size) {
|
||||||
if (client->pending_length > 0) {
|
size_t new_size = client->read_buffer_len + read_available + 1;
|
||||||
if ((uint32_t)read_available >= client->pending_length) {
|
char *new_buffer = realloc(client->read_buffer, new_size);
|
||||||
// Reset pending values.
|
if (new_buffer == NULL) {
|
||||||
uint32_t pending_length = client->pending_length;
|
sway_log_errno(SWAY_INFO, "Unable to increase read buffer for IPC client");
|
||||||
enum ipc_command_type pending_type = client->pending_type;
|
ipc_client_disconnect(client);
|
||||||
client->pending_length = 0;
|
return 0;
|
||||||
ipc_client_handle_command(client, pending_length, pending_type);
|
|
||||||
}
|
}
|
||||||
return 0;
|
client->read_buffer = new_buffer;
|
||||||
|
client->read_buffer_size = new_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (read_available < (int) IPC_HEADER_SIZE) {
|
ssize_t received = recv(client_fd, client->read_buffer + client->read_buffer_len, read_available, 0);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t buf[IPC_HEADER_SIZE];
|
|
||||||
// Should be fully available, because read_available >= IPC_HEADER_SIZE
|
|
||||||
ssize_t received = recv(client_fd, buf, IPC_HEADER_SIZE, 0);
|
|
||||||
if (received == -1) {
|
if (received == -1) {
|
||||||
sway_log_errno(SWAY_INFO, "Unable to receive header from IPC client");
|
sway_log_errno(SWAY_INFO, "Unable to receive header from IPC client");
|
||||||
ipc_client_disconnect(client);
|
ipc_client_disconnect(client);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
client->read_buffer_len += received;
|
||||||
|
|
||||||
if (memcmp(buf, ipc_magic, sizeof(ipc_magic)) != 0) {
|
if (client->pending_length == 0) {
|
||||||
sway_log(SWAY_DEBUG, "IPC header check failed");
|
if (client->read_buffer_len < (size_t) IPC_HEADER_SIZE) {
|
||||||
ipc_client_disconnect(client);
|
return 0;
|
||||||
return 0;
|
}
|
||||||
|
|
||||||
|
if (memcmp(client->read_buffer, ipc_magic, sizeof(ipc_magic)) != 0) {
|
||||||
|
sway_log(SWAY_DEBUG, "IPC header check failed");
|
||||||
|
ipc_client_disconnect(client);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&client->pending_length,
|
||||||
|
client->read_buffer + sizeof(ipc_magic), sizeof(uint32_t));
|
||||||
|
memcpy(&client->pending_type,
|
||||||
|
client->read_buffer + sizeof(ipc_magic) + sizeof(uint32_t), sizeof(uint32_t));
|
||||||
|
if (client->pending_length > IPC_MAX_SIZE) {
|
||||||
|
sway_log_errno(SWAY_INFO, "Receiving too much payload from IPC client");
|
||||||
|
ipc_client_disconnect(client);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(&client->pending_length, buf + sizeof(ipc_magic), sizeof(uint32_t));
|
if (client->read_buffer_len >= IPC_HEADER_SIZE + client->pending_length) {
|
||||||
memcpy(&client->pending_type, buf + sizeof(ipc_magic) + sizeof(uint32_t), sizeof(uint32_t));
|
|
||||||
|
|
||||||
if (read_available - received >= (long)client->pending_length) {
|
|
||||||
// Reset pending values.
|
|
||||||
uint32_t pending_length = client->pending_length;
|
uint32_t pending_length = client->pending_length;
|
||||||
enum ipc_command_type pending_type = client->pending_type;
|
enum ipc_command_type pending_type = client->pending_type;
|
||||||
client->pending_length = 0;
|
char c = client->read_buffer[IPC_HEADER_SIZE + client->pending_length];
|
||||||
|
client->read_buffer[IPC_HEADER_SIZE + client->pending_length] = '\0';
|
||||||
ipc_client_handle_command(client, pending_length, pending_type);
|
ipc_client_handle_command(client, pending_length, pending_type);
|
||||||
|
// Reset values.
|
||||||
|
client->read_buffer[IPC_HEADER_SIZE + client->pending_length] = c;
|
||||||
|
memmove(client->read_buffer, client->read_buffer + IPC_HEADER_SIZE + client->pending_length,
|
||||||
|
client->read_buffer_len - IPC_HEADER_SIZE - client->pending_length);
|
||||||
|
client->read_buffer_len -= IPC_HEADER_SIZE + client->pending_length;
|
||||||
|
client->pending_length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,6 +605,7 @@ void ipc_client_disconnect(struct ipc_client *client) {
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
list_del(ipc_client_list, i);
|
list_del(ipc_client_list, i);
|
||||||
|
free(client->read_buffer);
|
||||||
free(client->write_buffer);
|
free(client->write_buffer);
|
||||||
close(client->fd);
|
close(client->fd);
|
||||||
free(client);
|
free(client);
|
||||||
|
@ -610,24 +644,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *buf = malloc(payload_length + 1);
|
char *buf = client->read_buffer + IPC_HEADER_SIZE;
|
||||||
if (!buf) {
|
|
||||||
sway_log_errno(SWAY_INFO, "Unable to allocate IPC payload");
|
|
||||||
ipc_client_disconnect(client);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (payload_length > 0) {
|
|
||||||
// Payload should be fully available
|
|
||||||
ssize_t received = recv(client->fd, buf, payload_length, 0);
|
|
||||||
if (received == -1)
|
|
||||||
{
|
|
||||||
sway_log_errno(SWAY_INFO, "Unable to receive payload from IPC client");
|
|
||||||
ipc_client_disconnect(client);
|
|
||||||
free(buf);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf[payload_length] = '\0';
|
|
||||||
|
|
||||||
switch (payload_type) {
|
switch (payload_type) {
|
||||||
case IPC_COMMAND:
|
case IPC_COMMAND:
|
||||||
|
@ -654,14 +671,14 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
list_del(res_list, 0);
|
list_del(res_list, 0);
|
||||||
}
|
}
|
||||||
list_free(res_list);
|
list_free(res_list);
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_SEND_TICK:
|
case IPC_SEND_TICK:
|
||||||
{
|
{
|
||||||
ipc_event_tick(buf);
|
ipc_event_tick(buf);
|
||||||
ipc_send_reply(client, payload_type, "{\"success\": true}", 17);
|
ipc_send_reply(client, payload_type, "{\"success\": true}", 17);
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_OUTPUTS:
|
case IPC_GET_OUTPUTS:
|
||||||
|
@ -696,7 +713,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(outputs); // free
|
json_object_put(outputs); // free
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_WORKSPACES:
|
case IPC_GET_WORKSPACES:
|
||||||
|
@ -707,7 +724,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(workspaces); // free
|
json_object_put(workspaces); // free
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_SUBSCRIBE:
|
case IPC_SUBSCRIBE:
|
||||||
|
@ -718,7 +735,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
const char msg[] = "{\"success\": false}";
|
const char msg[] = "{\"success\": false}";
|
||||||
ipc_send_reply(client, payload_type, msg, strlen(msg));
|
ipc_send_reply(client, payload_type, msg, strlen(msg));
|
||||||
sway_log(SWAY_INFO, "Failed to parse subscribe request");
|
sway_log(SWAY_INFO, "Failed to parse subscribe request");
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_tick = false;
|
bool is_tick = false;
|
||||||
|
@ -749,7 +766,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, msg, strlen(msg));
|
ipc_send_reply(client, payload_type, msg, strlen(msg));
|
||||||
json_object_put(request);
|
json_object_put(request);
|
||||||
sway_log(SWAY_INFO, "Unsupported event type in subscribe request");
|
sway_log(SWAY_INFO, "Unsupported event type in subscribe request");
|
||||||
goto exit_cleanup;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -761,7 +778,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, IPC_EVENT_TICK, tickmsg,
|
ipc_send_reply(client, IPC_EVENT_TICK, tickmsg,
|
||||||
strlen(tickmsg));
|
strlen(tickmsg));
|
||||||
}
|
}
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_INPUTS:
|
case IPC_GET_INPUTS:
|
||||||
|
@ -775,7 +792,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(inputs); // free
|
json_object_put(inputs); // free
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_SEATS:
|
case IPC_GET_SEATS:
|
||||||
|
@ -789,7 +806,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(seats); // free
|
json_object_put(seats); // free
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_TREE:
|
case IPC_GET_TREE:
|
||||||
|
@ -799,7 +816,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(tree);
|
json_object_put(tree);
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_MARKS:
|
case IPC_GET_MARKS:
|
||||||
|
@ -810,7 +827,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(marks);
|
json_object_put(marks);
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_VERSION:
|
case IPC_GET_VERSION:
|
||||||
|
@ -820,7 +837,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(version); // free
|
json_object_put(version); // free
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_BAR_CONFIG:
|
case IPC_GET_BAR_CONFIG:
|
||||||
|
@ -850,7 +867,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
const char *error = "{ \"success\": false, \"error\": \"No bar with that ID\" }";
|
const char *error = "{ \"success\": false, \"error\": \"No bar with that ID\" }";
|
||||||
ipc_send_reply(client, payload_type, error,
|
ipc_send_reply(client, payload_type, error,
|
||||||
(uint32_t)strlen(error));
|
(uint32_t)strlen(error));
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
json_object *json = ipc_json_describe_bar_config(bar);
|
json_object *json = ipc_json_describe_bar_config(bar);
|
||||||
const char *json_string = json_object_to_json_string(json);
|
const char *json_string = json_object_to_json_string(json);
|
||||||
|
@ -858,7 +875,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(json); // free
|
json_object_put(json); // free
|
||||||
}
|
}
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_BINDING_MODES:
|
case IPC_GET_BINDING_MODES:
|
||||||
|
@ -872,7 +889,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(modes); // free
|
json_object_put(modes); // free
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_BINDING_STATE:
|
case IPC_GET_BINDING_STATE:
|
||||||
|
@ -882,7 +899,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(current_mode); // free
|
json_object_put(current_mode); // free
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_GET_CONFIG:
|
case IPC_GET_CONFIG:
|
||||||
|
@ -893,7 +910,7 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
ipc_send_reply(client, payload_type, json_string,
|
ipc_send_reply(client, payload_type, json_string,
|
||||||
(uint32_t)strlen(json_string));
|
(uint32_t)strlen(json_string));
|
||||||
json_object_put(json); // free
|
json_object_put(json); // free
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case IPC_SYNC:
|
case IPC_SYNC:
|
||||||
|
@ -901,17 +918,13 @@ void ipc_client_handle_command(struct ipc_client *client, uint32_t payload_lengt
|
||||||
// It was decided sway will not support this, just return success:false
|
// It was decided sway will not support this, just return success:false
|
||||||
const char msg[] = "{\"success\": false}";
|
const char msg[] = "{\"success\": false}";
|
||||||
ipc_send_reply(client, payload_type, msg, strlen(msg));
|
ipc_send_reply(client, payload_type, msg, strlen(msg));
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
sway_log(SWAY_INFO, "Unknown IPC command type %x", payload_type);
|
sway_log(SWAY_INFO, "Unknown IPC command type %x", payload_type);
|
||||||
goto exit_cleanup;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
exit_cleanup:
|
|
||||||
free(buf);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_type,
|
bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_type,
|
||||||
|
@ -929,7 +942,7 @@ bool ipc_send_reply(struct ipc_client *client, enum ipc_command_type payload_typ
|
||||||
client->write_buffer_size *= 2;
|
client->write_buffer_size *= 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client->write_buffer_size > 4e6) { // 4 MB
|
if (client->write_buffer_size > IPC_MAX_SIZE) {
|
||||||
sway_log(SWAY_ERROR, "Client write buffer too big (%zu), disconnecting client",
|
sway_log(SWAY_ERROR, "Client write buffer too big (%zu), disconnecting client",
|
||||||
client->write_buffer_size);
|
client->write_buffer_size);
|
||||||
ipc_client_disconnect(client);
|
ipc_client_disconnect(client);
|
||||||
|
|
Loading…
Reference in a new issue