diff --git a/gui-daemon/guid.conf b/gui-daemon/guid.conf index 3f1530a..04d1454 100644 --- a/gui-daemon/guid.conf +++ b/gui-daemon/guid.conf @@ -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 } diff --git a/gui-daemon/qubes-guid b/gui-daemon/qubes-guid new file mode 100755 index 0000000..039e5c2 Binary files /dev/null and b/gui-daemon/qubes-guid differ diff --git a/gui-daemon/xside.c b/gui-daemon/xside.c index 947596c..c3f5a4b 100644 --- a/gui-daemon/xside.c +++ b/gui-daemon/xside.c @@ -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 && \ @@ -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) @@ -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); @@ -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; @@ -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; @@ -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); @@ -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"; @@ -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"); @@ -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); } } } @@ -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; @@ -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); @@ -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); } diff --git a/gui-daemon/xside.h b/gui-daemon/xside.h index f799c86..f2374b7 100644 --- a/gui-daemon/xside.h +++ b/gui-daemon/xside.h @@ -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) */