Skip to content

Commit 0d83229

Browse files
committed
Add event buffering for cloaking user input patterns
The user may wish to prevent biometric information about their mouse and keyboard patterns from leaking into certain Qubes. To make this possible, this feature inserts random noise into the delivery timing of all X events, making it more difficult to distinguish the user from other X event buffering users. The maximum delay in event delivery is user-configurable through the "events_max_delay" configuration option.
1 parent 3a74c63 commit 0d83229

File tree

5 files changed

+164
-17
lines changed

5 files changed

+164
-17
lines changed

gui-common/txrx-vchan.c

+3-12
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ int vchan_is_closed = 0;
3838
*/
3939
int double_buffered = 1;
4040

41-
static int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd);
42-
4341
void vchan_register_at_eof(void (*new_vchan_at_eof)(void)) {
4442
vchan_at_eof = new_vchan_at_eof;
4543
}
@@ -99,7 +97,7 @@ int read_data(libvchan_t *vchan, char *buf, int size)
9997
int ret;
10098
while (written < size) {
10199
while (!libvchan_data_ready(vchan))
102-
wait_for_vchan_or_argfd_once(vchan, -1);
100+
wait_for_vchan_or_argfd_once(vchan, -1, 1000);
103101
ret = libvchan_read(vchan, buf + written, size - written);
104102
if (ret <= 0)
105103
handle_vchan_error(vchan, "read data");
@@ -109,15 +107,15 @@ int read_data(libvchan_t *vchan, char *buf, int size)
109107
return size;
110108
}
111109

112-
static int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd)
110+
int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd, int timeout)
113111
{
114112
int ret;
115113
write_data(vchan, NULL, 0); // trigger write of queued data, if any present
116114
struct pollfd fds[] = {
117115
{ .fd = libvchan_fd_for_select(vchan), .events = POLLIN, .revents = 0 },
118116
{ .fd = fd, .events = POLLIN, .revents = 0 },
119117
};
120-
ret = poll(fds, fd == -1 ? 1 : 2, 1000);
118+
ret = poll(fds, fd == -1 ? 1 : 2, timeout);
121119
if (ret < 0) {
122120
if (errno == EINTR)
123121
return -1;
@@ -140,10 +138,3 @@ static int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd)
140138
}
141139
return ret;
142140
}
143-
144-
int wait_for_vchan_or_argfd(libvchan_t *vchan, int fd)
145-
{
146-
int ret;
147-
while ((ret=wait_for_vchan_or_argfd_once(vchan, fd)) == 0);
148-
return ret;
149-
}

gui-daemon/guid.conf

+6
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,10 @@ global: {
8585
# 256 to 256000 characters. Default is 64000 characters.
8686
#
8787
# max_clipboard_size = 64000
88+
89+
# Maximum delay in milliseconds for event buffering (used for obfuscating
90+
# biometric data that could be obtained by observing keyboard and mouse
91+
# patterns). Set to 0 to disable event buffering entirely.
92+
#
93+
# events_max_delay = 0;
8894
}

gui-daemon/xside.c

+135-4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
#include <sys/wait.h>
3838
#include <sys/resource.h>
3939
#include <sys/uio.h>
40+
#include <sys/queue.h>
41+
#include <sys/random.h>
4042
#include <signal.h>
4143
#include <poll.h>
4244
#include <errno.h>
@@ -2592,11 +2594,85 @@ static void process_xevent_xembed(Ghandles * g, const XClientMessageEvent * ev)
25922594

25932595
}
25942596

2597+
/* get current time */
2598+
static int64_t ebuf_current_time_ms()
2599+
{
2600+
int64_t timeval;
2601+
struct timespec spec;
2602+
clock_gettime(CLOCK_MONOTONIC, &spec);
2603+
timeval = (((int64_t)spec.tv_sec) * 1000LL) + (((int64_t)spec.tv_nsec) / 1000000LL);
2604+
return timeval;
2605+
}
2606+
2607+
/* get random delay value */
2608+
static uint32_t ebuf_random_delay(uint32_t upper_bound, uint32_t lower_bound)
2609+
{
2610+
uint32_t maxval;
2611+
uint32_t randval;
2612+
size_t randsize;
2613+
union ebuf_rand ebuf_rand_data;
2614+
2615+
if (lower_bound >= upper_bound) {
2616+
if (lower_bound > upper_bound) {
2617+
fprintf(stderr,
2618+
"Bug detected - lower_bound > upper_bound, events may get briefly stuck");
2619+
}
2620+
return upper_bound;
2621+
}
2622+
2623+
maxval = upper_bound - lower_bound + 1;
2624+
2625+
do {
2626+
randsize = getrandom(ebuf_rand_data.raw, sizeof(uint32_t), 0);
2627+
if (randsize != sizeof(uint32_t))
2628+
continue;
2629+
} while (ebuf_rand_data.val > UINT32_MAX - ((uint32_t)((((uint64_t)UINT32_MAX) + 1) % maxval)));
2630+
2631+
randval = ebuf_rand_data.val % maxval;
2632+
return lower_bound + randval;
2633+
}
2634+
2635+
/* queue input event */
2636+
static void ebuf_queue_xevent(Ghandles * g, XEvent xev)
2637+
{
2638+
int64_t current_time;
2639+
uint32_t random_delay;
2640+
struct ebuf_entry *new_ebuf_entry;
2641+
uint32_t lower_bound;
2642+
2643+
current_time = ebuf_current_time_ms();
2644+
2645+
/*
2646+
* Each event is scheduled by taking the current time and adding a delay.
2647+
* We do not want events later in the queue having a release timestamp
2648+
* that is *less* than an event earlier in the queue. This means that
2649+
* whatever delay we add *must* be at least enough to give a release
2650+
* timestamp larger than the one generated last time. To facilitate this,
2651+
* we set lower_bound to the scheduled release time of the last event
2652+
* minus the current time. Some sanity checks are included to make sure
2653+
* lower_bound is never less than 0 or greater than ebuf_max_delay.
2654+
*/
2655+
lower_bound = min(max(g->ebuf_prev_release_time - current_time, 0), g->ebuf_max_delay);
2656+
2657+
random_delay = ebuf_random_delay(g->ebuf_max_delay, lower_bound);
2658+
new_ebuf_entry = malloc(sizeof(struct ebuf_entry));
2659+
if (new_ebuf_entry == NULL) {
2660+
perror("Could not allocate ebuf_entry:");
2661+
exit(1);
2662+
}
2663+
if (current_time > 0 && random_delay > (INT64_MAX - current_time)) {
2664+
fprintf(stderr, "Event scheduler overflow detected, cannot continue");
2665+
exit(1);
2666+
}
2667+
new_ebuf_entry->time = current_time + random_delay;
2668+
new_ebuf_entry->xev = xev;
2669+
TAILQ_INSERT_TAIL(&(g->ebuf_head), new_ebuf_entry, entries);
2670+
g->ebuf_prev_release_time = new_ebuf_entry->time;
2671+
}
2672+
25952673
/* dispatch local Xserver event */
2596-
static void process_xevent(Ghandles * g)
2674+
static void process_xevent_core(Ghandles * g, XEvent event_buffer)
25972675
{
2598-
XEvent event_buffer;
2599-
XNextEvent(g->display, &event_buffer);
26002676
switch (event_buffer.type) {
26012677
case KeyPress:
26022678
case KeyRelease:
@@ -2655,6 +2731,40 @@ static void process_xevent(Ghandles * g)
26552731
}
26562732
}
26572733

2734+
/* dispatch queued events */
2735+
static void ebuf_release_xevents(Ghandles * g)
2736+
{
2737+
int64_t current_time;
2738+
struct ebuf_entry *current_ebuf_entry;
2739+
2740+
current_time = ebuf_current_time_ms();
2741+
while ((current_ebuf_entry = TAILQ_FIRST(&(g->ebuf_head)))
2742+
&& (current_time >= current_ebuf_entry->time)) {
2743+
XEvent event_buffer = current_ebuf_entry->xev;
2744+
process_xevent_core(g, event_buffer);
2745+
TAILQ_REMOVE(&(g->ebuf_head), current_ebuf_entry, entries);
2746+
free(current_ebuf_entry);
2747+
}
2748+
current_ebuf_entry = TAILQ_FIRST(&(g->ebuf_head));
2749+
if (current_ebuf_entry == NULL) {
2750+
g->ebuf_next_timeout = VCHAN_DEFAULT_POLL_DURATION;
2751+
} else {
2752+
g->ebuf_next_timeout = (int)(current_ebuf_entry->time - current_time);
2753+
}
2754+
}
2755+
2756+
/* handle or queue local Xserver event */
2757+
static void process_xevent(Ghandles * g)
2758+
{
2759+
XEvent event_buffer;
2760+
XNextEvent(g->display, &event_buffer);
2761+
if (g->ebuf_max_delay > 0) {
2762+
ebuf_queue_xevent(g, event_buffer);
2763+
} else {
2764+
process_xevent_core(g, event_buffer);
2765+
}
2766+
}
2767+
26582768

26592769
/* handle VM message: MSG_SHMIMAGE
26602770
* pass message data to do_shm_update - there input validation will be done */
@@ -4482,6 +4592,18 @@ static void parse_vm_config(Ghandles * g, config_setting_t * group)
44824592
exit(1);
44834593
}
44844594
}
4595+
4596+
if ((setting =
4597+
config_setting_get_member(group, "events_max_delay"))) {
4598+
int delay_val = config_setting_get_int(setting);
4599+
if (delay_val < 0 || delay_val > 5000) {
4600+
fprintf(stderr,
4601+
"unsupported value '%d' for events_max_delay (must be >= 0 and <= 5000)",
4602+
delay_val);
4603+
exit(1);
4604+
}
4605+
g->ebuf_max_delay = delay_val;
4606+
}
44854607
}
44864608

44874609
static void parse_config(Ghandles * g)
@@ -4604,6 +4726,8 @@ int main(int argc, char **argv)
46044726
/* parse cmdline, possibly overriding values from config */
46054727
parse_cmdline(&ghandles, argc, argv);
46064728
get_boot_lock(ghandles.domid);
4729+
/* init event queue */
4730+
TAILQ_INIT(&(ghandles.ebuf_head));
46074731

46084732
if (!ghandles.nofork) {
46094733
// daemonize...
@@ -4799,8 +4923,15 @@ int main(int argc, char **argv)
47994923
handle_message(&ghandles);
48004924
busy = 1;
48014925
}
4926+
if (ghandles.ebuf_max_delay > 0) {
4927+
ebuf_release_xevents(&ghandles);
4928+
}
48024929
} while (busy);
4803-
wait_for_vchan_or_argfd(ghandles.vchan, xfd);
4930+
if (ghandles.ebuf_max_delay > 0) {
4931+
wait_for_vchan_or_argfd_once(ghandles.vchan, xfd, ghandles.ebuf_next_timeout);
4932+
} else {
4933+
wait_for_vchan_or_argfd_once(ghandles.vchan, xfd, VCHAN_DEFAULT_POLL_DURATION);
4934+
}
48044935
}
48054936
return 0;
48064937
}

gui-daemon/xside.h

+19
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272

7373
#define MAX_SCREENSAVER_NAMES 10
7474

75+
#define VCHAN_DEFAULT_POLL_DURATION 1000
76+
7577
#ifdef __GNUC__
7678
# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
7779
#else
@@ -82,6 +84,7 @@
8284
#include <stdbool.h>
8385
#include <assert.h>
8486
#include <unistd.h>
87+
#include <sys/queue.h>
8588
#include <libvchan.h>
8689
#include <X11/Xlib.h>
8790
#include <xcb/xcb.h>
@@ -139,6 +142,17 @@ struct extra_prop {
139142
int nelements; /* data size, in "format" units */
140143
};
141144

145+
struct ebuf_entry {
146+
XEvent xev;
147+
int64_t time;
148+
TAILQ_ENTRY(ebuf_entry) entries;
149+
};
150+
151+
union ebuf_rand {
152+
uint32_t val;
153+
char raw[sizeof(uint32_t)];
154+
};
155+
142156
/* global variables
143157
* keep them in this struct for readability
144158
*/
@@ -240,6 +254,11 @@ struct _global_handles {
240254
int xen_fd; /* O_PATH file descriptor to /dev/xen/gntdev */
241255
int xen_dir_fd; /* file descriptor to /dev/xen */
242256
bool permit_subwindows : 1; /* Permit subwindows */
257+
uint32_t ebuf_max_delay;
258+
/* ebuf state */
259+
TAILQ_HEAD(tailhead, ebuf_entry) ebuf_head;
260+
int64_t ebuf_prev_release_time;
261+
int ebuf_next_timeout;
243262
};
244263

245264
typedef struct _global_handles Ghandles;

include/txrx.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ int read_data(libvchan_t *vchan, char *buf, int size);
3333
x.untrusted_len = sizeof(y); \
3434
real_write_message(vchan, (char*)&x, sizeof(x), (char*)&y, sizeof(y)); \
3535
} while(0)
36-
int wait_for_vchan_or_argfd(libvchan_t *vchan, int fd);
36+
int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd, int timeout);
3737
void vchan_register_at_eof(void (*new_vchan_at_eof)(void));
3838

3939
#endif /* _QUBES_TXRX_H */

0 commit comments

Comments
 (0)