From 353123ee133a258db7d1595eaeca097bedee2753 Mon Sep 17 00:00:00 2001 From: Piotr Bartman-Szwarc Date: Sat, 22 Nov 2025 18:04:18 +0100 Subject: [PATCH 1/5] sys-log: alpha --- debian/qubes-core-agent.install | 2 + qubes-rpc/.gitignore | 1 + qubes-rpc/Makefile | 12 +- qubes-rpc/qubes.Log | 2 + qubes-rpc/vm-log.c | 236 ++++++++++++++++++++++++++++++++ rpm_spec/core-agent.spec.in | 2 + 6 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 qubes-rpc/qubes.Log create mode 100644 qubes-rpc/vm-log.c diff --git a/debian/qubes-core-agent.install b/debian/qubes-core-agent.install index 5c1c92c8..78f7856c 100644 --- a/debian/qubes-core-agent.install +++ b/debian/qubes-core-agent.install @@ -16,6 +16,7 @@ etc/qubes-rpc/qubes.Filecopy etc/qubes-rpc/qubes.GetAppmenus etc/qubes-rpc/qubes.GetImageRGBA etc/qubes-rpc/qubes.InstallUpdatesGUI +etc/qubes-rpc/qubes.Log etc/qubes-rpc/qubes.OpenInVM etc/qubes-rpc/qubes.OpenURL etc/qubes-rpc/qubes.PostInstall @@ -194,6 +195,7 @@ usr/lib/qubes/update-proxy-configs usr/lib/qubes/upgrades-installed-check usr/lib/qubes/upgrades-status-notify usr/lib/qubes/vm-file-editor +usr/lib/qubes/vm-log usr/lib/qubes/xdg-icon usr/lib/qubes/set-default-text-editor usr/share/glib-2.0/schemas/* diff --git a/qubes-rpc/.gitignore b/qubes-rpc/.gitignore index 681721b8..11898196 100644 --- a/qubes-rpc/.gitignore +++ b/qubes-rpc/.gitignore @@ -6,4 +6,5 @@ qfile-agent-dvm qfile-unpacker qopen-in-vm vm-file-editor +vm-log tar2qfile diff --git a/qubes-rpc/Makefile b/qubes-rpc/Makefile index 0c2673f6..9cd5e941 100644 --- a/qubes-rpc/Makefile +++ b/qubes-rpc/Makefile @@ -17,13 +17,13 @@ LDLIBS := -lqubes-rpc-filecopy -lqubes-pure .PHONY: all clean install -all: vm-file-editor qopen-in-vm qfile-agent qfile-unpacker tar2qfile qubes-fs-tree-check bin-qfile-unpacker +all: vm-file-editor vm-log qopen-in-vm qfile-agent qfile-unpacker tar2qfile qubes-fs-tree-check bin-qfile-unpacker ifdef DEVEL_BUILD # Ensure that these programs can find their shared libraries, # even when installed in e.g. a TemplateBasedVM to somewhere other # than /usr. -vm-file-editor qopen-in-vm qfile-agent qfile-unpacker tar2qfile qubes-fs-tree-check: LDFLAGS += '-Wl,-rpath,$$ORIGIN/../../$$LIB' +vm-file-editor vm-log qopen-in-vm qfile-agent qfile-unpacker tar2qfile qubes-fs-tree-check: LDFLAGS += '-Wl,-rpath,$$ORIGIN/../../$$LIB' # This is installed in /usr/bin, not /usr/lib/qubes, so it needs a different rpath. bin-qfile-unpacker: LDFLAGS += '-Wl,-rpath,$$ORIGIN/../$$LIB' endif @@ -33,13 +33,14 @@ bin-qfile-unpacker: qfile-unpacker.o gui-fatal.o qubes-fs-tree-check: LDLIBS := -lqubes-pure qubes-fs-tree-check: qubes-fs-tree-check.o vm-file-editor: vm-file-editor.o +vm-log: vm-log.o qopen-in-vm: qopen-in-vm.o gui-fatal.o qfile-agent: qfile-agent.o gui-fatal.o qfile-unpacker: qfile-unpacker.o gui-fatal.o tar2qfile: tar2qfile.o gui-fatal.o clean: - -$(RM) -- qopen-in-vm qfile-agent qfile-unpacker tar2qfile vm-file-editor qubes-fs-tree-check bin-qfile-unpacker *.o + -$(RM) -- qopen-in-vm qfile-agent qfile-unpacker tar2qfile vm-file-editor vm-log qubes-fs-tree-check bin-qfile-unpacker *.o install: install -d $(DESTDIR)$(BINDIR) @@ -61,7 +62,7 @@ install: prepare-suspend resize-rootfs \ qfile-agent qopen-in-vm qrun-in-vm qubes-sync-clock \ tar2qfile vm-file-editor xdg-icon qvm-template-repo-query \ - qubes-fs-tree-check + qubes-fs-tree-check vm-log # Install qfile-unpacker as SUID, because it will fail to receive # files from other vm. install -t $(DESTDIR)$(QUBESLIBDIR) -m 4755 qfile-unpacker @@ -95,7 +96,8 @@ install: qubes.GetDate \ qubes.ShowInTerminal \ qubes.TemplateSearch \ - qubes.TemplateDownload + qubes.TemplateDownload \ + qubes.Log $(LN) qubes.VMExec $(DESTDIR)$(QUBESRPCCMDDIR)/qubes.VMExecGUI $(LN) /dev/tcp/127.0.0.1 $(DESTDIR)$(QUBESRPCCMDDIR)/qubes.ConnectTCP $(LN) /dev/tcp/127.0.0.1/8082 $(DESTDIR)$(QUBESRPCCMDDIR)/qubes.UpdatesProxy diff --git a/qubes-rpc/qubes.Log b/qubes-rpc/qubes.Log new file mode 100644 index 00000000..8da1736e --- /dev/null +++ b/qubes-rpc/qubes.Log @@ -0,0 +1,2 @@ +#!/bin/sh +exec /usr/lib/qubes/vm-log \ No newline at end of file diff --git a/qubes-rpc/vm-log.c b/qubes-rpc/vm-log.c new file mode 100644 index 00000000..4b381e49 --- /dev/null +++ b/qubes-rpc/vm-log.c @@ -0,0 +1,236 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2025-2026 Piotr Bartman-Szwarc + + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_LINE_SIZE 4096 +#define DEFAULT_PRIO LOG_INFO + +// Syslog priority array (Severity 0 to 7) +const int SEV2SYSLOG_ARRAY[] = { + LOG_EMERG, + LOG_ALERT, + LOG_CRIT, + LOG_ERR, + LOG_WARNING, + LOG_NOTICE, + LOG_INFO, + LOG_DEBUG, +}; + + +/** + * Extracts the syslog severity (0-7). + * @param pri Full priority value (facility * 8 + severity) + * @return The severity value (LOG_EMERG, LOG_ALERT, etc.) + */ +int extract_priority(int pri) { + int severity = pri % 8; + if (severity >= 0 && severity <= 7) { + return SEV2SYSLOG_ARRAY[severity]; + } + return DEFAULT_PRIO; +} + +/** + * Implements strict ASCII filtering and replaces non-printable control characters. + * All bytes outside the standard ASCII range (0x00 - 0x7F) are replaced with '?'. + * Control characters (0x00-0x1F) and DEL (0x7F) are replaced with ' '. + * + * @param str Input string (read-only) + * @param result Buffer to store the sanitized output (must be large enough) + * @param max_len Maximum length of the input string to process + * @return The length of the sanitized string written to result + */ +size_t filter_to_ascii_and_sanitize(const char *str, char *result, size_t max_len) { + size_t i; + size_t j = 0; + + for (i = 0; i < max_len && str[i] != '\0'; i++) { + unsigned char byte = (unsigned char)str[i]; + + if (byte > 0x7F) { + result[j++] = '?'; + continue; + } + + // Replace harmful control characters (0x00-0x1F) and DEL (0x7F) + if ((byte < 0x20) || byte == 0x7F) { + result[j++] = ' '; + } + // Keep printable ASCII characters + else { + result[j++] = str[i]; + } + } + result[j] = '\0'; + return j; +} + +/** + * Analyzes priority and sanitizes the input string. + * + * @param msg_in Untrusted log line (read-only) + * @param msg_out Buffer for the sanitized message + * @param msg_out_size Size of the message buffer + * @return Syslog priority + */ +int sanitize(const char *msg_in, char *msg_out, size_t msg_out_size) { + int prio = DEFAULT_PRIO; + + // Max size for the matched priority string (up to 3 digits + NUL) + char prio_str[4] = {0}; + + regex_t regex; + // Regex pattern: starts with '<' followed by 1-3 digits, followed by '>', + // and then anything + const char *pattern = "^<([0-9]{1,3})>.*$"; + const int priority_id = 1; + regmatch_t matches[2]; + + if (regcomp(®ex, pattern, REG_EXTENDED) == 0) { + if (regexec(®ex, msg_in, 2, matches, 0) == 0) { + // Check if the priority group was matched + if (matches[priority_id].rm_so != -1) { + // Convert signed regoff_t difference to size_t + long len_signed = matches[priority_id].rm_eo - matches[priority_id].rm_so; + + if (len_signed > 0) { + size_t len = (size_t)len_signed; + + // Comparison: size_t (len) vs size_t (sizeof) + if (len < sizeof(prio_str)) { + strncpy(prio_str, msg_in + matches[priority_id].rm_so, len); + prio_str[len] = '\0'; + + // Convert string to integer + int full_prio = atoi(prio_str); + prio = extract_priority(full_prio); + } + } + } + } + regfree(®ex); + } + + // Sanitize the message and write it to msg_out + // Cast return value to void as it is ignored right now + (void)filter_to_ascii_and_sanitize(untr, msg_out, msg_out_size); + + return prio; +} + +void handle_untrusted(const char *vm) { + char ident[256]; + snprintf(ident, sizeof(ident), "qubes.Log(%s)", vm); + + openlog(ident, LOG_PID | LOG_CONS, LOG_USER); + + char buffer[MAX_LINE_SIZE + 2]; + char trusted_msg[MAX_LINE_SIZE + 4]; + + // Initial confirmation to the sender, i.e., we are ready to receive messages + printf("OK\n"); + fflush(stdout); + + while (fgets(buffer, sizeof(buffer), stdin) != NULL) { + + // Determine the actual length of the string read (before NUL) + size_t len = strlen(buffer); + + int truncated = 0; + // If fgets stopped due to buffer size (len >= MAX_LINE_SIZE + 1) + // AND the last character is NOT '\n', the line was truncated. + if (len == sizeof(buffer) - 1 && buffer[MAX_LINE_SIZE] != '\n') { + truncated = 1; + + int c; + while ((c = getchar()) != '\n' && c != EOF); + } + + // Remove trailing newline and carriage return + if (len > 0 && buffer[len - 1] == '\n') { + buffer[--len] = '\0'; + } + if (len > 0 && buffer[len - 1] == '\r') { + buffer[--len] = '\0'; + } + + // Check for empty lines + if (len == 0 && !feof(stdin) && !truncated) { + continue; + } + + int trusted_prio = sanitize(buffer, trusted_msg, MAX_LINE_SIZE); + + if (truncated) { + strcat(trusted_msg, "..."); + } + + + syslog(trusted_prio, "%s", trusted_msg); + // confrimation to the sender + printf("OK\n"); + fflush(stdout); + } + + closelog(); +} + + +int main() { + const char *remote_domain = getenv("QREXEC_REMOTE_DOMAIN"); + if (!remote_domain) { + fprintf(stderr, "Error: Failed to identify the source VM (QREXEC_REMOTE_DOMAIN not set).\n"); + return 1; + } + + if (strlen(remote_domain) > 100) { + fprintf(stderr, "Error: Remote domain name is too long.\n"); + return 1; + } + + regex_t regex; + const char *pattern = "^[a-zA-Z0-9_-]+$"; + + if (regcomp(®ex, pattern, REG_EXTENDED | REG_NOSUB) != 0) { + fprintf(stderr, "Error: Could not compile regex.\n"); + return 1; + } + + if (regexec(®ex, remote_domain, 0, NULL, 0) != 0) { + regfree(®ex); + fprintf(stderr, "Error: Invalid characters in QREXEC_REMOTE_DOMAIN.\n"); + return 1; + } + regfree(®ex); + + (void)handle_untrusted(remote_domain); + + return 0; +} \ No newline at end of file diff --git a/rpm_spec/core-agent.spec.in b/rpm_spec/core-agent.spec.in index e6d79b96..761afddf 100644 --- a/rpm_spec/core-agent.spec.in +++ b/rpm_spec/core-agent.spec.in @@ -898,6 +898,7 @@ rm -f %{name}-%{version} %dir /etc/qubes-rpc %config(noreplace) /etc/qubes-rpc/qubes.ShowInTerminal %config(noreplace) /etc/qubes-rpc/qubes.Filecopy +%config(noreplace) /etc/qubes-rpc/qubes.Log %config(noreplace) /etc/qubes-rpc/qubes.OpenInVM %config(noreplace) /etc/qubes-rpc/qubes.OpenURL %config(noreplace) /etc/qubes-rpc/qubes.GetAppmenus @@ -1027,6 +1028,7 @@ rm -f %{name}-%{version} /usr/lib/qubes/qubes-fs-tree-check /usr/lib/qubes/tar2qfile /usr/lib/qubes/vm-file-editor +/usr/lib/qubes/vm-log /usr/lib/qubes/xdg-icon /usr/lib/qubes/update-proxy-configs /usr/lib/qubes/upgrades-installed-check From 5b3c1ae7779ad06279c61833c11b8fec7cc5669b Mon Sep 17 00:00:00 2001 From: Piotr Bartman-Szwarc Date: Mon, 9 Feb 2026 10:27:35 +0100 Subject: [PATCH 2/5] sys-log: use sanitization from qubes-pure --- qubes-rpc/vm-log.c | 44 +++++--------------------------------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/qubes-rpc/vm-log.c b/qubes-rpc/vm-log.c index 4b381e49..8f4bfc0e 100644 --- a/qubes-rpc/vm-log.c +++ b/qubes-rpc/vm-log.c @@ -27,6 +27,7 @@ #include #include #include +#include #define MAX_LINE_SIZE 4096 #define DEFAULT_PRIO LOG_INFO @@ -57,41 +58,6 @@ int extract_priority(int pri) { return DEFAULT_PRIO; } -/** - * Implements strict ASCII filtering and replaces non-printable control characters. - * All bytes outside the standard ASCII range (0x00 - 0x7F) are replaced with '?'. - * Control characters (0x00-0x1F) and DEL (0x7F) are replaced with ' '. - * - * @param str Input string (read-only) - * @param result Buffer to store the sanitized output (must be large enough) - * @param max_len Maximum length of the input string to process - * @return The length of the sanitized string written to result - */ -size_t filter_to_ascii_and_sanitize(const char *str, char *result, size_t max_len) { - size_t i; - size_t j = 0; - - for (i = 0; i < max_len && str[i] != '\0'; i++) { - unsigned char byte = (unsigned char)str[i]; - - if (byte > 0x7F) { - result[j++] = '?'; - continue; - } - - // Replace harmful control characters (0x00-0x1F) and DEL (0x7F) - if ((byte < 0x20) || byte == 0x7F) { - result[j++] = ' '; - } - // Keep printable ASCII characters - else { - result[j++] = str[i]; - } - } - result[j] = '\0'; - return j; -} - /** * Analyzes priority and sanitizes the input string. * @@ -100,7 +66,7 @@ size_t filter_to_ascii_and_sanitize(const char *str, char *result, size_t max_le * @param msg_out_size Size of the message buffer * @return Syslog priority */ -int sanitize(const char *msg_in, char *msg_out, size_t msg_out_size) { +int sanitize(const char *untr_msg_in, char *msg_out, size_t msg_out_size) { int prio = DEFAULT_PRIO; // Max size for the matched priority string (up to 3 digits + NUL) @@ -114,7 +80,7 @@ int sanitize(const char *msg_in, char *msg_out, size_t msg_out_size) { regmatch_t matches[2]; if (regcomp(®ex, pattern, REG_EXTENDED) == 0) { - if (regexec(®ex, msg_in, 2, matches, 0) == 0) { + if (regexec(®ex, untr_msg_in, 2, matches, 0) == 0) { // Check if the priority group was matched if (matches[priority_id].rm_so != -1) { // Convert signed regoff_t difference to size_t @@ -125,7 +91,7 @@ int sanitize(const char *msg_in, char *msg_out, size_t msg_out_size) { // Comparison: size_t (len) vs size_t (sizeof) if (len < sizeof(prio_str)) { - strncpy(prio_str, msg_in + matches[priority_id].rm_so, len); + strncpy(prio_str, untr_msg_in + matches[priority_id].rm_so, len); prio_str[len] = '\0'; // Convert string to integer @@ -140,7 +106,7 @@ int sanitize(const char *msg_in, char *msg_out, size_t msg_out_size) { // Sanitize the message and write it to msg_out // Cast return value to void as it is ignored right now - (void)filter_to_ascii_and_sanitize(untr, msg_out, msg_out_size); + (void)qubes_pure_sanitize_string_safe_for_display(untr_msg_in, msg_out, msg_out_size); return prio; } From ce26a000e945c60e8602c3b5d839d0bfdff13e14 Mon Sep 17 00:00:00 2001 From: Piotr Bartman-Szwarc Date: Mon, 9 Mar 2026 20:32:11 +0100 Subject: [PATCH 3/5] sys-log: use qubes name checker from qubes-pure --- qubes-rpc/vm-log.c | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/qubes-rpc/vm-log.c b/qubes-rpc/vm-log.c index 8f4bfc0e..1cafeaac 100644 --- a/qubes-rpc/vm-log.c +++ b/qubes-rpc/vm-log.c @@ -176,26 +176,12 @@ int main() { return 1; } - if (strlen(remote_domain) > 100) { - fprintf(stderr, "Error: Remote domain name is too long.\n"); + struct QubesSlice remote_domain_slice = qubes_pure_buffer_init_from_nul_terminated_string(remote_domain); + if (qubes_pure_is_valid_qube_name(remote_domain_slice) != QUBE_NAME_OK) { + fprintf(stderr, "Error: Invalid QREXEC_REMOTE_DOMAIN.\n"); return 1; } - regex_t regex; - const char *pattern = "^[a-zA-Z0-9_-]+$"; - - if (regcomp(®ex, pattern, REG_EXTENDED | REG_NOSUB) != 0) { - fprintf(stderr, "Error: Could not compile regex.\n"); - return 1; - } - - if (regexec(®ex, remote_domain, 0, NULL, 0) != 0) { - regfree(®ex); - fprintf(stderr, "Error: Invalid characters in QREXEC_REMOTE_DOMAIN.\n"); - return 1; - } - regfree(®ex); - (void)handle_untrusted(remote_domain); return 0; From e6e99e4a7f0bc2fd2aa31f25fd3b563e5f67726a Mon Sep 17 00:00:00 2001 From: Piotr Bartman-Szwarc Date: Fri, 27 Mar 2026 16:51:01 +0100 Subject: [PATCH 4/5] sys-log: reformat whitespaces --- qubes-rpc/vm-log.c | 230 +++++++++++++++++++++++---------------------- 1 file changed, 118 insertions(+), 112 deletions(-) diff --git a/qubes-rpc/vm-log.c b/qubes-rpc/vm-log.c index 1cafeaac..5e6f2f1f 100644 --- a/qubes-rpc/vm-log.c +++ b/qubes-rpc/vm-log.c @@ -34,14 +34,14 @@ // Syslog priority array (Severity 0 to 7) const int SEV2SYSLOG_ARRAY[] = { - LOG_EMERG, - LOG_ALERT, - LOG_CRIT, - LOG_ERR, - LOG_WARNING, - LOG_NOTICE, - LOG_INFO, - LOG_DEBUG, + LOG_EMERG, + LOG_ALERT, + LOG_CRIT, + LOG_ERR, + LOG_WARNING, + LOG_NOTICE, + LOG_INFO, + LOG_DEBUG, }; @@ -51,11 +51,11 @@ const int SEV2SYSLOG_ARRAY[] = { * @return The severity value (LOG_EMERG, LOG_ALERT, etc.) */ int extract_priority(int pri) { - int severity = pri % 8; - if (severity >= 0 && severity <= 7) { - return SEV2SYSLOG_ARRAY[severity]; - } - return DEFAULT_PRIO; + int severity = pri % 8; + if (severity >= 0 && severity <= 7) { + return SEV2SYSLOG_ARRAY[severity]; + } + return DEFAULT_PRIO; } /** @@ -67,122 +67,128 @@ int extract_priority(int pri) { * @return Syslog priority */ int sanitize(const char *untr_msg_in, char *msg_out, size_t msg_out_size) { - int prio = DEFAULT_PRIO; - - // Max size for the matched priority string (up to 3 digits + NUL) - char prio_str[4] = {0}; - - regex_t regex; - // Regex pattern: starts with '<' followed by 1-3 digits, followed by '>', - // and then anything - const char *pattern = "^<([0-9]{1,3})>.*$"; - const int priority_id = 1; - regmatch_t matches[2]; - - if (regcomp(®ex, pattern, REG_EXTENDED) == 0) { - if (regexec(®ex, untr_msg_in, 2, matches, 0) == 0) { - // Check if the priority group was matched - if (matches[priority_id].rm_so != -1) { - // Convert signed regoff_t difference to size_t - long len_signed = matches[priority_id].rm_eo - matches[priority_id].rm_so; - - if (len_signed > 0) { - size_t len = (size_t)len_signed; - - // Comparison: size_t (len) vs size_t (sizeof) - if (len < sizeof(prio_str)) { - strncpy(prio_str, untr_msg_in + matches[priority_id].rm_so, len); - prio_str[len] = '\0'; - - // Convert string to integer - int full_prio = atoi(prio_str); - prio = extract_priority(full_prio); - } - } - } - } - regfree(®ex); - } - - // Sanitize the message and write it to msg_out - // Cast return value to void as it is ignored right now - (void)qubes_pure_sanitize_string_safe_for_display(untr_msg_in, msg_out, msg_out_size); - - return prio; + int prio = DEFAULT_PRIO; + + // Max size for the matched priority string (up to 3 digits + NUL) + char prio_str[4] = {0}; + + regex_t regex; + // Regex pattern: starts with '<' followed by 1-3 digits, followed by '>', + // and then anything + const char *pattern = "^<([0-9]{1,3})>.*$"; + const int priority_id = 1; + regmatch_t matches[2]; + + if (regcomp(®ex, pattern, REG_EXTENDED) == 0) { + if (regexec(®ex, untr_msg_in, 2, matches, 0) == 0) { + // Check if the priority group was matched + if (matches[priority_id].rm_so != -1) { + // Convert signed regoff_t difference to size_t + long len_signed = + matches[priority_id].rm_eo - matches[priority_id].rm_so; + + if (len_signed > 0) { + size_t len = (size_t) len_signed; + + // Comparison: size_t (len) vs size_t (sizeof) + if (len < sizeof(prio_str)) { + strncpy(prio_str, + untr_msg_in + matches[priority_id].rm_so, len); + prio_str[len] = '\0'; + + // Convert string to integer + int full_prio = atoi(prio_str); + prio = extract_priority(full_prio); + } + } + } + } + regfree(®ex); + } + + // Sanitize the message and write it to msg_out + // Cast return value to void as it is ignored right now + (void) qubes_pure_sanitize_string_safe_for_display( + untr_msg_in, msg_out, msg_out_size); + + return prio; } -void handle_untrusted(const char *vm) { - char ident[256]; - snprintf(ident, sizeof(ident), "qubes.Log(%s)", vm); +void handle_untrusted(const char *vm, const struct LogBackend *backend) { + char ident[256]; + snprintf(ident, sizeof(ident), "qubes.Log(%s)", vm); - openlog(ident, LOG_PID | LOG_CONS, LOG_USER); + backend->open(ident); - char buffer[MAX_LINE_SIZE + 2]; - char trusted_msg[MAX_LINE_SIZE + 4]; + char buffer[MAX_LINE_SIZE + 2]; + char trusted_msg[MAX_LINE_SIZE + 4]; - // Initial confirmation to the sender, i.e., we are ready to receive messages - printf("OK\n"); - fflush(stdout); + // Initial confirmation to the sender, i.e., we are ready to receive messages + printf("OK\n"); + fflush(stdout); - while (fgets(buffer, sizeof(buffer), stdin) != NULL) { + while (fgets(buffer, sizeof(buffer), stdin) != NULL) { + // Determine the actual length of the string read (before NUL) + size_t len = strlen(buffer); - // Determine the actual length of the string read (before NUL) - size_t len = strlen(buffer); + int truncated = 0; + // If fgets stopped due to buffer size (len >= MAX_LINE_SIZE + 1) + // AND the last character is NOT '\n', the line was truncated. + if (len == sizeof(buffer) - 1 && buffer[MAX_LINE_SIZE] != '\n') { + truncated = 1; - int truncated = 0; - // If fgets stopped due to buffer size (len >= MAX_LINE_SIZE + 1) - // AND the last character is NOT '\n', the line was truncated. - if (len == sizeof(buffer) - 1 && buffer[MAX_LINE_SIZE] != '\n') { - truncated = 1; + int c; + while ((c = getchar()) != '\n' && c != EOF); + } - int c; - while ((c = getchar()) != '\n' && c != EOF); - } + // Remove trailing newline and carriage return + if (len > 0 && buffer[len - 1] == '\n') { + buffer[--len] = '\0'; + } + if (len > 0 && buffer[len - 1] == '\r') { + buffer[--len] = '\0'; + } - // Remove trailing newline and carriage return - if (len > 0 && buffer[len - 1] == '\n') { - buffer[--len] = '\0'; - } - if (len > 0 && buffer[len - 1] == '\r') { - buffer[--len] = '\0'; - } + // Check for empty lines + if (len == 0 && !feof(stdin) && !truncated) { + continue; + } - // Check for empty lines - if (len == 0 && !feof(stdin) && !truncated) { - continue; - } + int trusted_prio = sanitize(buffer, trusted_msg, MAX_LINE_SIZE); - int trusted_prio = sanitize(buffer, trusted_msg, MAX_LINE_SIZE); + if (truncated) { + strcat(trusted_msg, "..."); + } - if (truncated) { - strcat(trusted_msg, "..."); - } + backend->write(trusted_prio, trusted_msg); - syslog(trusted_prio, "%s", trusted_msg); - // confrimation to the sender - printf("OK\n"); - fflush(stdout); - } + // confrimation to the sender + printf("OK\n"); + fflush(stdout); + } - closelog(); + backend->close(); } int main() { - const char *remote_domain = getenv("QREXEC_REMOTE_DOMAIN"); - if (!remote_domain) { - fprintf(stderr, "Error: Failed to identify the source VM (QREXEC_REMOTE_DOMAIN not set).\n"); - return 1; - } - - struct QubesSlice remote_domain_slice = qubes_pure_buffer_init_from_nul_terminated_string(remote_domain); - if (qubes_pure_is_valid_qube_name(remote_domain_slice) != QUBE_NAME_OK) { - fprintf(stderr, "Error: Invalid QREXEC_REMOTE_DOMAIN.\n"); - return 1; - } - - (void)handle_untrusted(remote_domain); - - return 0; -} \ No newline at end of file + const char *remote_domain = getenv("QREXEC_REMOTE_DOMAIN"); + if (!remote_domain) { + fprintf( + stderr, + "Error: Failed to identify the source VM (QREXEC_REMOTE_DOMAIN not set).\n"); + return 1; + } + + struct QubesSlice remote_domain_slice = + qubes_pure_buffer_init_from_nul_terminated_string(remote_domain); + if (qubes_pure_is_valid_qube_name(remote_domain_slice) != QUBE_NAME_OK) { + fprintf(stderr, "Error: Invalid QREXEC_REMOTE_DOMAIN.\n"); + return 1; + } + + (void) handle_untrusted(remote_domain); + + return 0; +} From 326ba0c121a5e174b62ec4fe92e8d4767d10c850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Pierret=20=28fepitre=29?= Date: Sun, 3 May 2026 15:06:21 +0200 Subject: [PATCH 5/5] sys-log: fix missing LogBackend definition and incomplete call --- qubes-rpc/vm-log.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/qubes-rpc/vm-log.c b/qubes-rpc/vm-log.c index 5e6f2f1f..fcf73bcd 100644 --- a/qubes-rpc/vm-log.c +++ b/qubes-rpc/vm-log.c @@ -32,6 +32,30 @@ #define MAX_LINE_SIZE 4096 #define DEFAULT_PRIO LOG_INFO +struct LogBackend { + void (*open)(const char *ident); + void (*write)(int priority, const char *message); + void (*close)(void); +}; + +static void syslog_open(const char *ident) { + openlog(ident, LOG_PID, LOG_DAEMON); +} + +static void syslog_write(int priority, const char *message) { + syslog(priority, "%s", message); +} + +static void syslog_close(void) { + closelog(); +} + +static const struct LogBackend SYSLOG_BACKEND = { + .open = syslog_open, + .write = syslog_write, + .close = syslog_close, +}; + // Syslog priority array (Severity 0 to 7) const int SEV2SYSLOG_ARRAY[] = { LOG_EMERG, @@ -188,7 +212,7 @@ int main() { return 1; } - (void) handle_untrusted(remote_domain); + handle_untrusted(remote_domain, &SYSLOG_BACKEND); return 0; }