summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '0088-tools-xenstore-let-unread-watch-events-time-out.patch')
-rw-r--r--0088-tools-xenstore-let-unread-watch-events-time-out.patch309
1 files changed, 309 insertions, 0 deletions
diff --git a/0088-tools-xenstore-let-unread-watch-events-time-out.patch b/0088-tools-xenstore-let-unread-watch-events-time-out.patch
new file mode 100644
index 0000000..03419c6
--- /dev/null
+++ b/0088-tools-xenstore-let-unread-watch-events-time-out.patch
@@ -0,0 +1,309 @@
+From 53a77b82717530d836300f1de0ad037de85477dd Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross@suse.com>
+Date: Tue, 13 Sep 2022 07:35:07 +0200
+Subject: [PATCH 088/126] tools/xenstore: let unread watch events time out
+
+A future modification will limit the number of outstanding requests
+for a domain, where "outstanding" means that the response of the
+request or any resulting watch event hasn't been consumed yet.
+
+In order to avoid a malicious guest being capable to block other guests
+by not reading watch events, add a timeout for watch events. In case a
+watch event hasn't been consumed after this timeout, it is being
+deleted. Set the default timeout to 20 seconds (a random value being
+not too high).
+
+In order to support to specify other timeout values in future, use a
+generic command line option for that purpose:
+
+--timeout|-w watch-event=<seconds>
+
+This is part of XSA-326 / CVE-2022-42311.
+
+Signed-off-by: Juergen Gross <jgross@suse.com>
+Reviewed-by: Julien Grall <jgrall@amazon.com>
+(cherry picked from commit 5285dcb1a5c01695c11e6397c95d906b5e765c98)
+---
+ tools/xenstore/xenstored_core.c | 133 +++++++++++++++++++++++++++++++-
+ tools/xenstore/xenstored_core.h | 6 ++
+ 2 files changed, 138 insertions(+), 1 deletion(-)
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index 5157a7527f58..ee3396fefa94 100644
+--- a/tools/xenstore/xenstored_core.c
++++ b/tools/xenstore/xenstored_core.c
+@@ -108,6 +108,8 @@ int quota_max_transaction = 10;
+ int quota_nb_perms_per_node = 5;
+ int quota_max_path_len = XENSTORE_REL_PATH_MAX;
+
++unsigned int timeout_watch_event_msec = 20000;
++
+ void trace(const char *fmt, ...)
+ {
+ va_list arglist;
+@@ -211,19 +213,92 @@ void reopen_log(void)
+ }
+ }
+
++static uint64_t get_now_msec(void)
++{
++ struct timespec now_ts;
++
++ if (clock_gettime(CLOCK_MONOTONIC, &now_ts))
++ barf_perror("Could not find time (clock_gettime failed)");
++
++ return now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000;
++}
++
+ static void free_buffered_data(struct buffered_data *out,
+ struct connection *conn)
+ {
++ struct buffered_data *req;
++
+ list_del(&out->list);
++
++ /*
++ * Update conn->timeout_msec with the next found timeout value in the
++ * queued pending requests.
++ */
++ if (out->timeout_msec) {
++ conn->timeout_msec = 0;
++ list_for_each_entry(req, &conn->out_list, list) {
++ if (req->timeout_msec) {
++ conn->timeout_msec = req->timeout_msec;
++ break;
++ }
++ }
++ }
++
+ talloc_free(out);
+ }
+
++static void check_event_timeout(struct connection *conn, uint64_t msecs,
++ int *ptimeout)
++{
++ uint64_t delta;
++ struct buffered_data *out, *tmp;
++
++ if (!conn->timeout_msec)
++ return;
++
++ delta = conn->timeout_msec - msecs;
++ if (conn->timeout_msec <= msecs) {
++ delta = 0;
++ list_for_each_entry_safe(out, tmp, &conn->out_list, list) {
++ /*
++ * Only look at buffers with timeout and no data
++ * already written to the ring.
++ */
++ if (out->timeout_msec && out->inhdr && !out->used) {
++ if (out->timeout_msec > msecs) {
++ conn->timeout_msec = out->timeout_msec;
++ delta = conn->timeout_msec - msecs;
++ break;
++ }
++
++ /*
++ * Free out without updating conn->timeout_msec,
++ * as the update is done in this loop already.
++ */
++ out->timeout_msec = 0;
++ trace("watch event path %s for domain %u timed out\n",
++ out->buffer, conn->id);
++ free_buffered_data(out, conn);
++ }
++ }
++ if (!delta) {
++ conn->timeout_msec = 0;
++ return;
++ }
++ }
++
++ if (*ptimeout == -1 || *ptimeout > delta)
++ *ptimeout = delta;
++}
++
+ void conn_free_buffered_data(struct connection *conn)
+ {
+ struct buffered_data *out;
+
+ while ((out = list_top(&conn->out_list, struct buffered_data, list)))
+ free_buffered_data(out, conn);
++
++ conn->timeout_msec = 0;
+ }
+
+ static bool write_messages(struct connection *conn)
+@@ -382,6 +457,7 @@ static void initialize_fds(int *p_sock_pollfd_idx, int *ptimeout)
+ {
+ struct connection *conn;
+ struct wrl_timestampt now;
++ uint64_t msecs;
+
+ if (fds)
+ memset(fds, 0, sizeof(struct pollfd) * current_array_size);
+@@ -402,10 +478,12 @@ static void initialize_fds(int *p_sock_pollfd_idx, int *ptimeout)
+
+ wrl_gettime_now(&now);
+ wrl_log_periodic(now);
++ msecs = get_now_msec();
+
+ list_for_each_entry(conn, &connections, list) {
+ if (conn->domain) {
+ wrl_check_timeout(conn->domain, now, ptimeout);
++ check_event_timeout(conn, msecs, ptimeout);
+ if (domain_can_read(conn) ||
+ (domain_can_write(conn) &&
+ !list_empty(&conn->out_list)))
+@@ -760,6 +838,7 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
+ return;
+ bdata->inhdr = true;
+ bdata->used = 0;
++ bdata->timeout_msec = 0;
+
+ if (len <= DEFAULT_BUFFER_SIZE)
+ bdata->buffer = bdata->default_buffer;
+@@ -811,6 +890,12 @@ void send_event(struct connection *conn, const char *path, const char *token)
+ bdata->hdr.msg.type = XS_WATCH_EVENT;
+ bdata->hdr.msg.len = len;
+
++ if (timeout_watch_event_msec && domain_is_unprivileged(conn)) {
++ bdata->timeout_msec = get_now_msec() + timeout_watch_event_msec;
++ if (!conn->timeout_msec)
++ conn->timeout_msec = bdata->timeout_msec;
++ }
++
+ /* Queue for later transmission. */
+ list_add_tail(&bdata->list, &conn->out_list);
+ }
+@@ -2099,6 +2184,9 @@ static void usage(void)
+ " -t, --transaction <nb> limit the number of transaction allowed per domain,\n"
+ " -A, --perm-nb <nb> limit the number of permissions per node,\n"
+ " -M, --path-max <chars> limit the allowed Xenstore node path length,\n"
++" -w, --timeout <what>=<seconds> set the timeout in seconds for <what>,\n"
++" allowed timeout candidates are:\n"
++" watch-event: time a watch-event is kept pending\n"
+ " -R, --no-recovery to request that no recovery should be attempted when\n"
+ " the store is corrupted (debug only),\n"
+ " -I, --internal-db store database in memory, not on disk\n"
+@@ -2121,6 +2209,7 @@ static struct option options[] = {
+ { "transaction", 1, NULL, 't' },
+ { "perm-nb", 1, NULL, 'A' },
+ { "path-max", 1, NULL, 'M' },
++ { "timeout", 1, NULL, 'w' },
+ { "no-recovery", 0, NULL, 'R' },
+ { "internal-db", 0, NULL, 'I' },
+ { "verbose", 0, NULL, 'V' },
+@@ -2135,6 +2224,39 @@ int dom0_domid = 0;
+ int dom0_event = 0;
+ int priv_domid = 0;
+
++static int get_optval_int(const char *arg)
++{
++ char *end;
++ long val;
++
++ val = strtol(arg, &end, 10);
++ if (!*arg || *end || val < 0 || val > INT_MAX)
++ barf("invalid parameter value \"%s\"\n", arg);
++
++ return val;
++}
++
++static bool what_matches(const char *arg, const char *what)
++{
++ unsigned int what_len = strlen(what);
++
++ return !strncmp(arg, what, what_len) && arg[what_len] == '=';
++}
++
++static void set_timeout(const char *arg)
++{
++ const char *eq = strchr(arg, '=');
++ int val;
++
++ if (!eq)
++ barf("quotas must be specified via <what>=<seconds>\n");
++ val = get_optval_int(eq + 1);
++ if (what_matches(arg, "watch-event"))
++ timeout_watch_event_msec = val * 1000;
++ else
++ barf("unknown timeout \"%s\"\n", arg);
++}
++
+ int main(int argc, char *argv[])
+ {
+ int opt;
+@@ -2149,7 +2271,7 @@ int main(int argc, char *argv[])
+ orig_argc = argc;
+ orig_argv = argv;
+
+- while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:T:RVW:U", options,
++ while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:T:RVW:w:U", options,
+ NULL)) != -1) {
+ switch (opt) {
+ case 'D':
+@@ -2198,6 +2320,9 @@ int main(int argc, char *argv[])
+ quota_max_path_len = min(XENSTORE_REL_PATH_MAX,
+ quota_max_path_len);
+ break;
++ case 'w':
++ set_timeout(optarg);
++ break;
+ case 'e':
+ dom0_event = strtol(optarg, NULL, 10);
+ break;
+@@ -2642,6 +2767,12 @@ static void add_buffered_data(struct buffered_data *bdata,
+ barf("error restoring buffered data");
+
+ memcpy(bdata->buffer, data, len);
++ if (bdata->hdr.msg.type == XS_WATCH_EVENT && timeout_watch_event_msec &&
++ domain_is_unprivileged(conn)) {
++ bdata->timeout_msec = get_now_msec() + timeout_watch_event_msec;
++ if (!conn->timeout_msec)
++ conn->timeout_msec = bdata->timeout_msec;
++ }
+
+ /* Queue for later transmission. */
+ list_add_tail(&bdata->list, &conn->out_list);
+diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
+index 0ba5b783d4d1..2db577928fc6 100644
+--- a/tools/xenstore/xenstored_core.h
++++ b/tools/xenstore/xenstored_core.h
+@@ -27,6 +27,7 @@
+ #include <dirent.h>
+ #include <stdbool.h>
+ #include <stdint.h>
++#include <time.h>
+ #include <errno.h>
+
+ #include "xenstore_lib.h"
+@@ -67,6 +68,8 @@ struct buffered_data
+ char raw[sizeof(struct xsd_sockmsg)];
+ } hdr;
+
++ uint64_t timeout_msec;
++
+ /* The actual data. */
+ char *buffer;
+ char default_buffer[DEFAULT_BUFFER_SIZE];
+@@ -110,6 +113,7 @@ struct connection
+
+ /* Buffered output data */
+ struct list_head out_list;
++ uint64_t timeout_msec;
+
+ /* Transaction context for current request (NULL if none). */
+ struct transaction *transaction;
+@@ -237,6 +241,8 @@ extern int dom0_event;
+ extern int priv_domid;
+ extern int quota_nb_entry_per_domain;
+
++extern unsigned int timeout_watch_event_msec;
++
+ /* Map the kernel's xenstore page. */
+ void *xenbus_map(void);
+ void unmap_xenbus(void *interface);
+--
+2.37.4
+