Skip to content

Commit

Permalink
Make maximum clipboard buffer lenght configurable
Browse files Browse the repository at this point in the history
Users will be able to set global and/or per qube clipboard limit size by
setting `max_clipboard_size` feature of GUIVM or individual qubes

Clipboard sizes over the limit will trigger special event for a pop-up

fixes: QubesOS/qubes-issues#9296
  • Loading branch information
alimirjamali committed Oct 13, 2024
1 parent 9966538 commit 3a15fee
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 7 deletions.
5 changes: 5 additions & 0 deletions gui-daemon/guid.conf
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,9 @@ global: {
# Timeout when waiting for qubes-gui-agent
#
# startup_timeout = 45;

# Inter-qube clipboard maximum character limit. This could be between
# 256 to 256000 characters. Default is 64000 characters.
#
# max_clipboard_size = 64000
}
Binary file added gui-daemon/qubes-guid
Binary file not shown.
73 changes: 66 additions & 7 deletions gui-daemon/xside.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
/* Supported protocol version */

#define PROTOCOL_VERSION_MAJOR UINT32_C(1)
#define PROTOCOL_VERSION_MINOR UINT32_C(7)
#define PROTOCOL_VERSION_MINOR UINT32_C(8)
#define PROTOCOL_VERSION(x, y) ((x) << 16 | (y))

#if !(PROTOCOL_VERSION_MAJOR == QUBES_GUID_PROTOCOL_VERSION_MAJOR && \
Expand Down Expand Up @@ -842,6 +842,31 @@ static void save_clipboard_source_vmname(const char *vmname) {
umask(old_umask);
}

// TODO: phase out .source and .xevent in favor of .metadata after r4.2 EOL
static void save_clipboard_metadata(Ghandles *g, int len) {
FILE *file;
mode_t old_umask;

/* grant group write */
old_umask = umask(0002);
file = fopen(QUBES_CLIPBOARD_FILENAME ".metadata", "w");
if (!file) {
perror("Can not create " QUBES_CLIPBOARD_FILENAME ".metadata file");
exit(1);
}
fprintf(file, "qube-name=%s\n", g->vmname);
fprintf(file, "xevent-timestamp=%lu\n", g->clipboard_xevent_time);
fprintf(file, "clipboard-sent-size=%d\n", len);
fprintf(file, "clipboard-buffer-size=%d\n", g->clipboard_buffer_size);
fprintf(file, "gui-protocol-version-xside=%d.%d\n",
PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR);
fprintf(file, "gui-protocol-version-vmside=%d.%d\n",
g->protocol_version >> 16, g->protocol_version & 0xFFFF);
// other key,value pairs could be added if needed in future
fclose(file);
umask(old_umask);
}

/* fetch clippboard content from file */
/* lock already taken in is_special_keypress() */
static void get_qubes_clipboard(Ghandles *g, char **data, int *len)
Expand Down Expand Up @@ -939,8 +964,8 @@ static int run_clipboard_rpc(Ghandles * g, enum clipboard_op op) {
}
umask(old_umask);
if (op == CLIPBOARD_COPY) {
rl.rlim_cur = MAX_CLIPBOARD_SIZE;
rl.rlim_max = MAX_CLIPBOARD_SIZE;
rl.rlim_cur = g->clipboard_buffer_size;
rl.rlim_max = g->clipboard_buffer_size;
setrlimit(RLIMIT_FSIZE, &rl);
}
dup2(fd, 1);
Expand Down Expand Up @@ -1013,16 +1038,25 @@ static void handle_clipboard_data(Ghandles * g, unsigned int untrusted_len)
FILE *file;
char *untrusted_data;
size_t untrusted_data_sz;
bool clipboard_buffer_exceeded = false;
Time clipboard_file_xevent_time;
mode_t old_umask;

if (g->log_level > 0)
fprintf(stderr, "handle_clipboard_data, len=0x%x\n",
untrusted_len);
if (untrusted_len > MAX_CLIPBOARD_SIZE) {
fprintf(stderr, "clipboard data len 0x%x?\n",
if ((untrusted_len > MAX_CLIPBOARD_SIZE) && (g->protocol_version < QUBES_GUID_MIN_CLIPBOARD_4X)) ||
(untrusted_len > MAX_CLIPBOARD_BUFFER_SIZE + 1) {
/* handle malformed clipboard sizes for both old and new protocols */
fprintf(stderr, "clipboard data len 0x%x exceeds maximum allowed!\n",
untrusted_len);
exit(1);
} else if (untrusted_len > g->clipboard_buffer_size) {
/* clipboard size over VM limit. trigger GUI notification */
clipboard_buffer_exceeded = true;
if (g->log_level > 0)
fprintf(stderr, "clipboard data len %d exceeds VM's allowed!\n",
untrusted_len);
}
/* now sanitized */
untrusted_data_sz = untrusted_len;
Expand Down Expand Up @@ -1057,7 +1091,8 @@ static void handle_clipboard_data(Ghandles * g, unsigned int untrusted_len)
show_error_message(g, "secure copy: failed to open file " QUBES_CLIPBOARD_FILENAME);
goto error;
}
if (fwrite(untrusted_data, 1, untrusted_data_sz, file) != untrusted_data_sz) {
if (! clipboard_buffer_exceeded) // do not write anything to output if size exceeds limit
if (fwrite(untrusted_data, 1, untrusted_data_sz, file) != untrusted_data_sz) {
fclose(file);
show_error_message(g, "secure copy: failed to write to file " QUBES_CLIPBOARD_FILENAME);
goto error;
Expand All @@ -1068,6 +1103,7 @@ static void handle_clipboard_data(Ghandles * g, unsigned int untrusted_len)
}
save_clipboard_source_vmname(g->vmname);
save_clipboard_file_xevent_timestamp(g->clipboard_xevent_time);
save_clipboard_metadata(g, untrusted_data_sz);
error:
umask(old_umask);
inter_appviewer_lock(g, 0);
Expand Down Expand Up @@ -3778,6 +3814,7 @@ struct option longopts[] = {
{ "trayicon-mode", required_argument, NULL, opt_trayicon_mode },
{ "screensaver-name", required_argument, NULL, opt_screensaver_name },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' }, // V is virtual and not a short option
{ 0, 0, 0, 0 },
};
static const char optstring[] = "+C:d:t:N:c:l:i:K:vqQnafIp:Th";
Expand Down Expand Up @@ -3807,6 +3844,8 @@ static void usage(FILE *stream)
fprintf(stream, " --title-name, -T\tprefix window titles with VM name\n");
fprintf(stream, " --trayicon-mode\ttrayicon coloring mode (see below); default: tint\n");
fprintf(stream, " --screensaver-name\tscreensaver window name, can be repeated, default: xscreensaver\n");
fprintf(stream, " --help, -h\tshow command help\n");
fprintf(stream, " --version\tshow protocol version\n");
fprintf(stream, " --override-redirect=disabled\tdisable the “override redirect” flag (will likely break applications)\n");
fprintf(stream, "\n");
fprintf(stream, "Log levels:\n");
Expand Down Expand Up @@ -3842,6 +3881,11 @@ static void parse_cmdline_config_path(Ghandles * g, int argc, char **argv)
} else if (opt == 'h') {
usage(stdout);
exit(0);
} else if (opt == 'V') {
fprintf(stdout, "Qubes GUI Daemon protocol version: %d.%d\n",
PROTOCOL_VERSION_MAJOR,
PROTOCOL_VERSION_MINOR);
exit(0);
}
}
}
Expand Down Expand Up @@ -4119,6 +4163,7 @@ static void load_default_config_values(Ghandles * g)
g->copy_seq_key = XK_c;
g->paste_seq_mask = ControlMask | ShiftMask;
g->paste_seq_key = XK_v;
g->clipboard_buffer_size = DEFAULT_CLIPBOARD_BUFFER_SIZE;
g->allow_fullscreen = 0;
g->override_redirect_protection = 1;
g->startup_timeout = 45;
Expand Down Expand Up @@ -4190,6 +4235,20 @@ static void parse_vm_config(Ghandles * g, config_setting_t * group)
&g->paste_seq_mask, &g->paste_seq_key);
}

if ((setting =
config_setting_get_member(group, "max_clipboard_size"))) {
int value = config_setting_get_int(setting);
if (value > MAX_CLIPBOARD_BUFFER_SIZE || value < MIN_CLIPBOARD_BUFFER_SIZE) {
fprintf(stderr,
"unsupported value ‘%d’ for max_clipboard_size "
"(must be between %d to %d characters).\n",
value, MAX_CLIPBOARD_BUFFER_SIZE, MIN_CLIPBOARD_BUFFER_SIZE);
exit(1);
} else {
g->clipboard_buffer_size = value;
}
}

if ((setting =
config_setting_get_member(group, "allow_utf8_titles"))) {
g->allow_utf8_titles = config_setting_get_bool(setting);
Expand Down Expand Up @@ -4237,7 +4296,7 @@ static void parse_vm_config(Ghandles * g, config_setting_t * group)
g->disable_override_redirect = 0;
else {
fprintf(stderr,
"unsupported value ‘%s’ for override_redirect (must be ‘disabled’ or ‘allow’\n",
"unsupported value ‘%s’ for override_redirect (must be ‘disabled’ or ‘allow’)\n",
value);
exit(1);
}
Expand Down
1 change: 1 addition & 0 deletions gui-daemon/xside.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ struct _global_handles {
KeySym copy_seq_key; /* key for secure-copy key sequence */
int paste_seq_mask; /* modifiers mask for secure-paste key sequence */
KeySym paste_seq_key; /* key for secure-paste key sequence */
unsigned int clipboard_buffer_size; /* maximum clipboard character limit */
int qrexec_clipboard; /* 0: use GUI protocol to fetch/put clipboard, 1: use qrexec */
int use_kdialog; /* use kdialog for prompts (default on KDE) or zenity (default on non-KDE) */
int prefix_titles; /* prefix windows titles with VM name (for WM without support for _QUBES_VMNAME property) */
Expand Down

0 comments on commit 3a15fee

Please sign in to comment.