Xen Security Advisory 115 v4 (CVE-2020-29480) - xenstore watch notifications lacking permission checks
Xen.org security team <[email protected]> Tue, 15 Dec 2020 12:18:24 +0000
| Newsgroups | gmane.comp.emulators.xen.announce |
|---|---|
| Message-ID | <E1kp9Hc-0005BK-Ic__48964.0704634198$1608035241$gmane$org@xenbits.xenproject.org> |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256
Xen Security Advisory CVE-2020-29480 / XSA-115
version 4
xenstore watch notifications lacking permission checks
UPDATES IN VERSION 4
====================
Public release.
ISSUE DESCRIPTION
=================
Neither xenstore implementation does any permissions checks when
reporting a xenstore watch event.
A guest administrator can watch the root xenstored node, which will
cause notifications for every created, modified and deleted key.
A guest administrator can also use the special watches, which will
cause a notification every time a domain is created and destroyed.
Data may include:
- number, type and domids of other VMs
- existence and domids of driver domains
- numbers of virtual interfaces, block devices, vcpus
- existence of virtual framebuffers and their backend style (eg,
existence of VNC service)
- Xen VM UUIDs for other domains
- timing information about domain creation and device setup
- some hints at the backend provisioning of VMs and their devices
The watch events do not contain values stored in xenstore, only key
names.
IMPACT
======
A guest administrator can observe non-sensitive domain and device
lifecycle events relating to other guests. This information allows
some insight into overall system configuration (including number of
general nature of other guests), and configuration of other guests
(including number and general nature of other guests' devices). This
information might be commercially interesting or might make other
attacks easier.
There is not believed to be exposure of sensitive data. Specifically,
there is no exposure of: VNC passwords; port numbers; pathnames in host
and guest filesystems; cryptopgraphic keys; or within-guest data.
VULNERABLE SYSTEMS
==================
All Xen systems are vulnerable.
Both Xenstore implementations (C and Ocaml) are vulnerable.
MITIGATION
==========
There is no mitigation available.
CREDITS
=======
This issue was discovered by Andrew Reimers and Alex Sharp from
OrionVM.
RESOLUTION
==========
Applying the appropriate attached patches resolves this issue.
Note that patches for released versions are generally prepared to
apply to the stable branches, and may not apply cleanly to the most
recent release tarball. Downstreams are encouraged to update to the
tip of the stable branch before applying these patches.
Note that the Ocaml patches depend on XSA-353.
xsa115-c/*.patch xen-unstable [C xenstored]
xsa115-4.14-c/*.patch Xen 4.14 [C xenstored]
xsa115-4.13-c/*.patch Xen 4.13 - 4.10 [C xenstored]
xsa115-o/*.patch xen-unstable - 4.12 [Ocaml xenstored, needs 353]
xsa115-4.11-o/*.patch Xen 4.11 [Ocaml xenstored, needs 353]
xsa115-4.10-o/*.patch Xen 4.10 [Ocaml xenstored, needs 353]
$ sha256sum xsa115* xsa115*/*
b2cc3bfbfb48b60e8623b276d823599bc6a33065a340fbc79804bad7ffee48be xsa115.meta
ced68edb7da44f3e7233120c34a343ee392a4bf094a61775d54d3ea7dc920837 xsa115-4.10-o/0001-tools-ocaml-xenstored-ignore-transaction-id-for-un-w.patch
21d0e3aff4c696875b9db02d6ba3fc683ba05b768d4716e1a197f4c5475ed324 xsa115-4.10-o/0002-tools-ocaml-xenstored-check-privilege-for-XS_IS_DOMA.patch
28249e3f48c255bbc1e87f6e4b70f5b832b50fa8028f44924c6308a9492a1cf2 xsa115-4.10-o/0003-tools-ocaml-xenstored-unify-watch-firing.patch
219f111181cc8ddcdbca73823688b33f86a2e4bddeb06dcc7dc84c63fc9e9053 xsa115-4.10-o/0004-tools-ocaml-xenstored-introduce-permissions-for-spec.patch
0cb14326baedd44650ce59a3da5ab6daa4a7f18f1e1440b6eda5d1a5d414233b xsa115-4.10-o/0005-tools-ocaml-xenstored-avoid-watch-events-for-nodes-w.patch
b84be5a85c1dadbf77fa1ea1157a293408052d9628fc9cb1f343cd3a1dcd687c xsa115-4.10-o/0006-tools-ocaml-xenstored-add-xenstored.conf-flag-to-tur.patch
ced68edb7da44f3e7233120c34a343ee392a4bf094a61775d54d3ea7dc920837 xsa115-4.11-o/0001-tools-ocaml-xenstored-ignore-transaction-id-for-un-w.patch
21d0e3aff4c696875b9db02d6ba3fc683ba05b768d4716e1a197f4c5475ed324 xsa115-4.11-o/0002-tools-ocaml-xenstored-check-privilege-for-XS_IS_DOMA.patch
28249e3f48c255bbc1e87f6e4b70f5b832b50fa8028f44924c6308a9492a1cf2 xsa115-4.11-o/0003-tools-ocaml-xenstored-unify-watch-firing.patch
046d6d9044c41481071760c54e0ad2f66db70ea720c8d39056cedfd51fda56b8 xsa115-4.11-o/0004-tools-ocaml-xenstored-introduce-permissions-for-spec.patch
a0042d3524f83ac2514d4040cc049108c3db1fe398f26d86b309dda1c1444472 xsa115-4.11-o/0005-tools-ocaml-xenstored-avoid-watch-events-for-nodes-w.patch
b84be5a85c1dadbf77fa1ea1157a293408052d9628fc9cb1f343cd3a1dcd687c xsa115-4.11-o/0006-tools-ocaml-xenstored-add-xenstored.conf-flag-to-tur.patch
383b1f8ae592f5330832962e98c02cf18b566ed090f9e96338536619ab1bd889 xsa115-4.13-c/0001-tools-xenstore-allow-removing-child-of-a-node-exceed.patch
0c96d9c27bc0031f2e72170c453aca5677d8f7469b15468dc797aef4bd1d67d6 xsa115-4.13-c/0002-tools-xenstore-ignore-transaction-id-for-un-watch.patch
11ec359a426abaa71b7eda4a5bf319d73b14b3cbfeac483206c134b0e3ad5391 xsa115-4.13-c/0003-tools-xenstore-fix-node-accounting-after-failed-node.patch
5fd6461cc96fd787a81a625b9b7e230a5c9092201a54976de088703305e86dd6 xsa115-4.13-c/0004-tools-xenstore-simplify-and-rename-check_event_node.patch
55bfaa3674fb355a2ed5830e0a7197ede0a5b9168f93889d7fa08044b312ab52 xsa115-4.13-c/0005-tools-xenstore-check-privilege-for-XS_IS_DOMAIN_INTR.patch
0013ad062ee5f2dd79f500e2c829a9534677282ed4a2d596cf16e6b362fd29af xsa115-4.13-c/0006-tools-xenstore-rework-node-removal.patch
e5ed745da88dd195b03f788f255d0d752eb9e801c39c6905707c0b5fa60e8ddf xsa115-4.13-c/0007-tools-xenstore-fire-watches-only-when-removing-a-spe.patch
83e6b4312be4b7fe651f680e5428d47e71a0fd7fdbff5d39433f48b0f4484ad4 xsa115-4.13-c/0008-tools-xenstore-introduce-node_perms-structure.patch
8fa565f136b1fab33f6a06eebad5da9bed571dcac030dcd0b85078817b5adc75 xsa115-4.13-c/0009-tools-xenstore-allow-special-watches-for-privileged-.patch
4038e76a3a8748b748811e06b91d87d01c3d3d3ae5fead4b123065cfe35eb81a xsa115-4.13-c/0010-tools-xenstore-avoid-watch-events-for-nodes-without-.patch
797772d456b194a7cdad1eedbcf61499d2c5c2a71a6ba9a11e4789ac7eda602f xsa115-4.14-c/0001-tools-xenstore-allow-removing-child-of-a-node-exceed.patch
2f37019e0d0ca3e425da0ab272a9afae749de963bf89c6a65696b0f134257011 xsa115-4.14-c/0002-tools-xenstore-ignore-transaction-id-for-un-watch.patch
7a7b63884dfbea232a14b7ff49f14d1bf89edd638bf738643676504aab6ef5f2 xsa115-4.14-c/0003-tools-xenstore-fix-node-accounting-after-failed-node.patch
52f2c03e318720b7ccf55c9cb11f5d33a46feb922dfed656c7c6db1e5f813d91 xsa115-4.14-c/0004-tools-xenstore-simplify-and-rename-check_event_node.patch
1db253543e2387abed872c6d94ac8915ce55f38e95d59f24cd0d19d173b8eadb xsa115-4.14-c/0005-tools-xenstore-check-privilege-for-XS_IS_DOMAIN_INTR.patch
4bd75552186793cbc8bc1567b5952990e41651c1ccbdc2c55b14bbe62b707ac0 xsa115-4.14-c/0006-tools-xenstore-rework-node-removal.patch
22d0a1bc7b413ff9689b06ee7833baf970f54c678da47db3a941801c79339464 xsa115-4.14-c/0007-tools-xenstore-fire-watches-only-when-removing-a-spe.patch
8d4a53c74d0ce42f8134b073acadf0550552da5a827840517cbae55628e5b4a9 xsa115-4.14-c/0008-tools-xenstore-introduce-node_perms-structure.patch
10a066d28b14ae667d11a9fc3c9113569fa16df4e6039380b13907886551a970 xsa115-4.14-c/0009-tools-xenstore-allow-special-watches-for-privileged-.patch
9731273b7b096326e28caad8d75b2f87e391131fe40f0952dbb8f974e6b3b298 xsa115-4.14-c/0010-tools-xenstore-avoid-watch-events-for-nodes-without-.patch
db1b0b44aad333cc8331a3b34199b052fad3897db5386d1f1b9e02247ff72106 xsa115-c/0001-tools-xenstore-allow-removing-child-of-a-node-exceed.patch
d052bff6d7971500bbed047f914b45fa95cc29b914a024f1d3da9bb151239432 xsa115-c/0002-tools-xenstore-ignore-transaction-id-for-un-watch.patch
cb016c3669b0d650d33dbfd6246545a79e75f605bbfe42f8851702a4848f71db xsa115-c/0003-tools-xenstore-fix-node-accounting-after-failed-node.patch
289beb0917e2554d3c3b6be90e2dd9215ac1aefd3e4fb0ed86e690abbd73b669 xsa115-c/0004-tools-xenstore-simplify-and-rename-check_event_node.patch
8a61a189987e88dbf4c7bdf4b247f1117c82cfe6ac308302753146b11802a670 xsa115-c/0005-tools-xenstore-check-privilege-for-XS_IS_DOMAIN_INTR.patch
6af64fa35e823fff2f47b11421409f2f21f8ecf853583ac70054907ad3ce83c7 xsa115-c/0006-tools-xenstore-rework-node-removal.patch
4fb7af8330e85f267235a05cce0758473326ddb5d39d47450a5492060209f0c0 xsa115-c/0007-tools-xenstore-fire-watches-only-when-removing-a-spe.patch
ff1af7e9d36dc8d3c423a3736e82c2e4ab2a595f3fc6622c57096c7a3a1dce59 xsa115-c/0008-tools-xenstore-introduce-node_perms-structure.patch
8895fbef5ab0b8bdf303becd809c848acd85249a53e0e414d1a9c4d917402ec3 xsa115-c/0009-tools-xenstore-allow-special-watches-for-privileged-.patch
a611598bc76874d69449c23aa43d8b6f1331595e64eb5746731f4ee64301441c xsa115-c/0010-tools-xenstore-avoid-watch-events-for-nodes-without-.patch
46c317b0975fe975162dc4b4bd61f82bf9a6b102e7edcd3cd0dccaad84165ed6 xsa115-o/0001-tools-ocaml-xenstored-ignore-transaction-id-for-un-w.patch
5d0f8c8901196715ed60593bf239caf39b168814ea01ed18c2e3789fb7879790 xsa115-o/0002-tools-ocaml-xenstored-check-privilege-for-XS_IS_DOMA.patch
002cb251a1dcde811dd5998a53a37afe67653361320316eaff9df2d9c5369f8d xsa115-o/0003-tools-ocaml-xenstored-unify-watch-firing.patch
f640ff6f2e86bc0c4074629a80d17328d7494da3f2fdc2c8d99d0018c36c28dc xsa115-o/0004-tools-ocaml-xenstored-introduce-permissions-for-spec.patch
fcc0d36ab9e27a2ab3dd2de8b54495676a454298ca1203d3d424cd4498e03321 xsa115-o/0005-tools-ocaml-xenstored-avoid-watch-events-for-nodes-w.patch
62aeb42ae0a5a93de246aed259b4fe5850a33eb001f03b8d183a70c9c5617618 xsa115-o/0006-tools-ocaml-xenstored-add-xenstored.conf-flag-to-tur.patch
$
DEPLOYMENT DURING EMBARGO
=========================
Deployment of the patches and/or mitigations described above (or
others which are substantially similar) is permitted during the
embargo, even on public-facing systems with untrusted guest users and
administrators.
But: Distribution of updated software is prohibited (except to other
members of the predisclosure list).
Predisclosure list members who wish to deploy significantly different
patches and/or mitigations, please contact the Xen Project Security
Team.
(Note: this during-embargo deployment notice is retained in
post-embargo publicly released Xen Project advisories, even though it
is then no longer applicable. This is to enable the community to have
oversight of the Xen Project Security Team's decisionmaking.)
For more information about permissible uses of embargoed information,
consult the Xen Project community's agreed Security Policy:
http://www.xenproject.org/security-policy.html
-----BEGIN PGP SIGNATURE-----
iQFABAEBCAAqFiEEI+MiLBRfRHX6gGCng/4UyVfoK9kFAl/YqMsMHHBncEB4ZW4u
b3JnAAoJEIP+FMlX6CvZRJUIAJ66U75O7Pf5tmu9s4vLrrG/n7rCo6qp+TZ1hcio
PNd2xYJaiVfr39m2JByoUyIgBbb3C7R03pXgM15Vbvk0/v6b3QySxzSBbqdIOn3H
yQtOJlNY4OnQh7n0Svs0HV1aCbd/81wIKZ5aCxn/X3ZBjBHOIQGMAdSZ/lkh8g0p
7CTkTZB//gbuR8QZV2KYqFYsKlwhhGCueOFYlnqIs/HWmAL2wnsacF/K7xffVw0S
Fu8pATp1jWXGYc3S1J9o+C77vF4Ai8x2OLw5TCSG8grmPAuojbmB5UuT+ez4VB5q
3KbpqkJSoyuOvWOPHxydb9Z/ExbpZUMgO0c1FmZ2opXRBoA=
=OtzN
-----END PGP SIGNATURE-----
xsa115.meta
(application/octet-stream, 1.9 KB) - not displayed
xsa115-4.10-o/0001-tools-ocaml-xenstored-ignore-transaction-id-for-un-w.patch
(application/octet-stream, 1.4 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml index 74c69f869c..0a0e43d1f0 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/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
xsa115-4.10-o/0002-tools-ocaml-xenstored-check-privilege-for-XS_IS_DOMA.patch
(application/octet-stream, 1.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml index 0a0e43d1f0..f374abe998 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/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
xsa115-4.10-o/0003-tools-ocaml-xenstored-unify-watch-firing.patch
(application/octet-stream, 1.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index be9c62f27f..d7432c6597 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/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 =
xsa115-4.10-o/0004-tools-ocaml-xenstored-introduce-permissions-for-spec.patch
(application/octet-stream, 4.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml index f374abe998..c3c8ea2f4b 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/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 --- a/tools/ocaml/xenstored/store.ml +++ b/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 e89c1aff04..f3d95e8897 100644 --- a/tools/ocaml/xenstored/utils.ml +++ b/tools/ocaml/xenstored/utils.ml @@ -89,19 +89,17 @@ let read_file_single_integer filename = Unix.close fd; int_of_string (String.sub 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 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/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
xsa115-4.10-o/0005-tools-ocaml-xenstored-avoid-watch-events-for-nodes-w.patch
(application/octet-stream, 15.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index d7432c6597..1389d971c2 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/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 --- a/tools/ocaml/xenstored/connections.ml +++ b/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 0c0d03d0c4..fab76829cf 100644 --- a/tools/ocaml/xenstored/logging.ml +++ b/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 " @@ -333,3 +337,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 --- a/tools/ocaml/xenstored/perms.ml +++ b/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 --- a/tools/ocaml/xenstored/process.ml +++ b/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 --- a/tools/ocaml/xenstored/transaction.ml +++ b/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 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/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
xsa115-4.10-o/0006-tools-ocaml-xenstored-add-xenstored.conf-flag-to-tur.patch
(application/octet-stream, 3.5 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index 1389d971c2..698f721345 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/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 --- a/tools/ocaml/xenstored/oxenstored.conf.in +++ b/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 --- a/tools/ocaml/xenstored/perms.ml +++ b/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 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/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);
xsa115-4.11-o/0001-tools-ocaml-xenstored-ignore-transaction-id-for-un-w.patch
(application/octet-stream, 1.4 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml index 74c69f869c..0a0e43d1f0 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/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
xsa115-4.11-o/0002-tools-ocaml-xenstored-check-privilege-for-XS_IS_DOMA.patch
(application/octet-stream, 1.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml index 0a0e43d1f0..f374abe998 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/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
xsa115-4.11-o/0003-tools-ocaml-xenstored-unify-watch-firing.patch
(application/octet-stream, 1.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index be9c62f27f..d7432c6597 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/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 =
xsa115-4.11-o/0004-tools-ocaml-xenstored-introduce-permissions-for-spec.patch
(application/octet-stream, 4.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml index f374abe998..c3c8ea2f4b 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/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 --- a/tools/ocaml/xenstored/store.ml +++ b/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 --- a/tools/ocaml/xenstored/utils.ml +++ b/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 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/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
xsa115-4.11-o/0005-tools-ocaml-xenstored-avoid-watch-events-for-nodes-w.patch
(application/octet-stream, 15.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index d7432c6597..1389d971c2 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/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 --- a/tools/ocaml/xenstored/connections.ml +++ b/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 --- a/tools/ocaml/xenstored/logging.ml +++ b/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 --- a/tools/ocaml/xenstored/perms.ml +++ b/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 --- a/tools/ocaml/xenstored/process.ml +++ b/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 --- a/tools/ocaml/xenstored/transaction.ml +++ b/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 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/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
xsa115-4.11-o/0006-tools-ocaml-xenstored-add-xenstored.conf-flag-to-tur.patch
(application/octet-stream, 3.5 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index 1389d971c2..698f721345 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/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 --- a/tools/ocaml/xenstored/oxenstored.conf.in +++ b/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 --- a/tools/ocaml/xenstored/perms.ml +++ b/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 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/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);
xsa115-4.13-c/0001-tools-xenstore-allow-removing-child-of-a-node-exceed.patch
(application/octet-stream, 5.8 KB)
From e92f3dfeaae21a335e666c9247954424e34e5c56 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/tools/xenstore/xenstored_core.c +++ b/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 --- a/tools/xenstore/xenstored_core.h +++ b/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 --- a/tools/xenstore/xenstored_transaction.c +++ b/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
xsa115-4.13-c/0002-tools-xenstore-ignore-transaction-id-for-un-watch.patch
(application/octet-stream, 3.2 KB)
From e8076f73de65c4816f69d6ebf75839c706145fcd Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/tools/xenstore/xenstored_core.c +++ b/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
xsa115-4.13-c/0003-tools-xenstore-fix-node-accounting-after-failed-node.patch
(application/octet-stream, 3.4 KB)
From b8c6dbb67ebb449126023446a7d209eedf966537 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Paul Durrant <[email protected]> Acked-by: Julien Grall <[email protected]> --- 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 --- a/tools/xenstore/xenstored_core.c +++ b/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
xsa115-4.13-c/0004-tools-xenstore-simplify-and-rename-check_event_node.patch
(application/octet-stream, 1.6 KB)
From 318aa75bd0c05423e717ad0b64adb204282025db Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/tools/xenstore/xenstored_watch.c +++ b/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
xsa115-4.13-c/0005-tools-xenstore-check-privilege-for-XS_IS_DOMAIN_INTR.patch
(application/octet-stream, 4.5 KB)
From c625fae44aedc246776b52eb1173cf847a3d4d80 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/tools/xenstore/xenstored_core.c +++ b/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 --- a/tools/xenstore/xenstored_domain.c +++ b/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
xsa115-4.13-c/0006-tools-xenstore-rework-node-removal.patch
(application/octet-stream, 6.8 KB)
From 461c880600175c06e23a63e62d9f1ccab755d708 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/tools/xenstore/xenstored_core.c +++ b/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 --- a/tools/xenstore/xenstored_watch.c +++ b/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 --- a/tools/xenstore/xenstored_watch.h +++ b/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
xsa115-4.13-c/0007-tools-xenstore-fire-watches-only-when-removing-a-spe.patch
(application/octet-stream, 4.3 KB)
From 6ca2e14b43aecc79effc1a0cd528a4aceef44d42 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/tools/xenstore/xenstored_core.c +++ b/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 --- a/tools/xenstore/xenstored_watch.c +++ b/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 --- a/tools/xenstore/xenstored_watch.h +++ b/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
xsa115-4.13-c/0008-tools-xenstore-introduce-node_perms-structure.patch
(application/octet-stream, 9.6 KB)
From 2d4f410899bf59e112c107f371c3d164f8a592f8 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Acked-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/tools/xenstore/xenstored_core.c +++ b/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 --- a/tools/xenstore/xenstored_core.h +++ b/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 --- a/tools/xenstore/xenstored_domain.c +++ b/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
xsa115-4.13-c/0009-tools-xenstore-allow-special-watches-for-privileged-.patch
(application/octet-stream, 7.6 KB)
From cddf74031b3c8a108e8fd7db0bf56e9c2809d3e2 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/docs/misc/xenstore.txt +++ b/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 --- a/tools/xenstore/xenstored_core.c +++ b/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 --- a/tools/xenstore/xenstored_core.h +++ b/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 --- a/tools/xenstore/xenstored_domain.c +++ b/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 --- a/tools/xenstore/xenstored_domain.h +++ b/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 --- a/tools/xenstore/xenstored_watch.c +++ b/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
xsa115-4.13-c/0010-tools-xenstore-avoid-watch-events-for-nodes-without-.patch
(application/octet-stream, 12.9 KB)
From e57b7687b43b033fe45e755e285efbe67bc71921 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> [julieng: Handle rebase conflict] Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/tools/xenstore/xenstored_core.c +++ b/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 --- a/tools/xenstore/xenstored_core.h +++ b/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 --- a/tools/xenstore/xenstored_domain.c +++ b/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 --- a/tools/xenstore/xenstored_transaction.c +++ b/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 --- a/tools/xenstore/xenstored_watch.c +++ b/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 --- a/tools/xenstore/xenstored_watch.h +++ b/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
xsa115-4.14-c/0001-tools-xenstore-allow-removing-child-of-a-node-exceed.patch
(application/octet-stream, 5.8 KB)
From 71623492f7b1b6d63ed76e2bf970c113b88ffa0b Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 7bd959f28b39..62a17a686edc 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -419,7 +419,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; @@ -429,7 +430,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; @@ -457,14 +458,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, @@ -1001,7 +1003,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; } @@ -1041,7 +1043,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; } @@ -1117,7 +1119,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); } @@ -1256,7 +1258,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); @@ -1516,7 +1518,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 c4c32bc88f0c..29d638fbc5a0 100644 --- a/tools/xenstore/xenstored_core.h +++ b/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 --- a/tools/xenstore/xenstored_transaction.c +++ b/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
xsa115-4.14-c/0002-tools-xenstore-ignore-transaction-id-for-un-watch.patch
(application/octet-stream, 3.2 KB)
From 072c729cfe90b4b09cacb12d912ba088db8274fe Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 62a17a686edc..2f989524b497 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -1270,13 +1270,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 }, @@ -1298,7 +1302,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**"; @@ -1313,7 +1317,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; @@ -1322,12 +1333,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
xsa115-4.14-c/0003-tools-xenstore-fix-node-accounting-after-failed-node.patch
(application/octet-stream, 3.4 KB)
From a133627453898759ca73dd5c1c185c3830fed754 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Paul Durrant <[email protected]> Acked-by: Julien Grall <[email protected]> --- 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 2f989524b497..c971519e542a 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -927,11 +927,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; @@ -964,7 +959,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: @@ -984,6 +978,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; } @@ -1000,18 +997,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
xsa115-4.14-c/0004-tools-xenstore-simplify-and-rename-check_event_node.patch
(application/octet-stream, 1.6 KB)
From dc6cf381bdeca4013b6bfe25c27e57f010e7ca84 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 --- a/tools/xenstore/xenstored_watch.c +++ b/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
xsa115-4.14-c/0005-tools-xenstore-check-privilege-for-XS_IS_DOMAIN_INTR.patch
(application/octet-stream, 4.5 KB)
From cd456dd7e3c4bbe229a0307a469c2fc3b8e7b590 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 c971519e542a..f38196ae2825 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -1285,8 +1285,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 }, @@ -1296,8 +1298,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 }, @@ -1306,9 +1310,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 }, }; @@ -1336,6 +1342,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 06359503f091..2d0d87ee89e1 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/tools/xenstore/xenstored_domain.c @@ -372,7 +372,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]); @@ -438,7 +438,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]); @@ -473,9 +473,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
xsa115-4.14-c/0006-tools-xenstore-rework-node-removal.patch
(application/octet-stream, 6.8 KB)
From a3d8089532ae573c03e1cdb2fc3c5ee5ebb52a60 Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 f38196ae2825..dfdb64f3ee60 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -1089,74 +1089,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); @@ -1167,11 +1169,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); } @@ -1209,7 +1213,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 --- a/tools/xenstore/xenstored_watch.c +++ b/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 --- a/tools/xenstore/xenstored_watch.h +++ b/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
xsa115-4.14-c/0007-tools-xenstore-fire-watches-only-when-removing-a-spe.patch
(application/octet-stream, 4.3 KB)
From 3d4e3fd6c78795bf426947fbfbfa9af6568ece9f Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 dfdb64f3ee60..20a7a3581555 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -1120,8 +1120,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; @@ -1133,7 +1133,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", @@ -1145,6 +1145,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); @@ -1174,8 +1175,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 --- a/tools/xenstore/xenstored_watch.c +++ b/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 --- a/tools/xenstore/xenstored_watch.h +++ b/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
xsa115-4.14-c/0008-tools-xenstore-introduce-node_perms-structure.patch
(application/octet-stream, 9.6 KB)
From 1069c600f85ff583c461cfbfee1afb1a0731796e Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Acked-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 20a7a3581555..79d305fbbe58 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -403,14 +403,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; @@ -427,7 +427,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) && @@ -439,12 +439,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); @@ -470,8 +471,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; @@ -480,16 +480,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; } /* @@ -536,7 +536,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; } @@ -582,8 +582,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; } @@ -759,16 +758,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, @@ -947,13 +945,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; @@ -1230,7 +1228,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; @@ -1241,13 +1239,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. */ @@ -1258,21 +1255,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)) @@ -1547,8 +1544,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 29d638fbc5a0..47ba0916dbe2 100644 --- a/tools/xenstore/xenstored_core.h +++ b/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 2d0d87ee89e1..aa9942fcc267 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/tools/xenstore/xenstored_domain.c @@ -650,12 +650,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++; } @@ -676,12 +676,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
xsa115-4.14-c/0009-tools-xenstore-allow-special-watches-for-privileged-.patch
(application/octet-stream, 7.6 KB)
From b9fff4b7ad6b41db860a43d35c401847fef789cb Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 cb8009cb686d..2081f20f55e4 100644 --- a/docs/misc/xenstore.txt +++ b/docs/misc/xenstore.txt @@ -170,6 +170,9 @@ SET_PERMS <path>|<perm-as-string>|+? n<domid> no access See https://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 79d305fbbe58..15ffbeb30f19 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -470,8 +470,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; @@ -1247,22 +1247,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 47ba0916dbe2..53f1050859fc 100644 --- a/tools/xenstore/xenstored_core.h +++ b/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 aa9942fcc267..a0d1a11c837f 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/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; @@ -582,6 +585,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; @@ -603,6 +659,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 --- a/tools/xenstore/xenstored_domain.h +++ b/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 --- a/tools/xenstore/xenstored_watch.c +++ b/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
xsa115-4.14-c/0010-tools-xenstore-avoid-watch-events-for-nodes-without-.patch
(application/octet-stream, 12.8 KB)
From f1cc47b0572b337269af7e34bd019584f4b8c98e Mon Sep 17 00:00:00 2001 From: Juergen Gross <[email protected]> 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> --- 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 15ffbeb30f19..92bfd54cff62 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -360,8 +360,8 @@ static void initialize_fds(int *p_sock_pollfd_idx, int *p_ro_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; @@ -496,7 +496,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, '/'); @@ -568,10 +568,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; @@ -1058,7 +1058,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; @@ -1080,7 +1080,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); @@ -1143,7 +1143,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); @@ -1167,13 +1167,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); } @@ -1239,7 +1240,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; @@ -1275,6 +1276,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); @@ -1282,7 +1284,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 53f1050859fc..eb19b71f5f46 100644 --- a/tools/xenstore/xenstored_core.h +++ b/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 a0d1a11c837f..9fad470f8331 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/tools/xenstore/xenstored_domain.c @@ -202,7 +202,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); @@ -240,7 +240,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. */ @@ -404,7 +404,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 { /* 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 --- a/tools/xenstore/xenstored_transaction.c +++ b/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 --- a/tools/xenstore/xenstored_watch.c +++ b/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 --- a/tools/xenstore/xenstored_watch.h +++ b/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
xsa115-c/0001-tools-xenstore-allow-removing-child-of-a-node-exceed.patch
(application/octet-stream, 5.4 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index b4be374d3f..7bf1123da3 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -413,7 +413,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; @@ -423,7 +424,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; @@ -451,14 +452,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, @@ -992,7 +994,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; } @@ -1032,7 +1034,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; } @@ -1108,7 +1110,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); } @@ -1247,7 +1249,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); @@ -1505,7 +1507,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 1df6ad94ab..53aafa1d9b 100644 --- a/tools/xenstore/xenstored_core.h +++ b/tools/xenstore/xenstored_core.h @@ -146,7 +146,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 2824f7b359..e878975734 100644 --- a/tools/xenstore/xenstored_transaction.c +++ b/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;
xsa115-c/0002-tools-xenstore-ignore-transaction-id-for-un-watch.patch
(application/octet-stream, 3 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index 7bf1123da3..3471ce1592 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -1261,13 +1261,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 }, @@ -1289,7 +1293,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**"; @@ -1304,7 +1308,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; @@ -1313,12 +1324,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);
xsa115-c/0003-tools-xenstore-fix-node-accounting-after-failed-node.patch
(application/octet-stream, 3.1 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Reviewed-by: Paul Durrant <[email protected]> Acked-by: Julien Grall <[email protected]> diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index 3471ce1592..476e69d658 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -918,11 +918,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; @@ -955,7 +950,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: @@ -975,6 +969,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; } @@ -991,18 +988,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; }
xsa115-c/0004-tools-xenstore-simplify-and-rename-check_event_node.patch
(application/octet-stream, 1.3 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c index 7dedca60df..f2f1bed47c 100644 --- a/tools/xenstore/xenstored_watch.c +++ b/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); /*
xsa115-c/0005-tools-xenstore-check-privilege-for-XS_IS_DOMAIN_INTR.patch
(application/octet-stream, 4.2 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index 476e69d658..3d0e7b3917 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -1276,8 +1276,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 }, @@ -1287,8 +1289,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 }, @@ -1297,9 +1301,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 }, }; @@ -1327,6 +1333,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 a2f144f6dd..364ad8ea63 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/tools/xenstore/xenstored_domain.c @@ -372,9 +372,6 @@ 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)) - return EACCES; - domid = atoi(vec[0]); /* Ignore the gfn, we don't need it. */ port = atoi(vec[2]); @@ -438,9 +435,6 @@ 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)) - return EACCES; - domid = atoi(vec[0]); tdomid = atoi(vec[1]); @@ -473,9 +467,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); }
xsa115-c/0006-tools-xenstore-rework-node-removal.patch
(application/octet-stream, 6.5 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index 3d0e7b3917..c74413bda2 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -1080,74 +1080,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); @@ -1158,11 +1160,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); } @@ -1200,7 +1204,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 f2f1bed47c..f0bbfe7a6d 100644 --- a/tools/xenstore/xenstored_watch.c +++ b/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 c72ea6a685..54d4ea7e0d 100644 --- a/tools/xenstore/xenstored_watch.h +++ b/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);
xsa115-c/0007-tools-xenstore-fire-watches-only-when-removing-a-spe.patch
(application/octet-stream, 3.9 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index c74413bda2..b39610c876 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -1111,8 +1111,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; @@ -1124,7 +1124,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", @@ -1136,6 +1136,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); @@ -1165,8 +1166,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 f0bbfe7a6d..3836675459 100644 --- a/tools/xenstore/xenstored_watch.c +++ b/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 54d4ea7e0d..1b3c80d3dd 100644 --- a/tools/xenstore/xenstored_watch.h +++ b/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);
xsa115-c/0008-tools-xenstore-introduce-node_perms-structure.patch
(application/octet-stream, 9.1 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Acked-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index b39610c876..06e937de66 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -397,14 +397,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; @@ -421,7 +421,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) && @@ -433,12 +433,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); @@ -464,23 +465,22 @@ 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; /* 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; } /* @@ -527,7 +527,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; } @@ -573,8 +573,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; } @@ -750,16 +749,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, @@ -938,13 +936,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; @@ -1221,7 +1219,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; @@ -1232,13 +1230,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. */ @@ -1249,21 +1246,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)) @@ -1536,8 +1533,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 53aafa1d9b..a291f15ce7 100644 --- a/tools/xenstore/xenstored_core.h +++ b/tools/xenstore/xenstored_core.h @@ -106,6 +106,11 @@ struct connection }; extern struct list_head connections; +struct node_perms { + unsigned int num; + struct xs_permissions *p; +}; + struct node { const char *name; @@ -117,8 +122,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 364ad8ea63..76bdd46c8d 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/tools/xenstore/xenstored_domain.c @@ -650,12 +650,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++; } @@ -676,12 +676,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--; }
xsa115-c/0009-tools-xenstore-allow-special-watches-for-privileged-.patch
(application/octet-stream, 7.1 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> diff --git a/docs/misc/xenstore.txt b/docs/misc/xenstore.txt index cb8009cb68..2081f20f55 100644 --- a/docs/misc/xenstore.txt +++ b/docs/misc/xenstore.txt @@ -170,6 +170,9 @@ SET_PERMS <path>|<perm-as-string>|+? n<domid> no access See https://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 06e937de66..1db9d0cc31 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -464,8 +464,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; @@ -1238,22 +1238,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 a291f15ce7..3f958c29ab 100644 --- a/tools/xenstore/xenstored_core.h +++ b/tools/xenstore/xenstored_core.h @@ -162,6 +162,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 76bdd46c8d..e1106d90b6 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/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; @@ -576,6 +579,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; @@ -597,6 +653,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 56ae015974..259183962a 100644 --- a/tools/xenstore/xenstored_domain.h +++ b/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 3836675459..f4e289362e 100644 --- a/tools/xenstore/xenstored_watch.c +++ b/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))
xsa115-c/0010-tools-xenstore-avoid-watch-events-for-nodes-without-.patch
(application/octet-stream, 12.3 KB)
From: Juergen Gross <[email protected]> Subject: 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 <[email protected]> Reviewed-by: Julien Grall <[email protected]> Reviewed-by: Paul Durrant <[email protected]> diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index 1db9d0cc31..ad1903c555 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -354,8 +354,8 @@ static void initialize_fds(int *p_sock_pollfd_idx, int *ptimeout) * 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; @@ -487,7 +487,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, '/'); @@ -559,10 +559,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; @@ -1049,7 +1049,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; @@ -1071,7 +1071,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); @@ -1134,7 +1134,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); @@ -1158,13 +1158,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); } @@ -1230,7 +1231,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; @@ -1266,6 +1267,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); @@ -1273,7 +1275,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 3f958c29ab..6c21d5bb9a 100644 --- a/tools/xenstore/xenstored_core.h +++ b/tools/xenstore/xenstored_core.h @@ -149,15 +149,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); @@ -168,6 +170,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 e1106d90b6..cf239c044b 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/tools/xenstore/xenstored_domain.c @@ -202,7 +202,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); @@ -240,7 +240,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. */ @@ -401,7 +401,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 { /* 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 e878975734..a7d8c5d475 100644 --- a/tools/xenstore/xenstored_transaction.c +++ b/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 f4e289362e..71c108ea99 100644 --- a/tools/xenstore/xenstored_watch.c +++ b/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 "" */ @@ -118,11 +102,59 @@ static void add_event(struct connection *conn, } /* + * 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 1b3c80d3dd..03094374f3 100644 --- a/tools/xenstore/xenstored_watch.h +++ b/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);
xsa115-o/0001-tools-ocaml-xenstored-ignore-transaction-id-for-un-w.patch
(application/octet-stream, 1.4 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml index ff5c9484fc..2fa6798e3b 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/tools/ocaml/xenstored/process.ml @@ -498,12 +498,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
xsa115-o/0002-tools-ocaml-xenstored-check-privilege-for-XS_IS_DOMA.patch
(application/octet-stream, 1.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml index 2fa6798e3b..fd79ef564f 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/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
xsa115-o/0003-tools-ocaml-xenstored-unify-watch-firing.patch
(application/octet-stream, 1.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index 24750ada43..e5df62d9e7 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/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 =
xsa115-o/0004-tools-ocaml-xenstored-introduce-permissions-for-spec.patch
(application/octet-stream, 4.1 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml index fd79ef564f..e528d1ecb2 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/tools/ocaml/xenstored/process.ml @@ -420,7 +420,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 @@ -439,7 +439,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 92b6289b5e..52b88b3ee1 100644 --- a/tools/ocaml/xenstored/store.ml +++ b/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 --- a/tools/ocaml/xenstored/utils.ml +++ b/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 7e7824761b..8d0c50bfa4 100644 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/tools/ocaml/xenstored/xenstored.ml @@ -286,6 +286,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 ( @@ -335,7 +337,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
xsa115-o/0005-tools-ocaml-xenstored-avoid-watch-events-for-nodes-w.patch
(application/octet-stream, 15.7 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index e5df62d9e7..644a448f2e 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/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 f2c4318c88..9f9f7ee2f0 100644 --- a/tools/ocaml/xenstored/connections.ml +++ b/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 c5cba79e92..1ede131329 100644 --- a/tools/ocaml/xenstored/logging.ml +++ b/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 --- a/tools/ocaml/xenstored/perms.ml +++ b/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 e528d1ecb2..f99b9e935c 100644 --- a/tools/ocaml/xenstored/process.ml +++ b/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 = ignore @@ 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 *) @@ -399,7 +404,7 @@ let do_transaction_end con t domains cons data = record_commit ~con ~tid:id ~before:oldstore ~after:cstore end -let do_introduce con _t domains cons data = +let do_introduce con t domains cons data = if not (Connection.is_dom0 con) then raise Define.Permission_denied; let (domid, mfn, port) = @@ -420,14 +425,14 @@ 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 if (Domain.get_remote_port dom) <> port || (Domain.get_mfn dom) <> mfn then raise Domain_not_match -let do_release con _t domains cons data = +let do_release con t domains cons data = if not (Connection.is_dom0 con) then raise Define.Permission_denied; let domid = @@ -439,7 +444,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 = @@ -507,6 +512,8 @@ let maybe_ignore_transaction = function Transaction.none | _ -> fun x -> x + +let () = Printexc.record_backtrace true (** * Nothrow guarantee. *) @@ -548,7 +555,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 963734a653..25bc8c3b4a 100644 --- a/tools/ocaml/xenstored/transaction.ml +++ b/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 8d0c50bfa4..f7b88065bb 100644 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/tools/ocaml/xenstored/xenstored.ml @@ -337,7 +337,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
xsa115-o/0006-tools-ocaml-xenstored-add-xenstored.conf-flag-to-tur.patch
(application/octet-stream, 3.5 KB)
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <[email protected]> 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 <[email protected]> Acked-by: Christian Lindig <[email protected]> Reviewed-by: Andrew Cooper <[email protected]> diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml index 644a448f2e..fa0d3c4d92 100644 --- a/tools/ocaml/xenstored/connection.ml +++ b/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 151b65b72d..f843482981 100644 --- a/tools/ocaml/xenstored/oxenstored.conf.in +++ b/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 --- a/tools/ocaml/xenstored/perms.ml +++ b/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 f7b88065bb..0d355bbcb8 100644 --- a/tools/ocaml/xenstored/xenstored.ml +++ b/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);