Blob Blame History Raw
Patch-mainline: never (alternative kabi aware implementation)
References: bsc#1179508 XSA-349 CVE-2020-29568
From: Juergen Gross <jgross@suse.com>
Date: Fri, 18 Dec 2020 10:08:40 +0100
Subject: [PATCH] xen: support having only one event pending per watch

Some Xenstore watches are meant to be watching only a single node
and for that reason can have only at most one event pending.

Provide variations for registering such a watch via new functions
register_xenbus_watch_exact(), xenbus_watch_path_exact() and
xenbus_watch_pathfmt_exact().

In case a new watch event for a watch registered via one of the new
functions is coming in, it will be dropped in case another event for
the same watch is queued already.

This is meant for avoiding the single thread handling watch events to
be kept busy by a flood of events, which can be triggered by other
guests.

This is an alternative implementation of the upstream commits
fed1755b118147721f2c87b37b9d66e62c39b668
2e85d32b1c865bec703ce0c962221a5e955c52c2
be987200fbaceaef340872841d4f7af2c5ee8dc3
3dc86ca6b4c8cfcba9da7996189d1b5a358a94fc
9996bd494794a2fe393e97e7a982388c6249aa76
which is less generic, but enough to mitigate XSA-349 and not requiring
a kABI modification.

This is XSA-349

Signed-off-by: Juergen Gross <jgross@suse.com>
---
 drivers/block/xen-blkback/xenbus.c |  2 +-
 drivers/net/xen-netback/xenbus.c   |  6 ++--
 drivers/xen/xenbus/xenbus_client.c | 48 +++++++++++++++++++++++++++
 drivers/xen/xenbus/xenbus_probe.c  |  2 +-
 drivers/xen/xenbus/xenbus_xs.c     | 52 ++++++++++++++++++++++++++----
 include/xen/xenbus.h               | 10 ++++++
 6 files changed, 108 insertions(+), 12 deletions(-)

diff --git a/drivers/block/xen-blkback/xenbus.c b/drivers/block/xen-blkback/xenbus.c
index e8b9ed31f10e..4bfd76df6f45 100644
--- a/drivers/block/xen-blkback/xenbus.c
+++ b/drivers/block/xen-blkback/xenbus.c
@@ -636,7 +636,7 @@ static int xen_blkbk_probe(struct xenbus_device *dev,
 	/* setup back pointer */
 	be->blkif->be = be;
 
-	err = xenbus_watch_pathfmt(dev, &be->backend_watch, backend_changed,
+	err = xenbus_watch_pathfmt_exact(dev, &be->backend_watch, backend_changed,
 				   "%s/%s", dev->nodename, "physical-device");
 	if (err)
 		goto fail;
diff --git a/drivers/net/xen-netback/xenbus.c b/drivers/net/xen-netback/xenbus.c
index a56d3eab35dd..f65d7625abf7 100644
--- a/drivers/net/xen-netback/xenbus.c
+++ b/drivers/net/xen-netback/xenbus.c
@@ -778,7 +778,7 @@ static int xen_register_credit_watch(struct xenbus_device *dev,
 	snprintf(node, maxlen, "%s/rate", dev->nodename);
 	vif->credit_watch.node = node;
 	vif->credit_watch.callback = xen_net_rate_changed;
-	err = register_xenbus_watch(&vif->credit_watch);
+	err = register_xenbus_watch_exact(&vif->credit_watch);
 	if (err) {
 		pr_err("Failed to set watcher %s\n", vif->credit_watch.node);
 		kfree(node);
@@ -830,7 +830,7 @@ static int xen_register_mcast_ctrl_watch(struct xenbus_device *dev,
 		 dev->otherend);
 	vif->mcast_ctrl_watch.node = node;
 	vif->mcast_ctrl_watch.callback = xen_mcast_ctrl_changed;
-	err = register_xenbus_watch(&vif->mcast_ctrl_watch);
+	err = register_xenbus_watch_exact(&vif->mcast_ctrl_watch);
 	if (err) {
 		pr_err("Failed to set watcher %s\n",
 		       vif->mcast_ctrl_watch.node);
@@ -1039,7 +1039,7 @@ static void connect(struct backend_info *be)
 	xenvif_carrier_on(be->vif);
 
 	unregister_hotplug_status_watch(be);
-	err = xenbus_watch_pathfmt(dev, &be->hotplug_status_watch,
+	err = xenbus_watch_pathfmt_exact(dev, &be->hotplug_status_watch,
 				   hotplug_status_changed,
 				   "%s/%s", dev->nodename, "hotplug-status");
 	if (!err)
diff --git a/drivers/xen/xenbus/xenbus_client.c b/drivers/xen/xenbus/xenbus_client.c
index 82a8866758ee..a298294a7f4a 100644
--- a/drivers/xen/xenbus/xenbus_client.c
+++ b/drivers/xen/xenbus/xenbus_client.c
@@ -134,6 +134,28 @@ int xenbus_watch_path(struct xenbus_device *dev, const char *path,
 }
 EXPORT_SYMBOL_GPL(xenbus_watch_path);
 
+int xenbus_watch_path_exact(struct xenbus_device *dev, const char *path,
+		      struct xenbus_watch *watch,
+		      void (*callback)(struct xenbus_watch *,
+				       const char *, const char *))
+{
+	int err;
+
+	watch->node = path;
+	watch->callback = callback;
+
+	err = register_xenbus_watch_exact(watch);
+
+	if (err) {
+		watch->node = NULL;
+		watch->callback = NULL;
+		xenbus_dev_fatal(dev, err, "adding watch on %s", path);
+	}
+
+	return err;
+}
+EXPORT_SYMBOL_GPL(xenbus_watch_path_exact);
+
 
 /**
  * xenbus_watch_pathfmt - register a watch on a sprintf-formatted path
@@ -176,6 +198,32 @@ int xenbus_watch_pathfmt(struct xenbus_device *dev,
 }
 EXPORT_SYMBOL_GPL(xenbus_watch_pathfmt);
 
+int xenbus_watch_pathfmt_exact(struct xenbus_device *dev,
+			 struct xenbus_watch *watch,
+			 void (*callback)(struct xenbus_watch *,
+					  const char *, const char *),
+			 const char *pathfmt, ...)
+{
+	int err;
+	va_list ap;
+	char *path;
+
+	va_start(ap, pathfmt);
+	path = kvasprintf(GFP_NOIO | __GFP_HIGH, pathfmt, ap);
+	va_end(ap);
+
+	if (!path) {
+		xenbus_dev_fatal(dev, -ENOMEM, "allocating path for watch");
+		return -ENOMEM;
+	}
+	err = xenbus_watch_path_exact(dev, path, watch, callback);
+
+	if (err)
+		kfree(path);
+	return err;
+}
+EXPORT_SYMBOL_GPL(xenbus_watch_pathfmt_exact);
+
 static void xenbus_switch_fatal(struct xenbus_device *, int, int,
 				const char *, ...);
 
diff --git a/drivers/xen/xenbus/xenbus_probe.c b/drivers/xen/xenbus/xenbus_probe.c
index 74888cacd0b0..bd682551f9dc 100644
--- a/drivers/xen/xenbus/xenbus_probe.c
+++ b/drivers/xen/xenbus/xenbus_probe.c
@@ -135,7 +135,7 @@ static int watch_otherend(struct xenbus_device *dev)
 	struct xen_bus_type *bus =
 		container_of(dev->dev.bus, struct xen_bus_type, bus);
 
-	return xenbus_watch_pathfmt(dev, &dev->otherend_watch,
+	return xenbus_watch_pathfmt_exact(dev, &dev->otherend_watch,
 				    bus->otherend_changed,
 				    "%s/%s", dev->otherend, "state");
 }
diff --git a/drivers/xen/xenbus/xenbus_xs.c b/drivers/xen/xenbus/xenbus_xs.c
index e46080214955..442eefe13ce1 100644
--- a/drivers/xen/xenbus/xenbus_xs.c
+++ b/drivers/xen/xenbus/xenbus_xs.c
@@ -76,6 +76,7 @@ static DECLARE_WAIT_QUEUE_HEAD(xs_state_exit_wq);
 
 /* List of registered watches, and a lock to protect it. */
 static LIST_HEAD(watches);
+static LIST_HEAD(watches_exact);
 static DEFINE_SPINLOCK(watches_lock);
 
 /* List of pending watch callback events, and a lock to protect it. */
@@ -670,21 +671,32 @@ static int xs_unwatch(const char *path, const char *token)
 				 ARRAY_SIZE(iov), NULL));
 }
 
-static struct xenbus_watch *find_watch(const char *token)
+static struct xenbus_watch *find_watch(const char *token, bool *exact)
 {
 	struct xenbus_watch *i, *cmp;
 
 	cmp = (void *)simple_strtoul(token, NULL, 16);
 
 	list_for_each_entry(i, &watches, list)
-		if (i == cmp)
+		if (i == cmp) {
+			if (exact)
+				*exact = false;
 			return i;
+		}
+	list_for_each_entry(i, &watches_exact, list)
+		if (i == cmp) {
+			if (exact)
+				*exact = true;
+			return i;
+		}
 
 	return NULL;
 }
 
 int xs_watch_msg(struct xs_watch_event *event)
 {
+	bool exact;
+
 	if (count_strings(event->body, event->len) != 2) {
 		kfree(event);
 		return -EINVAL;
@@ -693,11 +705,21 @@ int xs_watch_msg(struct xs_watch_event *event)
 	event->token = (const char *)strchr(event->body, '\0') + 1;
 
 	spin_lock(&watches_lock);
-	event->handle = find_watch(event->token);
+	event->handle = find_watch(event->token, &exact);
 	if (event->handle != NULL) {
 		spin_lock(&watch_events_lock);
+		if (exact) {
+			struct xs_watch_event *evlist;
+
+			list_for_each_entry(evlist, &watch_events, list)
+				if (evlist->handle == event->handle) {
+					kfree(event);
+					goto drop;
+				}
+		}
 		list_add_tail(&event->list, &watch_events);
 		wake_up(&watch_events_waitq);
+ drop:
 		spin_unlock(&watch_events_lock);
 	} else
 		kfree(event);
@@ -746,7 +768,8 @@ static void xs_reset_watches(void)
 }
 
 /* Register callback to watch this node. */
-int register_xenbus_watch(struct xenbus_watch *watch)
+static int register_xenbus_watch_list(struct xenbus_watch *watch,
+				      struct list_head *list)
 {
 	/* Pointer in ascii is the token. */
 	char token[sizeof(watch) * 2 + 1];
@@ -757,8 +780,8 @@ int register_xenbus_watch(struct xenbus_watch *watch)
 	down_read(&xs_watch_rwsem);
 
 	spin_lock(&watches_lock);
-	BUG_ON(find_watch(token));
-	list_add(&watch->list, &watches);
+	BUG_ON(find_watch(token, NULL));
+	list_add(&watch->list, list);
 	spin_unlock(&watches_lock);
 
 	err = xs_watch(watch->node, token);
@@ -773,8 +796,19 @@ int register_xenbus_watch(struct xenbus_watch *watch)
 
 	return err;
 }
+
+int register_xenbus_watch(struct xenbus_watch *watch)
+{
+	return register_xenbus_watch_list(watch, &watches);
+}
 EXPORT_SYMBOL_GPL(register_xenbus_watch);
 
+int register_xenbus_watch_exact(struct xenbus_watch *watch)
+{
+	return register_xenbus_watch_list(watch, &watches_exact);
+}
+EXPORT_SYMBOL_GPL(register_xenbus_watch_exact);
+
 void unregister_xenbus_watch(struct xenbus_watch *watch)
 {
 	struct xs_watch_event *event, *tmp;
@@ -786,7 +820,7 @@ void unregister_xenbus_watch(struct xenbus_watch *watch)
 	down_read(&xs_watch_rwsem);
 
 	spin_lock(&watches_lock);
-	BUG_ON(!find_watch(token));
+	BUG_ON(!find_watch(token, NULL));
 	list_del(&watch->list);
 	spin_unlock(&watches_lock);
 
@@ -840,6 +874,10 @@ void xs_resume(void)
 		sprintf(token, "%lX", (long)watch);
 		xs_watch(watch->node, token);
 	}
+	list_for_each_entry(watch, &watches_exact, list) {
+		sprintf(token, "%lX", (long)watch);
+		xs_watch(watch->node, token);
+	}
 
 	up_write(&xs_watch_rwsem);
 }
diff --git a/include/xen/xenbus.h b/include/xen/xenbus.h
index 869c816d5f8c..f032e9c85d20 100644
--- a/include/xen/xenbus.h
+++ b/include/xen/xenbus.h
@@ -171,6 +171,7 @@ int register_xenstore_notifier(struct notifier_block *nb);
 void unregister_xenstore_notifier(struct notifier_block *nb);
 
 int register_xenbus_watch(struct xenbus_watch *watch);
+int register_xenbus_watch_exact(struct xenbus_watch *watch);
 void unregister_xenbus_watch(struct xenbus_watch *watch);
 void xs_suspend(void);
 void xs_resume(void);
@@ -199,6 +200,15 @@ int xenbus_watch_pathfmt(struct xenbus_device *dev, struct xenbus_watch *watch,
 			 void (*callback)(struct xenbus_watch *,
 					  const char *, const char *),
 			 const char *pathfmt, ...);
+int xenbus_watch_path_exact(struct xenbus_device *dev, const char *path,
+		      struct xenbus_watch *watch,
+		      void (*callback)(struct xenbus_watch *,
+				       const char *, const char *));
+__printf(4, 5)
+int xenbus_watch_pathfmt_exact(struct xenbus_device *dev, struct xenbus_watch *watch,
+			 void (*callback)(struct xenbus_watch *,
+					  const char *, const char *),
+			 const char *pathfmt, ...);
 
 int xenbus_switch_state(struct xenbus_device *dev, enum xenbus_state new_state);
 int xenbus_grant_ring(struct xenbus_device *dev, void *vaddr,
-- 
2.26.2