diff --git a/Makefile b/Makefile index 582a14fd..1e5f69ef 100644 --- a/Makefile +++ b/Makefile @@ -168,6 +168,8 @@ install-common: install -d $(DESTDIR)/etc/qubes/post-install.d install -m 0755 appvm-scripts/etc/qubes/post-install.d/20-qubes-guivm-gui-agent.sh \ $(DESTDIR)/etc/qubes/post-install.d/20-qubes-guivm-gui-agent.sh + install -m 0755 appvm-scripts/etc/qubes/post-install.d/20-qubes-gui-agent.sh \ + $(DESTDIR)/etc/qubes/post-install.d/20-qubes-gui-agent.sh install -D appvm-scripts/usrbin/qubes-session \ $(DESTDIR)/usr/bin/qubes-session install -D appvm-scripts/usrbin/qubes-run-xorg \ diff --git a/appvm-scripts/etc/qubes/post-install.d/20-qubes-gui-agent.sh b/appvm-scripts/etc/qubes/post-install.d/20-qubes-gui-agent.sh new file mode 100644 index 00000000..e45fd8a2 --- /dev/null +++ b/appvm-scripts/etc/qubes/post-install.d/20-qubes-gui-agent.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +# announce support for late gui-daemon connection +qvm-features-request supported-feature.late-gui-daemon=1 diff --git a/appvm-scripts/usrbin/qubes-run-xorg b/appvm-scripts/usrbin/qubes-run-xorg index 04a4f828..e5ca772f 100755 --- a/appvm-scripts/usrbin/qubes-run-xorg +++ b/appvm-scripts/usrbin/qubes-run-xorg @@ -26,7 +26,14 @@ ## This script is triggered by qubes-gui-agent systemd service. -#expects W, H, MEM and DEPTH env vars to be set by invoker +#expects W, H, MEM and DEPTH env vars to be set by invoker, but use default +# values if they aren't + +[ -n "$W" ] || W=1920 +[ -n "$H" ] || H=1080 +[ -n "$MEM" ] || MEM=$(( W * H * 4 / 1024 )) +[ -n "$DEPTH" ] || DEPTH=24 + DUMMY_MAX_CLOCK=300 #hardcoded in dummy_drv PREFERRED_HSYNC=50 RES="$W"x"$H" diff --git a/debian/qubes-gui-agent.install b/debian/qubes-gui-agent.install index f51cb023..d5fd0610 100644 --- a/debian/qubes-gui-agent.install +++ b/debian/qubes-gui-agent.install @@ -10,6 +10,7 @@ etc/security/limits.d/90-qubes-gui.conf etc/qubes-rpc/qubes.SetMonitorLayout etc/qubes-rpc/qubes.GuiVMSession etc/qubes/post-install.d/20-qubes-guivm-gui-agent.sh +etc/qubes/post-install.d/20-qubes-gui-agent.sh etc/xdg/autostart/qubes-gtk4-workarounds.desktop etc/xdg/autostart/qubes-icon-sender.desktop etc/xdg/autostart/qubes-qrexec-fork-server.desktop diff --git a/gui-agent/vmside.c b/gui-agent/vmside.c index 09286c5b..fbf7b091 100644 --- a/gui-agent/vmside.c +++ b/gui-agent/vmside.c @@ -73,6 +73,8 @@ }) + sizeof(x)/sizeof((x)[0])) #define SOCKET_ADDRESS "/var/run/xf86-qubes-socket" +#define STATUS_FILE_PATH "/run/qubes/gui-agent.status" + /* Supported protocol version */ #define PROTOCOL_VERSION_MAJOR 1 @@ -2176,12 +2178,11 @@ static void handle_close(Ghandles * g, XID winid) /* start X server, returns its PID */ -static pid_t do_execute_xorg( - char *w_str, char *h_str, char *mem_str, char *depth_str, - char *gui_domid_str) +static pid_t do_execute_xorg(Ghandles *g) { pid_t pid; int fd; + char gui_domid_str[12]; pid = fork(); switch (pid) { @@ -2189,10 +2190,7 @@ static pid_t do_execute_xorg( perror("fork"); return -1; case 0: - setenv("W", w_str, 1); - setenv("H", h_str, 1); - setenv("MEM", mem_str, 1); - setenv("DEPTH", depth_str, 1); + snprintf(gui_domid_str, sizeof(gui_domid_str), "%u", g->domid); setenv("GUI_DOMID", gui_domid_str, 1); /* don't leak other FDs */ for (fd = 3; fd < 256; fd++) @@ -2374,27 +2372,11 @@ static void handle_message(Ghandles * g) } } -static pid_t get_xconf_and_run_x(Ghandles *g) -{ - struct msg_xconf xconf; - char w_str[12], h_str[12], mem_str[12], depth_str[12], gui_domid_str[12]; - pid_t x_pid; - read_struct(g->vchan, xconf); - snprintf(w_str, sizeof(w_str), "%d", xconf.w); - snprintf(h_str, sizeof(h_str), "%d", xconf.h); - snprintf(mem_str, sizeof(mem_str), "%d", xconf.mem); - snprintf(depth_str, sizeof(depth_str), "%d", xconf.depth); - snprintf(gui_domid_str, sizeof(gui_domid_str), "%u", g->domid); - x_pid = do_execute_xorg(w_str, h_str, mem_str, depth_str, gui_domid_str); - if (x_pid == (pid_t)-1) { - errx(1, "X server startup failed"); - } - return x_pid; -} - static void handshake(Ghandles *g) { uint32_t version = PROTOCOL_VERSION; + struct msg_xconf xconf; + write_struct(g->vchan, version); version = 0; read_struct(g->vchan, version); @@ -2407,27 +2389,54 @@ static void handshake(Ghandles *g) PROTOCOL_VERSION_MAJOR, PROTOCOL_VERSION_MINOR, major_version, minor_version); g->protocol_version = version; + + /* discard */ + read_struct(g->vchan, xconf); +} + +static void write_status_file(const char *status) +{ + int fd = open(STATUS_FILE_PATH, O_CREAT | O_WRONLY | O_NOFOLLOW | O_TRUNC, 0644); + if (fd < 0) { + perror("open status file"); + return; + } + FILE *f = fdopen(fd, "w"); + if (!f) { + perror("fdopen status file"); + close(fd); + return; + } + if (fwrite(status, strlen(status), 1, f) != 1) { + perror("write error"); + } + fclose(f); + close(fd); +} + +static void cleanup_status_file(void) +{ + unlink(STATUS_FILE_PATH); } static void handle_guid_disconnect(void) { Ghandles *g = ghandles_for_vchan_reinitialize; - struct msg_xconf xconf; if (!ghandles_for_vchan_reinitialize) { fprintf(stderr, "gui-daemon disconnected before fully initialized, " "cannot reconnect, exiting!\n"); exit(1); } + write_status_file("started\n"); libvchan_close(g->vchan); g->vchan = libvchan_server_init(g->domid, 6000, 4096, 4096); /* wait for gui daemon */ while (libvchan_is_open(g->vchan) == VCHAN_WAITING) libvchan_wait(g->vchan); handshake(g); - /* discard */ - read_struct(g->vchan, xconf); send_all_windows_info(g); + write_status_file("connected\n"); } static _Noreturn void handle_sigterm(int UNUSED(sig), @@ -2518,6 +2527,9 @@ int main(int argc, char **argv) int xfd; Ghandles g = { .x_pid = -1 }; + write_status_file("starting\n"); + atexit(cleanup_status_file); + g.created_input_device = access("/run/qubes-service/gui-agent-virtual-input-device", F_OK) == 0; if(g.created_input_device) { @@ -2571,7 +2583,6 @@ int main(int argc, char **argv) g.last_known_modifier_states = 0; } - parse_args(&g, argc, argv); /* Clipboard wipe functionality is controlled by the @@ -2580,18 +2591,8 @@ int main(int argc, char **argv) g.clipboard_wipe = access("/run/qubes-service/gui-agent-clipboard-wipe", F_OK) == 0; - g.vchan = libvchan_server_init(g.domid, 6000, 4096, 4096); - if (!g.vchan) { - fprintf(stderr, "vchan initialization failed\n"); - exit(1); - } - /* wait for gui daemon */ - while (libvchan_is_open(g.vchan) == VCHAN_WAITING) - libvchan_wait(g.vchan); - saved_argv = argv; - vchan_register_at_eof(handle_guid_disconnect); - ghandles_for_vchan_reinitialize = &g; + struct sigaction sigchld_handler = { .sa_sigaction = handle_sigchld, .sa_flags = SA_SIGINFO, @@ -2607,8 +2608,10 @@ int main(int argc, char **argv) if (sigaction(SIGTERM, &sigterm_handler, NULL)) err(1, "sigaction"); - handshake(&g); - g.x_pid = get_xconf_and_run_x(&g); + g.x_pid = do_execute_xorg(&g); + if (g.x_pid == (pid_t)-1) { + errx(1, "X server startup failed"); + } mkghandles(&g); /* Turn on Composite for all children of root window. This way X server @@ -2677,6 +2680,24 @@ int main(int argc, char **argv) fprintf(stderr, "Acquired MANAGER selection for tray\n"); } + + write_status_file("started\n"); + + g.vchan = libvchan_server_init(g.domid, 6000, 4096, 4096); + if (!g.vchan) { + fprintf(stderr, "vchan initialization failed\n"); + exit(1); + } + /* wait for gui daemon */ + while (libvchan_is_open(g.vchan) == VCHAN_WAITING) + libvchan_wait(g.vchan); + saved_argv = argv; + vchan_register_at_eof(handle_guid_disconnect); + + handshake(&g); + + write_status_file("connected\n"); + xfd = ConnectionNumber(g.display); struct pollfd fds[] = { { .fd = -1, .events = POLLIN | POLLHUP, .revents = 0 }, diff --git a/rpm_spec/gui-agent.spec.in b/rpm_spec/gui-agent.spec.in index ac95d997..9ba1d75d 100644 --- a/rpm_spec/gui-agent.spec.in +++ b/rpm_spec/gui-agent.spec.in @@ -315,6 +315,7 @@ rm -f %{name}-%{version} /etc/qubes-rpc/qubes.SetMonitorLayout /etc/qubes-rpc/qubes.GuiVMSession /etc/qubes/post-install.d/20-qubes-guivm-gui-agent.sh +/etc/qubes/post-install.d/20-qubes-gui-agent.sh %if 0%{?is_opensuse} %{_fillupdir}/desktop.qubes-gui-agent %else diff --git a/selinux/qubes-gui-agent.fc b/selinux/qubes-gui-agent.fc index 04ab6504..afc9da47 100644 --- a/selinux/qubes-gui-agent.fc +++ b/selinux/qubes-gui-agent.fc @@ -2,3 +2,4 @@ /usr/bin/qubes-gui-runuser -- gen_context(system_u:object_r:xdm_exec_t,s0) /usr/lib/qubes/qubes-xorg-wrapper -- gen_context(system_u:object_r:xserver_exec_t,s0) /usr/bin/qubes-run-xephyr -- gen_context(system_u:object_r:xserver_exec_t,s0) +/run/qubes/gui-agent.status -- gen_context(system_u:object_r:qubes_gui_agent_state_file_t,s0) diff --git a/selinux/qubes-gui-agent.te b/selinux/qubes-gui-agent.te index 7b1755c6..dca69509 100644 --- a/selinux/qubes-gui-agent.te +++ b/selinux/qubes-gui-agent.te @@ -7,6 +7,9 @@ require { class tcp_socket name_connect; } +type qubes_gui_agent_state_file_t; +files_type(qubes_gui_agent_state_file_t) + allow xdm_t etc_t:file { create write }; allow xdm_t self:passwd rootok; allow domain local_login_t:unix_stream_socket { rw_inherited_sock_file_perms ioctl }; @@ -16,6 +19,11 @@ init_domtrans_script(xdm_t) type_transition xdm_t qubes_var_run_t:sock_file qubes_qrexec_socket_t; manage_sock_files_pattern(xdm_t, qubes_var_run_t, qubes_qrexec_socket_t) write_sock_files_pattern(xdm_t, qubes_var_run_t, qubes_var_run_t) + +allow xdm_t qubes_gui_agent_state_file_t:file { create_file_perms write_file_perms delete_file_perms }; +allow xdm_t qubes_var_run_t:dir { add_entry_dir_perms del_entry_dir_perms }; +filetrans_pattern(xdm_t, qubes_var_run_t, qubes_gui_agent_state_file_t, file, "gui-agent.status") + optional { pulseaudio_domtrans(xdm_t) dev_rw_xen(pulseaudio_t)