Thu Dec 17 16:48:12 2020 UTC ()
Add upstream patches for a bunch of Xen security avisories, related
to xenstore permissions.
Bump PKGREVISION


(bouyer)
diff -r1.23 -r1.24 pkgsrc/sysutils/xentools411/Makefile
diff -r1.14 -r1.15 pkgsrc/sysutils/xentools411/distinfo
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA115-c
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA115-o
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA322-c
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA322-o
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA323
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA324
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA325
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA330
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA352
diff -r0 -r1.1 pkgsrc/sysutils/xentools411/patches/patch-XSA353
diff -r1.1 -r1.2 pkgsrc/sysutils/xentools411/patches/patch-tools_ocaml_xenstored_utils.ml

cvs diff -r1.23 -r1.24 pkgsrc/sysutils/xentools411/Attic/Makefile (expand / switch to unified diff)

--- pkgsrc/sysutils/xentools411/Attic/Makefile 2020/08/31 18:11:37 1.23
+++ pkgsrc/sysutils/xentools411/Attic/Makefile 2020/12/17 16:48:12 1.24
@@ -1,17 +1,17 @@ @@ -1,17 +1,17 @@
1# $NetBSD: Makefile,v 1.23 2020/08/31 18:11:37 wiz Exp $ 1# $NetBSD: Makefile,v 1.24 2020/12/17 16:48:12 bouyer Exp $
2# 2#
3# VERSION is set in version.mk as it is shared with other packages 3# VERSION is set in version.mk as it is shared with other packages
4PKGREVISION= 1 4PKGREVISION= 2
5.include "version.mk" 5.include "version.mk"
6 6
7DIST_IPXE= ipxe-git-${VERSION_IPXE}.tar.gz 7DIST_IPXE= ipxe-git-${VERSION_IPXE}.tar.gz
8 8
9DIST_SUBDIR= xen411 9DIST_SUBDIR= xen411
10DISTNAME= xen-${VERSION} 10DISTNAME= xen-${VERSION}
11PKGNAME= xentools411-${VERSION} 11PKGNAME= xentools411-${VERSION}
12#PKGREVISION= 5 12#PKGREVISION= 5
13CATEGORIES= sysutils 13CATEGORIES= sysutils
14MASTER_SITES= https://downloads.xenproject.org/release/xen/${VERSION}/ 14MASTER_SITES= https://downloads.xenproject.org/release/xen/${VERSION}/
15 15
16DISTFILES= ${DISTNAME}.tar.gz 16DISTFILES= ${DISTNAME}.tar.gz
17 17

cvs diff -r1.14 -r1.15 pkgsrc/sysutils/xentools411/Attic/distinfo (expand / switch to unified diff)

--- pkgsrc/sysutils/xentools411/Attic/distinfo 2020/08/24 10:33:27 1.14
+++ pkgsrc/sysutils/xentools411/Attic/distinfo 2020/12/17 16:48:12 1.15
@@ -1,27 +1,37 @@ @@ -1,27 +1,37 @@
1$NetBSD: distinfo,v 1.14 2020/08/24 10:33:27 bouyer Exp $ 1$NetBSD: distinfo,v 1.15 2020/12/17 16:48:12 bouyer Exp $
2 2
3SHA1 (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = 272b8c904dc0127690eca2c5c20c67479e40da34 3SHA1 (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = 272b8c904dc0127690eca2c5c20c67479e40da34
4RMD160 (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = cfcb4a314c15da19b36132b27126f3bd9699d0e5 4RMD160 (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = cfcb4a314c15da19b36132b27126f3bd9699d0e5
5SHA512 (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = bbcce5e55040e7e29adebd4a5253a046016a6e2e7ff34cf801a42d147e1ec1af57e0297318249bfa9c5bbeac969fe4b37c18cbf845a80b2136d65387a4fc31da 5SHA512 (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = bbcce5e55040e7e29adebd4a5253a046016a6e2e7ff34cf801a42d147e1ec1af57e0297318249bfa9c5bbeac969fe4b37c18cbf845a80b2136d65387a4fc31da
6Size (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = 3732065 bytes 6Size (xen411/ipxe-git-356f6c1b64d7a97746d1816cef8ca22bdd8d0b5d.tar.gz) = 3732065 bytes
7SHA1 (xen411/xen-4.11.4.tar.gz) = 6c8cdf441621c14dc5345196b48df6982c060c4f 7SHA1 (xen411/xen-4.11.4.tar.gz) = 6c8cdf441621c14dc5345196b48df6982c060c4f
8RMD160 (xen411/xen-4.11.4.tar.gz) = 49819fcd1de3985d4dea370be962548c862f2933 8RMD160 (xen411/xen-4.11.4.tar.gz) = 49819fcd1de3985d4dea370be962548c862f2933
9SHA512 (xen411/xen-4.11.4.tar.gz) = 8383f0b369fa08c8ecfdd68f902a2aaad140146a183131c50c020fe04c2f1e829c219b9bd9923fa8f1c180e1e7c6e73d0d68b7015fc39fd3b7f59e55c680cedb 9SHA512 (xen411/xen-4.11.4.tar.gz) = 8383f0b369fa08c8ecfdd68f902a2aaad140146a183131c50c020fe04c2f1e829c219b9bd9923fa8f1c180e1e7c6e73d0d68b7015fc39fd3b7f59e55c680cedb
10Size (xen411/xen-4.11.4.tar.gz) = 25184564 bytes 10Size (xen411/xen-4.11.4.tar.gz) = 25184564 bytes
11SHA1 (patch-.._ipxe_src_core_settings.c) = 1eab2fbd8b22dde2b8aa830ae7701603486f74e4 11SHA1 (patch-.._ipxe_src_core_settings.c) = 1eab2fbd8b22dde2b8aa830ae7701603486f74e4
12SHA1 (patch-.._ipxe_src_net_fcels.c) = eda41b25c3d5f5bef33caa9a6af28c40cb91e66b 12SHA1 (patch-.._ipxe_src_net_fcels.c) = eda41b25c3d5f5bef33caa9a6af28c40cb91e66b
13SHA1 (patch-Config.mk) = c41005a60de2f94a72b0206030eb021c137653d3 13SHA1 (patch-Config.mk) = c41005a60de2f94a72b0206030eb021c137653d3
14SHA1 (patch-Makefile) = 2f3a5eafc5039b149c98dd5e59c39a3197fd9264 14SHA1 (patch-Makefile) = 2f3a5eafc5039b149c98dd5e59c39a3197fd9264
 15SHA1 (patch-XSA115-c) = 7e3216a23c522fc73f47fa6deef8918c4dce7fae
 16SHA1 (patch-XSA115-o) = 6dc292060441c388b9a05e31ddc37835568a3e86
 17SHA1 (patch-XSA322-c) = c48a10eeab29775b9c97a36848556120741a9c9d
 18SHA1 (patch-XSA322-o) = 943e2aee69ac278871925223478b11f6dfabc9d7
 19SHA1 (patch-XSA323) = 98055b0c05ed0d0f5ebbe23d429a68a71d92f20f
 20SHA1 (patch-XSA324) = a1cdb872a79fd7d9234030ec2765d0a474f72fbb
 21SHA1 (patch-XSA325) = 59c7fba006588db4accee1068072612777620ac3
 22SHA1 (patch-XSA330) = 03b4f1d9c14e11eaee5b863276d32cee0544e604
 23SHA1 (patch-XSA352) = 7c4479c029d9bbbf6578ee148cb926bb2d849789
 24SHA1 (patch-XSA353) = 6983aa18399dcf0ac1471ffdf7c27c1bc041f49c
15SHA1 (patch-always_inline) = 23201b2b63072e040630525416a0b61280492f93 25SHA1 (patch-always_inline) = 23201b2b63072e040630525416a0b61280492f93
16SHA1 (patch-docs_man_xl-disk-configuration.pod.5) = 03ff4c22dde1e1b60ab8750c8971ea057e479151 26SHA1 (patch-docs_man_xl-disk-configuration.pod.5) = 03ff4c22dde1e1b60ab8750c8971ea057e479151
17SHA1 (patch-docs_man_xl.cfg.pod.5.in) = 951915037a9975b76cc5c41a0e1abe0a202a3696 27SHA1 (patch-docs_man_xl.cfg.pod.5.in) = 951915037a9975b76cc5c41a0e1abe0a202a3696
18SHA1 (patch-docs_man_xl.conf.pod.5) = d77e3313750db315d540d7713c95cd54d6f02938 28SHA1 (patch-docs_man_xl.conf.pod.5) = d77e3313750db315d540d7713c95cd54d6f02938
19SHA1 (patch-docs_man_xl.pod.1.in) = 9b37ef724f2827bc05110e5456a8668257509cab 29SHA1 (patch-docs_man_xl.pod.1.in) = 9b37ef724f2827bc05110e5456a8668257509cab
20SHA1 (patch-docs_man_xlcpupool.cfg.pod.5) = 3f6db65d95b5fc607c2fa7e2fc975e0ddbfdd5e5 30SHA1 (patch-docs_man_xlcpupool.cfg.pod.5) = 3f6db65d95b5fc607c2fa7e2fc975e0ddbfdd5e5
21SHA1 (patch-docs_misc_block-scripts.txt) = 29a49edc4d15c4c8db822fd1d6356d07aeb27b86 31SHA1 (patch-docs_misc_block-scripts.txt) = 29a49edc4d15c4c8db822fd1d6356d07aeb27b86
22SHA1 (patch-extras_mini-os_Config.mk) = cb5cdb32f1b3c55abad702ab6768caf59d886ff2 32SHA1 (patch-extras_mini-os_Config.mk) = cb5cdb32f1b3c55abad702ab6768caf59d886ff2
23SHA1 (patch-extras_mini-os_arch_x86_arch.mk) = 8b4f1fe0e888f5b70408d2cc3a3968ce27eae5dc 33SHA1 (patch-extras_mini-os_arch_x86_arch.mk) = 8b4f1fe0e888f5b70408d2cc3a3968ce27eae5dc
24SHA1 (patch-extras_mini-os_include_fcntl.h) = 4ed18497227c8c327ee3db9d793caa4ac6254822 34SHA1 (patch-extras_mini-os_include_fcntl.h) = 4ed18497227c8c327ee3db9d793caa4ac6254822
25SHA1 (patch-extras_mini-os_include_time.h) = ab3b0794bf892ce6a036aa889c6852d65b508596 35SHA1 (patch-extras_mini-os_include_time.h) = ab3b0794bf892ce6a036aa889c6852d65b508596
26SHA1 (patch-extras_mini-os_lib_sys.c) = f6514ad743a206c0e81dcccd2586e8fe65bfd1b9 36SHA1 (patch-extras_mini-os_lib_sys.c) = f6514ad743a206c0e81dcccd2586e8fe65bfd1b9
27SHA1 (patch-extras_mini-os_lock.c) = e28753793dee483c1ffad8ea8ed2706353046b50 37SHA1 (patch-extras_mini-os_lock.c) = e28753793dee483c1ffad8ea8ed2706353046b50
@@ -46,40 +56,39 @@ SHA1 (patch-tools_libs_call_private.h) = @@ -46,40 +56,39 @@ SHA1 (patch-tools_libs_call_private.h) =
46SHA1 (patch-tools_libs_evtchn_netbsd.c) = 1abb0f128ab0702b606e22f199d297d1e9c0aae5 56SHA1 (patch-tools_libs_evtchn_netbsd.c) = 1abb0f128ab0702b606e22f199d297d1e9c0aae5
47SHA1 (patch-tools_libs_foreignmemory_compat.c) = f1404738a9fac23fbde3249f5f4f1d79497aac4d 57SHA1 (patch-tools_libs_foreignmemory_compat.c) = f1404738a9fac23fbde3249f5f4f1d79497aac4d
48SHA1 (patch-tools_libs_foreignmemory_netbsd.c) = dabf9cb5c0623119143abb54c5dd2aecfbd79b2b 58SHA1 (patch-tools_libs_foreignmemory_netbsd.c) = dabf9cb5c0623119143abb54c5dd2aecfbd79b2b
49SHA1 (patch-tools_libs_foreignmemory_private.h) = 146a3d789a8b97b7ece471c392ed998c628e5be8 59SHA1 (patch-tools_libs_foreignmemory_private.h) = 146a3d789a8b97b7ece471c392ed998c628e5be8
50SHA1 (patch-tools_libxl_libxl__create.c) = 09cd562f0839dda6f92d63d8fc8ed6de08dd82a8 60SHA1 (patch-tools_libxl_libxl__create.c) = 09cd562f0839dda6f92d63d8fc8ed6de08dd82a8
51SHA1 (patch-tools_libxl_libxl_dom.c) = 0c4a96523d13de65aaa71ef705c3579ee8d641f3 61SHA1 (patch-tools_libxl_libxl_dom.c) = 0c4a96523d13de65aaa71ef705c3579ee8d641f3
52SHA1 (patch-tools_libxl_libxl_event.c) = 8147122d03102eccc45c48a7d6ca1aa5d6286040 62SHA1 (patch-tools_libxl_libxl_event.c) = 8147122d03102eccc45c48a7d6ca1aa5d6286040
53SHA1 (patch-tools_libxl_libxl_internal.h) = 613edc0fa055b23f5dc2a2448456d3d6005337fe 63SHA1 (patch-tools_libxl_libxl_internal.h) = 613edc0fa055b23f5dc2a2448456d3d6005337fe
54SHA1 (patch-tools_libxl_libxl_uuid.c) = 1eb8c027dbeb7e67df7475ee6e4d87220140c65b 64SHA1 (patch-tools_libxl_libxl_uuid.c) = 1eb8c027dbeb7e67df7475ee6e4d87220140c65b
55SHA1 (patch-tools_ocaml_common.make) = 4b845bdf3a013852109749ee18dfe28e3440d951 65SHA1 (patch-tools_ocaml_common.make) = 4b845bdf3a013852109749ee18dfe28e3440d951
56SHA1 (patch-tools_ocaml_libs_xentoollog_xentoollog__stubs.c) = adee03d87168e735cb0d42ce06d0c31a14315b8d 66SHA1 (patch-tools_ocaml_libs_xentoollog_xentoollog__stubs.c) = adee03d87168e735cb0d42ce06d0c31a14315b8d
57SHA1 (patch-tools_ocaml_libs_xl__xenlight_stubs.c) = cc612908524670f650a294af133a5912f955f39e 67SHA1 (patch-tools_ocaml_libs_xl__xenlight_stubs.c) = cc612908524670f650a294af133a5912f955f39e
58SHA1 (patch-tools_ocaml_xenstored_Makefile) = b267702cf4090c7b45bba530e60327fced24e3e5 68SHA1 (patch-tools_ocaml_xenstored_Makefile) = b267702cf4090c7b45bba530e60327fced24e3e5
59SHA1 (patch-tools_ocaml_xenstored_utils.ml) = fd951de732d6c31cae89bd4b58c5650108578d79 69SHA1 (patch-tools_ocaml_xenstored_utils.ml) = 96b69dd3b5adb10692d7646c1dbeb20d27e0e1a8
60SHA1 (patch-tools_qemu-xen-traditional_Makefile) = 5fbb55bf84f9856043be301d5d06530190fe9a60 70SHA1 (patch-tools_qemu-xen-traditional_Makefile) = 5fbb55bf84f9856043be301d5d06530190fe9a60
61SHA1 (patch-tools_qemu-xen-traditional_block-raw-posix.c) = eb3efea4b0c7fd744f627f1926fca737ba826b99 71SHA1 (patch-tools_qemu-xen-traditional_block-raw-posix.c) = eb3efea4b0c7fd744f627f1926fca737ba826b99
62SHA1 (patch-tools_qemu-xen-traditional_configure) = 6a42dcac010f90439a347c0f6e886b07185cb19a 72SHA1 (patch-tools_qemu-xen-traditional_configure) = 6a42dcac010f90439a347c0f6e886b07185cb19a
63SHA1 (patch-tools_qemu-xen-traditional_hw_e1000.c) = ec86584529cc743ea27768ad1fab523ee9fb6bea 73SHA1 (patch-tools_qemu-xen-traditional_hw_e1000.c) = ec86584529cc743ea27768ad1fab523ee9fb6bea
64SHA1 (patch-tools_qemu-xen-traditional_hw_ide.c) = b541546390c6d142bc6079dbd51cfd65b5223e9e 74SHA1 (patch-tools_qemu-xen-traditional_hw_ide.c) = b541546390c6d142bc6079dbd51cfd65b5223e9e
65SHA1 (patch-tools_qemu-xen-traditional_hw_pass-through.c) = 45848fb5038bdd6def218b35dd0a6496a14102f1 75SHA1 (patch-tools_qemu-xen-traditional_hw_pass-through.c) = 45848fb5038bdd6def218b35dd0a6496a14102f1
66SHA1 (patch-tools_qemu-xen-traditional_hw_pass-through.h) = 6edb7a9a03d99b84801bf595c4d68acbc361bb4b 76SHA1 (patch-tools_qemu-xen-traditional_hw_pass-through.h) = 6edb7a9a03d99b84801bf595c4d68acbc361bb4b
67SHA1 (patch-tools_qemu-xen-traditional_hw_piix4acpi.c) = 280265db035e239b173efd9d63b629201d2c0cfa 77SHA1 (patch-tools_qemu-xen-traditional_hw_piix4acpi.c) = 280265db035e239b173efd9d63b629201d2c0cfa
68SHA1 (patch-tools_qemu-xen-traditional_hw_pt-graphics.c) = b9c18042f40ccacb3f24ab0f1e3c6e1a5bf5521a 78SHA1 (patch-tools_qemu-xen-traditional_hw_pt-graphics.c) = b9c18042f40ccacb3f24ab0f1e3c6e1a5bf5521a
69SHA1 (patch-tools_qemu-xen-traditional_hw_pt-msi.c) = a953380dbc3e64528c39e26d2c45c0cbd3e4d738 79SHA1 (patch-tools_qemu-xen-traditional_hw_pt-msi.c) = a953380dbc3e64528c39e26d2c45c0cbd3e4d738
70SHA1 (patch-tools_qemu-xen-traditional_hw_pt-msi.h) = 787447cc24518fe5ae16942d72d3038db612419d 80SHA1 (patch-tools_qemu-xen-traditional_hw_pt-msi.h) = 787447cc24518fe5ae16942d72d3038db612419d
71SHA1 (patch-tools_qemu-xen-traditional_i386-dm_hookstarget.mak) = 3fdfbb7c9c2243a4cf5dabc5a6dd13c12f9d5850 81SHA1 (patch-tools_qemu-xen-traditional_i386-dm_hookstarget.mak) = 3fdfbb7c9c2243a4cf5dabc5a6dd13c12f9d5850
72SHA1 (patch-tools_qemu-xen-traditional_net.c) = 0999993172070db5dc8d7c7daaa4999a3edaadb8 82SHA1 (patch-tools_qemu-xen-traditional_net.c) = 0999993172070db5dc8d7c7daaa4999a3edaadb8
73SHA1 (patch-tools_qemu-xen-traditional_xen-hooks.mak) = 528878c8465d75b63e044e4f6c89609e4edbc3e0 83SHA1 (patch-tools_qemu-xen-traditional_xen-hooks.mak) = 528878c8465d75b63e044e4f6c89609e4edbc3e0
74SHA1 (patch-tools_qemu-xen_audio_audio.c) = a7f47ff0350900bcb99b5515eeee162144a98cf8 84SHA1 (patch-tools_qemu-xen_audio_audio.c) = a7f47ff0350900bcb99b5515eeee162144a98cf8
75SHA1 (patch-tools_qemu-xen_configure) = a658cb80bad09f61a6b39a65f4a1bde04105e661 85SHA1 (patch-tools_qemu-xen_configure) = a658cb80bad09f61a6b39a65f4a1bde04105e661
76SHA1 (patch-tools_qemu-xen_qemu-doc.texi) = e72d480ec29829055f01226eaf020d4cbd100d5d 86SHA1 (patch-tools_qemu-xen_qemu-doc.texi) = e72d480ec29829055f01226eaf020d4cbd100d5d
77SHA1 (patch-tools_xenpaging_xenpaging.c) = 0a154b97f1f452a5f80f7c9a835a1f216f12f7e1 87SHA1 (patch-tools_xenpaging_xenpaging.c) = 0a154b97f1f452a5f80f7c9a835a1f216f12f7e1
78SHA1 (patch-tools_xenpmd_xenpmd.c) = 851ce7a295e2f41ffcd966850d8003c82670c751 88SHA1 (patch-tools_xenpmd_xenpmd.c) = 851ce7a295e2f41ffcd966850d8003c82670c751
79SHA1 (patch-tools_xenstore_xc.c) = cf49b65cba064667e13bb041d1f0099c76ab6a40 89SHA1 (patch-tools_xenstore_xc.c) = cf49b65cba064667e13bb041d1f0099c76ab6a40
80SHA1 (patch-tools_xenstore_xs_lib.c) = e3a8f6e3b149c3ab0f4ec8dcfa455983f94e6c16 90SHA1 (patch-tools_xenstore_xs_lib.c) = e3a8f6e3b149c3ab0f4ec8dcfa455983f94e6c16
81SHA1 (patch-tools_xentrace_xentrace.c) = f964c7555f454358a39f28a2e75db8ee100a4243 91SHA1 (patch-tools_xentrace_xentrace.c) = f964c7555f454358a39f28a2e75db8ee100a4243
82SHA1 (patch-tools_xl_Makefile) = dd4fa8cc66c74eea8b022cd6129aa2831776f2a8 92SHA1 (patch-tools_xl_Makefile) = dd4fa8cc66c74eea8b022cd6129aa2831776f2a8
83SHA1 (patch-xen_Rules.mk) = c743dc63f51fc280d529a7d9e08650292c171dac 93SHA1 (patch-xen_Rules.mk) = c743dc63f51fc280d529a7d9e08650292c171dac
84SHA1 (patch-xen_common_lz4_dexompress.c) = 521a247c2d36980b3433c4be92c77308a2d3f3b9 
85SHA1 (patch-xen_tools_symbols.c) = 67b5a38312095029631e00457abc0e4bb633aaf8 94SHA1 (patch-xen_tools_symbols.c) = 67b5a38312095029631e00457abc0e4bb633aaf8

File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA115-c
$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>
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>
Reviewed-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 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>
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>
Reviewed-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 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>
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>
Reviewed-by: Paul Durrant <paul@xen.org>
Acked-by: Julien Grall <jgrall@amazon.com>
---
 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>
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>
Reviewed-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 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>
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>
Reviewed-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 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>
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>
Reviewed-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 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>
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>
Reviewed-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 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>
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>
Acked-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 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>
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>
Reviewed-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 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>
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>
[julieng: Handle rebase conflict]
Reviewed-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Paul Durrant <paul@xen.org>
---
 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


File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA115-o
$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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>

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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>

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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>

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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>

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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>

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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>

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);

File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA322-c
$NetBSD: patch-XSA322-c,v 1.1 2020/12/17 16:48:12 bouyer Exp $

From: Juergen Gross <jgross@suse.com>
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>
Reviewed-by: Paul Durrant <paul@xen.org>
Acked-by: Julien Grall <julien@amazon.com>

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


File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA322-o
$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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>

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

File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA323
$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>
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>
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
Acked-by: Christian Lindig <christian.lindig@citrix.com>

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);

File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA324
$NetBSD: patch-XSA324,v 1.1 2020/12/17 16:48:12 bouyer Exp $

From: Juergen Gross <jgross@suse.com>
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>
Acked-by: Julien Grall <jgrall@amazon.com>

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;

File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA325
$NetBSD: patch-XSA325,v 1.1 2020/12/17 16:48:12 bouyer Exp $

From: Harsha Shamsundara Havanur <havanur@amazon.com>
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>
Signed-off-by: Harsha Shamsundara Havanur <havanur@amazon.com>
Signed-off-by: Julien Grall <jgrall@amazon.com>
Reviewed-by: Juergen Gross <jgross@suse.com>
Reviewed-by: Paul Durrant <paul@xen.org>

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


File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA330
$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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>

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                                                                                    *)

File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA352
$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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>

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

File Added: pkgsrc/sysutils/xentools411/patches/Attic/patch-XSA353
$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>
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>
Acked-by: Christian Lindig <christian.lindig@citrix.com>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>

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;

cvs diff -r1.1 -r1.2 pkgsrc/sysutils/xentools411/patches/Attic/patch-tools_ocaml_xenstored_utils.ml (expand / switch to unified diff)

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