selection: add a 2 second timeout when receiving clipboard data

When reading clipboard data, a malicious clipboard provider could
stall us forever, by not sending any data, and not closing the pipe.

This commit adds a timer_fd based timeout of 2 seconds. If the timer
triggers, we abort the clipboard receive.
This commit is contained in:
Daniel Eklöf 2020-08-22 08:29:35 +02:00
parent 777a2eac51
commit 81222dac57
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F

View file

@ -8,6 +8,7 @@
#include <wctype.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#define LOG_MODULE "selection"
#define LOG_ENABLE_DBG 0
@ -993,12 +994,50 @@ selection_to_clipboard(struct seat *seat, struct terminal *term, uint32_t serial
}
struct clipboard_receive {
int read_fd;
int timeout_fd;
struct itimerspec timeout;
/* Callback data */
void (*cb)(const char *data, size_t size, void *user);
void (*done)(void *user);
void *user;
};
static void
clipboard_receive_done(struct fdm *fdm, struct clipboard_receive *ctx)
{
fdm_del(fdm, ctx->timeout_fd);
fdm_del(fdm, ctx->read_fd);
ctx->done(ctx->user);
free(ctx);
}
static bool
fdm_receive_timeout(struct fdm *fdm, int fd, int events, void *data)
{
struct clipboard_receive *ctx = data;
if (events & EPOLLHUP)
return false;
assert(events & EPOLLIN);
uint64_t expire_count;
ssize_t ret = read(fd, &expire_count, sizeof(expire_count));
if (ret < 0) {
if (errno == EAGAIN)
return true;
LOG_ERRNO("failed to read clipboard timeout timer");
return false;
}
LOG_WARN("no data received from clipboard in %llu seconds, aborting",
(unsigned long long)ctx->timeout.it_value.tv_sec);
clipboard_receive_done(fdm, ctx);
return true;
}
static bool
fdm_receive(struct fdm *fdm, int fd, int events, void *data)
@ -1008,6 +1047,12 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data)
if ((events & EPOLLHUP) && !(events & EPOLLIN))
goto done;
/* Reset timeout timer */
if (timerfd_settime(ctx->timeout_fd, 0, &ctx->timeout, NULL) < 0) {
LOG_ERRNO("failed to re-arm clipboard timeout timer");
return false;
}
/* Read until EOF */
while (true) {
char text[256];
@ -1044,9 +1089,7 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data)
}
done:
fdm_del(fdm, fd);
ctx->done(ctx->user);
free(ctx);
clipboard_receive_done(fdm, ctx);
return true;
}
@ -1055,28 +1098,52 @@ begin_receive_clipboard(struct terminal *term, int read_fd,
void (*cb)(const char *data, size_t size, void *user),
void (*done)(void *user), void *user)
{
int timeout_fd = -1;
struct clipboard_receive *ctx = NULL;
int flags;
if ((flags = fcntl(read_fd, F_GETFL)) < 0 ||
fcntl(read_fd, F_SETFL, flags | O_NONBLOCK) < 0)
{
LOG_ERRNO("failed to set O_NONBLOCK");
close(read_fd);
done(user);
return;
goto err;
}
struct clipboard_receive *ctx = xmalloc(sizeof(*ctx));
timeout_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if (timeout_fd < 0) {
LOG_ERRNO("failed to create clipboard timeout timer FD");
goto err;
}
const struct itimerspec timeout = {.it_value = {.tv_sec = 2}};
if (timerfd_settime(timeout_fd, 0, &timeout, NULL) < 0) {
LOG_ERRNO("faild to arm clipboard timeout timer");
goto err;
}
ctx = xmalloc(sizeof(*ctx));
*ctx = (struct clipboard_receive) {
.read_fd = read_fd,
.timeout_fd = timeout_fd,
.timeout = timeout,
.cb = cb,
.done = done,
.user = user,
};
if (!fdm_add(term->fdm, read_fd, EPOLLIN, &fdm_receive, ctx)) {
close(read_fd);
free(ctx);
done(user);
if (!fdm_add(term->fdm, read_fd, EPOLLIN, &fdm_receive, ctx) ||
!fdm_add(term->fdm, timeout_fd, EPOLLIN, &fdm_receive_timeout, ctx))
{
goto err;
}
return;
err:
free(ctx);
fdm_del(term->fdm, timeout_fd);
fdm_del(term->fdm, read_fd);
done(user);
}
void