From 4522b9e5c05f12bca0c7d1c2c9fea15c7bc41358 Mon Sep 17 00:00:00 2001
From: Juergen Gross <jgross@suse.com>
Date: Tue, 13 Sep 2022 07:35:08 +0200
Subject: tools/xenstore: don't buffer multiple identical watch events

A guest not reading its Xenstore response buffer fast enough might
pile up lots of Xenstore watch events buffered. Reduce the generated
load by dropping new events which already have an identical copy
pending.

The special events "@..." are excluded from that handling as there are
known use cases where the handler is relying on each event to be sent
individually.

This is part of XSA-326.

Reported-by: Julien Grall <jgrall@amazon.com>
Signed-off-by: Juergen Gross <jgross@suse.com>
Reviewed-by: Julien Grall <jgrall@amazon.com>

diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
index d871f217af9c..6ea06e20df91 100644
--- a/tools/xenstore/xenstored_core.c
+++ b/tools/xenstore/xenstored_core.c
@@ -882,6 +882,7 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
 	bdata->inhdr = true;
 	bdata->used = 0;
 	bdata->timeout_msec = 0;
+	bdata->watch_event = false;
 
 	if (len <= DEFAULT_BUFFER_SIZE)
 		bdata->buffer = bdata->default_buffer;
@@ -914,7 +915,7 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
 void send_event(struct buffered_data *req, struct connection *conn,
 		const char *path, const char *token)
 {
-	struct buffered_data *bdata;
+	struct buffered_data *bdata, *bd;
 	unsigned int len;
 
 	len = strlen(path) + 1 + strlen(token) + 1;
@@ -936,12 +937,29 @@ void send_event(struct buffered_data *req, struct connection *conn,
 	bdata->hdr.msg.type = XS_WATCH_EVENT;
 	bdata->hdr.msg.len = len;
 
+	/*
+	 * Check whether an identical event is pending already.
+	 * Special events are excluded from that check.
+	 */
+	if (path[0] != '@') {
+		list_for_each_entry(bd, &conn->out_list, list) {
+			if (bd->watch_event && bd->hdr.msg.len == len &&
+			    !memcmp(bdata->buffer, bd->buffer, len)) {
+				trace("dropping duplicate watch %s %s for domain %u\n",
+				      path, token, conn->id);
+				talloc_free(bdata);
+				return;
+			}
+		}
+	}
+
 	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;
 	}
 
+	bdata->watch_event = true;
 	bdata->pend.req = req;
 	if (req)
 		req->pend.ref.event_cnt++;
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
index fcb27399f116..afbd982c2654 100644
--- a/tools/xenstore/xenstored_core.h
+++ b/tools/xenstore/xenstored_core.h
@@ -62,6 +62,9 @@ struct buffered_data
 	/* Are we still doing the header? */
 	bool inhdr;
 
+	/* Is this a watch event? */
+	bool watch_event;
+
 	/* How far are we? */
 	unsigned int used;
 
