pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/sysutils/xentools411



Module Name:    pkgsrc
Committed By:   bouyer
Date:           Thu Dec 17 16:48:12 UTC 2020

Modified Files:
        pkgsrc/sysutils/xentools411: Makefile distinfo
        pkgsrc/sysutils/xentools411/patches:
            patch-tools_ocaml_xenstored_utils.ml
Added Files:
        pkgsrc/sysutils/xentools411/patches: patch-XSA115-c patch-XSA115-o
            patch-XSA322-c patch-XSA322-o patch-XSA323 patch-XSA324
            patch-XSA325 patch-XSA330 patch-XSA352 patch-XSA353

Log Message:
Add upstream patches for a bunch of Xen security avisories, related
to xenstore permissions.
Bump PKGREVISION


To generate a diff of this commit:
cvs rdiff -u -r1.23 -r1.24 pkgsrc/sysutils/xentools411/Makefile
cvs rdiff -u -r1.14 -r1.15 pkgsrc/sysutils/xentools411/distinfo
cvs rdiff -u -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA115-c \
    pkgsrc/sysutils/xentools411/patches/patch-XSA115-o \
    pkgsrc/sysutils/xentools411/patches/patch-XSA322-c \
    pkgsrc/sysutils/xentools411/patches/patch-XSA322-o \
    pkgsrc/sysutils/xentools411/patches/patch-XSA323 \
    pkgsrc/sysutils/xentools411/patches/patch-XSA324 \
    pkgsrc/sysutils/xentools411/patches/patch-XSA325 \
    pkgsrc/sysutils/xentools411/patches/patch-XSA330 \
    pkgsrc/sysutils/xentools411/patches/patch-XSA352 \
    pkgsrc/sysutils/xentools411/patches/patch-XSA353
cvs rdiff -u -r1.1 -r1.2 \
    pkgsrc/sysutils/xentools411/patches/patch-tools_ocaml_xenstored_utils.ml

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: pkgsrc/sysutils/xentools411/Makefile
diff -u pkgsrc/sysutils/xentools411/Makefile:1.23 pkgsrc/sysutils/xentools411/Makefile:1.24
--- pkgsrc/sysutils/xentools411/Makefile:1.23   Mon Aug 31 18:11:37 2020
+++ pkgsrc/sysutils/xentools411/Makefile        Thu Dec 17 16:48:12 2020
@@ -1,7 +1,7 @@
-# $NetBSD: Makefile,v 1.23 2020/08/31 18:11:37 wiz Exp $
+# $NetBSD: Makefile,v 1.24 2020/12/17 16:48:12 bouyer Exp $
 #
 # VERSION is set in version.mk as it is shared with other packages
-PKGREVISION=   1
+PKGREVISION=   2
 .include       "version.mk"
 
 DIST_IPXE=     ipxe-git-${VERSION_IPXE}.tar.gz

Index: pkgsrc/sysutils/xentools411/distinfo
diff -u pkgsrc/sysutils/xentools411/distinfo:1.14 pkgsrc/sysutils/xentools411/distinfo:1.15
--- pkgsrc/sysutils/xentools411/distinfo:1.14   Mon Aug 24 10:33:27 2020
+++ pkgsrc/sysutils/xentools411/distinfo        Thu Dec 17 16:48:12 2020
@@ -1,4 +1,4 @@
-$NetBSD: distinfo,v 1.14 2020/08/24 10:33:27 bouyer Exp $
+$NetBSD: distinfo,v 1.15 2020/12/17 16:48:12 bouyer Exp $
 
 SHA1 (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = 272b8c904dc0127690eca2c5c20c67479e40da34
 RMD160 (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = cfcb4a314c15da19b36132b27126f3bd9699d0e5
@@ -12,6 +12,16 @@ SHA1 (patch-.._ipxe_src_core_settings.c)
 SHA1 (patch-.._ipxe_src_net_fcels.c) = eda41b25c3d5f5bef33caa9a6af28c40cb91e66b
 SHA1 (patch-Config.mk) = c41005a60de2f94a72b0206030eb021c137653d3
 SHA1 (patch-Makefile) = 2f3a5eafc5039b149c98dd5e59c39a3197fd9264
+SHA1 (patch-XSA115-c) = 7e3216a23c522fc73f47fa6deef8918c4dce7fae
+SHA1 (patch-XSA115-o) = 6dc292060441c388b9a05e31ddc37835568a3e86
+SHA1 (patch-XSA322-c) = c48a10eeab29775b9c97a36848556120741a9c9d
+SHA1 (patch-XSA322-o) = 943e2aee69ac278871925223478b11f6dfabc9d7
+SHA1 (patch-XSA323) = 98055b0c05ed0d0f5ebbe23d429a68a71d92f20f
+SHA1 (patch-XSA324) = a1cdb872a79fd7d9234030ec2765d0a474f72fbb
+SHA1 (patch-XSA325) = 59c7fba006588db4accee1068072612777620ac3
+SHA1 (patch-XSA330) = 03b4f1d9c14e11eaee5b863276d32cee0544e604
+SHA1 (patch-XSA352) = 7c4479c029d9bbbf6578ee148cb926bb2d849789
+SHA1 (patch-XSA353) = 6983aa18399dcf0ac1471ffdf7c27c1bc041f49c
 SHA1 (patch-always_inline) = 23201b2b63072e040630525416a0b61280492f93
 SHA1 (patch-docs_man_xl-disk-configuration.pod.5) = 03ff4c22dde1e1b60ab8750c8971ea057e479151
 SHA1 (patch-docs_man_xl.cfg.pod.5.in) = 951915037a9975b76cc5c41a0e1abe0a202a3696
@@ -56,7 +66,7 @@ SHA1 (patch-tools_ocaml_common.make) = 4
 SHA1 (patch-tools_ocaml_libs_xentoollog_xentoollog__stubs.c) = adee03d87168e735cb0d42ce06d0c31a14315b8d
 SHA1 (patch-tools_ocaml_libs_xl__xenlight_stubs.c) = cc612908524670f650a294af133a5912f955f39e
 SHA1 (patch-tools_ocaml_xenstored_Makefile) = b267702cf4090c7b45bba530e60327fced24e3e5
-SHA1 (patch-tools_ocaml_xenstored_utils.ml) = fd951de732d6c31cae89bd4b58c5650108578d79
+SHA1 (patch-tools_ocaml_xenstored_utils.ml) = 96b69dd3b5adb10692d7646c1dbeb20d27e0e1a8
 SHA1 (patch-tools_qemu-xen-traditional_Makefile) = 5fbb55bf84f9856043be301d5d06530190fe9a60
 SHA1 (patch-tools_qemu-xen-traditional_block-raw-posix.c) = eb3efea4b0c7fd744f627f1926fca737ba826b99
 SHA1 (patch-tools_qemu-xen-traditional_configure) = 6a42dcac010f90439a347c0f6e886b07185cb19a
@@ -81,5 +91,4 @@ SHA1 (patch-tools_xenstore_xs_lib.c) = e
 SHA1 (patch-tools_xentrace_xentrace.c) = f964c7555f454358a39f28a2e75db8ee100a4243
 SHA1 (patch-tools_xl_Makefile) = dd4fa8cc66c74eea8b022cd6129aa2831776f2a8
 SHA1 (patch-xen_Rules.mk) = c743dc63f51fc280d529a7d9e08650292c171dac
-SHA1 (patch-xen_common_lz4_dexompress.c) = 521a247c2d36980b3433c4be92c77308a2d3f3b9
 SHA1 (patch-xen_tools_symbols.c) = 67b5a38312095029631e00457abc0e4bb633aaf8

Index: pkgsrc/sysutils/xentools411/patches/patch-tools_ocaml_xenstored_utils.ml
diff -u pkgsrc/sysutils/xentools411/patches/patch-tools_ocaml_xenstored_utils.ml:1.1 pkgsrc/sysutils/xentools411/patches/patch-tools_ocaml_xenstored_utils.ml:1.2
--- pkgsrc/sysutils/xentools411/patches/patch-tools_ocaml_xenstored_utils.ml:1.1        Tue Jul 24 13:40:11 2018
+++ pkgsrc/sysutils/xentools411/patches/patch-tools_ocaml_xenstored_utils.ml    Thu Dec 17 16:48:12 2020
@@ -1,13 +1,13 @@
-$NetBSD: patch-tools_ocaml_xenstored_utils.ml,v 1.1 2018/07/24 13:40:11 bouyer Exp $
+$NetBSD: patch-tools_ocaml_xenstored_utils.ml,v 1.2 2020/12/17 16:48:12 bouyer Exp $
 
---- ./tools/ocaml/xenstored/utils.ml.orig      2018-07-09 15:47:19.000000000 +0200
-+++ ./tools/ocaml/xenstored/utils.ml   2018-07-16 13:50:03.000000000 +0200
+--- tools/ocaml/xenstored/utils.ml.orig        2020-12-17 15:47:15.866790468 +0100
++++ tools/ocaml/xenstored/utils.ml     2020-12-17 15:53:47.618682147 +0100
 @@ -86,7 +86,7 @@
        let buf = Bytes.make 20 '\000' in
        let sz = Unix.read fd buf 0 20 in
        Unix.close fd;
 -      int_of_string (Bytes.sub_string buf 0 sz)
-+      int_of_string (String.trim (String.sub buf 0 sz))
++      int_of_string (String.trim (Bytes.sub_string buf 0 sz))
  
- let path_complete path connection_path =
-       if String.get path 0 <> '/' then
+ (* @path may be guest data and needs its length validating.  @connection_path
+  * is generated locally in xenstored and always of the form "/local/domain/$N/" *)

Added files:

Index: pkgsrc/sysutils/xentools411/patches/patch-XSA115-c
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA115-c:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA115-c  Thu Dec 17 16:48:12 2020
@@ -0,0 +1,1755 @@
+$NetBSD: patch-XSA115-c,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From e92f3dfeaae21a335e666c9247954424e34e5c56 Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:37 +0200
+Subject: [PATCH 01/10] tools/xenstore: allow removing child of a node
+ exceeding quota
+
+An unprivileged user of Xenstore is not allowed to write nodes with a
+size exceeding a global quota, while privileged users like dom0 are
+allowed to write such nodes. The size of a node is the needed space
+to store all node specific data, this includes the names of all
+children of the node.
+
+When deleting a node its parent has to be modified by removing the
+name of the to be deleted child from it.
+
+This results in the strange situation that an unprivileged owner of a
+node might not succeed in deleting that node in case its parent is
+exceeding the quota of that unprivileged user (it might have been
+written by dom0), as the user is not allowed to write the updated
+parent node.
+
+Fix that by not checking the quota when writing a node for the
+purpose of removing a child's name only.
+
+The same applies to transaction handling: a node being read during a
+transaction is written to the transaction specific area and it should
+not be tested for exceeding the quota, as it might not be owned by
+the reader and presumably the original write would have failed if the
+node is owned by the reader.
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+---
+ tools/xenstore/xenstored_core.c        | 20 +++++++++++---------
+ tools/xenstore/xenstored_core.h        |  3 ++-
+ tools/xenstore/xenstored_transaction.c |  2 +-
+ 3 files changed, 14 insertions(+), 11 deletions(-)
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index 97ceabf9642d..b43e1018babd 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -417,7 +417,8 @@ static struct node *read_node(struct connection *conn, const void *ctx,
+       return node;
+ }
+ 
+-int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node)
++int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
++                 bool no_quota_check)
+ {
+       TDB_DATA data;
+       void *p;
+@@ -427,7 +428,7 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node)
+               + node->num_perms*sizeof(node->perms[0])
+               + node->datalen + node->childlen;
+ 
+-      if (domain_is_unprivileged(conn) &&
++      if (!no_quota_check && domain_is_unprivileged(conn) &&
+           data.dsize >= quota_max_entry_size) {
+               errno = ENOSPC;
+               return errno;
+@@ -455,14 +456,15 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node)
+       return 0;
+ }
+ 
+-static int write_node(struct connection *conn, struct node *node)
++static int write_node(struct connection *conn, struct node *node,
++                    bool no_quota_check)
+ {
+       TDB_DATA key;
+ 
+       if (access_node(conn, node, NODE_ACCESS_WRITE, &key))
+               return errno;
+ 
+-      return write_node_raw(conn, &key, node);
++      return write_node_raw(conn, &key, node, no_quota_check);
+ }
+ 
+ static enum xs_perm_type perm_for_conn(struct connection *conn,
+@@ -999,7 +1001,7 @@ static struct node *create_node(struct connection *conn, const void *ctx,
+       /* We write out the nodes down, setting destructor in case
+        * something goes wrong. */
+       for (i = node; i; i = i->parent) {
+-              if (write_node(conn, i)) {
++              if (write_node(conn, i, false)) {
+                       domain_entry_dec(conn, i);
+                       return NULL;
+               }
+@@ -1039,7 +1041,7 @@ static int do_write(struct connection *conn, struct buffered_data *in)
+       } else {
+               node->data = in->buffer + offset;
+               node->datalen = datalen;
+-              if (write_node(conn, node))
++              if (write_node(conn, node, false))
+                       return errno;
+       }
+ 
+@@ -1115,7 +1117,7 @@ static int remove_child_entry(struct connection *conn, struct node *node,
+       size_t childlen = strlen(node->children + offset);
+       memdel(node->children, offset, childlen + 1, node->childlen);
+       node->childlen -= childlen + 1;
+-      return write_node(conn, node);
++      return write_node(conn, node, true);
+ }
+ 
+ 
+@@ -1254,7 +1256,7 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
+       node->num_perms = num;
+       domain_entry_inc(conn, node);
+ 
+-      if (write_node(conn, node))
++      if (write_node(conn, node, false))
+               return errno;
+ 
+       fire_watches(conn, in, name, false);
+@@ -1514,7 +1516,7 @@ static void manual_node(const char *name, const char *child)
+       if (child)
+               node->childlen = strlen(child) + 1;
+ 
+-      if (write_node(NULL, node))
++      if (write_node(NULL, node, false))
+               barf_perror("Could not create initial node %s", name);
+       talloc_free(node);
+ }
+diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
+index 56a279cfbb47..3cb1c235a101 100644
+--- tools/xenstore/xenstored_core.h.orig
++++ tools/xenstore/xenstored_core.h
+@@ -149,7 +149,8 @@ void send_ack(struct connection *conn, enum xsd_sockmsg_type type);
+ char *canonicalize(struct connection *conn, const void *ctx, const char *node);
+ 
+ /* Write a node to the tdb data base. */
+-int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node);
++int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
++                 bool no_quota_check);
+ 
+ /* Get this node, checking we have permissions. */
+ struct node *get_node(struct connection *conn,
+diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
+index 2824f7b359b8..e87897573469 100644
+--- tools/xenstore/xenstored_transaction.c.orig
++++ tools/xenstore/xenstored_transaction.c
+@@ -276,7 +276,7 @@ int access_node(struct connection *conn, struct node *node,
+                       i->check_gen = true;
+                       if (node->generation != NO_GENERATION) {
+                               set_tdb_key(trans_name, &local_key);
+-                              ret = write_node_raw(conn, &local_key, node);
++                              ret = write_node_raw(conn, &local_key, node, true);
+                               if (ret)
+                                       goto err;
+                               i->ta_node = true;
+-- 
+2.17.1
+
+From e8076f73de65c4816f69d6ebf75839c706145fcd Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:38 +0200
+Subject: [PATCH 02/10] tools/xenstore: ignore transaction id for [un]watch
+
+Instead of ignoring the transaction id for XS_WATCH and XS_UNWATCH
+commands as it is documented in docs/misc/xenstore.txt, it is tested
+for validity today.
+
+Really ignore the transaction id for XS_WATCH and XS_UNWATCH.
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+---
+ tools/xenstore/xenstored_core.c | 26 ++++++++++++++++----------
+ 1 file changed, 16 insertions(+), 10 deletions(-)
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index b43e1018babd..bb2f9fd4e76e 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -1268,13 +1268,17 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
+ static struct {
+       const char *str;
+       int (*func)(struct connection *conn, struct buffered_data *in);
++      unsigned int flags;
++#define XS_FLAG_NOTID         (1U << 0)       /* Ignore transaction id. */
+ } const wire_funcs[XS_TYPE_COUNT] = {
+       [XS_CONTROL]           = { "CONTROL",           do_control },
+       [XS_DIRECTORY]         = { "DIRECTORY",         send_directory },
+       [XS_READ]              = { "READ",              do_read },
+       [XS_GET_PERMS]         = { "GET_PERMS",         do_get_perms },
+-      [XS_WATCH]             = { "WATCH",             do_watch },
+-      [XS_UNWATCH]           = { "UNWATCH",           do_unwatch },
++      [XS_WATCH]             =
++          { "WATCH",         do_watch,        XS_FLAG_NOTID },
++      [XS_UNWATCH]           =
++          { "UNWATCH",       do_unwatch,      XS_FLAG_NOTID },
+       [XS_TRANSACTION_START] = { "TRANSACTION_START", do_transaction_start },
+       [XS_TRANSACTION_END]   = { "TRANSACTION_END",   do_transaction_end },
+       [XS_INTRODUCE]         = { "INTRODUCE",         do_introduce },
+@@ -1296,7 +1300,7 @@ static struct {
+ 
+ static const char *sockmsg_string(enum xsd_sockmsg_type type)
+ {
+-      if ((unsigned)type < XS_TYPE_COUNT && wire_funcs[type].str)
++      if ((unsigned int)type < ARRAY_SIZE(wire_funcs) && wire_funcs[type].str)
+               return wire_funcs[type].str;
+ 
+       return "**UNKNOWN**";
+@@ -1311,7 +1315,14 @@ static void process_message(struct connection *conn, struct buffered_data *in)
+       enum xsd_sockmsg_type type = in->hdr.msg.type;
+       int ret;
+ 
+-      trans = transaction_lookup(conn, in->hdr.msg.tx_id);
++      if ((unsigned int)type >= XS_TYPE_COUNT || !wire_funcs[type].func) {
++              eprintf("Client unknown operation %i", type);
++              send_error(conn, ENOSYS);
++              return;
++      }
++
++      trans = (wire_funcs[type].flags & XS_FLAG_NOTID)
++              ? NULL : transaction_lookup(conn, in->hdr.msg.tx_id);
+       if (IS_ERR(trans)) {
+               send_error(conn, -PTR_ERR(trans));
+               return;
+@@ -1320,12 +1331,7 @@ static void process_message(struct connection *conn, struct buffered_data *in)
+       assert(conn->transaction == NULL);
+       conn->transaction = trans;
+ 
+-      if ((unsigned)type < XS_TYPE_COUNT && wire_funcs[type].func)
+-              ret = wire_funcs[type].func(conn, in);
+-      else {
+-              eprintf("Client unknown operation %i", type);
+-              ret = ENOSYS;
+-      }
++      ret = wire_funcs[type].func(conn, in);
+       if (ret)
+               send_error(conn, ret);
+ 
+-- 
+2.17.1
+
+From b8c6dbb67ebb449126023446a7d209eedf966537 Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:39 +0200
+Subject: [PATCH 03/10] tools/xenstore: fix node accounting after failed node
+ creation
+
+When a node creation fails the number of nodes of the domain should be
+the same as before the failed node creation. In case of failure when
+trying to create a node requiring to create one or more intermediate
+nodes as well (e.g. when /a/b/c/d is to be created, but /a/b isn't
+existing yet) it might happen that the number of nodes of the creating
+domain is not reset to the value it had before.
+
+So move the quota accounting out of construct_node() and into the node
+write loop in create_node() in order to be able to undo the accounting
+in case of an error in the intermediate node destructor.
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+Acked-by: Julien Grall <jgrall%amazon.com@localhost>
+---
+ tools/xenstore/xenstored_core.c | 37 ++++++++++++++++++++++-----------
+ 1 file changed, 25 insertions(+), 12 deletions(-)
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index bb2f9fd4e76e..db9b9ca7957d 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -925,11 +925,6 @@ static struct node *construct_node(struct connection *conn, const void *ctx,
+       if (!parent)
+               return NULL;
+ 
+-      if (domain_entry(conn) >= quota_nb_entry_per_domain) {
+-              errno = ENOSPC;
+-              return NULL;
+-      }
+-
+       /* Add child to parent. */
+       base = basename(name);
+       baselen = strlen(base) + 1;
+@@ -962,7 +957,6 @@ static struct node *construct_node(struct connection *conn, const void *ctx,
+       node->children = node->data = NULL;
+       node->childlen = node->datalen = 0;
+       node->parent = parent;
+-      domain_entry_inc(conn, node);
+       return node;
+ 
+ nomem:
+@@ -982,6 +976,9 @@ static int destroy_node(void *_node)
+       key.dsize = strlen(node->name);
+ 
+       tdb_delete(tdb_ctx, key);
++
++      domain_entry_dec(talloc_parent(node), node);
++
+       return 0;
+ }
+ 
+@@ -998,18 +995,34 @@ static struct node *create_node(struct connection *conn, const void *ctx,
+       node->data = data;
+       node->datalen = datalen;
+ 
+-      /* We write out the nodes down, setting destructor in case
+-       * something goes wrong. */
++      /*
++       * We write out the nodes bottom up.
++       * All new created nodes will have i->parent set, while the final
++       * node will be already existing and won't have i->parent set.
++       * New nodes are subject to quota handling.
++       * Initially set a destructor for all new nodes removing them from
++       * TDB again and undoing quota accounting for the case of an error
++       * during the write loop.
++       */
+       for (i = node; i; i = i->parent) {
+-              if (write_node(conn, i, false)) {
+-                      domain_entry_dec(conn, i);
++              /* i->parent is set for each new node, so check quota. */
++              if (i->parent &&
++                  domain_entry(conn) >= quota_nb_entry_per_domain) {
++                      errno = ENOSPC;
+                       return NULL;
+               }
+-              talloc_set_destructor(i, destroy_node);
++              if (write_node(conn, i, false))
++                      return NULL;
++
++              /* Account for new node, set destructor for error case. */
++              if (i->parent) {
++                      domain_entry_inc(conn, i);
++                      talloc_set_destructor(i, destroy_node);
++              }
+       }
+ 
+       /* OK, now remove destructors so they stay around */
+-      for (i = node; i; i = i->parent)
++      for (i = node; i->parent; i = i->parent)
+               talloc_set_destructor(i, NULL);
+       return node;
+ }
+-- 
+2.17.1
+
+From 318aa75bd0c05423e717ad0b64adb204282025db Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:40 +0200
+Subject: [PATCH 04/10] tools/xenstore: simplify and rename check_event_node()
+
+There is no path which allows to call check_event_node() without a
+event name. So don't let the result depend on the name being NULL and
+add an assert() covering that case.
+
+Rename the function to check_special_event() to better match the
+semantics.
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+---
+ tools/xenstore/xenstored_watch.c | 12 +++++-------
+ 1 file changed, 5 insertions(+), 7 deletions(-)
+
+diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
+index 7dedca60dfd6..f2f1bed47cc6 100644
+--- tools/xenstore/xenstored_watch.c.orig
++++ tools/xenstore/xenstored_watch.c
+@@ -47,13 +47,11 @@ struct watch
+       char *node;
+ };
+ 
+-static bool check_event_node(const char *node)
++static bool check_special_event(const char *name)
+ {
+-      if (!node || !strstarts(node, "@")) {
+-              errno = EINVAL;
+-              return false;
+-      }
+-      return true;
++      assert(name);
++
++      return strstarts(name, "@");
+ }
+ 
+ /* Is child a subnode of parent, or equal? */
+@@ -87,7 +85,7 @@ static void add_event(struct connection *conn,
+       unsigned int len;
+       char *data;
+ 
+-      if (!check_event_node(name)) {
++      if (!check_special_event(name)) {
+               /* Can this conn load node, or see that it doesn't exist? */
+               struct node *node = get_node(conn, ctx, name, XS_PERM_READ);
+               /*
+-- 
+2.17.1
+
+From c625fae44aedc246776b52eb1173cf847a3d4d80 Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:41 +0200
+Subject: [PATCH 05/10] tools/xenstore: check privilege for
+ XS_IS_DOMAIN_INTRODUCED
+
+The Xenstore command XS_IS_DOMAIN_INTRODUCED should be possible for
+privileged domains only (the only user in the tree is the xenpaging
+daemon).
+
+Instead of having the privilege test for each command introduce a
+per-command flag for that purpose.
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+---
+ tools/xenstore/xenstored_core.c   | 24 ++++++++++++++++++------
+ tools/xenstore/xenstored_domain.c |  7 ++-----
+ 2 files changed, 20 insertions(+), 11 deletions(-)
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index db9b9ca7957d..6afd58431111 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -1283,8 +1283,10 @@ static struct {
+       int (*func)(struct connection *conn, struct buffered_data *in);
+       unsigned int flags;
+ #define XS_FLAG_NOTID         (1U << 0)       /* Ignore transaction id. */
++#define XS_FLAG_PRIV          (1U << 1)       /* Privileged domain only. */
+ } const wire_funcs[XS_TYPE_COUNT] = {
+-      [XS_CONTROL]           = { "CONTROL",           do_control },
++      [XS_CONTROL]           =
++          { "CONTROL",       do_control,      XS_FLAG_PRIV },
+       [XS_DIRECTORY]         = { "DIRECTORY",         send_directory },
+       [XS_READ]              = { "READ",              do_read },
+       [XS_GET_PERMS]         = { "GET_PERMS",         do_get_perms },
+@@ -1294,8 +1296,10 @@ static struct {
+           { "UNWATCH",       do_unwatch,      XS_FLAG_NOTID },
+       [XS_TRANSACTION_START] = { "TRANSACTION_START", do_transaction_start },
+       [XS_TRANSACTION_END]   = { "TRANSACTION_END",   do_transaction_end },
+-      [XS_INTRODUCE]         = { "INTRODUCE",         do_introduce },
+-      [XS_RELEASE]           = { "RELEASE",           do_release },
++      [XS_INTRODUCE]         =
++          { "INTRODUCE",     do_introduce,    XS_FLAG_PRIV },
++      [XS_RELEASE]           =
++          { "RELEASE",       do_release,      XS_FLAG_PRIV },
+       [XS_GET_DOMAIN_PATH]   = { "GET_DOMAIN_PATH",   do_get_domain_path },
+       [XS_WRITE]             = { "WRITE",             do_write },
+       [XS_MKDIR]             = { "MKDIR",             do_mkdir },
+@@ -1304,9 +1308,11 @@ static struct {
+       [XS_WATCH_EVENT]       = { "WATCH_EVENT",       NULL },
+       [XS_ERROR]             = { "ERROR",             NULL },
+       [XS_IS_DOMAIN_INTRODUCED] =
+-                      { "IS_DOMAIN_INTRODUCED", do_is_domain_introduced },
+-      [XS_RESUME]            = { "RESUME",            do_resume },
+-      [XS_SET_TARGET]        = { "SET_TARGET",        do_set_target },
++          { "IS_DOMAIN_INTRODUCED", do_is_domain_introduced, XS_FLAG_PRIV },
++      [XS_RESUME]            =
++          { "RESUME",        do_resume,       XS_FLAG_PRIV },
++      [XS_SET_TARGET]        =
++          { "SET_TARGET",    do_set_target,   XS_FLAG_PRIV },
+       [XS_RESET_WATCHES]     = { "RESET_WATCHES",     do_reset_watches },
+       [XS_DIRECTORY_PART]    = { "DIRECTORY_PART",    send_directory_part },
+ };
+@@ -1334,6 +1340,12 @@ static void process_message(struct connection *conn, struct buffered_data *in)
+               return;
+       }
+ 
++      if ((wire_funcs[type].flags & XS_FLAG_PRIV) &&
++          domain_is_unprivileged(conn)) {
++              send_error(conn, EACCES);
++              return;
++      }
++
+       trans = (wire_funcs[type].flags & XS_FLAG_NOTID)
+               ? NULL : transaction_lookup(conn, in->hdr.msg.tx_id);
+       if (IS_ERR(trans)) {
+diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
+index 1eae703ef680..0e2926e2a3d0 100644
+--- tools/xenstore/xenstored_domain.c.orig
++++ tools/xenstore/xenstored_domain.c
+@@ -377,7 +377,7 @@ int do_introduce(struct connection *conn, struct buffered_data *in)
+       if (get_strings(in, vec, ARRAY_SIZE(vec)) < ARRAY_SIZE(vec))
+               return EINVAL;
+ 
+-      if (domain_is_unprivileged(conn) || !conn->can_write)
++      if (!conn->can_write)
+               return EACCES;
+ 
+       domid = atoi(vec[0]);
+@@ -445,7 +445,7 @@ int do_set_target(struct connection *conn, struct buffered_data *in)
+       if (get_strings(in, vec, ARRAY_SIZE(vec)) < ARRAY_SIZE(vec))
+               return EINVAL;
+ 
+-      if (domain_is_unprivileged(conn) || !conn->can_write)
++      if (!conn->can_write)
+               return EACCES;
+ 
+       domid = atoi(vec[0]);
+@@ -480,9 +480,6 @@ static struct domain *onearg_domain(struct connection *conn,
+       if (!domid)
+               return ERR_PTR(-EINVAL);
+ 
+-      if (domain_is_unprivileged(conn))
+-              return ERR_PTR(-EACCES);
+-
+       return find_connected_domain(domid);
+ }
+ 
+-- 
+2.17.1
+
+From 461c880600175c06e23a63e62d9f1ccab755d708 Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:42 +0200
+Subject: [PATCH 06/10] tools/xenstore: rework node removal
+
+Today a Xenstore node is being removed by deleting it from the parent
+first and then deleting itself and all its children. This results in
+stale entries remaining in the data base in case e.g. a memory
+allocation is failing during processing. This would result in the
+rather strange behavior to be able to read a node (as its still in the
+data base) while not being visible in the tree view of Xenstore.
+
+Fix that by deleting the nodes from the leaf side instead of starting
+at the root.
+
+As fire_watches() is now called from _rm() the ctx parameter needs a
+const attribute.
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+---
+ tools/xenstore/xenstored_core.c  | 99 ++++++++++++++++----------------
+ tools/xenstore/xenstored_watch.c |  4 +-
+ tools/xenstore/xenstored_watch.h |  2 +-
+ 3 files changed, 54 insertions(+), 51 deletions(-)
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index 6afd58431111..1cb729a2cd5f 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -1087,74 +1087,76 @@ static int do_mkdir(struct connection *conn, struct buffered_data *in)
+       return 0;
+ }
+ 
+-static void delete_node(struct connection *conn, struct node *node)
+-{
+-      unsigned int i;
+-      char *name;
+-
+-      /* Delete self, then delete children.  If we crash, then the worst
+-         that can happen is the children will continue to take up space, but
+-         will otherwise be unreachable. */
+-      delete_node_single(conn, node);
+-
+-      /* Delete children, too. */
+-      for (i = 0; i < node->childlen; i += strlen(node->children+i) + 1) {
+-              struct node *child;
+-
+-              name = talloc_asprintf(node, "%s/%s", node->name,
+-                                     node->children + i);
+-              child = name ? read_node(conn, node, name) : NULL;
+-              if (child) {
+-                      delete_node(conn, child);
+-              }
+-              else {
+-                      trace("delete_node: Error deleting child '%s/%s'!\n",
+-                            node->name, node->children + i);
+-                      /* Skip it, we've already deleted the parent. */
+-              }
+-              talloc_free(name);
+-      }
+-}
+-
+-
+ /* Delete memory using memmove. */
+ static void memdel(void *mem, unsigned off, unsigned len, unsigned total)
+ {
+       memmove(mem + off, mem + off + len, total - off - len);
+ }
+ 
+-
+-static int remove_child_entry(struct connection *conn, struct node *node,
+-                            size_t offset)
++static void remove_child_entry(struct connection *conn, struct node *node,
++                             size_t offset)
+ {
+       size_t childlen = strlen(node->children + offset);
++
+       memdel(node->children, offset, childlen + 1, node->childlen);
+       node->childlen -= childlen + 1;
+-      return write_node(conn, node, true);
++      if (write_node(conn, node, true))
++              corrupt(conn, "Can't update parent node '%s'", node->name);
+ }
+ 
+-
+-static int delete_child(struct connection *conn,
+-                      struct node *node, const char *childname)
++static void delete_child(struct connection *conn,
++                       struct node *node, const char *childname)
+ {
+       unsigned int i;
+ 
+       for (i = 0; i < node->childlen; i += strlen(node->children+i) + 1) {
+               if (streq(node->children+i, childname)) {
+-                      return remove_child_entry(conn, node, i);
++                      remove_child_entry(conn, node, i);
++                      return;
+               }
+       }
+       corrupt(conn, "Can't find child '%s' in %s", childname, node->name);
+-      return ENOENT;
+ }
+ 
++static int delete_node(struct connection *conn, struct node *parent,
++                     struct node *node)
++{
++      char *name;
++
++      /* Delete children. */
++      while (node->childlen) {
++              struct node *child;
++
++              name = talloc_asprintf(node, "%s/%s", node->name,
++                                     node->children);
++              child = name ? read_node(conn, node, name) : NULL;
++              if (child) {
++                      if (delete_node(conn, node, child))
++                              return errno;
++              } else {
++                      trace("delete_node: Error deleting child '%s/%s'!\n",
++                            node->name, node->children);
++                      /* Quit deleting. */
++                      errno = ENOMEM;
++                      return errno;
++              }
++              talloc_free(name);
++      }
++
++      delete_node_single(conn, node);
++      delete_child(conn, parent, basename(node->name));
++      talloc_free(node);
++
++      return 0;
++}
+ 
+ static int _rm(struct connection *conn, const void *ctx, struct node *node,
+              const char *name)
+ {
+-      /* Delete from parent first, then if we crash, the worst that can
+-         happen is the child will continue to take up space, but will
+-         otherwise be unreachable. */
++      /*
++       * Deleting node by node, so the result is always consistent even in
++       * case of a failure.
++       */
+       struct node *parent;
+       char *parentname = get_parent(ctx, name);
+ 
+@@ -1165,11 +1167,13 @@ static int _rm(struct connection *conn, const void *ctx, struct node *node,
+       if (!parent)
+               return (errno == ENOMEM) ? ENOMEM : EINVAL;
+ 
+-      if (delete_child(conn, parent, basename(name)))
+-              return EINVAL;
+-
+-      delete_node(conn, node);
+-      return 0;
++      /*
++       * Fire the watches now, when we can still see the node permissions.
++       * This fine as we are single threaded and the next possible read will
++       * be handled only after the node has been really removed.
++       */
++      fire_watches(conn, ctx, name, true);
++      return delete_node(conn, parent, node);
+ }
+ 
+ 
+@@ -1207,7 +1211,6 @@ static int do_rm(struct connection *conn, struct buffered_data *in)
+       if (ret)
+               return ret;
+ 
+-      fire_watches(conn, in, name, true);
+       send_ack(conn, XS_RM);
+ 
+       return 0;
+diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
+index f2f1bed47cc6..f0bbfe7a6dc6 100644
+--- tools/xenstore/xenstored_watch.c.orig
++++ tools/xenstore/xenstored_watch.c
+@@ -77,7 +77,7 @@ static bool is_child(const char *child, const char *parent)
+  * Temporary memory allocations are done with ctx.
+  */
+ static void add_event(struct connection *conn,
+-                    void *ctx,
++                    const void *ctx,
+                     struct watch *watch,
+                     const char *name)
+ {
+@@ -121,7 +121,7 @@ static void add_event(struct connection *conn,
+  * Check whether any watch events are to be sent.
+  * Temporary memory allocations are done with ctx.
+  */
+-void fire_watches(struct connection *conn, void *ctx, const char *name,
++void fire_watches(struct connection *conn, const void *ctx, const char *name,
+                 bool recurse)
+ {
+       struct connection *i;
+diff --git a/tools/xenstore/xenstored_watch.h b/tools/xenstore/xenstored_watch.h
+index c72ea6a68542..54d4ea7e0d41 100644
+--- tools/xenstore/xenstored_watch.h.orig
++++ tools/xenstore/xenstored_watch.h
+@@ -25,7 +25,7 @@ int do_watch(struct connection *conn, struct buffered_data *in);
+ int do_unwatch(struct connection *conn, struct buffered_data *in);
+ 
+ /* Fire all watches: recurse means all the children are affected (ie. rm). */
+-void fire_watches(struct connection *conn, void *tmp, const char *name,
++void fire_watches(struct connection *conn, const void *tmp, const char *name,
+                 bool recurse);
+ 
+ void conn_delete_all_watches(struct connection *conn);
+-- 
+2.17.1
+
+From 6ca2e14b43aecc79effc1a0cd528a4aceef44d42 Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:43 +0200
+Subject: [PATCH 07/10] tools/xenstore: fire watches only when removing a
+ specific node
+
+Instead of firing all watches for removing a subtree in one go, do so
+only when the related node is being removed.
+
+The watches for the top-most node being removed include all watches
+including that node, while watches for nodes below that are only fired
+if they are matching exactly. This avoids firing any watch more than
+once when removing a subtree.
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+---
+ tools/xenstore/xenstored_core.c  | 11 ++++++-----
+ tools/xenstore/xenstored_watch.c | 13 ++++++++-----
+ tools/xenstore/xenstored_watch.h |  4 ++--
+ 3 files changed, 16 insertions(+), 12 deletions(-)
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index 1cb729a2cd5f..d7c025616ead 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -1118,8 +1118,8 @@ static void delete_child(struct connection *conn,
+       corrupt(conn, "Can't find child '%s' in %s", childname, node->name);
+ }
+ 
+-static int delete_node(struct connection *conn, struct node *parent,
+-                     struct node *node)
++static int delete_node(struct connection *conn, const void *ctx,
++                     struct node *parent, struct node *node)
+ {
+       char *name;
+ 
+@@ -1131,7 +1131,7 @@ static int delete_node(struct connection *conn, struct node *parent,
+                                      node->children);
+               child = name ? read_node(conn, node, name) : NULL;
+               if (child) {
+-                      if (delete_node(conn, node, child))
++                      if (delete_node(conn, ctx, node, child))
+                               return errno;
+               } else {
+                       trace("delete_node: Error deleting child '%s/%s'!\n",
+@@ -1143,6 +1143,7 @@ static int delete_node(struct connection *conn, struct node *parent,
+               talloc_free(name);
+       }
+ 
++      fire_watches(conn, ctx, node->name, true);
+       delete_node_single(conn, node);
+       delete_child(conn, parent, basename(node->name));
+       talloc_free(node);
+@@ -1172,8 +1173,8 @@ static int _rm(struct connection *conn, const void *ctx, struct node *node,
+        * This fine as we are single threaded and the next possible read will
+        * be handled only after the node has been really removed.
+        */
+-      fire_watches(conn, ctx, name, true);
+-      return delete_node(conn, parent, node);
++      fire_watches(conn, ctx, name, false);
++      return delete_node(conn, ctx, parent, node);
+ }
+ 
+ 
+diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
+index f0bbfe7a6dc6..3836675459fa 100644
+--- tools/xenstore/xenstored_watch.c.orig
++++ tools/xenstore/xenstored_watch.c
+@@ -122,7 +122,7 @@ static void add_event(struct connection *conn,
+  * Temporary memory allocations are done with ctx.
+  */
+ void fire_watches(struct connection *conn, const void *ctx, const char *name,
+-                bool recurse)
++                bool exact)
+ {
+       struct connection *i;
+       struct watch *watch;
+@@ -134,10 +134,13 @@ void fire_watches(struct connection *conn, const void *ctx, const char *name,
+       /* Create an event for each watch. */
+       list_for_each_entry(i, &connections, list) {
+               list_for_each_entry(watch, &i->watches, list) {
+-                      if (is_child(name, watch->node))
+-                              add_event(i, ctx, watch, name);
+-                      else if (recurse && is_child(watch->node, name))
+-                              add_event(i, ctx, watch, watch->node);
++                      if (exact) {
++                              if (streq(name, watch->node))
++                                      add_event(i, ctx, watch, name);
++                      } else {
++                              if (is_child(name, watch->node))
++                                      add_event(i, ctx, watch, name);
++                      }
+               }
+       }
+ }
+diff --git a/tools/xenstore/xenstored_watch.h b/tools/xenstore/xenstored_watch.h
+index 54d4ea7e0d41..1b3c80d3dda1 100644
+--- tools/xenstore/xenstored_watch.h.orig
++++ tools/xenstore/xenstored_watch.h
+@@ -24,9 +24,9 @@
+ int do_watch(struct connection *conn, struct buffered_data *in);
+ int do_unwatch(struct connection *conn, struct buffered_data *in);
+ 
+-/* Fire all watches: recurse means all the children are affected (ie. rm). */
++/* Fire all watches: !exact means all the children are affected (ie. rm). */
+ void fire_watches(struct connection *conn, const void *tmp, const char *name,
+-                bool recurse);
++                bool exact);
+ 
+ void conn_delete_all_watches(struct connection *conn);
+ 
+-- 
+2.17.1
+
+From 2d4f410899bf59e112c107f371c3d164f8a592f8 Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:44 +0200
+Subject: [PATCH 08/10] tools/xenstore: introduce node_perms structure
+
+There are several places in xenstored using a permission array and the
+size of that array. Introduce a new struct node_perms containing both.
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Acked-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+---
+ tools/xenstore/xenstored_core.c   | 79 +++++++++++++++----------------
+ tools/xenstore/xenstored_core.h   |  8 +++-
+ tools/xenstore/xenstored_domain.c | 12 ++---
+ 3 files changed, 50 insertions(+), 49 deletions(-)
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index d7c025616ead..fe9943113b9f 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -401,14 +401,14 @@ static struct node *read_node(struct connection *conn, const void *ctx,
+       /* Datalen, childlen, number of permissions */
+       hdr = (void *)data.dptr;
+       node->generation = hdr->generation;
+-      node->num_perms = hdr->num_perms;
++      node->perms.num = hdr->num_perms;
+       node->datalen = hdr->datalen;
+       node->childlen = hdr->childlen;
+ 
+       /* Permissions are struct xs_permissions. */
+-      node->perms = hdr->perms;
++      node->perms.p = hdr->perms;
+       /* Data is binary blob (usually ascii, no nul). */
+-      node->data = node->perms + node->num_perms;
++      node->data = node->perms.p + node->perms.num;
+       /* Children is strings, nul separated. */
+       node->children = node->data + node->datalen;
+ 
+@@ -425,7 +425,7 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
+       struct xs_tdb_record_hdr *hdr;
+ 
+       data.dsize = sizeof(*hdr)
+-              + node->num_perms*sizeof(node->perms[0])
++              + node->perms.num * sizeof(node->perms.p[0])
+               + node->datalen + node->childlen;
+ 
+       if (!no_quota_check && domain_is_unprivileged(conn) &&
+@@ -437,12 +437,13 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
+       data.dptr = talloc_size(node, data.dsize);
+       hdr = (void *)data.dptr;
+       hdr->generation = node->generation;
+-      hdr->num_perms = node->num_perms;
++      hdr->num_perms = node->perms.num;
+       hdr->datalen = node->datalen;
+       hdr->childlen = node->childlen;
+ 
+-      memcpy(hdr->perms, node->perms, node->num_perms*sizeof(node->perms[0]));
+-      p = hdr->perms + node->num_perms;
++      memcpy(hdr->perms, node->perms.p,
++             node->perms.num * sizeof(*node->perms.p));
++      p = hdr->perms + node->perms.num;
+       memcpy(p, node->data, node->datalen);
+       p += node->datalen;
+       memcpy(p, node->children, node->childlen);
+@@ -468,8 +469,7 @@ static int write_node(struct connection *conn, struct node *node,
+ }
+ 
+ static enum xs_perm_type perm_for_conn(struct connection *conn,
+-                                     struct xs_permissions *perms,
+-                                     unsigned int num)
++                                     const struct node_perms *perms)
+ {
+       unsigned int i;
+       enum xs_perm_type mask = XS_PERM_READ|XS_PERM_WRITE|XS_PERM_OWNER;
+@@ -478,16 +478,16 @@ static enum xs_perm_type perm_for_conn(struct connection *conn,
+               mask &= ~XS_PERM_WRITE;
+ 
+       /* Owners and tools get it all... */
+-      if (!domain_is_unprivileged(conn) || perms[0].id == conn->id
+-                || (conn->target && perms[0].id == conn->target->id))
++      if (!domain_is_unprivileged(conn) || perms->p[0].id == conn->id
++                || (conn->target && perms->p[0].id == conn->target->id))
+               return (XS_PERM_READ|XS_PERM_WRITE|XS_PERM_OWNER) & mask;
+ 
+-      for (i = 1; i < num; i++)
+-              if (perms[i].id == conn->id
+-                        || (conn->target && perms[i].id == conn->target->id))
+-                      return perms[i].perms & mask;
++      for (i = 1; i < perms->num; i++)
++              if (perms->p[i].id == conn->id
++                        || (conn->target && perms->p[i].id == conn->target->id))
++                      return perms->p[i].perms & mask;
+ 
+-      return perms[0].perms & mask;
++      return perms->p[0].perms & mask;
+ }
+ 
+ /*
+@@ -534,7 +534,7 @@ static int ask_parents(struct connection *conn, const void *ctx,
+               return 0;
+       }
+ 
+-      *perm = perm_for_conn(conn, node->perms, node->num_perms);
++      *perm = perm_for_conn(conn, &node->perms);
+       return 0;
+ }
+ 
+@@ -580,8 +580,7 @@ struct node *get_node(struct connection *conn,
+       node = read_node(conn, ctx, name);
+       /* If we don't have permission, we don't have node. */
+       if (node) {
+-              if ((perm_for_conn(conn, node->perms, node->num_perms) & perm)
+-                  != perm) {
++              if ((perm_for_conn(conn, &node->perms) & perm) != perm) {
+                       errno = EACCES;
+                       node = NULL;
+               }
+@@ -757,16 +756,15 @@ const char *onearg(struct buffered_data *in)
+       return in->buffer;
+ }
+ 
+-static char *perms_to_strings(const void *ctx,
+-                            struct xs_permissions *perms, unsigned int num,
++static char *perms_to_strings(const void *ctx, const struct node_perms *perms,
+                             unsigned int *len)
+ {
+       unsigned int i;
+       char *strings = NULL;
+       char buffer[MAX_STRLEN(unsigned int) + 1];
+ 
+-      for (*len = 0, i = 0; i < num; i++) {
+-              if (!xs_perm_to_string(&perms[i], buffer, sizeof(buffer)))
++      for (*len = 0, i = 0; i < perms->num; i++) {
++              if (!xs_perm_to_string(&perms->p[i], buffer, sizeof(buffer)))
+                       return NULL;
+ 
+               strings = talloc_realloc(ctx, strings, char,
+@@ -945,13 +943,13 @@ static struct node *construct_node(struct connection *conn, const void *ctx,
+               goto nomem;
+ 
+       /* Inherit permissions, except unprivileged domains own what they create */
+-      node->num_perms = parent->num_perms;
+-      node->perms = talloc_memdup(node, parent->perms,
+-                                  node->num_perms * sizeof(node->perms[0]));
+-      if (!node->perms)
++      node->perms.num = parent->perms.num;
++      node->perms.p = talloc_memdup(node, parent->perms.p,
++                                    node->perms.num * sizeof(*node->perms.p));
++      if (!node->perms.p)
+               goto nomem;
+       if (domain_is_unprivileged(conn))
+-              node->perms[0].id = conn->id;
++              node->perms.p[0].id = conn->id;
+ 
+       /* No children, no data */
+       node->children = node->data = NULL;
+@@ -1228,7 +1226,7 @@ static int do_get_perms(struct connection *conn, struct buffered_data *in)
+       if (!node)
+               return errno;
+ 
+-      strings = perms_to_strings(node, node->perms, node->num_perms, &len);
++      strings = perms_to_strings(node, &node->perms, &len);
+       if (!strings)
+               return errno;
+ 
+@@ -1239,13 +1237,12 @@ static int do_get_perms(struct connection *conn, struct buffered_data *in)
+ 
+ static int do_set_perms(struct connection *conn, struct buffered_data *in)
+ {
+-      unsigned int num;
+-      struct xs_permissions *perms;
++      struct node_perms perms;
+       char *name, *permstr;
+       struct node *node;
+ 
+-      num = xs_count_strings(in->buffer, in->used);
+-      if (num < 2)
++      perms.num = xs_count_strings(in->buffer, in->used);
++      if (perms.num < 2)
+               return EINVAL;
+ 
+       /* First arg is node name. */
+@@ -1256,21 +1253,21 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
+               return errno;
+ 
+       permstr = in->buffer + strlen(in->buffer) + 1;
+-      num--;
++      perms.num--;
+ 
+-      perms = talloc_array(node, struct xs_permissions, num);
+-      if (!perms)
++      perms.p = talloc_array(node, struct xs_permissions, perms.num);
++      if (!perms.p)
+               return ENOMEM;
+-      if (!xs_strings_to_perms(perms, num, permstr))
++      if (!xs_strings_to_perms(perms.p, perms.num, permstr))
+               return errno;
+ 
+       /* Unprivileged domains may not change the owner. */
+-      if (domain_is_unprivileged(conn) && perms[0].id != node->perms[0].id)
++      if (domain_is_unprivileged(conn) &&
++          perms.p[0].id != node->perms.p[0].id)
+               return EPERM;
+ 
+       domain_entry_dec(conn, node);
+       node->perms = perms;
+-      node->num_perms = num;
+       domain_entry_inc(conn, node);
+ 
+       if (write_node(conn, node, false))
+@@ -1545,8 +1542,8 @@ static void manual_node(const char *name, const char *child)
+               barf_perror("Could not allocate initial node %s", name);
+ 
+       node->name = name;
+-      node->perms = &perms;
+-      node->num_perms = 1;
++      node->perms.p = &perms;
++      node->perms.num = 1;
+       node->children = (char *)child;
+       if (child)
+               node->childlen = strlen(child) + 1;
+diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
+index 3cb1c235a101..193d93142636 100644
+--- tools/xenstore/xenstored_core.h.orig
++++ tools/xenstore/xenstored_core.h
+@@ -109,6 +109,11 @@ struct connection
+ };
+ extern struct list_head connections;
+ 
++struct node_perms {
++      unsigned int num;
++      struct xs_permissions *p;
++};
++
+ struct node {
+       const char *name;
+ 
+@@ -120,8 +125,7 @@ struct node {
+ #define NO_GENERATION ~((uint64_t)0)
+ 
+       /* Permissions. */
+-      unsigned int num_perms;
+-      struct xs_permissions *perms;
++      struct node_perms perms;
+ 
+       /* Contents. */
+       unsigned int datalen;
+diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
+index 0e2926e2a3d0..dc51cdfa9aa7 100644
+--- tools/xenstore/xenstored_domain.c.orig
++++ tools/xenstore/xenstored_domain.c
+@@ -657,12 +657,12 @@ void domain_entry_inc(struct connection *conn, struct node *node)
+       if (!conn)
+               return;
+ 
+-      if (node->perms && node->perms[0].id != conn->id) {
++      if (node->perms.p && node->perms.p[0].id != conn->id) {
+               if (conn->transaction) {
+                       transaction_entry_inc(conn->transaction,
+-                              node->perms[0].id);
++                              node->perms.p[0].id);
+               } else {
+-                      d = find_domain_by_domid(node->perms[0].id);
++                      d = find_domain_by_domid(node->perms.p[0].id);
+                       if (d)
+                               d->nbentry++;
+               }
+@@ -683,12 +683,12 @@ void domain_entry_dec(struct connection *conn, struct node *node)
+       if (!conn)
+               return;
+ 
+-      if (node->perms && node->perms[0].id != conn->id) {
++      if (node->perms.p && node->perms.p[0].id != conn->id) {
+               if (conn->transaction) {
+                       transaction_entry_dec(conn->transaction,
+-                              node->perms[0].id);
++                              node->perms.p[0].id);
+               } else {
+-                      d = find_domain_by_domid(node->perms[0].id);
++                      d = find_domain_by_domid(node->perms.p[0].id);
+                       if (d && d->nbentry)
+                               d->nbentry--;
+               }
+-- 
+2.17.1
+
+From cddf74031b3c8a108e8fd7db0bf56e9c2809d3e2 Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:45 +0200
+Subject: [PATCH 09/10] tools/xenstore: allow special watches for privileged
+ callers only
+
+The special watches "@introduceDomain" and "@releaseDomain" should be
+allowed for privileged callers only, as they allow to gain information
+about presence of other guests on the host. So send watch events for
+those watches via privileged connections only.
+
+In order to allow for disaggregated setups where e.g. driver domains
+need to make use of those special watches add support for calling
+"set permissions" for those special nodes, too.
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+---
+ docs/misc/xenstore.txt            |  5 +++
+ tools/xenstore/xenstored_core.c   | 27 ++++++++------
+ tools/xenstore/xenstored_core.h   |  2 ++
+ tools/xenstore/xenstored_domain.c | 60 +++++++++++++++++++++++++++++++
+ tools/xenstore/xenstored_domain.h |  5 +++
+ tools/xenstore/xenstored_watch.c  |  4 +++
+ 6 files changed, 93 insertions(+), 10 deletions(-)
+
+diff --git a/docs/misc/xenstore.txt b/docs/misc/xenstore.txt
+index 6f8569d5760f..32969eb3fecd 100644
+--- docs/misc/xenstore.txt.orig
++++ docs/misc/xenstore.txt
+@@ -170,6 +170,9 @@ SET_PERMS          <path>|<perm-as-string>|+?
+               n<domid>        no access
+       See http://wiki.xen.org/wiki/XenBus section
+       `Permissions' for details of the permissions system.
++      It is possible to set permissions for the special watch paths
++      "@introduceDomain" and "@releaseDomain" to enable receiving those
++      watches in unprivileged domains.
+ 
+ ---------- Watches ----------
+ 
+@@ -194,6 +197,8 @@ WATCH                      <wpath>|<token>|?
+           @releaseDomain      occurs on any domain crash or
+                               shutdown, and also on RELEASE
+                               and domain destruction
++      <wspecial> events are sent to privileged callers or explicitly
++      via SET_PERMS enabled domains only.
+ 
+       When a watch is first set up it is triggered once straight
+       away, with <path> equal to <wpath>.  Watches may be triggered
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index fe9943113b9f..720bec269dd3 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -468,8 +468,8 @@ static int write_node(struct connection *conn, struct node *node,
+       return write_node_raw(conn, &key, node, no_quota_check);
+ }
+ 
+-static enum xs_perm_type perm_for_conn(struct connection *conn,
+-                                     const struct node_perms *perms)
++enum xs_perm_type perm_for_conn(struct connection *conn,
++                              const struct node_perms *perms)
+ {
+       unsigned int i;
+       enum xs_perm_type mask = XS_PERM_READ|XS_PERM_WRITE|XS_PERM_OWNER;
+@@ -1245,22 +1245,29 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
+       if (perms.num < 2)
+               return EINVAL;
+ 
+-      /* First arg is node name. */
+-      /* We must own node to do this (tools can do this too). */
+-      node = get_node_canonicalized(conn, in, in->buffer, &name,
+-                                    XS_PERM_WRITE | XS_PERM_OWNER);
+-      if (!node)
+-              return errno;
+-
+       permstr = in->buffer + strlen(in->buffer) + 1;
+       perms.num--;
+ 
+-      perms.p = talloc_array(node, struct xs_permissions, perms.num);
++      perms.p = talloc_array(in, struct xs_permissions, perms.num);
+       if (!perms.p)
+               return ENOMEM;
+       if (!xs_strings_to_perms(perms.p, perms.num, permstr))
+               return errno;
+ 
++      /* First arg is node name. */
++      if (strstarts(in->buffer, "@")) {
++              if (set_perms_special(conn, in->buffer, &perms))
++                      return errno;
++              send_ack(conn, XS_SET_PERMS);
++              return 0;
++      }
++
++      /* We must own node to do this (tools can do this too). */
++      node = get_node_canonicalized(conn, in, in->buffer, &name,
++                                    XS_PERM_WRITE | XS_PERM_OWNER);
++      if (!node)
++              return errno;
++
+       /* Unprivileged domains may not change the owner. */
+       if (domain_is_unprivileged(conn) &&
+           perms.p[0].id != node->perms.p[0].id)
+diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
+index 193d93142636..f3da6bbc943d 100644
+--- tools/xenstore/xenstored_core.h.orig
++++ tools/xenstore/xenstored_core.h
+@@ -165,6 +165,8 @@ struct node *get_node(struct connection *conn,
+ struct connection *new_connection(connwritefn_t *write, connreadfn_t *read);
+ void check_store(void);
+ void corrupt(struct connection *conn, const char *fmt, ...);
++enum xs_perm_type perm_for_conn(struct connection *conn,
++                              const struct node_perms *perms);
+ 
+ /* Is this a valid node name? */
+ bool is_valid_nodename(const char *node);
+diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
+index dc51cdfa9aa7..7afabe0ae084 100644
+--- tools/xenstore/xenstored_domain.c.orig
++++ tools/xenstore/xenstored_domain.c
+@@ -41,6 +41,9 @@ static evtchn_port_t virq_port;
+ 
+ xenevtchn_handle *xce_handle = NULL;
+ 
++static struct node_perms dom_release_perms;
++static struct node_perms dom_introduce_perms;
++
+ struct domain
+ {
+       struct list_head list;
+@@ -589,6 +592,59 @@ void restore_existing_connections(void)
+ {
+ }
+ 
++static int set_dom_perms_default(struct node_perms *perms)
++{
++      perms->num = 1;
++      perms->p = talloc_array(NULL, struct xs_permissions, perms->num);
++      if (!perms->p)
++              return -1;
++      perms->p->id = 0;
++      perms->p->perms = XS_PERM_NONE;
++
++      return 0;
++}
++
++static struct node_perms *get_perms_special(const char *name)
++{
++      if (!strcmp(name, "@releaseDomain"))
++              return &dom_release_perms;
++      if (!strcmp(name, "@introduceDomain"))
++              return &dom_introduce_perms;
++      return NULL;
++}
++
++int set_perms_special(struct connection *conn, const char *name,
++                    struct node_perms *perms)
++{
++      struct node_perms *p;
++
++      p = get_perms_special(name);
++      if (!p)
++              return EINVAL;
++
++      if ((perm_for_conn(conn, p) & (XS_PERM_WRITE | XS_PERM_OWNER)) !=
++          (XS_PERM_WRITE | XS_PERM_OWNER))
++              return EACCES;
++
++      p->num = perms->num;
++      talloc_free(p->p);
++      p->p = perms->p;
++      talloc_steal(NULL, perms->p);
++
++      return 0;
++}
++
++bool check_perms_special(const char *name, struct connection *conn)
++{
++      struct node_perms *p;
++
++      p = get_perms_special(name);
++      if (!p)
++              return false;
++
++      return perm_for_conn(conn, p) & XS_PERM_READ;
++}
++
+ static int dom0_init(void) 
+ { 
+       evtchn_port_t port;
+@@ -610,6 +666,10 @@ static int dom0_init(void)
+ 
+       xenevtchn_notify(xce_handle, dom0->port);
+ 
++      if (set_dom_perms_default(&dom_release_perms) ||
++          set_dom_perms_default(&dom_introduce_perms))
++              return -1;
++
+       return 0; 
+ }
+ 
+diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
+index 56ae01597475..259183962a9c 100644
+--- tools/xenstore/xenstored_domain.h.orig
++++ tools/xenstore/xenstored_domain.h
+@@ -65,6 +65,11 @@ void domain_watch_inc(struct connection *conn);
+ void domain_watch_dec(struct connection *conn);
+ int domain_watch(struct connection *conn);
+ 
++/* Special node permission handling. */
++int set_perms_special(struct connection *conn, const char *name,
++                    struct node_perms *perms);
++bool check_perms_special(const char *name, struct connection *conn);
++
+ /* Write rate limiting */
+ 
+ #define WRL_FACTOR   1000 /* for fixed-point arithmetic */
+diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
+index 3836675459fa..f4e289362eb6 100644
+--- tools/xenstore/xenstored_watch.c.orig
++++ tools/xenstore/xenstored_watch.c
+@@ -133,6 +133,10 @@ void fire_watches(struct connection *conn, const void *ctx, const char *name,
+ 
+       /* Create an event for each watch. */
+       list_for_each_entry(i, &connections, list) {
++              /* introduce/release domain watches */
++              if (check_special_event(name) && !check_perms_special(name, i))
++                      continue;
++
+               list_for_each_entry(watch, &i->watches, list) {
+                       if (exact) {
+                               if (streq(name, watch->node))
+-- 
+2.17.1
+
+From e57b7687b43b033fe45e755e285efbe67bc71921 Mon Sep 17 00:00:00 2001
+From: Juergen Gross <jgross%suse.com@localhost>
+Date: Thu, 11 Jun 2020 16:12:46 +0200
+Subject: [PATCH 10/10] tools/xenstore: avoid watch events for nodes without
+ access
+
+Today watch events are sent regardless of the access rights of the
+node the event is sent for. This enables any guest to e.g. setup a
+watch for "/" in order to have a detailed record of all Xenstore
+modifications.
+
+Modify that by sending only watch events for nodes that the watcher
+has a chance to see otherwise (either via direct reads or by querying
+the children of a node). This includes cases where the visibility of
+a node for a watcher is changing (permissions being removed).
+
+This is part of XSA-115.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+[julieng: Handle rebase conflict]
+Reviewed-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+---
+ tools/xenstore/xenstored_core.c        | 28 +++++-----
+ tools/xenstore/xenstored_core.h        | 15 ++++--
+ tools/xenstore/xenstored_domain.c      |  6 +--
+ tools/xenstore/xenstored_transaction.c | 21 +++++++-
+ tools/xenstore/xenstored_watch.c       | 75 +++++++++++++++++++-------
+ tools/xenstore/xenstored_watch.h       |  2 +-
+ 6 files changed, 104 insertions(+), 43 deletions(-)
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index 720bec269dd3..1c2845454560 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -358,8 +358,8 @@ static void initialize_fds(int sock, int *p_sock_pollfd_idx,
+  * If it fails, returns NULL and sets errno.
+  * Temporary memory allocations will be done with ctx.
+  */
+-static struct node *read_node(struct connection *conn, const void *ctx,
+-                            const char *name)
++struct node *read_node(struct connection *conn, const void *ctx,
++                     const char *name)
+ {
+       TDB_DATA key, data;
+       struct xs_tdb_record_hdr *hdr;
+@@ -494,7 +494,7 @@ enum xs_perm_type perm_for_conn(struct connection *conn,
+  * Get name of node parent.
+  * Temporary memory allocations are done with ctx.
+  */
+-static char *get_parent(const void *ctx, const char *node)
++char *get_parent(const void *ctx, const char *node)
+ {
+       char *parent;
+       char *slash = strrchr(node + 1, '/');
+@@ -566,10 +566,10 @@ static int errno_from_parents(struct connection *conn, const void *ctx,
+  * If it fails, returns NULL and sets errno.
+  * Temporary memory allocations are done with ctx.
+  */
+-struct node *get_node(struct connection *conn,
+-                    const void *ctx,
+-                    const char *name,
+-                    enum xs_perm_type perm)
++static struct node *get_node(struct connection *conn,
++                           const void *ctx,
++                           const char *name,
++                           enum xs_perm_type perm)
+ {
+       struct node *node;
+ 
+@@ -1056,7 +1056,7 @@ static int do_write(struct connection *conn, struct buffered_data *in)
+                       return errno;
+       }
+ 
+-      fire_watches(conn, in, name, false);
++      fire_watches(conn, in, name, node, false, NULL);
+       send_ack(conn, XS_WRITE);
+ 
+       return 0;
+@@ -1078,7 +1078,7 @@ static int do_mkdir(struct connection *conn, struct buffered_data *in)
+               node = create_node(conn, in, name, NULL, 0);
+               if (!node)
+                       return errno;
+-              fire_watches(conn, in, name, false);
++              fire_watches(conn, in, name, node, false, NULL);
+       }
+       send_ack(conn, XS_MKDIR);
+ 
+@@ -1141,7 +1141,7 @@ static int delete_node(struct connection *conn, const void *ctx,
+               talloc_free(name);
+       }
+ 
+-      fire_watches(conn, ctx, node->name, true);
++      fire_watches(conn, ctx, node->name, node, true, NULL);
+       delete_node_single(conn, node);
+       delete_child(conn, parent, basename(node->name));
+       talloc_free(node);
+@@ -1165,13 +1165,14 @@ static int _rm(struct connection *conn, const void *ctx, struct node *node,
+       parent = read_node(conn, ctx, parentname);
+       if (!parent)
+               return (errno == ENOMEM) ? ENOMEM : EINVAL;
++      node->parent = parent;
+ 
+       /*
+        * Fire the watches now, when we can still see the node permissions.
+        * This fine as we are single threaded and the next possible read will
+        * be handled only after the node has been really removed.
+        */
+-      fire_watches(conn, ctx, name, false);
++      fire_watches(conn, ctx, name, node, false, NULL);
+       return delete_node(conn, ctx, parent, node);
+ }
+ 
+@@ -1237,7 +1238,7 @@ static int do_get_perms(struct connection *conn, struct buffered_data *in)
+ 
+ static int do_set_perms(struct connection *conn, struct buffered_data *in)
+ {
+-      struct node_perms perms;
++      struct node_perms perms, old_perms;
+       char *name, *permstr;
+       struct node *node;
+ 
+@@ -1273,6 +1274,7 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
+           perms.p[0].id != node->perms.p[0].id)
+               return EPERM;
+ 
++      old_perms = node->perms;
+       domain_entry_dec(conn, node);
+       node->perms = perms;
+       domain_entry_inc(conn, node);
+@@ -1280,7 +1282,7 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
+       if (write_node(conn, node, false))
+               return errno;
+ 
+-      fire_watches(conn, in, name, false);
++      fire_watches(conn, in, name, node, false, &old_perms);
+       send_ack(conn, XS_SET_PERMS);
+ 
+       return 0;
+diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
+index f3da6bbc943d..e050b27cbdde 100644
+--- tools/xenstore/xenstored_core.h.orig
++++ tools/xenstore/xenstored_core.h
+@@ -152,15 +152,17 @@ void send_ack(struct connection *conn, enum xsd_sockmsg_type type);
+ /* Canonicalize this path if possible. */
+ char *canonicalize(struct connection *conn, const void *ctx, const char *node);
+ 
++/* Get access permissions. */
++enum xs_perm_type perm_for_conn(struct connection *conn,
++                              const struct node_perms *perms);
++
+ /* Write a node to the tdb data base. */
+ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
+                  bool no_quota_check);
+ 
+-/* Get this node, checking we have permissions. */
+-struct node *get_node(struct connection *conn,
+-                    const void *ctx,
+-                    const char *name,
+-                    enum xs_perm_type perm);
++/* Get a node from the tdb data base. */
++struct node *read_node(struct connection *conn, const void *ctx,
++                     const char *name);
+ 
+ struct connection *new_connection(connwritefn_t *write, connreadfn_t *read);
+ void check_store(void);
+@@ -171,6 +173,9 @@ enum xs_perm_type perm_for_conn(struct connection *conn,
+ /* Is this a valid node name? */
+ bool is_valid_nodename(const char *node);
+ 
++/* Get name of parent node. */
++char *get_parent(const void *ctx, const char *node);
++
+ /* Tracing infrastructure. */
+ void trace_create(const void *data, const char *type);
+ void trace_destroy(const void *data, const char *type);
+diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
+index 7afabe0ae084..711a11b18ad6 100644
+--- tools/xenstore/xenstored_domain.c.orig
++++ tools/xenstore/xenstored_domain.c
+@@ -206,7 +206,7 @@ static int destroy_domain(void *_domain)
+                       unmap_interface(domain->interface);
+       }
+ 
+-      fire_watches(NULL, domain, "@releaseDomain", false);
++      fire_watches(NULL, domain, "@releaseDomain", NULL, false, NULL);
+ 
+       wrl_domain_destroy(domain);
+ 
+@@ -244,7 +244,7 @@ static void domain_cleanup(void)
+       }
+ 
+       if (notify)
+-              fire_watches(NULL, NULL, "@releaseDomain", false);
++              fire_watches(NULL, NULL, "@releaseDomain", NULL, false, NULL);
+ }
+ 
+ /* We scan all domains rather than use the information given here. */
+@@ -410,7 +410,7 @@ int do_introduce(struct connection *conn, struct buffered_data *in)
+               /* Now domain belongs to its connection. */
+               talloc_steal(domain->conn, domain);
+ 
+-              fire_watches(NULL, in, "@introduceDomain", false);
++              fire_watches(NULL, in, "@introduceDomain", NULL, false, NULL);
+       } else if ((domain->mfn == mfn) && (domain->conn != conn)) {
+               /* Use XS_INTRODUCE for recreating the xenbus event-channel. */
+               if (domain->port)
+diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
+index e87897573469..a7d8c5d475ec 100644
+--- tools/xenstore/xenstored_transaction.c.orig
++++ tools/xenstore/xenstored_transaction.c
+@@ -114,6 +114,9 @@ struct accessed_node
+       /* Generation count (or NO_GENERATION) for conflict checking. */
+       uint64_t generation;
+ 
++      /* Original node permissions. */
++      struct node_perms perms;
++
+       /* Generation count checking required? */
+       bool check_gen;
+ 
+@@ -260,6 +263,15 @@ int access_node(struct connection *conn, struct node *node,
+               i->node = talloc_strdup(i, node->name);
+               if (!i->node)
+                       goto nomem;
++              if (node->generation != NO_GENERATION && node->perms.num) {
++                      i->perms.p = talloc_array(i, struct xs_permissions,
++                                                node->perms.num);
++                      if (!i->perms.p)
++                              goto nomem;
++                      i->perms.num = node->perms.num;
++                      memcpy(i->perms.p, node->perms.p,
++                             i->perms.num * sizeof(*i->perms.p));
++              }
+ 
+               introduce = true;
+               i->ta_node = false;
+@@ -368,9 +380,14 @@ static int finalize_transaction(struct connection *conn,
+                               talloc_free(data.dptr);
+                               if (ret)
+                                       goto err;
+-                      } else if (tdb_delete(tdb_ctx, key))
++                              fire_watches(conn, trans, i->node, NULL, false,
++                                           i->perms.p ? &i->perms : NULL);
++                      } else {
++                              fire_watches(conn, trans, i->node, NULL, false,
++                                           i->perms.p ? &i->perms : NULL);
++                              if (tdb_delete(tdb_ctx, key))
+                                       goto err;
+-                      fire_watches(conn, trans, i->node, false);
++                      }
+               }
+ 
+               if (i->ta_node && tdb_delete(tdb_ctx, ta_key))
+diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
+index f4e289362eb6..71c108ea99f1 100644
+--- tools/xenstore/xenstored_watch.c.orig
++++ tools/xenstore/xenstored_watch.c
+@@ -85,22 +85,6 @@ static void add_event(struct connection *conn,
+       unsigned int len;
+       char *data;
+ 
+-      if (!check_special_event(name)) {
+-              /* Can this conn load node, or see that it doesn't exist? */
+-              struct node *node = get_node(conn, ctx, name, XS_PERM_READ);
+-              /*
+-               * XXX We allow EACCES here because otherwise a non-dom0
+-               * backend driver cannot watch for disappearance of a frontend
+-               * xenstore directory. When the directory disappears, we
+-               * revert to permissions of the parent directory for that path,
+-               * which will typically disallow access for the backend.
+-               * But this breaks device-channel teardown!
+-               * Really we should fix this better...
+-               */
+-              if (!node && errno != ENOENT && errno != EACCES)
+-                      return;
+-      }
+-
+       if (watch->relative_path) {
+               name += strlen(watch->relative_path);
+               if (*name == '/') /* Could be "" */
+@@ -117,12 +101,60 @@ static void add_event(struct connection *conn,
+       talloc_free(data);
+ }
+ 
++/*
++ * Check permissions of a specific watch to fire:
++ * Either the node itself or its parent have to be readable by the connection
++ * the watch has been setup for. In case a watch event is created due to
++ * changed permissions we need to take the old permissions into account, too.
++ */
++static bool watch_permitted(struct connection *conn, const void *ctx,
++                          const char *name, struct node *node,
++                          struct node_perms *perms)
++{
++      enum xs_perm_type perm;
++      struct node *parent;
++      char *parent_name;
++
++      if (perms) {
++              perm = perm_for_conn(conn, perms);
++              if (perm & XS_PERM_READ)
++                      return true;
++      }
++
++      if (!node) {
++              node = read_node(conn, ctx, name);
++              if (!node)
++                      return false;
++      }
++
++      perm = perm_for_conn(conn, &node->perms);
++      if (perm & XS_PERM_READ)
++              return true;
++
++      parent = node->parent;
++      if (!parent) {
++              parent_name = get_parent(ctx, node->name);
++              if (!parent_name)
++                      return false;
++              parent = read_node(conn, ctx, parent_name);
++              if (!parent)
++                      return false;
++      }
++
++      perm = perm_for_conn(conn, &parent->perms);
++
++      return perm & XS_PERM_READ;
++}
++
+ /*
+  * Check whether any watch events are to be sent.
+  * Temporary memory allocations are done with ctx.
++ * We need to take the (potential) old permissions of the node into account
++ * as a watcher losing permissions to access a node should receive the
++ * watch event, too.
+  */
+ void fire_watches(struct connection *conn, const void *ctx, const char *name,
+-                bool exact)
++                struct node *node, bool exact, struct node_perms *perms)
+ {
+       struct connection *i;
+       struct watch *watch;
+@@ -134,8 +166,13 @@ void fire_watches(struct connection *conn, const void *ctx, const char *name,
+       /* Create an event for each watch. */
+       list_for_each_entry(i, &connections, list) {
+               /* introduce/release domain watches */
+-              if (check_special_event(name) && !check_perms_special(name, i))
+-                      continue;
++              if (check_special_event(name)) {
++                      if (!check_perms_special(name, i))
++                              continue;
++              } else {
++                      if (!watch_permitted(i, ctx, name, node, perms))
++                              continue;
++              }
+ 
+               list_for_each_entry(watch, &i->watches, list) {
+                       if (exact) {
+diff --git a/tools/xenstore/xenstored_watch.h b/tools/xenstore/xenstored_watch.h
+index 1b3c80d3dda1..03094374f379 100644
+--- tools/xenstore/xenstored_watch.h.orig
++++ tools/xenstore/xenstored_watch.h
+@@ -26,7 +26,7 @@ int do_unwatch(struct connection *conn, struct buffered_data *in);
+ 
+ /* Fire all watches: !exact means all the children are affected (ie. rm). */
+ void fire_watches(struct connection *conn, const void *tmp, const char *name,
+-                bool exact);
++                struct node *node, bool exact, struct node_perms *perms);
+ 
+ void conn_delete_all_watches(struct connection *conn);
+ 
+-- 
+2.17.1
+
Index: pkgsrc/sysutils/xentools411/patches/patch-XSA115-o
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA115-o:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA115-o  Thu Dec 17 16:48:12 2020
@@ -0,0 +1,694 @@
+$NetBSD: patch-XSA115-o,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: ignore transaction id for [un]watch
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Instead of ignoring the transaction id for XS_WATCH and XS_UNWATCH
+commands as it is documented in docs/misc/xenstore.txt, it is tested
+for validity today.
+
+Really ignore the transaction id for XS_WATCH and XS_UNWATCH.
+
+This is part of XSA-115.
+
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+Reviewed-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
+index 74c69f869c..0a0e43d1f0 100644
+--- tools/ocaml/xenstored/process.ml.orig
++++ tools/ocaml/xenstored/process.ml
+@@ -492,12 +492,19 @@ let retain_op_in_history ty =
+       | Xenbus.Xb.Op.Reset_watches
+       | Xenbus.Xb.Op.Invalid           -> false
+ 
++let maybe_ignore_transaction = function
++      | Xenbus.Xb.Op.Watch | Xenbus.Xb.Op.Unwatch -> fun tid ->
++              if tid <> Transaction.none then
++                      debug "Ignoring transaction ID %d for watch/unwatch" tid;
++              Transaction.none
++      | _ -> fun x -> x
++
+ (**
+  * Nothrow guarantee.
+  *)
+ let process_packet ~store ~cons ~doms ~con ~req =
+       let ty = req.Packet.ty in
+-      let tid = req.Packet.tid in
++      let tid = maybe_ignore_transaction ty req.Packet.tid in
+       let rid = req.Packet.rid in
+       try
+               let fct = function_of_type ty in
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: check privilege for XS_IS_DOMAIN_INTRODUCED
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The Xenstore command XS_IS_DOMAIN_INTRODUCED should be possible for privileged
+domains only (the only user in the tree is the xenpaging daemon).
+
+This is part of XSA-115.
+
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+Reviewed-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
+index 0a0e43d1f0..f374abe998 100644
+--- tools/ocaml/xenstored/process.ml.orig
++++ tools/ocaml/xenstored/process.ml
+@@ -166,7 +166,9 @@ let do_setperms con t domains cons data =
+ let do_error con t domains cons data =
+       raise Define.Unknown_operation
+ 
+-let do_isintroduced con t domains cons data =
++let do_isintroduced con _t domains _cons data =
++      if not (Connection.is_dom0 con)
++      then raise Define.Permission_denied;
+       let domid =
+               match (split None '\000' data) with
+               | domid :: _ -> int_of_string domid
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: unify watch firing
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This will make it easier insert additional checks in a follow-up patch.
+All watches are now fired from a single function.
+
+This is part of XSA-115.
+
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+Reviewed-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
+index be9c62f27f..d7432c6597 100644
+--- tools/ocaml/xenstored/connection.ml.orig
++++ tools/ocaml/xenstored/connection.ml
+@@ -210,8 +210,7 @@ let fire_watch watch path =
+               end else
+                       path
+       in
+-      let data = Utils.join_by_null [ new_path; watch.token; "" ] in
+-      send_reply watch.con Transaction.none 0 Xenbus.Xb.Op.Watchevent data
++      fire_single_watch { watch with path = new_path }
+ 
+ (* Search for a valid unused transaction id. *)
+ let rec valid_transaction_id con proposed_id =
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: introduce permissions for special watches
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The special watches "@introduceDomain" and "@releaseDomain" should be
+allowed for privileged callers only, as they allow to gain information
+about presence of other guests on the host. So send watch events for
+those watches via privileged connections only.
+
+Start to address this by treating the special watches as regular nodes
+in the tree, which gives them normal semantics for permissions.  A later
+change will restrict the handling, so that they can't be listed, etc.
+
+This is part of XSA-115.
+
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+Reviewed-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
+index f374abe998..c3c8ea2f4b 100644
+--- tools/ocaml/xenstored/process.ml.orig
++++ tools/ocaml/xenstored/process.ml
+@@ -414,7 +414,7 @@ let do_introduce con t domains cons data =
+               else try
+                       let ndom = Domains.create domains domid mfn port in
+                       Connections.add_domain cons ndom;
+-                      Connections.fire_spec_watches cons "@introduceDomain";
++                      Connections.fire_spec_watches cons Store.Path.introduce_domain;
+                       ndom
+               with _ -> raise Invalid_Cmd_Args
+       in
+@@ -433,7 +433,7 @@ let do_release con t domains cons data =
+       Domains.del domains domid;
+       Connections.del_domain cons domid;
+       if fire_spec_watches 
+-      then Connections.fire_spec_watches cons "@releaseDomain"
++      then Connections.fire_spec_watches cons Store.Path.release_domain
+       else raise Invalid_Cmd_Args
+ 
+ let do_resume con t domains cons data =
+diff --git a/tools/ocaml/xenstored/store.ml b/tools/ocaml/xenstored/store.ml
+index 6375a1c889..98d368d52f 100644
+--- tools/ocaml/xenstored/store.ml.orig
++++ tools/ocaml/xenstored/store.ml
+@@ -214,6 +214,11 @@ let rec lookup node path fct =
+ 
+ let apply rnode path fct =
+       lookup rnode path fct
++
++let introduce_domain = "@introduceDomain"
++let release_domain = "@releaseDomain"
++let specials = List.map of_string [ introduce_domain; release_domain ]
++
+ end
+ 
+ (* The Store.t type *)
+diff --git a/tools/ocaml/xenstored/utils.ml b/tools/ocaml/xenstored/utils.ml
+index b252db799b..e8c9fe4e94 100644
+--- tools/ocaml/xenstored/utils.ml.orig
++++ tools/ocaml/xenstored/utils.ml
+@@ -88,19 +88,17 @@ let read_file_single_integer filename =
+       Unix.close fd;
+       int_of_string (Bytes.sub_string buf 0 sz)
+ 
+-let path_complete path connection_path =
+-      if String.get path 0 <> '/' then
+-              connection_path ^ path
+-      else
+-              path
+-
++(* @path may be guest data and needs its length validating.  @connection_path
++ * is generated locally in xenstored and always of the form "/local/domain/$N/" *)
+ let path_validate path connection_path =
+-      if String.length path = 0 || String.length path > 1024 then
+-              raise Define.Invalid_path
+-      else
+-              let cpath = path_complete path connection_path in
+-              if String.get cpath 0 <> '/' then
+-                      raise Define.Invalid_path
+-              else
+-                      cpath
++      let len = String.length path in
++
++      if len = 0 || len > 1024 then raise Define.Invalid_path;
++
++      let abs_path =
++              match String.get path 0 with
++              | '/' | '@' -> path
++              | _   -> connection_path ^ path
++      in
+ 
++      abs_path
+diff --git a/tools/ocaml/xenstored/xenstored.ml b/tools/ocaml/xenstored/xenstored.ml
+index 49fc18bf19..32c3b1c0f1 100644
+--- tools/ocaml/xenstored/xenstored.ml.orig
++++ tools/ocaml/xenstored/xenstored.ml
+@@ -287,6 +287,8 @@ let _ =
+       let quit = ref false in
+ 
+       Logging.init_xenstored_log();
++      List.iter (fun path ->
++              Store.write store Perms.Connection.full_rights path "") Store.Path.specials;
+ 
+       let filename = Paths.xen_run_stored ^ "/db" in
+       if cf.restart && Sys.file_exists filename then (
+@@ -339,7 +341,7 @@ let _ =
+                                       let (notify, deaddom) = Domains.cleanup domains in
+                                       List.iter (Connections.del_domain cons) deaddom;
+                                       if deaddom <> [] || notify then
+-                                              Connections.fire_spec_watches cons "@releaseDomain"
++                                              Connections.fire_spec_watches cons Store.Path.release_domain
+                               )
+                               else
+                                       let c = Connections.find_domain_by_port cons port in
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: avoid watch events for nodes without access
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Today watch events are sent regardless of the access rights of the
+node the event is sent for. This enables any guest to e.g. setup a
+watch for "/" in order to have a detailed record of all Xenstore
+modifications.
+
+Modify that by sending only watch events for nodes that the watcher
+has a chance to see otherwise (either via direct reads or by querying
+the children of a node). This includes cases where the visibility of
+a node for a watcher is changing (permissions being removed).
+
+Permissions for nodes are looked up either in the old (pre
+transaction/command) or current trees (post transaction).  If
+permissions are changed multiple times in a transaction only the final
+version is checked, because considering a transaction atomic the
+individual permission changes would not be noticable to an outside
+observer.
+
+Two trees are only needed for set_perms: here we can either notice the
+node disappearing (if we loose permission), appearing
+(if we gain permission), or changing (if we preserve permission).
+
+RM needs to only look at the old tree: in the new tree the node would be
+gone, or could have different permissions if it was recreated (the
+recreation would get its own watch fired).
+
+Inside a tree we lookup the watch path's parent, and then the watch path
+child itself.  This gets us 4 sets of permissions in worst case, and if
+either of these allows a watch, then we permit it to fire.  The
+permission lookups are done without logging the failures, otherwise we'd
+get confusing errors about permission denied for some paths, but a watch
+still firing. The actual result is logged in xenstored-access log:
+
+  'w event ...' as usual if watch was fired
+  'w notfired...' if the watch was not fired, together with path and
+  permission set to help in troubleshooting
+
+Adding a watch bypasses permission checks and always fires the watch
+once immediately. This is consistent with the specification, and no
+information is gained (the watch is fired both if the path exists or
+doesn't, and both if you have or don't have access, i.e. it reflects the
+path a domain gave it back to that domain).
+
+There are some semantic changes here:
+
+  * Write+rm in a single transaction of the same path is unobservable
+    now via watches: both before and after a transaction the path
+    doesn't exist, thus both tree lookups come up with the empty
+    permission set, and noone, not even Dom0 can see this. This is
+    consistent with transaction atomicity though.
+  * Similar to above if we temporarily grant and then revoke permission
+    on a path any watches fired inbetween are ignored as well
+  * There is a new log event (w notfired) which shows the permission set
+    of the path, and the path.
+  * Watches on paths that a domain doesn't have access to are now not
+    seen, which is the purpose of the security fix.
+
+This is part of XSA-115.
+
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+Reviewed-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
+index d7432c6597..1389d971c2 100644
+--- tools/ocaml/xenstored/connection.ml.orig
++++ tools/ocaml/xenstored/connection.ml
+@@ -196,11 +196,36 @@ let list_watches con =
+               con.watches [] in
+       List.concat ll
+ 
+-let fire_single_watch watch =
++let dbg fmt = Logging.debug "connection" fmt
++let info fmt = Logging.info "connection" fmt
++
++let lookup_watch_perm path = function
++| None -> []
++| Some root ->
++      try Store.Path.apply root path @@ fun parent name ->
++              Store.Node.get_perms parent ::
++              try [Store.Node.get_perms (Store.Node.find parent name)]
++              with Not_found -> []
++      with Define.Invalid_path | Not_found -> []
++
++let lookup_watch_perms oldroot root path =
++      lookup_watch_perm path oldroot @ lookup_watch_perm path (Some root)
++
++let fire_single_watch_unchecked watch =
+       let data = Utils.join_by_null [watch.path; watch.token; ""] in
+       send_reply watch.con Transaction.none 0 Xenbus.Xb.Op.Watchevent data
+ 
+-let fire_watch watch path =
++let fire_single_watch (oldroot, root) watch =
++      let abspath = get_watch_path watch.con watch.path |> Store.Path.of_string in
++      let perms = lookup_watch_perms oldroot root abspath in
++      if List.exists (Perms.has watch.con.perm READ) perms then
++              fire_single_watch_unchecked watch
++      else
++              let perms = perms |> List.map (Perms.Node.to_string ~sep:" ") |> String.concat ", " in
++              let con = get_domstr watch.con in
++              Logging.watch_not_fired ~con perms (Store.Path.to_string abspath)
++
++let fire_watch roots watch path =
+       let new_path =
+               if watch.is_relative && path.[0] = '/'
+               then begin
+@@ -210,7 +235,7 @@ let fire_watch watch path =
+               end else
+                       path
+       in
+-      fire_single_watch { watch with path = new_path }
++      fire_single_watch roots { watch with path = new_path }
+ 
+ (* Search for a valid unused transaction id. *)
+ let rec valid_transaction_id con proposed_id =
+diff --git a/tools/ocaml/xenstored/connections.ml b/tools/ocaml/xenstored/connections.ml
+index ae7692819d..020b875dcd 100644
+--- tools/ocaml/xenstored/connections.ml.orig
++++ tools/ocaml/xenstored/connections.ml
+@@ -135,25 +135,26 @@ let del_watch cons con path token =
+       watch
+ 
+ (* path is absolute *)
+-let fire_watches cons path recurse =
++let fire_watches ?oldroot root cons path recurse =
+       let key = key_of_path path in
+       let path = Store.Path.to_string path in
++      let roots = oldroot, root in
+       let fire_watch _ = function
+               | None         -> ()
+-              | Some watches -> List.iter (fun w -> Connection.fire_watch w path) watches
++              | Some watches -> List.iter (fun w -> Connection.fire_watch roots w path) watches
+       in
+       let fire_rec x = function
+               | None         -> ()
+               | Some watches -> 
+-                        List.iter (fun w -> Connection.fire_single_watch w) watches
++                      List.iter (Connection.fire_single_watch roots) watches
+       in
+       Trie.iter_path fire_watch cons.watches key;
+       if recurse then
+               Trie.iter fire_rec (Trie.sub cons.watches key)
+ 
+-let fire_spec_watches cons specpath =
++let fire_spec_watches root cons specpath =
+       iter cons (fun con ->
+-              List.iter (fun w -> Connection.fire_single_watch w) (Connection.get_watches con specpath))
++              List.iter (Connection.fire_single_watch (None, root)) (Connection.get_watches con specpath))
+ 
+ let set_target cons domain target_domain =
+       let con = find_domain cons domain in
+diff --git a/tools/ocaml/xenstored/logging.ml b/tools/ocaml/xenstored/logging.ml
+index ea6033195d..99c7bc5e13 100644
+--- tools/ocaml/xenstored/logging.ml.orig
++++ tools/ocaml/xenstored/logging.ml
+@@ -161,6 +161,8 @@ let xenstored_log_nb_lines = ref 13215
+ let xenstored_log_nb_chars = ref (-1)
+ let xenstored_logger = ref (None: logger option)
+ 
++let debug_enabled () = !xenstored_log_level = Debug
++
+ let set_xenstored_log_destination s =
+       xenstored_log_destination := log_destination_of_string s
+ 
+@@ -204,6 +206,7 @@ type access_type =
+       | Commit
+       | Newconn
+       | Endconn
++      | Watch_not_fired
+       | XbOp of Xenbus.Xb.Op.operation
+ 
+ let string_of_tid ~con tid =
+@@ -217,6 +220,7 @@ let string_of_access_type = function
+       | Commit                  -> "commit   "
+       | Newconn                 -> "newconn  "
+       | Endconn                 -> "endconn  "
++      | Watch_not_fired         -> "w notfired"
+ 
+       | XbOp op -> match op with
+       | Xenbus.Xb.Op.Debug             -> "debug    "
+@@ -331,3 +335,7 @@ let xb_answer ~tid ~con ~ty data =
+               | _ -> false, Debug
+       in
+       if print then access_logging ~tid ~con ~data (XbOp ty) ~level
++
++let watch_not_fired ~con perms path =
++      let data = Printf.sprintf "EPERM perms=[%s] path=%s" perms path in
++      access_logging ~tid:0 ~con ~data Watch_not_fired ~level:Info
+diff --git a/tools/ocaml/xenstored/perms.ml b/tools/ocaml/xenstored/perms.ml
+index 3ea193ea14..23b80aba3d 100644
+--- tools/ocaml/xenstored/perms.ml.orig
++++ tools/ocaml/xenstored/perms.ml
+@@ -79,9 +79,9 @@ let of_string s =
+ let string_of_perm perm =
+       Printf.sprintf "%c%u" (char_of_permty (snd perm)) (fst perm)
+ 
+-let to_string permvec =
++let to_string ?(sep="\000") permvec =
+       let l = ((permvec.owner, permvec.other) :: permvec.acl) in
+-      String.concat "\000" (List.map string_of_perm l)
++      String.concat sep (List.map string_of_perm l)
+ 
+ end
+ 
+@@ -132,8 +132,8 @@ let check_owner (connection:Connection.t) (node:Node.t) =
+       then Connection.is_owner connection (Node.get_owner node)
+       else true
+ 
+-(* check if the current connection has the requested perm on the current node *)
+-let check (connection:Connection.t) request (node:Node.t) =
++(* check if the current connection lacks the requested perm on the current node *)
++let lacks (connection:Connection.t) request (node:Node.t) =
+       let check_acl domainid =
+               let perm =
+                       if List.mem_assoc domainid (Node.get_acl node)
+@@ -154,11 +154,19 @@ let check (connection:Connection.t) request (node:Node.t) =
+                       info "Permission denied: Domain %d has write only access" domainid;
+                       false
+       in
+-      if !activate
++      !activate
+       && not (Connection.is_dom0 connection)
+       && not (check_owner connection node)
+       && not (List.exists check_acl (Connection.get_owners connection))
++
++(* check if the current connection has the requested perm on the current node.
++*  Raises an exception if it doesn't. *)
++let check connection request node =
++      if lacks connection request node
+       then raise Define.Permission_denied
+ 
++(* check if the current connection has the requested perm on the current node *)
++let has connection request node = not (lacks connection request node)
++
+ let equiv perm1 perm2 =
+       (Node.to_string perm1) = (Node.to_string perm2)
+diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
+index c3c8ea2f4b..3cd0097db9 100644
+--- tools/ocaml/xenstored/process.ml.orig
++++ tools/ocaml/xenstored/process.ml
+@@ -56,15 +56,17 @@ let split_one_path data con =
+       | path :: "" :: [] -> Store.Path.create path (Connection.get_path con)
+       | _                -> raise Invalid_Cmd_Args
+ 
+-let process_watch ops cons =
++let process_watch t cons =
++      let oldroot = t.Transaction.oldroot in
++      let newroot = Store.get_root t.store in
++      let ops = Transaction.get_paths t |> List.rev in
+       let do_op_watch op cons =
+-              let recurse = match (fst op) with
+-              | Xenbus.Xb.Op.Write    -> false
+-              | Xenbus.Xb.Op.Mkdir    -> false
+-              | Xenbus.Xb.Op.Rm       -> true
+-              | Xenbus.Xb.Op.Setperms -> false
++              let recurse, oldroot, root = match (fst op) with
++              | Xenbus.Xb.Op.Write|Xenbus.Xb.Op.Mkdir -> false, None, newroot
++              | Xenbus.Xb.Op.Rm       -> true, None, oldroot
++              | Xenbus.Xb.Op.Setperms -> false, Some oldroot, newroot
+               | _              -> raise (Failure "huh ?") in
+-              Connections.fire_watches cons (snd op) recurse in
++              Connections.fire_watches ?oldroot root cons (snd op) recurse in
+       List.iter (fun op -> do_op_watch op cons) ops
+ 
+ let create_implicit_path t perm path =
+@@ -205,7 +207,7 @@ let reply_ack fct con t doms cons data =
+       fct con t doms cons data;
+       Packet.Ack (fun () ->
+               if Transaction.get_id t = Transaction.none then
+-                      process_watch (Transaction.get_paths t) cons
++                      process_watch t cons
+       )
+ 
+ let reply_data fct con t doms cons data =
+@@ -353,14 +355,17 @@ let transaction_replay c t doms cons =
+                       Connection.end_transaction c tid None
+               )
+ 
+-let do_watch con t domains cons data =
++let do_watch con t _domains cons data =
+       let (node, token) = 
+               match (split None '\000' data) with
+               | [node; token; ""]   -> node, token
+               | _                   -> raise Invalid_Cmd_Args
+               in
+       let watch = Connections.add_watch cons con node token in
+-      Packet.Ack (fun () -> Connection.fire_single_watch watch)
++      Packet.Ack (fun () ->
++              (* xenstore.txt says this watch is fired immediately,
++                 implying even if path doesn't exist or is unreadable *)
++              Connection.fire_single_watch_unchecked watch)
+ 
+ let do_unwatch con t domains cons data =
+       let (node, token) =
+@@ -391,7 +396,7 @@ let do_transaction_end con t domains cons data =
+       if not success then
+               raise Transaction_again;
+       if commit then begin
+-              process_watch (List.rev (Transaction.get_paths t)) cons;
++              process_watch t cons;
+               match t.Transaction.ty with
+               | Transaction.No ->
+                       () (* no need to record anything *)
+@@ -414,7 +419,7 @@ let do_introduce con t domains cons data =
+               else try
+                       let ndom = Domains.create domains domid mfn port in
+                       Connections.add_domain cons ndom;
+-                      Connections.fire_spec_watches cons Store.Path.introduce_domain;
++                      Connections.fire_spec_watches (Transaction.get_root t) cons Store.Path.introduce_domain;
+                       ndom
+               with _ -> raise Invalid_Cmd_Args
+       in
+@@ -433,7 +438,7 @@ let do_release con t domains cons data =
+       Domains.del domains domid;
+       Connections.del_domain cons domid;
+       if fire_spec_watches 
+-      then Connections.fire_spec_watches cons Store.Path.release_domain
++      then Connections.fire_spec_watches (Transaction.get_root t) cons Store.Path.release_domain
+       else raise Invalid_Cmd_Args
+ 
+ let do_resume con t domains cons data =
+@@ -501,6 +506,8 @@ let maybe_ignore_transaction = function
+               Transaction.none
+       | _ -> fun x -> x
+ 
++
++let () = Printexc.record_backtrace true
+ (**
+  * Nothrow guarantee.
+  *)
+@@ -542,7 +549,8 @@ let process_packet ~store ~cons ~doms ~con ~req =
+               (* Put the response on the wire *)
+               send_response ty con t rid response
+       with exn ->
+-              error "process packet: %s" (Printexc.to_string exn);
++              let bt = Printexc.get_backtrace () in
++              error "process packet: %s. %s" (Printexc.to_string exn) bt;
+               Connection.send_error con tid rid "EIO"
+ 
+ let do_input store cons doms con =
+diff --git a/tools/ocaml/xenstored/transaction.ml b/tools/ocaml/xenstored/transaction.ml
+index 23e7ccff1b..9e9e28db9b 100644
+--- tools/ocaml/xenstored/transaction.ml.orig
++++ tools/ocaml/xenstored/transaction.ml
+@@ -82,6 +82,7 @@ type t = {
+       start_count: int64;
+       store: Store.t; (* This is the store that we change in write operations. *)
+       quota: Quota.t;
++      oldroot: Store.Node.t;
+       mutable paths: (Xenbus.Xb.Op.operation * Store.Path.t) list;
+       mutable operations: (Packet.request * Packet.response) list;
+       mutable read_lowpath: Store.Path.t option;
+@@ -123,6 +124,7 @@ let make ?(internal=false) id store =
+               start_count = !counter;
+               store = if id = none then store else Store.copy store;
+               quota = Quota.copy store.Store.quota;
++              oldroot = Store.get_root store;
+               paths = [];
+               operations = [];
+               read_lowpath = None;
+@@ -137,6 +139,8 @@ let make ?(internal=false) id store =
+ let get_store t = t.store
+ let get_paths t = t.paths
+ 
++let get_root t = Store.get_root t.store
++
+ let is_read_only t = t.paths = []
+ let add_wop t ty path = t.paths <- (ty, path) :: t.paths
+ let add_operation ~perm t request response =
+diff --git a/tools/ocaml/xenstored/xenstored.ml b/tools/ocaml/xenstored/xenstored.ml
+index 32c3b1c0f1..e9f471846f 100644
+--- tools/ocaml/xenstored/xenstored.ml.orig
++++ tools/ocaml/xenstored/xenstored.ml
+@@ -341,7 +341,9 @@ let _ =
+                                       let (notify, deaddom) = Domains.cleanup domains in
+                                       List.iter (Connections.del_domain cons) deaddom;
+                                       if deaddom <> [] || notify then
+-                                              Connections.fire_spec_watches cons Store.Path.release_domain
++                                              Connections.fire_spec_watches
++                                                      (Store.get_root store)
++                                                      cons Store.Path.release_domain
+                               )
+                               else
+                                       let c = Connections.find_domain_by_port cons port in
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: add xenstored.conf flag to turn off watch
+ permission checks
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+There are flags to turn off quotas and the permission system, so add one
+that turns off the newly introduced watch permission checks as well.
+
+This is part of XSA-115.
+
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+Reviewed-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
+index 1389d971c2..698f721345 100644
+--- tools/ocaml/xenstored/connection.ml.orig
++++ tools/ocaml/xenstored/connection.ml
+@@ -218,7 +218,7 @@ let fire_single_watch_unchecked watch =
+ let fire_single_watch (oldroot, root) watch =
+       let abspath = get_watch_path watch.con watch.path |> Store.Path.of_string in
+       let perms = lookup_watch_perms oldroot root abspath in
+-      if List.exists (Perms.has watch.con.perm READ) perms then
++      if Perms.can_fire_watch watch.con.perm perms then
+               fire_single_watch_unchecked watch
+       else
+               let perms = perms |> List.map (Perms.Node.to_string ~sep:" ") |> String.concat ", " in
+diff --git a/tools/ocaml/xenstored/oxenstored.conf.in b/tools/ocaml/xenstored/oxenstored.conf.in
+index 6579b84448..d5d4f00de8 100644
+--- tools/ocaml/xenstored/oxenstored.conf.in.orig
++++ tools/ocaml/xenstored/oxenstored.conf.in
+@@ -44,6 +44,16 @@ conflict-rate-limit-is-aggregate = true
+ # Activate node permission system
+ perms-activate = true
+ 
++# Activate the watch permission system
++# When this is enabled unprivileged guests can only get watch events
++# for xenstore entries that they would've been able to read.
++#
++# When this is disabled unprivileged guests may get watch events
++# for xenstore entries that they cannot read. The watch event contains
++# only the entry name, not the value.
++# This restores behaviour prior to XSA-115.
++perms-watch-activate = true
++
+ # Activate quota
+ quota-activate = true
+ quota-maxentity = 1000
+diff --git a/tools/ocaml/xenstored/perms.ml b/tools/ocaml/xenstored/perms.ml
+index 23b80aba3d..ee7fee6bda 100644
+--- tools/ocaml/xenstored/perms.ml.orig
++++ tools/ocaml/xenstored/perms.ml
+@@ -20,6 +20,7 @@ let info fmt = Logging.info "perms" fmt
+ open Stdext
+ 
+ let activate = ref true
++let watch_activate = ref true
+ 
+ type permty = READ | WRITE | RDWR | NONE
+ 
+@@ -168,5 +169,9 @@ let check connection request node =
+ (* check if the current connection has the requested perm on the current node *)
+ let has connection request node = not (lacks connection request node)
+ 
++let can_fire_watch connection perms =
++      not !watch_activate
++      || List.exists (has connection READ) perms
++
+ let equiv perm1 perm2 =
+       (Node.to_string perm1) = (Node.to_string perm2)
+diff --git a/tools/ocaml/xenstored/xenstored.ml b/tools/ocaml/xenstored/xenstored.ml
+index e9f471846f..30fc874327 100644
+--- tools/ocaml/xenstored/xenstored.ml.orig
++++ tools/ocaml/xenstored/xenstored.ml
+@@ -95,6 +95,7 @@ let parse_config filename =
+               ("conflict-max-history-seconds", Config.Set_float Define.conflict_max_history_seconds);
+               ("conflict-rate-limit-is-aggregate", Config.Set_bool Define.conflict_rate_limit_is_aggregate);
+               ("perms-activate", Config.Set_bool Perms.activate);
++              ("perms-watch-activate", Config.Set_bool Perms.watch_activate);
+               ("quota-activate", Config.Set_bool Quota.activate);
+               ("quota-maxwatch", Config.Set_int Define.maxwatch);
+               ("quota-transaction", Config.Set_int Define.maxtransaction);
Index: pkgsrc/sysutils/xentools411/patches/patch-XSA322-c
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA322-c:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA322-c  Thu Dec 17 16:48:12 2020
@@ -0,0 +1,536 @@
+$NetBSD: patch-XSA322-c,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From: Juergen Gross <jgross%suse.com@localhost>
+Subject: tools/xenstore: revoke access rights for removed domains
+
+Access rights of Xenstore nodes are per domid. Unfortunately existing
+granted access rights are not removed when a domain is being destroyed.
+This means that a new domain created with the same domid will inherit
+the access rights to Xenstore nodes from the previous domain(s) with
+the same domid.
+
+This can be avoided by adding a generation counter to each domain.
+The generation counter of the domain is set to the global generation
+counter when a domain structure is being allocated. When reading or
+writing a node all permissions of domains which are younger than the
+node itself are dropped. This is done by flagging the related entry
+as invalid in order to avoid modifying permissions in a way the user
+could detect.
+
+A special case has to be considered: for a new domain the first
+Xenstore entries are already written before the domain is officially
+introduced in Xenstore. In order not to drop the permissions for the
+new domain a domain struct is allocated even before introduction if
+the hypervisor is aware of the domain. This requires adding another
+bool "introduced" to struct domain in xenstored. In order to avoid
+additional padding holes convert the shutdown flag to bool, too.
+
+As verifying permissions has its price regarding runtime add a new
+quota for limiting the number of permissions an unprivileged domain
+can set for a node. The default for that new quota is 5.
+
+This is part of XSA-322.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+Acked-by: Julien Grall <julien%amazon.com@localhost>
+
+diff --git a/tools/xenstore/include/xenstore_lib.h b/tools/xenstore/include/xenstore_lib.h
+index 0ffbae9eb574..4c9b6d16858d 100644
+--- tools/xenstore/include/xenstore_lib.h.orig
++++ tools/xenstore/include/xenstore_lib.h
+@@ -34,6 +34,7 @@ enum xs_perm_type {
+       /* Internal use. */
+       XS_PERM_ENOENT_OK = 4,
+       XS_PERM_OWNER = 8,
++      XS_PERM_IGNORE = 16,
+ };
+ 
+ struct xs_permissions
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index 2a86c4aa5bce..4fbe5c759c1b 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -101,6 +101,7 @@ int quota_nb_entry_per_domain = 1000;
+ int quota_nb_watch_per_domain = 128;
+ int quota_max_entry_size = 2048; /* 2K */
+ int quota_max_transaction = 10;
++int quota_nb_perms_per_node = 5;
+ 
+ void trace(const char *fmt, ...)
+ {
+@@ -407,8 +408,13 @@ struct node *read_node(struct connection *conn, const void *ctx,
+ 
+       /* Permissions are struct xs_permissions. */
+       node->perms.p = hdr->perms;
++      if (domain_adjust_node_perms(node)) {
++              talloc_free(node);
++              return NULL;
++      }
++
+       /* Data is binary blob (usually ascii, no nul). */
+-      node->data = node->perms.p + node->perms.num;
++      node->data = node->perms.p + hdr->num_perms;
+       /* Children is strings, nul separated. */
+       node->children = node->data + node->datalen;
+ 
+@@ -424,6 +430,9 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
+       void *p;
+       struct xs_tdb_record_hdr *hdr;
+ 
++      if (domain_adjust_node_perms(node))
++              return errno;
++
+       data.dsize = sizeof(*hdr)
+               + node->perms.num * sizeof(node->perms.p[0])
+               + node->datalen + node->childlen;
+@@ -483,8 +492,9 @@ enum xs_perm_type perm_for_conn(struct connection *conn,
+               return (XS_PERM_READ|XS_PERM_WRITE|XS_PERM_OWNER) & mask;
+ 
+       for (i = 1; i < perms->num; i++)
+-              if (perms->p[i].id == conn->id
+-                        || (conn->target && perms->p[i].id == conn->target->id))
++              if (!(perms->p[i].perms & XS_PERM_IGNORE) &&
++                  (perms->p[i].id == conn->id ||
++                   (conn->target && perms->p[i].id == conn->target->id)))
+                       return perms->p[i].perms & mask;
+ 
+       return perms->p[0].perms & mask;
+@@ -1246,8 +1256,12 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
+       if (perms.num < 2)
+               return EINVAL;
+ 
+-      permstr = in->buffer + strlen(in->buffer) + 1;
+       perms.num--;
++      if (domain_is_unprivileged(conn) &&
++          perms.num > quota_nb_perms_per_node)
++              return ENOSPC;
++
++      permstr = in->buffer + strlen(in->buffer) + 1;
+ 
+       perms.p = talloc_array(in, struct xs_permissions, perms.num);
+       if (!perms.p)
+@@ -1919,6 +1933,7 @@ static void usage(void)
+ "  -S, --entry-size <size> limit the size of entry per domain, and\n"
+ "  -W, --watch-nb <nb>     limit the number of watches per domain,\n"
+ "  -t, --transaction <nb>  limit the number of transaction allowed per domain,\n"
++"  -A, --perm-nb <nb>      limit the number of permissions per node,\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"
+@@ -1939,6 +1954,7 @@ static struct option options[] = {
+       { "entry-size", 1, NULL, 'S' },
+       { "trace-file", 1, NULL, 'T' },
+       { "transaction", 1, NULL, 't' },
++      { "perm-nb", 1, NULL, 'A' },
+       { "no-recovery", 0, NULL, 'R' },
+       { "internal-db", 0, NULL, 'I' },
+       { "verbose", 0, NULL, 'V' },
+@@ -1961,7 +1977,7 @@ int main(int argc, char *argv[])
+       int timeout;
+ 
+ 
+-      while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:T:RVW:", options,
++      while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:T:RVW:", options,
+                                 NULL)) != -1) {
+               switch (opt) {
+               case 'D':
+@@ -2003,6 +2019,9 @@ int main(int argc, char *argv[])
+               case 'W':
+                       quota_nb_watch_per_domain = strtol(optarg, NULL, 10);
+                       break;
++              case 'A':
++                      quota_nb_perms_per_node = strtol(optarg, NULL, 10);
++                      break;
+               case 'e':
+                       dom0_event = strtol(optarg, NULL, 10);
+                       break;
+diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
+index 0b2f49ac7d4c..f5e7af46e8aa 100644
+--- tools/xenstore/xenstored_domain.c.orig
++++ tools/xenstore/xenstored_domain.c
+@@ -71,8 +71,14 @@ struct domain
+       /* The connection associated with this. */
+       struct connection *conn;
+ 
++      /* Generation count at domain introduction time. */
++      uint64_t generation;
++
+       /* Have we noticed that this domain is shutdown? */
+-      int shutdown;
++      bool shutdown;
++
++      /* Has domain been officially introduced? */
++      bool introduced;
+ 
+       /* number of entry from this domain in the store */
+       int nbentry;
+@@ -200,6 +206,9 @@ static int destroy_domain(void *_domain)
+ 
+       list_del(&domain->list);
+ 
++      if (!domain->introduced)
++              return 0;
++
+       if (domain->port) {
+               if (xenevtchn_unbind(xce_handle, domain->port) == -1)
+                       eprintf("> Unbinding port %i failed!\n", domain->port);
+@@ -230,20 +230,33 @@
+       return 0;
+ }
+ 
++static bool get_domain_info(unsigned int domid, xc_dominfo_t *dominfo)
++{
++      return xc_domain_getinfo(*xc_handle, domid, 1, dominfo) == 1 &&
++           dominfo->domid == domid;
++}
++
+ static void domain_cleanup(void)
+ {
+       xc_dominfo_t dominfo;
+       struct domain *domain;
+       int notify = 0;
++      bool dom_valid;
+ 
+  again:
+       list_for_each_entry(domain, &domains, list) {
+-              if (xc_domain_getinfo(*xc_handle, domain->domid, 1,
+-                                    &dominfo) == 1 &&
+-                  dominfo.domid == domain->domid) {
++              dom_valid = get_domain_info(domain->domid, &dominfo);
++              if (!domain->introduced) {
++                      if (!dom_valid) {
++                              talloc_free(domain);
++                              goto again;
++                      }
++                      continue;
++              }
++              if (dom_valid) {
+                       if ((dominfo.crashed || dominfo.shutdown)
+                           && !domain->shutdown) {
+-                              domain->shutdown = 1;
++                              domain->shutdown = true;
+                               notify = 1;
+                       }
+                       if (!dominfo.dying)
+@@ -301,58 +323,84 @@ static char *talloc_domain_path(void *context, unsigned int domid)
+       return talloc_asprintf(context, "/local/domain/%u", domid);
+ }
+ 
+-static struct domain *new_domain(void *context, unsigned int domid,
+-                               int port)
++static struct domain *find_domain_struct(unsigned int domid)
++{
++      struct domain *i;
++
++      list_for_each_entry(i, &domains, list) {
++              if (i->domid == domid)
++                      return i;
++      }
++      return NULL;
++}
++
++static struct domain *alloc_domain(void *context, unsigned int domid)
+ {
+       struct domain *domain;
+-      int rc;
+ 
+       domain = talloc(context, struct domain);
+-      if (!domain)
++      if (!domain) {
++              errno = ENOMEM;
+               return NULL;
++      }
+ 
+-      domain->port = 0;
+-      domain->shutdown = 0;
+       domain->domid = domid;
+-      domain->path = talloc_domain_path(domain, domid);
+-      if (!domain->path)
+-              return NULL;
++      domain->generation = generation;
++      domain->introduced = false;
+ 
+-      wrl_domain_new(domain);
++      talloc_set_destructor(domain, destroy_domain);
+ 
+       list_add(&domain->list, &domains);
+-      talloc_set_destructor(domain, destroy_domain);
++
++      return domain;
++}
++
++static int new_domain(struct domain *domain, int port)
++{
++      int rc;
++
++      domain->port = 0;
++      domain->shutdown = false;
++      domain->path = talloc_domain_path(domain, domain->domid);
++      if (!domain->path) {
++              errno = ENOMEM;
++              return errno;
++      }
++
++      wrl_domain_new(domain);
+ 
+       /* Tell kernel we're interested in this event. */
+-      rc = xenevtchn_bind_interdomain(xce_handle, domid, port);
++      rc = xenevtchn_bind_interdomain(xce_handle, domain->domid, port);
+       if (rc == -1)
+-          return NULL;
++              return errno;
+       domain->port = rc;
+ 
++      domain->introduced = true;
++
+       domain->conn = new_connection(writechn, readchn);
+-      if (!domain->conn)
+-              return NULL;
++      if (!domain->conn)  {
++              errno = ENOMEM;
++              return errno;
++      }
+ 
+       domain->conn->domain = domain;
+-      domain->conn->id = domid;
++      domain->conn->id = domain->domid;
+ 
+       domain->remote_port = port;
+       domain->nbentry = 0;
+       domain->nbwatch = 0;
+ 
+-      return domain;
++      return 0;
+ }
+ 
+ 
+ static struct domain *find_domain_by_domid(unsigned int domid)
+ {
+-      struct domain *i;
++      struct domain *d;
+ 
+-      list_for_each_entry(i, &domains, list) {
+-              if (i->domid == domid)
+-                      return i;
+-      }
+-      return NULL;
++      d = find_domain_struct(domid);
++
++      return (d && d->introduced) ? d : NULL;
+ }
+ 
+ static void domain_conn_reset(struct domain *domain)
+@@ -399,15 +447,21 @@ int do_introduce(struct connection *conn, struct buffered_data *in)
+       if (port <= 0)
+               return EINVAL;
+ 
+-      domain = find_domain_by_domid(domid);
++      domain = find_domain_struct(domid);
+ 
+       if (domain == NULL) {
++              /* Hang domain off "in" until we're finished. */
++              domain = alloc_domain(in, domid);
++              if (domain == NULL)
++                      return ENOMEM;
++      }
++
++      if (!domain->introduced) {
+               interface = map_interface(domid, mfn);
+               if (!interface)
+                       return errno;
+               /* Hang domain off "in" until we're finished. */
+-              domain = new_domain(in, domid, port);
+-              if (!domain) {
++              if (new_domain(domain, port)) {
+                       rc = errno;
+                       unmap_interface(interface);
+                       return rc;
+@@ -518,8 +572,8 @@ int do_resume(struct connection *conn, struct buffered_data *in)
+       if (IS_ERR(domain))
+               return -PTR_ERR(domain);
+ 
+-      domain->shutdown = 0;
+-      
++      domain->shutdown = false;
++
+       send_ack(conn, XS_RESUME);
+ 
+       return 0;
+@@ -662,8 +716,10 @@ static int dom0_init(void)
+       if (port == -1)
+               return -1;
+ 
+-      dom0 = new_domain(NULL, xenbus_master_domid(), port);
+-      if (dom0 == NULL)
++      dom0 = alloc_domain(NULL, xenbus_master_domid());
++      if (!dom0)
++              return -1;
++      if (new_domain(dom0, port))
+               return -1;
+ 
+       dom0->interface = xenbus_map();
+@@ -744,6 +800,66 @@ void domain_entry_inc(struct connection *conn, struct node *node)
+       }
+ }
+ 
++/*
++ * Check whether a domain was created before or after a specific generation
++ * count (used for testing whether a node permission is older than a domain).
++ *
++ * Return values:
++ * -1: error
++ *  0: domain has higher generation count (it is younger than a node with the
++ *     given count), or domain isn't existing any longer
++ *  1: domain is older than the node
++ */
++static int chk_domain_generation(unsigned int domid, uint64_t gen)
++{
++      struct domain *d;
++      xc_dominfo_t dominfo;
++
++      if (!xc_handle && domid == 0)
++              return 1;
++
++      d = find_domain_struct(domid);
++      if (d)
++              return (d->generation <= gen) ? 1 : 0;
++
++      if (!get_domain_info(domid, &dominfo))
++              return 0;
++
++      d = alloc_domain(NULL, domid);
++      return d ? 1 : -1;
++}
++
++/*
++ * Remove permissions for no longer existing domains in order to avoid a new
++ * domain with the same domid inheriting the permissions.
++ */
++int domain_adjust_node_perms(struct node *node)
++{
++      unsigned int i;
++      int ret;
++
++      ret = chk_domain_generation(node->perms.p[0].id, node->generation);
++      if (ret < 0)
++              return errno;
++
++      /* If the owner doesn't exist any longer give it to priv domain. */
++      if (!ret)
++              node->perms.p[0].id = priv_domid;
++
++      for (i = 1; i < node->perms.num; i++) {
++              if (node->perms.p[i].perms & XS_PERM_IGNORE)
++                      continue;
++              ret = chk_domain_generation(node->perms.p[i].id,
++                                          node->generation);
++              if (ret < 0)
++                      return errno;
++              if (!ret)
++                      node->perms.p[i].perms |= XS_PERM_IGNORE;
++      }
++
++      return 0;
++}
++
+ void domain_entry_dec(struct connection *conn, struct node *node)
+ {
+       struct domain *d;
+diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
+index 259183962a9c..5e00087206c7 100644
+--- tools/xenstore/xenstored_domain.h.orig
++++ tools/xenstore/xenstored_domain.h
+@@ -56,6 +56,9 @@ bool domain_can_write(struct connection *conn);
+ 
+ bool domain_is_unprivileged(struct connection *conn);
+ 
++/* Remove node permissions for no longer existing domains. */
++int domain_adjust_node_perms(struct node *node);
++
+ /* Quota manipulation */
+ void domain_entry_inc(struct connection *conn, struct node *);
+ void domain_entry_dec(struct connection *conn, struct node *);
+diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
+index 36793b9b1af3..9fcb4c9ba986 100644
+--- tools/xenstore/xenstored_transaction.c.orig
++++ tools/xenstore/xenstored_transaction.c
+@@ -47,7 +47,12 @@
+  * transaction.
+  * Each time the global generation count is copied to either a node or a
+  * transaction it is incremented. This ensures all nodes and/or transactions
+- * are having a unique generation count.
++ * are having a unique generation count. The increment is done _before_ the
++ * copy as that is needed for checking whether a domain was created before
++ * or after a node has been written (the domain's generation is set with the
++ * actual generation count without incrementing it, in order to support
++ * writing a node for a domain before the domain has been officially
++ * introduced).
+  *
+  * Transaction conflicts are detected by checking the generation count of all
+  * nodes read in the transaction to match with the generation count in the
+@@ -161,7 +166,7 @@ struct transaction
+ };
+ 
+ extern int quota_max_transaction;
+-static uint64_t generation;
++uint64_t generation;
+ 
+ static void set_tdb_key(const char *name, TDB_DATA *key)
+ {
+@@ -237,7 +242,7 @@ int access_node(struct connection *conn, struct node *node,
+       bool introduce = false;
+ 
+       if (type != NODE_ACCESS_READ) {
+-              node->generation = generation++;
++              node->generation = ++generation;
+               if (conn && !conn->transaction)
+                       wrl_apply_debit_direct(conn);
+       }
+@@ -374,7 +379,7 @@ static int finalize_transaction(struct connection *conn,
+                               if (!data.dptr)
+                                       goto err;
+                               hdr = (void *)data.dptr;
+-                              hdr->generation = generation++;
++                              hdr->generation = ++generation;
+                               ret = tdb_store(tdb_ctx, key, data,
+                                               TDB_REPLACE);
+                               talloc_free(data.dptr);
+@@ -462,7 +467,7 @@ int do_transaction_start(struct connection *conn, struct buffered_data *in)
+       INIT_LIST_HEAD(&trans->accessed);
+       INIT_LIST_HEAD(&trans->changed_domains);
+       trans->fail = false;
+-      trans->generation = generation++;
++      trans->generation = ++generation;
+ 
+       /* Pick an unused transaction identifier. */
+       do {
+diff --git a/tools/xenstore/xenstored_transaction.h b/tools/xenstore/xenstored_transaction.h
+index 3386bac56508..43a162bea3f3 100644
+--- tools/xenstore/xenstored_transaction.h.orig
++++ tools/xenstore/xenstored_transaction.h
+@@ -27,6 +27,8 @@ enum node_access_type {
+ 
+ struct transaction;
+ 
++extern uint64_t generation;
++
+ int do_transaction_start(struct connection *conn, struct buffered_data *node);
+ int do_transaction_end(struct connection *conn, struct buffered_data *in);
+ 
+diff --git a/tools/xenstore/xs_lib.c b/tools/xenstore/xs_lib.c
+index 3e43f8809d42..d407d5713aff 100644
+--- tools/xenstore/xs_lib.c.orig
++++ tools/xenstore/xs_lib.c
+@@ -152,7 +152,7 @@ bool xs_strings_to_perms(struct xs_permissions *perms, unsigned int num,
+ bool xs_perm_to_string(const struct xs_permissions *perm,
+                        char *buffer, size_t buf_len)
+ {
+-      switch ((int)perm->perms) {
++      switch ((int)perm->perms & ~XS_PERM_IGNORE) {
+       case XS_PERM_WRITE:
+               *buffer = 'w';
+               break;
+-- 
+2.17.1
+
Index: pkgsrc/sysutils/xentools411/patches/patch-XSA322-o
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA322-o:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA322-o  Thu Dec 17 16:48:12 2020
@@ -0,0 +1,112 @@
+$NetBSD: patch-XSA322-o,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: clean up permissions for dead domains
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+domain ids are prone to wrapping (15-bits), and with sufficient number
+of VMs in a reboot loop it is possible to trigger it.  Xenstore entries
+may linger after a domain dies, until a toolstack cleans it up. During
+this time there is a window where a wrapped domid could access these
+xenstore keys (that belonged to another VM).
+
+To prevent this do a cleanup when a domain dies:
+ * walk the entire xenstore tree and update permissions for all nodes
+   * if the dead domain had an ACL entry: remove it
+   * if the dead domain was the owner: change the owner to Dom0
+
+This is done without quota checks or a transaction. Quota checks would
+be a no-op (either the domain is dead, or it is Dom0 where they are not
+enforced).  Transactions are not needed, because this is all done
+atomically by oxenstored's single thread.
+
+The xenstore entries owned by the dead domain are not deleted, because
+that could confuse a toolstack / backends that are still bound to it
+(or generate unexpected watch events). It is the responsibility of a
+toolstack to remove the xenstore entries themselves.
+
+This is part of XSA-322.
+
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/perms.ml b/tools/ocaml/xenstored/perms.ml
+index ee7fee6bda..e8a16221f8 100644
+--- tools/ocaml/xenstored/perms.ml.orig
++++ tools/ocaml/xenstored/perms.ml
+@@ -58,6 +58,15 @@ let get_other perms = perms.other
+ let get_acl perms = perms.acl
+ let get_owner perm = perm.owner
+ 
++(** [remote_domid ~domid perm] removes all ACLs for [domid] from perm.
++* If [domid] was the owner then it is changed to Dom0.
++* This is used for cleaning up after dead domains.
++* *)
++let remove_domid ~domid perm =
++      let acl = List.filter (fun (acl_domid, _) -> acl_domid <> domid) perm.acl in
++      let owner = if perm.owner = domid then 0 else perm.owner in
++      { perm with acl; owner }
++
+ let default0 = create 0 NONE []
+ 
+ let perm_of_string s =
+diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
+index 3cd0097db9..6a998f8764 100644
+--- tools/ocaml/xenstored/process.ml.orig
++++ tools/ocaml/xenstored/process.ml
+@@ -437,6 +437,7 @@ let do_release con t domains cons data =
+       let fire_spec_watches = Domains.exist domains domid in
+       Domains.del domains domid;
+       Connections.del_domain cons domid;
++      Store.reset_permissions (Transaction.get_store t) domid;
+       if fire_spec_watches 
+       then Connections.fire_spec_watches (Transaction.get_root t) cons Store.Path.release_domain
+       else raise Invalid_Cmd_Args
+diff --git a/tools/ocaml/xenstored/store.ml b/tools/ocaml/xenstored/store.ml
+index 0ce6f68e8d..101c094715 100644
+--- tools/ocaml/xenstored/store.ml.orig
++++ tools/ocaml/xenstored/store.ml
+@@ -89,6 +89,13 @@ let check_owner node connection =
+ 
+ let rec recurse fct node = fct node; List.iter (recurse fct) node.children
+ 
++(** [recurse_map f tree] applies [f] on each node in the tree recursively *)
++let recurse_map f =
++      let rec walk node =
++              f { node with children = List.rev_map walk node.children |> List.rev }
++      in
++      walk
++
+ let unpack node = (Symbol.to_string node.name, node.perms, node.value)
+ 
+ end
+@@ -405,6 +412,15 @@ let setperms store perm path nperms =
+               Quota.del_entry store.quota old_owner;
+               Quota.add_entry store.quota new_owner
+ 
++let reset_permissions store domid =
++      Logging.info "store|node" "Cleaning up xenstore ACLs for domid %d" domid;
++      store.root <- Node.recurse_map (fun node ->
++              let perms = Perms.Node.remove_domid ~domid node.perms in
++              if perms <> node.perms then
++                      Logging.debug "store|node" "Changed permissions for node %s" (Node.get_name node);
++              { node with perms }
++      ) store.root
++
+ type ops = {
+       store: t;
+       write: Path.t -> string -> unit;
+diff --git a/tools/ocaml/xenstored/xenstored.ml b/tools/ocaml/xenstored/xenstored.ml
+index 30fc874327..183dd2754b 100644
+--- tools/ocaml/xenstored/xenstored.ml.orig
++++ tools/ocaml/xenstored/xenstored.ml
+@@ -340,6 +340,7 @@ let _ =
+                       finally (fun () ->
+                               if Some port = eventchn.Event.virq_port then (
+                                       let (notify, deaddom) = Domains.cleanup domains in
++                                      List.iter (Store.reset_permissions store) deaddom;
+                                       List.iter (Connections.del_domain cons) deaddom;
+                                       if deaddom <> [] || notify then
+                                               Connections.fire_spec_watches
Index: pkgsrc/sysutils/xentools411/patches/patch-XSA323
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA323:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA323    Thu Dec 17 16:48:12 2020
@@ -0,0 +1,142 @@
+$NetBSD: patch-XSA323,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: Fix path length validation
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Currently, oxenstored checks the length of paths against 1024, then
+prepends "/local/domain/$DOMID/" to relative paths.  This allows a domU
+to create paths which can't subsequently be read by anyone, even dom0.
+This also interferes with listing directories, etc.
+
+Define a new oxenstored.conf entry: quota-path-max, defaulting to 1024
+as before.  For paths that begin with "/local/domain/$DOMID/" check the
+relative path length against this quota. For all other paths check the
+entire path length.
+
+This ensures that if the domid changes (and thus the length of a prefix
+changes) a path that used to be valid stays valid (e.g. after a
+live-migration).  It also ensures that regardless how the client tries
+to access a path (domid-relative or absolute) it will get consistent
+results, since the limit is always applied on the final canonicalized
+path.
+
+Delete the unused Domain.get_path to avoid it being confused with
+Connection.get_path (which differs by a trailing slash only).
+
+Rewrite Util.path_validate to apply the appropriate length restriction
+based on whether the path is relative or not.  Remove the check for
+connection_path being absolute, because it is not guest controlled data.
+
+This is part of XSA-323.
+
+Signed-off-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+
+diff --git a/tools/ocaml/libs/xb/partial.ml b/tools/ocaml/libs/xb/partial.ml
+index d4d1c7bdec..b6e2a716e2 100644
+--- tools/ocaml/libs/xb/partial.ml.orig
++++ tools/ocaml/libs/xb/partial.ml
+@@ -28,6 +28,7 @@ external header_of_string_internal: string -> int * int * int * int
+          = "stub_header_of_string"
+ 
+ let xenstore_payload_max = 4096 (* xen/include/public/io/xs_wire.h *)
++let xenstore_rel_path_max = 2048 (* xen/include/public/io/xs_wire.h *)
+ 
+ let of_string s =
+       let tid, rid, opint, dlen = header_of_string_internal s in
+diff --git a/tools/ocaml/libs/xb/partial.mli b/tools/ocaml/libs/xb/partial.mli
+index 359a75e88d..b9216018f5 100644
+--- tools/ocaml/libs/xb/partial.mli.orig
++++ tools/ocaml/libs/xb/partial.mli
+@@ -9,6 +9,7 @@ external header_size : unit -> int = "stub_header_size"
+ external header_of_string_internal : string -> int * int * int * int
+   = "stub_header_of_string"
+ val xenstore_payload_max : int
++val xenstore_rel_path_max : int
+ val of_string : string -> pkt
+ val append : pkt -> string -> int -> unit
+ val to_complete : pkt -> int
+diff --git a/tools/ocaml/xenstored/define.ml b/tools/ocaml/xenstored/define.ml
+index ea9e1b7620..ebe18b8e31 100644
+--- tools/ocaml/xenstored/define.ml.orig
++++ tools/ocaml/xenstored/define.ml
+@@ -31,6 +31,8 @@ let conflict_rate_limit_is_aggregate = ref true
+ 
+ let domid_self = 0x7FF0
+ 
++let path_max = ref Xenbus.Partial.xenstore_rel_path_max
++
+ exception Not_a_directory of string
+ exception Not_a_value of string
+ exception Already_exist
+diff --git a/tools/ocaml/xenstored/domain.ml b/tools/ocaml/xenstored/domain.ml
+index aeb185ff7e..81cb59b8f1 100644
+--- tools/ocaml/xenstored/domain.ml.orig
++++ tools/ocaml/xenstored/domain.ml
+@@ -38,7 +38,6 @@ type t =
+ }
+ 
+ let is_dom0 d = d.id = 0
+-let get_path dom = "/local/domain/" ^ (sprintf "%u" dom.id)
+ let get_id domain = domain.id
+ let get_interface d = d.interface
+ let get_mfn d = d.mfn
+diff --git a/tools/ocaml/xenstored/oxenstored.conf.in b/tools/ocaml/xenstored/oxenstored.conf.in
+index f843482981..4ae48e42d4 100644
+--- tools/ocaml/xenstored/oxenstored.conf.in.orig
++++ tools/ocaml/xenstored/oxenstored.conf.in
+@@ -61,6 +61,7 @@ quota-maxsize = 2048
+ quota-maxwatch = 100
+ quota-transaction = 10
+ quota-maxrequests = 1024
++quota-path-max = 1024
+ 
+ # Activate filed base backend
+ persistent = false
+diff --git a/tools/ocaml/xenstored/utils.ml b/tools/ocaml/xenstored/utils.ml
+index e8c9fe4e94..eb79bf0146 100644
+--- tools/ocaml/xenstored/utils.ml.orig
++++ tools/ocaml/xenstored/utils.ml
+@@ -93,7 +93,7 @@ let read_file_single_integer filename =
+ let path_validate path connection_path =
+       let len = String.length path in
+ 
+-      if len = 0 || len > 1024 then raise Define.Invalid_path;
++      if len = 0 then raise Define.Invalid_path;
+ 
+       let abs_path =
+               match String.get path 0 with
+@@ -101,4 +101,17 @@ let path_validate path connection_path =
+               | _   -> connection_path ^ path
+       in
+ 
++      (* Regardless whether client specified absolute or relative path,
++         canonicalize it (above) and, for domain-relative paths, check the
++         length of the relative part.
++
++         This prevents paths becoming invalid across migrate when the length
++         of the domid changes in @param connection_path.
++       *)
++      let len = String.length abs_path in
++      let on_absolute _ _ = len in
++      let on_relative _ offset = len - offset in
++      let len = Scanf.ksscanf abs_path on_absolute "/local/domain/%d/%n" on_relative in
++      if len > !Define.path_max then raise Define.Invalid_path;
++
+       abs_path
+diff --git a/tools/ocaml/xenstored/xenstored.ml b/tools/ocaml/xenstored/xenstored.ml
+index ff9fbbbac2..39d6d767e4 100644
+--- tools/ocaml/xenstored/xenstored.ml.orig
++++ tools/ocaml/xenstored/xenstored.ml
+@@ -102,6 +102,7 @@ let parse_config filename =
+               ("quota-maxentity", Config.Set_int Quota.maxent);
+               ("quota-maxsize", Config.Set_int Quota.maxsize);
+               ("quota-maxrequests", Config.Set_int Define.maxrequests);
++              ("quota-path-max", Config.Set_int Define.path_max);
+               ("test-eagain", Config.Set_bool Transaction.test_eagain);
+               ("persistent", Config.Set_bool Disk.enable);
+               ("xenstored-log-file", Config.String Logging.set_xenstored_log_destination);
Index: pkgsrc/sysutils/xentools411/patches/patch-XSA324
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA324:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA324    Thu Dec 17 16:48:12 2020
@@ -0,0 +1,50 @@
+$NetBSD: patch-XSA324,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From: Juergen Gross <jgross%suse.com@localhost>
+Subject: tools/xenstore: drop watch event messages exceeding maximum size
+
+By setting a watch with a very large tag it is possible to trick
+xenstored to send watch event messages exceeding the maximum allowed
+payload size. This might in turn lead to a crash of xenstored as the
+resulting error can cause dereferencing a NULL pointer in case there
+is no active request being handled by the guest the watch event is
+being sent to.
+
+Fix that by just dropping such watch events. Additionally modify the
+error handling to test the pointer to be not NULL before dereferencing
+it.
+
+This is XSA-324.
+
+Signed-off-by: Juergen Gross <jgross%suse.com@localhost>
+Acked-by: Julien Grall <jgrall%amazon.com@localhost>
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index 33f95dcf3c..3d74dbbb40 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -674,6 +674,9 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
+       /* Replies reuse the request buffer, events need a new one. */
+       if (type != XS_WATCH_EVENT) {
+               bdata = conn->in;
++              /* Drop asynchronous responses, e.g. errors for watch events. */
++              if (!bdata)
++                      return;
+               bdata->inhdr = true;
+               bdata->used = 0;
+               conn->in = NULL;
+diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
+index 71c108ea99..9ff20690c0 100644
+--- tools/xenstore/xenstored_watch.c.orig
++++ tools/xenstore/xenstored_watch.c
+@@ -92,6 +92,10 @@ static void add_event(struct connection *conn,
+       }
+ 
+       len = strlen(name) + 1 + strlen(watch->token) + 1;
++      /* Don't try to send over-long events. */
++      if (len > XENSTORE_PAYLOAD_MAX)
++              return;
++
+       data = talloc_array(ctx, char, len);
+       if (!data)
+               return;
Index: pkgsrc/sysutils/xentools411/patches/patch-XSA325
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA325:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA325    Thu Dec 17 16:48:12 2020
@@ -0,0 +1,194 @@
+$NetBSD: patch-XSA325,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From: Harsha Shamsundara Havanur <havanur%amazon.com@localhost>
+Subject: tools/xenstore: Preserve bad client until they are destroyed
+
+XenStored will kill any connection that it thinks has misbehaved,
+this is currently happening in two places:
+ * In `handle_input()` if the sanity check on the ring and the message
+   fails.
+ * In `handle_output()` when failing to write the response in the ring.
+
+As the domain structure is a child of the connection, XenStored will
+destroy its view of the domain when killing the connection. This will
+result in sending @releaseDomain event to all the watchers.
+
+As the watch event doesn't carry which domain has been released,
+the watcher (such as XenStored) will generally go through the list of
+domains registers and check if one of them is shutting down/dying.
+In the case of a client misbehaving, the domain will likely to be
+running, so no action will be performed.
+
+When the domain is effectively destroyed, XenStored will not be aware of
+the domain anymore. So the watch event is not going to be sent.
+By consequence, the watchers of the event will not release mappings
+they may have on the domain. This will result in a zombie domain.
+
+In order to send @releaseDomain event at the correct time, we want
+to keep the domain structure until the domain is effectively
+shutting-down/dying.
+
+We also want to keep the connection around so we could possibly revive
+the connection in the future.
+
+A new flag 'is_ignored' is added to mark whether a connection should be
+ignored when checking if there are work to do. Additionally any
+transactions, watches, buffers associated to the connection will be
+freed as you can't do much with them (restarting the connection will
+likely need a reset).
+
+As a side note, when the device model were running in a stubdomain, a
+guest would have been able to introduce a use-after-free because there
+is two parents for a guest connection.
+
+This is XSA-325.
+
+Reported-by: Pawel Wieczorkiewicz <wipawel%amazon.de@localhost>
+Signed-off-by: Harsha Shamsundara Havanur <havanur%amazon.com@localhost>
+Signed-off-by: Julien Grall <jgrall%amazon.com@localhost>
+Reviewed-by: Juergen Gross <jgross%suse.com@localhost>
+Reviewed-by: Paul Durrant <paul%xen.org@localhost>
+
+diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
+index af3d17004b3f..27d8f15b6b76 100644
+--- tools/xenstore/xenstored_core.c.orig
++++ tools/xenstore/xenstored_core.c
+@@ -1355,6 +1355,32 @@ static struct {
+       [XS_DIRECTORY_PART]    = { "DIRECTORY_PART",    send_directory_part },
+ };
+ 
++/*
++ * Keep the connection alive but stop processing any new request or sending
++ * reponse. This is to allow sending @releaseDomain watch event at the correct
++ * moment and/or to allow the connection to restart (not yet implemented).
++ *
++ * All watches, transactions, buffers will be freed.
++ */
++static void ignore_connection(struct connection *conn)
++{
++      struct buffered_data *out, *tmp;
++
++      trace("CONN %p ignored\n", conn);
++
++      conn->is_ignored = true;
++      conn_delete_all_watches(conn);
++      conn_delete_all_transactions(conn);
++
++      list_for_each_entry_safe(out, tmp, &conn->out_list, list) {
++              list_del(&out->list);
++              talloc_free(out);
++      }
++
++      talloc_free(conn->in);
++      conn->in = NULL;
++}
++
+ static const char *sockmsg_string(enum xsd_sockmsg_type type)
+ {
+       if ((unsigned int)type < ARRAY_SIZE(wire_funcs) && wire_funcs[type].str)
+@@ -1413,8 +1439,10 @@ static void consider_message(struct connection *conn)
+       assert(conn->in == NULL);
+ }
+ 
+-/* Errors in reading or allocating here mean we get out of sync, so we
+- * drop the whole client connection. */
++/*
++ * Errors in reading or allocating here means we get out of sync, so we mark
++ * the connection as ignored.
++ */
+ static void handle_input(struct connection *conn)
+ {
+       int bytes;
+@@ -1471,14 +1499,14 @@ static void handle_input(struct connection *conn)
+       return;
+ 
+ bad_client:
+-      /* Kill it. */
+-      talloc_free(conn);
++      ignore_connection(conn);
+ }
+ 
+ static void handle_output(struct connection *conn)
+ {
++      /* Ignore the connection if an error occured */
+       if (!write_messages(conn))
+-              talloc_free(conn);
++              ignore_connection(conn);
+ }
+ 
+ struct connection *new_connection(connwritefn_t *write, connreadfn_t *read)
+@@ -1494,6 +1522,7 @@ struct connection *new_connection(connwritefn_t *write, connreadfn_t *read)
+       new->write = write;
+       new->read = read;
+       new->can_write = true;
++      new->is_ignored = false;
+       new->transaction_started = 0;
+       INIT_LIST_HEAD(&new->out_list);
+       INIT_LIST_HEAD(&new->watches);
+@@ -2186,8 +2215,9 @@ int main(int argc, char *argv[])
+                                       if (fds[conn->pollfd_idx].revents
+                                           & ~(POLLIN|POLLOUT))
+                                               talloc_free(conn);
+-                                      else if (fds[conn->pollfd_idx].revents
+-                                               & POLLIN)
++                                      else if ((fds[conn->pollfd_idx].revents
++                                                & POLLIN) &&
++                                               !conn->is_ignored)
+                                               handle_input(conn);
+                               }
+                               if (talloc_free(conn) == 0)
+@@ -2199,8 +2229,9 @@ int main(int argc, char *argv[])
+                                       if (fds[conn->pollfd_idx].revents
+                                           & ~(POLLIN|POLLOUT))
+                                               talloc_free(conn);
+-                                      else if (fds[conn->pollfd_idx].revents
+-                                               & POLLOUT)
++                                      else if ((fds[conn->pollfd_idx].revents
++                                                & POLLOUT) &&
++                                               !conn->is_ignored)
+                                               handle_output(conn);
+                               }
+                               if (talloc_free(conn) == 0)
+diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
+index eb19b71f5f46..196a6fd2b0be 100644
+--- tools/xenstore/xenstored_core.h.orig
++++ tools/xenstore/xenstored_core.h
+@@ -80,6 +80,9 @@ struct connection
+       /* Is this a read-only connection? */
+       bool can_write;
+ 
++      /* Is this connection ignored? */
++      bool is_ignored;
++
+       /* Buffered incoming data. */
+       struct buffered_data *in;
+ 
+diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
+index dc635e9be30c..d5e1e3e9d42d 100644
+--- tools/xenstore/xenstored_domain.c.orig
++++ tools/xenstore/xenstored_domain.c
+@@ -286,6 +286,10 @@ bool domain_can_read(struct connection *conn)
+ 
+       if (domain_is_unprivileged(conn) && conn->domain->wrl_credit < 0)
+               return false;
++
++      if (conn->is_ignored)
++              return false;
++
+       return (intf->req_cons != intf->req_prod);
+ }
+ 
+@@ -303,6 +307,10 @@ bool domain_is_unprivileged(struct connection *conn)
+ bool domain_can_write(struct connection *conn)
+ {
+       struct xenstore_domain_interface *intf = conn->domain->interface;
++
++      if (conn->is_ignored)
++              return false;
++
+       return ((intf->rsp_prod - intf->rsp_cons) != XENSTORE_RING_SIZE);
+ }
+ 
+-- 
+2.17.1
+
Index: pkgsrc/sysutils/xentools411/patches/patch-XSA330
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA330:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA330    Thu Dec 17 16:48:12 2020
@@ -0,0 +1,68 @@
+$NetBSD: patch-XSA330,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: delete watch from trie too when resetting
+ watches
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+c/s f8c72b526129 "oxenstored: implement XS_RESET_WATCHES" from Xen 4.6
+introduced reset watches support in oxenstored by mirroring the change
+in cxenstored.
+
+However the OCaml version has some additional data structures to
+optimize watch firing, and just resetting the watches in one of the data
+structures creates a security bug where a malicious guest kernel can
+exceed its watch quota, driving oxenstored into OOM:
+ * create watches
+ * reset watches (this still keeps the watches lingering in another data
+   structure, using memory)
+ * create some more watches
+ * loop until oxenstored dies
+
+The guest kernel doesn't necessarily have to be malicious to trigger
+this:
+ * if control/platform-feature-xs_reset_watches is set
+ * the guest kexecs (e.g. because it crashes)
+ * on boot more watches are set up
+ * this will slowly "leak" memory for watches in oxenstored, driving it
+   towards OOM.
+
+This is XSA-330.
+
+Fixes: f8c72b526129 ("oxenstored: implement XS_RESET_WATCHES")
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+Reviewed-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/connections.ml b/tools/ocaml/xenstored/connections.ml
+index 020b875dcd..4e69de1d42 100644
+--- tools/ocaml/xenstored/connections.ml.orig
++++ tools/ocaml/xenstored/connections.ml
+@@ -134,6 +134,10 @@ let del_watch cons con path token =
+               cons.watches <- Trie.set cons.watches key watches;
+       watch
+ 
++let del_watches cons con =
++      Connection.del_watches con;
++      cons.watches <- Trie.map (del_watches_of_con con) cons.watches
++
+ (* path is absolute *)
+ let fire_watches ?oldroot root cons path recurse =
+       let key = key_of_path path in
+diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
+index 6a998f8764..12ad66fce6 100644
+--- tools/ocaml/xenstored/process.ml.orig
++++ tools/ocaml/xenstored/process.ml
+@@ -179,8 +179,8 @@ let do_isintroduced con _t domains _cons data =
+       if domid = Define.domid_self || Domains.exist domains domid then "T\000" else "F\000"
+ 
+ (* only in xen >= 4.2 *)
+-let do_reset_watches con t domains cons data =
+-  Connection.del_watches con;
++let do_reset_watches con _t _domains cons _data =
++  Connections.del_watches cons con;
+   Connection.del_transactions con
+ 
+ (* only in >= xen3.3                                                                                    *)
Index: pkgsrc/sysutils/xentools411/patches/patch-XSA352
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA352:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA352    Thu Dec 17 16:48:12 2020
@@ -0,0 +1,44 @@
+$NetBSD: patch-XSA352,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: only Dom0 can change node owner
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Otherwise we can give quota away to another domain, either causing it to run
+out of quota, or in case of Dom0 use unbounded amounts of memory and bypass
+the quota system entirely.
+
+This was fixed in the C version of xenstored in 2006 (c/s db34d2aaa5f5,
+predating the XSA process by 5 years).
+
+It was also fixed in the mirage version of xenstore in 2012, with a unit test
+demonstrating the vulnerability:
+
+  https://github.com/mirage/ocaml-xenstore/commit/6b91f3ac46b885d0530a51d57a9b3a57d64923a7
+  https://github.com/mirage/ocaml-xenstore/commit/22ee5417c90b8fda905c38de0d534506152eace6
+
+but possibly without realising that the vulnerability still affected the
+in-tree oxenstored (added c/s f44af660412 in 2010).
+
+This is XSA-352.
+
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+Reviewed-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/store.ml b/tools/ocaml/xenstored/store.ml
+index 3b05128f1b..5f915f2bbe 100644
+--- tools/ocaml/xenstored/store.ml.orig
++++ tools/ocaml/xenstored/store.ml
+@@ -407,7 +407,8 @@ let setperms store perm path nperms =
+       | Some node ->
+               let old_owner = Node.get_owner node in
+               let new_owner = Perms.Node.get_owner nperms in
+-              if not ((old_owner = new_owner) || (Perms.Connection.is_dom0 perm)) then Quota.check store.quota new_owner 0;
++              if not ((old_owner = new_owner) || (Perms.Connection.is_dom0 perm)) then
++                      raise Define.Permission_denied;
+               store.root <- path_setperms store perm path nperms;
+               Quota.del_entry store.quota old_owner;
+               Quota.add_entry store.quota new_owner
Index: pkgsrc/sysutils/xentools411/patches/patch-XSA353
diff -u /dev/null pkgsrc/sysutils/xentools411/patches/patch-XSA353:1.1
--- /dev/null   Thu Dec 17 16:48:12 2020
+++ pkgsrc/sysutils/xentools411/patches/patch-XSA353    Thu Dec 17 16:48:12 2020
@@ -0,0 +1,91 @@
+$NetBSD: patch-XSA353,v 1.1 2020/12/17 16:48:12 bouyer Exp $
+
+From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok%citrix.com@localhost>
+Subject: tools/ocaml/xenstored: do permission checks on xenstore root
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+This was lacking in a disappointing number of places.
+
+The xenstore root node is treated differently from all other nodes, because it
+doesn't have a parent, and mutation requires changing the parent.
+
+Unfortunately this lead to open-coding the special case for root into every
+single xenstore operation, and out of all the xenstore operations only read
+did a permission check when handling the root node.
+
+This means that an unprivileged guest can:
+
+ * xenstore-chmod / to its liking and subsequently write new arbitrary nodes
+   there (subject to quota)
+ * xenstore-rm -r / deletes almost the entire xenstore tree (xenopsd quickly
+   refills some, but you are left with a broken system)
+ * DIRECTORY on / lists all children when called through python
+   bindings (xenstore-ls stops at /local because it tries to list recursively)
+ * get-perms on / works too, but that is just a minor information leak
+
+Add the missing permission checks, but this should really be refactored to do
+the root handling and permission checks on the node only once from a single
+function, instead of getting it wrong nearly everywhere.
+
+This is XSA-353.
+
+Signed-off-by: Edwin Török <edvin.torok%citrix.com@localhost>
+Acked-by: Christian Lindig <christian.lindig%citrix.com@localhost>
+Reviewed-by: Andrew Cooper <andrew.cooper3%citrix.com@localhost>
+
+diff --git a/tools/ocaml/xenstored/store.ml b/tools/ocaml/xenstored/store.ml
+index f299ec6461..92b6289b5e 100644
+--- tools/ocaml/xenstored/store.ml.orig
++++ tools/ocaml/xenstored/store.ml
+@@ -273,15 +273,17 @@ let path_rm store perm path =
+                       Node.del_childname node name
+               with Not_found ->
+                       raise Define.Doesnt_exist in
+-      if path = [] then
++      if path = [] then (
++              Node.check_perm store.root perm Perms.WRITE;
+               Node.del_all_children store.root
+-      else
++      ) else
+               Path.apply_modify store.root path do_rm
+ 
+ let path_setperms store perm path perms =
+-      if path = [] then
++      if path = [] then (
++              Node.check_perm store.root perm Perms.WRITE;
+               Node.set_perms store.root perms
+-      else
++      ) else
+               let do_setperms node name =
+                       let c = Node.find node name in
+                       Node.check_owner c perm;
+@@ -313,9 +315,10 @@ let read store perm path =
+ 
+ let ls store perm path =
+       let children =
+-              if path = [] then
+-                      (Node.get_children store.root)
+-              else
++              if path = [] then (
++                      Node.check_perm store.root perm Perms.READ;
++                      Node.get_children store.root
++              ) else
+                       let do_ls node name =
+                               let cnode = Node.find node name in
+                               Node.check_perm cnode perm Perms.READ;
+@@ -324,9 +327,10 @@ let ls store perm path =
+       List.rev (List.map (fun n -> Symbol.to_string n.Node.name) children)
+ 
+ let getperms store perm path =
+-      if path = [] then
+-              (Node.get_perms store.root)
+-      else
++      if path = [] then (
++              Node.check_perm store.root perm Perms.READ;
++              Node.get_perms store.root
++      ) else
+               let fct n name =
+                       let c = Node.find n name in
+                       Node.check_perm c perm Perms.READ;



Home | Main Index | Thread Index | Old Index