Skip to content

Commit

Permalink
Add X event buffering for cloaking user input patterns
Browse files Browse the repository at this point in the history
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
certain 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 "xbuf_max_delay" configuration option.
  • Loading branch information
ArrayBolt3 committed Oct 4, 2024
1 parent 9966538 commit 1510ac9
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 17 deletions.
13 changes: 2 additions & 11 deletions gui-common/txrx-vchan.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ int vchan_is_closed = 0;
*/
int double_buffered = 1;

static int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd);

void vchan_register_at_eof(void (*new_vchan_at_eof)(void)) {
vchan_at_eof = new_vchan_at_eof;
}
Expand Down Expand Up @@ -109,15 +107,15 @@ int read_data(libvchan_t *vchan, char *buf, int size)
return size;
}

static int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd)
int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd)
{
int ret;
write_data(vchan, NULL, 0); // trigger write of queued data, if any present
struct pollfd fds[] = {
{ .fd = libvchan_fd_for_select(vchan), .events = POLLIN, .revents = 0 },
{ .fd = fd, .events = POLLIN, .revents = 0 },
};
ret = poll(fds, fd == -1 ? 1 : 2, 1000);
ret = poll(fds, fd == -1 ? 1 : 2, 1); // short timeout necessary for xbuf
if (ret < 0) {
if (errno == EINTR)
return -1;
Expand All @@ -140,10 +138,3 @@ static int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd)
}
return ret;
}

int wait_for_vchan_or_argfd(libvchan_t *vchan, int fd)
{
int ret;
while ((ret=wait_for_vchan_or_argfd_once(vchan, fd)) == 0);
return ret;
}
6 changes: 6 additions & 0 deletions gui-daemon/guid.conf
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,10 @@ global: {
# Timeout when waiting for qubes-gui-agent
#
# startup_timeout = 45;

# Maximum delay in milliseconds for X input buffering (used for obfuscating
# biometric data that could be obtained by observing keyboard and mosue
# patterns). Set to 0 to disable buffering entirely.
#
# xbuf_max_delay = 0;
}
128 changes: 123 additions & 5 deletions gui-daemon/xside.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/uio.h>
#include <sys/queue.h>
#include <sys/random.h>
#include <signal.h>
#include <poll.h>
#include <errno.h>
Expand Down Expand Up @@ -112,6 +114,13 @@ static Ghandles ghandles;

#define ignore_result(x) do { __typeof__(x) __attribute__((unused)) _ignore=(x); } while (0)

/* xbuf state and definitions */
TAILQ_HEAD(tailhead, xbuf_entry) xbuf_head;
union xbuf_rand xbuf_rand_data;
long xbuf_lower_bound = 0;
long xbuf_prev_release_time = 0;
long xbuf_max_delay = 0;

static int (*default_x11_io_error_handler)(Display *dpy);
static void inter_appviewer_lock(Ghandles *g, int mode);
static void release_mapped_mfns(Ghandles * g, struct windowdata *vm_window);
Expand Down Expand Up @@ -2386,11 +2395,61 @@ static void process_xevent_xembed(Ghandles * g, const XClientMessageEvent * ev)

}

/* get current time */
static long xbuf_current_time_ms()
{
struct timespec spec;
clock_gettime(CLOCK_MONOTONIC, &spec);
return (spec.tv_sec) * 1000 + (spec.tv_nsec) / 1000000;
}

/* get double-bounded random number */
static long xbuf_random_between(long lower, long upper)
{
long maxval;
long randval;
size_t randsize;
if (lower >= upper)
return upper;

maxval = upper - lower + 1;
if (maxval > UINT32_MAX)
return UINT32_MAX;

do {
randsize = getrandom(xbuf_rand_data.raw, sizeof(uint64_t), 0);
if (randsize != sizeof(uint64_t))
continue;
} while (xbuf_rand_data.val >= UINT64_MAX - (UINT64_MAX % maxval));

randval = (long)(xbuf_rand_data.val % maxval);
return lower + randval;
}

/* queue input event */
static void xbuf_queue_xevent(XEvent xev)
{
long current_time;
long random_delay;
struct xbuf_entry *new_xbuf_entry;

current_time = xbuf_current_time_ms();
xbuf_lower_bound = min(max(xbuf_prev_release_time - current_time, 0), xbuf_max_delay);
random_delay = xbuf_random_between(xbuf_lower_bound, xbuf_max_delay);
new_xbuf_entry = malloc(sizeof(struct xbuf_entry));
if (new_xbuf_entry == NULL) {
perror("Could not allocate xbuf_entry:");
exit(1);
}
new_xbuf_entry->time = current_time + random_delay;
new_xbuf_entry->xev = xev;
TAILQ_INSERT_TAIL(&xbuf_head, new_xbuf_entry, entries);
xbuf_prev_release_time = new_xbuf_entry->time;
}

/* dispatch local Xserver event */
static void process_xevent(Ghandles * g)
static void process_xevent_core(Ghandles * g, XEvent event_buffer)
{
XEvent event_buffer;
XNextEvent(g->display, &event_buffer);
switch (event_buffer.type) {
case KeyPress:
case KeyRelease:
Expand Down Expand Up @@ -2449,6 +2508,55 @@ static void process_xevent(Ghandles * g)
}
}

/* dispatch queued events */
static void xbuf_release_xevents(Ghandles * g)
{
long current_time;
struct xbuf_entry *current_xbuf_entry;

current_time = xbuf_current_time_ms();
while ((current_xbuf_entry = TAILQ_FIRST(&xbuf_head))
&& (current_time >= current_xbuf_entry->time)) {
XEvent event_buffer = current_xbuf_entry->xev;
process_xevent_core(g, event_buffer);
TAILQ_REMOVE(&xbuf_head, current_xbuf_entry, entries);
free(current_xbuf_entry);
}
}

/* handle or queue local Xserver event */
static void process_xevent(Ghandles * g)
{
XEvent event_buffer;
XNextEvent(g->display, &event_buffer);
if (xbuf_max_delay > 0) {
switch (event_buffer.type) {
case ReparentNotify:
case ConfigureNotify:
case EnterNotify:
case LeaveNotify:
case Expose:
case MapNotify:
case PropertyNotify:
case ClientMessage:
process_xevent_core(g, event_buffer);
break;
default:
// Buffered:
// KeyPress
// KeyRelease
// ButtonPress
// ButtonRelease
// MotionNotify
// FocusIn
// FocusOut
xbuf_queue_xevent(event_buffer);
}
} else {
process_xevent_core(g, event_buffer);
}
}


/* handle VM message: MSG_SHMIMAGE
* pass message data to do_shm_update - there input validation will be done */
Expand Down Expand Up @@ -4242,6 +4350,11 @@ static void parse_vm_config(Ghandles * g, config_setting_t * group)
exit(1);
}
}

if ((setting =
config_setting_get_member(group, "xbuf_max_delay"))) {
xbuf_max_delay = config_setting_get_int(setting);
}
}

static void parse_config(Ghandles * g)
Expand Down Expand Up @@ -4364,11 +4477,13 @@ int main(int argc, char **argv)
/* parse cmdline, possibly overriding values from config */
parse_cmdline(&ghandles, argc, argv);
get_boot_lock(ghandles.domid);
/* init event queue */
TAILQ_INIT(&xbuf_head);

if (!ghandles.nofork) {
// daemonize...
if (pipe(pipe_notify) < 0) {
perror("canot create pipe:");
perror("cannot create pipe:");
exit(1);
}

Expand Down Expand Up @@ -4559,8 +4674,11 @@ int main(int argc, char **argv)
handle_message(&ghandles);
busy = 1;
}
if (xbuf_max_delay > 0) {
xbuf_release_xevents(&ghandles);
}
} while (busy);
wait_for_vchan_or_argfd(ghandles.vchan, xfd);
wait_for_vchan_or_argfd_once(ghandles.vchan, xfd);
}
return 0;
}
12 changes: 12 additions & 0 deletions gui-daemon/xside.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
#include <stdbool.h>
#include <assert.h>
#include <unistd.h>
#include <sys/queue.h>
#include <libvchan.h>
#include <X11/Xlib.h>
#include <xcb/xcb.h>
Expand Down Expand Up @@ -243,6 +244,17 @@ struct _global_handles {

typedef struct _global_handles Ghandles;

struct xbuf_entry {
XEvent xev;
long time;
TAILQ_ENTRY(xbuf_entry) entries;
};

union xbuf_rand {
uint64_t val;
char raw[sizeof(uint64_t)];
};

#define ASSERT_HEIGHT_UNSIGNED(h) \
do { \
__typeof__(h) _h = (h); \
Expand Down
2 changes: 1 addition & 1 deletion include/txrx.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ int read_data(libvchan_t *vchan, char *buf, int size);
x.untrusted_len = sizeof(y); \
real_write_message(vchan, (char*)&x, sizeof(x), (char*)&y, sizeof(y)); \
} while(0)
int wait_for_vchan_or_argfd(libvchan_t *vchan, int fd);
int wait_for_vchan_or_argfd_once(libvchan_t *vchan, int fd);
void vchan_register_at_eof(void (*new_vchan_at_eof)(void));

#endif /* _QUBES_TXRX_H */

0 comments on commit 1510ac9

Please sign in to comment.