Compare commits
12 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5813d5853a | ||
|
|
8b84ab579c | ||
|
|
a65ac23041 | ||
|
|
046e9e564a | ||
|
|
5b38fd1c96 | ||
|
|
8f43f4c45b | ||
|
|
4a3496d719 | ||
|
|
01f64a0113 | ||
|
|
85b6cafcb8 | ||
|
|
0b14ad1009 | ||
|
|
06e1d41034 | ||
|
|
158b3ce973 |
93 changed files with 3602 additions and 10437 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -6,4 +6,4 @@ lwip-1.3.0.tar.gz
|
|||
pciutils-2.2.9.tar.bz2
|
||||
zlib-1.2.3.tar.gz
|
||||
polarssl-1.1.4-gpl.tgz
|
||||
/xen-4.16.2.tar.gz
|
||||
/xen-4.16.5.tar.gz
|
||||
|
|
|
|||
2
sources
2
sources
|
|
@ -4,4 +4,4 @@ SHA512 (newlib-1.16.0.tar.gz) = 40eb96bbc6736a16b6399e0cdb73e853d0d90b685c967e77
|
|||
SHA512 (zlib-1.2.3.tar.gz) = 021b958fcd0d346c4ba761bcf0cc40f3522de6186cf5a0a6ea34a70504ce9622b1c2626fce40675bc8282cf5f5ade18473656abc38050f72f5d6480507a2106e
|
||||
SHA512 (polarssl-1.1.4-gpl.tgz) = 88da614e4d3f4409c4fd3bb3e44c7587ba051e3fed4e33d526069a67e8180212e1ea22da984656f50e290049f60ddca65383e5983c0f8884f648d71f698303ad
|
||||
SHA512 (pciutils-2.2.9.tar.bz2) = 2b3d98d027e46d8c08037366dde6f0781ca03c610ef2b380984639e4ef39899ed8d8b8e4cd9c9dc54df101279b95879bd66bfd4d04ad07fef41e847ea7ae32b5
|
||||
SHA512 (xen-4.16.2.tar.gz) = b6cd036c1073798dffa167ca14c954fbdfb4c0ef99662f7c435e7e5de687d1bde8856ff6bd030d0d2e661bd17ab631551f01b2cc728cad7e70b59aaa6e692783
|
||||
SHA512 (xen-4.16.5.tar.gz) = 2f370787b72b2cd9d81c0b5f138133e676d2b9c8c76e31e6439649d7145242a6b7be0d51a7ff4f4197a99e3f6b24ac50e63d2fa49368da440d3f555e70c4ebd3
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
if (get_strings(in, vec, ARRAY_SIZE(vec)) != ARRAY_SIZE(vec))
|
||||
return EINVAL;
|
||||
|
||||
- node = canonicalize(conn, in, vec[0]);
|
||||
+ node = xenstore_canonicalize(conn, in, vec[0]);
|
||||
- node = canonicalize(conn, ctx, vec[0]);
|
||||
+ node = xenstore_canonicalize(conn, ctx, vec[0]);
|
||||
if (!node)
|
||||
return ENOMEM;
|
||||
list_for_each_entry(watch, &conn->watches, list) {
|
||||
|
|
|
|||
198
xen.spec
198
xen.spec
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
Summary: Xen is a virtual machine monitor
|
||||
Name: xen
|
||||
Version: 4.16.2
|
||||
Version: 4.16.5
|
||||
Release: 4%{?dist}
|
||||
License: GPLv2+ and LGPLv2+ and BSD
|
||||
URL: http://xen.org/
|
||||
|
|
@ -111,66 +111,35 @@ Patch43: xen.gcc11.fixes.patch
|
|||
Patch44: xsa376.patch
|
||||
Patch45: xen.gcc12.fixes.patch
|
||||
Patch46: xen.efi.build.patch
|
||||
Patch47: xsa410-4.16-01.patch
|
||||
Patch48: xsa410-4.16-02.patch
|
||||
Patch49: xsa410-4.16-03.patch
|
||||
Patch50: xsa410-4.16-04.patch
|
||||
Patch51: xsa410-4.16-05.patch
|
||||
Patch52: xsa410-4.16-06.patch
|
||||
Patch53: xsa410-4.16-07.patch
|
||||
Patch54: xsa410-4.16-08.patch
|
||||
Patch55: xsa410-4.16-09.patch
|
||||
Patch56: xsa410-4.16-10.patch
|
||||
Patch57: xsa409-4.13-0001-libxl-docs-Use-arch-specific-default-paging-memory.patch
|
||||
Patch58: xsa409-4.13-0002-xen-arm-Construct-the-P2M-pages-pool-for-guests.patch
|
||||
Patch59: xsa409-4.13-0003-xen-arm-libxl-Implement-XEN_DOMCTL_shadow_op-for-Arm.patch
|
||||
Patch60: xsa409-4.13-0004-xen-arm-Allocate-and-free-P2M-pages-from-the-P2M-poo.patch
|
||||
Patch61: xsa411.patch
|
||||
Patch62: xsa412-4.16.patch
|
||||
Patch63: xsa414.patch
|
||||
Patch64: xsa415.patch
|
||||
Patch65: xsa326-4.16-oxenstored-01.patch
|
||||
Patch66: xsa326-4.16-oxenstored-02.patch
|
||||
Patch67: xsa326-4.16-oxenstored-03.patch
|
||||
Patch68: xsa326-4.16-oxenstored-04.patch
|
||||
Patch69: xsa326-4.16-oxenstored-05.patch
|
||||
Patch70: xsa326-4.16-oxenstored-06.patch
|
||||
Patch71: xsa326-4.16-oxenstored-07.patch
|
||||
Patch72: xsa326-4.16-oxenstored-08.patch
|
||||
Patch73: xsa326-4.16-xenstored-01.patch
|
||||
Patch74: xsa326-4.16-xenstored-02.patch
|
||||
Patch75: xsa326-4.16-xenstored-03.patch
|
||||
Patch76: xsa326-4.16-xenstored-04.patch
|
||||
Patch77: xsa326-4.16-xenstored-05.patch
|
||||
Patch78: xsa326-4.16-xenstored-06.patch
|
||||
Patch79: xsa326-4.16-xenstored-07.patch
|
||||
Patch80: xsa326-4.16-xenstored-08.patch
|
||||
Patch81: xsa326-4.16-xenstored-09.patch
|
||||
Patch82: xsa326-4.16-xenstored-10.patch
|
||||
Patch83: xsa326-4.16-xenstored-11.patch
|
||||
Patch84: xsa326-4.16-xenstored-12.patch
|
||||
Patch85: xsa326-4.16-xenstored-13.patch
|
||||
Patch86: xsa326-4.16-xenstored-14.patch
|
||||
Patch87: xsa326-4.16-xenstored-15.patch
|
||||
Patch88: xsa326-4.16-xenstored-16.patch
|
||||
Patch89: xsa416-4.16.patch
|
||||
Patch90: xsa417.patch
|
||||
Patch91: xsa418-4.16-01.patch
|
||||
Patch92: xsa418-4.16-02.patch
|
||||
Patch93: xsa418-4.16-03.patch
|
||||
Patch94: xsa418-4.16-04.patch
|
||||
Patch95: xsa418-4.16-05.patch
|
||||
Patch96: xsa418-4.16-06.patch
|
||||
Patch97: xsa418-4.16-07.patch
|
||||
Patch98: xsa419-oxenstored.patch
|
||||
Patch99: xsa419-xenstored-01.patch
|
||||
Patch100: xsa419-xenstored-02.patch
|
||||
Patch101: xsa419-xenstored-03.patch
|
||||
Patch102: xsa420.patch
|
||||
Patch103: xsa421-01.patch
|
||||
Patch104: xsa421-02.patch
|
||||
Patch105: xsa422-4.16-1.patch
|
||||
Patch106: xsa422-4.16-2.patch
|
||||
Patch47: xsa437-4.16.patch
|
||||
Patch48: xsa438-4.16.patch
|
||||
Patch49: xsa439-0001-x86-AMD-extend-Zenbleed-check-to-models-good-ucode-i.patch
|
||||
Patch50: xsa439-0002-x86-spec-ctrl-Fix-confusion-between-SPEC_CTRL_EXIT_T.patch
|
||||
Patch51: xsa439-0003-x86-spec-ctrl-Fold-DO_SPEC_CTRL_EXIT_TO_XEN-into-it-.patch
|
||||
Patch52: xsa439-0004-x86-spec-ctrl-Turn-the-remaining-SPEC_CTRL_-ENTRY-EX.patch
|
||||
Patch53: xsa439-0005-x86-spec-ctrl-Improve-all-SPEC_CTRL_-ENTER-EXIT-_-co.patch
|
||||
Patch54: xsa439-0006-x86-entry-Adjust-restore_all_xen-to-hold-stack_end-i.patch
|
||||
Patch55: xsa439-0007-x86-entry-Track-the-IST-ness-of-an-entry-for-the-exi.patch
|
||||
Patch56: xsa439-0008-x86-spec-ctrl-Issue-VERW-during-IST-exit-to-Xen.patch
|
||||
Patch57: xsa439-0009-x86-amd-Introduce-is_zen-1-2-_uarch-predicates.patch
|
||||
Patch58: xsa439-0010-x86-spec-ctrl-Mitigate-the-Zen1-DIV-leakage.patch
|
||||
Patch59: xsa440-4.17.patch
|
||||
Patch60: xsa442-4.17.patch
|
||||
Patch61: xsa443-4.16-01.patch
|
||||
Patch62: xsa443-4.16-02.patch
|
||||
Patch63: xsa443-4.16-03.patch
|
||||
Patch64: xsa443-4.16-04.patch
|
||||
Patch65: xsa443-4.16-05.patch
|
||||
Patch66: xsa443-4.16-06.patch
|
||||
Patch67: xsa443-4.16-07.patch
|
||||
Patch68: xsa443-4.16-08.patch
|
||||
Patch69: xsa443-4.16-09.patch
|
||||
Patch70: xsa443-4.16-10.patch
|
||||
Patch71: xsa443-4.16-11.patch
|
||||
Patch72: xsa444-4.16-1.patch
|
||||
Patch73: xsa444-4.16-2.patch
|
||||
Patch74: xsa445-4.16.patch
|
||||
Patch75: xsa446.patch
|
||||
|
||||
|
||||
%if %build_qemutrad
|
||||
|
|
@ -237,9 +206,6 @@ BuildRequires: systemd-devel
|
|||
%ifarch armv7hl aarch64
|
||||
BuildRequires: libfdt-devel
|
||||
%endif
|
||||
%if %build_ovmf
|
||||
BuildRequires: edk2-ovmf
|
||||
%endif
|
||||
%if %build_hyp
|
||||
BuildRequires: bison flex
|
||||
%endif
|
||||
|
|
@ -274,6 +240,9 @@ Recommends: qemu-system-x86-core
|
|||
# rom file for qemu-xen-traditional
|
||||
Recommends: ipxe-roms-qemu
|
||||
%endif
|
||||
%if %build_ovmf
|
||||
Recommends: edk2-ovmf-xen
|
||||
%endif
|
||||
|
||||
%description runtime
|
||||
This package contains the runtime programs and daemons which
|
||||
|
|
@ -412,37 +381,6 @@ manage Xen virtual machines.
|
|||
%patch73 -p1
|
||||
%patch74 -p1
|
||||
%patch75 -p1
|
||||
%patch76 -p1
|
||||
%patch77 -p1
|
||||
%patch78 -p1
|
||||
%patch79 -p1
|
||||
%patch80 -p1
|
||||
%patch81 -p1
|
||||
%patch82 -p1
|
||||
%patch83 -p1
|
||||
%patch84 -p1
|
||||
%patch85 -p1
|
||||
%patch86 -p1
|
||||
%patch87 -p1
|
||||
%patch88 -p1
|
||||
%patch89 -p1
|
||||
%patch90 -p1
|
||||
%patch91 -p1
|
||||
%patch92 -p1
|
||||
%patch93 -p1
|
||||
%patch94 -p1
|
||||
%patch95 -p1
|
||||
%patch96 -p1
|
||||
%patch97 -p1
|
||||
%patch98 -p1
|
||||
%patch99 -p1
|
||||
%patch100 -p1
|
||||
%patch101 -p1
|
||||
%patch102 -p1
|
||||
%patch103 -p1
|
||||
%patch104 -p1
|
||||
%patch105 -p1
|
||||
%patch106 -p1
|
||||
|
||||
# qemu-xen-traditional patches
|
||||
pushd tools/qemu-xen-traditional
|
||||
|
|
@ -494,7 +432,7 @@ CONFIG_EXTRA="--enable-qemu-traditional"
|
|||
CONFIG_EXTRA=""
|
||||
%endif
|
||||
%if %build_ovmf
|
||||
CONFIG_EXTRA="$CONFIG_EXTRA --with-system-ovmf=%{_libexecdir}/%{name}/boot/ovmf.bin"
|
||||
CONFIG_EXTRA="$CONFIG_EXTRA --with-system-ovmf=/usr/share/edk2/xen/OVMF.fd"
|
||||
%endif
|
||||
%ifnarch armv7hl aarch64
|
||||
CONFIG_EXTRA="$CONFIG_EXTRA --with-system-ipxe=/usr/share/ipxe/10ec8139.rom"
|
||||
|
|
@ -628,10 +566,6 @@ strip -s %{buildroot}/%{_libdir}/efi/xen-%{version}.efi
|
|||
rm -rf %{buildroot}/%{_unitdir}/oxenstored.service
|
||||
%endif
|
||||
|
||||
%if %build_ovmf
|
||||
cat /usr/share/OVMF/OVMF_{VARS,CODE}.fd >%{buildroot}%{_libexecdir}/%{name}/boot/ovmf.bin
|
||||
%endif
|
||||
|
||||
############ fixup files in /etc ############
|
||||
|
||||
# logrotate
|
||||
|
|
@ -910,9 +844,6 @@ fi
|
|||
%{_libexecdir}/%{name}/boot/xen-shim
|
||||
/usr/lib/debug%{_libexecdir}/xen/boot/xen-shim-syms
|
||||
%endif
|
||||
%if %build_ovmf
|
||||
%{_libexecdir}/xen/boot/ovmf.bin
|
||||
%endif
|
||||
%if %build_stubdom
|
||||
%if %build_qemutrad
|
||||
%{_libexecdir}/xen/boot/ioemu-stubdom.gz
|
||||
|
|
@ -1058,6 +989,67 @@ fi
|
|||
%endif
|
||||
|
||||
%changelog
|
||||
* Wed Nov 15 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.5-4
|
||||
- x86/AMD: mismatch in IOMMU quarantine page table levels [XSA-445,
|
||||
CVE-2023-46835]
|
||||
- x86: BTC/SRSO fixes not fully effective [XSA-446, CVE-2023-46836]
|
||||
|
||||
* Wed Oct 11 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.5-3
|
||||
- xenstored: A transaction conflict can crash C Xenstored [XSA-440,
|
||||
CVE-2023-34323]
|
||||
- x86/AMD: missing IOMMU TLB flushing [XSA-442, CVE-2023-34326]
|
||||
- Multiple vulnerabilities in libfsimage disk handling [XSA-443,
|
||||
CVE-2023-34325]
|
||||
- x86/AMD: Debug Mask handling [XSA-444, CVE-2023-34327,
|
||||
CVE-2023-34328]
|
||||
|
||||
* Tue Sep 26 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.5-2
|
||||
- arm32: The cache may not be properly cleaned/invalidated [XSA-437,
|
||||
CVE-2023-34321]
|
||||
- top-level shadow reference dropped too early for 64-bit PV guests
|
||||
[XSA-438, CVE-2023-34322]
|
||||
- x86/AMD: Divide speculative information leak [XSA-439, CVE-2023-20588]
|
||||
|
||||
* Fri Aug 11 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.5-1
|
||||
- update to xen-4.16.5 which includes
|
||||
x86/AMD: Speculative Return Stack Overflow [XSA-434, CVE-2023-20569]
|
||||
x86/Intel: Gather Data Sampling [XSA-435, CVE-2022-40982]
|
||||
- remove patches now included upstream
|
||||
|
||||
* Tue Aug 01 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.4-4
|
||||
- arm: Guests can trigger a deadlock on Cortex-A77 [XSA-436, CVE-2023-34320]
|
||||
(#2228238)
|
||||
|
||||
* Mon Jul 31 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.4-3
|
||||
- bugfix for x86/AMD: Zenbleed [XSA-433, CVE-2023-20593]
|
||||
|
||||
* Wed Jul 26 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.4-2
|
||||
- x86/AMD: Zenbleed [XSA-433, CVE-2023-20593]
|
||||
|
||||
* Tue May 02 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.4-1
|
||||
- update to xen-4.16.4
|
||||
remove patches now included upstream
|
||||
|
||||
* Tue Mar 21 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.3-4
|
||||
- 3 security issues (#2180425)
|
||||
x86 shadow plus log-dirty mode use-after-free [XSA-427, CVE-2022-42332]
|
||||
x86/HVM pinned cache attributes mis-handling [XSA-428, CVE-2022-42333,
|
||||
CVE-2022-42334]
|
||||
x86: speculative vulnerability in 32bit SYSCALL path [XSA-429,
|
||||
CVE-2022-42331]
|
||||
|
||||
* Sat Feb 18 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.3-3
|
||||
- use OVMF.fd from new edk2-ovmf-xen package as ovmf.bin file
|
||||
built from edk2-ovmf package no longer supports xen (#2170930)
|
||||
|
||||
* Wed Feb 15 2023 Michael Young <m.a.young@durham.ac.uk> - 4.16.3-2
|
||||
- x86: Cross-Thread Return Address Predictions [XSA-426, CVE-2022-27672]
|
||||
(backported to xen-4.16)
|
||||
|
||||
* Mon Dec 19 2022 Michael Young <m.a.young@durham.ac.uk> - 4.16.3-1
|
||||
- update to xen-4.16.3
|
||||
remove or adjust patches now included or superceded upstream
|
||||
|
||||
* Tue Nov 08 2022 Michael Young <m.a.young@durham.ac.uk> - 4.16.2-4
|
||||
- x86: Multiple speculative security issues [XSA-422, CVE-2022-23824]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
From 8d6bb4ac40619877130533b11655829101b31d04 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok@citrix.com>
|
||||
Date: Wed, 12 Oct 2022 19:13:01 +0100
|
||||
Subject: tools/ocaml/xenstored: Synchronise defaults with oxenstore.conf.in
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
We currently have 2 different set of defaults in upstream Xen git tree:
|
||||
* defined in the source code, only used if there is no config file
|
||||
* defined in the oxenstored.conf.in upstream Xen
|
||||
|
||||
An oxenstored.conf file is not mandatory, and if missing, maxrequests in
|
||||
particular has an unsafe default.
|
||||
|
||||
Resync the defaults from oxenstored.conf.in into the source code.
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42316.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
|
||||
diff --git a/tools/ocaml/xenstored/define.ml b/tools/ocaml/xenstored/define.ml
|
||||
index ebe18b8e312c..6b06f808595b 100644
|
||||
--- a/tools/ocaml/xenstored/define.ml
|
||||
+++ b/tools/ocaml/xenstored/define.ml
|
||||
@@ -21,9 +21,9 @@ let xs_daemon_socket = Paths.xen_run_stored ^ "/socket"
|
||||
|
||||
let default_config_dir = Paths.xen_config_dir
|
||||
|
||||
-let maxwatch = ref (50)
|
||||
-let maxtransaction = ref (20)
|
||||
-let maxrequests = ref (-1) (* maximum requests per transaction *)
|
||||
+let maxwatch = ref (100)
|
||||
+let maxtransaction = ref (10)
|
||||
+let maxrequests = ref (1024) (* maximum requests per transaction *)
|
||||
|
||||
let conflict_burst_limit = ref 5.0
|
||||
let conflict_max_history_seconds = ref 0.05
|
||||
diff --git a/tools/ocaml/xenstored/quota.ml b/tools/ocaml/xenstored/quota.ml
|
||||
index abcac912805a..6e3d6401ae89 100644
|
||||
--- a/tools/ocaml/xenstored/quota.ml
|
||||
+++ b/tools/ocaml/xenstored/quota.ml
|
||||
@@ -20,8 +20,8 @@ exception Transaction_opened
|
||||
|
||||
let warn fmt = Logging.warn "quota" fmt
|
||||
let activate = ref true
|
||||
-let maxent = ref (10000)
|
||||
-let maxsize = ref (4096)
|
||||
+let maxent = ref (1000)
|
||||
+let maxsize = ref (2048)
|
||||
|
||||
type t = {
|
||||
maxent: int; (* max entities per domU *)
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
From 78d5af44ab13bb18c87b6ad75e505bd374379cb3 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok@citrix.com>
|
||||
Date: Thu, 28 Jul 2022 17:08:15 +0100
|
||||
Subject: tools/ocaml/xenstored: Check for maxrequests before performing
|
||||
operations
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Previously we'd perform the operation, record the updated tree in the
|
||||
transaction record, then try to insert a watchop path and the reply packet.
|
||||
|
||||
If we exceeded max requests we would've returned EQUOTA, but still:
|
||||
* have performed the operation on the transaction's tree
|
||||
* have recorded the watchop, making this queue effectively unbounded
|
||||
|
||||
It is better if we check whether we'd have room to store the operation before
|
||||
performing the transaction, and raise EQUOTA there. Then the transaction
|
||||
record won't grow.
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42317.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
|
||||
diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
|
||||
index 27790d4a5c41..dd58e6979cf9 100644
|
||||
--- a/tools/ocaml/xenstored/process.ml
|
||||
+++ b/tools/ocaml/xenstored/process.ml
|
||||
@@ -389,6 +389,7 @@ let input_handle_error ~cons ~doms ~fct ~con ~t ~req =
|
||||
let reply_error e =
|
||||
Packet.Error e in
|
||||
try
|
||||
+ Transaction.check_quota_exn ~perm:(Connection.get_perm con) t;
|
||||
fct con t doms cons req.Packet.data
|
||||
with
|
||||
| Define.Invalid_path -> reply_error "EINVAL"
|
||||
@@ -681,9 +682,10 @@ let process_packet ~store ~cons ~doms ~con ~req =
|
||||
in
|
||||
|
||||
let response = try
|
||||
+ Transaction.check_quota_exn ~perm:(Connection.get_perm con) t;
|
||||
if tid <> Transaction.none then
|
||||
(* Remember the request and response for this operation in case we need to replay the transaction *)
|
||||
- Transaction.add_operation ~perm:(Connection.get_perm con) t req response;
|
||||
+ Transaction.add_operation t req response;
|
||||
response
|
||||
with Quota.Limit_reached ->
|
||||
Packet.Error "EQUOTA"
|
||||
diff --git a/tools/ocaml/xenstored/transaction.ml b/tools/ocaml/xenstored/transaction.ml
|
||||
index 17b1bdf2eaf9..294143e2335b 100644
|
||||
--- a/tools/ocaml/xenstored/transaction.ml
|
||||
+++ b/tools/ocaml/xenstored/transaction.ml
|
||||
@@ -85,6 +85,7 @@ type t = {
|
||||
oldroot: Store.Node.t;
|
||||
mutable paths: (Xenbus.Xb.Op.operation * Store.Path.t) list;
|
||||
mutable operations: (Packet.request * Packet.response) list;
|
||||
+ mutable quota_reached: bool;
|
||||
mutable read_lowpath: Store.Path.t option;
|
||||
mutable write_lowpath: Store.Path.t option;
|
||||
}
|
||||
@@ -127,6 +128,7 @@ let make ?(internal=false) id store =
|
||||
oldroot = Store.get_root store;
|
||||
paths = [];
|
||||
operations = [];
|
||||
+ quota_reached = false;
|
||||
read_lowpath = None;
|
||||
write_lowpath = None;
|
||||
} in
|
||||
@@ -143,13 +145,19 @@ 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 =
|
||||
+let get_operations t = List.rev t.operations
|
||||
+
|
||||
+let check_quota_exn ~perm t =
|
||||
if !Define.maxrequests >= 0
|
||||
&& not (Perms.Connection.is_dom0 perm)
|
||||
- && List.length t.operations >= !Define.maxrequests
|
||||
- then raise Quota.Limit_reached;
|
||||
+ && (t.quota_reached || List.length t.operations >= !Define.maxrequests)
|
||||
+ then begin
|
||||
+ t.quota_reached <- true;
|
||||
+ raise Quota.Limit_reached;
|
||||
+ end
|
||||
+
|
||||
+let add_operation t request response =
|
||||
t.operations <- (request, response) :: t.operations
|
||||
-let get_operations t = List.rev t.operations
|
||||
let set_read_lowpath t path = t.read_lowpath <- get_lowest path t.read_lowpath
|
||||
let set_write_lowpath t path = t.write_lowpath <- get_lowest path t.write_lowpath
|
||||
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
From 600c45e49c2060e077c06ab19078da89aa8e2e08 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok@citrix.com>
|
||||
Date: Wed, 12 Oct 2022 19:13:07 +0100
|
||||
Subject: tools/ocaml: GC parameter tuning
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
By default the OCaml garbage collector would return memory to the OS only
|
||||
after unused memory is 5x live memory. Tweak this to 120% instead, which
|
||||
would match the major GC speed.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
|
||||
diff --git a/tools/ocaml/xenstored/define.ml b/tools/ocaml/xenstored/define.ml
|
||||
index 6b06f808595b..ba63a8147e09 100644
|
||||
--- a/tools/ocaml/xenstored/define.ml
|
||||
+++ b/tools/ocaml/xenstored/define.ml
|
||||
@@ -25,6 +25,7 @@ let maxwatch = ref (100)
|
||||
let maxtransaction = ref (10)
|
||||
let maxrequests = ref (1024) (* maximum requests per transaction *)
|
||||
|
||||
+let gc_max_overhead = ref 120 (* 120% see comment in xenstored.ml *)
|
||||
let conflict_burst_limit = ref 5.0
|
||||
let conflict_max_history_seconds = ref 0.05
|
||||
let conflict_rate_limit_is_aggregate = ref true
|
||||
diff --git a/tools/ocaml/xenstored/xenstored.ml b/tools/ocaml/xenstored/xenstored.ml
|
||||
index d44ae673c42a..3b57ad016dfb 100644
|
||||
--- a/tools/ocaml/xenstored/xenstored.ml
|
||||
+++ b/tools/ocaml/xenstored/xenstored.ml
|
||||
@@ -104,6 +104,7 @@ let parse_config filename =
|
||||
("quota-maxsize", Config.Set_int Quota.maxsize);
|
||||
("quota-maxrequests", Config.Set_int Define.maxrequests);
|
||||
("quota-path-max", Config.Set_int Define.path_max);
|
||||
+ ("gc-max-overhead", Config.Set_int Define.gc_max_overhead);
|
||||
("test-eagain", Config.Set_bool Transaction.test_eagain);
|
||||
("persistent", Config.Set_bool Disk.enable);
|
||||
("xenstored-log-file", Config.String Logging.set_xenstored_log_destination);
|
||||
@@ -265,6 +266,67 @@ let to_file store cons fds file =
|
||||
(fun () -> close_out channel)
|
||||
end
|
||||
|
||||
+(*
|
||||
+ By default OCaml's GC only returns memory to the OS when it exceeds a
|
||||
+ configurable 'max overhead' setting.
|
||||
+ The default is 500%, that is 5/6th of the OCaml heap needs to be free
|
||||
+ and only 1/6th live for a compaction to be triggerred that would
|
||||
+ release memory back to the OS.
|
||||
+ If the limit is not hit then the OCaml process can reuse that memory
|
||||
+ for its own purposes, but other processes won't be able to use it.
|
||||
+
|
||||
+ There is also a 'space overhead' setting that controls how much work
|
||||
+ each major GC slice does, and by default aims at having no more than
|
||||
+ 80% or 120% (depending on version) garbage values compared to live
|
||||
+ values.
|
||||
+ This doesn't have as much relevance to memory returned to the OS as
|
||||
+ long as space_overhead <= max_overhead, because compaction is only
|
||||
+ triggerred at the end of major GC cycles.
|
||||
+
|
||||
+ The defaults are too large once the program starts using ~100MiB of
|
||||
+ memory, at which point ~500MiB would be unavailable to other processes
|
||||
+ (which would be fine if this was the main process in this VM, but it is
|
||||
+ not).
|
||||
+
|
||||
+ Max overhead can also be set to 0, however this is for testing purposes
|
||||
+ only (setting it lower than 'space overhead' wouldn't help because the
|
||||
+ major GC wouldn't run fast enough, and compaction does have a
|
||||
+ performance cost: we can only compact contiguous regions, so memory has
|
||||
+ to be moved around).
|
||||
+
|
||||
+ Max overhead controls how often the heap is compacted, which is useful
|
||||
+ if there are burst of activity followed by long periods of idle state,
|
||||
+ or if a domain quits, etc. Compaction returns memory to the OS.
|
||||
+
|
||||
+ wasted = live * space_overhead / 100
|
||||
+
|
||||
+ For globally overriding the GC settings one can use OCAMLRUNPARAM,
|
||||
+ however we provide a config file override to be consistent with other
|
||||
+ oxenstored settings.
|
||||
+
|
||||
+ One might want to dynamically adjust the overhead setting based on used
|
||||
+ memory, i.e. to use a fixed upper bound in bytes, not percentage. However
|
||||
+ measurements show that such adjustments increase GC overhead massively,
|
||||
+ while still not guaranteeing that memory is returned any more quickly
|
||||
+ than with a percentage based setting.
|
||||
+
|
||||
+ The allocation policy could also be tweaked, e.g. first fit would reduce
|
||||
+ fragmentation and thus memory usage, but the documentation warns that it
|
||||
+ can be sensibly slower, and indeed one of our own testcases can trigger
|
||||
+ such a corner case where it is multiple times slower, so it is best to keep
|
||||
+ the default allocation policy (next-fit/best-fit depending on version).
|
||||
+
|
||||
+ There are other tweaks that can be attempted in the future, e.g. setting
|
||||
+ 'ulimit -v' to 75% of RAM, however getting the kernel to actually return
|
||||
+ NULL from allocations is difficult even with that setting, and without a
|
||||
+ NULL the emergency GC won't be triggerred.
|
||||
+ Perhaps cgroup limits could help, but for now tweak the safest only.
|
||||
+*)
|
||||
+
|
||||
+let tweak_gc () =
|
||||
+ Gc.set { (Gc.get ()) with Gc.max_overhead = !Define.gc_max_overhead }
|
||||
+
|
||||
+
|
||||
let _ =
|
||||
let cf = do_argv in
|
||||
let pidfile =
|
||||
@@ -274,6 +336,8 @@ let _ =
|
||||
default_pidfile
|
||||
in
|
||||
|
||||
+ tweak_gc ();
|
||||
+
|
||||
(try
|
||||
Unixext.mkdir_rec (Filename.dirname pidfile) 0o755
|
||||
with _ ->
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
From fd6d9cd3d20e496bdbf3e0a07354f65de0bcf4ae Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok@citrix.com>
|
||||
Date: Fri, 29 Jul 2022 18:53:29 +0100
|
||||
Subject: tools/ocaml/libs/xb: hide type of Xb.t
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Hiding the type will make it easier to change the implementation
|
||||
in the future without breaking code that relies on it.
|
||||
|
||||
No functional change.
|
||||
|
||||
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
(cherry picked from commit 7ade30a1451734d041363c750a65d322e25b47ba)
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
diff --git a/tools/ocaml/libs/xb/xb.ml b/tools/ocaml/libs/xb/xb.ml
|
||||
index 104d319d7747..8404ddd8a682 100644
|
||||
--- a/tools/ocaml/libs/xb/xb.ml
|
||||
+++ b/tools/ocaml/libs/xb/xb.ml
|
||||
@@ -196,6 +196,9 @@ let peek_output con = Queue.peek con.pkt_out
|
||||
let input_len con = Queue.length con.pkt_in
|
||||
let has_in_packet con = Queue.length con.pkt_in > 0
|
||||
let get_in_packet con = Queue.pop con.pkt_in
|
||||
+let has_partial_input con = match con.partial_in with
|
||||
+ | HaveHdr _ -> true
|
||||
+ | NoHdr (n, _) -> n < Partial.header_size ()
|
||||
let has_more_input con =
|
||||
match con.backend with
|
||||
| Fd _ -> false
|
||||
diff --git a/tools/ocaml/libs/xb/xb.mli b/tools/ocaml/libs/xb/xb.mli
|
||||
index 3a00da6cddc1..794e35bb343e 100644
|
||||
--- a/tools/ocaml/libs/xb/xb.mli
|
||||
+++ b/tools/ocaml/libs/xb/xb.mli
|
||||
@@ -66,13 +66,7 @@ type backend_mmap = {
|
||||
type backend_fd = { fd : Unix.file_descr; }
|
||||
type backend = Fd of backend_fd | Xenmmap of backend_mmap
|
||||
type partial_buf = HaveHdr of Partial.pkt | NoHdr of int * bytes
|
||||
-type t = {
|
||||
- backend : backend;
|
||||
- pkt_in : Packet.t Queue.t;
|
||||
- pkt_out : Packet.t Queue.t;
|
||||
- mutable partial_in : partial_buf;
|
||||
- mutable partial_out : string;
|
||||
-}
|
||||
+type t
|
||||
val init_partial_in : unit -> partial_buf
|
||||
val reconnect : t -> unit
|
||||
val queue : t -> Packet.t -> unit
|
||||
@@ -97,6 +91,7 @@ val has_output : t -> bool
|
||||
val peek_output : t -> Packet.t
|
||||
val input_len : t -> int
|
||||
val has_in_packet : t -> bool
|
||||
+val has_partial_input : t -> bool
|
||||
val get_in_packet : t -> Packet.t
|
||||
val has_more_input : t -> bool
|
||||
val is_selectable : t -> bool
|
||||
diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
|
||||
index 65f99ea6f28a..38b47363a173 100644
|
||||
--- a/tools/ocaml/xenstored/connection.ml
|
||||
+++ b/tools/ocaml/xenstored/connection.ml
|
||||
@@ -125,9 +125,7 @@ let get_perm con =
|
||||
let set_target con target_domid =
|
||||
con.perm <- Perms.Connection.set_target (get_perm con) ~perms:[Perms.READ; Perms.WRITE] target_domid
|
||||
|
||||
-let is_backend_mmap con = match con.xb.Xenbus.Xb.backend with
|
||||
- | Xenbus.Xb.Xenmmap _ -> true
|
||||
- | _ -> false
|
||||
+let is_backend_mmap con = Xenbus.Xb.is_mmap con.xb
|
||||
|
||||
let send_reply con tid rid ty data =
|
||||
if (String.length data) > xenstore_payload_max && (is_backend_mmap con) then
|
||||
@@ -280,9 +278,7 @@ let get_transaction con tid =
|
||||
|
||||
let do_input con = Xenbus.Xb.input con.xb
|
||||
let has_input con = Xenbus.Xb.has_in_packet con.xb
|
||||
-let has_partial_input con = match con.xb.Xenbus.Xb.partial_in with
|
||||
- | HaveHdr _ -> true
|
||||
- | NoHdr (n, _) -> n < Xenbus.Partial.header_size ()
|
||||
+let has_partial_input con = Xenbus.Xb.has_partial_input con.xb
|
||||
let pop_in con = Xenbus.Xb.get_in_packet con.xb
|
||||
let has_more_input con = Xenbus.Xb.has_more_input con.xb
|
||||
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
From f13fe5903361953e4ccf8602b9c8df7e64568d55 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok@citrix.com>
|
||||
Date: Wed, 12 Oct 2022 19:13:02 +0100
|
||||
Subject: tools/ocaml: Change Xb.input to return Packet.t option
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The queue here would only ever hold at most one element. This will simplify
|
||||
follow-up patches.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
|
||||
diff --git a/tools/ocaml/libs/xb/xb.ml b/tools/ocaml/libs/xb/xb.ml
|
||||
index 8404ddd8a682..165fd4a1edf4 100644
|
||||
--- a/tools/ocaml/libs/xb/xb.ml
|
||||
+++ b/tools/ocaml/libs/xb/xb.ml
|
||||
@@ -45,7 +45,6 @@ type partial_buf = HaveHdr of Partial.pkt | NoHdr of int * bytes
|
||||
type t =
|
||||
{
|
||||
backend: backend;
|
||||
- pkt_in: Packet.t Queue.t;
|
||||
pkt_out: Packet.t Queue.t;
|
||||
mutable partial_in: partial_buf;
|
||||
mutable partial_out: string;
|
||||
@@ -62,7 +61,6 @@ let reconnect t = match t.backend with
|
||||
Xs_ring.close backend.mmap;
|
||||
backend.eventchn_notify ();
|
||||
(* Clear our old connection state *)
|
||||
- Queue.clear t.pkt_in;
|
||||
Queue.clear t.pkt_out;
|
||||
t.partial_in <- init_partial_in ();
|
||||
t.partial_out <- ""
|
||||
@@ -124,7 +122,6 @@ let output con =
|
||||
|
||||
(* NB: can throw Reconnect *)
|
||||
let input con =
|
||||
- let newpacket = ref false in
|
||||
let to_read =
|
||||
match con.partial_in with
|
||||
| HaveHdr partial_pkt -> Partial.to_complete partial_pkt
|
||||
@@ -143,21 +140,19 @@ let input con =
|
||||
if Partial.to_complete partial_pkt = 0 then (
|
||||
let pkt = Packet.of_partialpkt partial_pkt in
|
||||
con.partial_in <- init_partial_in ();
|
||||
- Queue.push pkt con.pkt_in;
|
||||
- newpacket := true
|
||||
- )
|
||||
+ Some pkt
|
||||
+ ) else None
|
||||
| NoHdr (i, buf) ->
|
||||
(* we complete the partial header *)
|
||||
if sz > 0 then
|
||||
Bytes.blit b 0 buf (Partial.header_size () - i) sz;
|
||||
con.partial_in <- if sz = i then
|
||||
- HaveHdr (Partial.of_string (Bytes.to_string buf)) else NoHdr (i - sz, buf)
|
||||
- );
|
||||
- !newpacket
|
||||
+ HaveHdr (Partial.of_string (Bytes.to_string buf)) else NoHdr (i - sz, buf);
|
||||
+ None
|
||||
+ )
|
||||
|
||||
let newcon backend = {
|
||||
backend = backend;
|
||||
- pkt_in = Queue.create ();
|
||||
pkt_out = Queue.create ();
|
||||
partial_in = init_partial_in ();
|
||||
partial_out = "";
|
||||
@@ -193,9 +188,6 @@ let has_output con = has_new_output con || has_old_output con
|
||||
|
||||
let peek_output con = Queue.peek con.pkt_out
|
||||
|
||||
-let input_len con = Queue.length con.pkt_in
|
||||
-let has_in_packet con = Queue.length con.pkt_in > 0
|
||||
-let get_in_packet con = Queue.pop con.pkt_in
|
||||
let has_partial_input con = match con.partial_in with
|
||||
| HaveHdr _ -> true
|
||||
| NoHdr (n, _) -> n < Partial.header_size ()
|
||||
diff --git a/tools/ocaml/libs/xb/xb.mli b/tools/ocaml/libs/xb/xb.mli
|
||||
index 794e35bb343e..91c682162cea 100644
|
||||
--- a/tools/ocaml/libs/xb/xb.mli
|
||||
+++ b/tools/ocaml/libs/xb/xb.mli
|
||||
@@ -77,7 +77,7 @@ val write_fd : backend_fd -> 'a -> string -> int -> int
|
||||
val write_mmap : backend_mmap -> 'a -> string -> int -> int
|
||||
val write : t -> string -> int -> int
|
||||
val output : t -> bool
|
||||
-val input : t -> bool
|
||||
+val input : t -> Packet.t option
|
||||
val newcon : backend -> t
|
||||
val open_fd : Unix.file_descr -> t
|
||||
val open_mmap : Xenmmap.mmap_interface -> (unit -> unit) -> t
|
||||
@@ -89,10 +89,7 @@ val has_new_output : t -> bool
|
||||
val has_old_output : t -> bool
|
||||
val has_output : t -> bool
|
||||
val peek_output : t -> Packet.t
|
||||
-val input_len : t -> int
|
||||
-val has_in_packet : t -> bool
|
||||
val has_partial_input : t -> bool
|
||||
-val get_in_packet : t -> Packet.t
|
||||
val has_more_input : t -> bool
|
||||
val is_selectable : t -> bool
|
||||
val get_fd : t -> Unix.file_descr
|
||||
diff --git a/tools/ocaml/libs/xs/xsraw.ml b/tools/ocaml/libs/xs/xsraw.ml
|
||||
index d982fb24dbb1..451f8b38dbcc 100644
|
||||
--- a/tools/ocaml/libs/xs/xsraw.ml
|
||||
+++ b/tools/ocaml/libs/xs/xsraw.ml
|
||||
@@ -94,26 +94,18 @@ let pkt_send con =
|
||||
done
|
||||
|
||||
(* receive one packet - can sleep *)
|
||||
-let pkt_recv con =
|
||||
- let workdone = ref false in
|
||||
- while not !workdone
|
||||
- do
|
||||
- workdone := Xb.input con.xb
|
||||
- done;
|
||||
- Xb.get_in_packet con.xb
|
||||
+let rec pkt_recv con =
|
||||
+ match Xb.input con.xb with
|
||||
+ | Some packet -> packet
|
||||
+ | None -> pkt_recv con
|
||||
|
||||
let pkt_recv_timeout con timeout =
|
||||
let fd = Xb.get_fd con.xb in
|
||||
let r, _, _ = Unix.select [ fd ] [] [] timeout in
|
||||
if r = [] then
|
||||
true, None
|
||||
- else (
|
||||
- let workdone = Xb.input con.xb in
|
||||
- if workdone then
|
||||
- false, (Some (Xb.get_in_packet con.xb))
|
||||
- else
|
||||
- false, None
|
||||
- )
|
||||
+ else
|
||||
+ false, Xb.input con.xb
|
||||
|
||||
let queue_watchevent con data =
|
||||
let ls = split_string ~limit:2 '\000' data in
|
||||
diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
|
||||
index 38b47363a173..cc20e047d2b9 100644
|
||||
--- a/tools/ocaml/xenstored/connection.ml
|
||||
+++ b/tools/ocaml/xenstored/connection.ml
|
||||
@@ -277,9 +277,7 @@ let get_transaction con tid =
|
||||
Hashtbl.find con.transactions tid
|
||||
|
||||
let do_input con = Xenbus.Xb.input con.xb
|
||||
-let has_input con = Xenbus.Xb.has_in_packet con.xb
|
||||
let has_partial_input con = Xenbus.Xb.has_partial_input con.xb
|
||||
-let pop_in con = Xenbus.Xb.get_in_packet con.xb
|
||||
let has_more_input con = Xenbus.Xb.has_more_input con.xb
|
||||
|
||||
let has_output con = Xenbus.Xb.has_output con.xb
|
||||
@@ -307,7 +305,7 @@ let is_bad con = match con.dom with None -> false | Some dom -> Domain.is_bad_do
|
||||
Restrictions below can be relaxed once xenstored learns to dump more
|
||||
of its live state in a safe way *)
|
||||
let has_extra_connection_data con =
|
||||
- let has_in = has_input con || has_partial_input con in
|
||||
+ let has_in = has_partial_input con in
|
||||
let has_out = has_output con in
|
||||
let has_socket = con.dom = None in
|
||||
let has_nondefault_perms = make_perm con.dom <> con.perm in
|
||||
diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
|
||||
index 6a3435c265d3..2d67456a2aa0 100644
|
||||
--- a/tools/ocaml/xenstored/process.ml
|
||||
+++ b/tools/ocaml/xenstored/process.ml
|
||||
@@ -195,10 +195,9 @@ let parse_live_update args =
|
||||
| _ when Unix.gettimeofday () < t.deadline -> false
|
||||
| l ->
|
||||
warn "timeout reached: have to wait, migrate or shutdown %d domains:" (List.length l);
|
||||
- let msgs = List.rev_map (fun con -> Printf.sprintf "%s: %d tx, in: %b, out: %b, perm: %s"
|
||||
+ let msgs = List.rev_map (fun con -> Printf.sprintf "%s: %d tx, out: %b, perm: %s"
|
||||
(Connection.get_domstr con)
|
||||
(Connection.number_of_transactions con)
|
||||
- (Connection.has_input con)
|
||||
(Connection.has_output con)
|
||||
(Connection.get_perm con |> Perms.Connection.to_string)
|
||||
) l in
|
||||
@@ -705,16 +704,17 @@ let do_input store cons doms con =
|
||||
info "%s requests a reconnect" (Connection.get_domstr con);
|
||||
History.reconnect con;
|
||||
info "%s reconnection complete" (Connection.get_domstr con);
|
||||
- false
|
||||
+ None
|
||||
| Failure exp ->
|
||||
error "caught exception %s" exp;
|
||||
error "got a bad client %s" (sprintf "%-8s" (Connection.get_domstr con));
|
||||
Connection.mark_as_bad con;
|
||||
- false
|
||||
+ None
|
||||
in
|
||||
|
||||
- if newpacket then (
|
||||
- let packet = Connection.pop_in con in
|
||||
+ match newpacket with
|
||||
+ | None -> ()
|
||||
+ | Some packet ->
|
||||
let tid, rid, ty, data = Xenbus.Xb.Packet.unpack packet in
|
||||
let req = {Packet.tid=tid; Packet.rid=rid; Packet.ty=ty; Packet.data=data} in
|
||||
|
||||
@@ -724,8 +724,7 @@ let do_input store cons doms con =
|
||||
(Xenbus.Xb.Op.to_string ty) (sanitize_data data); *)
|
||||
process_packet ~store ~cons ~doms ~con ~req;
|
||||
write_access_log ~ty ~tid ~con:(Connection.get_domstr con) ~data;
|
||||
- Connection.incr_ops con;
|
||||
- )
|
||||
+ Connection.incr_ops con
|
||||
|
||||
let do_output _store _cons _doms con =
|
||||
if Connection.has_output con then (
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
From 2440a8b69a118fe14e73eb6cab4a050922866f1a Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok@citrix.com>
|
||||
Date: Wed, 12 Oct 2022 19:13:03 +0100
|
||||
Subject: tools/ocaml/xb: Add BoundedQueue
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Ensures we cannot store more than [capacity] elements in a [Queue]. Replacing
|
||||
all Queue with this module will then ensure at compile time that all Queues
|
||||
are correctly bound checked.
|
||||
|
||||
Each element in the queue has a class with its own limits. This, in a
|
||||
subsequent change, will ensure that command responses can proceed during a
|
||||
flood of watch events.
|
||||
|
||||
No functional change.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
|
||||
diff --git a/tools/ocaml/libs/xb/xb.ml b/tools/ocaml/libs/xb/xb.ml
|
||||
index 165fd4a1edf4..4197a3888a68 100644
|
||||
--- a/tools/ocaml/libs/xb/xb.ml
|
||||
+++ b/tools/ocaml/libs/xb/xb.ml
|
||||
@@ -17,6 +17,98 @@
|
||||
module Op = struct include Op end
|
||||
module Packet = struct include Packet end
|
||||
|
||||
+module BoundedQueue : sig
|
||||
+ type ('a, 'b) t
|
||||
+
|
||||
+ (** [create ~capacity ~classify ~limit] creates a queue with maximum [capacity] elements.
|
||||
+ This is burst capacity, each element is further classified according to [classify],
|
||||
+ and each class can have its own [limit].
|
||||
+ [capacity] is enforced as an overall limit.
|
||||
+ The [limit] can be dynamic, and can be smaller than the number of elements already queued of that class,
|
||||
+ in which case those elements are considered to use "burst capacity".
|
||||
+ *)
|
||||
+ val create: capacity:int -> classify:('a -> 'b) -> limit:('b -> int) -> ('a, 'b) t
|
||||
+
|
||||
+ (** [clear q] discards all elements from [q] *)
|
||||
+ val clear: ('a, 'b) t -> unit
|
||||
+
|
||||
+ (** [can_push q] when [length q < capacity]. *)
|
||||
+ val can_push: ('a, 'b) t -> 'b -> bool
|
||||
+
|
||||
+ (** [push e q] adds [e] at the end of queue [q] if [can_push q], or returns [None]. *)
|
||||
+ val push: 'a -> ('a, 'b) t -> unit option
|
||||
+
|
||||
+ (** [pop q] removes and returns first element in [q], or raises [Queue.Empty]. *)
|
||||
+ val pop: ('a, 'b) t -> 'a
|
||||
+
|
||||
+ (** [peek q] returns the first element in [q], or raises [Queue.Empty]. *)
|
||||
+ val peek : ('a, 'b) t -> 'a
|
||||
+
|
||||
+ (** [length q] returns the current number of elements in [q] *)
|
||||
+ val length: ('a, 'b) t -> int
|
||||
+
|
||||
+ (** [debug string_of_class q] prints queue usage statistics in an unspecified internal format. *)
|
||||
+ val debug: ('b -> string) -> (_, 'b) t -> string
|
||||
+end = struct
|
||||
+ type ('a, 'b) t =
|
||||
+ { q: 'a Queue.t
|
||||
+ ; capacity: int
|
||||
+ ; classify: 'a -> 'b
|
||||
+ ; limit: 'b -> int
|
||||
+ ; class_count: ('b, int) Hashtbl.t
|
||||
+ }
|
||||
+
|
||||
+ let create ~capacity ~classify ~limit =
|
||||
+ { capacity; q = Queue.create (); classify; limit; class_count = Hashtbl.create 3 }
|
||||
+
|
||||
+ let get_count t classification = try Hashtbl.find t.class_count classification with Not_found -> 0
|
||||
+
|
||||
+ let can_push_internal t classification class_count =
|
||||
+ Queue.length t.q < t.capacity && class_count < t.limit classification
|
||||
+
|
||||
+ let ok = Some ()
|
||||
+
|
||||
+ let push e t =
|
||||
+ let classification = t.classify e in
|
||||
+ let class_count = get_count t classification in
|
||||
+ if can_push_internal t classification class_count then begin
|
||||
+ Queue.push e t.q;
|
||||
+ Hashtbl.replace t.class_count classification (class_count + 1);
|
||||
+ ok
|
||||
+ end
|
||||
+ else
|
||||
+ None
|
||||
+
|
||||
+ let can_push t classification =
|
||||
+ can_push_internal t classification @@ get_count t classification
|
||||
+
|
||||
+ let clear t =
|
||||
+ Queue.clear t.q;
|
||||
+ Hashtbl.reset t.class_count
|
||||
+
|
||||
+ let pop t =
|
||||
+ let e = Queue.pop t.q in
|
||||
+ let classification = t.classify e in
|
||||
+ let () = match get_count t classification - 1 with
|
||||
+ | 0 -> Hashtbl.remove t.class_count classification (* reduces memusage *)
|
||||
+ | n -> Hashtbl.replace t.class_count classification n
|
||||
+ in
|
||||
+ e
|
||||
+
|
||||
+ let peek t = Queue.peek t.q
|
||||
+ let length t = Queue.length t.q
|
||||
+
|
||||
+ let debug string_of_class t =
|
||||
+ let b = Buffer.create 128 in
|
||||
+ Printf.bprintf b "BoundedQueue capacity: %d, used: {" t.capacity;
|
||||
+ Hashtbl.iter (fun packet_class count ->
|
||||
+ Printf.bprintf b " %s: %d" (string_of_class packet_class) count
|
||||
+ ) t.class_count;
|
||||
+ Printf.bprintf b "}";
|
||||
+ Buffer.contents b
|
||||
+end
|
||||
+
|
||||
+
|
||||
exception End_of_file
|
||||
exception Eagain
|
||||
exception Noent
|
||||
|
|
@ -1,872 +0,0 @@
|
|||
From bc0f05e6f3a3c93c853ceffd1f6d2022dc30fb77 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok@citrix.com>
|
||||
Date: Wed, 12 Oct 2022 19:13:04 +0100
|
||||
Subject: tools/ocaml: Limit maximum in-flight requests / outstanding replies
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Introduce a limit on the number of outstanding reply packets in the xenbus
|
||||
queue. This limits the number of in-flight requests: when the output queue is
|
||||
full we'll stop processing inputs until the output queue has room again.
|
||||
|
||||
To avoid a busy loop on the Unix socket we only add it to the watched input
|
||||
file descriptor set if we'd be able to call `input` on it. Even though Dom0
|
||||
is trusted and exempt from quotas a flood of events might cause a backlog
|
||||
where events are produced faster than daemons in Dom0 can consume them, which
|
||||
could lead to an unbounded queue size and OOM.
|
||||
|
||||
Therefore the xenbus queue limit must apply to all connections, Dom0 is not
|
||||
exempt from it, although if everything works correctly it will eventually
|
||||
catch up.
|
||||
|
||||
This prevents a malicious guest from sending more commands while it has
|
||||
outstanding watch events or command replies in its input ring. However if it
|
||||
can cause the generation of watch events by other means (e.g. by Dom0, or
|
||||
another cooperative guest) and stop reading its own ring then watch events
|
||||
would've queued up without limit.
|
||||
|
||||
The xenstore protocol doesn't have a back-pressure mechanism, and doesn't
|
||||
allow dropping watch events. In fact, dropping watch events is known to break
|
||||
some pieces of normal functionality. This leaves little choice to safely
|
||||
implement the xenstore protocol without exposing the xenstore daemon to
|
||||
out-of-memory attacks.
|
||||
|
||||
Implement the fix as pipes with bounded buffers:
|
||||
* Use a bounded buffer for watch events
|
||||
* The watch structure will have a bounded receiving pipe of watch events
|
||||
* The source will have an "overflow" pipe of pending watch events it couldn't
|
||||
deliver
|
||||
|
||||
Items are queued up on one end and are sent as far along the pipe as possible:
|
||||
|
||||
source domain -> watch -> xenbus of target -> xenstore ring/socket of target
|
||||
|
||||
If the pipe is "full" at any point then back-pressure is applied and we prevent
|
||||
more items from being queued up. For the source domain this means that we'll
|
||||
stop accepting new commands as long as its pipe buffer is not empty.
|
||||
|
||||
Before we try to enqueue an item we first check whether it is possible to send
|
||||
it further down the pipe, by attempting to recursively flush the pipes. This
|
||||
ensures that we retain the order of events as much as possible.
|
||||
|
||||
We might break causality of watch events if the target domain's queue is full
|
||||
and we need to start using the watch's queue. This is a breaking change in
|
||||
the xenstore protocol, but only for domains which are not processing their
|
||||
incoming ring as expected.
|
||||
|
||||
When a watch is deleted its entire pending queue is dropped (no code is needed
|
||||
for that, because it is part of the 'watch' type).
|
||||
|
||||
There is a cache of watches that have pending events that we attempt to flush
|
||||
at every cycle if possible.
|
||||
|
||||
Introduce 3 limits here:
|
||||
* quota-maxwatchevents on watch event destination: when this is hit the
|
||||
source will not be allowed to queue up more watch events.
|
||||
* quota-maxoustanding which is the number of responses not read from the ring:
|
||||
once exceeded, no more inputs are processed until all outstanding replies
|
||||
are consumed by the client.
|
||||
* overflow queue on the watch event source: all watches that cannot be stored
|
||||
on destination are queued up here, a single command can trigger multiple
|
||||
watches (e.g. due to recursion).
|
||||
|
||||
The overflow queue currently doesn't have an upper bound, it is difficult to
|
||||
accurately calculate one as it depends on whether you are Dom0 and how many
|
||||
watches each path has registered and how many watch events you can trigger
|
||||
with a single command (e.g. a commit). However these events were already
|
||||
using memory, this just moves them elsewhere, and as long as we correctly
|
||||
block a domain it shouldn't result in unbounded memory usage.
|
||||
|
||||
Note that Dom0 is not excluded from these checks, it is important that Dom0 is
|
||||
especially not excluded when it is the source, since there are many ways in
|
||||
which a guest could trigger Dom0 to send it watch events.
|
||||
|
||||
This should protect against malicious frontends as long as the backend follows
|
||||
the PV xenstore protocol and only exposes paths needed by the frontend, and
|
||||
changes those paths at most once as a reaction to guest events, or protocol
|
||||
state.
|
||||
|
||||
The queue limits are per watch, and per domain-pair, so even if one
|
||||
communication channel would be "blocked", others would keep working, and the
|
||||
domain itself won't get blocked as long as it doesn't overflow the queue of
|
||||
watch events.
|
||||
|
||||
Similarly a malicious backend could cause the frontend to get blocked, but
|
||||
this watch queue protects the frontend as well as long as it follows the PV
|
||||
protocol. (Although note that protection against malicious backends is only a
|
||||
best effort at the moment)
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42318.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
|
||||
diff --git a/tools/ocaml/libs/xb/xb.ml b/tools/ocaml/libs/xb/xb.ml
|
||||
index 4197a3888a68..b292ed7a874d 100644
|
||||
--- a/tools/ocaml/libs/xb/xb.ml
|
||||
+++ b/tools/ocaml/libs/xb/xb.ml
|
||||
@@ -134,14 +134,44 @@ type backend = Fd of backend_fd | Xenmmap of backend_mmap
|
||||
|
||||
type partial_buf = HaveHdr of Partial.pkt | NoHdr of int * bytes
|
||||
|
||||
+(*
|
||||
+ separate capacity reservation for replies and watch events:
|
||||
+ this allows a domain to keep working even when under a constant flood of
|
||||
+ watch events
|
||||
+*)
|
||||
+type capacity = { maxoutstanding: int; maxwatchevents: int }
|
||||
+
|
||||
+module Queue = BoundedQueue
|
||||
+
|
||||
+type packet_class =
|
||||
+ | CommandReply
|
||||
+ | Watchevent
|
||||
+
|
||||
+let string_of_packet_class = function
|
||||
+ | CommandReply -> "command_reply"
|
||||
+ | Watchevent -> "watch_event"
|
||||
+
|
||||
type t =
|
||||
{
|
||||
backend: backend;
|
||||
- pkt_out: Packet.t Queue.t;
|
||||
+ pkt_out: (Packet.t, packet_class) Queue.t;
|
||||
mutable partial_in: partial_buf;
|
||||
mutable partial_out: string;
|
||||
+ capacity: capacity
|
||||
}
|
||||
|
||||
+let to_read con =
|
||||
+ match con.partial_in with
|
||||
+ | HaveHdr partial_pkt -> Partial.to_complete partial_pkt
|
||||
+ | NoHdr (i, _) -> i
|
||||
+
|
||||
+let debug t =
|
||||
+ Printf.sprintf "XenBus state: partial_in: %d needed, partial_out: %d bytes, pkt_out: %d packets, %s"
|
||||
+ (to_read t)
|
||||
+ (String.length t.partial_out)
|
||||
+ (Queue.length t.pkt_out)
|
||||
+ (BoundedQueue.debug string_of_packet_class t.pkt_out)
|
||||
+
|
||||
let init_partial_in () = NoHdr
|
||||
(Partial.header_size (), Bytes.make (Partial.header_size()) '\000')
|
||||
|
||||
@@ -199,7 +229,8 @@ let output con =
|
||||
let s = if String.length con.partial_out > 0 then
|
||||
con.partial_out
|
||||
else if Queue.length con.pkt_out > 0 then
|
||||
- Packet.to_string (Queue.pop con.pkt_out)
|
||||
+ let pkt = Queue.pop con.pkt_out in
|
||||
+ Packet.to_string pkt
|
||||
else
|
||||
"" in
|
||||
(* send data from s, and save the unsent data to partial_out *)
|
||||
@@ -212,12 +243,15 @@ let output con =
|
||||
(* after sending one packet, partial is empty *)
|
||||
con.partial_out = ""
|
||||
|
||||
+(* we can only process an input packet if we're guaranteed to have room
|
||||
+ to store the response packet *)
|
||||
+let can_input con = Queue.can_push con.pkt_out CommandReply
|
||||
+
|
||||
(* NB: can throw Reconnect *)
|
||||
let input con =
|
||||
- let to_read =
|
||||
- match con.partial_in with
|
||||
- | HaveHdr partial_pkt -> Partial.to_complete partial_pkt
|
||||
- | NoHdr (i, _) -> i in
|
||||
+ if not (can_input con) then None
|
||||
+ else
|
||||
+ let to_read = to_read con in
|
||||
|
||||
(* try to get more data from input stream *)
|
||||
let b = Bytes.make to_read '\000' in
|
||||
@@ -243,11 +277,22 @@ let input con =
|
||||
None
|
||||
)
|
||||
|
||||
-let newcon backend = {
|
||||
+let classify t =
|
||||
+ match t.Packet.ty with
|
||||
+ | Op.Watchevent -> Watchevent
|
||||
+ | _ -> CommandReply
|
||||
+
|
||||
+let newcon ~capacity backend =
|
||||
+ let limit = function
|
||||
+ | CommandReply -> capacity.maxoutstanding
|
||||
+ | Watchevent -> capacity.maxwatchevents
|
||||
+ in
|
||||
+ {
|
||||
backend = backend;
|
||||
- pkt_out = Queue.create ();
|
||||
+ pkt_out = Queue.create ~capacity:(capacity.maxoutstanding + capacity.maxwatchevents) ~classify ~limit;
|
||||
partial_in = init_partial_in ();
|
||||
partial_out = "";
|
||||
+ capacity = capacity;
|
||||
}
|
||||
|
||||
let open_fd fd = newcon (Fd { fd = fd; })
|
||||
diff --git a/tools/ocaml/libs/xb/xb.mli b/tools/ocaml/libs/xb/xb.mli
|
||||
index 91c682162cea..71b2754ca788 100644
|
||||
--- a/tools/ocaml/libs/xb/xb.mli
|
||||
+++ b/tools/ocaml/libs/xb/xb.mli
|
||||
@@ -66,10 +66,11 @@ type backend_mmap = {
|
||||
type backend_fd = { fd : Unix.file_descr; }
|
||||
type backend = Fd of backend_fd | Xenmmap of backend_mmap
|
||||
type partial_buf = HaveHdr of Partial.pkt | NoHdr of int * bytes
|
||||
+type capacity = { maxoutstanding: int; maxwatchevents: int }
|
||||
type t
|
||||
val init_partial_in : unit -> partial_buf
|
||||
val reconnect : t -> unit
|
||||
-val queue : t -> Packet.t -> unit
|
||||
+val queue : t -> Packet.t -> unit option
|
||||
val read_fd : backend_fd -> 'a -> bytes -> int -> int
|
||||
val read_mmap : backend_mmap -> 'a -> bytes -> int -> int
|
||||
val read : t -> bytes -> int -> int
|
||||
@@ -78,13 +79,14 @@ val write_mmap : backend_mmap -> 'a -> string -> int -> int
|
||||
val write : t -> string -> int -> int
|
||||
val output : t -> bool
|
||||
val input : t -> Packet.t option
|
||||
-val newcon : backend -> t
|
||||
-val open_fd : Unix.file_descr -> t
|
||||
-val open_mmap : Xenmmap.mmap_interface -> (unit -> unit) -> t
|
||||
+val newcon : capacity:capacity -> backend -> t
|
||||
+val open_fd : Unix.file_descr -> capacity:capacity -> t
|
||||
+val open_mmap : Xenmmap.mmap_interface -> (unit -> unit) -> capacity:capacity -> t
|
||||
val close : t -> unit
|
||||
val is_fd : t -> bool
|
||||
val is_mmap : t -> bool
|
||||
val output_len : t -> int
|
||||
+val can_input: t -> bool
|
||||
val has_new_output : t -> bool
|
||||
val has_old_output : t -> bool
|
||||
val has_output : t -> bool
|
||||
@@ -93,3 +95,4 @@ val has_partial_input : t -> bool
|
||||
val has_more_input : t -> bool
|
||||
val is_selectable : t -> bool
|
||||
val get_fd : t -> Unix.file_descr
|
||||
+val debug: t -> string
|
||||
diff --git a/tools/ocaml/libs/xs/queueop.ml b/tools/ocaml/libs/xs/queueop.ml
|
||||
index 9ff5bbd529ce..4e532cdaeacb 100644
|
||||
--- a/tools/ocaml/libs/xs/queueop.ml
|
||||
+++ b/tools/ocaml/libs/xs/queueop.ml
|
||||
@@ -16,9 +16,10 @@
|
||||
open Xenbus
|
||||
|
||||
let data_concat ls = (String.concat "\000" ls) ^ "\000"
|
||||
+let queue con pkt = let r = Xb.queue con pkt in assert (r <> None)
|
||||
let queue_path ty (tid: int) (path: string) con =
|
||||
let data = data_concat [ path; ] in
|
||||
- Xb.queue con (Xb.Packet.create tid 0 ty data)
|
||||
+ queue con (Xb.Packet.create tid 0 ty data)
|
||||
|
||||
(* operations *)
|
||||
let directory tid path con = queue_path Xb.Op.Directory tid path con
|
||||
@@ -27,48 +28,48 @@ let read tid path con = queue_path Xb.Op.Read tid path con
|
||||
let getperms tid path con = queue_path Xb.Op.Getperms tid path con
|
||||
|
||||
let debug commands con =
|
||||
- Xb.queue con (Xb.Packet.create 0 0 Xb.Op.Debug (data_concat commands))
|
||||
+ queue con (Xb.Packet.create 0 0 Xb.Op.Debug (data_concat commands))
|
||||
|
||||
let watch path data con =
|
||||
let data = data_concat [ path; data; ] in
|
||||
- Xb.queue con (Xb.Packet.create 0 0 Xb.Op.Watch data)
|
||||
+ queue con (Xb.Packet.create 0 0 Xb.Op.Watch data)
|
||||
|
||||
let unwatch path data con =
|
||||
let data = data_concat [ path; data; ] in
|
||||
- Xb.queue con (Xb.Packet.create 0 0 Xb.Op.Unwatch data)
|
||||
+ queue con (Xb.Packet.create 0 0 Xb.Op.Unwatch data)
|
||||
|
||||
let transaction_start con =
|
||||
- Xb.queue con (Xb.Packet.create 0 0 Xb.Op.Transaction_start (data_concat []))
|
||||
+ queue con (Xb.Packet.create 0 0 Xb.Op.Transaction_start (data_concat []))
|
||||
|
||||
let transaction_end tid commit con =
|
||||
let data = data_concat [ (if commit then "T" else "F"); ] in
|
||||
- Xb.queue con (Xb.Packet.create tid 0 Xb.Op.Transaction_end data)
|
||||
+ queue con (Xb.Packet.create tid 0 Xb.Op.Transaction_end data)
|
||||
|
||||
let introduce domid mfn port con =
|
||||
let data = data_concat [ Printf.sprintf "%u" domid;
|
||||
Printf.sprintf "%nu" mfn;
|
||||
string_of_int port; ] in
|
||||
- Xb.queue con (Xb.Packet.create 0 0 Xb.Op.Introduce data)
|
||||
+ queue con (Xb.Packet.create 0 0 Xb.Op.Introduce data)
|
||||
|
||||
let release domid con =
|
||||
let data = data_concat [ Printf.sprintf "%u" domid; ] in
|
||||
- Xb.queue con (Xb.Packet.create 0 0 Xb.Op.Release data)
|
||||
+ queue con (Xb.Packet.create 0 0 Xb.Op.Release data)
|
||||
|
||||
let resume domid con =
|
||||
let data = data_concat [ Printf.sprintf "%u" domid; ] in
|
||||
- Xb.queue con (Xb.Packet.create 0 0 Xb.Op.Resume data)
|
||||
+ queue con (Xb.Packet.create 0 0 Xb.Op.Resume data)
|
||||
|
||||
let getdomainpath domid con =
|
||||
let data = data_concat [ Printf.sprintf "%u" domid; ] in
|
||||
- Xb.queue con (Xb.Packet.create 0 0 Xb.Op.Getdomainpath data)
|
||||
+ queue con (Xb.Packet.create 0 0 Xb.Op.Getdomainpath data)
|
||||
|
||||
let write tid path value con =
|
||||
let data = path ^ "\000" ^ value (* no NULL at the end *) in
|
||||
- Xb.queue con (Xb.Packet.create tid 0 Xb.Op.Write data)
|
||||
+ queue con (Xb.Packet.create tid 0 Xb.Op.Write data)
|
||||
|
||||
let mkdir tid path con = queue_path Xb.Op.Mkdir tid path con
|
||||
let rm tid path con = queue_path Xb.Op.Rm tid path con
|
||||
|
||||
let setperms tid path perms con =
|
||||
let data = data_concat [ path; perms ] in
|
||||
- Xb.queue con (Xb.Packet.create tid 0 Xb.Op.Setperms data)
|
||||
+ queue con (Xb.Packet.create tid 0 Xb.Op.Setperms data)
|
||||
diff --git a/tools/ocaml/libs/xs/xsraw.ml b/tools/ocaml/libs/xs/xsraw.ml
|
||||
index 451f8b38dbcc..cbd17280600c 100644
|
||||
--- a/tools/ocaml/libs/xs/xsraw.ml
|
||||
+++ b/tools/ocaml/libs/xs/xsraw.ml
|
||||
@@ -36,8 +36,10 @@ type con = {
|
||||
let close con =
|
||||
Xb.close con.xb
|
||||
|
||||
+let capacity = { Xb.maxoutstanding = 1; maxwatchevents = 0; }
|
||||
+
|
||||
let open_fd fd = {
|
||||
- xb = Xb.open_fd fd;
|
||||
+ xb = Xb.open_fd ~capacity fd;
|
||||
watchevents = Queue.create ();
|
||||
}
|
||||
|
||||
diff --git a/tools/ocaml/xenstored/connection.ml b/tools/ocaml/xenstored/connection.ml
|
||||
index cc20e047d2b9..9624a5f9da2c 100644
|
||||
--- a/tools/ocaml/xenstored/connection.ml
|
||||
+++ b/tools/ocaml/xenstored/connection.ml
|
||||
@@ -20,12 +20,84 @@ open Stdext
|
||||
|
||||
let xenstore_payload_max = 4096 (* xen/include/public/io/xs_wire.h *)
|
||||
|
||||
+type 'a bounded_sender = 'a -> unit option
|
||||
+(** a bounded sender accepts an ['a] item and returns:
|
||||
+ None - if there is no room to accept the item
|
||||
+ Some () - if it has successfully accepted/sent the item
|
||||
+ *)
|
||||
+
|
||||
+module BoundedPipe : sig
|
||||
+ type 'a t
|
||||
+
|
||||
+ (** [create ~capacity ~destination] creates a bounded pipe with a
|
||||
+ local buffer holding at most [capacity] items. Once the buffer is
|
||||
+ full it will not accept further items. items from the pipe are
|
||||
+ flushed into [destination] as long as it accepts items. The
|
||||
+ destination could be another pipe.
|
||||
+ *)
|
||||
+ val create: capacity:int -> destination:'a bounded_sender -> 'a t
|
||||
+
|
||||
+ (** [is_empty t] returns whether the local buffer of [t] is empty. *)
|
||||
+ val is_empty : _ t -> bool
|
||||
+
|
||||
+ (** [length t] the number of items in the internal buffer *)
|
||||
+ val length: _ t -> int
|
||||
+
|
||||
+ (** [flush_pipe t] sends as many items from the local buffer as possible,
|
||||
+ which could be none. *)
|
||||
+ val flush_pipe: _ t -> unit
|
||||
+
|
||||
+ (** [push t item] tries to [flush_pipe] and then push [item]
|
||||
+ into the pipe if its [capacity] allows.
|
||||
+ Returns [None] if there is no more room
|
||||
+ *)
|
||||
+ val push : 'a t -> 'a bounded_sender
|
||||
+end = struct
|
||||
+ (* items are enqueued in [q], and then flushed to [connect_to] *)
|
||||
+ type 'a t =
|
||||
+ { q: 'a Queue.t
|
||||
+ ; destination: 'a bounded_sender
|
||||
+ ; capacity: int
|
||||
+ }
|
||||
+
|
||||
+ let create ~capacity ~destination =
|
||||
+ { q = Queue.create (); capacity; destination }
|
||||
+
|
||||
+ let rec flush_pipe t =
|
||||
+ if not Queue.(is_empty t.q) then
|
||||
+ let item = Queue.peek t.q in
|
||||
+ match t.destination item with
|
||||
+ | None -> () (* no room *)
|
||||
+ | Some () ->
|
||||
+ (* successfully sent item to next stage *)
|
||||
+ let _ = Queue.pop t.q in
|
||||
+ (* continue trying to send more items *)
|
||||
+ flush_pipe t
|
||||
+
|
||||
+ let push t item =
|
||||
+ (* first try to flush as many items from this pipe as possible to make room,
|
||||
+ it is important to do this first to preserve the order of the items
|
||||
+ *)
|
||||
+ flush_pipe t;
|
||||
+ if Queue.length t.q < t.capacity then begin
|
||||
+ (* enqueue, instead of sending directly.
|
||||
+ this ensures that [out] sees the items in the same order as we receive them
|
||||
+ *)
|
||||
+ Queue.push item t.q;
|
||||
+ Some (flush_pipe t)
|
||||
+ end else None
|
||||
+
|
||||
+ let is_empty t = Queue.is_empty t.q
|
||||
+ let length t = Queue.length t.q
|
||||
+end
|
||||
+
|
||||
type watch = {
|
||||
con: t;
|
||||
token: string;
|
||||
path: string;
|
||||
base: string;
|
||||
is_relative: bool;
|
||||
+ pending_watchevents: Xenbus.Xb.Packet.t BoundedPipe.t;
|
||||
}
|
||||
|
||||
and t = {
|
||||
@@ -38,8 +110,36 @@ and t = {
|
||||
anonid: int;
|
||||
mutable stat_nb_ops: int;
|
||||
mutable perm: Perms.Connection.t;
|
||||
+ pending_source_watchevents: (watch * Xenbus.Xb.Packet.t) BoundedPipe.t
|
||||
}
|
||||
|
||||
+module Watch = struct
|
||||
+ module T = struct
|
||||
+ type t = watch
|
||||
+
|
||||
+ let compare w1 w2 =
|
||||
+ (* cannot compare watches from different connections *)
|
||||
+ assert (w1.con == w2.con);
|
||||
+ match String.compare w1.token w2.token with
|
||||
+ | 0 -> String.compare w1.path w2.path
|
||||
+ | n -> n
|
||||
+ end
|
||||
+ module Set = Set.Make(T)
|
||||
+
|
||||
+ let flush_events t =
|
||||
+ BoundedPipe.flush_pipe t.pending_watchevents;
|
||||
+ not (BoundedPipe.is_empty t.pending_watchevents)
|
||||
+
|
||||
+ let pending_watchevents t =
|
||||
+ BoundedPipe.length t.pending_watchevents
|
||||
+end
|
||||
+
|
||||
+let source_flush_watchevents t =
|
||||
+ BoundedPipe.flush_pipe t.pending_source_watchevents
|
||||
+
|
||||
+let source_pending_watchevents t =
|
||||
+ BoundedPipe.length t.pending_source_watchevents
|
||||
+
|
||||
let mark_as_bad con =
|
||||
match con.dom with
|
||||
|None -> ()
|
||||
@@ -67,7 +167,8 @@ let watch_create ~con ~path ~token = {
|
||||
token = token;
|
||||
path = path;
|
||||
base = get_path con;
|
||||
- is_relative = path.[0] <> '/' && path.[0] <> '@'
|
||||
+ is_relative = path.[0] <> '/' && path.[0] <> '@';
|
||||
+ pending_watchevents = BoundedPipe.create ~capacity:!Define.maxwatchevents ~destination:(Xenbus.Xb.queue con.xb)
|
||||
}
|
||||
|
||||
let get_con w = w.con
|
||||
@@ -93,6 +194,9 @@ let make_perm dom =
|
||||
Perms.Connection.create ~perms:[Perms.READ; Perms.WRITE] domid
|
||||
|
||||
let create xbcon dom =
|
||||
+ let destination (watch, pkt) =
|
||||
+ BoundedPipe.push watch.pending_watchevents pkt
|
||||
+ in
|
||||
let id =
|
||||
match dom with
|
||||
| None -> let old = !anon_id_next in incr anon_id_next; old
|
||||
@@ -109,6 +213,16 @@ let create xbcon dom =
|
||||
anonid = id;
|
||||
stat_nb_ops = 0;
|
||||
perm = make_perm dom;
|
||||
+
|
||||
+ (* the actual capacity will be lower, this is used as an overflow
|
||||
+ buffer: anything that doesn't fit elsewhere gets put here, only
|
||||
+ limited by the amount of watches that you can generate with a
|
||||
+ single xenstore command (which is finite, although possibly very
|
||||
+ large in theory for Dom0). Once the pipe here has any contents the
|
||||
+ domain is blocked from sending more commands until it is empty
|
||||
+ again though.
|
||||
+ *)
|
||||
+ pending_source_watchevents = BoundedPipe.create ~capacity:Sys.max_array_length ~destination
|
||||
}
|
||||
in
|
||||
Logging.new_connection ~tid:Transaction.none ~con:(get_domstr con);
|
||||
@@ -127,11 +241,17 @@ let set_target con target_domid =
|
||||
|
||||
let is_backend_mmap con = Xenbus.Xb.is_mmap con.xb
|
||||
|
||||
-let send_reply con tid rid ty data =
|
||||
+let packet_of con tid rid ty data =
|
||||
if (String.length data) > xenstore_payload_max && (is_backend_mmap con) then
|
||||
- Xenbus.Xb.queue con.xb (Xenbus.Xb.Packet.create tid rid Xenbus.Xb.Op.Error "E2BIG\000")
|
||||
+ Xenbus.Xb.Packet.create tid rid Xenbus.Xb.Op.Error "E2BIG\000"
|
||||
else
|
||||
- Xenbus.Xb.queue con.xb (Xenbus.Xb.Packet.create tid rid ty data)
|
||||
+ Xenbus.Xb.Packet.create tid rid ty data
|
||||
+
|
||||
+let send_reply con tid rid ty data =
|
||||
+ let result = Xenbus.Xb.queue con.xb (packet_of con tid rid ty data) in
|
||||
+ (* should never happen: we only process an input packet when there is room for an output packet *)
|
||||
+ (* and the limit for replies is different from the limit for watch events *)
|
||||
+ assert (result <> None)
|
||||
|
||||
let send_error con tid rid err = send_reply con tid rid Xenbus.Xb.Op.Error (err ^ "\000")
|
||||
let send_ack con tid rid ty = send_reply con tid rid ty "OK\000"
|
||||
@@ -181,11 +301,11 @@ let del_watch con path token =
|
||||
apath, w
|
||||
|
||||
let del_watches con =
|
||||
- Hashtbl.clear con.watches;
|
||||
+ Hashtbl.reset con.watches;
|
||||
con.nb_watches <- 0
|
||||
|
||||
let del_transactions con =
|
||||
- Hashtbl.clear con.transactions
|
||||
+ Hashtbl.reset con.transactions
|
||||
|
||||
let list_watches con =
|
||||
let ll = Hashtbl.fold
|
||||
@@ -208,21 +328,29 @@ let lookup_watch_perm path = function
|
||||
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 fire_single_watch_unchecked source 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 pkt = packet_of watch.con Transaction.none 0 Xenbus.Xb.Op.Watchevent data in
|
||||
+
|
||||
+ match BoundedPipe.push source.pending_source_watchevents (watch, pkt) with
|
||||
+ | Some () -> () (* packet queued *)
|
||||
+ | None ->
|
||||
+ (* a well behaved Dom0 shouldn't be able to trigger this,
|
||||
+ if it happens it is likely a Dom0 bug causing runaway memory usage
|
||||
+ *)
|
||||
+ failwith "watch event overflow, cannot happen"
|
||||
|
||||
-let fire_single_watch (oldroot, root) watch =
|
||||
+let fire_single_watch source (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 Perms.can_fire_watch watch.con.perm perms then
|
||||
- fire_single_watch_unchecked watch
|
||||
+ fire_single_watch_unchecked source 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 fire_watch source roots watch path =
|
||||
let new_path =
|
||||
if watch.is_relative && path.[0] = '/'
|
||||
then begin
|
||||
@@ -232,7 +360,7 @@ let fire_watch roots watch path =
|
||||
end else
|
||||
path
|
||||
in
|
||||
- fire_single_watch roots { watch with path = new_path }
|
||||
+ fire_single_watch source roots { watch with path = new_path }
|
||||
|
||||
(* Search for a valid unused transaction id. *)
|
||||
let rec valid_transaction_id con proposed_id =
|
||||
@@ -280,6 +408,7 @@ let do_input con = Xenbus.Xb.input con.xb
|
||||
let has_partial_input con = Xenbus.Xb.has_partial_input con.xb
|
||||
let has_more_input con = Xenbus.Xb.has_more_input con.xb
|
||||
|
||||
+let can_input con = Xenbus.Xb.can_input con.xb && BoundedPipe.is_empty con.pending_source_watchevents
|
||||
let has_output con = Xenbus.Xb.has_output con.xb
|
||||
let has_old_output con = Xenbus.Xb.has_old_output con.xb
|
||||
let has_new_output con = Xenbus.Xb.has_new_output con.xb
|
||||
@@ -323,7 +452,7 @@ let prevents_live_update con = not (is_bad con)
|
||||
&& (has_extra_connection_data con || has_transaction_data con)
|
||||
|
||||
let has_more_work con =
|
||||
- has_more_input con || not (has_old_output con) && has_new_output con
|
||||
+ (has_more_input con && can_input con) || not (has_old_output con) && has_new_output con
|
||||
|
||||
let incr_ops con = con.stat_nb_ops <- con.stat_nb_ops + 1
|
||||
|
||||
diff --git a/tools/ocaml/xenstored/connections.ml b/tools/ocaml/xenstored/connections.ml
|
||||
index 3c7429fe7f61..7d68c583b43a 100644
|
||||
--- a/tools/ocaml/xenstored/connections.ml
|
||||
+++ b/tools/ocaml/xenstored/connections.ml
|
||||
@@ -22,22 +22,30 @@ type t = {
|
||||
domains: (int, Connection.t) Hashtbl.t;
|
||||
ports: (Xeneventchn.t, Connection.t) Hashtbl.t;
|
||||
mutable watches: Connection.watch list Trie.t;
|
||||
+ mutable has_pending_watchevents: Connection.Watch.Set.t
|
||||
}
|
||||
|
||||
let create () = {
|
||||
anonymous = Hashtbl.create 37;
|
||||
domains = Hashtbl.create 37;
|
||||
ports = Hashtbl.create 37;
|
||||
- watches = Trie.create ()
|
||||
+ watches = Trie.create ();
|
||||
+ has_pending_watchevents = Connection.Watch.Set.empty;
|
||||
}
|
||||
|
||||
+let get_capacity () =
|
||||
+ (* not multiplied by maxwatch on purpose: 2nd queue in watch itself! *)
|
||||
+ { Xenbus.Xb.maxoutstanding = !Define.maxoutstanding; maxwatchevents = !Define.maxwatchevents }
|
||||
+
|
||||
let add_anonymous cons fd =
|
||||
- let xbcon = Xenbus.Xb.open_fd fd in
|
||||
+ let capacity = get_capacity () in
|
||||
+ let xbcon = Xenbus.Xb.open_fd fd ~capacity in
|
||||
let con = Connection.create xbcon None in
|
||||
Hashtbl.add cons.anonymous (Xenbus.Xb.get_fd xbcon) con
|
||||
|
||||
let add_domain cons dom =
|
||||
- let xbcon = Xenbus.Xb.open_mmap (Domain.get_interface dom) (fun () -> Domain.notify dom) in
|
||||
+ let capacity = get_capacity () in
|
||||
+ let xbcon = Xenbus.Xb.open_mmap ~capacity (Domain.get_interface dom) (fun () -> Domain.notify dom) in
|
||||
let con = Connection.create xbcon (Some dom) in
|
||||
Hashtbl.add cons.domains (Domain.get_id dom) con;
|
||||
match Domain.get_port dom with
|
||||
@@ -48,7 +56,9 @@ let select ?(only_if = (fun _ -> true)) cons =
|
||||
Hashtbl.fold (fun _ con (ins, outs) ->
|
||||
if (only_if con) then (
|
||||
let fd = Connection.get_fd con in
|
||||
- (fd :: ins, if Connection.has_output con then fd :: outs else outs)
|
||||
+ let in_fds = if Connection.can_input con then fd :: ins else ins in
|
||||
+ let out_fds = if Connection.has_output con then fd :: outs else outs in
|
||||
+ in_fds, out_fds
|
||||
) else (ins, outs)
|
||||
)
|
||||
cons.anonymous ([], [])
|
||||
@@ -67,10 +77,17 @@ let del_watches_of_con con watches =
|
||||
| [] -> None
|
||||
| ws -> Some ws
|
||||
|
||||
+let del_watches cons con =
|
||||
+ Connection.del_watches con;
|
||||
+ cons.watches <- Trie.map (del_watches_of_con con) cons.watches;
|
||||
+ cons.has_pending_watchevents <-
|
||||
+ cons.has_pending_watchevents |> Connection.Watch.Set.filter @@ fun w ->
|
||||
+ Connection.get_con w != con
|
||||
+
|
||||
let del_anonymous cons con =
|
||||
try
|
||||
Hashtbl.remove cons.anonymous (Connection.get_fd con);
|
||||
- cons.watches <- Trie.map (del_watches_of_con con) cons.watches;
|
||||
+ del_watches cons con;
|
||||
Connection.close con
|
||||
with exn ->
|
||||
debug "del anonymous %s" (Printexc.to_string exn)
|
||||
@@ -85,7 +102,7 @@ let del_domain cons id =
|
||||
| Some p -> Hashtbl.remove cons.ports p
|
||||
| None -> ())
|
||||
| None -> ());
|
||||
- cons.watches <- Trie.map (del_watches_of_con con) cons.watches;
|
||||
+ del_watches cons con;
|
||||
Connection.close con
|
||||
with exn ->
|
||||
debug "del domain %u: %s" id (Printexc.to_string exn)
|
||||
@@ -136,31 +153,33 @@ let del_watch cons con path token =
|
||||
cons.watches <- Trie.set cons.watches key watches;
|
||||
watch
|
||||
|
||||
-let del_watches cons con =
|
||||
- Connection.del_watches con;
|
||||
- cons.watches <- Trie.map (del_watches_of_con con) cons.watches
|
||||
-
|
||||
(* path is absolute *)
|
||||
-let fire_watches ?oldroot root cons path recurse =
|
||||
+let fire_watches ?oldroot source 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 roots w path) watches
|
||||
+ | Some watches -> List.iter (fun w -> Connection.fire_watch source roots w path) watches
|
||||
in
|
||||
let fire_rec _x = function
|
||||
| None -> ()
|
||||
| Some watches ->
|
||||
- List.iter (Connection.fire_single_watch roots) watches
|
||||
+ List.iter (Connection.fire_single_watch source roots) watches
|
||||
in
|
||||
Trie.iter_path fire_watch cons.watches key;
|
||||
if recurse then
|
||||
Trie.iter fire_rec (Trie.sub cons.watches key)
|
||||
|
||||
+let send_watchevents cons con =
|
||||
+ cons.has_pending_watchevents <-
|
||||
+ cons.has_pending_watchevents |> Connection.Watch.Set.filter Connection.Watch.flush_events;
|
||||
+ Connection.source_flush_watchevents con
|
||||
+
|
||||
let fire_spec_watches root cons specpath =
|
||||
+ let source = find_domain cons 0 in
|
||||
iter cons (fun con ->
|
||||
- List.iter (Connection.fire_single_watch (None, root)) (Connection.get_watches con specpath))
|
||||
+ List.iter (Connection.fire_single_watch source (None, root)) (Connection.get_watches con specpath))
|
||||
|
||||
let set_target cons domain target_domain =
|
||||
let con = find_domain cons domain in
|
||||
@@ -197,6 +216,16 @@ let debug cons =
|
||||
let domains = Hashtbl.fold (fun _ con accu -> Connection.debug con :: accu) cons.domains [] in
|
||||
String.concat "" (domains @ anonymous)
|
||||
|
||||
+let debug_watchevents cons con =
|
||||
+ (* == (physical equality)
|
||||
+ has to be used here because w.con.xb.backend might contain a [unit->unit] value causing regular
|
||||
+ comparison to fail due to having a 'functional value' which cannot be compared.
|
||||
+ *)
|
||||
+ let s = cons.has_pending_watchevents |> Connection.Watch.Set.filter (fun w -> w.con == con) in
|
||||
+ let pending = s |> Connection.Watch.Set.elements
|
||||
+ |> List.map (fun w -> Connection.Watch.pending_watchevents w) |> List.fold_left (+) 0 in
|
||||
+ Printf.sprintf "Watches with pending events: %d, pending events total: %d" (Connection.Watch.Set.cardinal s) pending
|
||||
+
|
||||
let filter ~f cons =
|
||||
let fold _ v acc = if f v then v :: acc else acc in
|
||||
[]
|
||||
diff --git a/tools/ocaml/xenstored/define.ml b/tools/ocaml/xenstored/define.ml
|
||||
index ba63a8147e09..327b6d795ec7 100644
|
||||
--- a/tools/ocaml/xenstored/define.ml
|
||||
+++ b/tools/ocaml/xenstored/define.ml
|
||||
@@ -24,6 +24,13 @@ let default_config_dir = Paths.xen_config_dir
|
||||
let maxwatch = ref (100)
|
||||
let maxtransaction = ref (10)
|
||||
let maxrequests = ref (1024) (* maximum requests per transaction *)
|
||||
+let maxoutstanding = ref (1024) (* maximum outstanding requests, i.e. in-flight requests / domain *)
|
||||
+let maxwatchevents = ref (1024)
|
||||
+(*
|
||||
+ maximum outstanding watch events per watch,
|
||||
+ recommended >= maxoutstanding to avoid blocking backend transactions due to
|
||||
+ malicious frontends
|
||||
+ *)
|
||||
|
||||
let gc_max_overhead = ref 120 (* 120% see comment in xenstored.ml *)
|
||||
let conflict_burst_limit = ref 5.0
|
||||
diff --git a/tools/ocaml/xenstored/oxenstored.conf.in b/tools/ocaml/xenstored/oxenstored.conf.in
|
||||
index 4ae48e42d47d..9d034e744b4b 100644
|
||||
--- a/tools/ocaml/xenstored/oxenstored.conf.in
|
||||
+++ b/tools/ocaml/xenstored/oxenstored.conf.in
|
||||
@@ -62,6 +62,8 @@ quota-maxwatch = 100
|
||||
quota-transaction = 10
|
||||
quota-maxrequests = 1024
|
||||
quota-path-max = 1024
|
||||
+quota-maxoutstanding = 1024
|
||||
+quota-maxwatchevents = 1024
|
||||
|
||||
# Activate filed base backend
|
||||
persistent = false
|
||||
diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
|
||||
index 2d67456a2aa0..6dcedfda86e4 100644
|
||||
--- a/tools/ocaml/xenstored/process.ml
|
||||
+++ b/tools/ocaml/xenstored/process.ml
|
||||
@@ -57,7 +57,7 @@ let split_one_path data con =
|
||||
| path :: "" :: [] -> Store.Path.create path (Connection.get_path con)
|
||||
| _ -> raise Invalid_Cmd_Args
|
||||
|
||||
-let process_watch t cons =
|
||||
+let process_watch source 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
|
||||
@@ -67,8 +67,9 @@ let process_watch t cons =
|
||||
| Xenbus.Xb.Op.Rm -> true, None, oldroot
|
||||
| Xenbus.Xb.Op.Setperms -> false, Some oldroot, newroot
|
||||
| _ -> raise (Failure "huh ?") in
|
||||
- Connections.fire_watches ?oldroot root cons (snd op) recurse in
|
||||
- List.iter (fun op -> do_op_watch op cons) ops
|
||||
+ Connections.fire_watches ?oldroot source root cons (snd op) recurse in
|
||||
+ List.iter (fun op -> do_op_watch op cons) ops;
|
||||
+ Connections.send_watchevents cons source
|
||||
|
||||
let create_implicit_path t perm path =
|
||||
let dirname = Store.Path.get_parent path in
|
||||
@@ -234,6 +235,20 @@ let do_debug con t _domains cons data =
|
||||
| "watches" :: _ ->
|
||||
let watches = Connections.debug cons in
|
||||
Some (watches ^ "\000")
|
||||
+ | "xenbus" :: domid :: _ ->
|
||||
+ let domid = int_of_string domid in
|
||||
+ let con = Connections.find_domain cons domid in
|
||||
+ let s = Printf.sprintf "xenbus: %s; overflow queue length: %d, can_input: %b, has_more_input: %b, has_old_output: %b, has_new_output: %b, has_more_work: %b. pending: %s"
|
||||
+ (Xenbus.Xb.debug con.xb)
|
||||
+ (Connection.source_pending_watchevents con)
|
||||
+ (Connection.can_input con)
|
||||
+ (Connection.has_more_input con)
|
||||
+ (Connection.has_old_output con)
|
||||
+ (Connection.has_new_output con)
|
||||
+ (Connection.has_more_work con)
|
||||
+ (Connections.debug_watchevents cons con)
|
||||
+ in
|
||||
+ Some s
|
||||
| "mfn" :: domid :: _ ->
|
||||
let domid = int_of_string domid in
|
||||
let con = Connections.find_domain cons domid in
|
||||
@@ -342,7 +357,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 t cons
|
||||
+ process_watch con t cons
|
||||
)
|
||||
|
||||
let reply_data fct con t doms cons data =
|
||||
@@ -501,7 +516,7 @@ let do_watch con t _domains cons data =
|
||||
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)
|
||||
+ Connection.fire_single_watch_unchecked con watch)
|
||||
|
||||
let do_unwatch con _t _domains cons data =
|
||||
let (node, token) =
|
||||
@@ -532,7 +547,7 @@ let do_transaction_end con t domains cons data =
|
||||
if not success then
|
||||
raise Transaction_again;
|
||||
if commit then begin
|
||||
- process_watch t cons;
|
||||
+ process_watch con t cons;
|
||||
match t.Transaction.ty with
|
||||
| Transaction.No ->
|
||||
() (* no need to record anything *)
|
||||
@@ -699,7 +714,8 @@ let process_packet ~store ~cons ~doms ~con ~req =
|
||||
let do_input store cons doms con =
|
||||
let newpacket =
|
||||
try
|
||||
- Connection.do_input con
|
||||
+ if Connection.can_input con then Connection.do_input con
|
||||
+ else None
|
||||
with Xenbus.Xb.Reconnect ->
|
||||
info "%s requests a reconnect" (Connection.get_domstr con);
|
||||
History.reconnect con;
|
||||
@@ -727,6 +743,7 @@ let do_input store cons doms con =
|
||||
Connection.incr_ops con
|
||||
|
||||
let do_output _store _cons _doms con =
|
||||
+ Connection.source_flush_watchevents con;
|
||||
if Connection.has_output con then (
|
||||
if Connection.has_new_output con then (
|
||||
let packet = Connection.peek_output con in
|
||||
diff --git a/tools/ocaml/xenstored/xenstored.ml b/tools/ocaml/xenstored/xenstored.ml
|
||||
index 3b57ad016dfb..c799e20f1145 100644
|
||||
--- a/tools/ocaml/xenstored/xenstored.ml
|
||||
+++ b/tools/ocaml/xenstored/xenstored.ml
|
||||
@@ -103,6 +103,8 @@ let parse_config filename =
|
||||
("quota-maxentity", Config.Set_int Quota.maxent);
|
||||
("quota-maxsize", Config.Set_int Quota.maxsize);
|
||||
("quota-maxrequests", Config.Set_int Define.maxrequests);
|
||||
+ ("quota-maxoutstanding", Config.Set_int Define.maxoutstanding);
|
||||
+ ("quota-maxwatchevents", Config.Set_int Define.maxwatchevents);
|
||||
("quota-path-max", Config.Set_int Define.path_max);
|
||||
("gc-max-overhead", Config.Set_int Define.gc_max_overhead);
|
||||
("test-eagain", Config.Set_bool Transaction.test_eagain);
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
From 09aa10649f75a262028e9a9b7d859ef7efb23d54 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Thu, 29 Sep 2022 13:07:35 +0200
|
||||
Subject: SUPPORT.md: clarify support of untrusted driver domains with
|
||||
oxenstored
|
||||
|
||||
Add a support statement for the scope of support regarding different
|
||||
Xenstore variants. Especially oxenstored does not (yet) have security
|
||||
support of untrusted driver domains, as those might drive oxenstored
|
||||
out of memory by creating lots of watch events for the guests they are
|
||||
servicing.
|
||||
|
||||
Add a statement regarding Live Update support of oxenstored.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Acked-by: George Dunlap <george.dunlap@citrix.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
|
||||
diff --git a/SUPPORT.md b/SUPPORT.md
|
||||
index 85726102eab8..7d0cb34c8f6f 100644
|
||||
--- a/SUPPORT.md
|
||||
+++ b/SUPPORT.md
|
||||
@@ -179,13 +179,18 @@ Support for running qemu-xen device model in a linux stubdomain.
|
||||
|
||||
Status: Tech Preview
|
||||
|
||||
-## Liveupdate of C xenstored daemon
|
||||
+## Xenstore
|
||||
|
||||
- Status: Tech Preview
|
||||
+### C xenstored daemon
|
||||
|
||||
-## Liveupdate of OCaml xenstored daemon
|
||||
+ Status: Supported
|
||||
+ Status, Liveupdate: Tech Preview
|
||||
|
||||
- Status: Tech Preview
|
||||
+### OCaml xenstored daemon
|
||||
+
|
||||
+ Status: Supported
|
||||
+ Status, untrusted driver domains: Supported, not security supported
|
||||
+ Status, Liveupdate: Not functional
|
||||
|
||||
## Toolstack/3rd party
|
||||
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
From 5192f13a41661b1c1b9e0889d57c0f5b41925c39 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:07 +0200
|
||||
Subject: tools/xenstore: split up send_reply()
|
||||
|
||||
Today send_reply() is used for both, normal request replies and watch
|
||||
events.
|
||||
|
||||
Split it up into send_reply() and send_event(). This will be used to
|
||||
add some event specific handling.
|
||||
|
||||
add_event() can be merged into send_event(), removing the need for an
|
||||
intermediate memory allocation.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index e9c9695fd16e..249ad5ec6fb1 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -767,49 +767,32 @@ static void send_error(struct connection *conn, int error)
|
||||
void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
const void *data, unsigned int len)
|
||||
{
|
||||
- struct buffered_data *bdata;
|
||||
+ struct buffered_data *bdata = conn->in;
|
||||
+
|
||||
+ assert(type != XS_WATCH_EVENT);
|
||||
|
||||
if ( len > XENSTORE_PAYLOAD_MAX ) {
|
||||
send_error(conn, E2BIG);
|
||||
return;
|
||||
}
|
||||
|
||||
- /* Replies reuse the request buffer, events need a new one. */
|
||||
- if (type != XS_WATCH_EVENT) {
|
||||
- bdata = conn->in;
|
||||
- /* Drop asynchronous responses, e.g. errors for watch events. */
|
||||
- if (!bdata)
|
||||
- return;
|
||||
- bdata->inhdr = true;
|
||||
- bdata->used = 0;
|
||||
- conn->in = NULL;
|
||||
- } else {
|
||||
- /* Message is a child of the connection for auto-cleanup. */
|
||||
- bdata = new_buffer(conn);
|
||||
+ if (!bdata)
|
||||
+ return;
|
||||
+ bdata->inhdr = true;
|
||||
+ bdata->used = 0;
|
||||
|
||||
- /*
|
||||
- * Allocation failure here is unfortunate: we have no way to
|
||||
- * tell anybody about it.
|
||||
- */
|
||||
- if (!bdata)
|
||||
- return;
|
||||
- }
|
||||
if (len <= DEFAULT_BUFFER_SIZE)
|
||||
bdata->buffer = bdata->default_buffer;
|
||||
- else
|
||||
+ else {
|
||||
bdata->buffer = talloc_array(bdata, char, len);
|
||||
- if (!bdata->buffer) {
|
||||
- if (type == XS_WATCH_EVENT) {
|
||||
- /* Same as above: no way to tell someone. */
|
||||
- talloc_free(bdata);
|
||||
+ if (!bdata->buffer) {
|
||||
+ send_error(conn, ENOMEM);
|
||||
return;
|
||||
}
|
||||
- /* re-establish request buffer for sending ENOMEM. */
|
||||
- conn->in = bdata;
|
||||
- send_error(conn, ENOMEM);
|
||||
- return;
|
||||
}
|
||||
|
||||
+ conn->in = NULL;
|
||||
+
|
||||
/* Update relevant header fields and fill in the message body. */
|
||||
bdata->hdr.msg.type = type;
|
||||
bdata->hdr.msg.len = len;
|
||||
@@ -817,8 +800,39 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
|
||||
/* Queue for later transmission. */
|
||||
list_add_tail(&bdata->list, &conn->out_list);
|
||||
+}
|
||||
|
||||
- return;
|
||||
+/*
|
||||
+ * Send a watch event.
|
||||
+ * As this is not directly related to the current command, errors can't be
|
||||
+ * reported.
|
||||
+ */
|
||||
+void send_event(struct connection *conn, const char *path, const char *token)
|
||||
+{
|
||||
+ struct buffered_data *bdata;
|
||||
+ unsigned int len;
|
||||
+
|
||||
+ len = strlen(path) + 1 + strlen(token) + 1;
|
||||
+ /* Don't try to send over-long events. */
|
||||
+ if (len > XENSTORE_PAYLOAD_MAX)
|
||||
+ return;
|
||||
+
|
||||
+ bdata = new_buffer(conn);
|
||||
+ if (!bdata)
|
||||
+ return;
|
||||
+
|
||||
+ bdata->buffer = talloc_array(bdata, char, len);
|
||||
+ if (!bdata->buffer) {
|
||||
+ talloc_free(bdata);
|
||||
+ return;
|
||||
+ }
|
||||
+ strcpy(bdata->buffer, path);
|
||||
+ strcpy(bdata->buffer + strlen(path) + 1, token);
|
||||
+ bdata->hdr.msg.type = XS_WATCH_EVENT;
|
||||
+ bdata->hdr.msg.len = len;
|
||||
+
|
||||
+ /* Queue for later transmission. */
|
||||
+ list_add_tail(&bdata->list, &conn->out_list);
|
||||
}
|
||||
|
||||
/* Some routines (write, mkdir, etc) just need a non-error return */
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index 0004fa848c83..9af9af4390bd 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -187,6 +187,7 @@ unsigned int get_string(const struct buffered_data *data, unsigned int offset);
|
||||
|
||||
void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
const void *data, unsigned int len);
|
||||
+void send_event(struct connection *conn, const char *path, const char *token);
|
||||
|
||||
/* Some routines (write, mkdir, etc) just need a non-error return */
|
||||
void send_ack(struct connection *conn, enum xsd_sockmsg_type type);
|
||||
diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
|
||||
index aca0a71bada1..99a2c266b28a 100644
|
||||
--- a/tools/xenstore/xenstored_watch.c
|
||||
+++ b/tools/xenstore/xenstored_watch.c
|
||||
@@ -86,35 +86,6 @@ static const char *get_watch_path(const struct watch *watch, const char *name)
|
||||
}
|
||||
|
||||
/*
|
||||
- * Send a watch event.
|
||||
- * Temporary memory allocations are done with ctx.
|
||||
- */
|
||||
-static void add_event(struct connection *conn,
|
||||
- const void *ctx,
|
||||
- struct watch *watch,
|
||||
- const char *name)
|
||||
-{
|
||||
- /* Data to send (node\0token\0). */
|
||||
- unsigned int len;
|
||||
- char *data;
|
||||
-
|
||||
- name = get_watch_path(watch, name);
|
||||
-
|
||||
- len = strlen(name) + 1 + strlen(watch->token) + 1;
|
||||
- /* Don't try to send over-long events. */
|
||||
- if (len > XENSTORE_PAYLOAD_MAX)
|
||||
- return;
|
||||
-
|
||||
- data = talloc_array(ctx, char, len);
|
||||
- if (!data)
|
||||
- return;
|
||||
- strcpy(data, name);
|
||||
- strcpy(data + strlen(name) + 1, watch->token);
|
||||
- send_reply(conn, XS_WATCH_EVENT, data, len);
|
||||
- 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
|
||||
@@ -190,10 +161,14 @@ void fire_watches(struct connection *conn, const void *ctx, const char *name,
|
||||
list_for_each_entry(watch, &i->watches, list) {
|
||||
if (exact) {
|
||||
if (streq(name, watch->node))
|
||||
- add_event(i, ctx, watch, name);
|
||||
+ send_event(i,
|
||||
+ get_watch_path(watch, name),
|
||||
+ watch->token);
|
||||
} else {
|
||||
if (is_child(name, watch->node))
|
||||
- add_event(i, ctx, watch, name);
|
||||
+ send_event(i,
|
||||
+ get_watch_path(watch, name),
|
||||
+ watch->token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,7 +267,7 @@ int do_watch(struct connection *conn, struct buffered_data *in)
|
||||
send_ack(conn, XS_WATCH);
|
||||
|
||||
/* We fire once up front: simplifies clients and restart. */
|
||||
- add_event(conn, in, watch, watch->node);
|
||||
+ send_event(conn, get_watch_path(watch, watch->node), watch->token);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
From 0a4c86f8a8febd85610496470123adfc4fbc1c5d Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:07 +0200
|
||||
Subject: tools/xenstore: add helpers to free struct buffered_data
|
||||
|
||||
Add two helpers for freeing struct buffered_data: free_buffered_data()
|
||||
for freeing one instance and conn_free_buffered_data() for freeing all
|
||||
instances for a connection.
|
||||
|
||||
This is avoiding duplicated code and will help later when more actions
|
||||
are needed when freeing a struct buffered_data.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 249ad5ec6fb1..527a1ebdeded 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -211,6 +211,21 @@ void reopen_log(void)
|
||||
}
|
||||
}
|
||||
|
||||
+static void free_buffered_data(struct buffered_data *out,
|
||||
+ struct connection *conn)
|
||||
+{
|
||||
+ list_del(&out->list);
|
||||
+ talloc_free(out);
|
||||
+}
|
||||
+
|
||||
+void conn_free_buffered_data(struct connection *conn)
|
||||
+{
|
||||
+ struct buffered_data *out;
|
||||
+
|
||||
+ while ((out = list_top(&conn->out_list, struct buffered_data, list)))
|
||||
+ free_buffered_data(out, conn);
|
||||
+}
|
||||
+
|
||||
static bool write_messages(struct connection *conn)
|
||||
{
|
||||
int ret;
|
||||
@@ -254,8 +269,7 @@ static bool write_messages(struct connection *conn)
|
||||
|
||||
trace_io(conn, out, 1);
|
||||
|
||||
- list_del(&out->list);
|
||||
- talloc_free(out);
|
||||
+ free_buffered_data(out, conn);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1506,18 +1520,12 @@ static struct {
|
||||
*/
|
||||
void ignore_connection(struct connection *conn)
|
||||
{
|
||||
- struct buffered_data *out, *tmp;
|
||||
-
|
||||
trace("CONN %p ignored\n", conn);
|
||||
|
||||
conn->is_ignored = true;
|
||||
conn_delete_all_watches(conn);
|
||||
conn_delete_all_transactions(conn);
|
||||
-
|
||||
- list_for_each_entry_safe(out, tmp, &conn->out_list, list) {
|
||||
- list_del(&out->list);
|
||||
- talloc_free(out);
|
||||
- }
|
||||
+ conn_free_buffered_data(conn);
|
||||
|
||||
talloc_free(conn->in);
|
||||
conn->in = NULL;
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index 9af9af4390bd..e7ee87825c3b 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -276,6 +276,8 @@ int remember_string(struct hashtable *hash, const char *str);
|
||||
|
||||
void set_tdb_key(const char *name, TDB_DATA *key);
|
||||
|
||||
+void conn_free_buffered_data(struct connection *conn);
|
||||
+
|
||||
const char *dump_state_global(FILE *fp);
|
||||
const char *dump_state_buffered_data(FILE *fp, const struct connection *c,
|
||||
struct xs_state_connection *sc);
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index d03c7d93a9e7..93c4c1edcdd1 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -411,15 +411,10 @@ static struct domain *find_domain_by_domid(unsigned int domid)
|
||||
static void domain_conn_reset(struct domain *domain)
|
||||
{
|
||||
struct connection *conn = domain->conn;
|
||||
- struct buffered_data *out;
|
||||
|
||||
conn_delete_all_watches(conn);
|
||||
conn_delete_all_transactions(conn);
|
||||
-
|
||||
- while ((out = list_top(&conn->out_list, struct buffered_data, list))) {
|
||||
- list_del(&out->list);
|
||||
- talloc_free(out);
|
||||
- }
|
||||
+ conn_free_buffered_data(conn);
|
||||
|
||||
talloc_free(conn->in);
|
||||
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
From a6c4198242bf69bea1825492b7665b559023390c Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:07 +0200
|
||||
Subject: tools/xenstore: reduce number of watch events
|
||||
|
||||
When removing a watched node outside of a transaction, two watch events
|
||||
are being produced instead of just a single one.
|
||||
|
||||
When finalizing a transaction watch events can be generated for each
|
||||
node which is being modified, even if outside a transaction such
|
||||
modifications might not have resulted in a watch event.
|
||||
|
||||
This happens e.g.:
|
||||
|
||||
- for nodes which are only modified due to added/removed child entries
|
||||
- for nodes being removed or created implicitly (e.g. creation of a/b/c
|
||||
is implicitly creating a/b, resulting in watch events for a, a/b and
|
||||
a/b/c instead of a/b/c only)
|
||||
|
||||
Avoid these additional watch events, in order to reduce the needed
|
||||
memory inside Xenstore for queueing them.
|
||||
|
||||
This is being achieved by adding event flags to struct accessed_node
|
||||
specifying whether an event should be triggered, and whether it should
|
||||
be an exact match of the modified path. Both flags can be set from
|
||||
fire_watches() instead of implying them only.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 527a1ebdeded..bf2243873901 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -1295,7 +1295,7 @@ static void delete_child(struct connection *conn,
|
||||
}
|
||||
|
||||
static int delete_node(struct connection *conn, const void *ctx,
|
||||
- struct node *parent, struct node *node)
|
||||
+ struct node *parent, struct node *node, bool watch_exact)
|
||||
{
|
||||
char *name;
|
||||
|
||||
@@ -1307,7 +1307,7 @@ static int delete_node(struct connection *conn, const void *ctx,
|
||||
node->children);
|
||||
child = name ? read_node(conn, node, name) : NULL;
|
||||
if (child) {
|
||||
- if (delete_node(conn, ctx, node, child))
|
||||
+ if (delete_node(conn, ctx, node, child, true))
|
||||
return errno;
|
||||
} else {
|
||||
trace("delete_node: Error deleting child '%s/%s'!\n",
|
||||
@@ -1319,7 +1319,12 @@ static int delete_node(struct connection *conn, const void *ctx,
|
||||
talloc_free(name);
|
||||
}
|
||||
|
||||
- fire_watches(conn, ctx, node->name, node, true, NULL);
|
||||
+ /*
|
||||
+ * 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, node->name, node, watch_exact, NULL);
|
||||
delete_node_single(conn, node);
|
||||
delete_child(conn, parent, basename(node->name));
|
||||
talloc_free(node);
|
||||
@@ -1345,13 +1350,7 @@ static int _rm(struct connection *conn, const void *ctx, struct node *node,
|
||||
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, node, false, NULL);
|
||||
- return delete_node(conn, ctx, parent, node);
|
||||
+ return delete_node(conn, ctx, parent, node, false);
|
||||
}
|
||||
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index faf6c930e42a..54432907fc76 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -130,6 +130,10 @@ struct accessed_node
|
||||
|
||||
/* Transaction node in data base? */
|
||||
bool ta_node;
|
||||
+
|
||||
+ /* Watch event flags. */
|
||||
+ bool fire_watch;
|
||||
+ bool watch_exact;
|
||||
};
|
||||
|
||||
struct changed_domain
|
||||
@@ -324,6 +328,29 @@ int access_node(struct connection *conn, struct node *node,
|
||||
}
|
||||
|
||||
/*
|
||||
+ * A watch event should be fired for a node modified inside a transaction.
|
||||
+ * Set the corresponding information. A non-exact event is replacing an exact
|
||||
+ * one, but not the other way round.
|
||||
+ */
|
||||
+void queue_watches(struct connection *conn, const char *name, bool watch_exact)
|
||||
+{
|
||||
+ struct accessed_node *i;
|
||||
+
|
||||
+ i = find_accessed_node(conn->transaction, name);
|
||||
+ if (!i) {
|
||||
+ conn->transaction->fail = true;
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ if (!i->fire_watch) {
|
||||
+ i->fire_watch = true;
|
||||
+ i->watch_exact = watch_exact;
|
||||
+ } else if (!watch_exact) {
|
||||
+ i->watch_exact = false;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
* Finalize transaction:
|
||||
* Walk through accessed nodes and check generation against global data.
|
||||
* If all entries match, read the transaction entries and write them without
|
||||
@@ -377,15 +404,15 @@ static int finalize_transaction(struct connection *conn,
|
||||
ret = tdb_store(tdb_ctx, key, data,
|
||||
TDB_REPLACE);
|
||||
talloc_free(data.dptr);
|
||||
- if (ret)
|
||||
- goto err;
|
||||
- fire_watches(conn, trans, i->node, NULL, false,
|
||||
- i->perms.p ? &i->perms : NULL);
|
||||
} else {
|
||||
- fire_watches(conn, trans, i->node, NULL, false,
|
||||
+ ret = tdb_delete(tdb_ctx, key);
|
||||
+ }
|
||||
+ if (ret)
|
||||
+ goto err;
|
||||
+ if (i->fire_watch) {
|
||||
+ fire_watches(conn, trans, i->node, NULL,
|
||||
+ i->watch_exact,
|
||||
i->perms.p ? &i->perms : NULL);
|
||||
- if (tdb_delete(tdb_ctx, key))
|
||||
- goto err;
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_transaction.h b/tools/xenstore/xenstored_transaction.h
|
||||
index 14062730e3c9..0093cac807e3 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.h
|
||||
+++ b/tools/xenstore/xenstored_transaction.h
|
||||
@@ -42,6 +42,9 @@ void transaction_entry_dec(struct transaction *trans, unsigned int domid);
|
||||
int access_node(struct connection *conn, struct node *node,
|
||||
enum node_access_type type, TDB_DATA *key);
|
||||
|
||||
+/* Queue watches for a modified node. */
|
||||
+void queue_watches(struct connection *conn, const char *name, bool watch_exact);
|
||||
+
|
||||
/* Prepend the transaction to name if appropriate. */
|
||||
int transaction_prepend(struct connection *conn, const char *name,
|
||||
TDB_DATA *key);
|
||||
diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
|
||||
index 99a2c266b28a..205d9d8ea116 100644
|
||||
--- a/tools/xenstore/xenstored_watch.c
|
||||
+++ b/tools/xenstore/xenstored_watch.c
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "xenstore_lib.h"
|
||||
#include "utils.h"
|
||||
#include "xenstored_domain.h"
|
||||
+#include "xenstored_transaction.h"
|
||||
|
||||
extern int quota_nb_watch_per_domain;
|
||||
|
||||
@@ -143,9 +144,11 @@ void fire_watches(struct connection *conn, const void *ctx, const char *name,
|
||||
struct connection *i;
|
||||
struct watch *watch;
|
||||
|
||||
- /* During transactions, don't fire watches. */
|
||||
- if (conn && conn->transaction)
|
||||
+ /* During transactions, don't fire watches, but queue them. */
|
||||
+ if (conn && conn->transaction) {
|
||||
+ queue_watches(conn, name, exact);
|
||||
return;
|
||||
+ }
|
||||
|
||||
/* Create an event for each watch. */
|
||||
list_for_each_entry(i, &connections, list) {
|
||||
|
|
@ -1,302 +0,0 @@
|
|||
From 2feed737530592688382c655680982e10951c1ec Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:07 +0200
|
||||
Subject: tools/xenstore: let unread watch events time out
|
||||
|
||||
A future modification will limit the number of outstanding requests
|
||||
for a domain, where "outstanding" means that the response of the
|
||||
request or any resulting watch event hasn't been consumed yet.
|
||||
|
||||
In order to avoid a malicious guest being capable to block other guests
|
||||
by not reading watch events, add a timeout for watch events. In case a
|
||||
watch event hasn't been consumed after this timeout, it is being
|
||||
deleted. Set the default timeout to 20 seconds (a random value being
|
||||
not too high).
|
||||
|
||||
In order to support to specify other timeout values in future, use a
|
||||
generic command line option for that purpose:
|
||||
|
||||
--timeout|-w watch-event=<seconds>
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42311.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index bf2243873901..45244c021cd3 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -108,6 +108,8 @@ int quota_max_transaction = 10;
|
||||
int quota_nb_perms_per_node = 5;
|
||||
int quota_max_path_len = XENSTORE_REL_PATH_MAX;
|
||||
|
||||
+unsigned int timeout_watch_event_msec = 20000;
|
||||
+
|
||||
void trace(const char *fmt, ...)
|
||||
{
|
||||
va_list arglist;
|
||||
@@ -211,19 +213,92 @@ void reopen_log(void)
|
||||
}
|
||||
}
|
||||
|
||||
+static uint64_t get_now_msec(void)
|
||||
+{
|
||||
+ struct timespec now_ts;
|
||||
+
|
||||
+ if (clock_gettime(CLOCK_MONOTONIC, &now_ts))
|
||||
+ barf_perror("Could not find time (clock_gettime failed)");
|
||||
+
|
||||
+ return now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000;
|
||||
+}
|
||||
+
|
||||
static void free_buffered_data(struct buffered_data *out,
|
||||
struct connection *conn)
|
||||
{
|
||||
+ struct buffered_data *req;
|
||||
+
|
||||
list_del(&out->list);
|
||||
+
|
||||
+ /*
|
||||
+ * Update conn->timeout_msec with the next found timeout value in the
|
||||
+ * queued pending requests.
|
||||
+ */
|
||||
+ if (out->timeout_msec) {
|
||||
+ conn->timeout_msec = 0;
|
||||
+ list_for_each_entry(req, &conn->out_list, list) {
|
||||
+ if (req->timeout_msec) {
|
||||
+ conn->timeout_msec = req->timeout_msec;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
talloc_free(out);
|
||||
}
|
||||
|
||||
+static void check_event_timeout(struct connection *conn, uint64_t msecs,
|
||||
+ int *ptimeout)
|
||||
+{
|
||||
+ uint64_t delta;
|
||||
+ struct buffered_data *out, *tmp;
|
||||
+
|
||||
+ if (!conn->timeout_msec)
|
||||
+ return;
|
||||
+
|
||||
+ delta = conn->timeout_msec - msecs;
|
||||
+ if (conn->timeout_msec <= msecs) {
|
||||
+ delta = 0;
|
||||
+ list_for_each_entry_safe(out, tmp, &conn->out_list, list) {
|
||||
+ /*
|
||||
+ * Only look at buffers with timeout and no data
|
||||
+ * already written to the ring.
|
||||
+ */
|
||||
+ if (out->timeout_msec && out->inhdr && !out->used) {
|
||||
+ if (out->timeout_msec > msecs) {
|
||||
+ conn->timeout_msec = out->timeout_msec;
|
||||
+ delta = conn->timeout_msec - msecs;
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ /*
|
||||
+ * Free out without updating conn->timeout_msec,
|
||||
+ * as the update is done in this loop already.
|
||||
+ */
|
||||
+ out->timeout_msec = 0;
|
||||
+ trace("watch event path %s for domain %u timed out\n",
|
||||
+ out->buffer, conn->id);
|
||||
+ free_buffered_data(out, conn);
|
||||
+ }
|
||||
+ }
|
||||
+ if (!delta) {
|
||||
+ conn->timeout_msec = 0;
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (*ptimeout == -1 || *ptimeout > delta)
|
||||
+ *ptimeout = delta;
|
||||
+}
|
||||
+
|
||||
void conn_free_buffered_data(struct connection *conn)
|
||||
{
|
||||
struct buffered_data *out;
|
||||
|
||||
while ((out = list_top(&conn->out_list, struct buffered_data, list)))
|
||||
free_buffered_data(out, conn);
|
||||
+
|
||||
+ conn->timeout_msec = 0;
|
||||
}
|
||||
|
||||
static bool write_messages(struct connection *conn)
|
||||
@@ -411,6 +486,7 @@ static void initialize_fds(int *p_sock_pollfd_idx, int *ptimeout)
|
||||
{
|
||||
struct connection *conn;
|
||||
struct wrl_timestampt now;
|
||||
+ uint64_t msecs;
|
||||
|
||||
if (fds)
|
||||
memset(fds, 0, sizeof(struct pollfd) * current_array_size);
|
||||
@@ -431,10 +507,12 @@ static void initialize_fds(int *p_sock_pollfd_idx, int *ptimeout)
|
||||
|
||||
wrl_gettime_now(&now);
|
||||
wrl_log_periodic(now);
|
||||
+ msecs = get_now_msec();
|
||||
|
||||
list_for_each_entry(conn, &connections, list) {
|
||||
if (conn->domain) {
|
||||
wrl_check_timeout(conn->domain, now, ptimeout);
|
||||
+ check_event_timeout(conn, msecs, ptimeout);
|
||||
if (conn_can_read(conn) ||
|
||||
(conn_can_write(conn) &&
|
||||
!list_empty(&conn->out_list)))
|
||||
@@ -794,6 +872,7 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
return;
|
||||
bdata->inhdr = true;
|
||||
bdata->used = 0;
|
||||
+ bdata->timeout_msec = 0;
|
||||
|
||||
if (len <= DEFAULT_BUFFER_SIZE)
|
||||
bdata->buffer = bdata->default_buffer;
|
||||
@@ -845,6 +924,12 @@ void send_event(struct connection *conn, const char *path, const char *token)
|
||||
bdata->hdr.msg.type = XS_WATCH_EVENT;
|
||||
bdata->hdr.msg.len = len;
|
||||
|
||||
+ if (timeout_watch_event_msec && domain_is_unprivileged(conn)) {
|
||||
+ bdata->timeout_msec = get_now_msec() + timeout_watch_event_msec;
|
||||
+ if (!conn->timeout_msec)
|
||||
+ conn->timeout_msec = bdata->timeout_msec;
|
||||
+ }
|
||||
+
|
||||
/* Queue for later transmission. */
|
||||
list_add_tail(&bdata->list, &conn->out_list);
|
||||
}
|
||||
@@ -2201,6 +2286,9 @@ static void usage(void)
|
||||
" -t, --transaction <nb> limit the number of transaction allowed per domain,\n"
|
||||
" -A, --perm-nb <nb> limit the number of permissions per node,\n"
|
||||
" -M, --path-max <chars> limit the allowed Xenstore node path length,\n"
|
||||
+" -w, --timeout <what>=<seconds> set the timeout in seconds for <what>,\n"
|
||||
+" allowed timeout candidates are:\n"
|
||||
+" watch-event: time a watch-event is kept pending\n"
|
||||
" -R, --no-recovery to request that no recovery should be attempted when\n"
|
||||
" the store is corrupted (debug only),\n"
|
||||
" -I, --internal-db store database in memory, not on disk\n"
|
||||
@@ -2223,6 +2311,7 @@ static struct option options[] = {
|
||||
{ "transaction", 1, NULL, 't' },
|
||||
{ "perm-nb", 1, NULL, 'A' },
|
||||
{ "path-max", 1, NULL, 'M' },
|
||||
+ { "timeout", 1, NULL, 'w' },
|
||||
{ "no-recovery", 0, NULL, 'R' },
|
||||
{ "internal-db", 0, NULL, 'I' },
|
||||
{ "verbose", 0, NULL, 'V' },
|
||||
@@ -2236,6 +2325,39 @@ int dom0_domid = 0;
|
||||
int dom0_event = 0;
|
||||
int priv_domid = 0;
|
||||
|
||||
+static int get_optval_int(const char *arg)
|
||||
+{
|
||||
+ char *end;
|
||||
+ long val;
|
||||
+
|
||||
+ val = strtol(arg, &end, 10);
|
||||
+ if (!*arg || *end || val < 0 || val > INT_MAX)
|
||||
+ barf("invalid parameter value \"%s\"\n", arg);
|
||||
+
|
||||
+ return val;
|
||||
+}
|
||||
+
|
||||
+static bool what_matches(const char *arg, const char *what)
|
||||
+{
|
||||
+ unsigned int what_len = strlen(what);
|
||||
+
|
||||
+ return !strncmp(arg, what, what_len) && arg[what_len] == '=';
|
||||
+}
|
||||
+
|
||||
+static void set_timeout(const char *arg)
|
||||
+{
|
||||
+ const char *eq = strchr(arg, '=');
|
||||
+ int val;
|
||||
+
|
||||
+ if (!eq)
|
||||
+ barf("quotas must be specified via <what>=<seconds>\n");
|
||||
+ val = get_optval_int(eq + 1);
|
||||
+ if (what_matches(arg, "watch-event"))
|
||||
+ timeout_watch_event_msec = val * 1000;
|
||||
+ else
|
||||
+ barf("unknown timeout \"%s\"\n", arg);
|
||||
+}
|
||||
+
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int opt;
|
||||
@@ -2250,7 +2372,7 @@ int main(int argc, char *argv[])
|
||||
orig_argc = argc;
|
||||
orig_argv = argv;
|
||||
|
||||
- while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:T:RVW:U", options,
|
||||
+ while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:T:RVW:w:U", options,
|
||||
NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'D':
|
||||
@@ -2300,6 +2422,9 @@ int main(int argc, char *argv[])
|
||||
quota_max_path_len = min(XENSTORE_REL_PATH_MAX,
|
||||
quota_max_path_len);
|
||||
break;
|
||||
+ case 'w':
|
||||
+ set_timeout(optarg);
|
||||
+ break;
|
||||
case 'e':
|
||||
dom0_event = strtol(optarg, NULL, 10);
|
||||
break;
|
||||
@@ -2741,6 +2866,12 @@ static void add_buffered_data(struct buffered_data *bdata,
|
||||
barf("error restoring buffered data");
|
||||
|
||||
memcpy(bdata->buffer, data, len);
|
||||
+ if (bdata->hdr.msg.type == XS_WATCH_EVENT && timeout_watch_event_msec &&
|
||||
+ domain_is_unprivileged(conn)) {
|
||||
+ bdata->timeout_msec = get_now_msec() + timeout_watch_event_msec;
|
||||
+ if (!conn->timeout_msec)
|
||||
+ conn->timeout_msec = bdata->timeout_msec;
|
||||
+ }
|
||||
|
||||
/* Queue for later transmission. */
|
||||
list_add_tail(&bdata->list, &conn->out_list);
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index e7ee87825c3b..8a81fc693f01 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
+#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "xenstore_lib.h"
|
||||
@@ -67,6 +68,8 @@ struct buffered_data
|
||||
char raw[sizeof(struct xsd_sockmsg)];
|
||||
} hdr;
|
||||
|
||||
+ uint64_t timeout_msec;
|
||||
+
|
||||
/* The actual data. */
|
||||
char *buffer;
|
||||
char default_buffer[DEFAULT_BUFFER_SIZE];
|
||||
@@ -118,6 +121,7 @@ struct connection
|
||||
|
||||
/* Buffered output data */
|
||||
struct list_head out_list;
|
||||
+ uint64_t timeout_msec;
|
||||
|
||||
/* Transaction context for current request (NULL if none). */
|
||||
struct transaction *transaction;
|
||||
@@ -244,6 +248,8 @@ extern int dom0_event;
|
||||
extern int priv_domid;
|
||||
extern int quota_nb_entry_per_domain;
|
||||
|
||||
+extern unsigned int timeout_watch_event_msec;
|
||||
+
|
||||
/* Map the kernel's xenstore page. */
|
||||
void *xenbus_map(void);
|
||||
void unmap_xenbus(void *interface);
|
||||
|
|
@ -1,443 +0,0 @@
|
|||
From 2eee122a45eb4a218596b103ce7f0759a824cf2e Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:08 +0200
|
||||
Subject: tools/xenstore: limit outstanding requests
|
||||
|
||||
Add another quota for limiting the number of outstanding requests of a
|
||||
guest. As the way to specify quotas on the command line is becoming
|
||||
rather nasty, switch to a new scheme using [--quota|-Q] <what>=<val>
|
||||
allowing to add more quotas in future easily.
|
||||
|
||||
Set the default value to 20 (basically a random value not seeming to
|
||||
be too high or too low).
|
||||
|
||||
A request is said to be outstanding if any message generated by this
|
||||
request (the direct response plus potential watch events) is not yet
|
||||
completely stored into a ring buffer. The initial watch event sent as
|
||||
a result of registering a watch is an exception.
|
||||
|
||||
Note that across a live update the relation to buffered watch events
|
||||
for other domains is lost.
|
||||
|
||||
Use talloc_zero() for allocating the domain structure in order to have
|
||||
all per-domain quota zeroed initially.
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42312.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 45244c021cd3..488d540f3a32 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -107,6 +107,7 @@ int quota_max_entry_size = 2048; /* 2K */
|
||||
int quota_max_transaction = 10;
|
||||
int quota_nb_perms_per_node = 5;
|
||||
int quota_max_path_len = XENSTORE_REL_PATH_MAX;
|
||||
+int quota_req_outstanding = 20;
|
||||
|
||||
unsigned int timeout_watch_event_msec = 20000;
|
||||
|
||||
@@ -223,12 +224,24 @@ static uint64_t get_now_msec(void)
|
||||
return now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Remove a struct buffered_data from the list of outgoing data.
|
||||
+ * A struct buffered_data related to a request having caused watch events to be
|
||||
+ * sent is kept until all those events have been written out.
|
||||
+ * Each watch event is referencing the related request via pend.req, while the
|
||||
+ * number of watch events caused by a request is kept in pend.ref.event_cnt
|
||||
+ * (those two cases are mutually exclusive, so the two fields can share memory
|
||||
+ * via a union).
|
||||
+ * The struct buffered_data is freed only if no related watch event is
|
||||
+ * referencing it. The related return data can be freed right away.
|
||||
+ */
|
||||
static void free_buffered_data(struct buffered_data *out,
|
||||
struct connection *conn)
|
||||
{
|
||||
struct buffered_data *req;
|
||||
|
||||
list_del(&out->list);
|
||||
+ out->on_out_list = false;
|
||||
|
||||
/*
|
||||
* Update conn->timeout_msec with the next found timeout value in the
|
||||
@@ -244,6 +257,30 @@ static void free_buffered_data(struct buffered_data *out,
|
||||
}
|
||||
}
|
||||
|
||||
+ if (out->hdr.msg.type == XS_WATCH_EVENT) {
|
||||
+ req = out->pend.req;
|
||||
+ if (req) {
|
||||
+ req->pend.ref.event_cnt--;
|
||||
+ if (!req->pend.ref.event_cnt && !req->on_out_list) {
|
||||
+ if (req->on_ref_list) {
|
||||
+ domain_outstanding_domid_dec(
|
||||
+ req->pend.ref.domid);
|
||||
+ list_del(&req->list);
|
||||
+ }
|
||||
+ talloc_free(req);
|
||||
+ }
|
||||
+ }
|
||||
+ } else if (out->pend.ref.event_cnt) {
|
||||
+ /* Hang out off from conn. */
|
||||
+ talloc_steal(NULL, out);
|
||||
+ if (out->buffer != out->default_buffer)
|
||||
+ talloc_free(out->buffer);
|
||||
+ list_add(&out->list, &conn->ref_list);
|
||||
+ out->on_ref_list = true;
|
||||
+ return;
|
||||
+ } else
|
||||
+ domain_outstanding_dec(conn);
|
||||
+
|
||||
talloc_free(out);
|
||||
}
|
||||
|
||||
@@ -405,6 +442,7 @@ int delay_request(struct connection *conn, struct buffered_data *in,
|
||||
static int destroy_conn(void *_conn)
|
||||
{
|
||||
struct connection *conn = _conn;
|
||||
+ struct buffered_data *req;
|
||||
|
||||
/* Flush outgoing if possible, but don't block. */
|
||||
if (!conn->domain) {
|
||||
@@ -418,6 +456,11 @@ static int destroy_conn(void *_conn)
|
||||
break;
|
||||
close(conn->fd);
|
||||
}
|
||||
+
|
||||
+ conn_free_buffered_data(conn);
|
||||
+ list_for_each_entry(req, &conn->ref_list, list)
|
||||
+ req->on_ref_list = false;
|
||||
+
|
||||
if (conn->target)
|
||||
talloc_unlink(conn, conn->target);
|
||||
list_del(&conn->list);
|
||||
@@ -893,6 +936,8 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
|
||||
/* Queue for later transmission. */
|
||||
list_add_tail(&bdata->list, &conn->out_list);
|
||||
+ bdata->on_out_list = true;
|
||||
+ domain_outstanding_inc(conn);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -900,7 +945,8 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
* As this is not directly related to the current command, errors can't be
|
||||
* reported.
|
||||
*/
|
||||
-void send_event(struct connection *conn, const char *path, const char *token)
|
||||
+void send_event(struct buffered_data *req, struct connection *conn,
|
||||
+ const char *path, const char *token)
|
||||
{
|
||||
struct buffered_data *bdata;
|
||||
unsigned int len;
|
||||
@@ -930,8 +976,13 @@ void send_event(struct connection *conn, const char *path, const char *token)
|
||||
conn->timeout_msec = bdata->timeout_msec;
|
||||
}
|
||||
|
||||
+ bdata->pend.req = req;
|
||||
+ if (req)
|
||||
+ req->pend.ref.event_cnt++;
|
||||
+
|
||||
/* Queue for later transmission. */
|
||||
list_add_tail(&bdata->list, &conn->out_list);
|
||||
+ bdata->on_out_list = true;
|
||||
}
|
||||
|
||||
/* Some routines (write, mkdir, etc) just need a non-error return */
|
||||
@@ -1740,6 +1791,7 @@ static void handle_input(struct connection *conn)
|
||||
return;
|
||||
}
|
||||
in = conn->in;
|
||||
+ in->pend.ref.domid = conn->id;
|
||||
|
||||
/* Not finished header yet? */
|
||||
if (in->inhdr) {
|
||||
@@ -1808,6 +1860,7 @@ struct connection *new_connection(const struct interface_funcs *funcs)
|
||||
new->is_stalled = false;
|
||||
new->transaction_started = 0;
|
||||
INIT_LIST_HEAD(&new->out_list);
|
||||
+ INIT_LIST_HEAD(&new->ref_list);
|
||||
INIT_LIST_HEAD(&new->watches);
|
||||
INIT_LIST_HEAD(&new->transaction_list);
|
||||
INIT_LIST_HEAD(&new->delayed);
|
||||
@@ -2286,6 +2339,9 @@ static void usage(void)
|
||||
" -t, --transaction <nb> limit the number of transaction allowed per domain,\n"
|
||||
" -A, --perm-nb <nb> limit the number of permissions per node,\n"
|
||||
" -M, --path-max <chars> limit the allowed Xenstore node path length,\n"
|
||||
+" -Q, --quota <what>=<nb> set the quota <what> to the value <nb>, allowed\n"
|
||||
+" quotas are:\n"
|
||||
+" outstanding: number of outstanding requests\n"
|
||||
" -w, --timeout <what>=<seconds> set the timeout in seconds for <what>,\n"
|
||||
" allowed timeout candidates are:\n"
|
||||
" watch-event: time a watch-event is kept pending\n"
|
||||
@@ -2311,6 +2367,7 @@ static struct option options[] = {
|
||||
{ "transaction", 1, NULL, 't' },
|
||||
{ "perm-nb", 1, NULL, 'A' },
|
||||
{ "path-max", 1, NULL, 'M' },
|
||||
+ { "quota", 1, NULL, 'Q' },
|
||||
{ "timeout", 1, NULL, 'w' },
|
||||
{ "no-recovery", 0, NULL, 'R' },
|
||||
{ "internal-db", 0, NULL, 'I' },
|
||||
@@ -2358,6 +2415,20 @@ static void set_timeout(const char *arg)
|
||||
barf("unknown timeout \"%s\"\n", arg);
|
||||
}
|
||||
|
||||
+static void set_quota(const char *arg)
|
||||
+{
|
||||
+ const char *eq = strchr(arg, '=');
|
||||
+ int val;
|
||||
+
|
||||
+ if (!eq)
|
||||
+ barf("quotas must be specified via <what>=<nb>\n");
|
||||
+ val = get_optval_int(eq + 1);
|
||||
+ if (what_matches(arg, "outstanding"))
|
||||
+ quota_req_outstanding = val;
|
||||
+ else
|
||||
+ barf("unknown quota \"%s\"\n", arg);
|
||||
+}
|
||||
+
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int opt;
|
||||
@@ -2372,8 +2443,8 @@ int main(int argc, char *argv[])
|
||||
orig_argc = argc;
|
||||
orig_argv = argv;
|
||||
|
||||
- while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:T:RVW:w:U", options,
|
||||
- NULL)) != -1) {
|
||||
+ while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:Q:T:RVW:w:U",
|
||||
+ options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'D':
|
||||
no_domain_init = true;
|
||||
@@ -2422,6 +2493,9 @@ int main(int argc, char *argv[])
|
||||
quota_max_path_len = min(XENSTORE_REL_PATH_MAX,
|
||||
quota_max_path_len);
|
||||
break;
|
||||
+ case 'Q':
|
||||
+ set_quota(optarg);
|
||||
+ break;
|
||||
case 'w':
|
||||
set_timeout(optarg);
|
||||
break;
|
||||
@@ -2875,6 +2949,14 @@ static void add_buffered_data(struct buffered_data *bdata,
|
||||
|
||||
/* Queue for later transmission. */
|
||||
list_add_tail(&bdata->list, &conn->out_list);
|
||||
+ bdata->on_out_list = true;
|
||||
+ /*
|
||||
+ * Watch events are never "outstanding", but the request causing them
|
||||
+ * are instead kept "outstanding" until all watch events caused by that
|
||||
+ * request have been delivered.
|
||||
+ */
|
||||
+ if (bdata->hdr.msg.type != XS_WATCH_EVENT)
|
||||
+ domain_outstanding_inc(conn);
|
||||
}
|
||||
|
||||
void read_state_buffered_data(const void *ctx, struct connection *conn,
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index 8a81fc693f01..db09f463a657 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -56,6 +56,8 @@ struct xs_state_connection;
|
||||
struct buffered_data
|
||||
{
|
||||
struct list_head list;
|
||||
+ bool on_out_list;
|
||||
+ bool on_ref_list;
|
||||
|
||||
/* Are we still doing the header? */
|
||||
bool inhdr;
|
||||
@@ -63,6 +65,17 @@ struct buffered_data
|
||||
/* How far are we? */
|
||||
unsigned int used;
|
||||
|
||||
+ /* Outstanding request accounting. */
|
||||
+ union {
|
||||
+ /* ref is being used for requests. */
|
||||
+ struct {
|
||||
+ unsigned int event_cnt; /* # of outstanding events. */
|
||||
+ unsigned int domid; /* domid of request. */
|
||||
+ } ref;
|
||||
+ /* req is being used for watch events. */
|
||||
+ struct buffered_data *req; /* request causing event. */
|
||||
+ } pend;
|
||||
+
|
||||
union {
|
||||
struct xsd_sockmsg msg;
|
||||
char raw[sizeof(struct xsd_sockmsg)];
|
||||
@@ -123,6 +136,9 @@ struct connection
|
||||
struct list_head out_list;
|
||||
uint64_t timeout_msec;
|
||||
|
||||
+ /* Referenced requests no longer pending. */
|
||||
+ struct list_head ref_list;
|
||||
+
|
||||
/* Transaction context for current request (NULL if none). */
|
||||
struct transaction *transaction;
|
||||
|
||||
@@ -191,7 +207,8 @@ unsigned int get_string(const struct buffered_data *data, unsigned int offset);
|
||||
|
||||
void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
const void *data, unsigned int len);
|
||||
-void send_event(struct connection *conn, const char *path, const char *token);
|
||||
+void send_event(struct buffered_data *req, struct connection *conn,
|
||||
+ const char *path, const char *token);
|
||||
|
||||
/* Some routines (write, mkdir, etc) just need a non-error return */
|
||||
void send_ack(struct connection *conn, enum xsd_sockmsg_type type);
|
||||
@@ -247,6 +264,7 @@ extern int dom0_domid;
|
||||
extern int dom0_event;
|
||||
extern int priv_domid;
|
||||
extern int quota_nb_entry_per_domain;
|
||||
+extern int quota_req_outstanding;
|
||||
|
||||
extern unsigned int timeout_watch_event_msec;
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index 93c4c1edcdd1..850085a92c76 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -78,6 +78,9 @@ struct domain
|
||||
/* number of watch for this domain */
|
||||
int nbwatch;
|
||||
|
||||
+ /* Number of outstanding requests. */
|
||||
+ int nboutstanding;
|
||||
+
|
||||
/* write rate limit */
|
||||
wrl_creditt wrl_credit; /* [ -wrl_config_writecost, +_dburst ] */
|
||||
struct wrl_timestampt wrl_timestamp;
|
||||
@@ -183,8 +186,12 @@ static bool domain_can_read(struct connection *conn)
|
||||
{
|
||||
struct xenstore_domain_interface *intf = conn->domain->interface;
|
||||
|
||||
- if (domain_is_unprivileged(conn) && conn->domain->wrl_credit < 0)
|
||||
- return false;
|
||||
+ if (domain_is_unprivileged(conn)) {
|
||||
+ if (conn->domain->wrl_credit < 0)
|
||||
+ return false;
|
||||
+ if (conn->domain->nboutstanding >= quota_req_outstanding)
|
||||
+ return false;
|
||||
+ }
|
||||
|
||||
return (intf->req_cons != intf->req_prod);
|
||||
}
|
||||
@@ -331,7 +338,7 @@ static struct domain *alloc_domain(const void *context, unsigned int domid)
|
||||
{
|
||||
struct domain *domain;
|
||||
|
||||
- domain = talloc(context, struct domain);
|
||||
+ domain = talloc_zero(context, struct domain);
|
||||
if (!domain) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
@@ -392,9 +399,6 @@ static int new_domain(struct domain *domain, int port, bool restore)
|
||||
domain->conn->domain = domain;
|
||||
domain->conn->id = domain->domid;
|
||||
|
||||
- domain->nbentry = 0;
|
||||
- domain->nbwatch = 0;
|
||||
-
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -938,6 +942,28 @@ int domain_watch(struct connection *conn)
|
||||
: 0;
|
||||
}
|
||||
|
||||
+void domain_outstanding_inc(struct connection *conn)
|
||||
+{
|
||||
+ if (!conn || !conn->domain)
|
||||
+ return;
|
||||
+ conn->domain->nboutstanding++;
|
||||
+}
|
||||
+
|
||||
+void domain_outstanding_dec(struct connection *conn)
|
||||
+{
|
||||
+ if (!conn || !conn->domain)
|
||||
+ return;
|
||||
+ conn->domain->nboutstanding--;
|
||||
+}
|
||||
+
|
||||
+void domain_outstanding_domid_dec(unsigned int domid)
|
||||
+{
|
||||
+ struct domain *d = find_domain_by_domid(domid);
|
||||
+
|
||||
+ if (d)
|
||||
+ d->nboutstanding--;
|
||||
+}
|
||||
+
|
||||
static wrl_creditt wrl_config_writecost = WRL_FACTOR;
|
||||
static wrl_creditt wrl_config_rate = WRL_RATE * WRL_FACTOR;
|
||||
static wrl_creditt wrl_config_dburst = WRL_DBURST * WRL_FACTOR;
|
||||
diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
|
||||
index 1e929b8f8c6f..4f51b005291a 100644
|
||||
--- a/tools/xenstore/xenstored_domain.h
|
||||
+++ b/tools/xenstore/xenstored_domain.h
|
||||
@@ -64,6 +64,9 @@ int domain_entry(struct connection *conn);
|
||||
void domain_watch_inc(struct connection *conn);
|
||||
void domain_watch_dec(struct connection *conn);
|
||||
int domain_watch(struct connection *conn);
|
||||
+void domain_outstanding_inc(struct connection *conn);
|
||||
+void domain_outstanding_dec(struct connection *conn);
|
||||
+void domain_outstanding_domid_dec(unsigned int domid);
|
||||
|
||||
/* Special node permission handling. */
|
||||
int set_perms_special(struct connection *conn, const char *name,
|
||||
diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
|
||||
index 205d9d8ea116..0755ffa375ba 100644
|
||||
--- a/tools/xenstore/xenstored_watch.c
|
||||
+++ b/tools/xenstore/xenstored_watch.c
|
||||
@@ -142,6 +142,7 @@ void fire_watches(struct connection *conn, const void *ctx, const char *name,
|
||||
struct node *node, bool exact, struct node_perms *perms)
|
||||
{
|
||||
struct connection *i;
|
||||
+ struct buffered_data *req;
|
||||
struct watch *watch;
|
||||
|
||||
/* During transactions, don't fire watches, but queue them. */
|
||||
@@ -150,6 +151,8 @@ void fire_watches(struct connection *conn, const void *ctx, const char *name,
|
||||
return;
|
||||
}
|
||||
|
||||
+ req = domain_is_unprivileged(conn) ? conn->in : NULL;
|
||||
+
|
||||
/* Create an event for each watch. */
|
||||
list_for_each_entry(i, &connections, list) {
|
||||
/* introduce/release domain watches */
|
||||
@@ -164,12 +167,12 @@ void fire_watches(struct connection *conn, const void *ctx, const char *name,
|
||||
list_for_each_entry(watch, &i->watches, list) {
|
||||
if (exact) {
|
||||
if (streq(name, watch->node))
|
||||
- send_event(i,
|
||||
+ send_event(req, i,
|
||||
get_watch_path(watch, name),
|
||||
watch->token);
|
||||
} else {
|
||||
if (is_child(name, watch->node))
|
||||
- send_event(i,
|
||||
+ send_event(req, i,
|
||||
get_watch_path(watch, name),
|
||||
watch->token);
|
||||
}
|
||||
@@ -269,8 +272,12 @@ int do_watch(struct connection *conn, struct buffered_data *in)
|
||||
trace_create(watch, "watch");
|
||||
send_ack(conn, XS_WATCH);
|
||||
|
||||
- /* We fire once up front: simplifies clients and restart. */
|
||||
- send_event(conn, get_watch_path(watch, watch->node), watch->token);
|
||||
+ /*
|
||||
+ * We fire once up front: simplifies clients and restart.
|
||||
+ * This event will not be linked to the XS_WATCH request.
|
||||
+ */
|
||||
+ send_event(NULL, conn, get_watch_path(watch, watch->node),
|
||||
+ watch->token);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
From c8057cb483abf2cd4060b39616423e19283fbd0a Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:08 +0200
|
||||
Subject: tools/xenstore: don't buffer multiple identical watch events
|
||||
|
||||
A guest not reading its Xenstore response buffer fast enough might
|
||||
pile up lots of Xenstore watch events buffered. Reduce the generated
|
||||
load by dropping new events which already have an identical copy
|
||||
pending.
|
||||
|
||||
The special events "@..." are excluded from that handling as there are
|
||||
known use cases where the handler is relying on each event to be sent
|
||||
individually.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 488d540f3a32..f1fa97b8cf50 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -916,6 +916,7 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
bdata->inhdr = true;
|
||||
bdata->used = 0;
|
||||
bdata->timeout_msec = 0;
|
||||
+ bdata->watch_event = false;
|
||||
|
||||
if (len <= DEFAULT_BUFFER_SIZE)
|
||||
bdata->buffer = bdata->default_buffer;
|
||||
@@ -948,7 +949,7 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
void send_event(struct buffered_data *req, struct connection *conn,
|
||||
const char *path, const char *token)
|
||||
{
|
||||
- struct buffered_data *bdata;
|
||||
+ struct buffered_data *bdata, *bd;
|
||||
unsigned int len;
|
||||
|
||||
len = strlen(path) + 1 + strlen(token) + 1;
|
||||
@@ -970,12 +971,29 @@ void send_event(struct buffered_data *req, struct connection *conn,
|
||||
bdata->hdr.msg.type = XS_WATCH_EVENT;
|
||||
bdata->hdr.msg.len = len;
|
||||
|
||||
+ /*
|
||||
+ * Check whether an identical event is pending already.
|
||||
+ * Special events are excluded from that check.
|
||||
+ */
|
||||
+ if (path[0] != '@') {
|
||||
+ list_for_each_entry(bd, &conn->out_list, list) {
|
||||
+ if (bd->watch_event && bd->hdr.msg.len == len &&
|
||||
+ !memcmp(bdata->buffer, bd->buffer, len)) {
|
||||
+ trace("dropping duplicate watch %s %s for domain %u\n",
|
||||
+ path, token, conn->id);
|
||||
+ talloc_free(bdata);
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
if (timeout_watch_event_msec && domain_is_unprivileged(conn)) {
|
||||
bdata->timeout_msec = get_now_msec() + timeout_watch_event_msec;
|
||||
if (!conn->timeout_msec)
|
||||
conn->timeout_msec = bdata->timeout_msec;
|
||||
}
|
||||
|
||||
+ bdata->watch_event = true;
|
||||
bdata->pend.req = req;
|
||||
if (req)
|
||||
req->pend.ref.event_cnt++;
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index db09f463a657..b9b50e81c7b4 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -62,6 +62,9 @@ struct buffered_data
|
||||
/* Are we still doing the header? */
|
||||
bool inhdr;
|
||||
|
||||
+ /* Is this a watch event? */
|
||||
+ bool watch_event;
|
||||
+
|
||||
/* How far are we? */
|
||||
unsigned int used;
|
||||
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
From 5eac692b841633be3e85f0125c59fa02af103989 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:08 +0200
|
||||
Subject: tools/xenstore: fix connection->id usage
|
||||
|
||||
Don't use conn->id for privilege checks, but domain_is_unprivileged().
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_control.c b/tools/xenstore/xenstored_control.c
|
||||
index 7b4300ef7777..adb8d51b043b 100644
|
||||
--- a/tools/xenstore/xenstored_control.c
|
||||
+++ b/tools/xenstore/xenstored_control.c
|
||||
@@ -891,7 +891,7 @@ int do_control(struct connection *conn, struct buffered_data *in)
|
||||
unsigned int cmd, num, off;
|
||||
char **vec = NULL;
|
||||
|
||||
- if (conn->id != 0)
|
||||
+ if (domain_is_unprivileged(conn))
|
||||
return EACCES;
|
||||
|
||||
off = get_string(in, 0);
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index b9b50e81c7b4..b1a70488b989 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -123,7 +123,7 @@ struct connection
|
||||
/* The index of pollfd in global pollfd array */
|
||||
int pollfd_idx;
|
||||
|
||||
- /* Who am I? 0 for socket connections. */
|
||||
+ /* Who am I? Domid of connection. */
|
||||
unsigned int id;
|
||||
|
||||
/* Is this connection ignored? */
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index 54432907fc76..ee1b09031a3b 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -477,7 +477,8 @@ int do_transaction_start(struct connection *conn, struct buffered_data *in)
|
||||
if (conn->transaction)
|
||||
return EBUSY;
|
||||
|
||||
- if (conn->id && conn->transaction_started > quota_max_transaction)
|
||||
+ if (domain_is_unprivileged(conn) &&
|
||||
+ conn->transaction_started > quota_max_transaction)
|
||||
return ENOSPC;
|
||||
|
||||
/* Attach transaction to input for autofree until it's complete */
|
||||
|
|
@ -1,326 +0,0 @@
|
|||
From f9f3171441b5fcb3339cf612400794fc26cd2ec2 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:08 +0200
|
||||
Subject: tools/xenstore: simplify and fix per domain node accounting
|
||||
|
||||
The accounting of nodes can be simplified now that each connection
|
||||
holds the associated domid.
|
||||
|
||||
Fix the node accounting to cover nodes created for a domain before it
|
||||
has been introduced. This requires to react properly to an allocation
|
||||
failure inside domain_entry_inc() by returning an error code.
|
||||
|
||||
Especially in error paths the node accounting has to be fixed in some
|
||||
cases.
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42313.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index f1fa97b8cf50..692d863fce35 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -638,7 +638,7 @@ struct node *read_node(struct connection *conn, const void *ctx,
|
||||
|
||||
/* Permissions are struct xs_permissions. */
|
||||
node->perms.p = hdr->perms;
|
||||
- if (domain_adjust_node_perms(node)) {
|
||||
+ if (domain_adjust_node_perms(conn, node)) {
|
||||
talloc_free(node);
|
||||
return NULL;
|
||||
}
|
||||
@@ -660,7 +660,7 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
|
||||
void *p;
|
||||
struct xs_tdb_record_hdr *hdr;
|
||||
|
||||
- if (domain_adjust_node_perms(node))
|
||||
+ if (domain_adjust_node_perms(conn, node))
|
||||
return errno;
|
||||
|
||||
data.dsize = sizeof(*hdr)
|
||||
@@ -1272,13 +1272,17 @@ static struct node *construct_node(struct connection *conn, const void *ctx,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
-static int destroy_node(struct connection *conn, struct node *node)
|
||||
+static void destroy_node_rm(struct node *node)
|
||||
{
|
||||
if (streq(node->name, "/"))
|
||||
corrupt(NULL, "Destroying root node!");
|
||||
|
||||
tdb_delete(tdb_ctx, node->key);
|
||||
+}
|
||||
|
||||
+static int destroy_node(struct connection *conn, struct node *node)
|
||||
+{
|
||||
+ destroy_node_rm(node);
|
||||
domain_entry_dec(conn, node);
|
||||
|
||||
/*
|
||||
@@ -1328,8 +1332,12 @@ static struct node *create_node(struct connection *conn, const void *ctx,
|
||||
goto err;
|
||||
|
||||
/* Account for new node */
|
||||
- if (i->parent)
|
||||
- domain_entry_inc(conn, i);
|
||||
+ if (i->parent) {
|
||||
+ if (domain_entry_inc(conn, i)) {
|
||||
+ destroy_node_rm(i);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -1614,10 +1622,27 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
|
||||
old_perms = node->perms;
|
||||
domain_entry_dec(conn, node);
|
||||
node->perms = perms;
|
||||
- domain_entry_inc(conn, node);
|
||||
+ if (domain_entry_inc(conn, node)) {
|
||||
+ node->perms = old_perms;
|
||||
+ /*
|
||||
+ * This should never fail because we had a reference on the
|
||||
+ * domain before and Xenstored is single-threaded.
|
||||
+ */
|
||||
+ domain_entry_inc(conn, node);
|
||||
+ return ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ if (write_node(conn, node, false)) {
|
||||
+ int saved_errno = errno;
|
||||
|
||||
- if (write_node(conn, node, false))
|
||||
+ domain_entry_dec(conn, node);
|
||||
+ node->perms = old_perms;
|
||||
+ /* No failure possible as above. */
|
||||
+ domain_entry_inc(conn, node);
|
||||
+
|
||||
+ errno = saved_errno;
|
||||
return errno;
|
||||
+ }
|
||||
|
||||
fire_watches(conn, in, name, node, false, &old_perms);
|
||||
send_ack(conn, XS_SET_PERMS);
|
||||
@@ -3122,7 +3147,9 @@ void read_state_node(const void *ctx, const void *state)
|
||||
set_tdb_key(name, &key);
|
||||
if (write_node_raw(NULL, &key, node, true))
|
||||
barf("write node error restoring node");
|
||||
- domain_entry_inc(&conn, node);
|
||||
+
|
||||
+ if (domain_entry_inc(&conn, node))
|
||||
+ barf("node accounting error restoring node");
|
||||
|
||||
talloc_free(node);
|
||||
}
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index 850085a92c76..260952e09096 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -16,6 +16,7 @@
|
||||
along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
+#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
@@ -363,6 +364,18 @@ static struct domain *find_or_alloc_domain(const void *ctx, unsigned int domid)
|
||||
return domain ? : alloc_domain(ctx, domid);
|
||||
}
|
||||
|
||||
+static struct domain *find_or_alloc_existing_domain(unsigned int domid)
|
||||
+{
|
||||
+ struct domain *domain;
|
||||
+ xc_dominfo_t dominfo;
|
||||
+
|
||||
+ domain = find_domain_struct(domid);
|
||||
+ if (!domain && get_domain_info(domid, &dominfo))
|
||||
+ domain = alloc_domain(NULL, domid);
|
||||
+
|
||||
+ return domain;
|
||||
+}
|
||||
+
|
||||
static int new_domain(struct domain *domain, int port, bool restore)
|
||||
{
|
||||
int rc;
|
||||
@@ -782,30 +795,28 @@ void domain_deinit(void)
|
||||
xenevtchn_unbind(xce_handle, virq_port);
|
||||
}
|
||||
|
||||
-void domain_entry_inc(struct connection *conn, struct node *node)
|
||||
+int domain_entry_inc(struct connection *conn, struct node *node)
|
||||
{
|
||||
struct domain *d;
|
||||
+ unsigned int domid;
|
||||
|
||||
if (!conn)
|
||||
- return;
|
||||
+ return 0;
|
||||
|
||||
- if (node->perms.p && node->perms.p[0].id != conn->id) {
|
||||
- if (conn->transaction) {
|
||||
- transaction_entry_inc(conn->transaction,
|
||||
- node->perms.p[0].id);
|
||||
- } else {
|
||||
- d = find_domain_by_domid(node->perms.p[0].id);
|
||||
- if (d)
|
||||
- d->nbentry++;
|
||||
- }
|
||||
- } else if (conn->domain) {
|
||||
- if (conn->transaction) {
|
||||
- transaction_entry_inc(conn->transaction,
|
||||
- conn->domain->domid);
|
||||
- } else {
|
||||
- conn->domain->nbentry++;
|
||||
- }
|
||||
+ domid = node->perms.p ? node->perms.p[0].id : conn->id;
|
||||
+
|
||||
+ if (conn->transaction) {
|
||||
+ transaction_entry_inc(conn->transaction, domid);
|
||||
+ } else {
|
||||
+ d = (domid == conn->id && conn->domain) ? conn->domain
|
||||
+ : find_or_alloc_existing_domain(domid);
|
||||
+ if (d)
|
||||
+ d->nbentry++;
|
||||
+ else
|
||||
+ return ENOMEM;
|
||||
}
|
||||
+
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -841,7 +852,7 @@ static int chk_domain_generation(unsigned int domid, uint64_t gen)
|
||||
* Remove permissions for no longer existing domains in order to avoid a new
|
||||
* domain with the same domid inheriting the permissions.
|
||||
*/
|
||||
-int domain_adjust_node_perms(struct node *node)
|
||||
+int domain_adjust_node_perms(struct connection *conn, struct node *node)
|
||||
{
|
||||
unsigned int i;
|
||||
int ret;
|
||||
@@ -851,8 +862,14 @@ int domain_adjust_node_perms(struct node *node)
|
||||
return errno;
|
||||
|
||||
/* If the owner doesn't exist any longer give it to priv domain. */
|
||||
- if (!ret)
|
||||
+ if (!ret) {
|
||||
+ /*
|
||||
+ * In theory we'd need to update the number of dom0 nodes here,
|
||||
+ * but we could be called for a read of the node. So better
|
||||
+ * avoid the risk to overflow the node count of dom0.
|
||||
+ */
|
||||
node->perms.p[0].id = priv_domid;
|
||||
+ }
|
||||
|
||||
for (i = 1; i < node->perms.num; i++) {
|
||||
if (node->perms.p[i].perms & XS_PERM_IGNORE)
|
||||
@@ -871,25 +888,25 @@ int domain_adjust_node_perms(struct node *node)
|
||||
void domain_entry_dec(struct connection *conn, struct node *node)
|
||||
{
|
||||
struct domain *d;
|
||||
+ unsigned int domid;
|
||||
|
||||
if (!conn)
|
||||
return;
|
||||
|
||||
- if (node->perms.p && node->perms.p[0].id != conn->id) {
|
||||
- if (conn->transaction) {
|
||||
- transaction_entry_dec(conn->transaction,
|
||||
- node->perms.p[0].id);
|
||||
- } else {
|
||||
- d = find_domain_by_domid(node->perms.p[0].id);
|
||||
- if (d && d->nbentry)
|
||||
- d->nbentry--;
|
||||
- }
|
||||
- } else if (conn->domain && conn->domain->nbentry) {
|
||||
- if (conn->transaction) {
|
||||
- transaction_entry_dec(conn->transaction,
|
||||
- conn->domain->domid);
|
||||
+ domid = node->perms.p ? node->perms.p[0].id : conn->id;
|
||||
+
|
||||
+ if (conn->transaction) {
|
||||
+ transaction_entry_dec(conn->transaction, domid);
|
||||
+ } else {
|
||||
+ d = (domid == conn->id && conn->domain) ? conn->domain
|
||||
+ : find_domain_struct(domid);
|
||||
+ if (d) {
|
||||
+ d->nbentry--;
|
||||
} else {
|
||||
- conn->domain->nbentry--;
|
||||
+ errno = ENOENT;
|
||||
+ corrupt(conn,
|
||||
+ "Node \"%s\" owned by non-existing domain %u\n",
|
||||
+ node->name, domid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -899,13 +916,23 @@ int domain_entry_fix(unsigned int domid, int num, bool update)
|
||||
struct domain *d;
|
||||
int cnt;
|
||||
|
||||
- d = find_domain_by_domid(domid);
|
||||
- if (!d)
|
||||
- return 0;
|
||||
+ if (update) {
|
||||
+ d = find_domain_struct(domid);
|
||||
+ assert(d);
|
||||
+ } else {
|
||||
+ /*
|
||||
+ * We are called first with update == false in order to catch
|
||||
+ * any error. So do a possible allocation and check for error
|
||||
+ * only in this case, as in the case of update == true nothing
|
||||
+ * can go wrong anymore as the allocation already happened.
|
||||
+ */
|
||||
+ d = find_or_alloc_existing_domain(domid);
|
||||
+ if (!d)
|
||||
+ return -1;
|
||||
+ }
|
||||
|
||||
cnt = d->nbentry + num;
|
||||
- if (cnt < 0)
|
||||
- cnt = 0;
|
||||
+ assert(cnt >= 0);
|
||||
|
||||
if (update)
|
||||
d->nbentry = cnt;
|
||||
diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
|
||||
index 4f51b005291a..d6519904d831 100644
|
||||
--- a/tools/xenstore/xenstored_domain.h
|
||||
+++ b/tools/xenstore/xenstored_domain.h
|
||||
@@ -54,10 +54,10 @@ const char *get_implicit_path(const struct connection *conn);
|
||||
bool domain_is_unprivileged(struct connection *conn);
|
||||
|
||||
/* Remove node permissions for no longer existing domains. */
|
||||
-int domain_adjust_node_perms(struct node *node);
|
||||
+int domain_adjust_node_perms(struct connection *conn, struct node *node);
|
||||
|
||||
/* Quota manipulation */
|
||||
-void domain_entry_inc(struct connection *conn, struct node *);
|
||||
+int domain_entry_inc(struct connection *conn, struct node *);
|
||||
void domain_entry_dec(struct connection *conn, struct node *);
|
||||
int domain_entry_fix(unsigned int domid, int num, bool update);
|
||||
int domain_entry(struct connection *conn);
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index ee1b09031a3b..86caf6c398be 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -519,8 +519,12 @@ static int transaction_fix_domains(struct transaction *trans, bool update)
|
||||
|
||||
list_for_each_entry(d, &trans->changed_domains, list) {
|
||||
cnt = domain_entry_fix(d->domid, d->nbentry, update);
|
||||
- if (!update && cnt >= quota_nb_entry_per_domain)
|
||||
- return ENOSPC;
|
||||
+ if (!update) {
|
||||
+ if (cnt >= quota_nb_entry_per_domain)
|
||||
+ return ENOSPC;
|
||||
+ if (cnt < 0)
|
||||
+ return ENOMEM;
|
||||
+ }
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -1,244 +0,0 @@
|
|||
From 71aac6f7e89d5c101adb9e82eea7031e16d34e46 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:09 +0200
|
||||
Subject: tools/xenstore: limit max number of nodes accessed in a transaction
|
||||
|
||||
Today a guest is free to access as many nodes in a single transaction
|
||||
as it wants. This can lead to unbounded memory consumption in Xenstore
|
||||
as there is the need to keep track of all nodes having been accessed
|
||||
during a transaction.
|
||||
|
||||
In oxenstored the number of requests in a transaction is being limited
|
||||
via a quota maxrequests (default is 1024). As multiple accesses of a
|
||||
node are not problematic in C Xenstore, limit the number of accessed
|
||||
nodes.
|
||||
|
||||
In order to let read_node() detect a quota error in case too many nodes
|
||||
are being accessed, check the return value of access_node() and return
|
||||
NULL in case an error has been seen. Introduce __must_check and add it
|
||||
to the access_node() prototype.
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42314.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Suggested-by: Julien Grall <julien@xen.org>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/include/xen-tools/libs.h b/tools/include/xen-tools/libs.h
|
||||
index a16e0c380709..bafc90e2f603 100644
|
||||
--- a/tools/include/xen-tools/libs.h
|
||||
+++ b/tools/include/xen-tools/libs.h
|
||||
@@ -63,4 +63,8 @@
|
||||
#define ROUNDUP(_x,_w) (((unsigned long)(_x)+(1UL<<(_w))-1) & ~((1UL<<(_w))-1))
|
||||
#endif
|
||||
|
||||
+#ifndef __must_check
|
||||
+#define __must_check __attribute__((__warn_unused_result__))
|
||||
+#endif
|
||||
+
|
||||
#endif /* __XEN_TOOLS_LIBS__ */
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 692d863fce35..f835aa1b2f1f 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -106,6 +106,7 @@ int quota_nb_watch_per_domain = 128;
|
||||
int quota_max_entry_size = 2048; /* 2K */
|
||||
int quota_max_transaction = 10;
|
||||
int quota_nb_perms_per_node = 5;
|
||||
+int quota_trans_nodes = 1024;
|
||||
int quota_max_path_len = XENSTORE_REL_PATH_MAX;
|
||||
int quota_req_outstanding = 20;
|
||||
|
||||
@@ -595,6 +596,7 @@ struct node *read_node(struct connection *conn, const void *ctx,
|
||||
TDB_DATA key, data;
|
||||
struct xs_tdb_record_hdr *hdr;
|
||||
struct node *node;
|
||||
+ int err;
|
||||
|
||||
node = talloc(ctx, struct node);
|
||||
if (!node) {
|
||||
@@ -616,14 +618,13 @@ struct node *read_node(struct connection *conn, const void *ctx,
|
||||
if (data.dptr == NULL) {
|
||||
if (tdb_error(tdb_ctx) == TDB_ERR_NOEXIST) {
|
||||
node->generation = NO_GENERATION;
|
||||
- access_node(conn, node, NODE_ACCESS_READ, NULL);
|
||||
- errno = ENOENT;
|
||||
+ err = access_node(conn, node, NODE_ACCESS_READ, NULL);
|
||||
+ errno = err ? : ENOENT;
|
||||
} else {
|
||||
log("TDB error on read: %s", tdb_errorstr(tdb_ctx));
|
||||
errno = EIO;
|
||||
}
|
||||
- talloc_free(node);
|
||||
- return NULL;
|
||||
+ goto error;
|
||||
}
|
||||
|
||||
node->parent = NULL;
|
||||
@@ -638,19 +639,36 @@ struct node *read_node(struct connection *conn, const void *ctx,
|
||||
|
||||
/* Permissions are struct xs_permissions. */
|
||||
node->perms.p = hdr->perms;
|
||||
- if (domain_adjust_node_perms(conn, node)) {
|
||||
- talloc_free(node);
|
||||
- return NULL;
|
||||
- }
|
||||
+ if (domain_adjust_node_perms(conn, node))
|
||||
+ goto error;
|
||||
|
||||
/* Data is binary blob (usually ascii, no nul). */
|
||||
node->data = node->perms.p + hdr->num_perms;
|
||||
/* Children is strings, nul separated. */
|
||||
node->children = node->data + node->datalen;
|
||||
|
||||
- access_node(conn, node, NODE_ACCESS_READ, NULL);
|
||||
+ if (access_node(conn, node, NODE_ACCESS_READ, NULL))
|
||||
+ goto error;
|
||||
|
||||
return node;
|
||||
+
|
||||
+ error:
|
||||
+ err = errno;
|
||||
+ talloc_free(node);
|
||||
+ errno = err;
|
||||
+ return NULL;
|
||||
+}
|
||||
+
|
||||
+static bool read_node_can_propagate_errno(void)
|
||||
+{
|
||||
+ /*
|
||||
+ * 2 error cases for read_node() can always be propagated up:
|
||||
+ * ENOMEM, because this has nothing to do with the node being in the
|
||||
+ * data base or not, but is caused by a general lack of memory.
|
||||
+ * ENOSPC, because this is related to hitting quota limits which need
|
||||
+ * to be respected.
|
||||
+ */
|
||||
+ return errno == ENOMEM || errno == ENOSPC;
|
||||
}
|
||||
|
||||
int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
|
||||
@@ -767,7 +785,7 @@ static int ask_parents(struct connection *conn, const void *ctx,
|
||||
node = read_node(conn, ctx, name);
|
||||
if (node)
|
||||
break;
|
||||
- if (errno == ENOMEM)
|
||||
+ if (read_node_can_propagate_errno())
|
||||
return errno;
|
||||
} while (!streq(name, "/"));
|
||||
|
||||
@@ -829,7 +847,7 @@ static struct node *get_node(struct connection *conn,
|
||||
}
|
||||
}
|
||||
/* Clean up errno if they weren't supposed to know. */
|
||||
- if (!node && errno != ENOMEM)
|
||||
+ if (!node && !read_node_can_propagate_errno())
|
||||
errno = errno_from_parents(conn, ctx, name, errno, perm);
|
||||
return node;
|
||||
}
|
||||
@@ -1235,7 +1253,7 @@ static struct node *construct_node(struct connection *conn, const void *ctx,
|
||||
|
||||
/* If parent doesn't exist, create it. */
|
||||
parent = read_node(conn, parentname, parentname);
|
||||
- if (!parent)
|
||||
+ if (!parent && errno == ENOENT)
|
||||
parent = construct_node(conn, ctx, parentname);
|
||||
if (!parent)
|
||||
return NULL;
|
||||
@@ -1509,7 +1527,7 @@ 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;
|
||||
+ return read_node_can_propagate_errno() ? errno : EINVAL;
|
||||
node->parent = parent;
|
||||
|
||||
return delete_node(conn, ctx, parent, node, false);
|
||||
@@ -1539,7 +1557,7 @@ static int do_rm(struct connection *conn, struct buffered_data *in)
|
||||
return 0;
|
||||
}
|
||||
/* Restore errno, just in case. */
|
||||
- if (errno != ENOMEM)
|
||||
+ if (!read_node_can_propagate_errno())
|
||||
errno = ENOENT;
|
||||
}
|
||||
return errno;
|
||||
@@ -2384,6 +2402,8 @@ static void usage(void)
|
||||
" -M, --path-max <chars> limit the allowed Xenstore node path length,\n"
|
||||
" -Q, --quota <what>=<nb> set the quota <what> to the value <nb>, allowed\n"
|
||||
" quotas are:\n"
|
||||
+" transaction-nodes: number of accessed node per\n"
|
||||
+" transaction\n"
|
||||
" outstanding: number of outstanding requests\n"
|
||||
" -w, --timeout <what>=<seconds> set the timeout in seconds for <what>,\n"
|
||||
" allowed timeout candidates are:\n"
|
||||
@@ -2468,6 +2488,8 @@ static void set_quota(const char *arg)
|
||||
val = get_optval_int(eq + 1);
|
||||
if (what_matches(arg, "outstanding"))
|
||||
quota_req_outstanding = val;
|
||||
+ else if (what_matches(arg, "transaction-nodes"))
|
||||
+ quota_trans_nodes = val;
|
||||
else
|
||||
barf("unknown quota \"%s\"\n", arg);
|
||||
}
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index b1a70488b989..245f9258235f 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -268,6 +268,7 @@ extern int dom0_event;
|
||||
extern int priv_domid;
|
||||
extern int quota_nb_entry_per_domain;
|
||||
extern int quota_req_outstanding;
|
||||
+extern int quota_trans_nodes;
|
||||
|
||||
extern unsigned int timeout_watch_event_msec;
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index 86caf6c398be..7bd41eb475e3 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -156,6 +156,9 @@ struct transaction
|
||||
/* Connection-local identifier for this transaction. */
|
||||
uint32_t id;
|
||||
|
||||
+ /* Node counter. */
|
||||
+ unsigned int nodes;
|
||||
+
|
||||
/* Generation when transaction started. */
|
||||
uint64_t generation;
|
||||
|
||||
@@ -260,6 +263,11 @@ int access_node(struct connection *conn, struct node *node,
|
||||
|
||||
i = find_accessed_node(trans, node->name);
|
||||
if (!i) {
|
||||
+ if (trans->nodes >= quota_trans_nodes &&
|
||||
+ domain_is_unprivileged(conn)) {
|
||||
+ ret = ENOSPC;
|
||||
+ goto err;
|
||||
+ }
|
||||
i = talloc_zero(trans, struct accessed_node);
|
||||
if (!i)
|
||||
goto nomem;
|
||||
@@ -297,6 +305,7 @@ int access_node(struct connection *conn, struct node *node,
|
||||
i->ta_node = true;
|
||||
}
|
||||
}
|
||||
+ trans->nodes++;
|
||||
list_add_tail(&i->list, &trans->accessed);
|
||||
}
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_transaction.h b/tools/xenstore/xenstored_transaction.h
|
||||
index 0093cac807e3..e3cbd6b23095 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.h
|
||||
+++ b/tools/xenstore/xenstored_transaction.h
|
||||
@@ -39,8 +39,8 @@ void transaction_entry_inc(struct transaction *trans, unsigned int domid);
|
||||
void transaction_entry_dec(struct transaction *trans, unsigned int domid);
|
||||
|
||||
/* This node was accessed. */
|
||||
-int access_node(struct connection *conn, struct node *node,
|
||||
- enum node_access_type type, TDB_DATA *key);
|
||||
+int __must_check access_node(struct connection *conn, struct node *node,
|
||||
+ enum node_access_type type, TDB_DATA *key);
|
||||
|
||||
/* Queue watches for a modified node. */
|
||||
void queue_watches(struct connection *conn, const char *name, bool watch_exact);
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
From 90013d6a735491a7b93a6832eb2a51e5633254f5 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:09 +0200
|
||||
Subject: tools/xenstore: move the call of setup_structure() to dom0
|
||||
introduction
|
||||
|
||||
Setting up the basic structure when introducing dom0 has the advantage
|
||||
to be able to add proper node memory accounting for the added nodes
|
||||
later.
|
||||
|
||||
This makes it possible to do proper node accounting, too.
|
||||
|
||||
An additional requirement to make that work fine is to correct the
|
||||
owner of the created nodes to be dom0_domid instead of domid 0.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index f835aa1b2f1f..5171d34c947e 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -2039,7 +2039,8 @@ static int tdb_flags;
|
||||
static void manual_node(const char *name, const char *child)
|
||||
{
|
||||
struct node *node;
|
||||
- struct xs_permissions perms = { .id = 0, .perms = XS_PERM_NONE };
|
||||
+ struct xs_permissions perms = { .id = dom0_domid,
|
||||
+ .perms = XS_PERM_NONE };
|
||||
|
||||
node = talloc_zero(NULL, struct node);
|
||||
if (!node)
|
||||
@@ -2078,7 +2079,7 @@ static void tdb_logger(TDB_CONTEXT *tdb, int level, const char * fmt, ...)
|
||||
}
|
||||
}
|
||||
|
||||
-static void setup_structure(bool live_update)
|
||||
+void setup_structure(bool live_update)
|
||||
{
|
||||
char *tdbname;
|
||||
|
||||
@@ -2101,6 +2102,7 @@ static void setup_structure(bool live_update)
|
||||
manual_node("/", "tool");
|
||||
manual_node("/tool", "xenstored");
|
||||
manual_node("/tool/xenstored", NULL);
|
||||
+ domain_entry_fix(dom0_domid, 3, true);
|
||||
}
|
||||
|
||||
check_store();
|
||||
@@ -2614,9 +2616,6 @@ int main(int argc, char *argv[])
|
||||
|
||||
init_pipe(reopen_log_pipe);
|
||||
|
||||
- /* Setup the database */
|
||||
- setup_structure(live_update);
|
||||
-
|
||||
/* Listen to hypervisor. */
|
||||
if (!no_domain_init && !live_update) {
|
||||
domain_init(-1);
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index 245f9258235f..2c77ec7ee0f4 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -231,6 +231,7 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
|
||||
struct node *read_node(struct connection *conn, const void *ctx,
|
||||
const char *name);
|
||||
|
||||
+void setup_structure(bool live_update);
|
||||
struct connection *new_connection(const struct interface_funcs *funcs);
|
||||
struct connection *get_connection_by_id(unsigned int conn_id);
|
||||
void ignore_connection(struct connection *conn);
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index 260952e09096..f04b7aae8a32 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -470,6 +470,9 @@ static struct domain *introduce_domain(const void *ctx,
|
||||
}
|
||||
domain->interface = interface;
|
||||
|
||||
+ if (is_master_domain)
|
||||
+ setup_structure(restore);
|
||||
+
|
||||
/* Now domain belongs to its connection. */
|
||||
talloc_steal(domain->conn, domain);
|
||||
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
From 6af17b8bf52b9dfdc6a5ecd3efbcea9fddd57d91 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:09 +0200
|
||||
Subject: tools/xenstore: add infrastructure to keep track of per domain memory
|
||||
usage
|
||||
|
||||
The amount of memory a domain can consume in Xenstore is limited by
|
||||
various quota today, but even with sane quota a domain can still
|
||||
consume rather large memory quantities.
|
||||
|
||||
Add the infrastructure for keeping track of the amount of memory a
|
||||
domain is consuming in Xenstore. Note that this is only the memory a
|
||||
domain has direct control over, so any internal administration data
|
||||
needed by Xenstore only is not being accounted for.
|
||||
|
||||
There are two quotas defined: a soft quota which will result in a
|
||||
warning issued via syslog() when it is exceeded, and a hard quota
|
||||
resulting in a stop of accepting further requests or watch events as
|
||||
long as the hard quota would be violated by accepting those.
|
||||
|
||||
Setting any of those quotas to 0 will disable it.
|
||||
|
||||
As default values use 2MB per domain for the soft limit (this basically
|
||||
covers the allowed case to create 1000 nodes needing 2kB each), and
|
||||
2.5MB for the hard limit.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 5171d34c947e..b2bf6740d430 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -109,6 +109,8 @@ int quota_nb_perms_per_node = 5;
|
||||
int quota_trans_nodes = 1024;
|
||||
int quota_max_path_len = XENSTORE_REL_PATH_MAX;
|
||||
int quota_req_outstanding = 20;
|
||||
+int quota_memory_per_domain_soft = 2 * 1024 * 1024; /* 2 MB */
|
||||
+int quota_memory_per_domain_hard = 2 * 1024 * 1024 + 512 * 1024; /* 2.5 MB */
|
||||
|
||||
unsigned int timeout_watch_event_msec = 20000;
|
||||
|
||||
@@ -2406,7 +2408,14 @@ static void usage(void)
|
||||
" quotas are:\n"
|
||||
" transaction-nodes: number of accessed node per\n"
|
||||
" transaction\n"
|
||||
+" memory: total used memory per domain for nodes,\n"
|
||||
+" transactions, watches and requests, above\n"
|
||||
+" which Xenstore will stop talking to domain\n"
|
||||
" outstanding: number of outstanding requests\n"
|
||||
+" -q, --quota-soft <what>=<nb> set a soft quota <what> to the value <nb>,\n"
|
||||
+" causing a warning to be issued via syslog() if the\n"
|
||||
+" limit is violated, allowed quotas are:\n"
|
||||
+" memory: see above\n"
|
||||
" -w, --timeout <what>=<seconds> set the timeout in seconds for <what>,\n"
|
||||
" allowed timeout candidates are:\n"
|
||||
" watch-event: time a watch-event is kept pending\n"
|
||||
@@ -2433,6 +2442,7 @@ static struct option options[] = {
|
||||
{ "perm-nb", 1, NULL, 'A' },
|
||||
{ "path-max", 1, NULL, 'M' },
|
||||
{ "quota", 1, NULL, 'Q' },
|
||||
+ { "quota-soft", 1, NULL, 'q' },
|
||||
{ "timeout", 1, NULL, 'w' },
|
||||
{ "no-recovery", 0, NULL, 'R' },
|
||||
{ "internal-db", 0, NULL, 'I' },
|
||||
@@ -2480,7 +2490,7 @@ static void set_timeout(const char *arg)
|
||||
barf("unknown timeout \"%s\"\n", arg);
|
||||
}
|
||||
|
||||
-static void set_quota(const char *arg)
|
||||
+static void set_quota(const char *arg, bool soft)
|
||||
{
|
||||
const char *eq = strchr(arg, '=');
|
||||
int val;
|
||||
@@ -2488,11 +2498,16 @@ static void set_quota(const char *arg)
|
||||
if (!eq)
|
||||
barf("quotas must be specified via <what>=<nb>\n");
|
||||
val = get_optval_int(eq + 1);
|
||||
- if (what_matches(arg, "outstanding"))
|
||||
+ if (what_matches(arg, "outstanding") && !soft)
|
||||
quota_req_outstanding = val;
|
||||
- else if (what_matches(arg, "transaction-nodes"))
|
||||
+ else if (what_matches(arg, "transaction-nodes") && !soft)
|
||||
quota_trans_nodes = val;
|
||||
- else
|
||||
+ else if (what_matches(arg, "memory")) {
|
||||
+ if (soft)
|
||||
+ quota_memory_per_domain_soft = val;
|
||||
+ else
|
||||
+ quota_memory_per_domain_hard = val;
|
||||
+ } else
|
||||
barf("unknown quota \"%s\"\n", arg);
|
||||
}
|
||||
|
||||
@@ -2510,7 +2525,7 @@ int main(int argc, char *argv[])
|
||||
orig_argc = argc;
|
||||
orig_argv = argv;
|
||||
|
||||
- while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:Q:T:RVW:w:U",
|
||||
+ while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:Q:q:T:RVW:w:U",
|
||||
options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'D':
|
||||
@@ -2561,7 +2576,10 @@ int main(int argc, char *argv[])
|
||||
quota_max_path_len);
|
||||
break;
|
||||
case 'Q':
|
||||
- set_quota(optarg);
|
||||
+ set_quota(optarg, false);
|
||||
+ break;
|
||||
+ case 'q':
|
||||
+ set_quota(optarg, true);
|
||||
break;
|
||||
case 'w':
|
||||
set_timeout(optarg);
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index 2c77ec7ee0f4..373af18297bf 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -270,6 +270,8 @@ extern int priv_domid;
|
||||
extern int quota_nb_entry_per_domain;
|
||||
extern int quota_req_outstanding;
|
||||
extern int quota_trans_nodes;
|
||||
+extern int quota_memory_per_domain_soft;
|
||||
+extern int quota_memory_per_domain_hard;
|
||||
|
||||
extern unsigned int timeout_watch_event_msec;
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index f04b7aae8a32..94fd561e9de4 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -76,6 +76,13 @@ struct domain
|
||||
/* number of entry from this domain in the store */
|
||||
int nbentry;
|
||||
|
||||
+ /* Amount of memory allocated for this domain. */
|
||||
+ int memory;
|
||||
+ bool soft_quota_reported;
|
||||
+ bool hard_quota_reported;
|
||||
+ time_t mem_last_msg;
|
||||
+#define MEM_WARN_MINTIME_SEC 10
|
||||
+
|
||||
/* number of watch for this domain */
|
||||
int nbwatch;
|
||||
|
||||
@@ -192,6 +199,9 @@ static bool domain_can_read(struct connection *conn)
|
||||
return false;
|
||||
if (conn->domain->nboutstanding >= quota_req_outstanding)
|
||||
return false;
|
||||
+ if (conn->domain->memory >= quota_memory_per_domain_hard &&
|
||||
+ quota_memory_per_domain_hard)
|
||||
+ return false;
|
||||
}
|
||||
|
||||
return (intf->req_cons != intf->req_prod);
|
||||
@@ -950,6 +960,89 @@ int domain_entry(struct connection *conn)
|
||||
: 0;
|
||||
}
|
||||
|
||||
+static bool domain_chk_quota(struct domain *domain, int mem)
|
||||
+{
|
||||
+ time_t now;
|
||||
+
|
||||
+ if (!domain || !domid_is_unprivileged(domain->domid) ||
|
||||
+ (domain->conn && domain->conn->is_ignored))
|
||||
+ return false;
|
||||
+
|
||||
+ now = time(NULL);
|
||||
+
|
||||
+ if (mem >= quota_memory_per_domain_hard &&
|
||||
+ quota_memory_per_domain_hard) {
|
||||
+ if (domain->hard_quota_reported)
|
||||
+ return true;
|
||||
+ syslog(LOG_ERR, "Domain %u exceeds hard memory quota, Xenstore interface to domain stalled\n",
|
||||
+ domain->domid);
|
||||
+ domain->mem_last_msg = now;
|
||||
+ domain->hard_quota_reported = true;
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ if (now - domain->mem_last_msg >= MEM_WARN_MINTIME_SEC) {
|
||||
+ if (domain->hard_quota_reported) {
|
||||
+ domain->mem_last_msg = now;
|
||||
+ domain->hard_quota_reported = false;
|
||||
+ syslog(LOG_INFO, "Domain %u below hard memory quota again\n",
|
||||
+ domain->domid);
|
||||
+ }
|
||||
+ if (mem >= quota_memory_per_domain_soft &&
|
||||
+ quota_memory_per_domain_soft &&
|
||||
+ !domain->soft_quota_reported) {
|
||||
+ domain->mem_last_msg = now;
|
||||
+ domain->soft_quota_reported = true;
|
||||
+ syslog(LOG_WARNING, "Domain %u exceeds soft memory quota\n",
|
||||
+ domain->domid);
|
||||
+ }
|
||||
+ if (mem < quota_memory_per_domain_soft &&
|
||||
+ domain->soft_quota_reported) {
|
||||
+ domain->mem_last_msg = now;
|
||||
+ domain->soft_quota_reported = false;
|
||||
+ syslog(LOG_INFO, "Domain %u below soft memory quota again\n",
|
||||
+ domain->domid);
|
||||
+ }
|
||||
+
|
||||
+ }
|
||||
+
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
+int domain_memory_add(unsigned int domid, int mem, bool no_quota_check)
|
||||
+{
|
||||
+ struct domain *domain;
|
||||
+
|
||||
+ domain = find_domain_struct(domid);
|
||||
+ if (domain) {
|
||||
+ /*
|
||||
+ * domain_chk_quota() will print warning and also store whether
|
||||
+ * the soft/hard quota has been hit. So check no_quota_check
|
||||
+ * *after*.
|
||||
+ */
|
||||
+ if (domain_chk_quota(domain, domain->memory + mem) &&
|
||||
+ !no_quota_check)
|
||||
+ return ENOMEM;
|
||||
+ domain->memory += mem;
|
||||
+ } else {
|
||||
+ /*
|
||||
+ * The domain the memory is to be accounted for should always
|
||||
+ * exist, as accounting is done either for a domain related to
|
||||
+ * the current connection, or for the domain owning a node
|
||||
+ * (which is always existing, as the owner of the node is
|
||||
+ * tested to exist and replaced by domid 0 if not).
|
||||
+ * So not finding the related domain MUST be an error in the
|
||||
+ * data base.
|
||||
+ */
|
||||
+ errno = ENOENT;
|
||||
+ corrupt(NULL, "Accounting called for non-existing domain %u\n",
|
||||
+ domid);
|
||||
+ return ENOENT;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
void domain_watch_inc(struct connection *conn)
|
||||
{
|
||||
if (!conn || !conn->domain)
|
||||
diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
|
||||
index d6519904d831..633c9a0a0a1f 100644
|
||||
--- a/tools/xenstore/xenstored_domain.h
|
||||
+++ b/tools/xenstore/xenstored_domain.h
|
||||
@@ -61,6 +61,26 @@ int domain_entry_inc(struct connection *conn, struct node *);
|
||||
void domain_entry_dec(struct connection *conn, struct node *);
|
||||
int domain_entry_fix(unsigned int domid, int num, bool update);
|
||||
int domain_entry(struct connection *conn);
|
||||
+int domain_memory_add(unsigned int domid, int mem, bool no_quota_check);
|
||||
+
|
||||
+/*
|
||||
+ * domain_memory_add_chk(): to be used when memory quota should be checked.
|
||||
+ * Not to be used when specifying a negative mem value, as lowering the used
|
||||
+ * memory should always be allowed.
|
||||
+ */
|
||||
+static inline int domain_memory_add_chk(unsigned int domid, int mem)
|
||||
+{
|
||||
+ return domain_memory_add(domid, mem, false);
|
||||
+}
|
||||
+/*
|
||||
+ * domain_memory_add_nochk(): to be used when memory quota should not be
|
||||
+ * checked, e.g. when lowering memory usage, or in an error case for undoing
|
||||
+ * a previous memory adjustment.
|
||||
+ */
|
||||
+static inline void domain_memory_add_nochk(unsigned int domid, int mem)
|
||||
+{
|
||||
+ domain_memory_add(domid, mem, true);
|
||||
+}
|
||||
void domain_watch_inc(struct connection *conn);
|
||||
void domain_watch_dec(struct connection *conn);
|
||||
int domain_watch(struct connection *conn);
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
From ae7042f024af7584251f776a12d9bb24d13fecaf Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:09 +0200
|
||||
Subject: tools/xenstore: add memory accounting for responses
|
||||
|
||||
Add the memory accounting for queued responses.
|
||||
|
||||
In case adding a watch event for a guest is causing the hard memory
|
||||
quota of that guest to be violated, the event is dropped. This will
|
||||
ensure that it is impossible to drive another guest past its memory
|
||||
quota by generating insane amounts of events for that guest. This is
|
||||
especially important for protecting driver domains from that attack
|
||||
vector.
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42315.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index b2bf6740d430..ecab6cfbbe15 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -260,6 +260,8 @@ static void free_buffered_data(struct buffered_data *out,
|
||||
}
|
||||
}
|
||||
|
||||
+ domain_memory_add_nochk(conn->id, -out->hdr.msg.len - sizeof(out->hdr));
|
||||
+
|
||||
if (out->hdr.msg.type == XS_WATCH_EVENT) {
|
||||
req = out->pend.req;
|
||||
if (req) {
|
||||
@@ -938,11 +940,14 @@ void send_reply(struct connection *conn, enum xsd_sockmsg_type type,
|
||||
bdata->timeout_msec = 0;
|
||||
bdata->watch_event = false;
|
||||
|
||||
- if (len <= DEFAULT_BUFFER_SIZE)
|
||||
+ if (len <= DEFAULT_BUFFER_SIZE) {
|
||||
bdata->buffer = bdata->default_buffer;
|
||||
- else {
|
||||
+ /* Don't check quota, path might be used for returning error. */
|
||||
+ domain_memory_add_nochk(conn->id, len + sizeof(bdata->hdr));
|
||||
+ } else {
|
||||
bdata->buffer = talloc_array(bdata, char, len);
|
||||
- if (!bdata->buffer) {
|
||||
+ if (!bdata->buffer ||
|
||||
+ domain_memory_add_chk(conn->id, len + sizeof(bdata->hdr))) {
|
||||
send_error(conn, ENOMEM);
|
||||
return;
|
||||
}
|
||||
@@ -1007,6 +1012,11 @@ void send_event(struct buffered_data *req, struct connection *conn,
|
||||
}
|
||||
}
|
||||
|
||||
+ if (domain_memory_add_chk(conn->id, len + sizeof(bdata->hdr))) {
|
||||
+ talloc_free(bdata);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
if (timeout_watch_event_msec && domain_is_unprivileged(conn)) {
|
||||
bdata->timeout_msec = get_now_msec() + timeout_watch_event_msec;
|
||||
if (!conn->timeout_msec)
|
||||
@@ -3039,6 +3049,12 @@ static void add_buffered_data(struct buffered_data *bdata,
|
||||
*/
|
||||
if (bdata->hdr.msg.type != XS_WATCH_EVENT)
|
||||
domain_outstanding_inc(conn);
|
||||
+ /*
|
||||
+ * We are restoring the state after Live-Update and the new quota may
|
||||
+ * be smaller. So ignore it. The limit will be applied for any resource
|
||||
+ * after the state has been fully restored.
|
||||
+ */
|
||||
+ domain_memory_add_nochk(conn->id, len + sizeof(bdata->hdr));
|
||||
}
|
||||
|
||||
void read_state_buffered_data(const void *ctx, struct connection *conn,
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
From 4628ae0a56b037dcdc8a3e42c543c5b9fd9990cf Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:10 +0200
|
||||
Subject: tools/xenstore: add memory accounting for watches
|
||||
|
||||
Add the memory accounting for registered watches.
|
||||
|
||||
When a socket connection is destroyed, the associated watches are
|
||||
removed, too. In order to keep memory accounting correct the watches
|
||||
must be removed explicitly via a call of conn_delete_all_watches() from
|
||||
destroy_conn().
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42315.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index ecab6cfbbe15..d86942f5aa77 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -463,6 +463,7 @@ static int destroy_conn(void *_conn)
|
||||
}
|
||||
|
||||
conn_free_buffered_data(conn);
|
||||
+ conn_delete_all_watches(conn);
|
||||
list_for_each_entry(req, &conn->ref_list, list)
|
||||
req->on_ref_list = false;
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
|
||||
index 0755ffa375ba..fdf9b2d653a0 100644
|
||||
--- a/tools/xenstore/xenstored_watch.c
|
||||
+++ b/tools/xenstore/xenstored_watch.c
|
||||
@@ -211,7 +211,7 @@ static int check_watch_path(struct connection *conn, const void *ctx,
|
||||
}
|
||||
|
||||
static struct watch *add_watch(struct connection *conn, char *path, char *token,
|
||||
- bool relative)
|
||||
+ bool relative, bool no_quota_check)
|
||||
{
|
||||
struct watch *watch;
|
||||
|
||||
@@ -222,6 +222,9 @@ static struct watch *add_watch(struct connection *conn, char *path, char *token,
|
||||
watch->token = talloc_strdup(watch, token);
|
||||
if (!watch->node || !watch->token)
|
||||
goto nomem;
|
||||
+ if (domain_memory_add(conn->id, strlen(path) + strlen(token),
|
||||
+ no_quota_check))
|
||||
+ goto nomem;
|
||||
|
||||
if (relative)
|
||||
watch->relative_path = get_implicit_path(conn);
|
||||
@@ -265,7 +268,7 @@ int do_watch(struct connection *conn, struct buffered_data *in)
|
||||
if (domain_watch(conn) > quota_nb_watch_per_domain)
|
||||
return E2BIG;
|
||||
|
||||
- watch = add_watch(conn, vec[0], vec[1], relative);
|
||||
+ watch = add_watch(conn, vec[0], vec[1], relative, false);
|
||||
if (!watch)
|
||||
return errno;
|
||||
|
||||
@@ -296,6 +299,8 @@ int do_unwatch(struct connection *conn, struct buffered_data *in)
|
||||
list_for_each_entry(watch, &conn->watches, list) {
|
||||
if (streq(watch->node, node) && streq(watch->token, vec[1])) {
|
||||
list_del(&watch->list);
|
||||
+ domain_memory_add_nochk(conn->id, -strlen(watch->node) -
|
||||
+ strlen(watch->token));
|
||||
talloc_free(watch);
|
||||
domain_watch_dec(conn);
|
||||
send_ack(conn, XS_UNWATCH);
|
||||
@@ -311,6 +316,8 @@ void conn_delete_all_watches(struct connection *conn)
|
||||
|
||||
while ((watch = list_top(&conn->watches, struct watch, list))) {
|
||||
list_del(&watch->list);
|
||||
+ domain_memory_add_nochk(conn->id, -strlen(watch->node) -
|
||||
+ strlen(watch->token));
|
||||
talloc_free(watch);
|
||||
domain_watch_dec(conn);
|
||||
}
|
||||
@@ -373,7 +380,7 @@ void read_state_watch(const void *ctx, const void *state)
|
||||
if (!path)
|
||||
barf("allocation error for read watch");
|
||||
|
||||
- if (!add_watch(conn, path, token, relative))
|
||||
+ if (!add_watch(conn, path, token, relative, true))
|
||||
barf("error adding watch");
|
||||
}
|
||||
|
||||
|
|
@ -1,334 +0,0 @@
|
|||
From b8bd74e5e962955211ab0c5c1924ebf2bb526799 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:10 +0200
|
||||
Subject: tools/xenstore: add memory accounting for nodes
|
||||
|
||||
Add the memory accounting for Xenstore nodes. In order to make this
|
||||
not too complicated allow for some sloppiness when writing nodes. Any
|
||||
hard quota violation will result in no further requests to be accepted.
|
||||
|
||||
This is part of XSA-326 / CVE-2022-42315.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index d86942f5aa77..16504de42017 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -591,6 +591,117 @@ void set_tdb_key(const char *name, TDB_DATA *key)
|
||||
key->dsize = strlen(name);
|
||||
}
|
||||
|
||||
+static void get_acc_data(TDB_DATA *key, struct node_account_data *acc)
|
||||
+{
|
||||
+ TDB_DATA old_data;
|
||||
+ struct xs_tdb_record_hdr *hdr;
|
||||
+
|
||||
+ if (acc->memory < 0) {
|
||||
+ old_data = tdb_fetch(tdb_ctx, *key);
|
||||
+ /* No check for error, as the node might not exist. */
|
||||
+ if (old_data.dptr == NULL) {
|
||||
+ acc->memory = 0;
|
||||
+ } else {
|
||||
+ hdr = (void *)old_data.dptr;
|
||||
+ acc->memory = old_data.dsize;
|
||||
+ acc->domid = hdr->perms[0].id;
|
||||
+ }
|
||||
+ talloc_free(old_data.dptr);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Per-transaction nodes need to be accounted for the transaction owner.
|
||||
+ * Those nodes are stored in the data base with the transaction generation
|
||||
+ * count prepended (e.g. 123/local/domain/...). So testing for the node's
|
||||
+ * key not to start with "/" is sufficient.
|
||||
+ */
|
||||
+static unsigned int get_acc_domid(struct connection *conn, TDB_DATA *key,
|
||||
+ unsigned int domid)
|
||||
+{
|
||||
+ return (!conn || key->dptr[0] == '/') ? domid : conn->id;
|
||||
+}
|
||||
+
|
||||
+int do_tdb_write(struct connection *conn, TDB_DATA *key, TDB_DATA *data,
|
||||
+ struct node_account_data *acc, bool no_quota_check)
|
||||
+{
|
||||
+ struct xs_tdb_record_hdr *hdr = (void *)data->dptr;
|
||||
+ struct node_account_data old_acc = {};
|
||||
+ unsigned int old_domid, new_domid;
|
||||
+ int ret;
|
||||
+
|
||||
+ if (!acc)
|
||||
+ old_acc.memory = -1;
|
||||
+ else
|
||||
+ old_acc = *acc;
|
||||
+
|
||||
+ get_acc_data(key, &old_acc);
|
||||
+ old_domid = get_acc_domid(conn, key, old_acc.domid);
|
||||
+ new_domid = get_acc_domid(conn, key, hdr->perms[0].id);
|
||||
+
|
||||
+ /*
|
||||
+ * Don't check for ENOENT, as we want to be able to switch orphaned
|
||||
+ * nodes to new owners.
|
||||
+ */
|
||||
+ if (old_acc.memory)
|
||||
+ domain_memory_add_nochk(old_domid,
|
||||
+ -old_acc.memory - key->dsize);
|
||||
+ ret = domain_memory_add(new_domid, data->dsize + key->dsize,
|
||||
+ no_quota_check);
|
||||
+ if (ret) {
|
||||
+ /* Error path, so no quota check. */
|
||||
+ if (old_acc.memory)
|
||||
+ domain_memory_add_nochk(old_domid,
|
||||
+ old_acc.memory + key->dsize);
|
||||
+ return ret;
|
||||
+ }
|
||||
+
|
||||
+ /* TDB should set errno, but doesn't even set ecode AFAICT. */
|
||||
+ if (tdb_store(tdb_ctx, *key, *data, TDB_REPLACE) != 0) {
|
||||
+ domain_memory_add_nochk(new_domid, -data->dsize - key->dsize);
|
||||
+ /* Error path, so no quota check. */
|
||||
+ if (old_acc.memory)
|
||||
+ domain_memory_add_nochk(old_domid,
|
||||
+ old_acc.memory + key->dsize);
|
||||
+ errno = EIO;
|
||||
+ return errno;
|
||||
+ }
|
||||
+
|
||||
+ if (acc) {
|
||||
+ /* Don't use new_domid, as it might be a transaction node. */
|
||||
+ acc->domid = hdr->perms[0].id;
|
||||
+ acc->memory = data->dsize;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+int do_tdb_delete(struct connection *conn, TDB_DATA *key,
|
||||
+ struct node_account_data *acc)
|
||||
+{
|
||||
+ struct node_account_data tmp_acc;
|
||||
+ unsigned int domid;
|
||||
+
|
||||
+ if (!acc) {
|
||||
+ acc = &tmp_acc;
|
||||
+ acc->memory = -1;
|
||||
+ }
|
||||
+
|
||||
+ get_acc_data(key, acc);
|
||||
+
|
||||
+ if (tdb_delete(tdb_ctx, *key)) {
|
||||
+ errno = EIO;
|
||||
+ return errno;
|
||||
+ }
|
||||
+
|
||||
+ if (acc->memory) {
|
||||
+ domid = get_acc_domid(conn, key, acc->domid);
|
||||
+ domain_memory_add_nochk(domid, -acc->memory - key->dsize);
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* If it fails, returns NULL and sets errno.
|
||||
* Temporary memory allocations will be done with ctx.
|
||||
@@ -644,9 +755,15 @@ struct node *read_node(struct connection *conn, const void *ctx,
|
||||
|
||||
/* Permissions are struct xs_permissions. */
|
||||
node->perms.p = hdr->perms;
|
||||
+ node->acc.domid = node->perms.p[0].id;
|
||||
+ node->acc.memory = data.dsize;
|
||||
if (domain_adjust_node_perms(conn, node))
|
||||
goto error;
|
||||
|
||||
+ /* If owner is gone reset currently accounted memory size. */
|
||||
+ if (node->acc.domid != node->perms.p[0].id)
|
||||
+ node->acc.memory = 0;
|
||||
+
|
||||
/* Data is binary blob (usually ascii, no nul). */
|
||||
node->data = node->perms.p + hdr->num_perms;
|
||||
/* Children is strings, nul separated. */
|
||||
@@ -715,12 +832,9 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
|
||||
p += node->datalen;
|
||||
memcpy(p, node->children, node->childlen);
|
||||
|
||||
- /* TDB should set errno, but doesn't even set ecode AFAICT. */
|
||||
- if (tdb_store(tdb_ctx, *key, data, TDB_REPLACE) != 0) {
|
||||
- corrupt(conn, "Write of %s failed", key->dptr);
|
||||
- errno = EIO;
|
||||
- return errno;
|
||||
- }
|
||||
+ if (do_tdb_write(conn, key, &data, &node->acc, no_quota_check))
|
||||
+ return EIO;
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1222,7 +1336,7 @@ static void delete_node_single(struct connection *conn, struct node *node)
|
||||
if (access_node(conn, node, NODE_ACCESS_DELETE, &key))
|
||||
return;
|
||||
|
||||
- if (tdb_delete(tdb_ctx, key) != 0) {
|
||||
+ if (do_tdb_delete(conn, &key, &node->acc) != 0) {
|
||||
corrupt(conn, "Could not delete '%s'", node->name);
|
||||
return;
|
||||
}
|
||||
@@ -1295,6 +1409,7 @@ static struct node *construct_node(struct connection *conn, const void *ctx,
|
||||
/* No children, no data */
|
||||
node->children = node->data = NULL;
|
||||
node->childlen = node->datalen = 0;
|
||||
+ node->acc.memory = 0;
|
||||
node->parent = parent;
|
||||
return node;
|
||||
|
||||
@@ -1303,17 +1418,17 @@ static struct node *construct_node(struct connection *conn, const void *ctx,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
-static void destroy_node_rm(struct node *node)
|
||||
+static void destroy_node_rm(struct connection *conn, struct node *node)
|
||||
{
|
||||
if (streq(node->name, "/"))
|
||||
corrupt(NULL, "Destroying root node!");
|
||||
|
||||
- tdb_delete(tdb_ctx, node->key);
|
||||
+ do_tdb_delete(conn, &node->key, &node->acc);
|
||||
}
|
||||
|
||||
static int destroy_node(struct connection *conn, struct node *node)
|
||||
{
|
||||
- destroy_node_rm(node);
|
||||
+ destroy_node_rm(conn, node);
|
||||
domain_entry_dec(conn, node);
|
||||
|
||||
/*
|
||||
@@ -1365,7 +1480,7 @@ static struct node *create_node(struct connection *conn, const void *ctx,
|
||||
/* Account for new node */
|
||||
if (i->parent) {
|
||||
if (domain_entry_inc(conn, i)) {
|
||||
- destroy_node_rm(i);
|
||||
+ destroy_node_rm(conn, i);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -2291,7 +2406,7 @@ static int clean_store_(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA val,
|
||||
if (!hashtable_search(reachable, name)) {
|
||||
log("clean_store: '%s' is orphaned!", name);
|
||||
if (recovery) {
|
||||
- tdb_delete(tdb, key);
|
||||
+ do_tdb_delete(NULL, &key, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3149,6 +3264,7 @@ void read_state_node(const void *ctx, const void *state)
|
||||
if (!node)
|
||||
barf("allocation error restoring node");
|
||||
|
||||
+ node->acc.memory = 0;
|
||||
node->name = name;
|
||||
node->generation = ++generation;
|
||||
node->datalen = sn->data_len;
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index 373af18297bf..da9ecce67f31 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -176,6 +176,11 @@ struct node_perms {
|
||||
struct xs_permissions *p;
|
||||
};
|
||||
|
||||
+struct node_account_data {
|
||||
+ unsigned int domid;
|
||||
+ int memory; /* -1 if unknown */
|
||||
+};
|
||||
+
|
||||
struct node {
|
||||
const char *name;
|
||||
/* Key used to update TDB */
|
||||
@@ -198,6 +203,9 @@ struct node {
|
||||
/* Children, each nul-terminated. */
|
||||
unsigned int childlen;
|
||||
char *children;
|
||||
+
|
||||
+ /* Allocation information for node currently in store. */
|
||||
+ struct node_account_data acc;
|
||||
};
|
||||
|
||||
/* Return the only argument in the input. */
|
||||
@@ -306,6 +314,10 @@ extern xengnttab_handle **xgt_handle;
|
||||
int remember_string(struct hashtable *hash, const char *str);
|
||||
|
||||
void set_tdb_key(const char *name, TDB_DATA *key);
|
||||
+int do_tdb_write(struct connection *conn, TDB_DATA *key, TDB_DATA *data,
|
||||
+ struct node_account_data *acc, bool no_quota_check);
|
||||
+int do_tdb_delete(struct connection *conn, TDB_DATA *key,
|
||||
+ struct node_account_data *acc);
|
||||
|
||||
void conn_free_buffered_data(struct connection *conn);
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index 7bd41eb475e3..ace9a11d77bb 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -153,6 +153,9 @@ struct transaction
|
||||
/* List of all transactions active on this connection. */
|
||||
struct list_head list;
|
||||
|
||||
+ /* Connection this transaction is associated with. */
|
||||
+ struct connection *conn;
|
||||
+
|
||||
/* Connection-local identifier for this transaction. */
|
||||
uint32_t id;
|
||||
|
||||
@@ -286,6 +289,8 @@ int access_node(struct connection *conn, struct node *node,
|
||||
|
||||
introduce = true;
|
||||
i->ta_node = false;
|
||||
+ /* acc.memory < 0 means "unknown, get size from TDB". */
|
||||
+ node->acc.memory = -1;
|
||||
|
||||
/*
|
||||
* Additional transaction-specific node for read type. We only
|
||||
@@ -410,11 +415,11 @@ static int finalize_transaction(struct connection *conn,
|
||||
goto err;
|
||||
hdr = (void *)data.dptr;
|
||||
hdr->generation = ++generation;
|
||||
- ret = tdb_store(tdb_ctx, key, data,
|
||||
- TDB_REPLACE);
|
||||
+ ret = do_tdb_write(conn, &key, &data, NULL,
|
||||
+ true);
|
||||
talloc_free(data.dptr);
|
||||
} else {
|
||||
- ret = tdb_delete(tdb_ctx, key);
|
||||
+ ret = do_tdb_delete(conn, &key, NULL);
|
||||
}
|
||||
if (ret)
|
||||
goto err;
|
||||
@@ -425,7 +430,7 @@ static int finalize_transaction(struct connection *conn,
|
||||
}
|
||||
}
|
||||
|
||||
- if (i->ta_node && tdb_delete(tdb_ctx, ta_key))
|
||||
+ if (i->ta_node && do_tdb_delete(conn, &ta_key, NULL))
|
||||
goto err;
|
||||
list_del(&i->list);
|
||||
talloc_free(i);
|
||||
@@ -453,7 +458,7 @@ static int destroy_transaction(void *_transaction)
|
||||
i->node);
|
||||
if (trans_name) {
|
||||
set_tdb_key(trans_name, &key);
|
||||
- tdb_delete(tdb_ctx, key);
|
||||
+ do_tdb_delete(trans->conn, &key, NULL);
|
||||
}
|
||||
}
|
||||
list_del(&i->list);
|
||||
@@ -497,6 +502,7 @@ int do_transaction_start(struct connection *conn, struct buffered_data *in)
|
||||
|
||||
INIT_LIST_HEAD(&trans->accessed);
|
||||
INIT_LIST_HEAD(&trans->changed_domains);
|
||||
+ trans->conn = conn;
|
||||
trans->fail = false;
|
||||
trans->generation = ++generation;
|
||||
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
From c55a1ea0a5ea7f6a3dc850cb015a49ba9ec571ab Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:10 +0200
|
||||
Subject: tools/xenstore: add exports for quota variables
|
||||
|
||||
Some quota variables are not exported via header files.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index da9ecce67f31..bfd3fc1e9df3 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -275,6 +275,11 @@ extern TDB_CONTEXT *tdb_ctx;
|
||||
extern int dom0_domid;
|
||||
extern int dom0_event;
|
||||
extern int priv_domid;
|
||||
+extern int quota_nb_watch_per_domain;
|
||||
+extern int quota_max_transaction;
|
||||
+extern int quota_max_entry_size;
|
||||
+extern int quota_nb_perms_per_node;
|
||||
+extern int quota_max_path_len;
|
||||
extern int quota_nb_entry_per_domain;
|
||||
extern int quota_req_outstanding;
|
||||
extern int quota_trans_nodes;
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index ace9a11d77bb..28774813de83 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -175,7 +175,6 @@ struct transaction
|
||||
bool fail;
|
||||
};
|
||||
|
||||
-extern int quota_max_transaction;
|
||||
uint64_t generation;
|
||||
|
||||
static struct accessed_node *find_accessed_node(struct transaction *trans,
|
||||
diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
|
||||
index fdf9b2d653a0..85362bcce314 100644
|
||||
--- a/tools/xenstore/xenstored_watch.c
|
||||
+++ b/tools/xenstore/xenstored_watch.c
|
||||
@@ -31,8 +31,6 @@
|
||||
#include "xenstored_domain.h"
|
||||
#include "xenstored_transaction.h"
|
||||
|
||||
-extern int quota_nb_watch_per_domain;
|
||||
-
|
||||
struct watch
|
||||
{
|
||||
/* Watches on this connection */
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
From 05cc2af50ba43431d6d50aff758e968833aab9c6 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:10 +0200
|
||||
Subject: tools/xenstore: add control command for setting and showing quota
|
||||
|
||||
Add a xenstore-control command "quota" to:
|
||||
- show current quota settings
|
||||
- change quota settings
|
||||
- show current quota related values of a domain
|
||||
|
||||
Note that in the case the new quota is lower than existing one,
|
||||
Xenstored may continue to handle requests from a domain exceeding the
|
||||
new limit (depends on which one has been broken) and the amount of
|
||||
resource used will not change. However the domain will not be able to
|
||||
create more resource (associated to the quota) until it is back to below
|
||||
the limit.
|
||||
|
||||
This is part of XSA-326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/docs/misc/xenstore.txt b/docs/misc/xenstore.txt
|
||||
index 334dc8b6fdf5..a7d006519ae8 100644
|
||||
--- a/docs/misc/xenstore.txt
|
||||
+++ b/docs/misc/xenstore.txt
|
||||
@@ -366,6 +366,17 @@ CONTROL <command>|[<parameters>|]
|
||||
print|<string>
|
||||
print <string> to syslog (xenstore runs as daemon) or
|
||||
to console (xenstore runs as stubdom)
|
||||
+ quota|[set <name> <val>|<domid>]
|
||||
+ without parameters: print the current quota settings
|
||||
+ with "set <name> <val>": set the quota <name> to new value
|
||||
+ <val> (The admin should make sure all the domain usage is
|
||||
+ below the quota. If it is not, then Xenstored may continue to
|
||||
+ handle requests from the domain as long as the resource
|
||||
+ violating the new quota setting isn't increased further)
|
||||
+ with "<domid>": print quota related accounting data for
|
||||
+ the domain <domid>
|
||||
+ quota-soft|[set <name> <val>]
|
||||
+ like the "quota" command, but for soft-quota.
|
||||
help <supported-commands>
|
||||
return list of supported commands for CONTROL
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_control.c b/tools/xenstore/xenstored_control.c
|
||||
index adb8d51b043b..1031a81c3874 100644
|
||||
--- a/tools/xenstore/xenstored_control.c
|
||||
+++ b/tools/xenstore/xenstored_control.c
|
||||
@@ -196,6 +196,115 @@ static int do_control_log(void *ctx, struct connection *conn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+struct quota {
|
||||
+ const char *name;
|
||||
+ int *quota;
|
||||
+ const char *descr;
|
||||
+};
|
||||
+
|
||||
+static const struct quota hard_quotas[] = {
|
||||
+ { "nodes", "a_nb_entry_per_domain, "Nodes per domain" },
|
||||
+ { "watches", "a_nb_watch_per_domain, "Watches per domain" },
|
||||
+ { "transactions", "a_max_transaction, "Transactions per domain" },
|
||||
+ { "outstanding", "a_req_outstanding,
|
||||
+ "Outstanding requests per domain" },
|
||||
+ { "transaction-nodes", "a_trans_nodes,
|
||||
+ "Max. number of accessed nodes per transaction" },
|
||||
+ { "memory", "a_memory_per_domain_hard,
|
||||
+ "Total Xenstore memory per domain (error level)" },
|
||||
+ { "node-size", "a_max_entry_size, "Max. size of a node" },
|
||||
+ { "path-max", "a_max_path_len, "Max. length of a node path" },
|
||||
+ { "permissions", "a_nb_perms_per_node,
|
||||
+ "Max. number of permissions per node" },
|
||||
+ { NULL, NULL, NULL }
|
||||
+};
|
||||
+
|
||||
+static const struct quota soft_quotas[] = {
|
||||
+ { "memory", "a_memory_per_domain_soft,
|
||||
+ "Total Xenstore memory per domain (warning level)" },
|
||||
+ { NULL, NULL, NULL }
|
||||
+};
|
||||
+
|
||||
+static int quota_show_current(const void *ctx, struct connection *conn,
|
||||
+ const struct quota *quotas)
|
||||
+{
|
||||
+ char *resp;
|
||||
+ unsigned int i;
|
||||
+
|
||||
+ resp = talloc_strdup(ctx, "Quota settings:\n");
|
||||
+ if (!resp)
|
||||
+ return ENOMEM;
|
||||
+
|
||||
+ for (i = 0; quotas[i].quota; i++) {
|
||||
+ resp = talloc_asprintf_append(resp, "%-17s: %8d %s\n",
|
||||
+ quotas[i].name, *quotas[i].quota,
|
||||
+ quotas[i].descr);
|
||||
+ if (!resp)
|
||||
+ return ENOMEM;
|
||||
+ }
|
||||
+
|
||||
+ send_reply(conn, XS_CONTROL, resp, strlen(resp) + 1);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int quota_set(const void *ctx, struct connection *conn,
|
||||
+ char **vec, int num, const struct quota *quotas)
|
||||
+{
|
||||
+ unsigned int i;
|
||||
+ int val;
|
||||
+
|
||||
+ if (num != 2)
|
||||
+ return EINVAL;
|
||||
+
|
||||
+ val = atoi(vec[1]);
|
||||
+ if (val < 1)
|
||||
+ return EINVAL;
|
||||
+
|
||||
+ for (i = 0; quotas[i].quota; i++) {
|
||||
+ if (!strcmp(vec[0], quotas[i].name)) {
|
||||
+ *quotas[i].quota = val;
|
||||
+ send_ack(conn, XS_CONTROL);
|
||||
+ return 0;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return EINVAL;
|
||||
+}
|
||||
+
|
||||
+static int quota_get(const void *ctx, struct connection *conn,
|
||||
+ char **vec, int num)
|
||||
+{
|
||||
+ if (num != 1)
|
||||
+ return EINVAL;
|
||||
+
|
||||
+ return domain_get_quota(ctx, conn, atoi(vec[0]));
|
||||
+}
|
||||
+
|
||||
+static int do_control_quota(void *ctx, struct connection *conn,
|
||||
+ char **vec, int num)
|
||||
+{
|
||||
+ if (num == 0)
|
||||
+ return quota_show_current(ctx, conn, hard_quotas);
|
||||
+
|
||||
+ if (!strcmp(vec[0], "set"))
|
||||
+ return quota_set(ctx, conn, vec + 1, num - 1, hard_quotas);
|
||||
+
|
||||
+ return quota_get(ctx, conn, vec, num);
|
||||
+}
|
||||
+
|
||||
+static int do_control_quota_s(void *ctx, struct connection *conn,
|
||||
+ char **vec, int num)
|
||||
+{
|
||||
+ if (num == 0)
|
||||
+ return quota_show_current(ctx, conn, soft_quotas);
|
||||
+
|
||||
+ if (!strcmp(vec[0], "set"))
|
||||
+ return quota_set(ctx, conn, vec + 1, num - 1, soft_quotas);
|
||||
+
|
||||
+ return EINVAL;
|
||||
+}
|
||||
+
|
||||
#ifdef __MINIOS__
|
||||
static int do_control_memreport(void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
@@ -847,6 +956,8 @@ static struct cmd_s cmds[] = {
|
||||
{ "memreport", do_control_memreport, "[<file>]" },
|
||||
#endif
|
||||
{ "print", do_control_print, "<string>" },
|
||||
+ { "quota", do_control_quota, "[set <name> <val>|<domid>]" },
|
||||
+ { "quota-soft", do_control_quota_s, "[set <name> <val>]" },
|
||||
{ "help", do_control_help, "" },
|
||||
};
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index 94fd561e9de4..e7c6886ccf47 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "xenstored_domain.h"
|
||||
#include "xenstored_transaction.h"
|
||||
#include "xenstored_watch.h"
|
||||
+#include "xenstored_control.h"
|
||||
|
||||
#include <xenevtchn.h>
|
||||
#include <xenctrl.h>
|
||||
@@ -345,6 +346,38 @@ static struct domain *find_domain_struct(unsigned int domid)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+int domain_get_quota(const void *ctx, struct connection *conn,
|
||||
+ unsigned int domid)
|
||||
+{
|
||||
+ struct domain *d = find_domain_struct(domid);
|
||||
+ char *resp;
|
||||
+ int ta;
|
||||
+
|
||||
+ if (!d)
|
||||
+ return ENOENT;
|
||||
+
|
||||
+ ta = d->conn ? d->conn->transaction_started : 0;
|
||||
+ resp = talloc_asprintf(ctx, "Domain %u:\n", domid);
|
||||
+ if (!resp)
|
||||
+ return ENOMEM;
|
||||
+
|
||||
+#define ent(t, e) \
|
||||
+ resp = talloc_asprintf_append(resp, "%-16s: %8d\n", #t, e); \
|
||||
+ if (!resp) return ENOMEM
|
||||
+
|
||||
+ ent(nodes, d->nbentry);
|
||||
+ ent(watches, d->nbwatch);
|
||||
+ ent(transactions, ta);
|
||||
+ ent(outstanding, d->nboutstanding);
|
||||
+ ent(memory, d->memory);
|
||||
+
|
||||
+#undef ent
|
||||
+
|
||||
+ send_reply(conn, XS_CONTROL, resp, strlen(resp) + 1);
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static struct domain *alloc_domain(const void *context, unsigned int domid)
|
||||
{
|
||||
struct domain *domain;
|
||||
diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
|
||||
index 633c9a0a0a1f..904faa923afb 100644
|
||||
--- a/tools/xenstore/xenstored_domain.h
|
||||
+++ b/tools/xenstore/xenstored_domain.h
|
||||
@@ -87,6 +87,8 @@ int domain_watch(struct connection *conn);
|
||||
void domain_outstanding_inc(struct connection *conn);
|
||||
void domain_outstanding_dec(struct connection *conn);
|
||||
void domain_outstanding_domid_dec(unsigned int domid);
|
||||
+int domain_get_quota(const void *ctx, struct connection *conn,
|
||||
+ unsigned int domid);
|
||||
|
||||
/* Special node permission handling. */
|
||||
int set_perms_special(struct connection *conn, const char *name,
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
From bd4a7db4001364fd03a80a2e73b81c46aaa44e9c Mon Sep 17 00:00:00 2001
|
||||
From: Henry Wang <Henry.Wang@arm.com>
|
||||
Date: Mon, 22 Aug 2022 01:35:09 +0000
|
||||
Subject: [PATCH 1/4] libxl, docs: Use arch-specific default paging memory
|
||||
|
||||
The default paging memory (descibed in `shadow_memory` entry in xl
|
||||
config) in libxl is used to determine the memory pool size for xl
|
||||
guests. Currently this size is only used for x86, and contains a part
|
||||
of RAM to shadow the resident processes. Since on Arm there is no
|
||||
shadow mode guests, so the part of RAM to shadow the resident processes
|
||||
is not necessary. Therefore, this commit splits the function
|
||||
`libxl_get_required_shadow_memory()` to arch specific helpers and
|
||||
renamed the helper to `libxl__arch_get_required_paging_memory()`.
|
||||
|
||||
On x86, this helper calls the original value from
|
||||
`libxl_get_required_shadow_memory()` so no functional change intended.
|
||||
|
||||
On Arm, this helper returns 1MB per vcpu plus 4KB per MiB of RAM
|
||||
for the P2M map and additional 512KB.
|
||||
|
||||
Also update the xl.cfg documentation to add Arm documentation
|
||||
according to code changes and correct the comment style following Xen
|
||||
coding style.
|
||||
|
||||
This is part of CVE-2022-33747 / XSA-409.
|
||||
|
||||
Suggested-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Henry Wang <Henry.Wang@arm.com>
|
||||
Reviewed-by: Anthony PERARD <anthony.perard@citrix.com>
|
||||
---
|
||||
docs/man/xl.cfg.5.pod.in | 5 +++++
|
||||
tools/libs/light/libxl_arch.h | 4 ++++
|
||||
tools/libs/light/libxl_arm.c | 14 ++++++++++++++
|
||||
tools/libs/light/libxl_utils.c | 9 ++-------
|
||||
tools/libs/light/libxl_x86.c | 13 +++++++++++++
|
||||
5 files changed, 38 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/docs/man/xl.cfg.5.pod.in b/docs/man/xl.cfg.5.pod.in
|
||||
index b98d1613987e..eda1e77ebd06 100644
|
||||
--- a/docs/man/xl.cfg.5.pod.in
|
||||
+++ b/docs/man/xl.cfg.5.pod.in
|
||||
@@ -1768,6 +1768,11 @@ are not using hardware assisted paging (i.e. you are using shadow
|
||||
mode) and your guest workload consists of a very large number of
|
||||
similar processes then increasing this value may improve performance.
|
||||
|
||||
+On Arm, this field is used to determine the size of the guest P2M pages
|
||||
+pool, and the default value is 1MB per vCPU plus 4KB per MB of RAM for
|
||||
+the P2M map and additional 512KB for extended regions. Users should
|
||||
+adjust this value if bigger P2M pool size is needed.
|
||||
+
|
||||
=back
|
||||
|
||||
=head3 Processor and Platform Features
|
||||
diff --git a/tools/libs/light/libxl_arch.h b/tools/libs/light/libxl_arch.h
|
||||
index 1522ecb97f72..5a060c2c3033 100644
|
||||
--- a/tools/libs/light/libxl_arch.h
|
||||
+++ b/tools/libs/light/libxl_arch.h
|
||||
@@ -90,6 +90,10 @@ void libxl__arch_update_domain_config(libxl__gc *gc,
|
||||
libxl_domain_config *dst,
|
||||
const libxl_domain_config *src);
|
||||
|
||||
+_hidden
|
||||
+unsigned long libxl__arch_get_required_paging_memory(unsigned long maxmem_kb,
|
||||
+ unsigned int smp_cpus);
|
||||
+
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
|
||||
#define LAPIC_BASE_ADDRESS 0xfee00000
|
||||
diff --git a/tools/libs/light/libxl_arm.c b/tools/libs/light/libxl_arm.c
|
||||
index eef1de093914..73a95e83af24 100644
|
||||
--- a/tools/libs/light/libxl_arm.c
|
||||
+++ b/tools/libs/light/libxl_arm.c
|
||||
@@ -154,6 +154,20 @@ out:
|
||||
return rc;
|
||||
}
|
||||
|
||||
+unsigned long libxl__arch_get_required_paging_memory(unsigned long maxmem_kb,
|
||||
+ unsigned int smp_cpus)
|
||||
+{
|
||||
+ /*
|
||||
+ * 256 pages (1MB) per vcpu,
|
||||
+ * plus 1 page per MiB of RAM for the P2M map,
|
||||
+ * plus 1 page per MiB of extended region. This default value is 128 MiB
|
||||
+ * which should be enough for domains that are not running backend.
|
||||
+ * This is higher than the minimum that Xen would allocate if no value
|
||||
+ * were given (but the Xen minimum is for safety, not performance).
|
||||
+ */
|
||||
+ return 4 * (256 * smp_cpus + maxmem_kb / 1024 + 128);
|
||||
+}
|
||||
+
|
||||
static struct arch_info {
|
||||
const char *guest_type;
|
||||
const char *timer_compat;
|
||||
diff --git a/tools/libs/light/libxl_utils.c b/tools/libs/light/libxl_utils.c
|
||||
index 4699c4a0a36f..e276c0ee9cc3 100644
|
||||
--- a/tools/libs/light/libxl_utils.c
|
||||
+++ b/tools/libs/light/libxl_utils.c
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <ctype.h>
|
||||
|
||||
#include "libxl_internal.h"
|
||||
+#include "libxl_arch.h"
|
||||
#include "_paths.h"
|
||||
|
||||
#ifndef LIBXL_HAVE_NONCONST_LIBXL_BASENAME_RETURN_VALUE
|
||||
@@ -39,13 +40,7 @@ char *libxl_basename(const char *name)
|
||||
|
||||
unsigned long libxl_get_required_shadow_memory(unsigned long maxmem_kb, unsigned int smp_cpus)
|
||||
{
|
||||
- /* 256 pages (1MB) per vcpu,
|
||||
- plus 1 page per MiB of RAM for the P2M map,
|
||||
- plus 1 page per MiB of RAM to shadow the resident processes.
|
||||
- This is higher than the minimum that Xen would allocate if no value
|
||||
- were given (but the Xen minimum is for safety, not performance).
|
||||
- */
|
||||
- return 4 * (256 * smp_cpus + 2 * (maxmem_kb / 1024));
|
||||
+ return libxl__arch_get_required_paging_memory(maxmem_kb, smp_cpus);
|
||||
}
|
||||
|
||||
char *libxl_domid_to_name(libxl_ctx *ctx, uint32_t domid)
|
||||
diff --git a/tools/libs/light/libxl_x86.c b/tools/libs/light/libxl_x86.c
|
||||
index 1feadebb1852..51362893cf98 100644
|
||||
--- a/tools/libs/light/libxl_x86.c
|
||||
+++ b/tools/libs/light/libxl_x86.c
|
||||
@@ -882,6 +882,19 @@ void libxl__arch_update_domain_config(libxl__gc *gc,
|
||||
libxl_defbool_val(src->b_info.arch_x86.msr_relaxed));
|
||||
}
|
||||
|
||||
+unsigned long libxl__arch_get_required_paging_memory(unsigned long maxmem_kb,
|
||||
+ unsigned int smp_cpus)
|
||||
+{
|
||||
+ /*
|
||||
+ * 256 pages (1MB) per vcpu,
|
||||
+ * plus 1 page per MiB of RAM for the P2M map,
|
||||
+ * plus 1 page per MiB of RAM to shadow the resident processes.
|
||||
+ * This is higher than the minimum that Xen would allocate if no value
|
||||
+ * were given (but the Xen minimum is for safety, not performance).
|
||||
+ */
|
||||
+ return 4 * (256 * smp_cpus + 2 * (maxmem_kb / 1024));
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Local variables:
|
||||
* mode: C
|
||||
--
|
||||
2.37.1
|
||||
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
From 419a4bbc20cf7c5d7d9dedae59fb8049922e6a2c Mon Sep 17 00:00:00 2001
|
||||
From: Henry Wang <Henry.Wang@arm.com>
|
||||
Date: Mon, 6 Jun 2022 06:17:28 +0000
|
||||
Subject: [PATCH 2/4] xen/arm: Construct the P2M pages pool for guests
|
||||
|
||||
This commit constructs the p2m pages pool for guests from the
|
||||
data structure and helper perspective.
|
||||
|
||||
This is implemented by:
|
||||
|
||||
- Adding a `struct paging_domain` which contains a freelist, a
|
||||
counter variable and a spinlock to `struct arch_domain` to
|
||||
indicate the free p2m pages and the number of p2m total pages in
|
||||
the p2m pages pool.
|
||||
|
||||
- Adding a helper `p2m_get_allocation` to get the p2m pool size.
|
||||
|
||||
- Adding a helper `p2m_set_allocation` to set the p2m pages pool
|
||||
size. This helper should be called before allocating memory for
|
||||
a guest.
|
||||
|
||||
- Adding a helper `p2m_teardown_allocation` to free the p2m pages
|
||||
pool. This helper should be called during the xl domain destory.
|
||||
|
||||
This is part of CVE-2022-33747 / XSA-409.
|
||||
|
||||
Signed-off-by: Henry Wang <Henry.Wang@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
---
|
||||
xen/arch/arm/p2m.c | 88 ++++++++++++++++++++++++++++++++++++
|
||||
xen/include/asm-arm/domain.h | 10 ++++
|
||||
xen/include/asm-arm/p2m.h | 4 ++
|
||||
3 files changed, 102 insertions(+)
|
||||
|
||||
diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c
|
||||
index 3bcd1e897e88..79f3d37f5230 100644
|
||||
--- a/xen/arch/arm/p2m.c
|
||||
+++ b/xen/arch/arm/p2m.c
|
||||
@@ -50,6 +50,92 @@ static uint64_t generate_vttbr(uint16_t vmid, mfn_t root_mfn)
|
||||
return (mfn_to_maddr(root_mfn) | ((uint64_t)vmid << 48));
|
||||
}
|
||||
|
||||
+/* Return the size of the pool, rounded up to the nearest MB */
|
||||
+unsigned int p2m_get_allocation(struct domain *d)
|
||||
+{
|
||||
+ unsigned long nr_pages = ACCESS_ONCE(d->arch.paging.p2m_total_pages);
|
||||
+
|
||||
+ return ROUNDUP(nr_pages, 1 << (20 - PAGE_SHIFT)) >> (20 - PAGE_SHIFT);
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Set the pool of pages to the required number of pages.
|
||||
+ * Returns 0 for success, non-zero for failure.
|
||||
+ * Call with d->arch.paging.lock held.
|
||||
+ */
|
||||
+int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted)
|
||||
+{
|
||||
+ struct page_info *pg;
|
||||
+
|
||||
+ ASSERT(spin_is_locked(&d->arch.paging.lock));
|
||||
+
|
||||
+ for ( ; ; )
|
||||
+ {
|
||||
+ if ( d->arch.paging.p2m_total_pages < pages )
|
||||
+ {
|
||||
+ /* Need to allocate more memory from domheap */
|
||||
+ pg = alloc_domheap_page(NULL, 0);
|
||||
+ if ( pg == NULL )
|
||||
+ {
|
||||
+ printk(XENLOG_ERR "Failed to allocate P2M pages.\n");
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+ ACCESS_ONCE(d->arch.paging.p2m_total_pages) =
|
||||
+ d->arch.paging.p2m_total_pages + 1;
|
||||
+ page_list_add_tail(pg, &d->arch.paging.p2m_freelist);
|
||||
+ }
|
||||
+ else if ( d->arch.paging.p2m_total_pages > pages )
|
||||
+ {
|
||||
+ /* Need to return memory to domheap */
|
||||
+ pg = page_list_remove_head(&d->arch.paging.p2m_freelist);
|
||||
+ if( pg )
|
||||
+ {
|
||||
+ ACCESS_ONCE(d->arch.paging.p2m_total_pages) =
|
||||
+ d->arch.paging.p2m_total_pages - 1;
|
||||
+ free_domheap_page(pg);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ printk(XENLOG_ERR
|
||||
+ "Failed to free P2M pages, P2M freelist is empty.\n");
|
||||
+ return -ENOMEM;
|
||||
+ }
|
||||
+ }
|
||||
+ else
|
||||
+ break;
|
||||
+
|
||||
+ /* Check to see if we need to yield and try again */
|
||||
+ if ( preempted && general_preempt_check() )
|
||||
+ {
|
||||
+ *preempted = true;
|
||||
+ return -ERESTART;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+int p2m_teardown_allocation(struct domain *d)
|
||||
+{
|
||||
+ int ret = 0;
|
||||
+ bool preempted = false;
|
||||
+
|
||||
+ spin_lock(&d->arch.paging.lock);
|
||||
+ if ( d->arch.paging.p2m_total_pages != 0 )
|
||||
+ {
|
||||
+ ret = p2m_set_allocation(d, 0, &preempted);
|
||||
+ if ( preempted )
|
||||
+ {
|
||||
+ spin_unlock(&d->arch.paging.lock);
|
||||
+ return -ERESTART;
|
||||
+ }
|
||||
+ ASSERT(d->arch.paging.p2m_total_pages == 0);
|
||||
+ }
|
||||
+ spin_unlock(&d->arch.paging.lock);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
/* Unlock the flush and do a P2M TLB flush if necessary */
|
||||
void p2m_write_unlock(struct p2m_domain *p2m)
|
||||
{
|
||||
@@ -1599,7 +1685,9 @@ int p2m_init(struct domain *d)
|
||||
unsigned int cpu;
|
||||
|
||||
rwlock_init(&p2m->lock);
|
||||
+ spin_lock_init(&d->arch.paging.lock);
|
||||
INIT_PAGE_LIST_HEAD(&p2m->pages);
|
||||
+ INIT_PAGE_LIST_HEAD(&d->arch.paging.p2m_freelist);
|
||||
|
||||
p2m->vmid = INVALID_VMID;
|
||||
|
||||
diff --git a/xen/include/asm-arm/domain.h b/xen/include/asm-arm/domain.h
|
||||
index 9b3647587a04..c90daa65afa7 100644
|
||||
--- a/xen/include/asm-arm/domain.h
|
||||
+++ b/xen/include/asm-arm/domain.h
|
||||
@@ -40,6 +40,14 @@ struct vtimer {
|
||||
uint64_t cval;
|
||||
};
|
||||
|
||||
+struct paging_domain {
|
||||
+ spinlock_t lock;
|
||||
+ /* Free P2M pages from the pre-allocated P2M pool */
|
||||
+ struct page_list_head p2m_freelist;
|
||||
+ /* Number of pages from the pre-allocated P2M pool */
|
||||
+ unsigned long p2m_total_pages;
|
||||
+};
|
||||
+
|
||||
struct arch_domain
|
||||
{
|
||||
#ifdef CONFIG_ARM_64
|
||||
@@ -51,6 +59,8 @@ struct arch_domain
|
||||
|
||||
struct hvm_domain hvm;
|
||||
|
||||
+ struct paging_domain paging;
|
||||
+
|
||||
struct vmmio vmmio;
|
||||
|
||||
/* Continuable domain_relinquish_resources(). */
|
||||
diff --git a/xen/include/asm-arm/p2m.h b/xen/include/asm-arm/p2m.h
|
||||
index b3ba83283e11..c9598740bd02 100644
|
||||
--- a/xen/include/asm-arm/p2m.h
|
||||
+++ b/xen/include/asm-arm/p2m.h
|
||||
@@ -218,6 +218,10 @@ void p2m_restore_state(struct vcpu *n);
|
||||
/* Print debugging/statistial info about a domain's p2m */
|
||||
void p2m_dump_info(struct domain *d);
|
||||
|
||||
+unsigned int p2m_get_allocation(struct domain *d);
|
||||
+int p2m_set_allocation(struct domain *d, unsigned long pages, bool *preempted);
|
||||
+int p2m_teardown_allocation(struct domain *d);
|
||||
+
|
||||
static inline void p2m_write_lock(struct p2m_domain *p2m)
|
||||
{
|
||||
write_lock(&p2m->lock);
|
||||
--
|
||||
2.37.1
|
||||
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
From 332a9979d4dd0b047aa16db201c50fcedbd56743 Mon Sep 17 00:00:00 2001
|
||||
From: Henry Wang <Henry.Wang@arm.com>
|
||||
Date: Mon, 6 Jun 2022 06:17:29 +0000
|
||||
Subject: [PATCH 3/4] xen/arm, libxl: Implement XEN_DOMCTL_shadow_op for Arm
|
||||
|
||||
This commit implements the `XEN_DOMCTL_shadow_op` support in Xen
|
||||
for Arm. The p2m pages pool size for xl guests is supposed to be
|
||||
determined by `XEN_DOMCTL_shadow_op`. Hence, this commit:
|
||||
|
||||
- Introduces a function `p2m_domctl` and implements the subops
|
||||
`XEN_DOMCTL_SHADOW_OP_SET_ALLOCATION` and
|
||||
`XEN_DOMCTL_SHADOW_OP_GET_ALLOCATION` of `XEN_DOMCTL_shadow_op`.
|
||||
|
||||
- Adds the `XEN_DOMCTL_SHADOW_OP_SET_ALLOCATION` support in libxl.
|
||||
|
||||
Therefore enabling the setting of shadow memory pool size
|
||||
when creating a guest from xl and getting shadow memory pool size
|
||||
from Xen.
|
||||
|
||||
Note that the `XEN_DOMCTL_shadow_op` added in this commit is only
|
||||
a dummy op, and the functionality of setting/getting p2m memory pool
|
||||
size for xl guests will be added in following commits.
|
||||
|
||||
This is part of CVE-2022-33747 / XSA-409.
|
||||
|
||||
Signed-off-by: Henry Wang <Henry.Wang@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
---
|
||||
tools/libs/light/libxl_arm.c | 12 ++++++++++++
|
||||
xen/arch/arm/domctl.c | 32 ++++++++++++++++++++++++++++++++
|
||||
2 files changed, 44 insertions(+)
|
||||
|
||||
diff --git a/tools/libs/light/libxl_arm.c b/tools/libs/light/libxl_arm.c
|
||||
index 73a95e83af24..22a0c561bbc6 100644
|
||||
--- a/tools/libs/light/libxl_arm.c
|
||||
+++ b/tools/libs/light/libxl_arm.c
|
||||
@@ -131,6 +131,18 @@ int libxl__arch_domain_create(libxl__gc *gc,
|
||||
libxl__domain_build_state *state,
|
||||
uint32_t domid)
|
||||
{
|
||||
+ libxl_ctx *ctx = libxl__gc_owner(gc);
|
||||
+ unsigned int shadow_mb = DIV_ROUNDUP(d_config->b_info.shadow_memkb, 1024);
|
||||
+
|
||||
+ int r = xc_shadow_control(ctx->xch, domid,
|
||||
+ XEN_DOMCTL_SHADOW_OP_SET_ALLOCATION,
|
||||
+ &shadow_mb, 0);
|
||||
+ if (r) {
|
||||
+ LOGED(ERROR, domid,
|
||||
+ "Failed to set %u MiB shadow allocation", shadow_mb);
|
||||
+ return ERROR_FAIL;
|
||||
+ }
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
diff --git a/xen/arch/arm/domctl.c b/xen/arch/arm/domctl.c
|
||||
index 1baf25c3d98b..9bf72e693019 100644
|
||||
--- a/xen/arch/arm/domctl.c
|
||||
+++ b/xen/arch/arm/domctl.c
|
||||
@@ -47,11 +47,43 @@ static int handle_vuart_init(struct domain *d,
|
||||
return rc;
|
||||
}
|
||||
|
||||
+static long p2m_domctl(struct domain *d, struct xen_domctl_shadow_op *sc,
|
||||
+ XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
|
||||
+{
|
||||
+ if ( unlikely(d == current->domain) )
|
||||
+ {
|
||||
+ printk(XENLOG_ERR "Tried to do a p2m domctl op on itself.\n");
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ if ( unlikely(d->is_dying) )
|
||||
+ {
|
||||
+ printk(XENLOG_ERR "Tried to do a p2m domctl op on dying domain %u\n",
|
||||
+ d->domain_id);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+
|
||||
+ switch ( sc->op )
|
||||
+ {
|
||||
+ case XEN_DOMCTL_SHADOW_OP_SET_ALLOCATION:
|
||||
+ return 0;
|
||||
+ case XEN_DOMCTL_SHADOW_OP_GET_ALLOCATION:
|
||||
+ return 0;
|
||||
+ default:
|
||||
+ {
|
||||
+ printk(XENLOG_ERR "Bad p2m domctl op %u\n", sc->op);
|
||||
+ return -EINVAL;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
long arch_do_domctl(struct xen_domctl *domctl, struct domain *d,
|
||||
XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
|
||||
{
|
||||
switch ( domctl->cmd )
|
||||
{
|
||||
+ case XEN_DOMCTL_shadow_op:
|
||||
+ return p2m_domctl(d, &domctl->u.shadow_op, u_domctl);
|
||||
case XEN_DOMCTL_cacheflush:
|
||||
{
|
||||
gfn_t s = _gfn(domctl->u.cacheflush.start_pfn);
|
||||
--
|
||||
2.37.1
|
||||
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
From 39664d9ee041f96e9c7ee131ed8ef72a4d19c9f8 Mon Sep 17 00:00:00 2001
|
||||
From: Henry Wang <Henry.Wang@arm.com>
|
||||
Date: Mon, 6 Jun 2022 06:17:30 +0000
|
||||
Subject: [PATCH 4/4] xen/arm: Allocate and free P2M pages from the P2M pool
|
||||
|
||||
This commit sets/tearsdown of p2m pages pool for non-privileged Arm
|
||||
guests by calling `p2m_set_allocation` and `p2m_teardown_allocation`.
|
||||
|
||||
- For dom0, P2M pages should come from heap directly instead of p2m
|
||||
pool, so that the kernel may take advantage of the extended regions.
|
||||
|
||||
- For xl guests, the setting of the p2m pool is called in
|
||||
`XEN_DOMCTL_shadow_op` and the p2m pool is destroyed in
|
||||
`domain_relinquish_resources`. Note that domctl->u.shadow_op.mb is
|
||||
updated with the new size when setting the p2m pool.
|
||||
|
||||
- For dom0less domUs, the setting of the p2m pool is called before
|
||||
allocating memory during domain creation. Users can specify the p2m
|
||||
pool size by `xen,domain-p2m-mem-mb` dts property.
|
||||
|
||||
To actually allocate/free pages from the p2m pool, this commit adds
|
||||
two helper functions namely `p2m_alloc_page` and `p2m_free_page` to
|
||||
`struct p2m_domain`. By replacing the `alloc_domheap_page` and
|
||||
`free_domheap_page` with these two helper functions, p2m pages can
|
||||
be added/removed from the list of p2m pool rather than from the heap.
|
||||
|
||||
Since page from `p2m_alloc_page` is cleaned, take the opportunity
|
||||
to remove the redundant `clean_page` in `p2m_create_table`.
|
||||
|
||||
This is part of CVE-2022-33747 / XSA-409.
|
||||
|
||||
Signed-off-by: Henry Wang <Henry.Wang@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
---
|
||||
docs/misc/arm/device-tree/booting.txt | 8 ++++
|
||||
xen/arch/arm/domain.c | 6 +++
|
||||
xen/arch/arm/domain_build.c | 29 ++++++++++++++
|
||||
xen/arch/arm/domctl.c | 23 ++++++++++-
|
||||
xen/arch/arm/p2m.c | 57 +++++++++++++++++++++++++--
|
||||
5 files changed, 118 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/docs/misc/arm/device-tree/booting.txt b/docs/misc/arm/device-tree/booting.txt
|
||||
index 71895663a4de..d92ccc56ffe0 100644
|
||||
--- a/docs/misc/arm/device-tree/booting.txt
|
||||
+++ b/docs/misc/arm/device-tree/booting.txt
|
||||
@@ -182,6 +182,14 @@ with the following properties:
|
||||
Both #address-cells and #size-cells need to be specified because
|
||||
both sub-nodes (described shortly) have reg properties.
|
||||
|
||||
+- xen,domain-p2m-mem-mb
|
||||
+
|
||||
+ Optional. A 32-bit integer specifying the amount of megabytes of RAM
|
||||
+ used for the domain P2M pool. This is in-sync with the shadow_memory
|
||||
+ option in xl.cfg. Leaving this field empty in device tree will lead to
|
||||
+ the default size of domain P2M pool, i.e. 1MB per guest vCPU plus 4KB
|
||||
+ per MB of guest RAM plus 512KB for guest extended regions.
|
||||
+
|
||||
Under the "xen,domain" compatible node, one or more sub-nodes are present
|
||||
for the DomU kernel and ramdisk.
|
||||
|
||||
diff --git a/xen/arch/arm/domain.c b/xen/arch/arm/domain.c
|
||||
index 2694c39127c5..a818f33a1afa 100644
|
||||
--- a/xen/arch/arm/domain.c
|
||||
+++ b/xen/arch/arm/domain.c
|
||||
@@ -997,6 +997,7 @@ enum {
|
||||
PROG_page,
|
||||
PROG_mapping,
|
||||
PROG_p2m,
|
||||
+ PROG_p2m_pool,
|
||||
PROG_done,
|
||||
};
|
||||
|
||||
@@ -1062,6 +1063,11 @@ int domain_relinquish_resources(struct domain *d)
|
||||
if ( ret )
|
||||
return ret;
|
||||
|
||||
+ PROGRESS(p2m_pool):
|
||||
+ ret = p2m_teardown_allocation(d);
|
||||
+ if( ret )
|
||||
+ return ret;
|
||||
+
|
||||
PROGRESS(done):
|
||||
break;
|
||||
|
||||
diff --git a/xen/arch/arm/domain_build.c b/xen/arch/arm/domain_build.c
|
||||
index d02bacbcd1ed..8aec3755ca5d 100644
|
||||
--- a/xen/arch/arm/domain_build.c
|
||||
+++ b/xen/arch/arm/domain_build.c
|
||||
@@ -2833,6 +2833,21 @@ static void __init find_gnttab_region(struct domain *d,
|
||||
kinfo->gnttab_start, kinfo->gnttab_start + kinfo->gnttab_size);
|
||||
}
|
||||
|
||||
+static unsigned long __init domain_p2m_pages(unsigned long maxmem_kb,
|
||||
+ unsigned int smp_cpus)
|
||||
+{
|
||||
+ /*
|
||||
+ * Keep in sync with libxl__get_required_paging_memory().
|
||||
+ * 256 pages (1MB) per vcpu, plus 1 page per MiB of RAM for the P2M map,
|
||||
+ * plus 128 pages to cover extended regions.
|
||||
+ */
|
||||
+ unsigned long memkb = 4 * (256 * smp_cpus + (maxmem_kb / 1024) + 128);
|
||||
+
|
||||
+ BUILD_BUG_ON(PAGE_SIZE != SZ_4K);
|
||||
+
|
||||
+ return DIV_ROUND_UP(memkb, 1024) << (20 - PAGE_SHIFT);
|
||||
+}
|
||||
+
|
||||
static int __init construct_domain(struct domain *d, struct kernel_info *kinfo)
|
||||
{
|
||||
unsigned int i;
|
||||
@@ -2924,6 +2939,8 @@ static int __init construct_domU(struct domain *d,
|
||||
struct kernel_info kinfo = {};
|
||||
int rc;
|
||||
u64 mem;
|
||||
+ u32 p2m_mem_mb;
|
||||
+ unsigned long p2m_pages;
|
||||
|
||||
rc = dt_property_read_u64(node, "memory", &mem);
|
||||
if ( !rc )
|
||||
@@ -2933,6 +2950,18 @@ static int __init construct_domU(struct domain *d,
|
||||
}
|
||||
kinfo.unassigned_mem = (paddr_t)mem * SZ_1K;
|
||||
|
||||
+ rc = dt_property_read_u32(node, "xen,domain-p2m-mem-mb", &p2m_mem_mb);
|
||||
+ /* If xen,domain-p2m-mem-mb is not specified, use the default value. */
|
||||
+ p2m_pages = rc ?
|
||||
+ p2m_mem_mb << (20 - PAGE_SHIFT) :
|
||||
+ domain_p2m_pages(mem, d->max_vcpus);
|
||||
+
|
||||
+ spin_lock(&d->arch.paging.lock);
|
||||
+ rc = p2m_set_allocation(d, p2m_pages, NULL);
|
||||
+ spin_unlock(&d->arch.paging.lock);
|
||||
+ if ( rc != 0 )
|
||||
+ return rc;
|
||||
+
|
||||
printk("*** LOADING DOMU cpus=%u memory=%"PRIx64"KB ***\n", d->max_vcpus, mem);
|
||||
|
||||
kinfo.vpl011 = dt_property_read_bool(node, "vpl011");
|
||||
diff --git a/xen/arch/arm/domctl.c b/xen/arch/arm/domctl.c
|
||||
index 9bf72e693019..c8fdeb124084 100644
|
||||
--- a/xen/arch/arm/domctl.c
|
||||
+++ b/xen/arch/arm/domctl.c
|
||||
@@ -50,6 +50,9 @@ static int handle_vuart_init(struct domain *d,
|
||||
static long p2m_domctl(struct domain *d, struct xen_domctl_shadow_op *sc,
|
||||
XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
|
||||
{
|
||||
+ long rc;
|
||||
+ bool preempted = false;
|
||||
+
|
||||
if ( unlikely(d == current->domain) )
|
||||
{
|
||||
printk(XENLOG_ERR "Tried to do a p2m domctl op on itself.\n");
|
||||
@@ -66,9 +69,27 @@ static long p2m_domctl(struct domain *d, struct xen_domctl_shadow_op *sc,
|
||||
switch ( sc->op )
|
||||
{
|
||||
case XEN_DOMCTL_SHADOW_OP_SET_ALLOCATION:
|
||||
- return 0;
|
||||
+ {
|
||||
+ /* Allow and handle preemption */
|
||||
+ spin_lock(&d->arch.paging.lock);
|
||||
+ rc = p2m_set_allocation(d, sc->mb << (20 - PAGE_SHIFT), &preempted);
|
||||
+ spin_unlock(&d->arch.paging.lock);
|
||||
+
|
||||
+ if ( preempted )
|
||||
+ /* Not finished. Set up to re-run the call. */
|
||||
+ rc = hypercall_create_continuation(__HYPERVISOR_domctl, "h",
|
||||
+ u_domctl);
|
||||
+ else
|
||||
+ /* Finished. Return the new allocation. */
|
||||
+ sc->mb = p2m_get_allocation(d);
|
||||
+
|
||||
+ return rc;
|
||||
+ }
|
||||
case XEN_DOMCTL_SHADOW_OP_GET_ALLOCATION:
|
||||
+ {
|
||||
+ sc->mb = p2m_get_allocation(d);
|
||||
return 0;
|
||||
+ }
|
||||
default:
|
||||
{
|
||||
printk(XENLOG_ERR "Bad p2m domctl op %u\n", sc->op);
|
||||
diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c
|
||||
index 79f3d37f5230..1bf9cbeb53cf 100644
|
||||
--- a/xen/arch/arm/p2m.c
|
||||
+++ b/xen/arch/arm/p2m.c
|
||||
@@ -50,6 +50,54 @@ static uint64_t generate_vttbr(uint16_t vmid, mfn_t root_mfn)
|
||||
return (mfn_to_maddr(root_mfn) | ((uint64_t)vmid << 48));
|
||||
}
|
||||
|
||||
+static struct page_info *p2m_alloc_page(struct domain *d)
|
||||
+{
|
||||
+ struct page_info *pg;
|
||||
+
|
||||
+ spin_lock(&d->arch.paging.lock);
|
||||
+ /*
|
||||
+ * For hardware domain, there should be no limit in the number of pages that
|
||||
+ * can be allocated, so that the kernel may take advantage of the extended
|
||||
+ * regions. Hence, allocate p2m pages for hardware domains from heap.
|
||||
+ */
|
||||
+ if ( is_hardware_domain(d) )
|
||||
+ {
|
||||
+ pg = alloc_domheap_page(NULL, 0);
|
||||
+ if ( pg == NULL )
|
||||
+ {
|
||||
+ printk(XENLOG_G_ERR "Failed to allocate P2M pages for hwdom.\n");
|
||||
+ spin_unlock(&d->arch.paging.lock);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ pg = page_list_remove_head(&d->arch.paging.p2m_freelist);
|
||||
+ if ( unlikely(!pg) )
|
||||
+ {
|
||||
+ spin_unlock(&d->arch.paging.lock);
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ d->arch.paging.p2m_total_pages--;
|
||||
+ }
|
||||
+ spin_unlock(&d->arch.paging.lock);
|
||||
+
|
||||
+ return pg;
|
||||
+}
|
||||
+
|
||||
+static void p2m_free_page(struct domain *d, struct page_info *pg)
|
||||
+{
|
||||
+ spin_lock(&d->arch.paging.lock);
|
||||
+ if ( is_hardware_domain(d) )
|
||||
+ free_domheap_page(pg);
|
||||
+ else
|
||||
+ {
|
||||
+ d->arch.paging.p2m_total_pages++;
|
||||
+ page_list_add_tail(pg, &d->arch.paging.p2m_freelist);
|
||||
+ }
|
||||
+ spin_unlock(&d->arch.paging.lock);
|
||||
+}
|
||||
+
|
||||
/* Return the size of the pool, rounded up to the nearest MB */
|
||||
unsigned int p2m_get_allocation(struct domain *d)
|
||||
{
|
||||
@@ -751,7 +799,7 @@ static int p2m_create_table(struct p2m_domain *p2m, lpae_t *entry)
|
||||
|
||||
ASSERT(!p2m_is_valid(*entry));
|
||||
|
||||
- page = alloc_domheap_page(NULL, 0);
|
||||
+ page = p2m_alloc_page(p2m->domain);
|
||||
if ( page == NULL )
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -878,7 +926,7 @@ static void p2m_free_entry(struct p2m_domain *p2m,
|
||||
pg = mfn_to_page(mfn);
|
||||
|
||||
page_list_del(pg, &p2m->pages);
|
||||
- free_domheap_page(pg);
|
||||
+ p2m_free_page(p2m->domain, pg);
|
||||
}
|
||||
|
||||
static bool p2m_split_superpage(struct p2m_domain *p2m, lpae_t *entry,
|
||||
@@ -902,7 +950,7 @@ static bool p2m_split_superpage(struct p2m_domain *p2m, lpae_t *entry,
|
||||
ASSERT(level < target);
|
||||
ASSERT(p2m_is_superpage(*entry, level));
|
||||
|
||||
- page = alloc_domheap_page(NULL, 0);
|
||||
+ page = p2m_alloc_page(p2m->domain);
|
||||
if ( !page )
|
||||
return false;
|
||||
|
||||
@@ -1641,7 +1689,7 @@ int p2m_teardown(struct domain *d)
|
||||
|
||||
while ( (pg = page_list_remove_head(&p2m->pages)) )
|
||||
{
|
||||
- free_domheap_page(pg);
|
||||
+ p2m_free_page(p2m->domain, pg);
|
||||
count++;
|
||||
/* Arbitrarily preempt every 512 iterations */
|
||||
if ( !(count % 512) && hypercall_preempt_check() )
|
||||
@@ -1665,6 +1713,7 @@ void p2m_final_teardown(struct domain *d)
|
||||
return;
|
||||
|
||||
ASSERT(page_list_empty(&p2m->pages));
|
||||
+ ASSERT(page_list_empty(&d->arch.paging.p2m_freelist));
|
||||
|
||||
if ( p2m->root )
|
||||
free_domheap_pages(p2m->root, P2M_ROOT_ORDER);
|
||||
--
|
||||
2.37.1
|
||||
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
From 4b4359122a414cc15156e13e3805988b71ff9da0 Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <jgrall@amazon.com>
|
||||
Date: Mon, 6 Jun 2022 06:17:25 +0000
|
||||
Subject: [PATCH 1/2] xen/arm: p2m: Prevent adding mapping when domain is dying
|
||||
|
||||
During the domain destroy process, the domain will still be accessible
|
||||
until it is fully destroyed. So does the P2M because we don't bail
|
||||
out early if is_dying is non-zero. If a domain has permission to
|
||||
modify the other domain's P2M (i.e. dom0, or a stubdomain), then
|
||||
foreign mapping can be added past relinquish_p2m_mapping().
|
||||
|
||||
Therefore, we need to prevent mapping to be added when the domain
|
||||
is dying. This commit prevents such adding of mapping by adding the
|
||||
d->is_dying check to p2m_set_entry(). Also this commit enhances the
|
||||
check in relinquish_p2m_mapping() to make sure that no mappings can
|
||||
be added in the P2M after the P2M lock is released.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Henry Wang <Henry.Wang@arm.com>
|
||||
Tested-by: Henry Wang <Henry.Wang@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
---
|
||||
xen/arch/arm/p2m.c | 11 +++++++++++
|
||||
1 file changed, 11 insertions(+)
|
||||
|
||||
diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c
|
||||
index fb71fa4c1c90..cbeff90f4371 100644
|
||||
--- a/xen/arch/arm/p2m.c
|
||||
+++ b/xen/arch/arm/p2m.c
|
||||
@@ -1093,6 +1093,15 @@ int p2m_set_entry(struct p2m_domain *p2m,
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
+ /*
|
||||
+ * Any reference taken by the P2M mappings (e.g. foreign mapping) will
|
||||
+ * be dropped in relinquish_p2m_mapping(). As the P2M will still
|
||||
+ * be accessible after, we need to prevent mapping to be added when the
|
||||
+ * domain is dying.
|
||||
+ */
|
||||
+ if ( unlikely(p2m->domain->is_dying) )
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
while ( nr )
|
||||
{
|
||||
unsigned long mask;
|
||||
@@ -1610,6 +1619,8 @@ int relinquish_p2m_mapping(struct domain *d)
|
||||
unsigned int order;
|
||||
gfn_t start, end;
|
||||
|
||||
+ BUG_ON(!d->is_dying);
|
||||
+ /* No mappings can be added in the P2M after the P2M lock is released. */
|
||||
p2m_write_lock(p2m);
|
||||
|
||||
start = p2m->lowest_mapped_gfn;
|
||||
--
|
||||
2.37.1
|
||||
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
From 0d5846490348fa09a0d0915d7c795685a016ce10 Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <jgrall@amazon.com>
|
||||
Date: Mon, 6 Jun 2022 06:17:26 +0000
|
||||
Subject: [PATCH 2/2] xen/arm: p2m: Handle preemption when freeing intermediate
|
||||
page tables
|
||||
|
||||
At the moment the P2M page tables will be freed when the domain structure
|
||||
is freed without any preemption. As the P2M is quite large, iterating
|
||||
through this may take more time than it is reasonable without intermediate
|
||||
preemption (to run softirqs and perhaps scheduler).
|
||||
|
||||
Split p2m_teardown() in two parts: one preemptible and called when
|
||||
relinquishing the resources, the other one non-preemptible and called
|
||||
when freeing the domain structure.
|
||||
|
||||
As we are now freeing the P2M pages early, we also need to prevent
|
||||
further allocation if someone call p2m_set_entry() past p2m_teardown()
|
||||
(I wasn't able to prove this will never happen). This is done by
|
||||
the checking domain->is_dying from previous patch in p2m_set_entry().
|
||||
|
||||
Similarly, we want to make sure that no-one can accessed the free
|
||||
pages. Therefore the root is cleared before freeing pages.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Henry Wang <Henry.Wang@arm.com>
|
||||
Tested-by: Henry Wang <Henry.Wang@arm.com>
|
||||
Reviewed-by: Stefano Stabellini <sstabellini@kernel.org>
|
||||
---
|
||||
xen/arch/arm/domain.c | 10 +++++++--
|
||||
xen/arch/arm/p2m.c | 47 ++++++++++++++++++++++++++++++++++++---
|
||||
xen/include/asm-arm/p2m.h | 13 +++++++++--
|
||||
3 files changed, 63 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/arm/domain.c b/xen/arch/arm/domain.c
|
||||
index 96e1b235501d..2694c39127c5 100644
|
||||
--- a/xen/arch/arm/domain.c
|
||||
+++ b/xen/arch/arm/domain.c
|
||||
@@ -789,10 +789,10 @@ fail:
|
||||
void arch_domain_destroy(struct domain *d)
|
||||
{
|
||||
/* IOMMU page table is shared with P2M, always call
|
||||
- * iommu_domain_destroy() before p2m_teardown().
|
||||
+ * iommu_domain_destroy() before p2m_final_teardown().
|
||||
*/
|
||||
iommu_domain_destroy(d);
|
||||
- p2m_teardown(d);
|
||||
+ p2m_final_teardown(d);
|
||||
domain_vgic_free(d);
|
||||
domain_vuart_free(d);
|
||||
free_xenheap_page(d->shared_info);
|
||||
@@ -996,6 +996,7 @@ enum {
|
||||
PROG_xen,
|
||||
PROG_page,
|
||||
PROG_mapping,
|
||||
+ PROG_p2m,
|
||||
PROG_done,
|
||||
};
|
||||
|
||||
@@ -1056,6 +1057,11 @@ int domain_relinquish_resources(struct domain *d)
|
||||
if ( ret )
|
||||
return ret;
|
||||
|
||||
+ PROGRESS(p2m):
|
||||
+ ret = p2m_teardown(d);
|
||||
+ if ( ret )
|
||||
+ return ret;
|
||||
+
|
||||
PROGRESS(done):
|
||||
break;
|
||||
|
||||
diff --git a/xen/arch/arm/p2m.c b/xen/arch/arm/p2m.c
|
||||
index cbeff90f4371..3bcd1e897e88 100644
|
||||
--- a/xen/arch/arm/p2m.c
|
||||
+++ b/xen/arch/arm/p2m.c
|
||||
@@ -1527,17 +1527,58 @@ static void p2m_free_vmid(struct domain *d)
|
||||
spin_unlock(&vmid_alloc_lock);
|
||||
}
|
||||
|
||||
-void p2m_teardown(struct domain *d)
|
||||
+int p2m_teardown(struct domain *d)
|
||||
{
|
||||
struct p2m_domain *p2m = p2m_get_hostp2m(d);
|
||||
+ unsigned long count = 0;
|
||||
struct page_info *pg;
|
||||
+ unsigned int i;
|
||||
+ int rc = 0;
|
||||
+
|
||||
+ p2m_write_lock(p2m);
|
||||
+
|
||||
+ /*
|
||||
+ * We are about to free the intermediate page-tables, so clear the
|
||||
+ * root to prevent any walk to use them.
|
||||
+ */
|
||||
+ for ( i = 0; i < P2M_ROOT_PAGES; i++ )
|
||||
+ clear_and_clean_page(p2m->root + i);
|
||||
+
|
||||
+ /*
|
||||
+ * The domain will not be scheduled anymore, so in theory we should
|
||||
+ * not need to flush the TLBs. Do it for safety purpose.
|
||||
+ *
|
||||
+ * Note that all the devices have already been de-assigned. So we don't
|
||||
+ * need to flush the IOMMU TLB here.
|
||||
+ */
|
||||
+ p2m_force_tlb_flush_sync(p2m);
|
||||
+
|
||||
+ while ( (pg = page_list_remove_head(&p2m->pages)) )
|
||||
+ {
|
||||
+ free_domheap_page(pg);
|
||||
+ count++;
|
||||
+ /* Arbitrarily preempt every 512 iterations */
|
||||
+ if ( !(count % 512) && hypercall_preempt_check() )
|
||||
+ {
|
||||
+ rc = -ERESTART;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ p2m_write_unlock(p2m);
|
||||
+
|
||||
+ return rc;
|
||||
+}
|
||||
+
|
||||
+void p2m_final_teardown(struct domain *d)
|
||||
+{
|
||||
+ struct p2m_domain *p2m = p2m_get_hostp2m(d);
|
||||
|
||||
/* p2m not actually initialized */
|
||||
if ( !p2m->domain )
|
||||
return;
|
||||
|
||||
- while ( (pg = page_list_remove_head(&p2m->pages)) )
|
||||
- free_domheap_page(pg);
|
||||
+ ASSERT(page_list_empty(&p2m->pages));
|
||||
|
||||
if ( p2m->root )
|
||||
free_domheap_pages(p2m->root, P2M_ROOT_ORDER);
|
||||
diff --git a/xen/include/asm-arm/p2m.h b/xen/include/asm-arm/p2m.h
|
||||
index 8f11d9c97b5d..b3ba83283e11 100644
|
||||
--- a/xen/include/asm-arm/p2m.h
|
||||
+++ b/xen/include/asm-arm/p2m.h
|
||||
@@ -192,8 +192,17 @@ void setup_virt_paging(void);
|
||||
/* Init the datastructures for later use by the p2m code */
|
||||
int p2m_init(struct domain *d);
|
||||
|
||||
-/* Return all the p2m resources to Xen. */
|
||||
-void p2m_teardown(struct domain *d);
|
||||
+/*
|
||||
+ * The P2M resources are freed in two parts:
|
||||
+ * - p2m_teardown() will be called when relinquish the resources. It
|
||||
+ * will free large resources (e.g. intermediate page-tables) that
|
||||
+ * requires preemption.
|
||||
+ * - p2m_final_teardown() will be called when domain struct is been
|
||||
+ * freed. This *cannot* be preempted and therefore one small
|
||||
+ * resources should be freed here.
|
||||
+ */
|
||||
+int p2m_teardown(struct domain *d);
|
||||
+void p2m_final_teardown(struct domain *d);
|
||||
|
||||
/*
|
||||
* Remove mapping refcount on each mapping page in the p2m
|
||||
--
|
||||
2.37.1
|
||||
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
From: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Subject: x86/p2m: add option to skip root pagetable removal in p2m_teardown()
|
||||
|
||||
Add a new parameter to p2m_teardown() in order to select whether the
|
||||
root page table should also be freed. Note that all users are
|
||||
adjusted to pass the parameter to remove the root page tables, so
|
||||
behavior is not modified.
|
||||
|
||||
No functional change intended.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Suggested-by: Julien Grall <julien@xen.org>
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Tim Deegan <tim@xen.org>
|
||||
|
||||
--- a/xen/include/asm-x86/p2m.h
|
||||
+++ b/xen/include/asm-x86/p2m.h
|
||||
@@ -574,7 +574,7 @@ int p2m_init(struct domain *d);
|
||||
int p2m_alloc_table(struct p2m_domain *p2m);
|
||||
|
||||
/* Return all the p2m resources to Xen. */
|
||||
-void p2m_teardown(struct p2m_domain *p2m);
|
||||
+void p2m_teardown(struct p2m_domain *p2m, bool remove_root);
|
||||
void p2m_final_teardown(struct domain *d);
|
||||
|
||||
/* Add a page to a domain's p2m table */
|
||||
--- a/xen/arch/x86/mm/hap/hap.c
|
||||
+++ b/xen/arch/x86/mm/hap/hap.c
|
||||
@@ -541,18 +541,18 @@ void hap_final_teardown(struct domain *d
|
||||
}
|
||||
|
||||
for ( i = 0; i < MAX_ALTP2M; i++ )
|
||||
- p2m_teardown(d->arch.altp2m_p2m[i]);
|
||||
+ p2m_teardown(d->arch.altp2m_p2m[i], true);
|
||||
}
|
||||
|
||||
/* Destroy nestedp2m's first */
|
||||
for (i = 0; i < MAX_NESTEDP2M; i++) {
|
||||
- p2m_teardown(d->arch.nested_p2m[i]);
|
||||
+ p2m_teardown(d->arch.nested_p2m[i], true);
|
||||
}
|
||||
|
||||
if ( d->arch.paging.hap.total_pages != 0 )
|
||||
hap_teardown(d, NULL);
|
||||
|
||||
- p2m_teardown(p2m_get_hostp2m(d));
|
||||
+ p2m_teardown(p2m_get_hostp2m(d), true);
|
||||
/* Free any memory that the p2m teardown released */
|
||||
paging_lock(d);
|
||||
hap_set_allocation(d, 0, NULL);
|
||||
--- a/xen/arch/x86/mm/p2m.c
|
||||
+++ b/xen/arch/x86/mm/p2m.c
|
||||
@@ -749,11 +749,11 @@ int p2m_alloc_table(struct p2m_domain *p
|
||||
* hvm fixme: when adding support for pvh non-hardware domains, this path must
|
||||
* cleanup any foreign p2m types (release refcnts on them).
|
||||
*/
|
||||
-void p2m_teardown(struct p2m_domain *p2m)
|
||||
+void p2m_teardown(struct p2m_domain *p2m, bool remove_root)
|
||||
/* Return all the p2m pages to Xen.
|
||||
* We know we don't have any extra mappings to these pages */
|
||||
{
|
||||
- struct page_info *pg;
|
||||
+ struct page_info *pg, *root_pg = NULL;
|
||||
struct domain *d;
|
||||
|
||||
if (p2m == NULL)
|
||||
@@ -763,10 +763,22 @@ void p2m_teardown(struct p2m_domain *p2m
|
||||
|
||||
p2m_lock(p2m);
|
||||
ASSERT(atomic_read(&d->shr_pages) == 0);
|
||||
- p2m->phys_table = pagetable_null();
|
||||
+
|
||||
+ if ( remove_root )
|
||||
+ p2m->phys_table = pagetable_null();
|
||||
+ else if ( !pagetable_is_null(p2m->phys_table) )
|
||||
+ {
|
||||
+ root_pg = pagetable_get_page(p2m->phys_table);
|
||||
+ clear_domain_page(pagetable_get_mfn(p2m->phys_table));
|
||||
+ }
|
||||
|
||||
while ( (pg = page_list_remove_head(&p2m->pages)) )
|
||||
- d->arch.paging.free_page(d, pg);
|
||||
+ if ( pg != root_pg )
|
||||
+ d->arch.paging.free_page(d, pg);
|
||||
+
|
||||
+ if ( root_pg )
|
||||
+ page_list_add(root_pg, &p2m->pages);
|
||||
+
|
||||
p2m_unlock(p2m);
|
||||
}
|
||||
|
||||
--- a/xen/arch/x86/mm/shadow/common.c
|
||||
+++ b/xen/arch/x86/mm/shadow/common.c
|
||||
@@ -2701,7 +2701,7 @@ int shadow_enable(struct domain *d, u32
|
||||
paging_unlock(d);
|
||||
out_unlocked:
|
||||
if ( rv != 0 && !pagetable_is_null(p2m_get_pagetable(p2m)) )
|
||||
- p2m_teardown(p2m);
|
||||
+ p2m_teardown(p2m, true);
|
||||
if ( rv != 0 && pg != NULL )
|
||||
{
|
||||
pg->count_info &= ~PGC_count_mask;
|
||||
@@ -2866,7 +2866,7 @@ void shadow_final_teardown(struct domain
|
||||
shadow_teardown(d, NULL);
|
||||
|
||||
/* It is now safe to pull down the p2m map. */
|
||||
- p2m_teardown(p2m_get_hostp2m(d));
|
||||
+ p2m_teardown(p2m_get_hostp2m(d), true);
|
||||
/* Free any shadow memory that the p2m teardown released */
|
||||
paging_lock(d);
|
||||
shadow_set_allocation(d, 0, NULL);
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: x86/HAP: adjust monitor table related error handling
|
||||
|
||||
hap_make_monitor_table() will return INVALID_MFN if it encounters an
|
||||
error condition, but hap_update_paging_modes() wasn’t handling this
|
||||
value, resulting in an inappropriate value being stored in
|
||||
monitor_table. This would subsequently misguide at least
|
||||
hap_vcpu_teardown(). Avoid this by bailing early.
|
||||
|
||||
Further, when a domain has/was already crashed or (perhaps less
|
||||
important as there's no such path known to lead here) is already dying,
|
||||
avoid calling domain_crash() on it again - that's at best confusing.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
|
||||
--- a/xen/arch/x86/mm/hap/hap.c
|
||||
+++ b/xen/arch/x86/mm/hap/hap.c
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <asm/domain.h>
|
||||
#include <xen/numa.h>
|
||||
#include <asm/hvm/nestedhvm.h>
|
||||
+#include <public/sched.h>
|
||||
|
||||
#include "private.h"
|
||||
|
||||
@@ -405,8 +406,13 @@ static mfn_t hap_make_monitor_table(stru
|
||||
return m4mfn;
|
||||
|
||||
oom:
|
||||
- printk(XENLOG_G_ERR "out of memory building monitor pagetable\n");
|
||||
- domain_crash(d);
|
||||
+ if ( !d->is_dying &&
|
||||
+ (!d->is_shutting_down || d->shutdown_code != SHUTDOWN_crash) )
|
||||
+ {
|
||||
+ printk(XENLOG_G_ERR "%pd: out of memory building monitor pagetable\n",
|
||||
+ d);
|
||||
+ domain_crash(d);
|
||||
+ }
|
||||
return INVALID_MFN;
|
||||
}
|
||||
|
||||
@@ -766,6 +772,9 @@ static void hap_update_paging_modes(stru
|
||||
if ( pagetable_is_null(v->arch.hvm.monitor_table) )
|
||||
{
|
||||
mfn_t mmfn = hap_make_monitor_table(v);
|
||||
+
|
||||
+ if ( mfn_eq(mmfn, INVALID_MFN) )
|
||||
+ goto unlock;
|
||||
v->arch.hvm.monitor_table = pagetable_from_mfn(mmfn);
|
||||
make_cr3(v, mmfn);
|
||||
hvm_update_host_cr3(v);
|
||||
@@ -774,6 +783,7 @@ static void hap_update_paging_modes(stru
|
||||
/* CR3 is effectively updated by a mode change. Flush ASIDs, etc. */
|
||||
hap_update_cr3(v, 0, false);
|
||||
|
||||
+ unlock:
|
||||
paging_unlock(d);
|
||||
put_gfn(d, cr3_gfn);
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: x86/shadow: tolerate failure of sh_set_toplevel_shadow()
|
||||
|
||||
Subsequently sh_set_toplevel_shadow() will be adjusted to install a
|
||||
blank entry in case prealloc fails. There are, in fact, pre-existing
|
||||
error paths which would put in place a blank entry. The 4- and 2-level
|
||||
code in sh_update_cr3(), however, assume the top level entry to be
|
||||
valid.
|
||||
|
||||
Hence bail from the function in the unlikely event that it's not. Note
|
||||
that 3-level logic works differently: In particular a guest is free to
|
||||
supply a PDPTR pointing at 4 non-present (or otherwise deemed invalid)
|
||||
entries. The guest will crash, but we already cope with that.
|
||||
|
||||
Really mfn_valid() is likely wrong to use in sh_set_toplevel_shadow(),
|
||||
and it should instead be !mfn_eq(gmfn, INVALID_MFN). Avoid such a change
|
||||
in security context, but add a respective assertion.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Tim Deegan <tim@xen.org>
|
||||
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
|
||||
--- a/xen/arch/x86/mm/shadow/common.c
|
||||
+++ b/xen/arch/x86/mm/shadow/common.c
|
||||
@@ -2516,6 +2516,7 @@ void sh_set_toplevel_shadow(struct vcpu
|
||||
/* Now figure out the new contents: is this a valid guest MFN? */
|
||||
if ( !mfn_valid(gmfn) )
|
||||
{
|
||||
+ ASSERT(mfn_eq(gmfn, INVALID_MFN));
|
||||
new_entry = pagetable_null();
|
||||
goto install_new_entry;
|
||||
}
|
||||
--- a/xen/arch/x86/mm/shadow/multi.c
|
||||
+++ b/xen/arch/x86/mm/shadow/multi.c
|
||||
@@ -3312,6 +3312,11 @@ sh_update_cr3(struct vcpu *v, int do_loc
|
||||
if ( sh_remove_write_access(d, gmfn, 4, 0) != 0 )
|
||||
guest_flush_tlb_mask(d, d->dirty_cpumask);
|
||||
sh_set_toplevel_shadow(v, 0, gmfn, SH_type_l4_shadow, sh_make_shadow);
|
||||
+ if ( unlikely(pagetable_is_null(v->arch.paging.shadow.shadow_table[0])) )
|
||||
+ {
|
||||
+ ASSERT(d->is_dying || d->is_shutting_down);
|
||||
+ return;
|
||||
+ }
|
||||
if ( !shadow_mode_external(d) && !is_pv_32bit_domain(d) )
|
||||
{
|
||||
mfn_t smfn = pagetable_get_mfn(v->arch.paging.shadow.shadow_table[0]);
|
||||
@@ -3370,6 +3375,11 @@ sh_update_cr3(struct vcpu *v, int do_loc
|
||||
if ( sh_remove_write_access(d, gmfn, 2, 0) != 0 )
|
||||
guest_flush_tlb_mask(d, d->dirty_cpumask);
|
||||
sh_set_toplevel_shadow(v, 0, gmfn, SH_type_l2_shadow, sh_make_shadow);
|
||||
+ if ( unlikely(pagetable_is_null(v->arch.paging.shadow.shadow_table[0])) )
|
||||
+ {
|
||||
+ ASSERT(d->is_dying || d->is_shutting_down);
|
||||
+ return;
|
||||
+ }
|
||||
#else
|
||||
#error This should never happen
|
||||
#endif
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
From: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Subject: x86/shadow: tolerate failure in shadow_prealloc()
|
||||
|
||||
Prevent _shadow_prealloc() from calling BUG() when unable to fulfill
|
||||
the pre-allocation and instead return true/false. Modify
|
||||
shadow_prealloc() to crash the domain on allocation failure (if the
|
||||
domain is not already dying), as shadow cannot operate normally after
|
||||
that. Modify callers to also gracefully handle {_,}shadow_prealloc()
|
||||
failing to fulfill the request.
|
||||
|
||||
Note this in turn requires adjusting the callers of
|
||||
sh_make_monitor_table() also to handle it returning INVALID_MFN.
|
||||
sh_update_paging_modes() is also modified to add additional error
|
||||
paths in case of allocation failure, some of those will return with
|
||||
null monitor page tables (and the domain likely crashed). This is no
|
||||
different that current error paths, but the newly introduced ones are
|
||||
more likely to trigger.
|
||||
|
||||
The now added failure points in sh_update_paging_modes() also require
|
||||
that on some error return paths the previous structures are cleared,
|
||||
and thus monitor table is null.
|
||||
|
||||
While there adjust the 'type' parameter type of shadow_prealloc() to
|
||||
unsigned int rather than u32.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Tim Deegan <tim@xen.org>
|
||||
|
||||
--- a/xen/arch/x86/mm/shadow/common.c
|
||||
+++ b/xen/arch/x86/mm/shadow/common.c
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <asm/flushtlb.h>
|
||||
#include <asm/shadow.h>
|
||||
#include <xen/numa.h>
|
||||
+#include <public/sched.h>
|
||||
#include "private.h"
|
||||
|
||||
DEFINE_PER_CPU(uint32_t,trace_shadow_path_flags);
|
||||
@@ -928,14 +929,15 @@ static inline void trace_shadow_prealloc
|
||||
|
||||
/* Make sure there are at least count order-sized pages
|
||||
* available in the shadow page pool. */
|
||||
-static void _shadow_prealloc(struct domain *d, unsigned int pages)
|
||||
+static bool __must_check _shadow_prealloc(struct domain *d, unsigned int pages)
|
||||
{
|
||||
struct vcpu *v;
|
||||
struct page_info *sp, *t;
|
||||
mfn_t smfn;
|
||||
int i;
|
||||
|
||||
- if ( d->arch.paging.shadow.free_pages >= pages ) return;
|
||||
+ if ( d->arch.paging.shadow.free_pages >= pages )
|
||||
+ return true;
|
||||
|
||||
/* Shouldn't have enabled shadows if we've no vcpus. */
|
||||
ASSERT(d->vcpu && d->vcpu[0]);
|
||||
@@ -951,7 +953,8 @@ static void _shadow_prealloc(struct doma
|
||||
sh_unpin(d, smfn);
|
||||
|
||||
/* See if that freed up enough space */
|
||||
- if ( d->arch.paging.shadow.free_pages >= pages ) return;
|
||||
+ if ( d->arch.paging.shadow.free_pages >= pages )
|
||||
+ return true;
|
||||
}
|
||||
|
||||
/* Stage two: all shadow pages are in use in hierarchies that are
|
||||
@@ -974,7 +977,7 @@ static void _shadow_prealloc(struct doma
|
||||
if ( d->arch.paging.shadow.free_pages >= pages )
|
||||
{
|
||||
guest_flush_tlb_mask(d, d->dirty_cpumask);
|
||||
- return;
|
||||
+ return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -987,7 +990,12 @@ static void _shadow_prealloc(struct doma
|
||||
d->arch.paging.shadow.total_pages,
|
||||
d->arch.paging.shadow.free_pages,
|
||||
d->arch.paging.shadow.p2m_pages);
|
||||
- BUG();
|
||||
+
|
||||
+ ASSERT(d->is_dying);
|
||||
+
|
||||
+ guest_flush_tlb_mask(d, d->dirty_cpumask);
|
||||
+
|
||||
+ return false;
|
||||
}
|
||||
|
||||
/* Make sure there are at least count pages of the order according to
|
||||
@@ -995,9 +1003,19 @@ static void _shadow_prealloc(struct doma
|
||||
* This must be called before any calls to shadow_alloc(). Since this
|
||||
* will free existing shadows to make room, it must be called early enough
|
||||
* to avoid freeing shadows that the caller is currently working on. */
|
||||
-void shadow_prealloc(struct domain *d, u32 type, unsigned int count)
|
||||
+bool shadow_prealloc(struct domain *d, unsigned int type, unsigned int count)
|
||||
{
|
||||
- return _shadow_prealloc(d, shadow_size(type) * count);
|
||||
+ bool ret = _shadow_prealloc(d, shadow_size(type) * count);
|
||||
+
|
||||
+ if ( !ret && !d->is_dying &&
|
||||
+ (!d->is_shutting_down || d->shutdown_code != SHUTDOWN_crash) )
|
||||
+ /*
|
||||
+ * Failing to allocate memory required for shadow usage can only result in
|
||||
+ * a domain crash, do it here rather that relying on every caller to do it.
|
||||
+ */
|
||||
+ domain_crash(d);
|
||||
+
|
||||
+ return ret;
|
||||
}
|
||||
|
||||
/* Deliberately free all the memory we can: this will tear down all of
|
||||
@@ -1218,7 +1236,7 @@ void shadow_free(struct domain *d, mfn_t
|
||||
static struct page_info *
|
||||
shadow_alloc_p2m_page(struct domain *d)
|
||||
{
|
||||
- struct page_info *pg;
|
||||
+ struct page_info *pg = NULL;
|
||||
|
||||
/* This is called both from the p2m code (which never holds the
|
||||
* paging lock) and the log-dirty code (which always does). */
|
||||
@@ -1236,16 +1254,18 @@ shadow_alloc_p2m_page(struct domain *d)
|
||||
d->arch.paging.shadow.p2m_pages,
|
||||
shadow_min_acceptable_pages(d));
|
||||
}
|
||||
- paging_unlock(d);
|
||||
- return NULL;
|
||||
+ goto out;
|
||||
}
|
||||
|
||||
- shadow_prealloc(d, SH_type_p2m_table, 1);
|
||||
+ if ( !shadow_prealloc(d, SH_type_p2m_table, 1) )
|
||||
+ goto out;
|
||||
+
|
||||
pg = mfn_to_page(shadow_alloc(d, SH_type_p2m_table, 0));
|
||||
d->arch.paging.shadow.p2m_pages++;
|
||||
d->arch.paging.shadow.total_pages--;
|
||||
ASSERT(!page_get_owner(pg) && !(pg->count_info & PGC_count_mask));
|
||||
|
||||
+ out:
|
||||
paging_unlock(d);
|
||||
|
||||
return pg;
|
||||
@@ -1336,7 +1356,9 @@ int shadow_set_allocation(struct domain
|
||||
else if ( d->arch.paging.shadow.total_pages > pages )
|
||||
{
|
||||
/* Need to return memory to domheap */
|
||||
- _shadow_prealloc(d, 1);
|
||||
+ if ( !_shadow_prealloc(d, 1) )
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
sp = page_list_remove_head(&d->arch.paging.shadow.freelist);
|
||||
ASSERT(sp);
|
||||
/*
|
||||
@@ -2334,12 +2356,13 @@ static void sh_update_paging_modes(struc
|
||||
if ( mfn_eq(v->arch.paging.shadow.oos_snapshot[0], INVALID_MFN) )
|
||||
{
|
||||
int i;
|
||||
+
|
||||
+ if ( !shadow_prealloc(d, SH_type_oos_snapshot, SHADOW_OOS_PAGES) )
|
||||
+ return;
|
||||
+
|
||||
for(i = 0; i < SHADOW_OOS_PAGES; i++)
|
||||
- {
|
||||
- shadow_prealloc(d, SH_type_oos_snapshot, 1);
|
||||
v->arch.paging.shadow.oos_snapshot[i] =
|
||||
shadow_alloc(d, SH_type_oos_snapshot, 0);
|
||||
- }
|
||||
}
|
||||
#endif /* OOS */
|
||||
|
||||
@@ -2403,6 +2426,9 @@ static void sh_update_paging_modes(struc
|
||||
mfn_t mmfn = sh_make_monitor_table(
|
||||
v, v->arch.paging.mode->shadow.shadow_levels);
|
||||
|
||||
+ if ( mfn_eq(mmfn, INVALID_MFN) )
|
||||
+ return;
|
||||
+
|
||||
v->arch.hvm.monitor_table = pagetable_from_mfn(mmfn);
|
||||
make_cr3(v, mmfn);
|
||||
hvm_update_host_cr3(v);
|
||||
@@ -2441,6 +2467,12 @@ static void sh_update_paging_modes(struc
|
||||
v->arch.hvm.monitor_table = pagetable_null();
|
||||
new_mfn = sh_make_monitor_table(
|
||||
v, v->arch.paging.mode->shadow.shadow_levels);
|
||||
+ if ( mfn_eq(new_mfn, INVALID_MFN) )
|
||||
+ {
|
||||
+ sh_destroy_monitor_table(v, old_mfn,
|
||||
+ old_mode->shadow.shadow_levels);
|
||||
+ return;
|
||||
+ }
|
||||
v->arch.hvm.monitor_table = pagetable_from_mfn(new_mfn);
|
||||
SHADOW_PRINTK("new monitor table %"PRI_mfn "\n",
|
||||
mfn_x(new_mfn));
|
||||
@@ -2526,7 +2558,12 @@ void sh_set_toplevel_shadow(struct vcpu
|
||||
if ( !mfn_valid(smfn) )
|
||||
{
|
||||
/* Make sure there's enough free shadow memory. */
|
||||
- shadow_prealloc(d, root_type, 1);
|
||||
+ if ( !shadow_prealloc(d, root_type, 1) )
|
||||
+ {
|
||||
+ new_entry = pagetable_null();
|
||||
+ goto install_new_entry;
|
||||
+ }
|
||||
+
|
||||
/* Shadow the page. */
|
||||
smfn = make_shadow(v, gmfn, root_type);
|
||||
}
|
||||
--- a/xen/arch/x86/mm/shadow/hvm.c
|
||||
+++ b/xen/arch/x86/mm/shadow/hvm.c
|
||||
@@ -700,7 +700,9 @@ mfn_t sh_make_monitor_table(const struct
|
||||
ASSERT(!pagetable_get_pfn(v->arch.hvm.monitor_table));
|
||||
|
||||
/* Guarantee we can get the memory we need */
|
||||
- shadow_prealloc(d, SH_type_monitor_table, CONFIG_PAGING_LEVELS);
|
||||
+ if ( !shadow_prealloc(d, SH_type_monitor_table, CONFIG_PAGING_LEVELS) )
|
||||
+ return INVALID_MFN;
|
||||
+
|
||||
m4mfn = shadow_alloc(d, SH_type_monitor_table, 0);
|
||||
mfn_to_page(m4mfn)->shadow_flags = 4;
|
||||
|
||||
--- a/xen/arch/x86/mm/shadow/multi.c
|
||||
+++ b/xen/arch/x86/mm/shadow/multi.c
|
||||
@@ -2440,9 +2440,14 @@ static int sh_page_fault(struct vcpu *v,
|
||||
* Preallocate shadow pages *before* removing writable accesses
|
||||
* otherwhise an OOS L1 might be demoted and promoted again with
|
||||
* writable mappings. */
|
||||
- shadow_prealloc(d,
|
||||
- SH_type_l1_shadow,
|
||||
- GUEST_PAGING_LEVELS < 4 ? 1 : GUEST_PAGING_LEVELS - 1);
|
||||
+ if ( !shadow_prealloc(d, SH_type_l1_shadow,
|
||||
+ GUEST_PAGING_LEVELS < 4
|
||||
+ ? 1 : GUEST_PAGING_LEVELS - 1) )
|
||||
+ {
|
||||
+ paging_unlock(d);
|
||||
+ put_gfn(d, gfn_x(gfn));
|
||||
+ return 0;
|
||||
+ }
|
||||
|
||||
rc = gw_remove_write_accesses(v, va, &gw);
|
||||
|
||||
--- a/xen/arch/x86/mm/shadow/private.h
|
||||
+++ b/xen/arch/x86/mm/shadow/private.h
|
||||
@@ -383,7 +383,8 @@ void shadow_promote(struct domain *d, mf
|
||||
void shadow_demote(struct domain *d, mfn_t gmfn, u32 type);
|
||||
|
||||
/* Shadow page allocation functions */
|
||||
-void shadow_prealloc(struct domain *d, u32 shadow_type, unsigned int count);
|
||||
+bool __must_check shadow_prealloc(struct domain *d, unsigned int shadow_type,
|
||||
+ unsigned int count);
|
||||
mfn_t shadow_alloc(struct domain *d,
|
||||
u32 shadow_type,
|
||||
unsigned long backpointer);
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
From: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Subject: x86/p2m: refuse new allocations for dying domains
|
||||
|
||||
This will in particular prevent any attempts to add entries to the p2m,
|
||||
once - in a subsequent change - non-root entries have been removed.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Tim Deegan <tim@xen.org>
|
||||
|
||||
--- a/xen/arch/x86/mm/hap/hap.c
|
||||
+++ b/xen/arch/x86/mm/hap/hap.c
|
||||
@@ -245,6 +245,9 @@ static struct page_info *hap_alloc(struc
|
||||
|
||||
ASSERT(paging_locked_by_me(d));
|
||||
|
||||
+ if ( unlikely(d->is_dying) )
|
||||
+ return NULL;
|
||||
+
|
||||
pg = page_list_remove_head(&d->arch.paging.hap.freelist);
|
||||
if ( unlikely(!pg) )
|
||||
return NULL;
|
||||
@@ -281,7 +284,7 @@ static struct page_info *hap_alloc_p2m_p
|
||||
d->arch.paging.hap.p2m_pages++;
|
||||
ASSERT(!page_get_owner(pg) && !(pg->count_info & PGC_count_mask));
|
||||
}
|
||||
- else if ( !d->arch.paging.p2m_alloc_failed )
|
||||
+ else if ( !d->arch.paging.p2m_alloc_failed && !d->is_dying )
|
||||
{
|
||||
d->arch.paging.p2m_alloc_failed = 1;
|
||||
dprintk(XENLOG_ERR, "d%i failed to allocate from HAP pool\n",
|
||||
--- a/xen/arch/x86/mm/shadow/common.c
|
||||
+++ b/xen/arch/x86/mm/shadow/common.c
|
||||
@@ -939,6 +939,10 @@ static bool __must_check _shadow_preallo
|
||||
if ( d->arch.paging.shadow.free_pages >= pages )
|
||||
return true;
|
||||
|
||||
+ if ( unlikely(d->is_dying) )
|
||||
+ /* No reclaim when the domain is dying, teardown will take care of it. */
|
||||
+ return false;
|
||||
+
|
||||
/* Shouldn't have enabled shadows if we've no vcpus. */
|
||||
ASSERT(d->vcpu && d->vcpu[0]);
|
||||
|
||||
@@ -991,7 +995,7 @@ static bool __must_check _shadow_preallo
|
||||
d->arch.paging.shadow.free_pages,
|
||||
d->arch.paging.shadow.p2m_pages);
|
||||
|
||||
- ASSERT(d->is_dying);
|
||||
+ ASSERT_UNREACHABLE();
|
||||
|
||||
guest_flush_tlb_mask(d, d->dirty_cpumask);
|
||||
|
||||
@@ -1005,10 +1009,13 @@ static bool __must_check _shadow_preallo
|
||||
* to avoid freeing shadows that the caller is currently working on. */
|
||||
bool shadow_prealloc(struct domain *d, unsigned int type, unsigned int count)
|
||||
{
|
||||
- bool ret = _shadow_prealloc(d, shadow_size(type) * count);
|
||||
+ bool ret;
|
||||
+
|
||||
+ if ( unlikely(d->is_dying) )
|
||||
+ return false;
|
||||
|
||||
- if ( !ret && !d->is_dying &&
|
||||
- (!d->is_shutting_down || d->shutdown_code != SHUTDOWN_crash) )
|
||||
+ ret = _shadow_prealloc(d, shadow_size(type) * count);
|
||||
+ if ( !ret && (!d->is_shutting_down || d->shutdown_code != SHUTDOWN_crash) )
|
||||
/*
|
||||
* Failing to allocate memory required for shadow usage can only result in
|
||||
* a domain crash, do it here rather that relying on every caller to do it.
|
||||
@@ -1238,6 +1245,9 @@ shadow_alloc_p2m_page(struct domain *d)
|
||||
{
|
||||
struct page_info *pg = NULL;
|
||||
|
||||
+ if ( unlikely(d->is_dying) )
|
||||
+ return NULL;
|
||||
+
|
||||
/* This is called both from the p2m code (which never holds the
|
||||
* paging lock) and the log-dirty code (which always does). */
|
||||
paging_lock_recursive(d);
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
From: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Subject: x86/p2m: truly free paging pool memory for dying domains
|
||||
|
||||
Modify {hap,shadow}_free to free the page immediately if the domain is
|
||||
dying, so that pages don't accumulate in the pool when
|
||||
{shadow,hap}_final_teardown() get called. This is to limit the amount of
|
||||
work which needs to be done there (in a non-preemptable manner).
|
||||
|
||||
Note the call to shadow_free() in shadow_free_p2m_page() is moved after
|
||||
increasing total_pages, so that the decrease done in shadow_free() in
|
||||
case the domain is dying doesn't underflow the counter, even if just for
|
||||
a short interval.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Tim Deegan <tim@xen.org>
|
||||
|
||||
--- a/xen/arch/x86/mm/hap/hap.c
|
||||
+++ b/xen/arch/x86/mm/hap/hap.c
|
||||
@@ -265,6 +265,18 @@ static void hap_free(struct domain *d, m
|
||||
|
||||
ASSERT(paging_locked_by_me(d));
|
||||
|
||||
+ /*
|
||||
+ * For dying domains, actually free the memory here. This way less work is
|
||||
+ * left to hap_final_teardown(), which cannot easily have preemption checks
|
||||
+ * added.
|
||||
+ */
|
||||
+ if ( unlikely(d->is_dying) )
|
||||
+ {
|
||||
+ free_domheap_page(pg);
|
||||
+ d->arch.paging.hap.total_pages--;
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
d->arch.paging.hap.free_pages++;
|
||||
page_list_add_tail(pg, &d->arch.paging.hap.freelist);
|
||||
}
|
||||
--- a/xen/arch/x86/mm/shadow/common.c
|
||||
+++ b/xen/arch/x86/mm/shadow/common.c
|
||||
@@ -1187,6 +1187,7 @@ mfn_t shadow_alloc(struct domain *d,
|
||||
void shadow_free(struct domain *d, mfn_t smfn)
|
||||
{
|
||||
struct page_info *next = NULL, *sp = mfn_to_page(smfn);
|
||||
+ bool dying = ACCESS_ONCE(d->is_dying);
|
||||
struct page_list_head *pin_list;
|
||||
unsigned int pages;
|
||||
u32 shadow_type;
|
||||
@@ -1229,11 +1230,32 @@ void shadow_free(struct domain *d, mfn_t
|
||||
* just before the allocator hands the page out again. */
|
||||
page_set_tlbflush_timestamp(sp);
|
||||
perfc_decr(shadow_alloc_count);
|
||||
- page_list_add_tail(sp, &d->arch.paging.shadow.freelist);
|
||||
+
|
||||
+ /*
|
||||
+ * For dying domains, actually free the memory here. This way less
|
||||
+ * work is left to shadow_final_teardown(), which cannot easily have
|
||||
+ * preemption checks added.
|
||||
+ */
|
||||
+ if ( unlikely(dying) )
|
||||
+ {
|
||||
+ /*
|
||||
+ * The backpointer field (sh.back) used by shadow code aliases the
|
||||
+ * domain owner field, unconditionally clear it here to avoid
|
||||
+ * free_domheap_page() attempting to parse it.
|
||||
+ */
|
||||
+ page_set_owner(sp, NULL);
|
||||
+ free_domheap_page(sp);
|
||||
+ }
|
||||
+ else
|
||||
+ page_list_add_tail(sp, &d->arch.paging.shadow.freelist);
|
||||
+
|
||||
sp = next;
|
||||
}
|
||||
|
||||
- d->arch.paging.shadow.free_pages += pages;
|
||||
+ if ( unlikely(dying) )
|
||||
+ d->arch.paging.shadow.total_pages -= pages;
|
||||
+ else
|
||||
+ d->arch.paging.shadow.free_pages += pages;
|
||||
}
|
||||
|
||||
/* Divert a page from the pool to be used by the p2m mapping.
|
||||
@@ -1303,9 +1325,9 @@ shadow_free_p2m_page(struct domain *d, s
|
||||
* paging lock) and the log-dirty code (which always does). */
|
||||
paging_lock_recursive(d);
|
||||
|
||||
- shadow_free(d, page_to_mfn(pg));
|
||||
d->arch.paging.shadow.p2m_pages--;
|
||||
d->arch.paging.shadow.total_pages++;
|
||||
+ shadow_free(d, page_to_mfn(pg));
|
||||
|
||||
paging_unlock(d);
|
||||
}
|
||||
|
|
@ -1,159 +0,0 @@
|
|||
From: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Subject: x86/p2m: free the paging memory pool preemptively
|
||||
|
||||
The paging memory pool is currently freed in two different places:
|
||||
from {shadow,hap}_teardown() via domain_relinquish_resources() and
|
||||
from {shadow,hap}_final_teardown() via complete_domain_destroy().
|
||||
While the former does handle preemption, the later doesn't.
|
||||
|
||||
Attempt to move as much p2m related freeing as possible to happen
|
||||
before the call to {shadow,hap}_teardown(), so that most memory can be
|
||||
freed in a preemptive way. In order to avoid causing issues to
|
||||
existing callers leave the root p2m page tables set and free them in
|
||||
{hap,shadow}_final_teardown(). Also modify {hap,shadow}_free to free
|
||||
the page immediately if the domain is dying, so that pages don't
|
||||
accumulate in the pool when {shadow,hap}_final_teardown() get called.
|
||||
|
||||
Move altp2m_vcpu_disable_ve() to be done in hap_teardown(), as that's
|
||||
the place where altp2m_active gets disabled now.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Acked-by: Tim Deegan <tim@xen.org>
|
||||
|
||||
--- a/xen/arch/x86/domain.c
|
||||
+++ b/xen/arch/x86/domain.c
|
||||
@@ -38,7 +38,6 @@
|
||||
#include <xen/livepatch.h>
|
||||
#include <public/sysctl.h>
|
||||
#include <public/hvm/hvm_vcpu.h>
|
||||
-#include <asm/altp2m.h>
|
||||
#include <asm/regs.h>
|
||||
#include <asm/mc146818rtc.h>
|
||||
#include <asm/system.h>
|
||||
@@ -2381,12 +2380,6 @@ int domain_relinquish_resources(struct d
|
||||
vpmu_destroy(v);
|
||||
}
|
||||
|
||||
- if ( altp2m_active(d) )
|
||||
- {
|
||||
- for_each_vcpu ( d, v )
|
||||
- altp2m_vcpu_disable_ve(v);
|
||||
- }
|
||||
-
|
||||
if ( is_pv_domain(d) )
|
||||
{
|
||||
for_each_vcpu ( d, v )
|
||||
--- a/xen/arch/x86/mm/hap/hap.c
|
||||
+++ b/xen/arch/x86/mm/hap/hap.c
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <xen/domain_page.h>
|
||||
#include <xen/guest_access.h>
|
||||
#include <xen/keyhandler.h>
|
||||
+#include <asm/altp2m.h>
|
||||
#include <asm/event.h>
|
||||
#include <asm/page.h>
|
||||
#include <asm/current.h>
|
||||
@@ -546,24 +547,8 @@ void hap_final_teardown(struct domain *d
|
||||
unsigned int i;
|
||||
|
||||
if ( hvm_altp2m_supported() )
|
||||
- {
|
||||
- d->arch.altp2m_active = 0;
|
||||
-
|
||||
- if ( d->arch.altp2m_eptp )
|
||||
- {
|
||||
- free_xenheap_page(d->arch.altp2m_eptp);
|
||||
- d->arch.altp2m_eptp = NULL;
|
||||
- }
|
||||
-
|
||||
- if ( d->arch.altp2m_visible_eptp )
|
||||
- {
|
||||
- free_xenheap_page(d->arch.altp2m_visible_eptp);
|
||||
- d->arch.altp2m_visible_eptp = NULL;
|
||||
- }
|
||||
-
|
||||
for ( i = 0; i < MAX_ALTP2M; i++ )
|
||||
p2m_teardown(d->arch.altp2m_p2m[i], true);
|
||||
- }
|
||||
|
||||
/* Destroy nestedp2m's first */
|
||||
for (i = 0; i < MAX_NESTEDP2M; i++) {
|
||||
@@ -578,6 +563,8 @@ void hap_final_teardown(struct domain *d
|
||||
paging_lock(d);
|
||||
hap_set_allocation(d, 0, NULL);
|
||||
ASSERT(d->arch.paging.hap.p2m_pages == 0);
|
||||
+ ASSERT(d->arch.paging.hap.free_pages == 0);
|
||||
+ ASSERT(d->arch.paging.hap.total_pages == 0);
|
||||
paging_unlock(d);
|
||||
}
|
||||
|
||||
@@ -603,6 +590,7 @@ void hap_vcpu_teardown(struct vcpu *v)
|
||||
void hap_teardown(struct domain *d, bool *preempted)
|
||||
{
|
||||
struct vcpu *v;
|
||||
+ unsigned int i;
|
||||
|
||||
ASSERT(d->is_dying);
|
||||
ASSERT(d != current->domain);
|
||||
@@ -611,6 +599,28 @@ void hap_teardown(struct domain *d, bool
|
||||
for_each_vcpu ( d, v )
|
||||
hap_vcpu_teardown(v);
|
||||
|
||||
+ /* Leave the root pt in case we get further attempts to modify the p2m. */
|
||||
+ if ( hvm_altp2m_supported() )
|
||||
+ {
|
||||
+ if ( altp2m_active(d) )
|
||||
+ for_each_vcpu ( d, v )
|
||||
+ altp2m_vcpu_disable_ve(v);
|
||||
+
|
||||
+ d->arch.altp2m_active = 0;
|
||||
+
|
||||
+ FREE_XENHEAP_PAGE(d->arch.altp2m_eptp);
|
||||
+ FREE_XENHEAP_PAGE(d->arch.altp2m_visible_eptp);
|
||||
+
|
||||
+ for ( i = 0; i < MAX_ALTP2M; i++ )
|
||||
+ p2m_teardown(d->arch.altp2m_p2m[i], false);
|
||||
+ }
|
||||
+
|
||||
+ /* Destroy nestedp2m's after altp2m. */
|
||||
+ for ( i = 0; i < MAX_NESTEDP2M; i++ )
|
||||
+ p2m_teardown(d->arch.nested_p2m[i], false);
|
||||
+
|
||||
+ p2m_teardown(p2m_get_hostp2m(d), false);
|
||||
+
|
||||
paging_lock(d); /* Keep various asserts happy */
|
||||
|
||||
if ( d->arch.paging.hap.total_pages != 0 )
|
||||
--- a/xen/arch/x86/mm/shadow/common.c
|
||||
+++ b/xen/arch/x86/mm/shadow/common.c
|
||||
@@ -2824,8 +2824,17 @@ void shadow_teardown(struct domain *d, b
|
||||
for_each_vcpu ( d, v )
|
||||
shadow_vcpu_teardown(v);
|
||||
|
||||
+ p2m_teardown(p2m_get_hostp2m(d), false);
|
||||
+
|
||||
paging_lock(d);
|
||||
|
||||
+ /*
|
||||
+ * Reclaim all shadow memory so that shadow_set_allocation() doesn't find
|
||||
+ * in-use pages, as _shadow_prealloc() will no longer try to reclaim pages
|
||||
+ * because the domain is dying.
|
||||
+ */
|
||||
+ shadow_blow_tables(d);
|
||||
+
|
||||
#if (SHADOW_OPTIMIZATIONS & (SHOPT_VIRTUAL_TLB|SHOPT_OUT_OF_SYNC))
|
||||
/* Free the virtual-TLB array attached to each vcpu */
|
||||
for_each_vcpu(d, v)
|
||||
@@ -2946,6 +2955,9 @@ void shadow_final_teardown(struct domain
|
||||
d->arch.paging.shadow.total_pages,
|
||||
d->arch.paging.shadow.free_pages,
|
||||
d->arch.paging.shadow.p2m_pages);
|
||||
+ ASSERT(!d->arch.paging.shadow.total_pages);
|
||||
+ ASSERT(!d->arch.paging.shadow.free_pages);
|
||||
+ ASSERT(!d->arch.paging.shadow.p2m_pages);
|
||||
paging_unlock(d);
|
||||
}
|
||||
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
From: Julien Grall <jgrall@amazon.com>
|
||||
Subject: xen/x86: p2m: Add preemption in p2m_teardown()
|
||||
|
||||
The list p2m->pages contain all the pages used by the P2M. On large
|
||||
instance this can be quite large and the time spent to call
|
||||
d->arch.paging.free_page() will take more than 1ms for a 80GB guest
|
||||
on a Xen running in nested environment on a c5.metal.
|
||||
|
||||
By extrapolation, it would take > 100ms for a 8TB guest (what we
|
||||
current security support). So add some preemption in p2m_teardown()
|
||||
and propagate to the callers. Note there are 3 places where
|
||||
the preemption is not enabled:
|
||||
- hap_final_teardown()/shadow_final_teardown(): We are
|
||||
preventing update the P2M once the domain is dying (so
|
||||
no more pages could be allocated) and most of the P2M pages
|
||||
will be freed in preemptive manneer when relinquishing the
|
||||
resources. So this is fine to disable preemption.
|
||||
- shadow_enable(): This is fine because it will undo the allocation
|
||||
that may have been made by p2m_alloc_table() (so only the root
|
||||
page table).
|
||||
|
||||
The preemption is arbitrarily checked every 1024 iterations.
|
||||
|
||||
Note that with the current approach, Xen doesn't keep track on whether
|
||||
the alt/nested P2Ms have been cleared. So there are some redundant work.
|
||||
However, this is not expected to incurr too much overhead (the P2M lock
|
||||
shouldn't be contended during teardown). So this is optimization is
|
||||
left outside of the security event.
|
||||
|
||||
This is part of CVE-2022-33746 / XSA-410.
|
||||
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
--- a/xen/include/asm-x86/p2m.h
|
||||
+++ b/xen/include/asm-x86/p2m.h
|
||||
@@ -574,7 +574,7 @@ int p2m_init(struct domain *d);
|
||||
int p2m_alloc_table(struct p2m_domain *p2m);
|
||||
|
||||
/* Return all the p2m resources to Xen. */
|
||||
-void p2m_teardown(struct p2m_domain *p2m, bool remove_root);
|
||||
+void p2m_teardown(struct p2m_domain *p2m, bool remove_root, bool *preempted);
|
||||
void p2m_final_teardown(struct domain *d);
|
||||
|
||||
/* Add a page to a domain's p2m table */
|
||||
--- a/xen/arch/x86/mm/hap/hap.c
|
||||
+++ b/xen/arch/x86/mm/hap/hap.c
|
||||
@@ -548,17 +548,17 @@ void hap_final_teardown(struct domain *d
|
||||
|
||||
if ( hvm_altp2m_supported() )
|
||||
for ( i = 0; i < MAX_ALTP2M; i++ )
|
||||
- p2m_teardown(d->arch.altp2m_p2m[i], true);
|
||||
+ p2m_teardown(d->arch.altp2m_p2m[i], true, NULL);
|
||||
|
||||
/* Destroy nestedp2m's first */
|
||||
for (i = 0; i < MAX_NESTEDP2M; i++) {
|
||||
- p2m_teardown(d->arch.nested_p2m[i], true);
|
||||
+ p2m_teardown(d->arch.nested_p2m[i], true, NULL);
|
||||
}
|
||||
|
||||
if ( d->arch.paging.hap.total_pages != 0 )
|
||||
hap_teardown(d, NULL);
|
||||
|
||||
- p2m_teardown(p2m_get_hostp2m(d), true);
|
||||
+ p2m_teardown(p2m_get_hostp2m(d), true, NULL);
|
||||
/* Free any memory that the p2m teardown released */
|
||||
paging_lock(d);
|
||||
hap_set_allocation(d, 0, NULL);
|
||||
@@ -612,14 +612,24 @@ void hap_teardown(struct domain *d, bool
|
||||
FREE_XENHEAP_PAGE(d->arch.altp2m_visible_eptp);
|
||||
|
||||
for ( i = 0; i < MAX_ALTP2M; i++ )
|
||||
- p2m_teardown(d->arch.altp2m_p2m[i], false);
|
||||
+ {
|
||||
+ p2m_teardown(d->arch.altp2m_p2m[i], false, preempted);
|
||||
+ if ( preempted && *preempted )
|
||||
+ return;
|
||||
+ }
|
||||
}
|
||||
|
||||
/* Destroy nestedp2m's after altp2m. */
|
||||
for ( i = 0; i < MAX_NESTEDP2M; i++ )
|
||||
- p2m_teardown(d->arch.nested_p2m[i], false);
|
||||
+ {
|
||||
+ p2m_teardown(d->arch.nested_p2m[i], false, preempted);
|
||||
+ if ( preempted && *preempted )
|
||||
+ return;
|
||||
+ }
|
||||
|
||||
- p2m_teardown(p2m_get_hostp2m(d), false);
|
||||
+ p2m_teardown(p2m_get_hostp2m(d), false, preempted);
|
||||
+ if ( preempted && *preempted )
|
||||
+ return;
|
||||
|
||||
paging_lock(d); /* Keep various asserts happy */
|
||||
|
||||
--- a/xen/arch/x86/mm/p2m.c
|
||||
+++ b/xen/arch/x86/mm/p2m.c
|
||||
@@ -749,12 +749,13 @@ int p2m_alloc_table(struct p2m_domain *p
|
||||
* hvm fixme: when adding support for pvh non-hardware domains, this path must
|
||||
* cleanup any foreign p2m types (release refcnts on them).
|
||||
*/
|
||||
-void p2m_teardown(struct p2m_domain *p2m, bool remove_root)
|
||||
+void p2m_teardown(struct p2m_domain *p2m, bool remove_root, bool *preempted)
|
||||
/* Return all the p2m pages to Xen.
|
||||
* We know we don't have any extra mappings to these pages */
|
||||
{
|
||||
struct page_info *pg, *root_pg = NULL;
|
||||
struct domain *d;
|
||||
+ unsigned int i = 0;
|
||||
|
||||
if (p2m == NULL)
|
||||
return;
|
||||
@@ -773,8 +774,19 @@ void p2m_teardown(struct p2m_domain *p2m
|
||||
}
|
||||
|
||||
while ( (pg = page_list_remove_head(&p2m->pages)) )
|
||||
- if ( pg != root_pg )
|
||||
- d->arch.paging.free_page(d, pg);
|
||||
+ {
|
||||
+ if ( pg == root_pg )
|
||||
+ continue;
|
||||
+
|
||||
+ d->arch.paging.free_page(d, pg);
|
||||
+
|
||||
+ /* Arbitrarily check preemption every 1024 iterations */
|
||||
+ if ( preempted && !(++i % 1024) && general_preempt_check() )
|
||||
+ {
|
||||
+ *preempted = true;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
|
||||
if ( root_pg )
|
||||
page_list_add(root_pg, &p2m->pages);
|
||||
--- a/xen/arch/x86/mm/shadow/common.c
|
||||
+++ b/xen/arch/x86/mm/shadow/common.c
|
||||
@@ -2770,8 +2770,12 @@ int shadow_enable(struct domain *d, u32
|
||||
out_locked:
|
||||
paging_unlock(d);
|
||||
out_unlocked:
|
||||
+ /*
|
||||
+ * This is fine to ignore the preemption here because only the root
|
||||
+ * will be allocated by p2m_alloc_table().
|
||||
+ */
|
||||
if ( rv != 0 && !pagetable_is_null(p2m_get_pagetable(p2m)) )
|
||||
- p2m_teardown(p2m, true);
|
||||
+ p2m_teardown(p2m, true, NULL);
|
||||
if ( rv != 0 && pg != NULL )
|
||||
{
|
||||
pg->count_info &= ~PGC_count_mask;
|
||||
@@ -2824,7 +2828,9 @@ void shadow_teardown(struct domain *d, b
|
||||
for_each_vcpu ( d, v )
|
||||
shadow_vcpu_teardown(v);
|
||||
|
||||
- p2m_teardown(p2m_get_hostp2m(d), false);
|
||||
+ p2m_teardown(p2m_get_hostp2m(d), false, preempted);
|
||||
+ if ( preempted && *preempted )
|
||||
+ return;
|
||||
|
||||
paging_lock(d);
|
||||
|
||||
@@ -2945,7 +2951,7 @@ void shadow_final_teardown(struct domain
|
||||
shadow_teardown(d, NULL);
|
||||
|
||||
/* It is now safe to pull down the p2m map. */
|
||||
- p2m_teardown(p2m_get_hostp2m(d), true);
|
||||
+ p2m_teardown(p2m_get_hostp2m(d), true, NULL);
|
||||
/* Free any shadow memory that the p2m teardown released */
|
||||
paging_lock(d);
|
||||
shadow_set_allocation(d, 0, NULL);
|
||||
55
xsa411.patch
55
xsa411.patch
|
|
@ -1,55 +0,0 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: gnttab: correct locking on transitive grant copy error path
|
||||
|
||||
While the comment next to the lock dropping in preparation of
|
||||
recursively calling acquire_grant_for_copy() mistakenly talks about the
|
||||
rd == td case (excluded a few lines further up), the same concerns apply
|
||||
to the calling of release_grant_for_copy() on a subsequent error path.
|
||||
|
||||
This is CVE-2022-33748 / XSA-411.
|
||||
|
||||
Fixes: ad48fb963dbf ("gnttab: fix transitive grant handling")
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
v2: Extend code comment.
|
||||
|
||||
--- a/xen/common/grant_table.c
|
||||
+++ b/xen/common/grant_table.c
|
||||
@@ -2622,9 +2622,8 @@ acquire_grant_for_copy(
|
||||
trans_domid);
|
||||
|
||||
/*
|
||||
- * acquire_grant_for_copy() could take the lock on the
|
||||
- * remote table (if rd == td), so we have to drop the lock
|
||||
- * here and reacquire.
|
||||
+ * acquire_grant_for_copy() will take the lock on the remote table,
|
||||
+ * so we have to drop the lock here and reacquire.
|
||||
*/
|
||||
active_entry_release(act);
|
||||
grant_read_unlock(rgt);
|
||||
@@ -2661,11 +2660,25 @@ acquire_grant_for_copy(
|
||||
act->trans_gref != trans_gref ||
|
||||
!act->is_sub_page)) )
|
||||
{
|
||||
+ /*
|
||||
+ * Like above for acquire_grant_for_copy() we need to drop and then
|
||||
+ * re-acquire the locks here to prevent lock order inversion issues.
|
||||
+ * Unlike for acquire_grant_for_copy() we don't need to re-check
|
||||
+ * anything, as release_grant_for_copy() doesn't depend on the grant
|
||||
+ * table entry: It only updates internal state and the status flags.
|
||||
+ */
|
||||
+ active_entry_release(act);
|
||||
+ grant_read_unlock(rgt);
|
||||
+
|
||||
release_grant_for_copy(td, trans_gref, readonly);
|
||||
rcu_unlock_domain(td);
|
||||
+
|
||||
+ grant_read_lock(rgt);
|
||||
+ act = active_entry_acquire(rgt, gref);
|
||||
reduce_status_for_pin(rd, act, status, readonly);
|
||||
active_entry_release(act);
|
||||
grant_read_unlock(rgt);
|
||||
+
|
||||
put_page(*page);
|
||||
*page = NULL;
|
||||
return ERESTART;
|
||||
|
|
@ -1,245 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/vmx: Revert "VMX: use a single, global APIC access page"
|
||||
|
||||
The claim "No accesses would ever go to this page." is false. A consequence
|
||||
of how Intel's APIC Acceleration works, and Xen's choice to have per-domain
|
||||
P2Ms (rather than per-vCPU P2Ms) means that the APIC page is fully read-write
|
||||
to any vCPU which is not in xAPIC mode.
|
||||
|
||||
This reverts commit 58850b9074d3e7affdf3bc94c84e417ecfa4d165.
|
||||
|
||||
This is XSA-412 / CVE-2022-42327.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/xen/arch/x86/hvm/vmx/vmx.c b/xen/arch/x86/hvm/vmx/vmx.c
|
||||
index d429d76c18c9..3f4276531322 100644
|
||||
--- a/xen/arch/x86/hvm/vmx/vmx.c
|
||||
+++ b/xen/arch/x86/hvm/vmx/vmx.c
|
||||
@@ -66,7 +66,8 @@ boolean_param("force-ept", opt_force_ept);
|
||||
static void vmx_ctxt_switch_from(struct vcpu *v);
|
||||
static void vmx_ctxt_switch_to(struct vcpu *v);
|
||||
|
||||
-static int alloc_vlapic_mapping(void);
|
||||
+static int vmx_alloc_vlapic_mapping(struct domain *d);
|
||||
+static void vmx_free_vlapic_mapping(struct domain *d);
|
||||
static void vmx_install_vlapic_mapping(struct vcpu *v);
|
||||
static void vmx_update_guest_cr(struct vcpu *v, unsigned int cr,
|
||||
unsigned int flags);
|
||||
@@ -77,8 +78,6 @@ static int vmx_msr_read_intercept(unsigned int msr, uint64_t *msr_content);
|
||||
static int vmx_msr_write_intercept(unsigned int msr, uint64_t msr_content);
|
||||
static void vmx_invlpg(struct vcpu *v, unsigned long linear);
|
||||
|
||||
-static mfn_t __read_mostly apic_access_mfn = INVALID_MFN_INITIALIZER;
|
||||
-
|
||||
/* Values for domain's ->arch.hvm_domain.pi_ops.flags. */
|
||||
#define PI_CSW_FROM (1u << 0)
|
||||
#define PI_CSW_TO (1u << 1)
|
||||
@@ -402,6 +401,7 @@ static int vmx_domain_initialise(struct domain *d)
|
||||
.to = vmx_ctxt_switch_to,
|
||||
.tail = vmx_do_resume,
|
||||
};
|
||||
+ int rc;
|
||||
|
||||
d->arch.ctxt_switch = &csw;
|
||||
|
||||
@@ -411,15 +411,24 @@ static int vmx_domain_initialise(struct domain *d)
|
||||
*/
|
||||
d->arch.hvm.vmx.exec_sp = is_hardware_domain(d) || opt_ept_exec_sp;
|
||||
|
||||
+ if ( (rc = vmx_alloc_vlapic_mapping(d)) != 0 )
|
||||
+ return rc;
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static void vmx_domain_relinquish_resources(struct domain *d)
|
||||
+{
|
||||
+ vmx_free_vlapic_mapping(d);
|
||||
+}
|
||||
+
|
||||
static void domain_creation_finished(struct domain *d)
|
||||
{
|
||||
gfn_t gfn = gaddr_to_gfn(APIC_DEFAULT_PHYS_BASE);
|
||||
+ mfn_t apic_access_mfn = d->arch.hvm.vmx.apic_access_mfn;
|
||||
bool ipat;
|
||||
|
||||
- if ( !has_vlapic(d) || mfn_eq(apic_access_mfn, INVALID_MFN) )
|
||||
+ if ( mfn_eq(apic_access_mfn, _mfn(0)) )
|
||||
return;
|
||||
|
||||
ASSERT(epte_get_entry_emt(d, gfn, apic_access_mfn, 0, &ipat,
|
||||
@@ -2481,6 +2490,7 @@ static struct hvm_function_table __initdata vmx_function_table = {
|
||||
.cpu_up_prepare = vmx_cpu_up_prepare,
|
||||
.cpu_dead = vmx_cpu_dead,
|
||||
.domain_initialise = vmx_domain_initialise,
|
||||
+ .domain_relinquish_resources = vmx_domain_relinquish_resources,
|
||||
.domain_creation_finished = domain_creation_finished,
|
||||
.vcpu_initialise = vmx_vcpu_initialise,
|
||||
.vcpu_destroy = vmx_vcpu_destroy,
|
||||
@@ -2731,7 +2741,7 @@ const struct hvm_function_table * __init start_vmx(void)
|
||||
{
|
||||
set_in_cr4(X86_CR4_VMXE);
|
||||
|
||||
- if ( vmx_vmcs_init() || alloc_vlapic_mapping() )
|
||||
+ if ( vmx_vmcs_init() )
|
||||
{
|
||||
printk("VMX: failed to initialise.\n");
|
||||
return NULL;
|
||||
@@ -3305,36 +3315,55 @@ static int vmx_msr_read_intercept(unsigned int msr, uint64_t *msr_content)
|
||||
return X86EMUL_EXCEPTION;
|
||||
}
|
||||
|
||||
-static int __init alloc_vlapic_mapping(void)
|
||||
+static int vmx_alloc_vlapic_mapping(struct domain *d)
|
||||
{
|
||||
struct page_info *pg;
|
||||
mfn_t mfn;
|
||||
|
||||
- if ( !cpu_has_vmx_virtualize_apic_accesses )
|
||||
+ if ( !has_vlapic(d) || !cpu_has_vmx_virtualize_apic_accesses )
|
||||
return 0;
|
||||
|
||||
- pg = alloc_domheap_page(NULL, 0);
|
||||
+ pg = alloc_domheap_page(d, MEMF_no_refcount);
|
||||
if ( !pg )
|
||||
return -ENOMEM;
|
||||
|
||||
- /*
|
||||
- * Signal to shadow code that this page cannot be refcounted. This also
|
||||
- * makes epte_get_entry_emt() recognize this page as "special".
|
||||
- */
|
||||
- page_suppress_refcounting(pg);
|
||||
+ if ( !get_page_and_type(pg, d, PGT_writable_page) )
|
||||
+ {
|
||||
+ /*
|
||||
+ * The domain can't possibly know about this page yet, so failure
|
||||
+ * here is a clear indication of something fishy going on.
|
||||
+ */
|
||||
+ domain_crash(d);
|
||||
+ return -ENODATA;
|
||||
+ }
|
||||
|
||||
mfn = page_to_mfn(pg);
|
||||
clear_domain_page(mfn);
|
||||
- apic_access_mfn = mfn;
|
||||
+ d->arch.hvm.vmx.apic_access_mfn = mfn;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static void vmx_free_vlapic_mapping(struct domain *d)
|
||||
+{
|
||||
+ mfn_t mfn = d->arch.hvm.vmx.apic_access_mfn;
|
||||
+
|
||||
+ d->arch.hvm.vmx.apic_access_mfn = _mfn(0);
|
||||
+ if ( !mfn_eq(mfn, _mfn(0)) )
|
||||
+ {
|
||||
+ struct page_info *pg = mfn_to_page(mfn);
|
||||
+
|
||||
+ put_page_alloc_ref(pg);
|
||||
+ put_page_and_type(pg);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static void vmx_install_vlapic_mapping(struct vcpu *v)
|
||||
{
|
||||
+ mfn_t apic_access_mfn = v->domain->arch.hvm.vmx.apic_access_mfn;
|
||||
paddr_t virt_page_ma, apic_page_ma;
|
||||
|
||||
- if ( !has_vlapic(v->domain) || mfn_eq(apic_access_mfn, INVALID_MFN) )
|
||||
+ if ( mfn_eq(apic_access_mfn, _mfn(0)) )
|
||||
return;
|
||||
|
||||
ASSERT(cpu_has_vmx_virtualize_apic_accesses);
|
||||
diff --git a/xen/arch/x86/mm/shadow/set.c b/xen/arch/x86/mm/shadow/set.c
|
||||
index 87e9c6eeb219..bd6c68b547c9 100644
|
||||
--- a/xen/arch/x86/mm/shadow/set.c
|
||||
+++ b/xen/arch/x86/mm/shadow/set.c
|
||||
@@ -101,14 +101,6 @@ shadow_get_page_from_l1e(shadow_l1e_t sl1e, struct domain *d, p2m_type_t type)
|
||||
owner = page_get_owner(pg);
|
||||
}
|
||||
|
||||
- /*
|
||||
- * Check whether refcounting is suppressed on this page. For example,
|
||||
- * VMX'es APIC access MFN is just a surrogate page. It doesn't actually
|
||||
- * get accessed, and hence there's no need to refcount it.
|
||||
- */
|
||||
- if ( pg && page_refcounting_suppressed(pg) )
|
||||
- return 0;
|
||||
-
|
||||
if ( owner == dom_io )
|
||||
owner = NULL;
|
||||
|
||||
diff --git a/xen/arch/x86/mm/shadow/types.h b/xen/arch/x86/mm/shadow/types.h
|
||||
index 6970e7d6ea4a..814a4018535a 100644
|
||||
--- a/xen/arch/x86/mm/shadow/types.h
|
||||
+++ b/xen/arch/x86/mm/shadow/types.h
|
||||
@@ -276,16 +276,9 @@ int shadow_set_l4e(struct domain *d, shadow_l4e_t *sl4e,
|
||||
static void inline
|
||||
shadow_put_page_from_l1e(shadow_l1e_t sl1e, struct domain *d)
|
||||
{
|
||||
- mfn_t mfn = shadow_l1e_get_mfn(sl1e);
|
||||
-
|
||||
if ( !shadow_mode_refcounts(d) )
|
||||
return;
|
||||
|
||||
- if ( mfn_valid(mfn) &&
|
||||
- /* See the respective comment in shadow_get_page_from_l1e(). */
|
||||
- page_refcounting_suppressed(mfn_to_page(mfn)) )
|
||||
- return;
|
||||
-
|
||||
put_page_from_l1e(sl1e, d);
|
||||
}
|
||||
|
||||
diff --git a/xen/include/asm-x86/hvm/vmx/vmcs.h b/xen/include/asm-x86/hvm/vmx/vmcs.h
|
||||
index 03c9ccf627ab..8073af323b96 100644
|
||||
--- a/xen/include/asm-x86/hvm/vmx/vmcs.h
|
||||
+++ b/xen/include/asm-x86/hvm/vmx/vmcs.h
|
||||
@@ -58,6 +58,7 @@ struct ept_data {
|
||||
#define _VMX_DOMAIN_PML_ENABLED 0
|
||||
#define VMX_DOMAIN_PML_ENABLED (1ul << _VMX_DOMAIN_PML_ENABLED)
|
||||
struct vmx_domain {
|
||||
+ mfn_t apic_access_mfn;
|
||||
/* VMX_DOMAIN_* */
|
||||
unsigned int status;
|
||||
|
||||
diff --git a/xen/include/asm-x86/mm.h b/xen/include/asm-x86/mm.h
|
||||
index 7bdf9c2290d8..e1bcea57a8f5 100644
|
||||
--- a/xen/include/asm-x86/mm.h
|
||||
+++ b/xen/include/asm-x86/mm.h
|
||||
@@ -83,7 +83,7 @@
|
||||
#define PGC_state_offlined PG_mask(2, 6)
|
||||
#define PGC_state_free PG_mask(3, 6)
|
||||
#define page_state_is(pg, st) (((pg)->count_info&PGC_state) == PGC_state_##st)
|
||||
-/* Page is not reference counted (see below for caveats) */
|
||||
+/* Page is not reference counted */
|
||||
#define _PGC_extra PG_shift(7)
|
||||
#define PGC_extra PG_mask(1, 7)
|
||||
|
||||
@@ -375,24 +375,6 @@ void zap_ro_mpt(mfn_t mfn);
|
||||
|
||||
bool is_iomem_page(mfn_t mfn);
|
||||
|
||||
-/*
|
||||
- * Pages with no owner which may get passed to functions wanting to
|
||||
- * refcount them can be marked PGC_extra to bypass this refcounting (which
|
||||
- * would fail due to the lack of an owner).
|
||||
- *
|
||||
- * (For pages with owner PGC_extra has different meaning.)
|
||||
- */
|
||||
-static inline void page_suppress_refcounting(struct page_info *pg)
|
||||
-{
|
||||
- ASSERT(!page_get_owner(pg));
|
||||
- pg->count_info |= PGC_extra;
|
||||
-}
|
||||
-
|
||||
-static inline bool page_refcounting_suppressed(const struct page_info *pg)
|
||||
-{
|
||||
- return !page_get_owner(pg) && (pg->count_info & PGC_extra);
|
||||
-}
|
||||
-
|
||||
struct platform_bad_page {
|
||||
unsigned long mfn;
|
||||
unsigned int order;
|
||||
112
xsa414.patch
112
xsa414.patch
|
|
@ -1,112 +0,0 @@
|
|||
From: Julien Grall <jgrall@amazon.com>
|
||||
Subject: tools/xenstore: create_node: Don't defer work to undo any changes on
|
||||
failure
|
||||
|
||||
XSA-115 extended destroy_node() to update the node accounting for the
|
||||
connection. The implementation is assuming the connection is the parent
|
||||
of the node, however all the nodes are allocated using a separate context
|
||||
(see process_message()). This will result to crash (or corrupt) xenstored
|
||||
as the pointer is wrongly used.
|
||||
|
||||
In case of an error, any changes to the database or update to the
|
||||
accounting will now be reverted in create_node() by calling directly
|
||||
destroy_node(). This has the nice advantage to remove the loop to unset
|
||||
the destructors in case of success.
|
||||
|
||||
Take the opportunity to free the nodes right now as they are not
|
||||
going to be reachable (the function returns NULL) and are just wasting
|
||||
resources.
|
||||
|
||||
This is XSA-414 / CVE-2022-42309.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Fixes: 0bfb2101f243 ("tools/xenstore: fix node accounting after failed node creation")
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Juergen Gross <jgross@suse.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 8867f93431d4..c30d14cbf2ab 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -1084,9 +1084,8 @@ static struct node *construct_node(struct connection *conn, const void *ctx,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
-static int destroy_node(void *_node)
|
||||
+static int destroy_node(struct connection *conn, struct node *node)
|
||||
{
|
||||
- struct node *node = _node;
|
||||
TDB_DATA key;
|
||||
|
||||
if (streq(node->name, "/"))
|
||||
@@ -1095,7 +1094,7 @@ static int destroy_node(void *_node)
|
||||
set_tdb_key(node->name, &key);
|
||||
tdb_delete(tdb_ctx, key);
|
||||
|
||||
- domain_entry_dec(talloc_parent(node), node);
|
||||
+ domain_entry_dec(conn, node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1104,7 +1103,8 @@ static struct node *create_node(struct connection *conn, const void *ctx,
|
||||
const char *name,
|
||||
void *data, unsigned int datalen)
|
||||
{
|
||||
- struct node *node, *i;
|
||||
+ struct node *node, *i, *j;
|
||||
+ int ret;
|
||||
|
||||
node = construct_node(conn, ctx, name);
|
||||
if (!node)
|
||||
@@ -1126,23 +1126,40 @@ static struct node *create_node(struct connection *conn, const void *ctx,
|
||||
/* 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;
|
||||
+ ret = ENOSPC;
|
||||
+ goto err;
|
||||
}
|
||||
- if (write_node(conn, i, false))
|
||||
- return NULL;
|
||||
|
||||
- /* Account for new node, set destructor for error case. */
|
||||
- if (i->parent) {
|
||||
+ ret = write_node(conn, i, false);
|
||||
+ if (ret)
|
||||
+ goto err;
|
||||
+
|
||||
+ /* Account for new node */
|
||||
+ 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->parent; i = i->parent)
|
||||
- talloc_set_destructor(i, NULL);
|
||||
return node;
|
||||
+
|
||||
+err:
|
||||
+ /*
|
||||
+ * We failed to update TDB for some of the nodes. Undo any work that
|
||||
+ * have already been done.
|
||||
+ */
|
||||
+ for (j = node; j != i; j = j->parent)
|
||||
+ destroy_node(conn, j);
|
||||
+
|
||||
+ /* We don't need to keep the nodes around, so free them. */
|
||||
+ i = node;
|
||||
+ while (i) {
|
||||
+ j = i;
|
||||
+ i = i->parent;
|
||||
+ talloc_free(j);
|
||||
+ }
|
||||
+
|
||||
+ errno = ret;
|
||||
+
|
||||
+ return NULL;
|
||||
}
|
||||
|
||||
/* path, data... */
|
||||
134
xsa415.patch
134
xsa415.patch
|
|
@ -1,134 +0,0 @@
|
|||
From: Julien Grall <jgrall@amazon.com>
|
||||
Subject: tools/xenstore: Fail a transaction if it is not possible to create a
|
||||
node
|
||||
|
||||
Commit f2bebf72c4d5 "xenstore: rework of transaction handling" moved
|
||||
out from copying the entire database everytime a new transaction is
|
||||
opened to track the list of nodes changed.
|
||||
|
||||
The content of all the nodes accessed during a transaction will be
|
||||
temporarily stored in TDB using a different key.
|
||||
|
||||
The function create_node() may write/update multiple nodes if the child
|
||||
doesn't exist. In case of a failure, the function will revert any
|
||||
changes (this include any update to TDB). Unfortunately, the function
|
||||
which reverts the changes (i.e. destroy_node()) will not use the correct
|
||||
key to delete any update or even request the transaction to fail.
|
||||
|
||||
This means that if a client decide to go ahead with committing the
|
||||
transaction, orphan nodes will be created because they were not linked
|
||||
to an existing node (create_node() will write the nodes backwards).
|
||||
|
||||
Once some nodes have been partially updated in a transaction, it is not
|
||||
easily possible to undo any changes. So rather than continuing and hit
|
||||
weird issue while committing, it is much saner to fail the transaction.
|
||||
|
||||
This will have an impact on any client that decides to commit even if it
|
||||
can't write a node. Although, it is not clear why a normal client would
|
||||
want to do that...
|
||||
|
||||
Lastly, update destroy_node() to use the correct key for deleting the
|
||||
node. Rather than recreating it (this will allocate memory and
|
||||
therefore fail), stash the key in the structure node.
|
||||
|
||||
This is XSA-415 / CVE-2022-42310.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Juergen Gross <jgross@suse.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index c30d14cbf2ab..55b79e4c032e 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -562,15 +562,17 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * Write the node. If the node is written, caller can find the key used in
|
||||
+ * node->key. This can later be used if the change needs to be reverted.
|
||||
+ */
|
||||
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))
|
||||
+ if (access_node(conn, node, NODE_ACCESS_WRITE, &node->key))
|
||||
return errno;
|
||||
|
||||
- return write_node_raw(conn, &key, node, no_quota_check);
|
||||
+ return write_node_raw(conn, &node->key, node, no_quota_check);
|
||||
}
|
||||
|
||||
unsigned int perm_for_conn(struct connection *conn,
|
||||
@@ -1086,16 +1088,21 @@ static struct node *construct_node(struct connection *conn, const void *ctx,
|
||||
|
||||
static int destroy_node(struct connection *conn, struct node *node)
|
||||
{
|
||||
- TDB_DATA key;
|
||||
-
|
||||
if (streq(node->name, "/"))
|
||||
corrupt(NULL, "Destroying root node!");
|
||||
|
||||
- set_tdb_key(node->name, &key);
|
||||
- tdb_delete(tdb_ctx, key);
|
||||
+ tdb_delete(tdb_ctx, node->key);
|
||||
|
||||
domain_entry_dec(conn, node);
|
||||
|
||||
+ /*
|
||||
+ * It is not possible to easily revert the changes in a transaction.
|
||||
+ * So if the failure happens in a transaction, mark it as fail to
|
||||
+ * prevent any commit.
|
||||
+ */
|
||||
+ if ( conn->transaction )
|
||||
+ fail_transaction(conn->transaction);
|
||||
+
|
||||
return 0;
|
||||
}
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index 742812a97469..7d0fe77e7989 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -155,6 +155,8 @@ struct node_perms {
|
||||
|
||||
struct node {
|
||||
const char *name;
|
||||
+ /* Key used to update TDB */
|
||||
+ TDB_DATA key;
|
||||
|
||||
/* Parent (optional) */
|
||||
struct node *parent;
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index cd07fb0f218b..faf6c930e42a 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -580,6 +580,11 @@ void transaction_entry_dec(struct transaction *trans, unsigned int domid)
|
||||
list_add_tail(&d->list, &trans->changed_domains);
|
||||
}
|
||||
|
||||
+void fail_transaction(struct transaction *trans)
|
||||
+{
|
||||
+ trans->fail = true;
|
||||
+}
|
||||
+
|
||||
void conn_delete_all_transactions(struct connection *conn)
|
||||
{
|
||||
struct transaction *trans;
|
||||
diff --git a/tools/xenstore/xenstored_transaction.h b/tools/xenstore/xenstored_transaction.h
|
||||
index 43a162bea3f3..14062730e3c9 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.h
|
||||
+++ b/tools/xenstore/xenstored_transaction.h
|
||||
@@ -46,6 +46,9 @@ int access_node(struct connection *conn, struct node *node,
|
||||
int transaction_prepend(struct connection *conn, const char *name,
|
||||
TDB_DATA *key);
|
||||
|
||||
+/* Mark the transaction as failed. This will prevent it to be committed. */
|
||||
+void fail_transaction(struct transaction *trans);
|
||||
+
|
||||
void conn_delete_all_transactions(struct connection *conn);
|
||||
int check_transactions(struct hashtable *hash);
|
||||
|
||||
|
|
@ -1,704 +0,0 @@
|
|||
From 80d128b14482d2e9342184d2d9949367851c4d14 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:10 +0200
|
||||
Subject: tools/xenstore: don't use conn->in as context for temporary
|
||||
allocations
|
||||
|
||||
Using the struct buffered data pointer of the current processed request
|
||||
for temporary data allocations has a major drawback: the used area (and
|
||||
with that the temporary data) is freed only after the response of the
|
||||
request has been written to the ring page or has been read via the
|
||||
socket. This can happen much later in case a guest isn't reading its
|
||||
responses fast enough.
|
||||
|
||||
As the temporary data can be safely freed after creating the response,
|
||||
add a temporary context for that purpose and use that for allocating
|
||||
the temporary memory, as it was already the case before commit
|
||||
cc0612464896 ("xenstore: add small default data buffer to internal
|
||||
struct").
|
||||
|
||||
Some sub-functions need to gain the "const" attribute for the talloc
|
||||
context.
|
||||
|
||||
This is XSA-416 / CVE-2022-42319.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Fixes: cc0612464896 ("xenstore: add small default data buffer to internal struct")
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_control.c b/tools/xenstore/xenstored_control.c
|
||||
index 1031a81c3874..d0350c6ad861 100644
|
||||
--- a/tools/xenstore/xenstored_control.c
|
||||
+++ b/tools/xenstore/xenstored_control.c
|
||||
@@ -155,7 +155,7 @@ bool lu_is_pending(void)
|
||||
|
||||
struct cmd_s {
|
||||
char *cmd;
|
||||
- int (*func)(void *, struct connection *, char **, int);
|
||||
+ int (*func)(const void *, struct connection *, char **, int);
|
||||
char *pars;
|
||||
/*
|
||||
* max_pars can be used to limit the size of the parameter vector,
|
||||
@@ -167,7 +167,7 @@ struct cmd_s {
|
||||
unsigned int max_pars;
|
||||
};
|
||||
|
||||
-static int do_control_check(void *ctx, struct connection *conn,
|
||||
+static int do_control_check(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
if (num)
|
||||
@@ -179,7 +179,7 @@ static int do_control_check(void *ctx, struct connection *conn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int do_control_log(void *ctx, struct connection *conn,
|
||||
+static int do_control_log(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
if (num != 1)
|
||||
@@ -281,7 +281,7 @@ static int quota_get(const void *ctx, struct connection *conn,
|
||||
return domain_get_quota(ctx, conn, atoi(vec[0]));
|
||||
}
|
||||
|
||||
-static int do_control_quota(void *ctx, struct connection *conn,
|
||||
+static int do_control_quota(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
if (num == 0)
|
||||
@@ -293,7 +293,7 @@ static int do_control_quota(void *ctx, struct connection *conn,
|
||||
return quota_get(ctx, conn, vec, num);
|
||||
}
|
||||
|
||||
-static int do_control_quota_s(void *ctx, struct connection *conn,
|
||||
+static int do_control_quota_s(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
if (num == 0)
|
||||
@@ -306,7 +306,7 @@ static int do_control_quota_s(void *ctx, struct connection *conn,
|
||||
}
|
||||
|
||||
#ifdef __MINIOS__
|
||||
-static int do_control_memreport(void *ctx, struct connection *conn,
|
||||
+static int do_control_memreport(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
if (num)
|
||||
@@ -318,7 +318,7 @@ static int do_control_memreport(void *ctx, struct connection *conn,
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
-static int do_control_logfile(void *ctx, struct connection *conn,
|
||||
+static int do_control_logfile(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
if (num != 1)
|
||||
@@ -333,7 +333,7 @@ static int do_control_logfile(void *ctx, struct connection *conn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int do_control_memreport(void *ctx, struct connection *conn,
|
||||
+static int do_control_memreport(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
FILE *fp;
|
||||
@@ -373,7 +373,7 @@ static int do_control_memreport(void *ctx, struct connection *conn,
|
||||
}
|
||||
#endif
|
||||
|
||||
-static int do_control_print(void *ctx, struct connection *conn,
|
||||
+static int do_control_print(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
if (num != 1)
|
||||
@@ -875,7 +875,7 @@ static const char *lu_start(const void *ctx, struct connection *conn,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
-static int do_control_lu(void *ctx, struct connection *conn,
|
||||
+static int do_control_lu(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
const char *ret = NULL;
|
||||
@@ -922,7 +922,7 @@ static int do_control_lu(void *ctx, struct connection *conn,
|
||||
}
|
||||
#endif
|
||||
|
||||
-static int do_control_help(void *, struct connection *, char **, int);
|
||||
+static int do_control_help(const void *, struct connection *, char **, int);
|
||||
|
||||
static struct cmd_s cmds[] = {
|
||||
{ "check", do_control_check, "" },
|
||||
@@ -961,7 +961,7 @@ static struct cmd_s cmds[] = {
|
||||
{ "help", do_control_help, "" },
|
||||
};
|
||||
|
||||
-static int do_control_help(void *ctx, struct connection *conn,
|
||||
+static int do_control_help(const void *ctx, struct connection *conn,
|
||||
char **vec, int num)
|
||||
{
|
||||
int cmd, len = 0;
|
||||
@@ -997,7 +997,8 @@ static int do_control_help(void *ctx, struct connection *conn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
-int do_control(struct connection *conn, struct buffered_data *in)
|
||||
+int do_control(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
unsigned int cmd, num, off;
|
||||
char **vec = NULL;
|
||||
@@ -1017,11 +1018,11 @@ int do_control(struct connection *conn, struct buffered_data *in)
|
||||
num = xs_count_strings(in->buffer, in->used);
|
||||
if (cmds[cmd].max_pars)
|
||||
num = min(num, cmds[cmd].max_pars);
|
||||
- vec = talloc_array(in, char *, num);
|
||||
+ vec = talloc_array(ctx, char *, num);
|
||||
if (!vec)
|
||||
return ENOMEM;
|
||||
if (get_strings(in, vec, num) < num)
|
||||
return EIO;
|
||||
|
||||
- return cmds[cmd].func(in, conn, vec + 1, num - 1);
|
||||
+ return cmds[cmd].func(ctx, conn, vec + 1, num - 1);
|
||||
}
|
||||
diff --git a/tools/xenstore/xenstored_control.h b/tools/xenstore/xenstored_control.h
|
||||
index 98b6fbcea2b1..a8cb76559ba1 100644
|
||||
--- a/tools/xenstore/xenstored_control.h
|
||||
+++ b/tools/xenstore/xenstored_control.h
|
||||
@@ -16,7 +16,8 @@
|
||||
along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
-int do_control(struct connection *conn, struct buffered_data *in);
|
||||
+int do_control(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
void lu_read_state(void);
|
||||
|
||||
struct connection *lu_get_connection(void);
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 16504de42017..411cc0e44714 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -1248,11 +1248,13 @@ static struct node *get_node_canonicalized(struct connection *conn,
|
||||
return get_node(conn, ctx, *canonical_name, perm);
|
||||
}
|
||||
|
||||
-static int send_directory(struct connection *conn, struct buffered_data *in)
|
||||
+static int send_directory(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct node *node;
|
||||
|
||||
- node = get_node_canonicalized(conn, in, onearg(in), NULL, XS_PERM_READ);
|
||||
+ node = get_node_canonicalized(conn, ctx, onearg(in), NULL,
|
||||
+ XS_PERM_READ);
|
||||
if (!node)
|
||||
return errno;
|
||||
|
||||
@@ -1261,7 +1263,7 @@ static int send_directory(struct connection *conn, struct buffered_data *in)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int send_directory_part(struct connection *conn,
|
||||
+static int send_directory_part(const void *ctx, struct connection *conn,
|
||||
struct buffered_data *in)
|
||||
{
|
||||
unsigned int off, len, maxlen, genlen;
|
||||
@@ -1273,7 +1275,8 @@ static int send_directory_part(struct connection *conn,
|
||||
return EINVAL;
|
||||
|
||||
/* First arg is node name. */
|
||||
- node = get_node_canonicalized(conn, in, in->buffer, NULL, XS_PERM_READ);
|
||||
+ node = get_node_canonicalized(conn, ctx, in->buffer, NULL,
|
||||
+ XS_PERM_READ);
|
||||
if (!node)
|
||||
return errno;
|
||||
|
||||
@@ -1300,7 +1303,7 @@ static int send_directory_part(struct connection *conn,
|
||||
break;
|
||||
}
|
||||
|
||||
- data = talloc_array(in, char, genlen + len + 1);
|
||||
+ data = talloc_array(ctx, char, genlen + len + 1);
|
||||
if (!data)
|
||||
return ENOMEM;
|
||||
|
||||
@@ -1316,11 +1319,13 @@ static int send_directory_part(struct connection *conn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int do_read(struct connection *conn, struct buffered_data *in)
|
||||
+static int do_read(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct node *node;
|
||||
|
||||
- node = get_node_canonicalized(conn, in, onearg(in), NULL, XS_PERM_READ);
|
||||
+ node = get_node_canonicalized(conn, ctx, onearg(in), NULL,
|
||||
+ XS_PERM_READ);
|
||||
if (!node)
|
||||
return errno;
|
||||
|
||||
@@ -1510,7 +1515,8 @@ static struct node *create_node(struct connection *conn, const void *ctx,
|
||||
}
|
||||
|
||||
/* path, data... */
|
||||
-static int do_write(struct connection *conn, struct buffered_data *in)
|
||||
+static int do_write(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
unsigned int offset, datalen;
|
||||
struct node *node;
|
||||
@@ -1524,12 +1530,12 @@ static int do_write(struct connection *conn, struct buffered_data *in)
|
||||
offset = strlen(vec[0]) + 1;
|
||||
datalen = in->used - offset;
|
||||
|
||||
- node = get_node_canonicalized(conn, in, vec[0], &name, XS_PERM_WRITE);
|
||||
+ node = get_node_canonicalized(conn, ctx, vec[0], &name, XS_PERM_WRITE);
|
||||
if (!node) {
|
||||
/* No permissions, invalid input? */
|
||||
if (errno != ENOENT)
|
||||
return errno;
|
||||
- node = create_node(conn, in, name, in->buffer + offset,
|
||||
+ node = create_node(conn, ctx, name, in->buffer + offset,
|
||||
datalen);
|
||||
if (!node)
|
||||
return errno;
|
||||
@@ -1540,18 +1546,19 @@ static int do_write(struct connection *conn, struct buffered_data *in)
|
||||
return errno;
|
||||
}
|
||||
|
||||
- fire_watches(conn, in, name, node, false, NULL);
|
||||
+ fire_watches(conn, ctx, name, node, false, NULL);
|
||||
send_ack(conn, XS_WRITE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int do_mkdir(struct connection *conn, struct buffered_data *in)
|
||||
+static int do_mkdir(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct node *node;
|
||||
char *name;
|
||||
|
||||
- node = get_node_canonicalized(conn, in, onearg(in), &name,
|
||||
+ node = get_node_canonicalized(conn, ctx, onearg(in), &name,
|
||||
XS_PERM_WRITE);
|
||||
|
||||
/* If it already exists, fine. */
|
||||
@@ -1561,10 +1568,10 @@ static int do_mkdir(struct connection *conn, struct buffered_data *in)
|
||||
return errno;
|
||||
if (!name)
|
||||
return ENOMEM;
|
||||
- node = create_node(conn, in, name, NULL, 0);
|
||||
+ node = create_node(conn, ctx, name, NULL, 0);
|
||||
if (!node)
|
||||
return errno;
|
||||
- fire_watches(conn, in, name, node, false, NULL);
|
||||
+ fire_watches(conn, ctx, name, node, false, NULL);
|
||||
}
|
||||
send_ack(conn, XS_MKDIR);
|
||||
|
||||
@@ -1662,24 +1669,25 @@ static int _rm(struct connection *conn, const void *ctx, struct node *node,
|
||||
}
|
||||
|
||||
|
||||
-static int do_rm(struct connection *conn, struct buffered_data *in)
|
||||
+static int do_rm(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct node *node;
|
||||
int ret;
|
||||
char *name;
|
||||
char *parentname;
|
||||
|
||||
- node = get_node_canonicalized(conn, in, onearg(in), &name,
|
||||
+ node = get_node_canonicalized(conn, ctx, onearg(in), &name,
|
||||
XS_PERM_WRITE);
|
||||
if (!node) {
|
||||
/* Didn't exist already? Fine, if parent exists. */
|
||||
if (errno == ENOENT) {
|
||||
if (!name)
|
||||
return ENOMEM;
|
||||
- parentname = get_parent(in, name);
|
||||
+ parentname = get_parent(ctx, name);
|
||||
if (!parentname)
|
||||
return errno;
|
||||
- node = read_node(conn, in, parentname);
|
||||
+ node = read_node(conn, ctx, parentname);
|
||||
if (node) {
|
||||
send_ack(conn, XS_RM);
|
||||
return 0;
|
||||
@@ -1694,7 +1702,7 @@ static int do_rm(struct connection *conn, struct buffered_data *in)
|
||||
if (streq(name, "/"))
|
||||
return EINVAL;
|
||||
|
||||
- ret = _rm(conn, in, node, name);
|
||||
+ ret = _rm(conn, ctx, node, name);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -1704,13 +1712,15 @@ static int do_rm(struct connection *conn, struct buffered_data *in)
|
||||
}
|
||||
|
||||
|
||||
-static int do_get_perms(struct connection *conn, struct buffered_data *in)
|
||||
+static int do_get_perms(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct node *node;
|
||||
char *strings;
|
||||
unsigned int len;
|
||||
|
||||
- node = get_node_canonicalized(conn, in, onearg(in), NULL, XS_PERM_READ);
|
||||
+ node = get_node_canonicalized(conn, ctx, onearg(in), NULL,
|
||||
+ XS_PERM_READ);
|
||||
if (!node)
|
||||
return errno;
|
||||
|
||||
@@ -1723,7 +1733,8 @@ static int do_get_perms(struct connection *conn, struct buffered_data *in)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int do_set_perms(struct connection *conn, struct buffered_data *in)
|
||||
+static int do_set_perms(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct node_perms perms, old_perms;
|
||||
char *name, *permstr;
|
||||
@@ -1740,7 +1751,7 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
|
||||
|
||||
permstr = in->buffer + strlen(in->buffer) + 1;
|
||||
|
||||
- perms.p = talloc_array(in, struct xs_permissions, perms.num);
|
||||
+ perms.p = talloc_array(ctx, struct xs_permissions, perms.num);
|
||||
if (!perms.p)
|
||||
return ENOMEM;
|
||||
if (!xs_strings_to_perms(perms.p, perms.num, permstr))
|
||||
@@ -1755,7 +1766,7 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
|
||||
}
|
||||
|
||||
/* We must own node to do this (tools can do this too). */
|
||||
- node = get_node_canonicalized(conn, in, in->buffer, &name,
|
||||
+ node = get_node_canonicalized(conn, ctx, in->buffer, &name,
|
||||
XS_PERM_WRITE | XS_PERM_OWNER);
|
||||
if (!node)
|
||||
return errno;
|
||||
@@ -1790,7 +1801,7 @@ static int do_set_perms(struct connection *conn, struct buffered_data *in)
|
||||
return errno;
|
||||
}
|
||||
|
||||
- fire_watches(conn, in, name, node, false, &old_perms);
|
||||
+ fire_watches(conn, ctx, name, node, false, &old_perms);
|
||||
send_ack(conn, XS_SET_PERMS);
|
||||
|
||||
return 0;
|
||||
@@ -1798,7 +1809,8 @@ 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);
|
||||
+ int (*func)(const void *ctx, 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. */
|
||||
@@ -1874,6 +1886,7 @@ static void process_message(struct connection *conn, struct buffered_data *in)
|
||||
struct transaction *trans;
|
||||
enum xsd_sockmsg_type type = in->hdr.msg.type;
|
||||
int ret;
|
||||
+ void *ctx;
|
||||
|
||||
/* At least send_error() and send_reply() expects conn->in == in */
|
||||
assert(conn->in == in);
|
||||
@@ -1898,10 +1911,17 @@ static void process_message(struct connection *conn, struct buffered_data *in)
|
||||
return;
|
||||
}
|
||||
|
||||
+ ctx = talloc_new(NULL);
|
||||
+ if (!ctx) {
|
||||
+ send_error(conn, ENOMEM);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
assert(conn->transaction == NULL);
|
||||
conn->transaction = trans;
|
||||
|
||||
- ret = wire_funcs[type].func(conn, in);
|
||||
+ ret = wire_funcs[type].func(ctx, conn, in);
|
||||
+ talloc_free(ctx);
|
||||
if (ret)
|
||||
send_error(conn, ret);
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index e7c6886ccf47..fb732d0a14c3 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -330,7 +330,7 @@ bool domain_is_unprivileged(struct connection *conn)
|
||||
domid_is_unprivileged(conn->domain->domid);
|
||||
}
|
||||
|
||||
-static char *talloc_domain_path(void *context, unsigned int domid)
|
||||
+static char *talloc_domain_path(const void *context, unsigned int domid)
|
||||
{
|
||||
return talloc_asprintf(context, "/local/domain/%u", domid);
|
||||
}
|
||||
@@ -534,7 +534,8 @@ static struct domain *introduce_domain(const void *ctx,
|
||||
}
|
||||
|
||||
/* domid, gfn, evtchn, path */
|
||||
-int do_introduce(struct connection *conn, struct buffered_data *in)
|
||||
+int do_introduce(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct domain *domain;
|
||||
char *vec[3];
|
||||
@@ -552,7 +553,7 @@ int do_introduce(struct connection *conn, struct buffered_data *in)
|
||||
if (port <= 0)
|
||||
return EINVAL;
|
||||
|
||||
- domain = introduce_domain(in, domid, port, false);
|
||||
+ domain = introduce_domain(ctx, domid, port, false);
|
||||
if (!domain)
|
||||
return errno;
|
||||
|
||||
@@ -575,7 +576,8 @@ static struct domain *find_connected_domain(unsigned int domid)
|
||||
return domain;
|
||||
}
|
||||
|
||||
-int do_set_target(struct connection *conn, struct buffered_data *in)
|
||||
+int do_set_target(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
char *vec[2];
|
||||
unsigned int domid, tdomid;
|
||||
@@ -619,7 +621,8 @@ static struct domain *onearg_domain(struct connection *conn,
|
||||
}
|
||||
|
||||
/* domid */
|
||||
-int do_release(struct connection *conn, struct buffered_data *in)
|
||||
+int do_release(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct domain *domain;
|
||||
|
||||
@@ -634,7 +637,8 @@ int do_release(struct connection *conn, struct buffered_data *in)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-int do_resume(struct connection *conn, struct buffered_data *in)
|
||||
+int do_resume(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct domain *domain;
|
||||
|
||||
@@ -649,7 +653,8 @@ int do_resume(struct connection *conn, struct buffered_data *in)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-int do_get_domain_path(struct connection *conn, struct buffered_data *in)
|
||||
+int do_get_domain_path(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
char *path;
|
||||
const char *domid_str = onearg(in);
|
||||
@@ -657,18 +662,17 @@ int do_get_domain_path(struct connection *conn, struct buffered_data *in)
|
||||
if (!domid_str)
|
||||
return EINVAL;
|
||||
|
||||
- path = talloc_domain_path(conn, atoi(domid_str));
|
||||
+ path = talloc_domain_path(ctx, atoi(domid_str));
|
||||
if (!path)
|
||||
return errno;
|
||||
|
||||
send_reply(conn, XS_GET_DOMAIN_PATH, path, strlen(path) + 1);
|
||||
|
||||
- talloc_free(path);
|
||||
-
|
||||
return 0;
|
||||
}
|
||||
|
||||
-int do_is_domain_introduced(struct connection *conn, struct buffered_data *in)
|
||||
+int do_is_domain_introduced(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
int result;
|
||||
unsigned int domid;
|
||||
@@ -689,7 +693,8 @@ int do_is_domain_introduced(struct connection *conn, struct buffered_data *in)
|
||||
}
|
||||
|
||||
/* Allow guest to reset all watches */
|
||||
-int do_reset_watches(struct connection *conn, struct buffered_data *in)
|
||||
+int do_reset_watches(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
conn_delete_all_watches(conn);
|
||||
conn_delete_all_transactions(conn);
|
||||
diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
|
||||
index 904faa923afb..b9e152890149 100644
|
||||
--- a/tools/xenstore/xenstored_domain.h
|
||||
+++ b/tools/xenstore/xenstored_domain.h
|
||||
@@ -24,25 +24,32 @@ void handle_event(void);
|
||||
void check_domains(void);
|
||||
|
||||
/* domid, mfn, eventchn, path */
|
||||
-int do_introduce(struct connection *conn, struct buffered_data *in);
|
||||
+int do_introduce(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
|
||||
/* domid */
|
||||
-int do_is_domain_introduced(struct connection *conn, struct buffered_data *in);
|
||||
+int do_is_domain_introduced(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
|
||||
/* domid */
|
||||
-int do_release(struct connection *conn, struct buffered_data *in);
|
||||
+int do_release(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
|
||||
/* domid */
|
||||
-int do_resume(struct connection *conn, struct buffered_data *in);
|
||||
+int do_resume(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
|
||||
/* domid, target */
|
||||
-int do_set_target(struct connection *conn, struct buffered_data *in);
|
||||
+int do_set_target(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
|
||||
/* domid */
|
||||
-int do_get_domain_path(struct connection *conn, struct buffered_data *in);
|
||||
+int do_get_domain_path(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
|
||||
/* Allow guest to reset all watches */
|
||||
-int do_reset_watches(struct connection *conn, struct buffered_data *in);
|
||||
+int do_reset_watches(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
|
||||
void domain_init(int evtfd);
|
||||
void dom0_init(void);
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index 28774813de83..3e3eb47326cc 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -481,7 +481,8 @@ struct transaction *transaction_lookup(struct connection *conn, uint32_t id)
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
-int do_transaction_start(struct connection *conn, struct buffered_data *in)
|
||||
+int do_transaction_start(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct transaction *trans, *exists;
|
||||
char id_str[20];
|
||||
@@ -494,8 +495,8 @@ int do_transaction_start(struct connection *conn, struct buffered_data *in)
|
||||
conn->transaction_started > quota_max_transaction)
|
||||
return ENOSPC;
|
||||
|
||||
- /* Attach transaction to input for autofree until it's complete */
|
||||
- trans = talloc_zero(in, struct transaction);
|
||||
+ /* Attach transaction to ctx for autofree until it's complete */
|
||||
+ trans = talloc_zero(ctx, struct transaction);
|
||||
if (!trans)
|
||||
return ENOMEM;
|
||||
|
||||
@@ -544,7 +545,8 @@ static int transaction_fix_domains(struct transaction *trans, bool update)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-int do_transaction_end(struct connection *conn, struct buffered_data *in)
|
||||
+int do_transaction_end(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
const char *arg = onearg(in);
|
||||
struct transaction *trans;
|
||||
@@ -562,8 +564,8 @@ int do_transaction_end(struct connection *conn, struct buffered_data *in)
|
||||
if (!conn->transaction_started)
|
||||
conn->ta_start_time = 0;
|
||||
|
||||
- /* Attach transaction to in for auto-cleanup */
|
||||
- talloc_steal(in, trans);
|
||||
+ /* Attach transaction to ctx for auto-cleanup */
|
||||
+ talloc_steal(ctx, trans);
|
||||
|
||||
if (streq(arg, "T")) {
|
||||
if (trans->fail)
|
||||
diff --git a/tools/xenstore/xenstored_transaction.h b/tools/xenstore/xenstored_transaction.h
|
||||
index e3cbd6b23095..39d7f81c5127 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.h
|
||||
+++ b/tools/xenstore/xenstored_transaction.h
|
||||
@@ -29,8 +29,10 @@ struct transaction;
|
||||
|
||||
extern uint64_t generation;
|
||||
|
||||
-int do_transaction_start(struct connection *conn, struct buffered_data *node);
|
||||
-int do_transaction_end(struct connection *conn, struct buffered_data *in);
|
||||
+int do_transaction_start(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *node);
|
||||
+int do_transaction_end(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
|
||||
struct transaction *transaction_lookup(struct connection *conn, uint32_t id);
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_watch.c b/tools/xenstore/xenstored_watch.c
|
||||
index 85362bcce314..316c08b7f754 100644
|
||||
--- a/tools/xenstore/xenstored_watch.c
|
||||
+++ b/tools/xenstore/xenstored_watch.c
|
||||
@@ -243,7 +243,7 @@ static struct watch *add_watch(struct connection *conn, char *path, char *token,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
-int do_watch(struct connection *conn, struct buffered_data *in)
|
||||
+int do_watch(const void *ctx, struct connection *conn, struct buffered_data *in)
|
||||
{
|
||||
struct watch *watch;
|
||||
char *vec[2];
|
||||
@@ -252,7 +252,7 @@ int do_watch(struct connection *conn, struct buffered_data *in)
|
||||
if (get_strings(in, vec, ARRAY_SIZE(vec)) != ARRAY_SIZE(vec))
|
||||
return EINVAL;
|
||||
|
||||
- errno = check_watch_path(conn, in, &(vec[0]), &relative);
|
||||
+ errno = check_watch_path(conn, ctx, &(vec[0]), &relative);
|
||||
if (errno)
|
||||
return errno;
|
||||
|
||||
@@ -283,7 +283,8 @@ int do_watch(struct connection *conn, struct buffered_data *in)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-int do_unwatch(struct connection *conn, struct buffered_data *in)
|
||||
+int do_unwatch(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in)
|
||||
{
|
||||
struct watch *watch;
|
||||
char *node, *vec[2];
|
||||
@@ -291,7 +292,7 @@ int do_unwatch(struct connection *conn, struct buffered_data *in)
|
||||
if (get_strings(in, vec, ARRAY_SIZE(vec)) != ARRAY_SIZE(vec))
|
||||
return EINVAL;
|
||||
|
||||
- node = xenstore_canonicalize(conn, in, vec[0]);
|
||||
+ node = xenstore_canonicalize(conn, ctx, vec[0]);
|
||||
if (!node)
|
||||
return ENOMEM;
|
||||
list_for_each_entry(watch, &conn->watches, list) {
|
||||
diff --git a/tools/xenstore/xenstored_watch.h b/tools/xenstore/xenstored_watch.h
|
||||
index 0e693f0839cd..091890edca96 100644
|
||||
--- a/tools/xenstore/xenstored_watch.h
|
||||
+++ b/tools/xenstore/xenstored_watch.h
|
||||
@@ -21,8 +21,10 @@
|
||||
|
||||
#include "xenstored_core.h"
|
||||
|
||||
-int do_watch(struct connection *conn, struct buffered_data *in);
|
||||
-int do_unwatch(struct connection *conn, struct buffered_data *in);
|
||||
+int do_watch(const void *ctx, struct connection *conn,
|
||||
+ struct buffered_data *in);
|
||||
+int do_unwatch(const void *ctx, 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,
|
||||
135
xsa417.patch
135
xsa417.patch
|
|
@ -1,135 +0,0 @@
|
|||
From 67d5ecd609b8f12346eadb40e547cd7e01d825dc Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:10 +0200
|
||||
Subject: tools/xenstore: fix checking node permissions
|
||||
|
||||
Today chk_domain_generation() is being used to check whether a node
|
||||
permission entry is still valid or whether it is referring to a domain
|
||||
no longer existing. This is done by comparing the node's and the
|
||||
domain's generation count.
|
||||
|
||||
In case no struct domain is existing for a checked domain, but the
|
||||
domain itself is valid, chk_domain_generation() assumes it is being
|
||||
called due to the first node created for a new domain and it will
|
||||
return success.
|
||||
|
||||
This might be wrong in case the checked permission is related to an
|
||||
old domain, which has just been replaced with a new domain using the
|
||||
same domid.
|
||||
|
||||
Fix that by letting chk_domain_generation() fail in case a struct
|
||||
domain isn't found. In order to cover the case of the first node for
|
||||
a new domain try to allocate the needed struct domain explicitly when
|
||||
processing the related SET_PERMS command. In case a referenced domain
|
||||
isn't existing, flag the related permission to be ignored right away.
|
||||
|
||||
This is XSA-417 / CVE-2022-42320.
|
||||
|
||||
Reported-by: Juergen Gross <jgross@suse.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 66bbeaf6bfb0..a0c176fa203e 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -1753,6 +1753,11 @@ static int do_set_perms(const void *ctx, struct connection *conn,
|
||||
if (!xs_strings_to_perms(perms.p, perms.num, permstr))
|
||||
return errno;
|
||||
|
||||
+ if (domain_alloc_permrefs(&perms) < 0)
|
||||
+ return ENOMEM;
|
||||
+ if (perms.p[0].perms & XS_PERM_IGNORE)
|
||||
+ return ENOENT;
|
||||
+
|
||||
/* First arg is node name. */
|
||||
if (strstarts(in->buffer, "@")) {
|
||||
if (set_perms_special(conn, in->buffer, &perms))
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index b9ff4ded8360..98b401fdec30 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -907,7 +907,6 @@ int domain_entry_inc(struct connection *conn, struct node *node)
|
||||
* count (used for testing whether a node permission is older than a domain).
|
||||
*
|
||||
* Return values:
|
||||
- * -1: error
|
||||
* 0: domain has higher generation count (it is younger than a node with the
|
||||
* given count), or domain isn't existing any longer
|
||||
* 1: domain is older than the node
|
||||
@@ -915,20 +914,38 @@ int domain_entry_inc(struct connection *conn, struct node *node)
|
||||
static int chk_domain_generation(unsigned int domid, uint64_t gen)
|
||||
{
|
||||
struct domain *d;
|
||||
- xc_dominfo_t dominfo;
|
||||
|
||||
if (!xc_handle && domid == 0)
|
||||
return 1;
|
||||
|
||||
d = find_domain_struct(domid);
|
||||
- if (d)
|
||||
- return (d->generation <= gen) ? 1 : 0;
|
||||
|
||||
- if (!get_domain_info(domid, &dominfo))
|
||||
- return 0;
|
||||
+ return (d && d->generation <= gen) ? 1 : 0;
|
||||
+}
|
||||
|
||||
- d = alloc_domain(NULL, domid);
|
||||
- return d ? 1 : -1;
|
||||
+/*
|
||||
+ * Allocate all missing struct domain referenced by a permission set.
|
||||
+ * Any permission entries for not existing domains will be marked to be
|
||||
+ * ignored.
|
||||
+ */
|
||||
+int domain_alloc_permrefs(struct node_perms *perms)
|
||||
+{
|
||||
+ unsigned int i, domid;
|
||||
+ struct domain *d;
|
||||
+ xc_dominfo_t dominfo;
|
||||
+
|
||||
+ for (i = 0; i < perms->num; i++) {
|
||||
+ domid = perms->p[i].id;
|
||||
+ d = find_domain_struct(domid);
|
||||
+ if (!d) {
|
||||
+ if (!get_domain_info(domid, &dominfo))
|
||||
+ perms->p[i].perms |= XS_PERM_IGNORE;
|
||||
+ else if (!alloc_domain(NULL, domid))
|
||||
+ return ENOMEM;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -941,8 +958,6 @@ int domain_adjust_node_perms(struct connection *conn, struct node *node)
|
||||
int ret;
|
||||
|
||||
ret = chk_domain_generation(node->perms.p[0].id, node->generation);
|
||||
- if (ret < 0)
|
||||
- return errno;
|
||||
|
||||
/* If the owner doesn't exist any longer give it to priv domain. */
|
||||
if (!ret) {
|
||||
@@ -959,8 +974,6 @@ int domain_adjust_node_perms(struct connection *conn, struct node *node)
|
||||
continue;
|
||||
ret = chk_domain_generation(node->perms.p[i].id,
|
||||
node->generation);
|
||||
- if (ret < 0)
|
||||
- return errno;
|
||||
if (!ret)
|
||||
node->perms.p[i].perms |= XS_PERM_IGNORE;
|
||||
}
|
||||
diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
|
||||
index 209442190911..7fe0a21d9e45 100644
|
||||
--- a/tools/xenstore/xenstored_domain.h
|
||||
+++ b/tools/xenstore/xenstored_domain.h
|
||||
@@ -63,6 +63,7 @@ bool domain_is_unprivileged(struct connection *conn);
|
||||
|
||||
/* Remove node permissions for no longer existing domains. */
|
||||
int domain_adjust_node_perms(struct connection *conn, struct node *node);
|
||||
+int domain_alloc_permrefs(struct node_perms *perms);
|
||||
|
||||
/* Quota manipulation */
|
||||
int domain_entry_inc(struct connection *conn, struct node *);
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
From d1e6dca486599ab914af7b38b3782b237d3d603b Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:11 +0200
|
||||
Subject: tools/xenstore: remove recursion from construct_node()
|
||||
|
||||
In order to reduce stack usage due to recursion, switch
|
||||
construct_node() to use a loop instead.
|
||||
|
||||
This is part of XSA-418 / CVE-2022-42321.
|
||||
|
||||
Reported-by: Julien Grall <dvrabel@amazon.co.uk>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index c676ee4e4e4f..3907c35643e9 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -1377,45 +1377,69 @@ static int add_child(const void *ctx, struct node *parent, const char *name)
|
||||
static struct node *construct_node(struct connection *conn, const void *ctx,
|
||||
const char *name)
|
||||
{
|
||||
- struct node *parent, *node;
|
||||
- char *parentname = get_parent(ctx, name);
|
||||
+ const char **names = NULL;
|
||||
+ unsigned int levels = 0;
|
||||
+ struct node *node = NULL;
|
||||
+ struct node *parent = NULL;
|
||||
+ const char *parentname = talloc_strdup(ctx, name);
|
||||
|
||||
if (!parentname)
|
||||
return NULL;
|
||||
|
||||
- /* If parent doesn't exist, create it. */
|
||||
- parent = read_node(conn, parentname, parentname);
|
||||
- if (!parent && errno == ENOENT)
|
||||
- parent = construct_node(conn, ctx, parentname);
|
||||
- if (!parent)
|
||||
- return NULL;
|
||||
+ /* Walk the path up until an existing node is found. */
|
||||
+ while (!parent) {
|
||||
+ names = talloc_realloc(ctx, names, const char *, levels + 1);
|
||||
+ if (!names)
|
||||
+ goto nomem;
|
||||
|
||||
- /* Add child to parent. */
|
||||
- if (add_child(ctx, parent, name))
|
||||
- goto nomem;
|
||||
+ /*
|
||||
+ * names[0] is the name of the node to construct initially,
|
||||
+ * names[1] is its parent, and so on.
|
||||
+ */
|
||||
+ names[levels] = parentname;
|
||||
+ parentname = get_parent(ctx, parentname);
|
||||
+ if (!parentname)
|
||||
+ return NULL;
|
||||
|
||||
- /* Allocate node */
|
||||
- node = talloc(ctx, struct node);
|
||||
- if (!node)
|
||||
- goto nomem;
|
||||
- node->name = talloc_strdup(node, name);
|
||||
- if (!node->name)
|
||||
- goto nomem;
|
||||
+ /* Try to read parent node until we found an existing one. */
|
||||
+ parent = read_node(conn, ctx, parentname);
|
||||
+ if (!parent && (errno != ENOENT || !strcmp(parentname, "/")))
|
||||
+ return NULL;
|
||||
|
||||
- /* Inherit permissions, except unprivileged domains own what they create */
|
||||
- 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.p[0].id = conn->id;
|
||||
+ levels++;
|
||||
+ }
|
||||
+
|
||||
+ /* Walk the path down again constructing the missing nodes. */
|
||||
+ for (; levels > 0; levels--) {
|
||||
+ /* Add child to parent. */
|
||||
+ if (add_child(ctx, parent, names[levels - 1]))
|
||||
+ goto nomem;
|
||||
+
|
||||
+ /* Allocate node */
|
||||
+ node = talloc(ctx, struct node);
|
||||
+ if (!node)
|
||||
+ goto nomem;
|
||||
+ node->name = talloc_steal(node, names[levels - 1]);
|
||||
+
|
||||
+ /* Inherit permissions, unpriv domains own what they create. */
|
||||
+ 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.p[0].id = conn->id;
|
||||
+
|
||||
+ /* No children, no data */
|
||||
+ node->children = node->data = NULL;
|
||||
+ node->childlen = node->datalen = 0;
|
||||
+ node->acc.memory = 0;
|
||||
+ node->parent = parent;
|
||||
+
|
||||
+ parent = node;
|
||||
+ }
|
||||
|
||||
- /* No children, no data */
|
||||
- node->children = node->data = NULL;
|
||||
- node->childlen = node->datalen = 0;
|
||||
- node->acc.memory = 0;
|
||||
- node->parent = parent;
|
||||
return node;
|
||||
|
||||
nomem:
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
From c13d85a2fe94bbf3cb8186b89324c5d1b4f9a61f Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:11 +0200
|
||||
Subject: tools/xenstore: don't let remove_child_entry() call corrupt()
|
||||
|
||||
In case of write_node() returning an error, remove_child_entry() will
|
||||
call corrupt() today. This could result in an endless recursion, as
|
||||
remove_child_entry() is called by corrupt(), too:
|
||||
|
||||
corrupt()
|
||||
check_store()
|
||||
check_store_()
|
||||
remove_child_entry()
|
||||
|
||||
Fix that by letting remove_child_entry() return an error instead and
|
||||
let the caller decide what to do.
|
||||
|
||||
This is part of XSA-418 / CVE-2022-42321.
|
||||
|
||||
Reported-by: Julien Grall <dvrabel@amazon.co.uk>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 3907c35643e9..f433a45dc217 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -1608,15 +1608,15 @@ static void memdel(void *mem, unsigned off, unsigned len, unsigned total)
|
||||
memmove(mem + off, mem + off + len, total - off - len);
|
||||
}
|
||||
|
||||
-static void remove_child_entry(struct connection *conn, struct node *node,
|
||||
- size_t offset)
|
||||
+static int 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;
|
||||
- if (write_node(conn, node, true))
|
||||
- corrupt(conn, "Can't update parent node '%s'", node->name);
|
||||
+
|
||||
+ return write_node(conn, node, true);
|
||||
}
|
||||
|
||||
static void delete_child(struct connection *conn,
|
||||
@@ -1626,7 +1626,9 @@ static void delete_child(struct connection *conn,
|
||||
|
||||
for (i = 0; i < node->childlen; i += strlen(node->children+i) + 1) {
|
||||
if (streq(node->children+i, childname)) {
|
||||
- remove_child_entry(conn, node, i);
|
||||
+ if (remove_child_entry(conn, node, i))
|
||||
+ corrupt(conn, "Can't update parent node '%s'",
|
||||
+ node->name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2325,6 +2327,17 @@ int remember_string(struct hashtable *hash, const char *str)
|
||||
return hashtable_insert(hash, k, (void *)1);
|
||||
}
|
||||
|
||||
+static int rm_child_entry(struct node *node, size_t off, size_t len)
|
||||
+{
|
||||
+ if (!recovery)
|
||||
+ return off;
|
||||
+
|
||||
+ if (remove_child_entry(NULL, node, off))
|
||||
+ log("check_store: child entry could not be removed from '%s'",
|
||||
+ node->name);
|
||||
+
|
||||
+ return off - len - 1;
|
||||
+}
|
||||
|
||||
/**
|
||||
* A node has a children field that names the children of the node, separated
|
||||
@@ -2377,12 +2390,7 @@ static int check_store_(const char *name, struct hashtable *reachable)
|
||||
if (hashtable_search(children, childname)) {
|
||||
log("check_store: '%s' is duplicated!",
|
||||
childname);
|
||||
-
|
||||
- if (recovery) {
|
||||
- remove_child_entry(NULL, node,
|
||||
- i);
|
||||
- i -= childlen + 1;
|
||||
- }
|
||||
+ i = rm_child_entry(node, i, childlen);
|
||||
}
|
||||
else {
|
||||
if (!remember_string(children,
|
||||
@@ -2399,11 +2407,7 @@ static int check_store_(const char *name, struct hashtable *reachable)
|
||||
} else if (errno != ENOMEM) {
|
||||
log("check_store: No child '%s' found!\n",
|
||||
childname);
|
||||
-
|
||||
- if (recovery) {
|
||||
- remove_child_entry(NULL, node, i);
|
||||
- i -= childlen + 1;
|
||||
- }
|
||||
+ i = rm_child_entry(node, i, childlen);
|
||||
} else {
|
||||
log("check_store: ENOMEM");
|
||||
ret = ENOMEM;
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
From aac9b51b6fbbbd16c910f69365345528c5bec106 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:11 +0200
|
||||
Subject: tools/xenstore: add generic treewalk function
|
||||
|
||||
Add a generic function to walk the complete node tree. It will start
|
||||
at "/" and descend recursively into each child, calling a function
|
||||
specified by the caller. Depending on the return value of the user
|
||||
specified function the walk will be aborted, continued, or the current
|
||||
child will be skipped by not descending into its children.
|
||||
|
||||
This is part of XSA-418 / CVE-2022-42321.
|
||||
|
||||
Reported-by: Julien Grall <dvrabel@amazon.co.uk>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index f433a45dc217..2cda3ee375ab 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -1838,6 +1838,135 @@ static int do_set_perms(const void *ctx, struct connection *conn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
+static char *child_name(const void *ctx, const char *s1, const char *s2)
|
||||
+{
|
||||
+ if (strcmp(s1, "/"))
|
||||
+ return talloc_asprintf(ctx, "%s/%s", s1, s2);
|
||||
+ return talloc_asprintf(ctx, "/%s", s2);
|
||||
+}
|
||||
+
|
||||
+static int rm_from_parent(struct connection *conn, struct node *parent,
|
||||
+ const char *name)
|
||||
+{
|
||||
+ size_t off;
|
||||
+
|
||||
+ if (!parent)
|
||||
+ return WALK_TREE_ERROR_STOP;
|
||||
+
|
||||
+ for (off = parent->childoff - 1; off && parent->children[off - 1];
|
||||
+ off--);
|
||||
+ if (remove_child_entry(conn, parent, off)) {
|
||||
+ log("treewalk: child entry could not be removed from '%s'",
|
||||
+ parent->name);
|
||||
+ return WALK_TREE_ERROR_STOP;
|
||||
+ }
|
||||
+ parent->childoff = off;
|
||||
+
|
||||
+ return WALK_TREE_OK;
|
||||
+}
|
||||
+
|
||||
+static int walk_call_func(const void *ctx, struct connection *conn,
|
||||
+ struct node *node, struct node *parent, void *arg,
|
||||
+ int (*func)(const void *ctx, struct connection *conn,
|
||||
+ struct node *node, void *arg))
|
||||
+{
|
||||
+ int ret;
|
||||
+
|
||||
+ if (!func)
|
||||
+ return WALK_TREE_OK;
|
||||
+
|
||||
+ ret = func(ctx, conn, node, arg);
|
||||
+ if (ret == WALK_TREE_RM_CHILDENTRY && parent)
|
||||
+ ret = rm_from_parent(conn, parent, node->name);
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
+int walk_node_tree(const void *ctx, struct connection *conn, const char *root,
|
||||
+ struct walk_funcs *funcs, void *arg)
|
||||
+{
|
||||
+ int ret = 0;
|
||||
+ void *tmpctx;
|
||||
+ char *name;
|
||||
+ struct node *node = NULL;
|
||||
+ struct node *parent = NULL;
|
||||
+
|
||||
+ tmpctx = talloc_new(ctx);
|
||||
+ if (!tmpctx) {
|
||||
+ errno = ENOMEM;
|
||||
+ return WALK_TREE_ERROR_STOP;
|
||||
+ }
|
||||
+ name = talloc_strdup(tmpctx, root);
|
||||
+ if (!name) {
|
||||
+ errno = ENOMEM;
|
||||
+ talloc_free(tmpctx);
|
||||
+ return WALK_TREE_ERROR_STOP;
|
||||
+ }
|
||||
+
|
||||
+ /* Continue the walk until an error is returned. */
|
||||
+ while (ret >= 0) {
|
||||
+ /* node == NULL possible only for the initial loop iteration. */
|
||||
+ if (node) {
|
||||
+ /* Go one step up if ret or if last child finished. */
|
||||
+ if (ret || node->childoff >= node->childlen) {
|
||||
+ parent = node->parent;
|
||||
+ /* Call function AFTER processing a node. */
|
||||
+ ret = walk_call_func(ctx, conn, node, parent,
|
||||
+ arg, funcs->exit);
|
||||
+ /* Last node, so exit loop. */
|
||||
+ if (!parent)
|
||||
+ break;
|
||||
+ talloc_free(node);
|
||||
+ /* Continue with parent. */
|
||||
+ node = parent;
|
||||
+ continue;
|
||||
+ }
|
||||
+ /* Get next child of current node. */
|
||||
+ name = child_name(tmpctx, node->name,
|
||||
+ node->children + node->childoff);
|
||||
+ if (!name) {
|
||||
+ ret = WALK_TREE_ERROR_STOP;
|
||||
+ break;
|
||||
+ }
|
||||
+ /* Point to next child. */
|
||||
+ node->childoff += strlen(node->children +
|
||||
+ node->childoff) + 1;
|
||||
+ /* Descent into children. */
|
||||
+ parent = node;
|
||||
+ }
|
||||
+ /* Read next node (root node or next child). */
|
||||
+ node = read_node(conn, tmpctx, name);
|
||||
+ if (!node) {
|
||||
+ /* Child not found - should not happen! */
|
||||
+ /* ENOENT case can be handled by supplied function. */
|
||||
+ if (errno == ENOENT && funcs->enoent)
|
||||
+ ret = funcs->enoent(ctx, conn, parent, name,
|
||||
+ arg);
|
||||
+ else
|
||||
+ ret = WALK_TREE_ERROR_STOP;
|
||||
+ if (!parent)
|
||||
+ break;
|
||||
+ if (ret == WALK_TREE_RM_CHILDENTRY)
|
||||
+ ret = rm_from_parent(conn, parent, name);
|
||||
+ if (ret < 0)
|
||||
+ break;
|
||||
+ talloc_free(name);
|
||||
+ node = parent;
|
||||
+ continue;
|
||||
+ }
|
||||
+ talloc_free(name);
|
||||
+ node->parent = parent;
|
||||
+ node->childoff = 0;
|
||||
+ /* Call function BEFORE processing a node. */
|
||||
+ ret = walk_call_func(ctx, conn, node, parent, arg,
|
||||
+ funcs->enter);
|
||||
+ }
|
||||
+
|
||||
+ talloc_free(tmpctx);
|
||||
+
|
||||
+ return ret < 0 ? ret : WALK_TREE_OK;
|
||||
+}
|
||||
+
|
||||
static struct {
|
||||
const char *str;
|
||||
int (*func)(const void *ctx, struct connection *conn,
|
||||
@@ -2305,18 +2434,6 @@ static int keys_equal_fn(void *key1, void *key2)
|
||||
return 0 == strcmp((char *)key1, (char *)key2);
|
||||
}
|
||||
|
||||
-
|
||||
-static char *child_name(const char *s1, const char *s2)
|
||||
-{
|
||||
- if (strcmp(s1, "/")) {
|
||||
- return talloc_asprintf(NULL, "%s/%s", s1, s2);
|
||||
- }
|
||||
- else {
|
||||
- return talloc_asprintf(NULL, "/%s", s2);
|
||||
- }
|
||||
-}
|
||||
-
|
||||
-
|
||||
int remember_string(struct hashtable *hash, const char *str)
|
||||
{
|
||||
char *k = malloc(strlen(str) + 1);
|
||||
@@ -2376,7 +2493,7 @@ static int check_store_(const char *name, struct hashtable *reachable)
|
||||
while (i < node->childlen && !ret) {
|
||||
struct node *childnode;
|
||||
size_t childlen = strlen(node->children + i);
|
||||
- char * childname = child_name(node->name,
|
||||
+ char * childname = child_name(NULL, node->name,
|
||||
node->children + i);
|
||||
|
||||
if (!childname) {
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index bfd3fc1e9df3..2d9942171d92 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -202,6 +202,7 @@ struct node {
|
||||
|
||||
/* Children, each nul-terminated. */
|
||||
unsigned int childlen;
|
||||
+ unsigned int childoff; /* Used by walk_node_tree() internally. */
|
||||
char *children;
|
||||
|
||||
/* Allocation information for node currently in store. */
|
||||
@@ -338,6 +339,45 @@ void read_state_buffered_data(const void *ctx, struct connection *conn,
|
||||
const struct xs_state_connection *sc);
|
||||
void read_state_node(const void *ctx, const void *state);
|
||||
|
||||
+/*
|
||||
+ * Walk the node tree below root calling funcs->enter() and funcs->exit() for
|
||||
+ * each node. funcs->enter() is being called when entering a node, so before
|
||||
+ * any of the children of the node is processed. funcs->exit() is being
|
||||
+ * called when leaving the node, so after all children have been processed.
|
||||
+ * funcs->enoent() is being called when a node isn't existing.
|
||||
+ * funcs->*() return values:
|
||||
+ * < 0: tree walk is stopped, walk_node_tree() returns funcs->*() return value
|
||||
+ * in case WALK_TREE_ERROR_STOP is returned, errno should be set
|
||||
+ * WALK_TREE_OK: tree walk is continuing
|
||||
+ * WALK_TREE_SKIP_CHILDREN: tree walk won't descend below current node, but
|
||||
+ * walk continues
|
||||
+ * WALK_TREE_RM_CHILDENTRY: Remove the child entry from its parent and write
|
||||
+ * the modified parent node back to the data base, implies to not descend
|
||||
+ * below the current node, but to continue the walk
|
||||
+ * funcs->*() is allowed to modify the node it is called for in the data base.
|
||||
+ * In case funcs->enter() is deleting the node, it must not return WALK_TREE_OK
|
||||
+ * in order to avoid descending into no longer existing children.
|
||||
+ */
|
||||
+/* Return values for funcs->*() and walk_node_tree(). */
|
||||
+#define WALK_TREE_SUCCESS_STOP -100 /* Stop walk early, no error. */
|
||||
+#define WALK_TREE_ERROR_STOP -1 /* Stop walk due to error. */
|
||||
+#define WALK_TREE_OK 0 /* No error. */
|
||||
+/* Return value for funcs->*() only. */
|
||||
+#define WALK_TREE_SKIP_CHILDREN 1 /* Don't recurse below current node. */
|
||||
+#define WALK_TREE_RM_CHILDENTRY 2 /* Remove child entry from parent. */
|
||||
+
|
||||
+struct walk_funcs {
|
||||
+ int (*enter)(const void *ctx, struct connection *conn,
|
||||
+ struct node *node, void *arg);
|
||||
+ int (*exit)(const void *ctx, struct connection *conn,
|
||||
+ struct node *node, void *arg);
|
||||
+ int (*enoent)(const void *ctx, struct connection *conn,
|
||||
+ struct node *parent, char *name, void *arg);
|
||||
+};
|
||||
+
|
||||
+int walk_node_tree(const void *ctx, struct connection *conn, const char *root,
|
||||
+ struct walk_funcs *funcs, void *arg);
|
||||
+
|
||||
#endif /* _XENSTORED_CORE_H */
|
||||
|
||||
/*
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
From bdc931fb5dcebbd8d0e44b5d8bd3fb9106ee8596 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:12 +0200
|
||||
Subject: tools/xenstore: simplify check_store()
|
||||
|
||||
check_store() is using a hash table for storing all node names it has
|
||||
found via walking the tree. Additionally it using another hash table
|
||||
for all children of a node to detect duplicate child names.
|
||||
|
||||
Simplify that by dropping the second hash table as the first one is
|
||||
already holding all the needed information.
|
||||
|
||||
This is part of XSA-418 / CVE-2022-42321.
|
||||
|
||||
Reported-by: Julien Grall <dvrabel@amazon.co.uk>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 2cda3ee375ab..760f3c16c794 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -2477,50 +2477,34 @@ static int check_store_(const char *name, struct hashtable *reachable)
|
||||
if (node) {
|
||||
size_t i = 0;
|
||||
|
||||
- struct hashtable * children =
|
||||
- create_hashtable(16, hash_from_key_fn, keys_equal_fn);
|
||||
- if (!children) {
|
||||
- log("check_store create table: ENOMEM");
|
||||
- return ENOMEM;
|
||||
- }
|
||||
-
|
||||
if (!remember_string(reachable, name)) {
|
||||
- hashtable_destroy(children, 0);
|
||||
log("check_store: ENOMEM");
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
while (i < node->childlen && !ret) {
|
||||
- struct node *childnode;
|
||||
+ struct node *childnode = NULL;
|
||||
size_t childlen = strlen(node->children + i);
|
||||
- char * childname = child_name(NULL, node->name,
|
||||
- node->children + i);
|
||||
+ char *childname = child_name(NULL, node->name,
|
||||
+ node->children + i);
|
||||
|
||||
if (!childname) {
|
||||
log("check_store: ENOMEM");
|
||||
ret = ENOMEM;
|
||||
break;
|
||||
}
|
||||
+
|
||||
+ if (hashtable_search(reachable, childname)) {
|
||||
+ log("check_store: '%s' is duplicated!",
|
||||
+ childname);
|
||||
+ i = rm_child_entry(node, i, childlen);
|
||||
+ goto next;
|
||||
+ }
|
||||
+
|
||||
childnode = read_node(NULL, childname, childname);
|
||||
-
|
||||
+
|
||||
if (childnode) {
|
||||
- if (hashtable_search(children, childname)) {
|
||||
- log("check_store: '%s' is duplicated!",
|
||||
- childname);
|
||||
- i = rm_child_entry(node, i, childlen);
|
||||
- }
|
||||
- else {
|
||||
- if (!remember_string(children,
|
||||
- childname)) {
|
||||
- log("check_store: ENOMEM");
|
||||
- talloc_free(childnode);
|
||||
- talloc_free(childname);
|
||||
- ret = ENOMEM;
|
||||
- break;
|
||||
- }
|
||||
- ret = check_store_(childname,
|
||||
- reachable);
|
||||
- }
|
||||
+ ret = check_store_(childname, reachable);
|
||||
} else if (errno != ENOMEM) {
|
||||
log("check_store: No child '%s' found!\n",
|
||||
childname);
|
||||
@@ -2530,19 +2514,18 @@ static int check_store_(const char *name, struct hashtable *reachable)
|
||||
ret = ENOMEM;
|
||||
}
|
||||
|
||||
+ next:
|
||||
talloc_free(childnode);
|
||||
talloc_free(childname);
|
||||
i += childlen + 1;
|
||||
}
|
||||
|
||||
- hashtable_destroy(children, 0 /* Don't free values (they are
|
||||
- all (void *)1) */);
|
||||
talloc_free(node);
|
||||
} else if (errno != ENOMEM) {
|
||||
/* Impossible, because no database should ever be without the
|
||||
root, and otherwise, we've just checked in our caller
|
||||
(which made a recursive call to get here). */
|
||||
-
|
||||
+
|
||||
log("check_store: No child '%s' found: impossible!", name);
|
||||
} else {
|
||||
log("check_store: ENOMEM");
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
From 27817f0a7d6802be04e8f43a0900b02f881b28b2 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:12 +0200
|
||||
Subject: tools/xenstore: use treewalk for check_store()
|
||||
|
||||
Instead of doing an open tree walk using call recursion, use
|
||||
walk_node_tree() when checking the store for inconsistencies.
|
||||
|
||||
This will reduce code size and avoid many nesting levels of function
|
||||
calls which could potentially exhaust the stack.
|
||||
|
||||
This is part of XSA-418 / CVE-2022-42321.
|
||||
|
||||
Reported-by: Julien Grall <dvrabel@amazon.co.uk>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 760f3c16c794..efdd1888fd78 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -2444,18 +2444,6 @@ int remember_string(struct hashtable *hash, const char *str)
|
||||
return hashtable_insert(hash, k, (void *)1);
|
||||
}
|
||||
|
||||
-static int rm_child_entry(struct node *node, size_t off, size_t len)
|
||||
-{
|
||||
- if (!recovery)
|
||||
- return off;
|
||||
-
|
||||
- if (remove_child_entry(NULL, node, off))
|
||||
- log("check_store: child entry could not be removed from '%s'",
|
||||
- node->name);
|
||||
-
|
||||
- return off - len - 1;
|
||||
-}
|
||||
-
|
||||
/**
|
||||
* A node has a children field that names the children of the node, separated
|
||||
* by NULs. We check whether there are entries in there that are duplicated
|
||||
@@ -2469,70 +2457,29 @@ static int rm_child_entry(struct node *node, size_t off, size_t len)
|
||||
* As we go, we record each node in the given reachable hashtable. These
|
||||
* entries will be used later in clean_store.
|
||||
*/
|
||||
-static int check_store_(const char *name, struct hashtable *reachable)
|
||||
+static int check_store_step(const void *ctx, struct connection *conn,
|
||||
+ struct node *node, void *arg)
|
||||
{
|
||||
- struct node *node = read_node(NULL, name, name);
|
||||
- int ret = 0;
|
||||
-
|
||||
- if (node) {
|
||||
- size_t i = 0;
|
||||
-
|
||||
- if (!remember_string(reachable, name)) {
|
||||
- log("check_store: ENOMEM");
|
||||
- return ENOMEM;
|
||||
- }
|
||||
-
|
||||
- while (i < node->childlen && !ret) {
|
||||
- struct node *childnode = NULL;
|
||||
- size_t childlen = strlen(node->children + i);
|
||||
- char *childname = child_name(NULL, node->name,
|
||||
- node->children + i);
|
||||
-
|
||||
- if (!childname) {
|
||||
- log("check_store: ENOMEM");
|
||||
- ret = ENOMEM;
|
||||
- break;
|
||||
- }
|
||||
+ struct hashtable *reachable = arg;
|
||||
|
||||
- if (hashtable_search(reachable, childname)) {
|
||||
- log("check_store: '%s' is duplicated!",
|
||||
- childname);
|
||||
- i = rm_child_entry(node, i, childlen);
|
||||
- goto next;
|
||||
- }
|
||||
-
|
||||
- childnode = read_node(NULL, childname, childname);
|
||||
-
|
||||
- if (childnode) {
|
||||
- ret = check_store_(childname, reachable);
|
||||
- } else if (errno != ENOMEM) {
|
||||
- log("check_store: No child '%s' found!\n",
|
||||
- childname);
|
||||
- i = rm_child_entry(node, i, childlen);
|
||||
- } else {
|
||||
- log("check_store: ENOMEM");
|
||||
- ret = ENOMEM;
|
||||
- }
|
||||
+ if (hashtable_search(reachable, (void *)node->name)) {
|
||||
+ log("check_store: '%s' is duplicated!", node->name);
|
||||
+ return recovery ? WALK_TREE_RM_CHILDENTRY
|
||||
+ : WALK_TREE_SKIP_CHILDREN;
|
||||
+ }
|
||||
|
||||
- next:
|
||||
- talloc_free(childnode);
|
||||
- talloc_free(childname);
|
||||
- i += childlen + 1;
|
||||
- }
|
||||
+ if (!remember_string(reachable, node->name))
|
||||
+ return WALK_TREE_ERROR_STOP;
|
||||
|
||||
- talloc_free(node);
|
||||
- } else if (errno != ENOMEM) {
|
||||
- /* Impossible, because no database should ever be without the
|
||||
- root, and otherwise, we've just checked in our caller
|
||||
- (which made a recursive call to get here). */
|
||||
+ return WALK_TREE_OK;
|
||||
+}
|
||||
|
||||
- log("check_store: No child '%s' found: impossible!", name);
|
||||
- } else {
|
||||
- log("check_store: ENOMEM");
|
||||
- ret = ENOMEM;
|
||||
- }
|
||||
+static int check_store_enoent(const void *ctx, struct connection *conn,
|
||||
+ struct node *parent, char *name, void *arg)
|
||||
+{
|
||||
+ log("check_store: node '%s' not found", name);
|
||||
|
||||
- return ret;
|
||||
+ return recovery ? WALK_TREE_RM_CHILDENTRY : WALK_TREE_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -2581,24 +2528,28 @@ static void clean_store(struct hashtable *reachable)
|
||||
|
||||
void check_store(void)
|
||||
{
|
||||
- char * root = talloc_strdup(NULL, "/");
|
||||
- struct hashtable * reachable =
|
||||
- create_hashtable(16, hash_from_key_fn, keys_equal_fn);
|
||||
-
|
||||
+ struct hashtable *reachable;
|
||||
+ struct walk_funcs walkfuncs = {
|
||||
+ .enter = check_store_step,
|
||||
+ .enoent = check_store_enoent,
|
||||
+ };
|
||||
+
|
||||
+ reachable = create_hashtable(16, hash_from_key_fn, keys_equal_fn);
|
||||
if (!reachable) {
|
||||
log("check_store: ENOMEM");
|
||||
return;
|
||||
}
|
||||
|
||||
log("Checking store ...");
|
||||
- if (!check_store_(root, reachable) &&
|
||||
- !check_transactions(reachable))
|
||||
+ if (walk_node_tree(NULL, NULL, "/", &walkfuncs, reachable)) {
|
||||
+ if (errno == ENOMEM)
|
||||
+ log("check_store: ENOMEM");
|
||||
+ } else if (!check_transactions(reachable))
|
||||
clean_store(reachable);
|
||||
log("Checking store complete.");
|
||||
|
||||
hashtable_destroy(reachable, 0 /* Don't free values (they are all
|
||||
(void *)1) */);
|
||||
- talloc_free(root);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
From 6ea0ffbd88b11f23779d763501ec1370b590bb2a Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:12 +0200
|
||||
Subject: tools/xenstore: use treewalk for deleting nodes
|
||||
|
||||
Instead of doing an open tree walk using call recursion, use
|
||||
walk_node_tree() when deleting a sub-tree of nodes.
|
||||
|
||||
This will reduce code size and avoid many nesting levels of function
|
||||
calls which could potentially exhaust the stack.
|
||||
|
||||
This is part of XSA-418 / CVE-2022-42321.
|
||||
|
||||
Reported-by: Julien Grall <dvrabel@amazon.co.uk>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index efdd1888fd78..58fb651542ec 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -1334,21 +1334,6 @@ static int do_read(const void *ctx, struct connection *conn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static void delete_node_single(struct connection *conn, struct node *node)
|
||||
-{
|
||||
- TDB_DATA key;
|
||||
-
|
||||
- if (access_node(conn, node, NODE_ACCESS_DELETE, &key))
|
||||
- return;
|
||||
-
|
||||
- if (do_tdb_delete(conn, &key, &node->acc) != 0) {
|
||||
- corrupt(conn, "Could not delete '%s'", node->name);
|
||||
- return;
|
||||
- }
|
||||
-
|
||||
- domain_entry_dec(conn, node);
|
||||
-}
|
||||
-
|
||||
/* Must not be / */
|
||||
static char *basename(const char *name)
|
||||
{
|
||||
@@ -1619,69 +1604,59 @@ static int remove_child_entry(struct connection *conn, struct node *node,
|
||||
return write_node(conn, node, true);
|
||||
}
|
||||
|
||||
-static void delete_child(struct connection *conn,
|
||||
- struct node *node, const char *childname)
|
||||
+static int 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)) {
|
||||
- if (remove_child_entry(conn, node, i))
|
||||
- corrupt(conn, "Can't update parent node '%s'",
|
||||
- node->name);
|
||||
- return;
|
||||
+ errno = remove_child_entry(conn, node, i) ? EIO : 0;
|
||||
+ return errno;
|
||||
}
|
||||
}
|
||||
corrupt(conn, "Can't find child '%s' in %s", childname, node->name);
|
||||
+
|
||||
+ errno = EIO;
|
||||
+ return errno;
|
||||
}
|
||||
|
||||
-static int delete_node(struct connection *conn, const void *ctx,
|
||||
- struct node *parent, struct node *node, bool watch_exact)
|
||||
+static int delnode_sub(const void *ctx, struct connection *conn,
|
||||
+ struct node *node, void *arg)
|
||||
{
|
||||
- char *name;
|
||||
+ const char *root = arg;
|
||||
+ bool watch_exact;
|
||||
+ int ret;
|
||||
+ TDB_DATA key;
|
||||
|
||||
- /* Delete children. */
|
||||
- while (node->childlen) {
|
||||
- struct node *child;
|
||||
+ /* Any error here will probably be repeated for all following calls. */
|
||||
+ ret = access_node(conn, node, NODE_ACCESS_DELETE, &key);
|
||||
+ if (ret > 0)
|
||||
+ return WALK_TREE_SUCCESS_STOP;
|
||||
|
||||
- name = talloc_asprintf(node, "%s/%s", node->name,
|
||||
- node->children);
|
||||
- child = name ? read_node(conn, node, name) : NULL;
|
||||
- if (child) {
|
||||
- if (delete_node(conn, ctx, node, child, true))
|
||||
- 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);
|
||||
- }
|
||||
+ /* In case of error stop the walk. */
|
||||
+ if (!ret && do_tdb_delete(conn, &key, &node->acc))
|
||||
+ return WALK_TREE_SUCCESS_STOP;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
- */
|
||||
+ */
|
||||
+ watch_exact = strcmp(root, node->name);
|
||||
fire_watches(conn, ctx, node->name, node, watch_exact, NULL);
|
||||
- delete_node_single(conn, node);
|
||||
- delete_child(conn, parent, basename(node->name));
|
||||
- talloc_free(node);
|
||||
|
||||
- return 0;
|
||||
+ domain_entry_dec(conn, node);
|
||||
+
|
||||
+ return WALK_TREE_RM_CHILDENTRY;
|
||||
}
|
||||
|
||||
-static int _rm(struct connection *conn, const void *ctx, struct node *node,
|
||||
- const char *name)
|
||||
+static int _rm(struct connection *conn, const void *ctx, const char *name)
|
||||
{
|
||||
- /*
|
||||
- * 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);
|
||||
+ struct walk_funcs walkfuncs = { .exit = delnode_sub };
|
||||
+ int ret;
|
||||
|
||||
if (!parentname)
|
||||
return errno;
|
||||
@@ -1689,9 +1664,21 @@ static int _rm(struct connection *conn, const void *ctx, struct node *node,
|
||||
parent = read_node(conn, ctx, parentname);
|
||||
if (!parent)
|
||||
return read_node_can_propagate_errno() ? errno : EINVAL;
|
||||
- node->parent = parent;
|
||||
|
||||
- return delete_node(conn, ctx, parent, node, false);
|
||||
+ ret = walk_node_tree(ctx, conn, name, &walkfuncs, (void *)name);
|
||||
+ if (ret < 0) {
|
||||
+ if (ret == WALK_TREE_ERROR_STOP) {
|
||||
+ corrupt(conn, "error when deleting sub-nodes of %s\n",
|
||||
+ name);
|
||||
+ errno = EIO;
|
||||
+ }
|
||||
+ return errno;
|
||||
+ }
|
||||
+
|
||||
+ if (delete_child(conn, parent, basename(name)))
|
||||
+ return errno;
|
||||
+
|
||||
+ return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1728,7 +1715,7 @@ static int do_rm(const void *ctx, struct connection *conn,
|
||||
if (streq(name, "/"))
|
||||
return EINVAL;
|
||||
|
||||
- ret = _rm(conn, ctx, node, name);
|
||||
+ ret = _rm(conn, ctx, name);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
From 1ee281b18b52bec87335ea64ee74cc159e63d036 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:12 +0200
|
||||
Subject: tools/xenstore: use treewalk for creating node records
|
||||
|
||||
Instead of doing an open tree walk using call recursion, use
|
||||
walk_node_tree() when creating the node records during a live update.
|
||||
|
||||
This will reduce code size and avoid many nesting levels of function
|
||||
calls which could potentially exhaust the stack.
|
||||
|
||||
This is part of XSA-418 / CVE-2022-42321.
|
||||
|
||||
Reported-by: Julien Grall <dvrabel@amazon.co.uk>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 58fb651542ec..05d349778bb4 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -3120,101 +3120,76 @@ const char *dump_state_node_perms(FILE *fp, const struct xs_permissions *perms,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
-static const char *dump_state_node_tree(FILE *fp, char *path,
|
||||
- unsigned int path_max_len)
|
||||
+struct dump_node_data {
|
||||
+ FILE *fp;
|
||||
+ const char *err;
|
||||
+};
|
||||
+
|
||||
+static int dump_state_node_err(struct dump_node_data *data, const char *err)
|
||||
{
|
||||
- unsigned int pathlen, childlen, p = 0;
|
||||
+ data->err = err;
|
||||
+ return WALK_TREE_ERROR_STOP;
|
||||
+}
|
||||
+
|
||||
+static int dump_state_node(const void *ctx, struct connection *conn,
|
||||
+ struct node *node, void *arg)
|
||||
+{
|
||||
+ struct dump_node_data *data = arg;
|
||||
+ FILE *fp = data->fp;
|
||||
+ unsigned int pathlen;
|
||||
struct xs_state_record_header head;
|
||||
struct xs_state_node sn;
|
||||
- TDB_DATA key, data;
|
||||
- const struct xs_tdb_record_hdr *hdr;
|
||||
- const char *child;
|
||||
const char *ret;
|
||||
|
||||
- pathlen = strlen(path) + 1;
|
||||
-
|
||||
- set_tdb_key(path, &key);
|
||||
- data = tdb_fetch(tdb_ctx, key);
|
||||
- if (data.dptr == NULL)
|
||||
- return "Error reading node";
|
||||
-
|
||||
- /* Clean up in case of failure. */
|
||||
- talloc_steal(path, data.dptr);
|
||||
-
|
||||
- hdr = (void *)data.dptr;
|
||||
+ pathlen = strlen(node->name) + 1;
|
||||
|
||||
head.type = XS_STATE_TYPE_NODE;
|
||||
head.length = sizeof(sn);
|
||||
sn.conn_id = 0;
|
||||
sn.ta_id = 0;
|
||||
sn.ta_access = 0;
|
||||
- sn.perm_n = hdr->num_perms;
|
||||
+ sn.perm_n = node->perms.num;
|
||||
sn.path_len = pathlen;
|
||||
- sn.data_len = hdr->datalen;
|
||||
- head.length += hdr->num_perms * sizeof(*sn.perms);
|
||||
+ sn.data_len = node->datalen;
|
||||
+ head.length += node->perms.num * sizeof(*sn.perms);
|
||||
head.length += pathlen;
|
||||
- head.length += hdr->datalen;
|
||||
+ head.length += node->datalen;
|
||||
head.length = ROUNDUP(head.length, 3);
|
||||
|
||||
if (fwrite(&head, sizeof(head), 1, fp) != 1)
|
||||
- return "Dump node state error";
|
||||
+ return dump_state_node_err(data, "Dump node head error");
|
||||
if (fwrite(&sn, sizeof(sn), 1, fp) != 1)
|
||||
- return "Dump node state error";
|
||||
+ return dump_state_node_err(data, "Dump node state error");
|
||||
|
||||
- ret = dump_state_node_perms(fp, hdr->perms, hdr->num_perms);
|
||||
+ ret = dump_state_node_perms(fp, node->perms.p, node->perms.num);
|
||||
if (ret)
|
||||
- return ret;
|
||||
+ return dump_state_node_err(data, ret);
|
||||
+
|
||||
+ if (fwrite(node->name, pathlen, 1, fp) != 1)
|
||||
+ return dump_state_node_err(data, "Dump node path error");
|
||||
|
||||
- if (fwrite(path, pathlen, 1, fp) != 1)
|
||||
- return "Dump node path error";
|
||||
- if (hdr->datalen &&
|
||||
- fwrite(hdr->perms + hdr->num_perms, hdr->datalen, 1, fp) != 1)
|
||||
- return "Dump node data error";
|
||||
+ if (node->datalen && fwrite(node->data, node->datalen, 1, fp) != 1)
|
||||
+ return dump_state_node_err(data, "Dump node data error");
|
||||
|
||||
ret = dump_state_align(fp);
|
||||
if (ret)
|
||||
- return ret;
|
||||
+ return dump_state_node_err(data, ret);
|
||||
|
||||
- child = (char *)(hdr->perms + hdr->num_perms) + hdr->datalen;
|
||||
-
|
||||
- /*
|
||||
- * Use path for constructing children paths.
|
||||
- * As we don't write out nodes without having written their parent
|
||||
- * already we will never clobber a part of the path we'll need later.
|
||||
- */
|
||||
- pathlen--;
|
||||
- if (path[pathlen - 1] != '/') {
|
||||
- path[pathlen] = '/';
|
||||
- pathlen++;
|
||||
- }
|
||||
- while (p < hdr->childlen) {
|
||||
- childlen = strlen(child) + 1;
|
||||
- if (pathlen + childlen > path_max_len)
|
||||
- return "Dump node path length error";
|
||||
- strcpy(path + pathlen, child);
|
||||
- ret = dump_state_node_tree(fp, path, path_max_len);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
- p += childlen;
|
||||
- child += childlen;
|
||||
- }
|
||||
-
|
||||
- talloc_free(data.dptr);
|
||||
-
|
||||
- return NULL;
|
||||
+ return WALK_TREE_OK;
|
||||
}
|
||||
|
||||
const char *dump_state_nodes(FILE *fp, const void *ctx)
|
||||
{
|
||||
- char *path;
|
||||
-
|
||||
- path = talloc_size(ctx, XENSTORE_ABS_PATH_MAX + 1);
|
||||
- if (!path)
|
||||
- return "Path buffer allocation error";
|
||||
+ struct dump_node_data data = {
|
||||
+ .fp = fp,
|
||||
+ .err = "Dump node walk error"
|
||||
+ };
|
||||
+ struct walk_funcs walkfuncs = { .enter = dump_state_node };
|
||||
|
||||
- strcpy(path, "/");
|
||||
+ if (walk_node_tree(ctx, NULL, "/", &walkfuncs, &data))
|
||||
+ return data.err;
|
||||
|
||||
- return dump_state_node_tree(fp, path, XENSTORE_ABS_PATH_MAX + 1);
|
||||
+ return NULL;
|
||||
}
|
||||
|
||||
void read_state_global(const void *ctx, const void *state)
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
From 09228369a549427294febe351372d7227e624da1 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok@citrix.com>
|
||||
Date: Wed, 12 Oct 2022 19:13:06 +0100
|
||||
Subject: tools/ocaml/xenstored: Fix quota bypass on domain shutdown
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
XSA-322 fixed a domid reuse vulnerability by assigning Dom0 as the owner of
|
||||
any nodes left after a domain is shutdown (e.g. outside its /local/domain/N
|
||||
tree).
|
||||
|
||||
However Dom0 has no quota on purpose, so this opened up another potential
|
||||
attack vector. Avoid it by deleting these nodes instead of assigning them to
|
||||
Dom0.
|
||||
|
||||
This is part of XSA-419 / CVE-2022-42323.
|
||||
|
||||
Reported-by: Juergen Gross <jgross@suse.com>
|
||||
Fixes: c46eff921209 ("tools/ocaml/xenstored: clean up permissions for dead domains")
|
||||
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
|
||||
diff --git a/tools/ocaml/xenstored/perms.ml b/tools/ocaml/xenstored/perms.ml
|
||||
index e8a16221f8fa..84f2503e8e29 100644
|
||||
--- a/tools/ocaml/xenstored/perms.ml
|
||||
+++ b/tools/ocaml/xenstored/perms.ml
|
||||
@@ -64,8 +64,7 @@ let get_owner perm = perm.owner
|
||||
* *)
|
||||
let remove_domid ~domid perm =
|
||||
let acl = List.filter (fun (acl_domid, _) -> acl_domid <> domid) perm.acl in
|
||||
- let owner = if perm.owner = domid then 0 else perm.owner in
|
||||
- { perm with acl; owner }
|
||||
+ if perm.owner = domid then None else Some { perm with acl; owner = perm.owner }
|
||||
|
||||
let default0 = create 0 NONE []
|
||||
|
||||
diff --git a/tools/ocaml/xenstored/store.ml b/tools/ocaml/xenstored/store.ml
|
||||
index 20e67b142746..70f0c83de404 100644
|
||||
--- a/tools/ocaml/xenstored/store.ml
|
||||
+++ b/tools/ocaml/xenstored/store.ml
|
||||
@@ -87,10 +87,21 @@ let check_owner node connection =
|
||||
|
||||
let rec recurse fct node = fct node; SymbolMap.iter (fun _ -> recurse fct) node.children
|
||||
|
||||
-(** [recurse_map f tree] applies [f] on each node in the tree recursively *)
|
||||
-let recurse_map f =
|
||||
+(** [recurse_filter_map f tree] applies [f] on each node in the tree recursively,
|
||||
+ possibly removing some nodes.
|
||||
+ Note that the nodes removed this way won't generate watch events.
|
||||
+*)
|
||||
+let recurse_filter_map f =
|
||||
+ let invalid = -1 in
|
||||
+ let is_valid _ node = node.perms.owner <> invalid in
|
||||
let rec walk node =
|
||||
- f { node with children = SymbolMap.map walk node.children }
|
||||
+ (* Map.filter_map is Ocaml 4.11+ only *)
|
||||
+ let node =
|
||||
+ { node with children =
|
||||
+ SymbolMap.map walk node.children |> SymbolMap.filter is_valid } in
|
||||
+ match f node with
|
||||
+ | Some keep -> keep
|
||||
+ | None -> { node with perms = {node.perms with owner = invalid } }
|
||||
in
|
||||
walk
|
||||
|
||||
@@ -444,11 +455,13 @@ let setperms store perm path nperms =
|
||||
|
||||
let reset_permissions store domid =
|
||||
Logging.info "store|node" "Cleaning up xenstore ACLs for domid %d" domid;
|
||||
- store.root <- Node.recurse_map (fun node ->
|
||||
- let perms = Perms.Node.remove_domid ~domid node.perms in
|
||||
- if perms <> node.perms then
|
||||
- Logging.debug "store|node" "Changed permissions for node %s" (Node.get_name node);
|
||||
- { node with perms }
|
||||
+ store.root <- Node.recurse_filter_map (fun node ->
|
||||
+ match Perms.Node.remove_domid ~domid node.perms with
|
||||
+ | None -> None
|
||||
+ | Some perms ->
|
||||
+ if perms <> node.perms then
|
||||
+ Logging.debug "store|node" "Changed permissions for node %s" (Node.get_name node);
|
||||
+ Some { node with perms }
|
||||
) store.root
|
||||
|
||||
type ops = {
|
||||
|
|
@ -1,289 +0,0 @@
|
|||
From e4250bf8b39ed73623c75b0f1436ac7c1d45aba7 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:12 +0200
|
||||
Subject: tools/xenstore: remove nodes owned by destroyed domain
|
||||
|
||||
In case a domain is removed from Xenstore, remove all nodes owned by
|
||||
it per default.
|
||||
|
||||
This tackles the problem that nodes might be created by a domain
|
||||
outside its home path in Xenstore, leading to Xenstore hogging more
|
||||
and more memory. Domain quota don't work in this case if the guest is
|
||||
rebooting in between.
|
||||
|
||||
Since XSA-322 ownership of such stale nodes is transferred to dom0,
|
||||
which is helping against unintended access, but not against OOM of
|
||||
Xenstore.
|
||||
|
||||
As a fallback for weird cases add a Xenstore start parameter for
|
||||
keeping today's way to handle stale nodes, adding the risk of Xenstore
|
||||
hitting an OOM situation.
|
||||
|
||||
This is part of XSA-419 / CVE-2022-42322.
|
||||
|
||||
Reported-by: Juergen Gross <jgross@suse.com>
|
||||
Fixes: 496306324d8d ("tools/xenstore: revoke access rights for removed domains")
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index bdc14679adf5..13e48aaa731c 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -80,6 +80,7 @@ static bool verbose = false;
|
||||
LIST_HEAD(connections);
|
||||
int tracefd = -1;
|
||||
static bool recovery = true;
|
||||
+bool keep_orphans = false;
|
||||
static int reopen_log_pipe[2];
|
||||
static int reopen_log_pipe0_pollfd_idx = -1;
|
||||
char *tracefile = NULL;
|
||||
@@ -753,7 +754,7 @@ struct node *read_node(struct connection *conn, const void *ctx,
|
||||
node->perms.p = hdr->perms;
|
||||
node->acc.domid = node->perms.p[0].id;
|
||||
node->acc.memory = data.dsize;
|
||||
- if (domain_adjust_node_perms(conn, node))
|
||||
+ if (domain_adjust_node_perms(node))
|
||||
goto error;
|
||||
|
||||
/* If owner is gone reset currently accounted memory size. */
|
||||
@@ -796,7 +797,7 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
|
||||
void *p;
|
||||
struct xs_tdb_record_hdr *hdr;
|
||||
|
||||
- if (domain_adjust_node_perms(conn, node))
|
||||
+ if (domain_adjust_node_perms(node))
|
||||
return errno;
|
||||
|
||||
data.dsize = sizeof(*hdr)
|
||||
@@ -1647,7 +1648,7 @@ static int delnode_sub(const void *ctx, struct connection *conn,
|
||||
return WALK_TREE_RM_CHILDENTRY;
|
||||
}
|
||||
|
||||
-static int _rm(struct connection *conn, const void *ctx, const char *name)
|
||||
+int rm_node(struct connection *conn, const void *ctx, const char *name)
|
||||
{
|
||||
struct node *parent;
|
||||
char *parentname = get_parent(ctx, name);
|
||||
@@ -1711,7 +1712,7 @@ static int do_rm(const void *ctx, struct connection *conn,
|
||||
if (streq(name, "/"))
|
||||
return EINVAL;
|
||||
|
||||
- ret = _rm(conn, ctx, name);
|
||||
+ ret = rm_node(conn, ctx, name);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -2618,6 +2619,8 @@ static void usage(void)
|
||||
" -R, --no-recovery to request that no recovery should be attempted when\n"
|
||||
" the store is corrupted (debug only),\n"
|
||||
" -I, --internal-db store database in memory, not on disk\n"
|
||||
+" -K, --keep-orphans don't delete nodes owned by a domain when the\n"
|
||||
+" domain is deleted (this is a security risk!)\n"
|
||||
" -V, --verbose to request verbose execution.\n");
|
||||
}
|
||||
|
||||
@@ -2642,6 +2645,7 @@ static struct option options[] = {
|
||||
{ "timeout", 1, NULL, 'w' },
|
||||
{ "no-recovery", 0, NULL, 'R' },
|
||||
{ "internal-db", 0, NULL, 'I' },
|
||||
+ { "keep-orphans", 0, NULL, 'K' },
|
||||
{ "verbose", 0, NULL, 'V' },
|
||||
{ "watch-nb", 1, NULL, 'W' },
|
||||
#ifndef NO_LIVE_UPDATE
|
||||
@@ -2721,7 +2725,7 @@ int main(int argc, char *argv[])
|
||||
orig_argc = argc;
|
||||
orig_argv = argv;
|
||||
|
||||
- while ((opt = getopt_long(argc, argv, "DE:F:HNPS:t:A:M:Q:q:T:RVW:w:U",
|
||||
+ while ((opt = getopt_long(argc, argv, "DE:F:HKNPS:t:A:M:Q:q:T:RVW:w:U",
|
||||
options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'D':
|
||||
@@ -2757,6 +2761,9 @@ int main(int argc, char *argv[])
|
||||
case 'I':
|
||||
tdb_flags = TDB_INTERNAL|TDB_NOLOCK;
|
||||
break;
|
||||
+ case 'K':
|
||||
+ keep_orphans = true;
|
||||
+ break;
|
||||
case 'V':
|
||||
verbose = true;
|
||||
break;
|
||||
diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h
|
||||
index acb00ad96914..37006d508dbf 100644
|
||||
--- a/tools/xenstore/xenstored_core.h
|
||||
+++ b/tools/xenstore/xenstored_core.h
|
||||
@@ -240,6 +240,9 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
|
||||
struct node *read_node(struct connection *conn, const void *ctx,
|
||||
const char *name);
|
||||
|
||||
+/* Remove a node and its children. */
|
||||
+int rm_node(struct connection *conn, const void *ctx, const char *name);
|
||||
+
|
||||
void setup_structure(bool live_update);
|
||||
struct connection *new_connection(const struct interface_funcs *funcs);
|
||||
struct connection *get_connection_by_id(unsigned int conn_id);
|
||||
@@ -284,6 +287,7 @@ extern int quota_req_outstanding;
|
||||
extern int quota_trans_nodes;
|
||||
extern int quota_memory_per_domain_soft;
|
||||
extern int quota_memory_per_domain_hard;
|
||||
+extern bool keep_orphans;
|
||||
|
||||
extern unsigned int timeout_watch_event_msec;
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index 98b401fdec30..84b7817cd5e6 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -227,10 +227,64 @@ static void unmap_interface(void *interface)
|
||||
xengnttab_unmap(*xgt_handle, interface, 1);
|
||||
}
|
||||
|
||||
+static int domain_tree_remove_sub(const void *ctx, struct connection *conn,
|
||||
+ struct node *node, void *arg)
|
||||
+{
|
||||
+ struct domain *domain = arg;
|
||||
+ TDB_DATA key;
|
||||
+ int ret = WALK_TREE_OK;
|
||||
+
|
||||
+ if (node->perms.p[0].id != domain->domid)
|
||||
+ return WALK_TREE_OK;
|
||||
+
|
||||
+ if (keep_orphans) {
|
||||
+ set_tdb_key(node->name, &key);
|
||||
+ domain->nbentry--;
|
||||
+ node->perms.p[0].id = priv_domid;
|
||||
+ node->acc.memory = 0;
|
||||
+ domain_entry_inc(NULL, node);
|
||||
+ if (write_node_raw(NULL, &key, node, true)) {
|
||||
+ /* That's unfortunate. We only can try to continue. */
|
||||
+ syslog(LOG_ERR,
|
||||
+ "error when moving orphaned node %s to dom0\n",
|
||||
+ node->name);
|
||||
+ } else
|
||||
+ trace("orphaned node %s moved to dom0\n", node->name);
|
||||
+ } else {
|
||||
+ if (rm_node(NULL, ctx, node->name)) {
|
||||
+ /* That's unfortunate. We only can try to continue. */
|
||||
+ syslog(LOG_ERR,
|
||||
+ "error when deleting orphaned node %s\n",
|
||||
+ node->name);
|
||||
+ } else
|
||||
+ trace("orphaned node %s deleted\n", node->name);
|
||||
+
|
||||
+ /* Skip children in all cases in order to avoid more errors. */
|
||||
+ ret = WALK_TREE_SKIP_CHILDREN;
|
||||
+ }
|
||||
+
|
||||
+ return domain->nbentry > 0 ? ret : WALK_TREE_SUCCESS_STOP;
|
||||
+}
|
||||
+
|
||||
+static void domain_tree_remove(struct domain *domain)
|
||||
+{
|
||||
+ int ret;
|
||||
+ struct walk_funcs walkfuncs = { .enter = domain_tree_remove_sub };
|
||||
+
|
||||
+ if (domain->nbentry > 0) {
|
||||
+ ret = walk_node_tree(domain, NULL, "/", &walkfuncs, domain);
|
||||
+ if (ret == WALK_TREE_ERROR_STOP)
|
||||
+ syslog(LOG_ERR,
|
||||
+ "error when looking for orphaned nodes\n");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
static int destroy_domain(void *_domain)
|
||||
{
|
||||
struct domain *domain = _domain;
|
||||
|
||||
+ domain_tree_remove(domain);
|
||||
+
|
||||
list_del(&domain->list);
|
||||
|
||||
if (!domain->introduced)
|
||||
@@ -883,15 +937,15 @@ int domain_entry_inc(struct connection *conn, struct node *node)
|
||||
struct domain *d;
|
||||
unsigned int domid;
|
||||
|
||||
- if (!conn)
|
||||
+ if (!node->perms.p)
|
||||
return 0;
|
||||
|
||||
- domid = node->perms.p ? node->perms.p[0].id : conn->id;
|
||||
+ domid = node->perms.p[0].id;
|
||||
|
||||
- if (conn->transaction) {
|
||||
+ if (conn && conn->transaction) {
|
||||
transaction_entry_inc(conn->transaction, domid);
|
||||
} else {
|
||||
- d = (domid == conn->id && conn->domain) ? conn->domain
|
||||
+ d = (conn && domid == conn->id && conn->domain) ? conn->domain
|
||||
: find_or_alloc_existing_domain(domid);
|
||||
if (d)
|
||||
d->nbentry++;
|
||||
@@ -952,23 +1006,11 @@ int domain_alloc_permrefs(struct node_perms *perms)
|
||||
* Remove permissions for no longer existing domains in order to avoid a new
|
||||
* domain with the same domid inheriting the permissions.
|
||||
*/
|
||||
-int domain_adjust_node_perms(struct connection *conn, struct node *node)
|
||||
+int domain_adjust_node_perms(struct node *node)
|
||||
{
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
- ret = chk_domain_generation(node->perms.p[0].id, node->generation);
|
||||
-
|
||||
- /* If the owner doesn't exist any longer give it to priv domain. */
|
||||
- if (!ret) {
|
||||
- /*
|
||||
- * In theory we'd need to update the number of dom0 nodes here,
|
||||
- * but we could be called for a read of the node. So better
|
||||
- * avoid the risk to overflow the node count of dom0.
|
||||
- */
|
||||
- node->perms.p[0].id = priv_domid;
|
||||
- }
|
||||
-
|
||||
for (i = 1; i < node->perms.num; i++) {
|
||||
if (node->perms.p[i].perms & XS_PERM_IGNORE)
|
||||
continue;
|
||||
@@ -986,15 +1028,15 @@ void domain_entry_dec(struct connection *conn, struct node *node)
|
||||
struct domain *d;
|
||||
unsigned int domid;
|
||||
|
||||
- if (!conn)
|
||||
+ if (!node->perms.p)
|
||||
return;
|
||||
|
||||
domid = node->perms.p ? node->perms.p[0].id : conn->id;
|
||||
|
||||
- if (conn->transaction) {
|
||||
+ if (conn && conn->transaction) {
|
||||
transaction_entry_dec(conn->transaction, domid);
|
||||
} else {
|
||||
- d = (domid == conn->id && conn->domain) ? conn->domain
|
||||
+ d = (conn && domid == conn->id && conn->domain) ? conn->domain
|
||||
: find_domain_struct(domid);
|
||||
if (d) {
|
||||
d->nbentry--;
|
||||
@@ -1113,7 +1155,7 @@ int domain_memory_add(unsigned int domid, int mem, bool no_quota_check)
|
||||
* exist, as accounting is done either for a domain related to
|
||||
* the current connection, or for the domain owning a node
|
||||
* (which is always existing, as the owner of the node is
|
||||
- * tested to exist and replaced by domid 0 if not).
|
||||
+ * tested to exist and deleted or replaced by domid 0 if not).
|
||||
* So not finding the related domain MUST be an error in the
|
||||
* data base.
|
||||
*/
|
||||
diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h
|
||||
index 7fe0a21d9e45..b38c82991dc6 100644
|
||||
--- a/tools/xenstore/xenstored_domain.h
|
||||
+++ b/tools/xenstore/xenstored_domain.h
|
||||
@@ -62,7 +62,7 @@ const char *get_implicit_path(const struct connection *conn);
|
||||
bool domain_is_unprivileged(struct connection *conn);
|
||||
|
||||
/* Remove node permissions for no longer existing domains. */
|
||||
-int domain_adjust_node_perms(struct connection *conn, struct node *node);
|
||||
+int domain_adjust_node_perms(struct node *node);
|
||||
int domain_alloc_permrefs(struct node_perms *perms);
|
||||
|
||||
/* Quota manipulation */
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
From 929da557efea6c7d2340467d9a7fdae7fda6d2b1 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:13 +0200
|
||||
Subject: tools/xenstore: make the internal memory data base the default
|
||||
|
||||
Having a file backed data base has the only advantage of being capable
|
||||
to dump the contents of it while Xenstore is running, and potentially
|
||||
using less swap space in case the data base can't be kept in memory.
|
||||
|
||||
It has the major disadvantage of a huge performance overhead: switching
|
||||
to keep the data base in memory only speeds up live update of xenstored
|
||||
with 120000 nodes from 20 minutes to 11 seconds. A complete tree walk
|
||||
of this configuration will be reduced from 7 seconds to 280 msecs
|
||||
(measured by "xenstore-control check").
|
||||
|
||||
So make the internal memory data base the default and enhance the
|
||||
"--internal-db" command line parameter to take an optional parameter
|
||||
allowing to switch the internal data base back to the file based one.
|
||||
|
||||
This is part of XSA-419.
|
||||
|
||||
Reported-by: Juergen Gross <jgross@suse.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/helpers/init-xenstore-domain.c b/tools/helpers/init-xenstore-domain.c
|
||||
index 2d9ab6f1c583..04e351ca29a8 100644
|
||||
--- a/tools/helpers/init-xenstore-domain.c
|
||||
+++ b/tools/helpers/init-xenstore-domain.c
|
||||
@@ -222,9 +222,9 @@ static int build(xc_interface *xch)
|
||||
}
|
||||
|
||||
if ( param )
|
||||
- snprintf(cmdline, 512, "--event %d --internal-db %s", rv, param);
|
||||
+ snprintf(cmdline, 512, "--event %d %s", rv, param);
|
||||
else
|
||||
- snprintf(cmdline, 512, "--event %d --internal-db", rv);
|
||||
+ snprintf(cmdline, 512, "--event %d", rv);
|
||||
|
||||
dom->guest_domid = domid;
|
||||
dom->cmdline = xc_dom_strdup(dom, cmdline);
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 13e48aaa731c..36fb4a832834 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -2308,7 +2308,7 @@ static void accept_connection(int sock)
|
||||
}
|
||||
#endif
|
||||
|
||||
-static int tdb_flags;
|
||||
+static int tdb_flags = TDB_INTERNAL | TDB_NOLOCK;
|
||||
|
||||
/* We create initial nodes manually. */
|
||||
static void manual_node(const char *name, const char *child)
|
||||
@@ -2618,7 +2618,8 @@ static void usage(void)
|
||||
" watch-event: time a watch-event is kept pending\n"
|
||||
" -R, --no-recovery to request that no recovery should be attempted when\n"
|
||||
" the store is corrupted (debug only),\n"
|
||||
-" -I, --internal-db store database in memory, not on disk\n"
|
||||
+" -I, --internal-db [on|off] store database in memory, not on disk, default is\n"
|
||||
+" memory, with \"--internal-db off\" it is on disk\n"
|
||||
" -K, --keep-orphans don't delete nodes owned by a domain when the\n"
|
||||
" domain is deleted (this is a security risk!)\n"
|
||||
" -V, --verbose to request verbose execution.\n");
|
||||
@@ -2644,7 +2645,7 @@ static struct option options[] = {
|
||||
{ "quota-soft", 1, NULL, 'q' },
|
||||
{ "timeout", 1, NULL, 'w' },
|
||||
{ "no-recovery", 0, NULL, 'R' },
|
||||
- { "internal-db", 0, NULL, 'I' },
|
||||
+ { "internal-db", 2, NULL, 'I' },
|
||||
{ "keep-orphans", 0, NULL, 'K' },
|
||||
{ "verbose", 0, NULL, 'V' },
|
||||
{ "watch-nb", 1, NULL, 'W' },
|
||||
@@ -2725,7 +2726,8 @@ int main(int argc, char *argv[])
|
||||
orig_argc = argc;
|
||||
orig_argv = argv;
|
||||
|
||||
- while ((opt = getopt_long(argc, argv, "DE:F:HKNPS:t:A:M:Q:q:T:RVW:w:U",
|
||||
+ while ((opt = getopt_long(argc, argv,
|
||||
+ "DE:F:HI::KNPS:t:A:M:Q:q:T:RVW:w:U",
|
||||
options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'D':
|
||||
@@ -2759,7 +2761,8 @@ int main(int argc, char *argv[])
|
||||
tracefile = optarg;
|
||||
break;
|
||||
case 'I':
|
||||
- tdb_flags = TDB_INTERNAL|TDB_NOLOCK;
|
||||
+ if (optarg && !strcmp(optarg, "off"))
|
||||
+ tdb_flags = 0;
|
||||
break;
|
||||
case 'K':
|
||||
keep_orphans = true;
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
From 54e63b7e7c42e4f975163809a01574e78552a6ab Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:13 +0200
|
||||
Subject: docs: enhance xenstore.txt with permissions description
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The permission scheme of Xenstore nodes is not really covered by
|
||||
docs/misc/xenstore.txt, other than referring to the Xen wiki.
|
||||
|
||||
Add a paragraph explaining the permissions of nodes, and especially
|
||||
mentioning removal of nodes when a domain has been removed from
|
||||
Xenstore.
|
||||
|
||||
This is part of XSA-419.
|
||||
|
||||
Reported-by: Juergen Gross <jgross@suse.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/docs/misc/xenstore.txt b/docs/misc/xenstore.txt
|
||||
index 988ef89cba2d..44428ae3a755 100644
|
||||
--- a/docs/misc/xenstore.txt
|
||||
+++ b/docs/misc/xenstore.txt
|
||||
@@ -43,6 +43,17 @@ bytes are forbidden; clients specifying relative paths should keep
|
||||
them to within 2048 bytes. (See XENSTORE_*_PATH_MAX in xs_wire.h.)
|
||||
|
||||
|
||||
+Each node has one or multiple permission entries. Permissions are
|
||||
+granted by domain-id, the first permission entry of each node specifies
|
||||
+the owner of the node. Permissions of a node can be changed by the
|
||||
+owner of the node, the owner can only be modified by the control
|
||||
+domain (usually domain id 0). The owner always has the right to read
|
||||
+and write the node, while other permissions can be setup to allow
|
||||
+read and/or write access. When a domain is being removed from Xenstore
|
||||
+nodes owned by that domain will be removed together with all of those
|
||||
+nodes' children.
|
||||
+
|
||||
+
|
||||
Communication with xenstore is via either sockets, or event channel
|
||||
and shared memory, as specified in io/xs_wire.h: each message in
|
||||
either direction is a header formatted as a struct xsd_sockmsg
|
||||
68
xsa420.patch
68
xsa420.patch
|
|
@ -1,68 +0,0 @@
|
|||
From 210879456769ca211c6630f47399ca7a61a37f35 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Edwin=20T=C3=B6r=C3=B6k?= <edvin.torok@citrix.com>
|
||||
Date: Wed, 12 Oct 2022 19:13:05 +0100
|
||||
Subject: tools/ocaml: Ensure packet size is never negative
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Integers in Ocaml have 63 or 31 bits of signed precision.
|
||||
|
||||
On 64-bit builds of Ocaml, this is fine because a C uint32_t always fits
|
||||
within a 63-bit signed integer.
|
||||
|
||||
In 32-bit builds of Ocaml, this goes wrong. The C uint32_t is truncated
|
||||
first (loses the top bit), then has a unsigned/signed mismatch.
|
||||
|
||||
A "negative" value (i.e. a packet on the ring of between 1G and 2G in size)
|
||||
will trigger an exception later in Bytes.make in xb.ml, and because the packet
|
||||
is not removed from the ring, the exception re-triggers on every subsequent
|
||||
query, creating a livelock.
|
||||
|
||||
Fix both the source of the exception in Xb, and as defence in depth, mark the
|
||||
domain as bad for any Invalid_argument exceptions to avoid the risk of
|
||||
livelock.
|
||||
|
||||
This is XSA-420 / CVE-2022-42324.
|
||||
|
||||
Reported-by: Juergen Gross <jgross@suse.com>
|
||||
Signed-off-by: Edwin Török <edvin.torok@citrix.com>
|
||||
Acked-by: Christian Lindig <christian.lindig@citrix.com>
|
||||
|
||||
diff --git a/tools/ocaml/libs/xb/partial.ml b/tools/ocaml/libs/xb/partial.ml
|
||||
index b6e2a716e263..3aa8927eb7f0 100644
|
||||
--- a/tools/ocaml/libs/xb/partial.ml
|
||||
+++ b/tools/ocaml/libs/xb/partial.ml
|
||||
@@ -36,7 +36,7 @@ let of_string s =
|
||||
This will leave the guest connection is a bad state and will
|
||||
be hard to recover from without restarting the connection
|
||||
(ie rebooting the guest) *)
|
||||
- let dlen = min xenstore_payload_max dlen in
|
||||
+ let dlen = max 0 (min xenstore_payload_max dlen) in
|
||||
{
|
||||
tid = tid;
|
||||
rid = rid;
|
||||
@@ -46,8 +46,8 @@ let of_string s =
|
||||
}
|
||||
|
||||
let append pkt s sz =
|
||||
- if pkt.len > 4096 then failwith "Buffer.add: cannot grow buffer";
|
||||
- Buffer.add_string pkt.buf (String.sub s 0 sz)
|
||||
+ if Buffer.length pkt.buf + sz > xenstore_payload_max then failwith "Buffer.add: cannot grow buffer";
|
||||
+ Buffer.add_substring pkt.buf s 0 sz
|
||||
|
||||
let to_complete pkt =
|
||||
pkt.len - (Buffer.length pkt.buf)
|
||||
diff --git a/tools/ocaml/xenstored/process.ml b/tools/ocaml/xenstored/process.ml
|
||||
index 5f439fe59f47..f3a71b24ad94 100644
|
||||
--- a/tools/ocaml/xenstored/process.ml
|
||||
+++ b/tools/ocaml/xenstored/process.ml
|
||||
@@ -722,7 +722,7 @@ let do_input store cons doms con =
|
||||
History.reconnect con;
|
||||
info "%s reconnection complete" (Connection.get_domstr con);
|
||||
None
|
||||
- | Failure exp ->
|
||||
+ | Invalid_argument exp | Failure exp ->
|
||||
error "caught exception %s" exp;
|
||||
error "got a bad client %s" (sprintf "%-8s" (Connection.get_domstr con));
|
||||
Connection.mark_as_bad con;
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
From 9166869e7e6530befddfd8bb46ff37436a38efc1 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:13 +0200
|
||||
Subject: tools/xenstore: fix deleting node in transaction
|
||||
|
||||
In case a node has been created in a transaction and it is later
|
||||
deleted in the same transaction, the transaction will be terminated
|
||||
with an error.
|
||||
|
||||
As this error is encountered only when handling the deleted node at
|
||||
transaction finalization, the transaction will have been performed
|
||||
partially and without updating the accounting information. This will
|
||||
enable a malicious guest to create arbitrary number of nodes.
|
||||
|
||||
This is part of XSA-421 / CVE-2022-42325.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Tested-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index 3e3eb47326cc..7ffe21bb5285 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -418,7 +418,13 @@ static int finalize_transaction(struct connection *conn,
|
||||
true);
|
||||
talloc_free(data.dptr);
|
||||
} else {
|
||||
- ret = do_tdb_delete(conn, &key, NULL);
|
||||
+ /*
|
||||
+ * A node having been created and later deleted
|
||||
+ * in this transaction will have no generation
|
||||
+ * information stored.
|
||||
+ */
|
||||
+ ret = (i->generation == NO_GENERATION)
|
||||
+ ? 0 : do_tdb_delete(conn, &key, NULL);
|
||||
}
|
||||
if (ret)
|
||||
goto err;
|
||||
401
xsa421-02.patch
401
xsa421-02.patch
|
|
@ -1,401 +0,0 @@
|
|||
From 09fc22fea8a4689c5e563ba4a2fa959282071792 Mon Sep 17 00:00:00 2001
|
||||
From: Juergen Gross <jgross@suse.com>
|
||||
Date: Tue, 13 Sep 2022 07:35:14 +0200
|
||||
Subject: tools/xenstore: harden transaction finalization against errors
|
||||
|
||||
When finalizing a transaction, any error occurring after checking for
|
||||
conflicts will result in the transaction being performed only
|
||||
partially today. Additionally accounting data will not be updated at
|
||||
the end of the transaction, which might result in further problems
|
||||
later.
|
||||
|
||||
Avoid those problems by multiple modifications:
|
||||
|
||||
- free any transaction specific nodes which don't need to be committed
|
||||
as they haven't been written during the transaction as soon as their
|
||||
generation count has been verified, this will reduce the risk of
|
||||
out-of-memory situations
|
||||
|
||||
- store the transaction specific node name in struct accessed_node in
|
||||
order to avoid the need to allocate additional memory for it when
|
||||
finalizing the transaction
|
||||
|
||||
- don't stop the transaction finalization when hitting an error
|
||||
condition, but try to continue to handle all modified nodes
|
||||
|
||||
- in case of a detected error do the accounting update as needed and
|
||||
call the data base checking only after that
|
||||
|
||||
- if writing a node in a transaction is failing (e.g. due to a failed
|
||||
quota check), fail the transaction, as prior changes to struct
|
||||
accessed_node can't easily be undone in that case
|
||||
|
||||
This is part of XSA-421 / CVE-2022-42326.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Juergen Gross <jgross@suse.com>
|
||||
Reviewed-by: Julien Grall <jgrall@amazon.com>
|
||||
Tested-by: Julien Grall <jgrall@amazon.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c
|
||||
index 36fb4a832834..476d5c6d51bd 100644
|
||||
--- a/tools/xenstore/xenstored_core.c
|
||||
+++ b/tools/xenstore/xenstored_core.c
|
||||
@@ -723,8 +723,7 @@ struct node *read_node(struct connection *conn, const void *ctx,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
- if (transaction_prepend(conn, name, &key))
|
||||
- return NULL;
|
||||
+ transaction_prepend(conn, name, &key);
|
||||
|
||||
data = tdb_fetch(tdb_ctx, key);
|
||||
|
||||
@@ -842,10 +841,21 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
|
||||
static int write_node(struct connection *conn, struct node *node,
|
||||
bool no_quota_check)
|
||||
{
|
||||
+ int ret;
|
||||
+
|
||||
if (access_node(conn, node, NODE_ACCESS_WRITE, &node->key))
|
||||
return errno;
|
||||
|
||||
- return write_node_raw(conn, &node->key, node, no_quota_check);
|
||||
+ ret = write_node_raw(conn, &node->key, node, no_quota_check);
|
||||
+ if (ret && conn && conn->transaction) {
|
||||
+ /*
|
||||
+ * Reverting access_node() is hard, so just fail the
|
||||
+ * transaction.
|
||||
+ */
|
||||
+ fail_transaction(conn->transaction);
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
}
|
||||
|
||||
unsigned int perm_for_conn(struct connection *conn,
|
||||
diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c
|
||||
index 7ffe21bb5285..ac854197cadb 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.c
|
||||
+++ b/tools/xenstore/xenstored_transaction.c
|
||||
@@ -114,7 +114,8 @@ struct accessed_node
|
||||
struct list_head list;
|
||||
|
||||
/* The name of the node. */
|
||||
- char *node;
|
||||
+ char *trans_name; /* Transaction specific name. */
|
||||
+ char *node; /* Main data base name. */
|
||||
|
||||
/* Generation count (or NO_GENERATION) for conflict checking. */
|
||||
uint64_t generation;
|
||||
@@ -199,25 +200,20 @@ static char *transaction_get_node_name(void *ctx, struct transaction *trans,
|
||||
* Prepend the transaction to name if node has been modified in the current
|
||||
* transaction.
|
||||
*/
|
||||
-int transaction_prepend(struct connection *conn, const char *name,
|
||||
- TDB_DATA *key)
|
||||
+void transaction_prepend(struct connection *conn, const char *name,
|
||||
+ TDB_DATA *key)
|
||||
{
|
||||
- char *tdb_name;
|
||||
+ struct accessed_node *i;
|
||||
|
||||
- if (!conn || !conn->transaction ||
|
||||
- !find_accessed_node(conn->transaction, name)) {
|
||||
- set_tdb_key(name, key);
|
||||
- return 0;
|
||||
+ if (conn && conn->transaction) {
|
||||
+ i = find_accessed_node(conn->transaction, name);
|
||||
+ if (i) {
|
||||
+ set_tdb_key(i->trans_name, key);
|
||||
+ return;
|
||||
+ }
|
||||
}
|
||||
|
||||
- tdb_name = transaction_get_node_name(conn->transaction,
|
||||
- conn->transaction, name);
|
||||
- if (!tdb_name)
|
||||
- return errno;
|
||||
-
|
||||
- set_tdb_key(tdb_name, key);
|
||||
-
|
||||
- return 0;
|
||||
+ set_tdb_key(name, key);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -240,7 +236,6 @@ int access_node(struct connection *conn, struct node *node,
|
||||
struct accessed_node *i = NULL;
|
||||
struct transaction *trans;
|
||||
TDB_DATA local_key;
|
||||
- const char *trans_name = NULL;
|
||||
int ret;
|
||||
bool introduce = false;
|
||||
|
||||
@@ -259,10 +254,6 @@ int access_node(struct connection *conn, struct node *node,
|
||||
|
||||
trans = conn->transaction;
|
||||
|
||||
- trans_name = transaction_get_node_name(node, trans, node->name);
|
||||
- if (!trans_name)
|
||||
- goto nomem;
|
||||
-
|
||||
i = find_accessed_node(trans, node->name);
|
||||
if (!i) {
|
||||
if (trans->nodes >= quota_trans_nodes &&
|
||||
@@ -273,9 +264,10 @@ int access_node(struct connection *conn, struct node *node,
|
||||
i = talloc_zero(trans, struct accessed_node);
|
||||
if (!i)
|
||||
goto nomem;
|
||||
- i->node = talloc_strdup(i, node->name);
|
||||
- if (!i->node)
|
||||
+ i->trans_name = transaction_get_node_name(i, trans, node->name);
|
||||
+ if (!i->trans_name)
|
||||
goto nomem;
|
||||
+ i->node = strchr(i->trans_name, '/') + 1;
|
||||
if (node->generation != NO_GENERATION && node->perms.num) {
|
||||
i->perms.p = talloc_array(i, struct xs_permissions,
|
||||
node->perms.num);
|
||||
@@ -302,7 +294,7 @@ int access_node(struct connection *conn, struct node *node,
|
||||
i->generation = node->generation;
|
||||
i->check_gen = true;
|
||||
if (node->generation != NO_GENERATION) {
|
||||
- set_tdb_key(trans_name, &local_key);
|
||||
+ set_tdb_key(i->trans_name, &local_key);
|
||||
ret = write_node_raw(conn, &local_key, node, true);
|
||||
if (ret)
|
||||
goto err;
|
||||
@@ -321,7 +313,7 @@ int access_node(struct connection *conn, struct node *node,
|
||||
return -1;
|
||||
|
||||
if (key) {
|
||||
- set_tdb_key(trans_name, key);
|
||||
+ set_tdb_key(i->trans_name, key);
|
||||
if (type == NODE_ACCESS_WRITE)
|
||||
i->ta_node = true;
|
||||
if (type == NODE_ACCESS_DELETE)
|
||||
@@ -333,7 +325,6 @@ int access_node(struct connection *conn, struct node *node,
|
||||
nomem:
|
||||
ret = ENOMEM;
|
||||
err:
|
||||
- talloc_free((void *)trans_name);
|
||||
talloc_free(i);
|
||||
trans->fail = true;
|
||||
errno = ret;
|
||||
@@ -371,100 +362,90 @@ void queue_watches(struct connection *conn, const char *name, bool watch_exact)
|
||||
* base.
|
||||
*/
|
||||
static int finalize_transaction(struct connection *conn,
|
||||
- struct transaction *trans)
|
||||
+ struct transaction *trans, bool *is_corrupt)
|
||||
{
|
||||
- struct accessed_node *i;
|
||||
+ struct accessed_node *i, *n;
|
||||
TDB_DATA key, ta_key, data;
|
||||
struct xs_tdb_record_hdr *hdr;
|
||||
uint64_t gen;
|
||||
- char *trans_name;
|
||||
- int ret;
|
||||
|
||||
- list_for_each_entry(i, &trans->accessed, list) {
|
||||
- if (!i->check_gen)
|
||||
- continue;
|
||||
+ list_for_each_entry_safe(i, n, &trans->accessed, list) {
|
||||
+ if (i->check_gen) {
|
||||
+ set_tdb_key(i->node, &key);
|
||||
+ data = tdb_fetch(tdb_ctx, key);
|
||||
+ hdr = (void *)data.dptr;
|
||||
+ if (!data.dptr) {
|
||||
+ if (tdb_error(tdb_ctx) != TDB_ERR_NOEXIST)
|
||||
+ return EIO;
|
||||
+ gen = NO_GENERATION;
|
||||
+ } else
|
||||
+ gen = hdr->generation;
|
||||
+ talloc_free(data.dptr);
|
||||
+ if (i->generation != gen)
|
||||
+ return EAGAIN;
|
||||
+ }
|
||||
|
||||
- set_tdb_key(i->node, &key);
|
||||
- data = tdb_fetch(tdb_ctx, key);
|
||||
- hdr = (void *)data.dptr;
|
||||
- if (!data.dptr) {
|
||||
- if (tdb_error(tdb_ctx) != TDB_ERR_NOEXIST)
|
||||
- return EIO;
|
||||
- gen = NO_GENERATION;
|
||||
- } else
|
||||
- gen = hdr->generation;
|
||||
- talloc_free(data.dptr);
|
||||
- if (i->generation != gen)
|
||||
- return EAGAIN;
|
||||
+ /* Entries for unmodified nodes can be removed early. */
|
||||
+ if (!i->modified) {
|
||||
+ if (i->ta_node) {
|
||||
+ set_tdb_key(i->trans_name, &ta_key);
|
||||
+ if (do_tdb_delete(conn, &ta_key, NULL))
|
||||
+ return EIO;
|
||||
+ }
|
||||
+ list_del(&i->list);
|
||||
+ talloc_free(i);
|
||||
+ }
|
||||
}
|
||||
|
||||
while ((i = list_top(&trans->accessed, struct accessed_node, list))) {
|
||||
- trans_name = transaction_get_node_name(i, trans, i->node);
|
||||
- if (!trans_name)
|
||||
- /* We are doomed: the transaction is only partial. */
|
||||
- goto err;
|
||||
-
|
||||
- set_tdb_key(trans_name, &ta_key);
|
||||
-
|
||||
- if (i->modified) {
|
||||
- set_tdb_key(i->node, &key);
|
||||
- if (i->ta_node) {
|
||||
- data = tdb_fetch(tdb_ctx, ta_key);
|
||||
- if (!data.dptr)
|
||||
- goto err;
|
||||
+ set_tdb_key(i->node, &key);
|
||||
+ if (i->ta_node) {
|
||||
+ set_tdb_key(i->trans_name, &ta_key);
|
||||
+ data = tdb_fetch(tdb_ctx, ta_key);
|
||||
+ if (data.dptr) {
|
||||
hdr = (void *)data.dptr;
|
||||
hdr->generation = ++generation;
|
||||
- ret = do_tdb_write(conn, &key, &data, NULL,
|
||||
- true);
|
||||
+ *is_corrupt |= do_tdb_write(conn, &key, &data,
|
||||
+ NULL, true);
|
||||
talloc_free(data.dptr);
|
||||
+ if (do_tdb_delete(conn, &ta_key, NULL))
|
||||
+ *is_corrupt = true;
|
||||
} else {
|
||||
- /*
|
||||
- * A node having been created and later deleted
|
||||
- * in this transaction will have no generation
|
||||
- * information stored.
|
||||
- */
|
||||
- ret = (i->generation == NO_GENERATION)
|
||||
- ? 0 : do_tdb_delete(conn, &key, NULL);
|
||||
- }
|
||||
- if (ret)
|
||||
- goto err;
|
||||
- if (i->fire_watch) {
|
||||
- fire_watches(conn, trans, i->node, NULL,
|
||||
- i->watch_exact,
|
||||
- i->perms.p ? &i->perms : NULL);
|
||||
+ *is_corrupt = true;
|
||||
}
|
||||
+ } else {
|
||||
+ /*
|
||||
+ * A node having been created and later deleted
|
||||
+ * in this transaction will have no generation
|
||||
+ * information stored.
|
||||
+ */
|
||||
+ *is_corrupt |= (i->generation == NO_GENERATION)
|
||||
+ ? false
|
||||
+ : do_tdb_delete(conn, &key, NULL);
|
||||
}
|
||||
+ if (i->fire_watch)
|
||||
+ fire_watches(conn, trans, i->node, NULL, i->watch_exact,
|
||||
+ i->perms.p ? &i->perms : NULL);
|
||||
|
||||
- if (i->ta_node && do_tdb_delete(conn, &ta_key, NULL))
|
||||
- goto err;
|
||||
list_del(&i->list);
|
||||
talloc_free(i);
|
||||
}
|
||||
|
||||
return 0;
|
||||
-
|
||||
-err:
|
||||
- corrupt(conn, "Partial transaction");
|
||||
- return EIO;
|
||||
}
|
||||
|
||||
static int destroy_transaction(void *_transaction)
|
||||
{
|
||||
struct transaction *trans = _transaction;
|
||||
struct accessed_node *i;
|
||||
- char *trans_name;
|
||||
TDB_DATA key;
|
||||
|
||||
wrl_ntransactions--;
|
||||
trace_destroy(trans, "transaction");
|
||||
while ((i = list_top(&trans->accessed, struct accessed_node, list))) {
|
||||
if (i->ta_node) {
|
||||
- trans_name = transaction_get_node_name(i, trans,
|
||||
- i->node);
|
||||
- if (trans_name) {
|
||||
- set_tdb_key(trans_name, &key);
|
||||
- do_tdb_delete(trans->conn, &key, NULL);
|
||||
- }
|
||||
+ set_tdb_key(i->trans_name, &key);
|
||||
+ do_tdb_delete(trans->conn, &key, NULL);
|
||||
}
|
||||
list_del(&i->list);
|
||||
talloc_free(i);
|
||||
@@ -556,6 +537,7 @@ int do_transaction_end(const void *ctx, struct connection *conn,
|
||||
{
|
||||
const char *arg = onearg(in);
|
||||
struct transaction *trans;
|
||||
+ bool is_corrupt = false;
|
||||
int ret;
|
||||
|
||||
if (!arg || (!streq(arg, "T") && !streq(arg, "F")))
|
||||
@@ -579,13 +561,17 @@ int do_transaction_end(const void *ctx, struct connection *conn,
|
||||
ret = transaction_fix_domains(trans, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
- if (finalize_transaction(conn, trans))
|
||||
- return EAGAIN;
|
||||
+ ret = finalize_transaction(conn, trans, &is_corrupt);
|
||||
+ if (ret)
|
||||
+ return ret;
|
||||
|
||||
wrl_apply_debit_trans_commit(conn);
|
||||
|
||||
/* fix domain entry for each changed domain */
|
||||
transaction_fix_domains(trans, true);
|
||||
+
|
||||
+ if (is_corrupt)
|
||||
+ corrupt(conn, "transaction inconsistency");
|
||||
}
|
||||
send_ack(conn, XS_TRANSACTION_END);
|
||||
|
||||
@@ -660,7 +646,7 @@ int check_transactions(struct hashtable *hash)
|
||||
struct connection *conn;
|
||||
struct transaction *trans;
|
||||
struct accessed_node *i;
|
||||
- char *tname, *tnode;
|
||||
+ char *tname;
|
||||
|
||||
list_for_each_entry(conn, &connections, list) {
|
||||
list_for_each_entry(trans, &conn->transaction_list, list) {
|
||||
@@ -672,11 +658,8 @@ int check_transactions(struct hashtable *hash)
|
||||
list_for_each_entry(i, &trans->accessed, list) {
|
||||
if (!i->ta_node)
|
||||
continue;
|
||||
- tnode = transaction_get_node_name(tname, trans,
|
||||
- i->node);
|
||||
- if (!tnode || !remember_string(hash, tnode))
|
||||
+ if (!remember_string(hash, i->trans_name))
|
||||
goto nomem;
|
||||
- talloc_free(tnode);
|
||||
}
|
||||
|
||||
talloc_free(tname);
|
||||
diff --git a/tools/xenstore/xenstored_transaction.h b/tools/xenstore/xenstored_transaction.h
|
||||
index 39d7f81c5127..3417303f9427 100644
|
||||
--- a/tools/xenstore/xenstored_transaction.h
|
||||
+++ b/tools/xenstore/xenstored_transaction.h
|
||||
@@ -48,8 +48,8 @@ int __must_check access_node(struct connection *conn, struct node *node,
|
||||
void queue_watches(struct connection *conn, const char *name, bool watch_exact);
|
||||
|
||||
/* Prepend the transaction to name if appropriate. */
|
||||
-int transaction_prepend(struct connection *conn, const char *name,
|
||||
- TDB_DATA *key);
|
||||
+void transaction_prepend(struct connection *conn, const char *name,
|
||||
+ TDB_DATA *key);
|
||||
|
||||
/* Mark the transaction as failed. This will prevent it to be committed. */
|
||||
void fail_transaction(struct transaction *trans);
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/spec-ctrl: Enumeration for IBPB_RET
|
||||
|
||||
The IBPB_RET bit indicates that the CPU's implementation of MSR_PRED_CMD.IBPB
|
||||
does flush the RSB/RAS too.
|
||||
|
||||
This is part of XSA-422 / CVE-2022-23824.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Acked-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/tools/libs/light/libxl_cpuid.c b/tools/libs/light/libxl_cpuid.c
|
||||
index bf6fdee360a9..691d5c6b2a68 100644
|
||||
--- a/tools/libs/light/libxl_cpuid.c
|
||||
+++ b/tools/libs/light/libxl_cpuid.c
|
||||
@@ -289,6 +289,7 @@ int libxl_cpuid_parse_config(libxl_cpuid_policy_list *cpuid, const char* str)
|
||||
{"ssb-no", 0x80000008, NA, CPUID_REG_EBX, 26, 1},
|
||||
{"psfd", 0x80000008, NA, CPUID_REG_EBX, 28, 1},
|
||||
{"btc-no", 0x80000008, NA, CPUID_REG_EBX, 29, 1},
|
||||
+ {"ibpb-ret", 0x80000008, NA, CPUID_REG_EBX, 30, 1},
|
||||
|
||||
{"nc", 0x80000008, NA, CPUID_REG_ECX, 0, 8},
|
||||
{"apicidsize", 0x80000008, NA, CPUID_REG_ECX, 12, 4},
|
||||
diff --git a/tools/misc/xen-cpuid.c b/tools/misc/xen-cpuid.c
|
||||
index fe22f5f5b68b..cd094427dd4c 100644
|
||||
--- a/tools/misc/xen-cpuid.c
|
||||
+++ b/tools/misc/xen-cpuid.c
|
||||
@@ -159,6 +159,7 @@ static const char *const str_e8b[32] =
|
||||
[24] = "amd-ssbd", [25] = "virt-ssbd",
|
||||
[26] = "ssb-no",
|
||||
[28] = "psfd", [29] = "btc-no",
|
||||
+ [30] = "ibpb-ret",
|
||||
};
|
||||
|
||||
static const char *const str_7d0[32] =
|
||||
diff --git a/xen/arch/x86/spec_ctrl.c b/xen/arch/x86/spec_ctrl.c
|
||||
index 0f4bad3d3abb..16a562d3a172 100644
|
||||
--- a/xen/arch/x86/spec_ctrl.c
|
||||
+++ b/xen/arch/x86/spec_ctrl.c
|
||||
@@ -419,7 +419,7 @@ static void __init print_details(enum ind_thunk thunk, uint64_t caps)
|
||||
* Hardware read-only information, stating immunity to certain issues, or
|
||||
* suggestions of which mitigation to use.
|
||||
*/
|
||||
- printk(" Hardware hints:%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
|
||||
+ printk(" Hardware hints:%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
|
||||
(caps & ARCH_CAPS_RDCL_NO) ? " RDCL_NO" : "",
|
||||
(caps & ARCH_CAPS_IBRS_ALL) ? " IBRS_ALL" : "",
|
||||
(caps & ARCH_CAPS_RSBA) ? " RSBA" : "",
|
||||
@@ -436,7 +436,8 @@ static void __init print_details(enum ind_thunk thunk, uint64_t caps)
|
||||
(e8b & cpufeat_mask(X86_FEATURE_STIBP_ALWAYS)) ? " STIBP_ALWAYS" : "",
|
||||
(e8b & cpufeat_mask(X86_FEATURE_IBRS_FAST)) ? " IBRS_FAST" : "",
|
||||
(e8b & cpufeat_mask(X86_FEATURE_IBRS_SAME_MODE)) ? " IBRS_SAME_MODE" : "",
|
||||
- (e8b & cpufeat_mask(X86_FEATURE_BTC_NO)) ? " BTC_NO" : "");
|
||||
+ (e8b & cpufeat_mask(X86_FEATURE_BTC_NO)) ? " BTC_NO" : "",
|
||||
+ (e8b & cpufeat_mask(X86_FEATURE_IBPB_RET)) ? " IBPB_RET" : "");
|
||||
|
||||
/* Hardware features which need driving to mitigate issues. */
|
||||
printk(" Hardware features:%s%s%s%s%s%s%s%s%s%s%s%s\n",
|
||||
diff --git a/xen/include/public/arch-x86/cpufeatureset.h b/xen/include/public/arch-x86/cpufeatureset.h
|
||||
index e7b8167800a2..e0731221404c 100644
|
||||
--- a/xen/include/public/arch-x86/cpufeatureset.h
|
||||
+++ b/xen/include/public/arch-x86/cpufeatureset.h
|
||||
@@ -267,6 +267,7 @@ XEN_CPUFEATURE(VIRT_SSBD, 8*32+25) /* MSR_VIRT_SPEC_CTRL.SSBD */
|
||||
XEN_CPUFEATURE(SSB_NO, 8*32+26) /*A Hardware not vulnerable to SSB */
|
||||
XEN_CPUFEATURE(PSFD, 8*32+28) /*S MSR_SPEC_CTRL.PSFD */
|
||||
XEN_CPUFEATURE(BTC_NO, 8*32+29) /*A Hardware not vulnerable to Branch Type Confusion */
|
||||
+XEN_CPUFEATURE(IBPB_RET, 8*32+30) /*A IBPB clears RSB/RAS too. */
|
||||
|
||||
/* Intel-defined CPU features, CPUID level 0x00000007:0.edx, word 9 */
|
||||
XEN_CPUFEATURE(AVX512_4VNNIW, 9*32+ 2) /*A AVX512 Neural Network Instructions */
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/spec-ctrl: Mitigate IBPB not flushing the RSB/RAS
|
||||
|
||||
Introduce spec_ctrl_new_guest_context() to encapsulate all logic pertaining to
|
||||
using MSR_PRED_CMD for a new guest context, even if it only has one user
|
||||
presently.
|
||||
|
||||
Introduce X86_BUG_IBPB_NO_RET, and use it extend spec_ctrl_new_guest_context()
|
||||
with a manual fixup for hardware which mis-implements IBPB.
|
||||
|
||||
This is part of XSA-422 / CVE-2022-23824.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Acked-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/xen/arch/x86/asm-macros.c b/xen/arch/x86/asm-macros.c
|
||||
index 7e536b0d82f5..891d86c7655c 100644
|
||||
--- a/xen/arch/x86/asm-macros.c
|
||||
+++ b/xen/arch/x86/asm-macros.c
|
||||
@@ -1,2 +1,3 @@
|
||||
#include <asm/asm-defns.h>
|
||||
#include <asm/alternative-asm.h>
|
||||
+#include <asm/spec_ctrl_asm.h>
|
||||
diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c
|
||||
index 3fab2364be8d..3080cde62b5b 100644
|
||||
--- a/xen/arch/x86/domain.c
|
||||
+++ b/xen/arch/x86/domain.c
|
||||
@@ -2092,7 +2092,7 @@ void context_switch(struct vcpu *prev, struct vcpu *next)
|
||||
*/
|
||||
if ( *last_id != next_id )
|
||||
{
|
||||
- wrmsrl(MSR_PRED_CMD, PRED_CMD_IBPB);
|
||||
+ spec_ctrl_new_guest_context();
|
||||
*last_id = next_id;
|
||||
}
|
||||
}
|
||||
diff --git a/xen/arch/x86/spec_ctrl.c b/xen/arch/x86/spec_ctrl.c
|
||||
index 16a562d3a172..90d86fe5cb47 100644
|
||||
--- a/xen/arch/x86/spec_ctrl.c
|
||||
+++ b/xen/arch/x86/spec_ctrl.c
|
||||
@@ -805,6 +805,14 @@ static void __init ibpb_calculations(void)
|
||||
}
|
||||
|
||||
/*
|
||||
+ * AMD/Hygon CPUs to date (June 2022) don't flush the the RAS. Future
|
||||
+ * CPUs are expected to enumerate IBPB_RET when this has been fixed.
|
||||
+ * Until then, cover the difference with the software sequence.
|
||||
+ */
|
||||
+ if ( boot_cpu_has(X86_FEATURE_IBPB) && !boot_cpu_has(X86_FEATURE_IBPB_RET) )
|
||||
+ setup_force_cpu_cap(X86_BUG_IBPB_NO_RET);
|
||||
+
|
||||
+ /*
|
||||
* IBPB-on-entry mitigations for Branch Type Confusion.
|
||||
*
|
||||
* IBPB && !BTC_NO selects all AMD/Hygon hardware, not known to be safe,
|
||||
diff --git a/xen/include/asm-x86/cpufeatures.h b/xen/include/asm-x86/cpufeatures.h
|
||||
index 672c9ee22ba2..ecc1bb09505a 100644
|
||||
--- a/xen/include/asm-x86/cpufeatures.h
|
||||
+++ b/xen/include/asm-x86/cpufeatures.h
|
||||
@@ -49,6 +49,7 @@ XEN_CPUFEATURE(IBPB_ENTRY_HVM, X86_SYNTH(29)) /* MSR_PRED_CMD used by Xen for
|
||||
#define X86_BUG_FPU_PTRS X86_BUG( 0) /* (F)X{SAVE,RSTOR} doesn't save/restore FOP/FIP/FDP. */
|
||||
#define X86_BUG_NULL_SEG X86_BUG( 1) /* NULL-ing a selector preserves the base and limit. */
|
||||
#define X86_BUG_CLFLUSH_MFENCE X86_BUG( 2) /* MFENCE needed to serialise CLFLUSH */
|
||||
+#define X86_BUG_IBPB_NO_RET X86_BUG( 3) /* IBPB doesn't flush the RSB/RAS */
|
||||
|
||||
/* Total number of capability words, inc synth and bug words. */
|
||||
#define NCAPINTS (FSCAPINTS + X86_NR_SYNTH + X86_NR_BUG) /* N 32-bit words worth of info */
|
||||
diff --git a/xen/include/asm-x86/spec_ctrl.h b/xen/include/asm-x86/spec_ctrl.h
|
||||
index 9403b81dc7af..6a77c3937844 100644
|
||||
--- a/xen/include/asm-x86/spec_ctrl.h
|
||||
+++ b/xen/include/asm-x86/spec_ctrl.h
|
||||
@@ -65,6 +65,28 @@
|
||||
void init_speculation_mitigations(void);
|
||||
void spec_ctrl_init_domain(struct domain *d);
|
||||
|
||||
+/*
|
||||
+ * Switch to a new guest prediction context.
|
||||
+ *
|
||||
+ * This flushes all indirect branch predictors (BTB, RSB/RAS), so guest code
|
||||
+ * which has previously run on this CPU can't attack subsequent guest code.
|
||||
+ *
|
||||
+ * As this flushes the RSB/RAS, it destroys the predictions of the calling
|
||||
+ * context. For best performace, arrange for this to be used when we're going
|
||||
+ * to jump out of the current context, e.g. with reset_stack_and_jump().
|
||||
+ *
|
||||
+ * For hardware which mis-implements IBPB, fix up by flushing the RSB/RAS
|
||||
+ * manually.
|
||||
+ */
|
||||
+static always_inline void spec_ctrl_new_guest_context(void)
|
||||
+{
|
||||
+ wrmsrl(MSR_PRED_CMD, PRED_CMD_IBPB);
|
||||
+
|
||||
+ /* (ab)use alternative_input() to specify clobbers. */
|
||||
+ alternative_input("", "DO_OVERWRITE_RSB", X86_BUG_IBPB_NO_RET,
|
||||
+ : "rax", "rcx");
|
||||
+}
|
||||
+
|
||||
extern int8_t opt_ibpb_ctxt_switch;
|
||||
extern bool opt_ssbd;
|
||||
extern int8_t opt_eager_fpu;
|
||||
110
xsa437-4.16.patch
Normal file
110
xsa437-4.16.patch
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
From 2b21319eecd8623078f27f0bccbbfdeb26606ff1 Mon Sep 17 00:00:00 2001
|
||||
From: Stefano Stabellini <stefano.stabellini@amd.com>
|
||||
Date: Thu, 17 Aug 2023 13:41:35 +0100
|
||||
Subject: [PATCH] xen/arm: page: Handle cache flush of an element at the top of
|
||||
the address space
|
||||
|
||||
The region that needs to be cleaned/invalidated may be at the top
|
||||
of the address space. This means that 'end' (i.e. 'p + size') will
|
||||
be 0 and therefore nothing will be cleaned/invalidated as the check
|
||||
in the loop will always be false.
|
||||
|
||||
On Arm64, we only support we only support up to 48-bit Virtual
|
||||
address space. So this is not a concern there. However, for 32-bit,
|
||||
the mapcache is using the last 2GB of the address space. Therefore
|
||||
we may not clean/invalidate properly some pages. This could lead
|
||||
to memory corruption or data leakage (the scrubbed value may
|
||||
still sit in the cache when the guest could read directly the memory
|
||||
and therefore read the old content).
|
||||
|
||||
Rework invalidate_dcache_va_range(), clean_dcache_va_range(),
|
||||
clean_and_invalidate_dcache_va_range() to handle a cache flush
|
||||
with an element at the top of the address space.
|
||||
|
||||
This is CVE-2023-34321 / XSA-437.
|
||||
|
||||
Reported-by: Julien Grall <jgrall@amazon.com>
|
||||
Signed-off-by: Stefano Stabellini <stefano.stabellini@amd.com>
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Acked-by: Bertrand Marquis <bertrand.marquis@arm.com>
|
||||
|
||||
---
|
||||
xen/include/asm-arm/page.h | 33 ++++++++++++++++++++-------------
|
||||
1 file changed, 20 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/xen/include/asm-arm/page.h b/xen/include/asm-arm/page.h
|
||||
index c6f9fb0d4e0c..eff5883ef87b 100644
|
||||
--- a/xen/include/asm-arm/page.h
|
||||
+++ b/xen/include/asm-arm/page.h
|
||||
@@ -152,26 +152,25 @@ static inline size_t read_dcache_line_bytes(void)
|
||||
|
||||
static inline int invalidate_dcache_va_range(const void *p, unsigned long size)
|
||||
{
|
||||
- const void *end = p + size;
|
||||
size_t cacheline_mask = dcache_line_bytes - 1;
|
||||
|
||||
dsb(sy); /* So the CPU issues all writes to the range */
|
||||
|
||||
if ( (uintptr_t)p & cacheline_mask )
|
||||
{
|
||||
+ size -= dcache_line_bytes - ((uintptr_t)p & cacheline_mask);
|
||||
p = (void *)((uintptr_t)p & ~cacheline_mask);
|
||||
asm volatile (__clean_and_invalidate_dcache_one(0) : : "r" (p));
|
||||
p += dcache_line_bytes;
|
||||
}
|
||||
- if ( (uintptr_t)end & cacheline_mask )
|
||||
- {
|
||||
- end = (void *)((uintptr_t)end & ~cacheline_mask);
|
||||
- asm volatile (__clean_and_invalidate_dcache_one(0) : : "r" (end));
|
||||
- }
|
||||
|
||||
- for ( ; p < end; p += dcache_line_bytes )
|
||||
+ for ( ; size >= dcache_line_bytes;
|
||||
+ p += dcache_line_bytes, size -= dcache_line_bytes )
|
||||
asm volatile (__invalidate_dcache_one(0) : : "r" (p));
|
||||
|
||||
+ if ( size > 0 )
|
||||
+ asm volatile (__clean_and_invalidate_dcache_one(0) : : "r" (p));
|
||||
+
|
||||
dsb(sy); /* So we know the flushes happen before continuing */
|
||||
|
||||
return 0;
|
||||
@@ -179,10 +178,14 @@ static inline int invalidate_dcache_va_range(const void *p, unsigned long size)
|
||||
|
||||
static inline int clean_dcache_va_range(const void *p, unsigned long size)
|
||||
{
|
||||
- const void *end = p + size;
|
||||
+ size_t cacheline_mask = dcache_line_bytes - 1;
|
||||
+
|
||||
dsb(sy); /* So the CPU issues all writes to the range */
|
||||
- p = (void *)((uintptr_t)p & ~(dcache_line_bytes - 1));
|
||||
- for ( ; p < end; p += dcache_line_bytes )
|
||||
+ size += (uintptr_t)p & cacheline_mask;
|
||||
+ size = (size + cacheline_mask) & ~cacheline_mask;
|
||||
+ p = (void *)((uintptr_t)p & ~cacheline_mask);
|
||||
+ for ( ; size >= dcache_line_bytes;
|
||||
+ p += dcache_line_bytes, size -= dcache_line_bytes )
|
||||
asm volatile (__clean_dcache_one(0) : : "r" (p));
|
||||
dsb(sy); /* So we know the flushes happen before continuing */
|
||||
/* ARM callers assume that dcache_* functions cannot fail. */
|
||||
@@ -192,10 +195,14 @@ static inline int clean_dcache_va_range(const void *p, unsigned long size)
|
||||
static inline int clean_and_invalidate_dcache_va_range
|
||||
(const void *p, unsigned long size)
|
||||
{
|
||||
- const void *end = p + size;
|
||||
+ size_t cacheline_mask = dcache_line_bytes - 1;
|
||||
+
|
||||
dsb(sy); /* So the CPU issues all writes to the range */
|
||||
- p = (void *)((uintptr_t)p & ~(dcache_line_bytes - 1));
|
||||
- for ( ; p < end; p += dcache_line_bytes )
|
||||
+ size += (uintptr_t)p & cacheline_mask;
|
||||
+ size = (size + cacheline_mask) & ~cacheline_mask;
|
||||
+ p = (void *)((uintptr_t)p & ~cacheline_mask);
|
||||
+ for ( ; size >= dcache_line_bytes;
|
||||
+ p += dcache_line_bytes, size -= dcache_line_bytes )
|
||||
asm volatile (__clean_and_invalidate_dcache_one(0) : : "r" (p));
|
||||
dsb(sy); /* So we know the flushes happen before continuing */
|
||||
/* ARM callers assume that dcache_* functions cannot fail. */
|
||||
--
|
||||
2.40.1
|
||||
|
||||
414
xsa438-4.16.patch
Normal file
414
xsa438-4.16.patch
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Subject: x86/shadow: defer releasing of PV's top-level shadow reference
|
||||
|
||||
sh_set_toplevel_shadow() re-pinning the top-level shadow we may be
|
||||
running on is not enough (and at the same time unnecessary when the
|
||||
shadow isn't what we're running on): That shadow becomes eligible for
|
||||
blowing away (from e.g. shadow_prealloc()) immediately after the
|
||||
paging lock was dropped. Yet it needs to remain valid until the actual
|
||||
page table switch occurred.
|
||||
|
||||
Propagate up the call chain the shadow entry that needs releasing
|
||||
eventually, and carry out the release immediately after switching page
|
||||
tables. Handle update_cr3() failures by switching to idle pagetables.
|
||||
Note that various further uses of update_cr3() are HVM-only or only act
|
||||
on paused vCPU-s, in which case sh_set_toplevel_shadow() will not defer
|
||||
releasing of the reference.
|
||||
|
||||
While changing the update_cr3() hook, also convert the "do_locking"
|
||||
parameter to boolean.
|
||||
|
||||
This is CVE-2023-34322 / XSA-438.
|
||||
|
||||
Reported-by: Tim Deegan <tim@xen.org>
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: George Dunlap <george.dunlap@cloud.com>
|
||||
|
||||
--- a/xen/arch/x86/mm/hap/hap.c
|
||||
+++ b/xen/arch/x86/mm/hap/hap.c
|
||||
@@ -728,10 +728,12 @@ static bool_t hap_invlpg(struct vcpu *v,
|
||||
return 1;
|
||||
}
|
||||
|
||||
-static void hap_update_cr3(struct vcpu *v, int do_locking, bool noflush)
|
||||
+static pagetable_t hap_update_cr3(struct vcpu *v, bool do_locking, bool noflush)
|
||||
{
|
||||
v->arch.hvm.hw_cr[3] = v->arch.hvm.guest_cr[3];
|
||||
hvm_update_guest_cr3(v, noflush);
|
||||
+
|
||||
+ return pagetable_null();
|
||||
}
|
||||
|
||||
/*
|
||||
--- a/xen/arch/x86/mm/shadow/common.c
|
||||
+++ b/xen/arch/x86/mm/shadow/common.c
|
||||
@@ -2574,13 +2574,13 @@ void shadow_update_paging_modes(struct v
|
||||
}
|
||||
|
||||
/* Set up the top-level shadow and install it in slot 'slot' of shadow_table */
|
||||
-void sh_set_toplevel_shadow(struct vcpu *v,
|
||||
- unsigned int slot,
|
||||
- mfn_t gmfn,
|
||||
- unsigned int root_type,
|
||||
- mfn_t (*make_shadow)(struct vcpu *v,
|
||||
- mfn_t gmfn,
|
||||
- uint32_t shadow_type))
|
||||
+pagetable_t sh_set_toplevel_shadow(struct vcpu *v,
|
||||
+ unsigned int slot,
|
||||
+ mfn_t gmfn,
|
||||
+ unsigned int root_type,
|
||||
+ mfn_t (*make_shadow)(struct vcpu *v,
|
||||
+ mfn_t gmfn,
|
||||
+ uint32_t shadow_type))
|
||||
{
|
||||
mfn_t smfn;
|
||||
pagetable_t old_entry, new_entry;
|
||||
@@ -2637,20 +2637,37 @@ void sh_set_toplevel_shadow(struct vcpu
|
||||
mfn_x(gmfn), mfn_x(pagetable_get_mfn(new_entry)));
|
||||
v->arch.paging.shadow.shadow_table[slot] = new_entry;
|
||||
|
||||
- /* Decrement the refcount of the old contents of this slot */
|
||||
- if ( !pagetable_is_null(old_entry) )
|
||||
+ /*
|
||||
+ * Decrement the refcount of the old contents of this slot, unless
|
||||
+ * we're still running on that shadow - in that case it'll need holding
|
||||
+ * on to until the actual page table switch did occur.
|
||||
+ */
|
||||
+ if ( !pagetable_is_null(old_entry) && (v != current || !is_pv_domain(d)) )
|
||||
{
|
||||
- mfn_t old_smfn = pagetable_get_mfn(old_entry);
|
||||
- /* Need to repin the old toplevel shadow if it's been unpinned
|
||||
- * by shadow_prealloc(): in PV mode we're still running on this
|
||||
- * shadow and it's not safe to free it yet. */
|
||||
- if ( !mfn_to_page(old_smfn)->u.sh.pinned && !sh_pin(d, old_smfn) )
|
||||
- {
|
||||
- printk(XENLOG_G_ERR "can't re-pin %"PRI_mfn"\n", mfn_x(old_smfn));
|
||||
- domain_crash(d);
|
||||
- }
|
||||
- sh_put_ref(d, old_smfn, 0);
|
||||
+ sh_put_ref(d, pagetable_get_mfn(old_entry), 0);
|
||||
+ old_entry = pagetable_null();
|
||||
}
|
||||
+
|
||||
+ /*
|
||||
+ * 2- and 3-level shadow mode is used for HVM only. Therefore we never run
|
||||
+ * on such a shadow, so only call sites requesting an L4 shadow need to pay
|
||||
+ * attention to the returned value.
|
||||
+ */
|
||||
+ ASSERT(pagetable_is_null(old_entry) || root_type == SH_type_l4_64_shadow);
|
||||
+
|
||||
+ return old_entry;
|
||||
+}
|
||||
+
|
||||
+/*
|
||||
+ * Helper invoked when releasing of a top-level shadow's reference was
|
||||
+ * deferred in sh_set_toplevel_shadow() above.
|
||||
+ */
|
||||
+void shadow_put_top_level(struct domain *d, pagetable_t old_entry)
|
||||
+{
|
||||
+ ASSERT(!pagetable_is_null(old_entry));
|
||||
+ paging_lock(d);
|
||||
+ sh_put_ref(d, pagetable_get_mfn(old_entry), 0);
|
||||
+ paging_unlock(d);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
--- a/xen/arch/x86/mm/shadow/multi.c
|
||||
+++ b/xen/arch/x86/mm/shadow/multi.c
|
||||
@@ -3219,8 +3219,8 @@ sh_detach_old_tables(struct vcpu *v)
|
||||
}
|
||||
}
|
||||
|
||||
-static void
|
||||
-sh_update_cr3(struct vcpu *v, int do_locking, bool noflush)
|
||||
+static pagetable_t
|
||||
+sh_update_cr3(struct vcpu *v, bool do_locking, bool noflush)
|
||||
/* Updates vcpu->arch.cr3 after the guest has changed CR3.
|
||||
* Paravirtual guests should set v->arch.guest_table (and guest_table_user,
|
||||
* if appropriate).
|
||||
@@ -3234,6 +3234,7 @@ sh_update_cr3(struct vcpu *v, int do_loc
|
||||
{
|
||||
struct domain *d = v->domain;
|
||||
mfn_t gmfn;
|
||||
+ pagetable_t old_entry = pagetable_null();
|
||||
#if GUEST_PAGING_LEVELS == 3 && defined(CONFIG_HVM)
|
||||
const guest_l3e_t *gl3e;
|
||||
unsigned int i, guest_idx;
|
||||
@@ -3243,7 +3244,7 @@ sh_update_cr3(struct vcpu *v, int do_loc
|
||||
if ( !is_hvm_domain(d) && !v->is_initialised )
|
||||
{
|
||||
ASSERT(v->arch.cr3 == 0);
|
||||
- return;
|
||||
+ return old_entry;
|
||||
}
|
||||
|
||||
if ( do_locking ) paging_lock(v->domain);
|
||||
@@ -3316,11 +3317,12 @@ sh_update_cr3(struct vcpu *v, int do_loc
|
||||
#if GUEST_PAGING_LEVELS == 4
|
||||
if ( sh_remove_write_access(d, gmfn, 4, 0) != 0 )
|
||||
guest_flush_tlb_mask(d, d->dirty_cpumask);
|
||||
- sh_set_toplevel_shadow(v, 0, gmfn, SH_type_l4_shadow, sh_make_shadow);
|
||||
+ old_entry = sh_set_toplevel_shadow(v, 0, gmfn, SH_type_l4_shadow,
|
||||
+ sh_make_shadow);
|
||||
if ( unlikely(pagetable_is_null(v->arch.paging.shadow.shadow_table[0])) )
|
||||
{
|
||||
ASSERT(d->is_dying || d->is_shutting_down);
|
||||
- return;
|
||||
+ return old_entry;
|
||||
}
|
||||
if ( !shadow_mode_external(d) && !is_pv_32bit_domain(d) )
|
||||
{
|
||||
@@ -3366,24 +3368,30 @@ sh_update_cr3(struct vcpu *v, int do_loc
|
||||
gl2gfn = guest_l3e_get_gfn(gl3e[i]);
|
||||
gl2mfn = get_gfn_query_unlocked(d, gfn_x(gl2gfn), &p2mt);
|
||||
if ( p2m_is_ram(p2mt) )
|
||||
- sh_set_toplevel_shadow(v, i, gl2mfn, SH_type_l2_shadow,
|
||||
- sh_make_shadow);
|
||||
+ old_entry = sh_set_toplevel_shadow(v, i, gl2mfn,
|
||||
+ SH_type_l2_shadow,
|
||||
+ sh_make_shadow);
|
||||
else
|
||||
- sh_set_toplevel_shadow(v, i, INVALID_MFN, 0,
|
||||
- sh_make_shadow);
|
||||
+ old_entry = sh_set_toplevel_shadow(v, i, INVALID_MFN, 0,
|
||||
+ sh_make_shadow);
|
||||
}
|
||||
else
|
||||
- sh_set_toplevel_shadow(v, i, INVALID_MFN, 0, sh_make_shadow);
|
||||
+ old_entry = sh_set_toplevel_shadow(v, i, INVALID_MFN, 0,
|
||||
+ sh_make_shadow);
|
||||
+
|
||||
+ ASSERT(pagetable_is_null(old_entry));
|
||||
}
|
||||
}
|
||||
#elif GUEST_PAGING_LEVELS == 2
|
||||
if ( sh_remove_write_access(d, gmfn, 2, 0) != 0 )
|
||||
guest_flush_tlb_mask(d, d->dirty_cpumask);
|
||||
- sh_set_toplevel_shadow(v, 0, gmfn, SH_type_l2_shadow, sh_make_shadow);
|
||||
+ old_entry = sh_set_toplevel_shadow(v, 0, gmfn, SH_type_l2_shadow,
|
||||
+ sh_make_shadow);
|
||||
+ ASSERT(pagetable_is_null(old_entry));
|
||||
if ( unlikely(pagetable_is_null(v->arch.paging.shadow.shadow_table[0])) )
|
||||
{
|
||||
ASSERT(d->is_dying || d->is_shutting_down);
|
||||
- return;
|
||||
+ return old_entry;
|
||||
}
|
||||
#else
|
||||
#error This should never happen
|
||||
@@ -3477,6 +3485,8 @@ sh_update_cr3(struct vcpu *v, int do_loc
|
||||
|
||||
/* Release the lock, if we took it (otherwise it's the caller's problem) */
|
||||
if ( do_locking ) paging_unlock(v->domain);
|
||||
+
|
||||
+ return old_entry;
|
||||
}
|
||||
|
||||
|
||||
--- a/xen/arch/x86/mm/shadow/none.c
|
||||
+++ b/xen/arch/x86/mm/shadow/none.c
|
||||
@@ -52,9 +52,10 @@ static unsigned long _gva_to_gfn(struct
|
||||
}
|
||||
#endif
|
||||
|
||||
-static void _update_cr3(struct vcpu *v, int do_locking, bool noflush)
|
||||
+static pagetable_t _update_cr3(struct vcpu *v, bool do_locking, bool noflush)
|
||||
{
|
||||
ASSERT_UNREACHABLE();
|
||||
+ return pagetable_null();
|
||||
}
|
||||
|
||||
static void _update_paging_modes(struct vcpu *v)
|
||||
--- a/xen/arch/x86/mm/shadow/private.h
|
||||
+++ b/xen/arch/x86/mm/shadow/private.h
|
||||
@@ -391,13 +391,13 @@ mfn_t shadow_alloc(struct domain *d,
|
||||
void shadow_free(struct domain *d, mfn_t smfn);
|
||||
|
||||
/* Set up the top-level shadow and install it in slot 'slot' of shadow_table */
|
||||
-void sh_set_toplevel_shadow(struct vcpu *v,
|
||||
- unsigned int slot,
|
||||
- mfn_t gmfn,
|
||||
- unsigned int root_type,
|
||||
- mfn_t (*make_shadow)(struct vcpu *v,
|
||||
- mfn_t gmfn,
|
||||
- uint32_t shadow_type));
|
||||
+pagetable_t sh_set_toplevel_shadow(struct vcpu *v,
|
||||
+ unsigned int slot,
|
||||
+ mfn_t gmfn,
|
||||
+ unsigned int root_type,
|
||||
+ mfn_t (*make_shadow)(struct vcpu *v,
|
||||
+ mfn_t gmfn,
|
||||
+ uint32_t shadow_type));
|
||||
|
||||
/* Update the shadows in response to a pagetable write from Xen */
|
||||
int sh_validate_guest_entry(struct vcpu *v, mfn_t gmfn, void *entry, u32 size);
|
||||
--- a/xen/arch/x86/mm.c
|
||||
+++ b/xen/arch/x86/mm.c
|
||||
@@ -565,15 +565,12 @@ void write_ptbase(struct vcpu *v)
|
||||
*
|
||||
* Update ref counts to shadow tables appropriately.
|
||||
*/
|
||||
-void update_cr3(struct vcpu *v)
|
||||
+pagetable_t update_cr3(struct vcpu *v)
|
||||
{
|
||||
mfn_t cr3_mfn;
|
||||
|
||||
if ( paging_mode_enabled(v->domain) )
|
||||
- {
|
||||
- paging_update_cr3(v, false);
|
||||
- return;
|
||||
- }
|
||||
+ return paging_update_cr3(v, false);
|
||||
|
||||
if ( !(v->arch.flags & TF_kernel_mode) )
|
||||
cr3_mfn = pagetable_get_mfn(v->arch.guest_table_user);
|
||||
@@ -581,6 +578,8 @@ void update_cr3(struct vcpu *v)
|
||||
cr3_mfn = pagetable_get_mfn(v->arch.guest_table);
|
||||
|
||||
make_cr3(v, cr3_mfn);
|
||||
+
|
||||
+ return pagetable_null();
|
||||
}
|
||||
|
||||
static inline void set_tlbflush_timestamp(struct page_info *page)
|
||||
@@ -3254,6 +3253,7 @@ int new_guest_cr3(mfn_t mfn)
|
||||
struct domain *d = curr->domain;
|
||||
int rc;
|
||||
mfn_t old_base_mfn;
|
||||
+ pagetable_t old_shadow;
|
||||
|
||||
if ( is_pv_32bit_domain(d) )
|
||||
{
|
||||
@@ -3321,9 +3321,22 @@ int new_guest_cr3(mfn_t mfn)
|
||||
if ( !VM_ASSIST(d, m2p_strict) )
|
||||
fill_ro_mpt(mfn);
|
||||
curr->arch.guest_table = pagetable_from_mfn(mfn);
|
||||
- update_cr3(curr);
|
||||
+ old_shadow = update_cr3(curr);
|
||||
+
|
||||
+ /*
|
||||
+ * In shadow mode update_cr3() can fail, in which case here we're still
|
||||
+ * running on the prior top-level shadow (which we're about to release).
|
||||
+ * Switch to the idle page tables in such an event; the guest will have
|
||||
+ * been crashed already.
|
||||
+ */
|
||||
+ if ( likely(!mfn_eq(pagetable_get_mfn(old_shadow),
|
||||
+ maddr_to_mfn(curr->arch.cr3 & ~X86_CR3_NOFLUSH))) )
|
||||
+ write_ptbase(curr);
|
||||
+ else
|
||||
+ write_ptbase(idle_vcpu[curr->processor]);
|
||||
|
||||
- write_ptbase(curr);
|
||||
+ if ( !pagetable_is_null(old_shadow) )
|
||||
+ shadow_put_top_level(d, old_shadow);
|
||||
|
||||
if ( likely(mfn_x(old_base_mfn) != 0) )
|
||||
{
|
||||
--- a/xen/arch/x86/pv/domain.c
|
||||
+++ b/xen/arch/x86/pv/domain.c
|
||||
@@ -424,10 +424,13 @@ bool __init xpti_pcid_enabled(void)
|
||||
|
||||
static void _toggle_guest_pt(struct vcpu *v)
|
||||
{
|
||||
+ bool guest_update;
|
||||
+ pagetable_t old_shadow;
|
||||
unsigned long cr3;
|
||||
|
||||
v->arch.flags ^= TF_kernel_mode;
|
||||
- update_cr3(v);
|
||||
+ guest_update = v->arch.flags & TF_kernel_mode;
|
||||
+ old_shadow = update_cr3(v);
|
||||
|
||||
/*
|
||||
* Don't flush user global mappings from the TLB. Don't tick TLB clock.
|
||||
@@ -436,13 +439,31 @@ static void _toggle_guest_pt(struct vcpu
|
||||
* TLB flush (for just the incoming PCID), as the top level page table may
|
||||
* have changed behind our backs. To be on the safe side, suppress the
|
||||
* no-flush unconditionally in this case.
|
||||
+ *
|
||||
+ * Furthermore in shadow mode update_cr3() can fail, in which case here
|
||||
+ * we're still running on the prior top-level shadow (which we're about
|
||||
+ * to release). Switch to the idle page tables in such an event; the
|
||||
+ * guest will have been crashed already.
|
||||
*/
|
||||
cr3 = v->arch.cr3;
|
||||
if ( shadow_mode_enabled(v->domain) )
|
||||
+ {
|
||||
cr3 &= ~X86_CR3_NOFLUSH;
|
||||
+
|
||||
+ if ( unlikely(mfn_eq(pagetable_get_mfn(old_shadow),
|
||||
+ maddr_to_mfn(cr3))) )
|
||||
+ {
|
||||
+ cr3 = idle_vcpu[v->processor]->arch.cr3;
|
||||
+ /* Also suppress runstate/time area updates below. */
|
||||
+ guest_update = false;
|
||||
+ }
|
||||
+ }
|
||||
write_cr3(cr3);
|
||||
|
||||
- if ( !(v->arch.flags & TF_kernel_mode) )
|
||||
+ if ( !pagetable_is_null(old_shadow) )
|
||||
+ shadow_put_top_level(v->domain, old_shadow);
|
||||
+
|
||||
+ if ( !guest_update )
|
||||
return;
|
||||
|
||||
if ( v->arch.pv.need_update_runstate_area && update_runstate_area(v) )
|
||||
--- a/xen/include/asm-x86/mm.h
|
||||
+++ b/xen/include/asm-x86/mm.h
|
||||
@@ -564,7 +564,7 @@ void audit_domains(void);
|
||||
#endif
|
||||
|
||||
void make_cr3(struct vcpu *v, mfn_t mfn);
|
||||
-void update_cr3(struct vcpu *v);
|
||||
+pagetable_t update_cr3(struct vcpu *v);
|
||||
int vcpu_destroy_pagetables(struct vcpu *);
|
||||
void *do_page_walk(struct vcpu *v, unsigned long addr);
|
||||
|
||||
--- a/xen/include/asm-x86/paging.h
|
||||
+++ b/xen/include/asm-x86/paging.h
|
||||
@@ -138,7 +138,7 @@ struct paging_mode {
|
||||
paddr_t ga, uint32_t *pfec,
|
||||
unsigned int *page_order);
|
||||
#endif
|
||||
- void (*update_cr3 )(struct vcpu *v, int do_locking,
|
||||
+ pagetable_t (*update_cr3 )(struct vcpu *v, bool do_locking,
|
||||
bool noflush);
|
||||
void (*update_paging_modes )(struct vcpu *v);
|
||||
bool (*flush_tlb )(bool (*flush_vcpu)(void *ctxt,
|
||||
@@ -315,9 +315,9 @@ static inline unsigned long paging_ga_to
|
||||
/* Update all the things that are derived from the guest's CR3.
|
||||
* Called when the guest changes CR3; the caller can then use v->arch.cr3
|
||||
* as the value to load into the host CR3 to schedule this vcpu */
|
||||
-static inline void paging_update_cr3(struct vcpu *v, bool noflush)
|
||||
+static inline pagetable_t paging_update_cr3(struct vcpu *v, bool noflush)
|
||||
{
|
||||
- paging_get_hostmode(v)->update_cr3(v, 1, noflush);
|
||||
+ return paging_get_hostmode(v)->update_cr3(v, 1, noflush);
|
||||
}
|
||||
|
||||
/* Update all the things that are derived from the guest's CR0/CR3/CR4.
|
||||
--- a/xen/include/asm-x86/shadow.h
|
||||
+++ b/xen/include/asm-x86/shadow.h
|
||||
@@ -97,6 +97,9 @@ void shadow_blow_tables_per_domain(struc
|
||||
int shadow_set_allocation(struct domain *d, unsigned int pages,
|
||||
bool *preempted);
|
||||
|
||||
+/* Helper to invoke for deferred releasing of a top-level shadow's reference. */
|
||||
+void shadow_put_top_level(struct domain *d, pagetable_t old);
|
||||
+
|
||||
#else /* !CONFIG_SHADOW_PAGING */
|
||||
|
||||
#define shadow_vcpu_teardown(v) ASSERT(is_pv_vcpu(v))
|
||||
@@ -118,6 +121,11 @@ static inline void shadow_prepare_page_t
|
||||
|
||||
static inline void shadow_blow_tables_per_domain(struct domain *d) {}
|
||||
|
||||
+static inline void shadow_put_top_level(struct domain *d, pagetable_t old)
|
||||
+{
|
||||
+ ASSERT_UNREACHABLE();
|
||||
+}
|
||||
+
|
||||
static inline int shadow_domctl(struct domain *d,
|
||||
struct xen_domctl_shadow_op *sc,
|
||||
XEN_GUEST_HANDLE_PARAM(xen_domctl_t) u_domctl)
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
From 08539e8315fdae5f5bfd655d53ed35fd2922fe6c Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Jan Beulich <jbeulich@suse.com>
|
||||
Date: Wed, 23 Aug 2023 09:26:36 +0200
|
||||
Subject: [XEN PATCH 01/10] x86/AMD: extend Zenbleed check to models "good"
|
||||
ucode isn't known for
|
||||
|
||||
Reportedly the AMD Custom APU 0405 found on SteamDeck, models 0x90 and
|
||||
0x91, (quoting the respective Linux commit) is similarly affected. Put
|
||||
another instance of our Zen1 vs Zen2 distinction checks in
|
||||
amd_check_zenbleed(), forcing use of the chickenbit irrespective of
|
||||
ucode version (building upon real hardware never surfacing a version of
|
||||
0xffffffff).
|
||||
|
||||
Signed-off-by: Jan Beulich <jbeulich@suse.com>
|
||||
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
(cherry picked from commit 145a69c0944ac70cfcf9d247c85dee9e99d9d302)
|
||||
---
|
||||
xen/arch/x86/cpu/amd.c | 13 ++++++++++---
|
||||
1 file changed, 10 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/cpu/amd.c b/xen/arch/x86/cpu/amd.c
|
||||
index 60c6d88edf..a591038757 100644
|
||||
--- a/xen/arch/x86/cpu/amd.c
|
||||
+++ b/xen/arch/x86/cpu/amd.c
|
||||
@@ -800,10 +800,17 @@ void amd_check_zenbleed(void)
|
||||
case 0xa0 ... 0xaf: good_rev = 0x08a00008; break;
|
||||
default:
|
||||
/*
|
||||
- * With the Fam17h check above, parts getting here are Zen1.
|
||||
- * They're not affected.
|
||||
+ * With the Fam17h check above, most parts getting here are
|
||||
+ * Zen1. They're not affected. Assume Zen2 ones making it
|
||||
+ * here are affected regardless of microcode version.
|
||||
+ *
|
||||
+ * Zen1 vs Zen2 isn't a simple model number comparison, so use
|
||||
+ * STIBP as a heuristic to distinguish.
|
||||
*/
|
||||
- return;
|
||||
+ if (!boot_cpu_has(X86_FEATURE_AMD_STIBP))
|
||||
+ return;
|
||||
+ good_rev = ~0U;
|
||||
+ break;
|
||||
}
|
||||
|
||||
rdmsrl(MSR_AMD64_DE_CFG, val);
|
||||
--
|
||||
2.41.0
|
||||
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
From 1e52cdf07cdf52e5d99957c3ecbddf5b1feda963 Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <1e52cdf07cdf52e5d99957c3ecbddf5b1feda963.1695742580.git.m.a.young@durham.ac.uk>
|
||||
In-Reply-To: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
References: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Tue, 12 Sep 2023 15:06:49 +0100
|
||||
Subject: [XEN PATCH 02/10] x86/spec-ctrl: Fix confusion between
|
||||
SPEC_CTRL_EXIT_TO_XEN{,_IST}
|
||||
|
||||
c/s 3fffaf9c13e9 ("x86/entry: Avoid using alternatives in NMI/#MC paths")
|
||||
dropped the only user, leaving behind the (incorrect) implication that Xen had
|
||||
split exit paths.
|
||||
|
||||
Delete the unused SPEC_CTRL_EXIT_TO_XEN and rename SPEC_CTRL_EXIT_TO_XEN_IST
|
||||
to SPEC_CTRL_EXIT_TO_XEN for consistency.
|
||||
|
||||
No functional change.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 1c18d73774533a55ba9d1cbee8bdace03efdb5e7)
|
||||
---
|
||||
xen/arch/x86/x86_64/entry.S | 2 +-
|
||||
xen/include/asm-x86/spec_ctrl_asm.h | 10 ++--------
|
||||
2 files changed, 3 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/x86_64/entry.S b/xen/arch/x86/x86_64/entry.S
|
||||
index db2ea7871e..59f2040787 100644
|
||||
--- a/xen/arch/x86/x86_64/entry.S
|
||||
+++ b/xen/arch/x86/x86_64/entry.S
|
||||
@@ -664,7 +664,7 @@ UNLIKELY_START(ne, exit_cr3)
|
||||
UNLIKELY_END(exit_cr3)
|
||||
|
||||
/* WARNING! `ret`, `call *`, `jmp *` not safe beyond this point. */
|
||||
- SPEC_CTRL_EXIT_TO_XEN_IST /* Req: %rbx=end, Clob: acd */
|
||||
+ SPEC_CTRL_EXIT_TO_XEN /* Req: %rbx=end, Clob: acd */
|
||||
|
||||
RESTORE_ALL adj=8
|
||||
iretq
|
||||
diff --git a/xen/include/asm-x86/spec_ctrl_asm.h b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
index b61a5571ae..f5110616e4 100644
|
||||
--- a/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
+++ b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
@@ -79,7 +79,6 @@
|
||||
* - SPEC_CTRL_ENTRY_FROM_PV
|
||||
* - SPEC_CTRL_ENTRY_FROM_INTR
|
||||
* - SPEC_CTRL_ENTRY_FROM_INTR_IST
|
||||
- * - SPEC_CTRL_EXIT_TO_XEN_IST
|
||||
* - SPEC_CTRL_EXIT_TO_XEN
|
||||
* - SPEC_CTRL_EXIT_TO_PV
|
||||
*
|
||||
@@ -273,11 +272,6 @@
|
||||
ALTERNATIVE "", __stringify(DO_SPEC_CTRL_ENTRY maybexen=1), \
|
||||
X86_FEATURE_SC_MSR_PV
|
||||
|
||||
-/* Use when exiting to Xen context. */
|
||||
-#define SPEC_CTRL_EXIT_TO_XEN \
|
||||
- ALTERNATIVE "", \
|
||||
- DO_SPEC_CTRL_EXIT_TO_XEN, X86_FEATURE_SC_MSR_PV
|
||||
-
|
||||
/* Use when exiting to PV guest context. */
|
||||
#define SPEC_CTRL_EXIT_TO_PV \
|
||||
ALTERNATIVE "", \
|
||||
@@ -344,8 +338,8 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
UNLIKELY_END(\@_serialise)
|
||||
.endm
|
||||
|
||||
-/* Use when exiting to Xen in IST context. */
|
||||
-.macro SPEC_CTRL_EXIT_TO_XEN_IST
|
||||
+/* Use when exiting to Xen context. */
|
||||
+.macro SPEC_CTRL_EXIT_TO_XEN
|
||||
/*
|
||||
* Requires %rbx=stack_end
|
||||
* Clobbers %rax, %rcx, %rdx
|
||||
--
|
||||
2.41.0
|
||||
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
From afa5b17f385372226de6b0862f12ab39fda16b5c Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <afa5b17f385372226de6b0862f12ab39fda16b5c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
In-Reply-To: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
References: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Tue, 12 Sep 2023 17:03:16 +0100
|
||||
Subject: [XEN PATCH 03/10] x86/spec-ctrl: Fold DO_SPEC_CTRL_EXIT_TO_XEN into
|
||||
it's single user
|
||||
|
||||
With the SPEC_CTRL_EXIT_TO_XEN{,_IST} confusion fixed, it's now obvious that
|
||||
there's only a single EXIT_TO_XEN path. Fold DO_SPEC_CTRL_EXIT_TO_XEN into
|
||||
SPEC_CTRL_EXIT_TO_XEN to simplify further fixes.
|
||||
|
||||
When merging labels, switch the name to .L\@_skip_sc_msr as "skip" on its own
|
||||
is going to be too generic shortly.
|
||||
|
||||
No functional change.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 694bb0f280fd08a4377e36e32b84b5062def4de2)
|
||||
---
|
||||
xen/include/asm-x86/spec_ctrl_asm.h | 40 ++++++++++++-----------------
|
||||
1 file changed, 16 insertions(+), 24 deletions(-)
|
||||
|
||||
diff --git a/xen/include/asm-x86/spec_ctrl_asm.h b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
index f5110616e4..251c30eee5 100644
|
||||
--- a/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
+++ b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
@@ -216,27 +216,6 @@
|
||||
wrmsr
|
||||
.endm
|
||||
|
||||
-.macro DO_SPEC_CTRL_EXIT_TO_XEN
|
||||
-/*
|
||||
- * Requires %rbx=stack_end
|
||||
- * Clobbers %rax, %rcx, %rdx
|
||||
- *
|
||||
- * When returning to Xen context, look to see whether SPEC_CTRL shadowing is
|
||||
- * in effect, and reload the shadow value. This covers race conditions which
|
||||
- * exist with an NMI/MCE/etc hitting late in the return-to-guest path.
|
||||
- */
|
||||
- xor %edx, %edx
|
||||
-
|
||||
- testb $SCF_use_shadow, STACK_CPUINFO_FIELD(spec_ctrl_flags)(%rbx)
|
||||
- jz .L\@_skip
|
||||
-
|
||||
- mov STACK_CPUINFO_FIELD(shadow_spec_ctrl)(%rbx), %eax
|
||||
- mov $MSR_SPEC_CTRL, %ecx
|
||||
- wrmsr
|
||||
-
|
||||
-.L\@_skip:
|
||||
-.endm
|
||||
-
|
||||
.macro DO_SPEC_CTRL_EXIT_TO_GUEST
|
||||
/*
|
||||
* Requires %eax=spec_ctrl, %rsp=regs/cpuinfo
|
||||
@@ -345,11 +324,24 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
* Clobbers %rax, %rcx, %rdx
|
||||
*/
|
||||
testb $SCF_ist_sc_msr, STACK_CPUINFO_FIELD(spec_ctrl_flags)(%rbx)
|
||||
- jz .L\@_skip
|
||||
+ jz .L\@_skip_sc_msr
|
||||
|
||||
- DO_SPEC_CTRL_EXIT_TO_XEN
|
||||
+ /*
|
||||
+ * When returning to Xen context, look to see whether SPEC_CTRL shadowing
|
||||
+ * is in effect, and reload the shadow value. This covers race conditions
|
||||
+ * which exist with an NMI/MCE/etc hitting late in the return-to-guest
|
||||
+ * path.
|
||||
+ */
|
||||
+ xor %edx, %edx
|
||||
|
||||
-.L\@_skip:
|
||||
+ testb $SCF_use_shadow, STACK_CPUINFO_FIELD(spec_ctrl_flags)(%rbx)
|
||||
+ jz .L\@_skip_sc_msr
|
||||
+
|
||||
+ mov STACK_CPUINFO_FIELD(shadow_spec_ctrl)(%rbx), %eax
|
||||
+ mov $MSR_SPEC_CTRL, %ecx
|
||||
+ wrmsr
|
||||
+
|
||||
+.L\@_skip_sc_msr:
|
||||
.endm
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
--
|
||||
2.41.0
|
||||
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
From 353e876a9dd5a93d0bf8819e77613c33db0de97b Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <353e876a9dd5a93d0bf8819e77613c33db0de97b.1695742580.git.m.a.young@durham.ac.uk>
|
||||
In-Reply-To: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
References: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Fri, 1 Sep 2023 11:38:44 +0100
|
||||
Subject: [XEN PATCH 04/10] x86/spec-ctrl: Turn the remaining
|
||||
SPEC_CTRL_{ENTRY,EXIT}_* into asm macros
|
||||
|
||||
These have grown more complex over time, with some already having been
|
||||
converted.
|
||||
|
||||
Provide full Requires/Clobbers comments, otherwise missing at this level of
|
||||
indirection.
|
||||
|
||||
No functional change.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 7125429aafb9e3c9c88fc93001fc2300e0ac2cc8)
|
||||
---
|
||||
xen/include/asm-x86/spec_ctrl_asm.h | 37 ++++++++++++++++++++++-------
|
||||
1 file changed, 28 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/xen/include/asm-x86/spec_ctrl_asm.h b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
index 251c30eee5..94ed5dc880 100644
|
||||
--- a/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
+++ b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
@@ -236,26 +236,45 @@
|
||||
.endm
|
||||
|
||||
/* Use after an entry from PV context (syscall/sysenter/int80/int82/etc). */
|
||||
-#define SPEC_CTRL_ENTRY_FROM_PV \
|
||||
+.macro SPEC_CTRL_ENTRY_FROM_PV
|
||||
+/*
|
||||
+ * Requires %rsp=regs/cpuinfo, %rdx=0
|
||||
+ * Clobbers %rax, %rcx, %rdx
|
||||
+ */
|
||||
ALTERNATIVE "", __stringify(DO_SPEC_CTRL_COND_IBPB maybexen=0), \
|
||||
- X86_FEATURE_IBPB_ENTRY_PV; \
|
||||
- ALTERNATIVE "", DO_OVERWRITE_RSB, X86_FEATURE_SC_RSB_PV; \
|
||||
+ X86_FEATURE_IBPB_ENTRY_PV
|
||||
+
|
||||
+ ALTERNATIVE "", DO_OVERWRITE_RSB, X86_FEATURE_SC_RSB_PV
|
||||
+
|
||||
ALTERNATIVE "", __stringify(DO_SPEC_CTRL_ENTRY maybexen=0), \
|
||||
X86_FEATURE_SC_MSR_PV
|
||||
+.endm
|
||||
|
||||
/* Use in interrupt/exception context. May interrupt Xen or PV context. */
|
||||
-#define SPEC_CTRL_ENTRY_FROM_INTR \
|
||||
+.macro SPEC_CTRL_ENTRY_FROM_INTR
|
||||
+/*
|
||||
+ * Requires %rsp=regs, %r14=stack_end, %rdx=0
|
||||
+ * Clobbers %rax, %rcx, %rdx
|
||||
+ */
|
||||
ALTERNATIVE "", __stringify(DO_SPEC_CTRL_COND_IBPB maybexen=1), \
|
||||
- X86_FEATURE_IBPB_ENTRY_PV; \
|
||||
- ALTERNATIVE "", DO_OVERWRITE_RSB, X86_FEATURE_SC_RSB_PV; \
|
||||
+ X86_FEATURE_IBPB_ENTRY_PV
|
||||
+
|
||||
+ ALTERNATIVE "", DO_OVERWRITE_RSB, X86_FEATURE_SC_RSB_PV
|
||||
+
|
||||
ALTERNATIVE "", __stringify(DO_SPEC_CTRL_ENTRY maybexen=1), \
|
||||
X86_FEATURE_SC_MSR_PV
|
||||
+.endm
|
||||
|
||||
/* Use when exiting to PV guest context. */
|
||||
-#define SPEC_CTRL_EXIT_TO_PV \
|
||||
- ALTERNATIVE "", \
|
||||
- DO_SPEC_CTRL_EXIT_TO_GUEST, X86_FEATURE_SC_MSR_PV; \
|
||||
+.macro SPEC_CTRL_EXIT_TO_PV
|
||||
+/*
|
||||
+ * Requires %rax=spec_ctrl, %rsp=regs/info
|
||||
+ * Clobbers %rcx, %rdx
|
||||
+ */
|
||||
+ ALTERNATIVE "", DO_SPEC_CTRL_EXIT_TO_GUEST, X86_FEATURE_SC_MSR_PV
|
||||
+
|
||||
DO_SPEC_CTRL_COND_VERW
|
||||
+.endm
|
||||
|
||||
/*
|
||||
* Use in IST interrupt/exception context. May interrupt Xen or PV context.
|
||||
--
|
||||
2.41.0
|
||||
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
From 6cc49c355e952f4ff564c6b817e7eff57c5a02c7 Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <6cc49c355e952f4ff564c6b817e7eff57c5a02c7.1695742580.git.m.a.young@durham.ac.uk>
|
||||
In-Reply-To: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
References: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Wed, 30 Aug 2023 20:11:50 +0100
|
||||
Subject: [XEN PATCH 05/10] x86/spec-ctrl: Improve all SPEC_CTRL_{ENTER,EXIT}_*
|
||||
comments
|
||||
|
||||
... to better explain how they're used.
|
||||
|
||||
Doing so highlights that SPEC_CTRL_EXIT_TO_XEN is missing a VERW flush for the
|
||||
corner case when e.g. an NMI hits late in an exit-to-guest path.
|
||||
|
||||
Leave a TODO, which will be addressed in subsequent patches which arrange for
|
||||
VERW flushing to be safe within SPEC_CTRL_EXIT_TO_XEN.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 45f00557350dc7d0756551069803fc49c29184ca)
|
||||
---
|
||||
xen/include/asm-x86/spec_ctrl_asm.h | 36 +++++++++++++++++++++++++----
|
||||
1 file changed, 31 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/xen/include/asm-x86/spec_ctrl_asm.h b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
index 94ed5dc880..9c397f7cbd 100644
|
||||
--- a/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
+++ b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
@@ -235,7 +235,10 @@
|
||||
wrmsr
|
||||
.endm
|
||||
|
||||
-/* Use after an entry from PV context (syscall/sysenter/int80/int82/etc). */
|
||||
+/*
|
||||
+ * Used after an entry from PV context: SYSCALL, SYSENTER, INT,
|
||||
+ * etc. There is always a guest speculation state in context.
|
||||
+ */
|
||||
.macro SPEC_CTRL_ENTRY_FROM_PV
|
||||
/*
|
||||
* Requires %rsp=regs/cpuinfo, %rdx=0
|
||||
@@ -250,7 +253,11 @@
|
||||
X86_FEATURE_SC_MSR_PV
|
||||
.endm
|
||||
|
||||
-/* Use in interrupt/exception context. May interrupt Xen or PV context. */
|
||||
+/*
|
||||
+ * Used after an exception or maskable interrupt, hitting Xen or PV context.
|
||||
+ * There will either be a guest speculation context, or (barring fatal
|
||||
+ * exceptions) a well-formed Xen speculation context.
|
||||
+ */
|
||||
.macro SPEC_CTRL_ENTRY_FROM_INTR
|
||||
/*
|
||||
* Requires %rsp=regs, %r14=stack_end, %rdx=0
|
||||
@@ -265,7 +272,10 @@
|
||||
X86_FEATURE_SC_MSR_PV
|
||||
.endm
|
||||
|
||||
-/* Use when exiting to PV guest context. */
|
||||
+/*
|
||||
+ * Used when exiting from any entry context, back to PV context. This
|
||||
+ * includes from an IST entry which moved onto the primary stack.
|
||||
+ */
|
||||
.macro SPEC_CTRL_EXIT_TO_PV
|
||||
/*
|
||||
* Requires %rax=spec_ctrl, %rsp=regs/info
|
||||
@@ -277,7 +287,13 @@
|
||||
.endm
|
||||
|
||||
/*
|
||||
- * Use in IST interrupt/exception context. May interrupt Xen or PV context.
|
||||
+ * Used after an IST entry hitting Xen or PV context. Special care is needed,
|
||||
+ * because when hitting Xen context, there may not be a well-formed
|
||||
+ * speculation context. (i.e. it can hit in the middle of
|
||||
+ * SPEC_CTRL_{ENTRY,EXIT}_* regions.)
|
||||
+ *
|
||||
+ * An IST entry which hits PV context moves onto the primary stack and leaves
|
||||
+ * via SPEC_CTRL_EXIT_TO_PV, *not* SPEC_CTRL_EXIT_TO_XEN.
|
||||
*/
|
||||
.macro SPEC_CTRL_ENTRY_FROM_INTR_IST
|
||||
/*
|
||||
@@ -336,7 +352,14 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
UNLIKELY_END(\@_serialise)
|
||||
.endm
|
||||
|
||||
-/* Use when exiting to Xen context. */
|
||||
+/*
|
||||
+ * Use when exiting from any entry context, back to Xen context. This
|
||||
+ * includes returning to other SPEC_CTRL_{ENTRY,EXIT}_* regions with an
|
||||
+ * incomplete speculation context.
|
||||
+ *
|
||||
+ * Because we might have interrupted Xen beyond SPEC_CTRL_EXIT_TO_$GUEST, we
|
||||
+ * need to treat this as if it were an EXIT_TO_$GUEST case too.
|
||||
+ */
|
||||
.macro SPEC_CTRL_EXIT_TO_XEN
|
||||
/*
|
||||
* Requires %rbx=stack_end
|
||||
@@ -361,6 +384,9 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
wrmsr
|
||||
|
||||
.L\@_skip_sc_msr:
|
||||
+
|
||||
+ /* TODO VERW */
|
||||
+
|
||||
.endm
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
--
|
||||
2.41.0
|
||||
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
From 19aca8f32778f289112fc8db2ee547cdf29c81ca Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <19aca8f32778f289112fc8db2ee547cdf29c81ca.1695742580.git.m.a.young@durham.ac.uk>
|
||||
In-Reply-To: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
References: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Wed, 13 Sep 2023 13:48:16 +0100
|
||||
Subject: [XEN PATCH 06/10] x86/entry: Adjust restore_all_xen to hold stack_end
|
||||
in %r14
|
||||
|
||||
All other SPEC_CTRL_{ENTRY,EXIT}_* helpers hold stack_end in %r14. Adjust it
|
||||
for consistency.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 7aa28849a1155d856e214e9a80a7e65fffdc3e58)
|
||||
---
|
||||
xen/arch/x86/x86_64/entry.S | 8 ++++----
|
||||
xen/include/asm-x86/spec_ctrl_asm.h | 8 ++++----
|
||||
2 files changed, 8 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/x86_64/entry.S b/xen/arch/x86/x86_64/entry.S
|
||||
index 59f2040787..266c0a0990 100644
|
||||
--- a/xen/arch/x86/x86_64/entry.S
|
||||
+++ b/xen/arch/x86/x86_64/entry.S
|
||||
@@ -656,15 +656,15 @@ restore_all_xen:
|
||||
* Check whether we need to switch to the per-CPU page tables, in
|
||||
* case we return to late PV exit code (from an NMI or #MC).
|
||||
*/
|
||||
- GET_STACK_END(bx)
|
||||
- cmpb $0, STACK_CPUINFO_FIELD(use_pv_cr3)(%rbx)
|
||||
+ GET_STACK_END(14)
|
||||
+ cmpb $0, STACK_CPUINFO_FIELD(use_pv_cr3)(%r14)
|
||||
UNLIKELY_START(ne, exit_cr3)
|
||||
- mov STACK_CPUINFO_FIELD(pv_cr3)(%rbx), %rax
|
||||
+ mov STACK_CPUINFO_FIELD(pv_cr3)(%r14), %rax
|
||||
mov %rax, %cr3
|
||||
UNLIKELY_END(exit_cr3)
|
||||
|
||||
/* WARNING! `ret`, `call *`, `jmp *` not safe beyond this point. */
|
||||
- SPEC_CTRL_EXIT_TO_XEN /* Req: %rbx=end, Clob: acd */
|
||||
+ SPEC_CTRL_EXIT_TO_XEN /* Req: %r14=end, Clob: acd */
|
||||
|
||||
RESTORE_ALL adj=8
|
||||
iretq
|
||||
diff --git a/xen/include/asm-x86/spec_ctrl_asm.h b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
index 9c397f7cbd..3e745813cf 100644
|
||||
--- a/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
+++ b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
@@ -362,10 +362,10 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
*/
|
||||
.macro SPEC_CTRL_EXIT_TO_XEN
|
||||
/*
|
||||
- * Requires %rbx=stack_end
|
||||
+ * Requires %r14=stack_end
|
||||
* Clobbers %rax, %rcx, %rdx
|
||||
*/
|
||||
- testb $SCF_ist_sc_msr, STACK_CPUINFO_FIELD(spec_ctrl_flags)(%rbx)
|
||||
+ testb $SCF_ist_sc_msr, STACK_CPUINFO_FIELD(spec_ctrl_flags)(%r14)
|
||||
jz .L\@_skip_sc_msr
|
||||
|
||||
/*
|
||||
@@ -376,10 +376,10 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
*/
|
||||
xor %edx, %edx
|
||||
|
||||
- testb $SCF_use_shadow, STACK_CPUINFO_FIELD(spec_ctrl_flags)(%rbx)
|
||||
+ testb $SCF_use_shadow, STACK_CPUINFO_FIELD(spec_ctrl_flags)(%r14)
|
||||
jz .L\@_skip_sc_msr
|
||||
|
||||
- mov STACK_CPUINFO_FIELD(shadow_spec_ctrl)(%rbx), %eax
|
||||
+ mov STACK_CPUINFO_FIELD(shadow_spec_ctrl)(%r14), %eax
|
||||
mov $MSR_SPEC_CTRL, %ecx
|
||||
wrmsr
|
||||
|
||||
--
|
||||
2.41.0
|
||||
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
From 8064cbdbef79e328fad5158beeaf1c45bd0f5bd3 Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <8064cbdbef79e328fad5158beeaf1c45bd0f5bd3.1695742580.git.m.a.young@durham.ac.uk>
|
||||
In-Reply-To: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
References: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Wed, 13 Sep 2023 12:20:12 +0100
|
||||
Subject: [XEN PATCH 07/10] x86/entry: Track the IST-ness of an entry for the
|
||||
exit paths
|
||||
|
||||
Use %r12 to hold an ist_exit boolean. This register is zero elsewhere in the
|
||||
entry/exit asm, so it only needs setting in the IST path.
|
||||
|
||||
As this is subtle and fragile, add check_ist_exit() to be used in debugging
|
||||
builds to cross-check that the ist_exit boolean matches the entry vector.
|
||||
|
||||
Write check_ist_exit() it in C, because it's debug only and the logic more
|
||||
complicated than I care to maintain in asm.
|
||||
|
||||
For now, we only need to use this signal in the exit-to-Xen path, but some
|
||||
exit-to-guest paths happen in IST context too. Check the correctness in all
|
||||
exit paths to avoid the logic bit-rotting.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 21bdc25b05a0f8ab6bc73520a9ca01327360732c)
|
||||
|
||||
x86/entry: Partially revert IST-exit checks
|
||||
|
||||
The patch adding check_ist_exit() didn't account for the fact that
|
||||
reset_stack_and_jump() is not an ABI-preserving boundary. The IST-ness in
|
||||
%r12 doesn't survive into the next context, and is a stale value C.
|
||||
|
||||
This shows up in Gitlab CI for the Clang build:
|
||||
|
||||
https://gitlab.com/xen-project/people/andyhhp/xen/-/jobs/5112783827
|
||||
|
||||
and in OSSTest for GCC 8:
|
||||
|
||||
http://logs.test-lab.xenproject.org/osstest/logs/183045/test-amd64-amd64-xl-qemuu-debianhvm-amd64/serial-pinot0.log
|
||||
|
||||
There's no straightforward way to reconstruct the IST-exit-ness on the
|
||||
exit-to-guest path after a context switch. For now, we only need IST-exit on
|
||||
the return-to-Xen path.
|
||||
|
||||
Fixes: 21bdc25b05a0 ("x86/entry: Track the IST-ness of an entry for the exit paths")
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 9b57c800b79b96769ea3dcd6468578fa664d19f9)
|
||||
---
|
||||
xen/arch/x86/traps.c | 13 +++++++++++++
|
||||
xen/arch/x86/x86_64/entry.S | 13 ++++++++++++-
|
||||
2 files changed, 25 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/xen/arch/x86/traps.c b/xen/arch/x86/traps.c
|
||||
index 9679bfdb08..f7992ff230 100644
|
||||
--- a/xen/arch/x86/traps.c
|
||||
+++ b/xen/arch/x86/traps.c
|
||||
@@ -2348,6 +2348,19 @@ void asm_domain_crash_synchronous(unsigned long addr)
|
||||
do_softirq();
|
||||
}
|
||||
|
||||
+#ifdef CONFIG_DEBUG
|
||||
+void check_ist_exit(const struct cpu_user_regs *regs, bool ist_exit)
|
||||
+{
|
||||
+ const unsigned int ist_mask =
|
||||
+ (1U << X86_EXC_NMI) | (1U << X86_EXC_DB) |
|
||||
+ (1U << X86_EXC_DF) | (1U << X86_EXC_MC);
|
||||
+ uint8_t ev = regs->entry_vector;
|
||||
+ bool is_ist = (ev < TRAP_nr) && ((1U << ev) & ist_mask);
|
||||
+
|
||||
+ ASSERT(is_ist == ist_exit);
|
||||
+}
|
||||
+#endif
|
||||
+
|
||||
/*
|
||||
* Local variables:
|
||||
* mode: C
|
||||
diff --git a/xen/arch/x86/x86_64/entry.S b/xen/arch/x86/x86_64/entry.S
|
||||
index 266c0a0990..671e3b3fd5 100644
|
||||
--- a/xen/arch/x86/x86_64/entry.S
|
||||
+++ b/xen/arch/x86/x86_64/entry.S
|
||||
@@ -650,8 +650,15 @@ ret_from_intr:
|
||||
.section .text.entry, "ax", @progbits
|
||||
|
||||
ALIGN
|
||||
-/* No special register assumptions. */
|
||||
+/* %r12=ist_exit */
|
||||
restore_all_xen:
|
||||
+
|
||||
+#ifdef CONFIG_DEBUG
|
||||
+ mov %rsp, %rdi
|
||||
+ mov %r12, %rsi
|
||||
+ call check_ist_exit
|
||||
+#endif
|
||||
+
|
||||
/*
|
||||
* Check whether we need to switch to the per-CPU page tables, in
|
||||
* case we return to late PV exit code (from an NMI or #MC).
|
||||
@@ -1032,6 +1039,10 @@ handle_ist_exception:
|
||||
INDIRECT_CALL %rdx
|
||||
mov %r15, STACK_CPUINFO_FIELD(xen_cr3)(%r14)
|
||||
mov %bl, STACK_CPUINFO_FIELD(use_pv_cr3)(%r14)
|
||||
+
|
||||
+ /* This is an IST exit */
|
||||
+ mov $1, %r12d
|
||||
+
|
||||
cmpb $TRAP_nmi,UREGS_entry_vector(%rsp)
|
||||
jne ret_from_intr
|
||||
|
||||
--
|
||||
2.41.0
|
||||
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
From 3e51782ebb088fde39fdcfa30d002baddd1a9e06 Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <3e51782ebb088fde39fdcfa30d002baddd1a9e06.1695742580.git.m.a.young@durham.ac.uk>
|
||||
In-Reply-To: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
References: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Wed, 13 Sep 2023 13:53:33 +0100
|
||||
Subject: [XEN PATCH 08/10] x86/spec-ctrl: Issue VERW during IST exit to Xen
|
||||
|
||||
There is a corner case where e.g. an NMI hitting an exit-to-guest path after
|
||||
SPEC_CTRL_EXIT_TO_* would have run the entire NMI handler *after* the VERW
|
||||
flush to scrub potentially sensitive data from uarch buffers.
|
||||
|
||||
In order to compensate, issue VERW when exiting to Xen from an IST entry.
|
||||
|
||||
SPEC_CTRL_EXIT_TO_XEN already has two reads of spec_ctrl_flags off the stack,
|
||||
and we're about to add a third. Load the field into %ebx, and list the
|
||||
register as clobbered.
|
||||
|
||||
%r12 has been arranged to be the ist_exit signal, so add this as an input
|
||||
dependency and use it to identify when to issue a VERW.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit 3ee6066bcd737756b0990d417d94eddc0b0d2585)
|
||||
---
|
||||
xen/arch/x86/x86_64/entry.S | 2 +-
|
||||
xen/include/asm-x86/spec_ctrl_asm.h | 20 +++++++++++++++-----
|
||||
2 files changed, 16 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/x86_64/entry.S b/xen/arch/x86/x86_64/entry.S
|
||||
index 671e3b3fd5..88ff5c150f 100644
|
||||
--- a/xen/arch/x86/x86_64/entry.S
|
||||
+++ b/xen/arch/x86/x86_64/entry.S
|
||||
@@ -671,7 +671,7 @@ UNLIKELY_START(ne, exit_cr3)
|
||||
UNLIKELY_END(exit_cr3)
|
||||
|
||||
/* WARNING! `ret`, `call *`, `jmp *` not safe beyond this point. */
|
||||
- SPEC_CTRL_EXIT_TO_XEN /* Req: %r14=end, Clob: acd */
|
||||
+ SPEC_CTRL_EXIT_TO_XEN /* Req: %r12=ist_exit %r14=end, Clob: abcd */
|
||||
|
||||
RESTORE_ALL adj=8
|
||||
iretq
|
||||
diff --git a/xen/include/asm-x86/spec_ctrl_asm.h b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
index 3e745813cf..8a816b8cf6 100644
|
||||
--- a/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
+++ b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
@@ -362,10 +362,12 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
*/
|
||||
.macro SPEC_CTRL_EXIT_TO_XEN
|
||||
/*
|
||||
- * Requires %r14=stack_end
|
||||
- * Clobbers %rax, %rcx, %rdx
|
||||
+ * Requires %r12=ist_exit, %r14=stack_end
|
||||
+ * Clobbers %rax, %rbx, %rcx, %rdx
|
||||
*/
|
||||
- testb $SCF_ist_sc_msr, STACK_CPUINFO_FIELD(spec_ctrl_flags)(%r14)
|
||||
+ movzbl STACK_CPUINFO_FIELD(spec_ctrl_flags)(%r14), %ebx
|
||||
+
|
||||
+ testb $SCF_ist_sc_msr, %bl
|
||||
jz .L\@_skip_sc_msr
|
||||
|
||||
/*
|
||||
@@ -376,7 +378,7 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
*/
|
||||
xor %edx, %edx
|
||||
|
||||
- testb $SCF_use_shadow, STACK_CPUINFO_FIELD(spec_ctrl_flags)(%r14)
|
||||
+ testb $SCF_use_shadow, %bl
|
||||
jz .L\@_skip_sc_msr
|
||||
|
||||
mov STACK_CPUINFO_FIELD(shadow_spec_ctrl)(%r14), %eax
|
||||
@@ -385,8 +387,16 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
|
||||
.L\@_skip_sc_msr:
|
||||
|
||||
- /* TODO VERW */
|
||||
+ test %r12, %r12
|
||||
+ jz .L\@_skip_ist_exit
|
||||
+
|
||||
+ /* Logically DO_SPEC_CTRL_COND_VERW but without the %rsp=cpuinfo dependency */
|
||||
+ testb $SCF_verw, %bl
|
||||
+ jz .L\@_skip_verw
|
||||
+ verw STACK_CPUINFO_FIELD(verw_sel)(%r14)
|
||||
+.L\@_skip_verw:
|
||||
|
||||
+.L\@_skip_ist_exit:
|
||||
.endm
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
--
|
||||
2.41.0
|
||||
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
From a5857f1eca17a609119ae928c9fa73bb0996ddd9 Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <a5857f1eca17a609119ae928c9fa73bb0996ddd9.1695742580.git.m.a.young@durham.ac.uk>
|
||||
In-Reply-To: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
References: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Fri, 15 Sep 2023 12:13:51 +0100
|
||||
Subject: [XEN PATCH 09/10] x86/amd: Introduce is_zen{1,2}_uarch() predicates
|
||||
|
||||
We already have 3 cases using STIBP as a Zen1/2 heuristic, and are about to
|
||||
introduce a 4th. Wrap the heuristic into a pair of predicates rather than
|
||||
opencoding it, and the explanation of the heuristic, at each usage site.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit de1d265001397f308c5c3c5d3ffc30e7ef8c0705)
|
||||
---
|
||||
xen/arch/x86/cpu/amd.c | 18 ++++--------------
|
||||
xen/include/asm-x86/amd.h | 11 +++++++++++
|
||||
2 files changed, 15 insertions(+), 14 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/cpu/amd.c b/xen/arch/x86/cpu/amd.c
|
||||
index a591038757..b71d891901 100644
|
||||
--- a/xen/arch/x86/cpu/amd.c
|
||||
+++ b/xen/arch/x86/cpu/amd.c
|
||||
@@ -746,15 +746,13 @@ void amd_init_ssbd(const struct cpuinfo_x86 *c)
|
||||
* non-branch instructions to be ignored. It is to be set unilaterally in
|
||||
* newer microcode.
|
||||
*
|
||||
- * This chickenbit is something unrelated on Zen1, and Zen1 vs Zen2 isn't a
|
||||
- * simple model number comparison, so use STIBP as a heuristic to separate the
|
||||
- * two uarches in Fam17h(AMD)/18h(Hygon).
|
||||
+ * This chickenbit is something unrelated on Zen1.
|
||||
*/
|
||||
void amd_init_spectral_chicken(void)
|
||||
{
|
||||
uint64_t val, chickenbit = 1 << 1;
|
||||
|
||||
- if (cpu_has_hypervisor || !boot_cpu_has(X86_FEATURE_AMD_STIBP))
|
||||
+ if (cpu_has_hypervisor || !is_zen2_uarch())
|
||||
return;
|
||||
|
||||
if (rdmsr_safe(MSR_AMD64_DE_CFG2, val) == 0 && !(val & chickenbit))
|
||||
@@ -803,11 +801,8 @@ void amd_check_zenbleed(void)
|
||||
* With the Fam17h check above, most parts getting here are
|
||||
* Zen1. They're not affected. Assume Zen2 ones making it
|
||||
* here are affected regardless of microcode version.
|
||||
- *
|
||||
- * Zen1 vs Zen2 isn't a simple model number comparison, so use
|
||||
- * STIBP as a heuristic to distinguish.
|
||||
*/
|
||||
- if (!boot_cpu_has(X86_FEATURE_AMD_STIBP))
|
||||
+ if (is_zen1_uarch())
|
||||
return;
|
||||
good_rev = ~0U;
|
||||
break;
|
||||
@@ -1168,12 +1163,7 @@ static int __init zen2_c6_errata_check(void)
|
||||
*/
|
||||
s_time_t delta;
|
||||
|
||||
- /*
|
||||
- * Zen1 vs Zen2 isn't a simple model number comparison, so use STIBP as
|
||||
- * a heuristic to separate the two uarches in Fam17h.
|
||||
- */
|
||||
- if (cpu_has_hypervisor || boot_cpu_data.x86 != 0x17 ||
|
||||
- !boot_cpu_has(X86_FEATURE_AMD_STIBP))
|
||||
+ if (cpu_has_hypervisor || boot_cpu_data.x86 != 0x17 || !is_zen2_uarch())
|
||||
return 0;
|
||||
|
||||
/*
|
||||
diff --git a/xen/include/asm-x86/amd.h b/xen/include/asm-x86/amd.h
|
||||
index a82382e6bf..7fe1e19217 100644
|
||||
--- a/xen/include/asm-x86/amd.h
|
||||
+++ b/xen/include/asm-x86/amd.h
|
||||
@@ -140,6 +140,17 @@
|
||||
AMD_MODEL_RANGE(0x11, 0x0, 0x0, 0xff, 0xf), \
|
||||
AMD_MODEL_RANGE(0x12, 0x0, 0x0, 0xff, 0xf))
|
||||
|
||||
+/*
|
||||
+ * The Zen1 and Zen2 microarchitectures are implemented by AMD (Fam17h) and
|
||||
+ * Hygon (Fam18h) but without simple model number rules. Instead, use STIBP
|
||||
+ * as a heuristic that distinguishes the two.
|
||||
+ *
|
||||
+ * The caller is required to perform the appropriate vendor/family checks
|
||||
+ * first.
|
||||
+ */
|
||||
+#define is_zen1_uarch() (!boot_cpu_has(X86_FEATURE_AMD_STIBP))
|
||||
+#define is_zen2_uarch() boot_cpu_has(X86_FEATURE_AMD_STIBP)
|
||||
+
|
||||
struct cpuinfo_x86;
|
||||
int cpu_has_amd_erratum(const struct cpuinfo_x86 *, int, ...);
|
||||
|
||||
--
|
||||
2.41.0
|
||||
|
||||
239
xsa439-0010-x86-spec-ctrl-Mitigate-the-Zen1-DIV-leakage.patch
Normal file
239
xsa439-0010-x86-spec-ctrl-Mitigate-the-Zen1-DIV-leakage.patch
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
From de751c3d906d17b2e25ee429f81b17a689c7c6c0 Mon Sep 17 00:00:00 2001
|
||||
Message-ID: <de751c3d906d17b2e25ee429f81b17a689c7c6c0.1695742580.git.m.a.young@durham.ac.uk>
|
||||
In-Reply-To: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
References: <08539e8315fdae5f5bfd655d53ed35fd2922fe6c.1695742580.git.m.a.young@durham.ac.uk>
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Wed, 30 Aug 2023 20:24:25 +0100
|
||||
Subject: [XEN PATCH 10/10] x86/spec-ctrl: Mitigate the Zen1 DIV leakage
|
||||
|
||||
In the Zen1 microarchitecure, there is one divider in the pipeline which
|
||||
services uops from both threads. In the case of #DE, the latched result from
|
||||
the previous DIV to execute will be forwarded speculatively.
|
||||
|
||||
This is an interesting covert channel that allows two threads to communicate
|
||||
without any system calls. In also allows userspace to obtain the result of
|
||||
the most recent DIV instruction executed (even speculatively) in the core,
|
||||
which can be from a higher privilege context.
|
||||
|
||||
Scrub the result from the divider by executing a non-faulting divide. This
|
||||
needs performing on the exit-to-guest paths, and ist_exit-to-Xen.
|
||||
|
||||
Alternatives in IST context is believed safe now that it's done in NMI
|
||||
context.
|
||||
|
||||
This is XSA-439 / CVE-2023-20588.
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
(cherry picked from commit b5926c6ecf05c28ee99c6248c42d691ccbf0c315)
|
||||
---
|
||||
docs/misc/xen-command-line.pandoc | 6 +++-
|
||||
xen/arch/x86/hvm/svm/entry.S | 1 +
|
||||
xen/arch/x86/spec_ctrl.c | 49 ++++++++++++++++++++++++++++-
|
||||
xen/include/asm-x86/cpufeatures.h | 2 +-
|
||||
xen/include/asm-x86/spec_ctrl_asm.h | 17 ++++++++++
|
||||
5 files changed, 72 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/docs/misc/xen-command-line.pandoc b/docs/misc/xen-command-line.pandoc
|
||||
index a37a3890d1..a7a1362bac 100644
|
||||
--- a/docs/misc/xen-command-line.pandoc
|
||||
+++ b/docs/misc/xen-command-line.pandoc
|
||||
@@ -2263,7 +2263,7 @@ By default SSBD will be mitigated at runtime (i.e `ssbd=runtime`).
|
||||
> {msr-sc,rsb,md-clear,ibpb-entry}=<bool>|{pv,hvm}=<bool>,
|
||||
> bti-thunk=retpoline|lfence|jmp, {ibrs,ibpb,ssbd,psfd,
|
||||
> eager-fpu,l1d-flush,branch-harden,srb-lock,
|
||||
-> unpriv-mmio,gds-mit}=<bool> ]`
|
||||
+> unpriv-mmio,gds-mit,div-scrub}=<bool> ]`
|
||||
|
||||
Controls for speculative execution sidechannel mitigations. By default, Xen
|
||||
will pick the most appropriate mitigations based on compiled in support,
|
||||
@@ -2383,6 +2383,10 @@ has elected not to lock the configuration, Xen will use GDS_CTRL to mitigate
|
||||
GDS with. Otherwise, Xen will mitigate by disabling AVX, which blocks the use
|
||||
of the AVX2 Gather instructions.
|
||||
|
||||
+On all hardware, the `div-scrub=` option can be used to force or prevent Xen
|
||||
+from mitigating the DIV-leakage vulnerability. By default, Xen will mitigate
|
||||
+DIV-leakage on hardware believed to be vulnerable.
|
||||
+
|
||||
### sync_console
|
||||
> `= <boolean>`
|
||||
|
||||
diff --git a/xen/arch/x86/hvm/svm/entry.S b/xen/arch/x86/hvm/svm/entry.S
|
||||
index 0ff4008060..ad5ca50c12 100644
|
||||
--- a/xen/arch/x86/hvm/svm/entry.S
|
||||
+++ b/xen/arch/x86/hvm/svm/entry.S
|
||||
@@ -72,6 +72,7 @@ __UNLIKELY_END(nsvm_hap)
|
||||
1: /* No Spectre v1 concerns. Execution will hit VMRUN imminently. */
|
||||
.endm
|
||||
ALTERNATIVE "", svm_vmentry_spec_ctrl, X86_FEATURE_SC_MSR_HVM
|
||||
+ ALTERNATIVE "", DO_SPEC_CTRL_DIV, X86_FEATURE_SC_DIV
|
||||
|
||||
pop %r15
|
||||
pop %r14
|
||||
diff --git a/xen/arch/x86/spec_ctrl.c b/xen/arch/x86/spec_ctrl.c
|
||||
index 082445179d..6e82a126a3 100644
|
||||
--- a/xen/arch/x86/spec_ctrl.c
|
||||
+++ b/xen/arch/x86/spec_ctrl.c
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <xen/param.h>
|
||||
#include <xen/warning.h>
|
||||
|
||||
+#include <asm/amd.h>
|
||||
#include <asm/hvm/svm/svm.h>
|
||||
#include <asm/microcode.h>
|
||||
#include <asm/msr.h>
|
||||
@@ -78,6 +79,7 @@ static int8_t __initdata opt_srb_lock = -1;
|
||||
static bool __initdata opt_unpriv_mmio;
|
||||
static bool __read_mostly opt_fb_clear_mmio;
|
||||
static int8_t __initdata opt_gds_mit = -1;
|
||||
+static int8_t __initdata opt_div_scrub = -1;
|
||||
|
||||
static int __init parse_spec_ctrl(const char *s)
|
||||
{
|
||||
@@ -132,6 +134,7 @@ static int __init parse_spec_ctrl(const char *s)
|
||||
opt_srb_lock = 0;
|
||||
opt_unpriv_mmio = false;
|
||||
opt_gds_mit = 0;
|
||||
+ opt_div_scrub = 0;
|
||||
}
|
||||
else if ( val > 0 )
|
||||
rc = -EINVAL;
|
||||
@@ -284,6 +287,8 @@ static int __init parse_spec_ctrl(const char *s)
|
||||
opt_unpriv_mmio = val;
|
||||
else if ( (val = parse_boolean("gds-mit", s, ss)) >= 0 )
|
||||
opt_gds_mit = val;
|
||||
+ else if ( (val = parse_boolean("div-scrub", s, ss)) >= 0 )
|
||||
+ opt_div_scrub = val;
|
||||
else
|
||||
rc = -EINVAL;
|
||||
|
||||
@@ -484,7 +489,7 @@ static void __init print_details(enum ind_thunk thunk)
|
||||
"\n");
|
||||
|
||||
/* Settings for Xen's protection, irrespective of guests. */
|
||||
- printk(" Xen settings: BTI-Thunk %s, SPEC_CTRL: %s%s%s%s%s, Other:%s%s%s%s%s\n",
|
||||
+ printk(" Xen settings: BTI-Thunk %s, SPEC_CTRL: %s%s%s%s%s, Other:%s%s%s%s%s%s\n",
|
||||
thunk == THUNK_NONE ? "N/A" :
|
||||
thunk == THUNK_RETPOLINE ? "RETPOLINE" :
|
||||
thunk == THUNK_LFENCE ? "LFENCE" :
|
||||
@@ -509,6 +514,7 @@ static void __init print_details(enum ind_thunk thunk)
|
||||
opt_l1d_flush ? " L1D_FLUSH" : "",
|
||||
opt_md_clear_pv || opt_md_clear_hvm ||
|
||||
opt_fb_clear_mmio ? " VERW" : "",
|
||||
+ opt_div_scrub ? " DIV" : "",
|
||||
opt_branch_harden ? " BRANCH_HARDEN" : "");
|
||||
|
||||
/* L1TF diagnostics, printed if vulnerable or PV shadowing is in use. */
|
||||
@@ -962,6 +968,45 @@ static void __init srso_calculations(bool hw_smt_enabled)
|
||||
setup_force_cpu_cap(X86_FEATURE_SRSO_NO);
|
||||
}
|
||||
|
||||
+/*
|
||||
+ * The Div leakage issue is specific to the AMD Zen1 microarchitecure.
|
||||
+ *
|
||||
+ * However, there's no $FOO_NO bit defined, so if we're virtualised we have no
|
||||
+ * hope of spotting the case where we might move to vulnerable hardware. We
|
||||
+ * also can't make any useful conclusion about SMT-ness.
|
||||
+ *
|
||||
+ * Don't check the hypervisor bit, so at least we do the safe thing when
|
||||
+ * booting on something that looks like a Zen1 CPU.
|
||||
+ */
|
||||
+static bool __init has_div_vuln(void)
|
||||
+{
|
||||
+ if ( !(boot_cpu_data.x86_vendor &
|
||||
+ (X86_VENDOR_AMD | X86_VENDOR_HYGON)) )
|
||||
+ return false;
|
||||
+
|
||||
+ if ( boot_cpu_data.x86 != 0x17 && boot_cpu_data.x86 != 0x18 )
|
||||
+ return false;
|
||||
+
|
||||
+ return is_zen1_uarch();
|
||||
+}
|
||||
+
|
||||
+static void __init div_calculations(bool hw_smt_enabled)
|
||||
+{
|
||||
+ bool cpu_bug_div = has_div_vuln();
|
||||
+
|
||||
+ if ( opt_div_scrub == -1 )
|
||||
+ opt_div_scrub = cpu_bug_div;
|
||||
+
|
||||
+ if ( opt_div_scrub )
|
||||
+ setup_force_cpu_cap(X86_FEATURE_SC_DIV);
|
||||
+
|
||||
+ if ( opt_smt == -1 && !cpu_has_hypervisor && cpu_bug_div && hw_smt_enabled )
|
||||
+ warning_add(
|
||||
+ "Booted on leaky-DIV hardware with SMT/Hyperthreading\n"
|
||||
+ "enabled. Please assess your configuration and choose an\n"
|
||||
+ "explicit 'smt=<bool>' setting. See XSA-439.\n");
|
||||
+}
|
||||
+
|
||||
static void __init ibpb_calculations(void)
|
||||
{
|
||||
bool def_ibpb_entry = false;
|
||||
@@ -1716,6 +1761,8 @@ void __init init_speculation_mitigations(void)
|
||||
|
||||
ibpb_calculations();
|
||||
|
||||
+ div_calculations(hw_smt_enabled);
|
||||
+
|
||||
/* Check whether Eager FPU should be enabled by default. */
|
||||
if ( opt_eager_fpu == -1 )
|
||||
opt_eager_fpu = should_use_eager_fpu();
|
||||
diff --git a/xen/include/asm-x86/cpufeatures.h b/xen/include/asm-x86/cpufeatures.h
|
||||
index ccf9d7287c..70b93b6b44 100644
|
||||
--- a/xen/include/asm-x86/cpufeatures.h
|
||||
+++ b/xen/include/asm-x86/cpufeatures.h
|
||||
@@ -35,7 +35,7 @@ XEN_CPUFEATURE(SC_RSB_HVM, X86_SYNTH(19)) /* RSB overwrite needed for HVM
|
||||
XEN_CPUFEATURE(XEN_SELFSNOOP, X86_SYNTH(20)) /* SELFSNOOP gets used by Xen itself */
|
||||
XEN_CPUFEATURE(SC_MSR_IDLE, X86_SYNTH(21)) /* Clear MSR_SPEC_CTRL on idle */
|
||||
XEN_CPUFEATURE(XEN_LBR, X86_SYNTH(22)) /* Xen uses MSR_DEBUGCTL.LBR */
|
||||
-/* Bits 23 unused. */
|
||||
+XEN_CPUFEATURE(SC_DIV, X86_SYNTH(23)) /* DIV scrub needed */
|
||||
XEN_CPUFEATURE(SC_RSB_IDLE, X86_SYNTH(24)) /* RSB overwrite needed for idle. */
|
||||
XEN_CPUFEATURE(SC_VERW_IDLE, X86_SYNTH(25)) /* VERW used by Xen for idle */
|
||||
XEN_CPUFEATURE(XEN_SHSTK, X86_SYNTH(26)) /* Xen uses CET Shadow Stacks */
|
||||
diff --git a/xen/include/asm-x86/spec_ctrl_asm.h b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
index 8a816b8cf6..0e69971f66 100644
|
||||
--- a/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
+++ b/xen/include/asm-x86/spec_ctrl_asm.h
|
||||
@@ -182,6 +182,19 @@
|
||||
.L\@_verw_skip:
|
||||
.endm
|
||||
|
||||
+.macro DO_SPEC_CTRL_DIV
|
||||
+/*
|
||||
+ * Requires nothing
|
||||
+ * Clobbers %rax
|
||||
+ *
|
||||
+ * Issue a DIV for its flushing side effect (Zen1 uarch specific). Any
|
||||
+ * non-faulting DIV will do; a byte DIV has least latency, and doesn't clobber
|
||||
+ * %rdx.
|
||||
+ */
|
||||
+ mov $1, %eax
|
||||
+ div %al
|
||||
+.endm
|
||||
+
|
||||
.macro DO_SPEC_CTRL_ENTRY maybexen:req
|
||||
/*
|
||||
* Requires %rsp=regs (also cpuinfo if !maybexen)
|
||||
@@ -284,6 +297,8 @@
|
||||
ALTERNATIVE "", DO_SPEC_CTRL_EXIT_TO_GUEST, X86_FEATURE_SC_MSR_PV
|
||||
|
||||
DO_SPEC_CTRL_COND_VERW
|
||||
+
|
||||
+ ALTERNATIVE "", DO_SPEC_CTRL_DIV, X86_FEATURE_SC_DIV
|
||||
.endm
|
||||
|
||||
/*
|
||||
@@ -396,6 +411,8 @@ UNLIKELY_DISPATCH_LABEL(\@_serialise):
|
||||
verw STACK_CPUINFO_FIELD(verw_sel)(%r14)
|
||||
.L\@_skip_verw:
|
||||
|
||||
+ ALTERNATIVE "", DO_SPEC_CTRL_DIV, X86_FEATURE_SC_DIV
|
||||
+
|
||||
.L\@_skip_ist_exit:
|
||||
.endm
|
||||
|
||||
--
|
||||
2.41.0
|
||||
|
||||
58
xsa440-4.17.patch
Normal file
58
xsa440-4.17.patch
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
From 5d8b3d1ec98e56155d9650d7f4a70cd8ba9dc27d Mon Sep 17 00:00:00 2001
|
||||
From: Julien Grall <jgrall@amazon.com>
|
||||
Date: Fri, 22 Sep 2023 11:32:16 +0100
|
||||
Subject: tools/xenstored: domain_entry_fix(): Handle conflicting transaction
|
||||
|
||||
The function domain_entry_fix() will be initially called to check if the
|
||||
quota is correct before attempt to commit any nodes. So it would be
|
||||
possible that accounting is temporarily negative. This is the case
|
||||
in the following sequence:
|
||||
|
||||
1) Create 50 nodes
|
||||
2) Start two transactions
|
||||
3) Delete all the nodes in each transaction
|
||||
4) Commit the two transactions
|
||||
|
||||
Because the first transaction will have succeed and updated the
|
||||
accounting, there is no guarantee that 'd->nbentry + num' will still
|
||||
be above 0. So the assert() would be triggered.
|
||||
The assert() was introduced in dbef1f748289 ("tools/xenstore: simplify
|
||||
and fix per domain node accounting") with the assumption that the
|
||||
value can't be negative. As this is not true revert to the original
|
||||
check but restricted to the path where we don't update. Take the
|
||||
opportunity to explain the rationale behind the check.
|
||||
|
||||
This CVE-2023-34323 / XSA-440.
|
||||
|
||||
Reported-by: Stanislav Uschakow <suschako@amazon.de>
|
||||
Fixes: dbef1f748289 ("tools/xenstore: simplify and fix per domain node accounting")
|
||||
Signed-off-by: Julien Grall <jgrall@amazon.com>
|
||||
Reviewed-by: Juergen Gross <jgross@suse.com>
|
||||
|
||||
diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c
|
||||
index aa86892fed9e..6074df210c6e 100644
|
||||
--- a/tools/xenstore/xenstored_domain.c
|
||||
+++ b/tools/xenstore/xenstored_domain.c
|
||||
@@ -1094,10 +1094,20 @@ int domain_entry_fix(unsigned int domid, int num, bool update)
|
||||
}
|
||||
|
||||
cnt = d->nbentry + num;
|
||||
- assert(cnt >= 0);
|
||||
|
||||
- if (update)
|
||||
+ if (update) {
|
||||
+ assert(cnt >= 0);
|
||||
d->nbentry = cnt;
|
||||
+ } else if (cnt < 0) {
|
||||
+ /*
|
||||
+ * In a transaction when a node is being added/removed AND
|
||||
+ * the same node has been added/removed outside the
|
||||
+ * transaction in parallel, the result value may be negative.
|
||||
+ * This is no problem, as the transaction will fail due to
|
||||
+ * the resulting conflict. So override 'cnt'.
|
||||
+ */
|
||||
+ cnt = 0;
|
||||
+ }
|
||||
|
||||
return domid_is_unprivileged(domid) ? cnt : 0;
|
||||
}
|
||||
185
xsa442-4.17.patch
Normal file
185
xsa442-4.17.patch
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
From 5b2ccb60ff22fbff44dd66214c2956a434ee6271 Mon Sep 17 00:00:00 2001
|
||||
From: Roger Pau Monne <roger.pau@citrix.com>
|
||||
Date: Tue, 13 Jun 2023 15:01:05 +0200
|
||||
Subject: [PATCH] iommu/amd-vi: flush IOMMU TLB when flushing the DTE
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The caching invalidation guidelines from the AMD-Vi specification (48882—Rev
|
||||
3.07-PUB—Oct 2022) seem to be misleading on some hardware, as devices will
|
||||
malfunction (see stale DMA mappings) if some fields of the DTE are updated but
|
||||
the IOMMU TLB is not flushed. This has been observed in practice on AMD
|
||||
systems. Due to the lack of guidance from the currently published
|
||||
specification this patch aims to increase the flushing done in order to prevent
|
||||
device malfunction.
|
||||
|
||||
In order to fix, issue an INVALIDATE_IOMMU_PAGES command from
|
||||
amd_iommu_flush_device(), flushing all the address space. Note this requires
|
||||
callers to be adjusted in order to pass the DomID on the DTE previous to the
|
||||
modification.
|
||||
|
||||
Some call sites don't provide a valid DomID to amd_iommu_flush_device() in
|
||||
order to avoid the flush. That's because the device had address translations
|
||||
disabled and hence the previous DomID on the DTE is not valid. Note the
|
||||
current logic relies on the entity disabling address translations to also flush
|
||||
the TLB of the in use DomID.
|
||||
|
||||
Device I/O TLB flushing when ATS are enabled is not covered by the current
|
||||
change, as ATS usage is not security supported.
|
||||
|
||||
This is XSA-442 / CVE-2023-34326
|
||||
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/drivers/passthrough/amd/iommu.h | 3 ++-
|
||||
xen/drivers/passthrough/amd/iommu_cmd.c | 10 +++++++++-
|
||||
xen/drivers/passthrough/amd/iommu_guest.c | 5 +++--
|
||||
xen/drivers/passthrough/amd/iommu_init.c | 6 +++++-
|
||||
xen/drivers/passthrough/amd/pci_amd_iommu.c | 14 ++++++++++----
|
||||
5 files changed, 29 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/xen/drivers/passthrough/amd/iommu.h b/xen/drivers/passthrough/amd/iommu.h
|
||||
index 5429ada58ef5..a58be28bf96d 100644
|
||||
--- a/xen/drivers/passthrough/amd/iommu.h
|
||||
+++ b/xen/drivers/passthrough/amd/iommu.h
|
||||
@@ -283,7 +283,8 @@ void amd_iommu_flush_pages(struct domain *d, unsigned long dfn,
|
||||
unsigned int order);
|
||||
void amd_iommu_flush_iotlb(u8 devfn, const struct pci_dev *pdev,
|
||||
uint64_t gaddr, unsigned int order);
|
||||
-void amd_iommu_flush_device(struct amd_iommu *iommu, uint16_t bdf);
|
||||
+void amd_iommu_flush_device(struct amd_iommu *iommu, uint16_t bdf,
|
||||
+ domid_t domid);
|
||||
void amd_iommu_flush_intremap(struct amd_iommu *iommu, uint16_t bdf);
|
||||
void amd_iommu_flush_all_caches(struct amd_iommu *iommu);
|
||||
|
||||
diff --git a/xen/drivers/passthrough/amd/iommu_cmd.c b/xen/drivers/passthrough/amd/iommu_cmd.c
|
||||
index 40ddf366bb4d..cb28b36abc38 100644
|
||||
--- a/xen/drivers/passthrough/amd/iommu_cmd.c
|
||||
+++ b/xen/drivers/passthrough/amd/iommu_cmd.c
|
||||
@@ -363,10 +363,18 @@ void amd_iommu_flush_pages(struct domain *d,
|
||||
_amd_iommu_flush_pages(d, __dfn_to_daddr(dfn), order);
|
||||
}
|
||||
|
||||
-void amd_iommu_flush_device(struct amd_iommu *iommu, uint16_t bdf)
|
||||
+void amd_iommu_flush_device(struct amd_iommu *iommu, uint16_t bdf,
|
||||
+ domid_t domid)
|
||||
{
|
||||
invalidate_dev_table_entry(iommu, bdf);
|
||||
flush_command_buffer(iommu, 0);
|
||||
+
|
||||
+ /* Also invalidate IOMMU TLB entries when flushing the DTE. */
|
||||
+ if ( domid != DOMID_INVALID )
|
||||
+ {
|
||||
+ invalidate_iommu_pages(iommu, INV_IOMMU_ALL_PAGES_ADDRESS, domid, 0);
|
||||
+ flush_command_buffer(iommu, 0);
|
||||
+ }
|
||||
}
|
||||
|
||||
void amd_iommu_flush_intremap(struct amd_iommu *iommu, uint16_t bdf)
|
||||
diff --git a/xen/drivers/passthrough/amd/iommu_guest.c b/xen/drivers/passthrough/amd/iommu_guest.c
|
||||
index 80a331f546ed..be86bce6fb03 100644
|
||||
--- a/xen/drivers/passthrough/amd/iommu_guest.c
|
||||
+++ b/xen/drivers/passthrough/amd/iommu_guest.c
|
||||
@@ -385,7 +385,7 @@ static int do_completion_wait(struct domain *d, cmd_entry_t *cmd)
|
||||
|
||||
static int do_invalidate_dte(struct domain *d, cmd_entry_t *cmd)
|
||||
{
|
||||
- uint16_t gbdf, mbdf, req_id, gdom_id, hdom_id;
|
||||
+ uint16_t gbdf, mbdf, req_id, gdom_id, hdom_id, prev_domid;
|
||||
struct amd_iommu_dte *gdte, *mdte, *dte_base;
|
||||
struct amd_iommu *iommu = NULL;
|
||||
struct guest_iommu *g_iommu;
|
||||
@@ -445,13 +445,14 @@ static int do_invalidate_dte(struct domain *d, cmd_entry_t *cmd)
|
||||
req_id = get_dma_requestor_id(iommu->seg, mbdf);
|
||||
dte_base = iommu->dev_table.buffer;
|
||||
mdte = &dte_base[req_id];
|
||||
+ prev_domid = mdte->domain_id;
|
||||
|
||||
spin_lock_irqsave(&iommu->lock, flags);
|
||||
dte_set_gcr3_table(mdte, hdom_id, gcr3_mfn << PAGE_SHIFT, gv, glx);
|
||||
|
||||
spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
|
||||
- amd_iommu_flush_device(iommu, req_id);
|
||||
+ amd_iommu_flush_device(iommu, req_id, prev_domid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
diff --git a/xen/drivers/passthrough/amd/iommu_init.c b/xen/drivers/passthrough/amd/iommu_init.c
|
||||
index 166570648d26..101a60ce1794 100644
|
||||
--- a/xen/drivers/passthrough/amd/iommu_init.c
|
||||
+++ b/xen/drivers/passthrough/amd/iommu_init.c
|
||||
@@ -1547,7 +1547,11 @@ static int cf_check _invalidate_all_devices(
|
||||
req_id = ivrs_mappings[bdf].dte_requestor_id;
|
||||
if ( iommu )
|
||||
{
|
||||
- amd_iommu_flush_device(iommu, req_id);
|
||||
+ /*
|
||||
+ * IOMMU TLB flush performed separately (see
|
||||
+ * invalidate_all_domain_pages()).
|
||||
+ */
|
||||
+ amd_iommu_flush_device(iommu, req_id, DOMID_INVALID);
|
||||
amd_iommu_flush_intremap(iommu, req_id);
|
||||
}
|
||||
}
|
||||
diff --git a/xen/drivers/passthrough/amd/pci_amd_iommu.c b/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
index 94e37755064b..8641b84712a0 100644
|
||||
--- a/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
+++ b/xen/drivers/passthrough/amd/pci_amd_iommu.c
|
||||
@@ -192,10 +192,13 @@ static int __must_check amd_iommu_setup_domain_device(
|
||||
|
||||
spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
|
||||
- amd_iommu_flush_device(iommu, req_id);
|
||||
+ /* DTE didn't have DMA translations enabled, do not flush the TLB. */
|
||||
+ amd_iommu_flush_device(iommu, req_id, DOMID_INVALID);
|
||||
}
|
||||
else if ( dte->pt_root != mfn_x(page_to_mfn(root_pg)) )
|
||||
{
|
||||
+ domid_t prev_domid = dte->domain_id;
|
||||
+
|
||||
/*
|
||||
* Strictly speaking if the device is the only one with this requestor
|
||||
* ID, it could be allowed to be re-assigned regardless of unity map
|
||||
@@ -252,7 +255,7 @@ static int __must_check amd_iommu_setup_domain_device(
|
||||
|
||||
spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
|
||||
- amd_iommu_flush_device(iommu, req_id);
|
||||
+ amd_iommu_flush_device(iommu, req_id, prev_domid);
|
||||
}
|
||||
else
|
||||
spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
@@ -421,6 +424,8 @@ static void amd_iommu_disable_domain_device(const struct domain *domain,
|
||||
spin_lock_irqsave(&iommu->lock, flags);
|
||||
if ( dte->tv || dte->v )
|
||||
{
|
||||
+ domid_t prev_domid = dte->domain_id;
|
||||
+
|
||||
/* See the comment in amd_iommu_setup_device_table(). */
|
||||
dte->int_ctl = IOMMU_DEV_TABLE_INT_CONTROL_ABORTED;
|
||||
smp_wmb();
|
||||
@@ -439,7 +444,7 @@ static void amd_iommu_disable_domain_device(const struct domain *domain,
|
||||
|
||||
spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
|
||||
- amd_iommu_flush_device(iommu, req_id);
|
||||
+ amd_iommu_flush_device(iommu, req_id, prev_domid);
|
||||
|
||||
AMD_IOMMU_DEBUG("Disable: device id = %#x, "
|
||||
"domain = %d, paging mode = %d\n",
|
||||
@@ -610,7 +615,8 @@ static int cf_check amd_iommu_add_device(u8 devfn, struct pci_dev *pdev)
|
||||
|
||||
spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
|
||||
- amd_iommu_flush_device(iommu, bdf);
|
||||
+ /* DTE didn't have DMA translations enabled, do not flush the TLB. */
|
||||
+ amd_iommu_flush_device(iommu, bdf, DOMID_INVALID);
|
||||
}
|
||||
|
||||
if ( amd_iommu_reserve_domain_unity_map(
|
||||
--
|
||||
2.42.0
|
||||
|
||||
70
xsa443-4.16-01.patch
Normal file
70
xsa443-4.16-01.patch
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
From c4d597f63832a53bbb1b826af7a4677e40e9fded Mon Sep 17 00:00:00 2001
|
||||
From: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Date: Thu, 14 Sep 2023 13:22:50 +0100
|
||||
Subject: [PATCH 01/11] libfsimage/xfs: Remove dead code
|
||||
|
||||
xfs_info.agnolog (and related code) and XFS_INO_AGBNO_BITS are dead code
|
||||
that serve no purpose.
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
tools/libfsimage/xfs/fsys_xfs.c | 18 ------------------
|
||||
1 file changed, 18 deletions(-)
|
||||
|
||||
diff --git a/tools/libfsimage/xfs/fsys_xfs.c b/tools/libfsimage/xfs/fsys_xfs.c
|
||||
index d735a88e55f3..2800699f5985 100644
|
||||
--- a/tools/libfsimage/xfs/fsys_xfs.c
|
||||
+++ b/tools/libfsimage/xfs/fsys_xfs.c
|
||||
@@ -37,7 +37,6 @@ struct xfs_info {
|
||||
int blklog;
|
||||
int inopblog;
|
||||
int agblklog;
|
||||
- int agnolog;
|
||||
unsigned int nextents;
|
||||
xfs_daddr_t next;
|
||||
xfs_daddr_t daddr;
|
||||
@@ -65,9 +64,7 @@ static struct xfs_info xfs;
|
||||
|
||||
#define XFS_INO_MASK(k) ((xfs_uint32_t)((1ULL << (k)) - 1))
|
||||
#define XFS_INO_OFFSET_BITS xfs.inopblog
|
||||
-#define XFS_INO_AGBNO_BITS xfs.agblklog
|
||||
#define XFS_INO_AGINO_BITS (xfs.agblklog + xfs.inopblog)
|
||||
-#define XFS_INO_AGNO_BITS xfs.agnolog
|
||||
|
||||
static inline xfs_agblock_t
|
||||
agino2agbno (xfs_agino_t agino)
|
||||
@@ -149,20 +146,6 @@ xt_len (xfs_bmbt_rec_32_t *r)
|
||||
return le32(r->l3) & mask32lo(21);
|
||||
}
|
||||
|
||||
-static inline int
|
||||
-xfs_highbit32(xfs_uint32_t v)
|
||||
-{
|
||||
- int i;
|
||||
-
|
||||
- if (--v) {
|
||||
- for (i = 0; i < 31; i++, v >>= 1) {
|
||||
- if (v == 0)
|
||||
- return i;
|
||||
- }
|
||||
- }
|
||||
- return 0;
|
||||
-}
|
||||
-
|
||||
static int
|
||||
isinxt (xfs_fileoff_t key, xfs_fileoff_t offset, xfs_filblks_t len)
|
||||
{
|
||||
@@ -472,7 +455,6 @@ xfs_mount (fsi_file_t *ffi, const char *options)
|
||||
|
||||
xfs.inopblog = super.sb_inopblog;
|
||||
xfs.agblklog = super.sb_agblklog;
|
||||
- xfs.agnolog = xfs_highbit32 (le32(super.sb_agcount));
|
||||
|
||||
xfs.btnode_ptr0_off =
|
||||
((xfs.bsize - sizeof(xfs_btree_block_t)) /
|
||||
--
|
||||
2.42.0
|
||||
|
||||
32
xsa443-4.16-02.patch
Normal file
32
xsa443-4.16-02.patch
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
From f75b0a70da392672fb7d9feed2a9e9515d74df2c Mon Sep 17 00:00:00 2001
|
||||
From: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Date: Thu, 14 Sep 2023 13:22:51 +0100
|
||||
Subject: [PATCH 02/11] libfsimage/xfs: Amend mask32lo() to allow the value 32
|
||||
|
||||
agblklog could plausibly be 32, but that would overflow this shift.
|
||||
Perform the shift as ULL and cast to u32 at the end instead.
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Acked-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
tools/libfsimage/xfs/fsys_xfs.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/tools/libfsimage/xfs/fsys_xfs.c b/tools/libfsimage/xfs/fsys_xfs.c
|
||||
index 2800699f5985..4720bb4505c8 100644
|
||||
--- a/tools/libfsimage/xfs/fsys_xfs.c
|
||||
+++ b/tools/libfsimage/xfs/fsys_xfs.c
|
||||
@@ -60,7 +60,7 @@ static struct xfs_info xfs;
|
||||
#define inode ((xfs_dinode_t *)((char *)FSYS_BUF + 8192))
|
||||
#define icore (inode->di_core)
|
||||
|
||||
-#define mask32lo(n) (((xfs_uint32_t)1 << (n)) - 1)
|
||||
+#define mask32lo(n) ((xfs_uint32_t)((1ull << (n)) - 1))
|
||||
|
||||
#define XFS_INO_MASK(k) ((xfs_uint32_t)((1ULL << (k)) - 1))
|
||||
#define XFS_INO_OFFSET_BITS xfs.inopblog
|
||||
--
|
||||
2.42.0
|
||||
|
||||
137
xsa443-4.16-03.patch
Normal file
137
xsa443-4.16-03.patch
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
From 25fae23b32ee4d990ae11368ee21e28e66dbfa25 Mon Sep 17 00:00:00 2001
|
||||
From: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Date: Thu, 14 Sep 2023 13:22:52 +0100
|
||||
Subject: [PATCH 03/11] libfsimage/xfs: Sanity-check the superblock during
|
||||
mounts
|
||||
|
||||
Sanity-check the XFS superblock for wellformedness at the mount handler.
|
||||
This forces pygrub to abort parsing a potentially malformed filesystem and
|
||||
ensures the invariants assumed throughout the rest of the code hold.
|
||||
|
||||
Also, derive parameters from previously sanitized parameters where possible
|
||||
(rather than reading them off the superblock)
|
||||
|
||||
The code doesn't try to avoid overflowing the end of the disk, because
|
||||
that's an unlikely and benign error. Parameters used in calculations of
|
||||
xfs_daddr_t (like the root inode index) aren't in critical need of being
|
||||
sanitized.
|
||||
|
||||
The sanitization of agblklog is basically checking that no obvious
|
||||
overflows happen on agblklog, and then ensuring agblocks is contained in
|
||||
the range (2^(sb_agblklog-1), 2^sb_agblklog].
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Reported-by: Ferdinand Nölscher <noelscher@google.com>
|
||||
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
tools/libfsimage/xfs/fsys_xfs.c | 48 ++++++++++++++++++++++++++-------
|
||||
tools/libfsimage/xfs/xfs.h | 12 +++++++++
|
||||
2 files changed, 50 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/tools/libfsimage/xfs/fsys_xfs.c b/tools/libfsimage/xfs/fsys_xfs.c
|
||||
index 4720bb4505c8..e4eb7e1ee26f 100644
|
||||
--- a/tools/libfsimage/xfs/fsys_xfs.c
|
||||
+++ b/tools/libfsimage/xfs/fsys_xfs.c
|
||||
@@ -17,6 +17,7 @@
|
||||
* along with this program; If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
+#include <stdbool.h>
|
||||
#include <xenfsimage_grub.h>
|
||||
#include "xfs.h"
|
||||
|
||||
@@ -433,29 +434,56 @@ first_dentry (fsi_file_t *ffi, xfs_ino_t *ino)
|
||||
return next_dentry (ffi, ino);
|
||||
}
|
||||
|
||||
+static bool
|
||||
+xfs_sb_is_invalid (const xfs_sb_t *super)
|
||||
+{
|
||||
+ return (le32(super->sb_magicnum) != XFS_SB_MAGIC)
|
||||
+ || ((le16(super->sb_versionnum) & XFS_SB_VERSION_NUMBITS) !=
|
||||
+ XFS_SB_VERSION_4)
|
||||
+ || (super->sb_inodelog < XFS_SB_INODELOG_MIN)
|
||||
+ || (super->sb_inodelog > XFS_SB_INODELOG_MAX)
|
||||
+ || (super->sb_blocklog < XFS_SB_BLOCKLOG_MIN)
|
||||
+ || (super->sb_blocklog > XFS_SB_BLOCKLOG_MAX)
|
||||
+ || (super->sb_blocklog < super->sb_inodelog)
|
||||
+ || (super->sb_agblklog > XFS_SB_AGBLKLOG_MAX)
|
||||
+ || ((1ull << super->sb_agblklog) < le32(super->sb_agblocks))
|
||||
+ || (((1ull << super->sb_agblklog) >> 1) >=
|
||||
+ le32(super->sb_agblocks))
|
||||
+ || ((super->sb_blocklog + super->sb_dirblklog) >=
|
||||
+ XFS_SB_DIRBLK_NUMBITS);
|
||||
+}
|
||||
+
|
||||
static int
|
||||
xfs_mount (fsi_file_t *ffi, const char *options)
|
||||
{
|
||||
xfs_sb_t super;
|
||||
|
||||
if (!devread (ffi, 0, 0, sizeof(super), (char *)&super)
|
||||
- || (le32(super.sb_magicnum) != XFS_SB_MAGIC)
|
||||
- || ((le16(super.sb_versionnum)
|
||||
- & XFS_SB_VERSION_NUMBITS) != XFS_SB_VERSION_4) ) {
|
||||
+ || xfs_sb_is_invalid(&super)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- xfs.bsize = le32 (super.sb_blocksize);
|
||||
- xfs.blklog = super.sb_blocklog;
|
||||
- xfs.bdlog = xfs.blklog - SECTOR_BITS;
|
||||
+ /*
|
||||
+ * Not sanitized. It's exclusively used to generate disk addresses,
|
||||
+ * so it's not important from a security standpoint.
|
||||
+ */
|
||||
xfs.rootino = le64 (super.sb_rootino);
|
||||
- xfs.isize = le16 (super.sb_inodesize);
|
||||
- xfs.agblocks = le32 (super.sb_agblocks);
|
||||
- xfs.dirbsize = xfs.bsize << super.sb_dirblklog;
|
||||
|
||||
- xfs.inopblog = super.sb_inopblog;
|
||||
+ /*
|
||||
+ * Sanitized to be consistent with each other, only used to
|
||||
+ * generate disk addresses, so it's safe
|
||||
+ */
|
||||
+ xfs.agblocks = le32 (super.sb_agblocks);
|
||||
xfs.agblklog = super.sb_agblklog;
|
||||
|
||||
+ /* Derived from sanitized parameters */
|
||||
+ xfs.bsize = 1 << super.sb_blocklog;
|
||||
+ xfs.blklog = super.sb_blocklog;
|
||||
+ xfs.bdlog = super.sb_blocklog - SECTOR_BITS;
|
||||
+ xfs.isize = 1 << super.sb_inodelog;
|
||||
+ xfs.dirbsize = 1 << (super.sb_blocklog + super.sb_dirblklog);
|
||||
+ xfs.inopblog = super.sb_blocklog - super.sb_inodelog;
|
||||
+
|
||||
xfs.btnode_ptr0_off =
|
||||
((xfs.bsize - sizeof(xfs_btree_block_t)) /
|
||||
(sizeof (xfs_bmbt_key_t) + sizeof (xfs_bmbt_ptr_t)))
|
||||
diff --git a/tools/libfsimage/xfs/xfs.h b/tools/libfsimage/xfs/xfs.h
|
||||
index 40699281e44d..b87e37d3d7e9 100644
|
||||
--- a/tools/libfsimage/xfs/xfs.h
|
||||
+++ b/tools/libfsimage/xfs/xfs.h
|
||||
@@ -134,6 +134,18 @@ typedef struct xfs_sb
|
||||
xfs_uint8_t sb_dummy[7]; /* padding */
|
||||
} xfs_sb_t;
|
||||
|
||||
+/* Bound taken from xfs.c in GRUB2. It doesn't exist in the spec */
|
||||
+#define XFS_SB_DIRBLK_NUMBITS 27
|
||||
+/* Implied by the XFS specification. The minimum block size is 512 octets */
|
||||
+#define XFS_SB_BLOCKLOG_MIN 9
|
||||
+/* Implied by the XFS specification. The maximum block size is 65536 octets */
|
||||
+#define XFS_SB_BLOCKLOG_MAX 16
|
||||
+/* Implied by the XFS specification. The minimum inode size is 256 octets */
|
||||
+#define XFS_SB_INODELOG_MIN 8
|
||||
+/* Implied by the XFS specification. The maximum inode size is 2048 octets */
|
||||
+#define XFS_SB_INODELOG_MAX 11
|
||||
+/* High bound for sb_agblklog */
|
||||
+#define XFS_SB_AGBLKLOG_MAX 32
|
||||
|
||||
/* those are from xfs_btree.h */
|
||||
|
||||
--
|
||||
2.42.0
|
||||
|
||||
60
xsa443-4.16-04.patch
Normal file
60
xsa443-4.16-04.patch
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
From e72c68e702dd930bc6013182bb44d3e8fbbb6bf4 Mon Sep 17 00:00:00 2001
|
||||
From: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Date: Thu, 14 Sep 2023 13:22:53 +0100
|
||||
Subject: [PATCH 04/11] libfsimage/xfs: Add compile-time check to libfsimage
|
||||
|
||||
Adds the common tools include folder to the -I compile flags
|
||||
of libfsimage. This allows us to use:
|
||||
xen-tools/common-macros.h:BUILD_BUG_ON()
|
||||
|
||||
With it, statically assert a sanitized "blocklog - SECTOR_BITS" cannot
|
||||
underflow.
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
tools/libfsimage/Rules.mk | 2 +-
|
||||
tools/libfsimage/xfs/fsys_xfs.c | 4 +++-
|
||||
2 files changed, 4 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/tools/libfsimage/Rules.mk b/tools/libfsimage/Rules.mk
|
||||
index bb6d42abb494..80598fb70aa7 100644
|
||||
--- a/tools/libfsimage/Rules.mk
|
||||
+++ b/tools/libfsimage/Rules.mk
|
||||
@@ -1,6 +1,6 @@
|
||||
include $(XEN_ROOT)/tools/Rules.mk
|
||||
|
||||
-CFLAGS += -Wno-unknown-pragmas -I$(XEN_ROOT)/tools/libfsimage/common/ -DFSIMAGE_FSDIR=\"$(FSDIR)\"
|
||||
+CFLAGS += -Wno-unknown-pragmas -I$(XEN_ROOT)/tools/libfsimage/common/ $(CFLAGS_xeninclude) -DFSIMAGE_FSDIR=\"$(FSDIR)\"
|
||||
CFLAGS += -Werror -D_GNU_SOURCE
|
||||
LDFLAGS += -L../common/
|
||||
|
||||
diff --git a/tools/libfsimage/xfs/fsys_xfs.c b/tools/libfsimage/xfs/fsys_xfs.c
|
||||
index e4eb7e1ee26f..4a8dd6f2397b 100644
|
||||
--- a/tools/libfsimage/xfs/fsys_xfs.c
|
||||
+++ b/tools/libfsimage/xfs/fsys_xfs.c
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <xenfsimage_grub.h>
|
||||
+#include <xen-tools/libs.h>
|
||||
#include "xfs.h"
|
||||
|
||||
#define MAX_LINK_COUNT 8
|
||||
@@ -477,9 +478,10 @@ xfs_mount (fsi_file_t *ffi, const char *options)
|
||||
xfs.agblklog = super.sb_agblklog;
|
||||
|
||||
/* Derived from sanitized parameters */
|
||||
+ BUILD_BUG_ON(XFS_SB_BLOCKLOG_MIN < SECTOR_BITS);
|
||||
+ xfs.bdlog = super.sb_blocklog - SECTOR_BITS;
|
||||
xfs.bsize = 1 << super.sb_blocklog;
|
||||
xfs.blklog = super.sb_blocklog;
|
||||
- xfs.bdlog = super.sb_blocklog - SECTOR_BITS;
|
||||
xfs.isize = 1 << super.sb_inodelog;
|
||||
xfs.dirbsize = 1 << (super.sb_blocklog + super.sb_dirblklog);
|
||||
xfs.inopblog = super.sb_blocklog - super.sb_inodelog;
|
||||
--
|
||||
2.42.0
|
||||
|
||||
59
xsa443-4.16-05.patch
Normal file
59
xsa443-4.16-05.patch
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
From 75fdc03c5a6b7fac0c3a5ac06a5beaac73aad36f Mon Sep 17 00:00:00 2001
|
||||
From: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Date: Mon, 25 Sep 2023 18:32:21 +0100
|
||||
Subject: [PATCH 05/11] tools/pygrub: Remove unnecessary hypercall
|
||||
|
||||
There's a hypercall being issued in order to determine whether PV64 is
|
||||
supported, but since Xen 4.3 that's strictly true so it's not required.
|
||||
|
||||
Plus, this way we can avoid mapping the privcmd interface altogether in the
|
||||
depriv pygrub.
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
---
|
||||
tools/pygrub/src/pygrub | 12 +-----------
|
||||
1 file changed, 1 insertion(+), 11 deletions(-)
|
||||
|
||||
diff --git a/tools/pygrub/src/pygrub b/tools/pygrub/src/pygrub
|
||||
index ce7ab0eb8cf3..ce4e07d3e823 100755
|
||||
--- a/tools/pygrub/src/pygrub
|
||||
+++ b/tools/pygrub/src/pygrub
|
||||
@@ -18,7 +18,6 @@ import os, sys, string, struct, tempfile, re, traceback, stat, errno
|
||||
import copy
|
||||
import logging
|
||||
import platform
|
||||
-import xen.lowlevel.xc
|
||||
|
||||
import curses, _curses, curses.textpad, curses.ascii
|
||||
import getopt
|
||||
@@ -668,14 +667,6 @@ def run_grub(file, entry, fs, cfg_args):
|
||||
|
||||
return grubcfg
|
||||
|
||||
-def supports64bitPVguest():
|
||||
- xc = xen.lowlevel.xc.xc()
|
||||
- caps = xc.xeninfo()['xen_caps'].split(" ")
|
||||
- for cap in caps:
|
||||
- if cap == "xen-3.0-x86_64":
|
||||
- return True
|
||||
- return False
|
||||
-
|
||||
# If nothing has been specified, look for a Solaris domU. If found, perform the
|
||||
# necessary tweaks.
|
||||
def sniff_solaris(fs, cfg):
|
||||
@@ -684,8 +675,7 @@ def sniff_solaris(fs, cfg):
|
||||
return cfg
|
||||
|
||||
if not cfg["kernel"]:
|
||||
- if supports64bitPVguest() and \
|
||||
- fs.file_exists("/platform/i86xpv/kernel/amd64/unix"):
|
||||
+ if fs.file_exists("/platform/i86xpv/kernel/amd64/unix"):
|
||||
cfg["kernel"] = "/platform/i86xpv/kernel/amd64/unix"
|
||||
cfg["ramdisk"] = "/platform/i86pc/amd64/boot_archive"
|
||||
elif fs.file_exists("/platform/i86xpv/kernel/unix"):
|
||||
--
|
||||
2.42.0
|
||||
|
||||
65
xsa443-4.16-06.patch
Normal file
65
xsa443-4.16-06.patch
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
From 1083a16f63461e844e9515ac4d35d48bf55785af Mon Sep 17 00:00:00 2001
|
||||
From: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Date: Mon, 25 Sep 2023 18:32:22 +0100
|
||||
Subject: [PATCH 06/11] tools/pygrub: Small refactors
|
||||
|
||||
Small tidy up to ensure output_directory always has a trailing '/' to ease
|
||||
concatenating paths and that `output` can only be a filename or None.
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
---
|
||||
tools/pygrub/src/pygrub | 10 +++++-----
|
||||
1 file changed, 5 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/tools/pygrub/src/pygrub b/tools/pygrub/src/pygrub
|
||||
index ce4e07d3e823..1042c05b8676 100755
|
||||
--- a/tools/pygrub/src/pygrub
|
||||
+++ b/tools/pygrub/src/pygrub
|
||||
@@ -793,7 +793,7 @@ if __name__ == "__main__":
|
||||
debug = False
|
||||
not_really = False
|
||||
output_format = "sxp"
|
||||
- output_directory = "/var/run/xen/pygrub"
|
||||
+ output_directory = "/var/run/xen/pygrub/"
|
||||
|
||||
# what was passed in
|
||||
incfg = { "kernel": None, "ramdisk": None, "args": "" }
|
||||
@@ -815,7 +815,8 @@ if __name__ == "__main__":
|
||||
usage()
|
||||
sys.exit()
|
||||
elif o in ("--output",):
|
||||
- output = a
|
||||
+ if a != "-":
|
||||
+ output = a
|
||||
elif o in ("--kernel",):
|
||||
incfg["kernel"] = a
|
||||
elif o in ("--ramdisk",):
|
||||
@@ -847,12 +848,11 @@ if __name__ == "__main__":
|
||||
if not os.path.isdir(a):
|
||||
print("%s is not an existing directory" % a)
|
||||
sys.exit(1)
|
||||
- output_directory = a
|
||||
+ output_directory = a + '/'
|
||||
|
||||
if debug:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
-
|
||||
try:
|
||||
os.makedirs(output_directory, 0o700)
|
||||
except OSError as e:
|
||||
@@ -861,7 +861,7 @@ if __name__ == "__main__":
|
||||
else:
|
||||
raise
|
||||
|
||||
- if output is None or output == "-":
|
||||
+ if output is None:
|
||||
fd = sys.stdout.fileno()
|
||||
else:
|
||||
fd = os.open(output, os.O_WRONLY)
|
||||
--
|
||||
2.42.0
|
||||
|
||||
105
xsa443-4.16-07.patch
Normal file
105
xsa443-4.16-07.patch
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
From 350db30e33f39af40c1e3752d73c0a30ef2d26e7 Mon Sep 17 00:00:00 2001
|
||||
From: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Date: Mon, 25 Sep 2023 18:32:23 +0100
|
||||
Subject: [PATCH 07/11] tools/pygrub: Open the output files earlier
|
||||
|
||||
This patch allows pygrub to get ahold of every RW file descriptor it needs
|
||||
early on. A later patch will clamp the filesystem it can access so it can't
|
||||
obtain any others.
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
---
|
||||
tools/pygrub/src/pygrub | 37 ++++++++++++++++++++++---------------
|
||||
1 file changed, 22 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/tools/pygrub/src/pygrub b/tools/pygrub/src/pygrub
|
||||
index 1042c05b8676..91e2ec2ab105 100755
|
||||
--- a/tools/pygrub/src/pygrub
|
||||
+++ b/tools/pygrub/src/pygrub
|
||||
@@ -738,8 +738,7 @@ if __name__ == "__main__":
|
||||
def usage():
|
||||
print("Usage: %s [-q|--quiet] [-i|--interactive] [-l|--list-entries] [-n|--not-really] [--output=] [--kernel=] [--ramdisk=] [--args=] [--entry=] [--output-directory=] [--output-format=sxp|simple|simple0] [--offset=] <image>" %(sys.argv[0],), file=sys.stderr)
|
||||
|
||||
- def copy_from_image(fs, file_to_read, file_type, output_directory,
|
||||
- not_really):
|
||||
+ def copy_from_image(fs, file_to_read, file_type, fd_dst, path_dst, not_really):
|
||||
if not_really:
|
||||
if fs.file_exists(file_to_read):
|
||||
return "<%s:%s>" % (file_type, file_to_read)
|
||||
@@ -750,21 +749,18 @@ if __name__ == "__main__":
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit("Error opening %s in guest" % file_to_read)
|
||||
- (tfd, ret) = tempfile.mkstemp(prefix="boot_"+file_type+".",
|
||||
- dir=output_directory)
|
||||
dataoff = 0
|
||||
while True:
|
||||
data = datafile.read(FS_READ_MAX, dataoff)
|
||||
if len(data) == 0:
|
||||
- os.close(tfd)
|
||||
+ os.close(fd_dst)
|
||||
del datafile
|
||||
- return ret
|
||||
+ return
|
||||
try:
|
||||
- os.write(tfd, data)
|
||||
+ os.write(fd_dst, data)
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
- os.close(tfd)
|
||||
- os.unlink(ret)
|
||||
+ os.unlink(path_dst)
|
||||
del datafile
|
||||
sys.exit("Error writing temporary copy of "+file_type)
|
||||
dataoff += len(data)
|
||||
@@ -861,6 +857,14 @@ if __name__ == "__main__":
|
||||
else:
|
||||
raise
|
||||
|
||||
+ if not_really:
|
||||
+ fd_kernel = path_kernel = fd_ramdisk = path_ramdisk = None
|
||||
+ else:
|
||||
+ (fd_kernel, path_kernel) = tempfile.mkstemp(prefix="boot_kernel.",
|
||||
+ dir=output_directory)
|
||||
+ (fd_ramdisk, path_ramdisk) = tempfile.mkstemp(prefix="boot_ramdisk.",
|
||||
+ dir=output_directory)
|
||||
+
|
||||
if output is None:
|
||||
fd = sys.stdout.fileno()
|
||||
else:
|
||||
@@ -920,20 +924,23 @@ if __name__ == "__main__":
|
||||
if fs is None:
|
||||
raise RuntimeError("Unable to find partition containing kernel")
|
||||
|
||||
- bootcfg["kernel"] = copy_from_image(fs, chosencfg["kernel"], "kernel",
|
||||
- output_directory, not_really)
|
||||
+ copy_from_image(fs, chosencfg["kernel"], "kernel",
|
||||
+ fd_kernel, path_kernel, not_really)
|
||||
+ bootcfg["kernel"] = path_kernel
|
||||
|
||||
if chosencfg["ramdisk"]:
|
||||
try:
|
||||
- bootcfg["ramdisk"] = copy_from_image(fs, chosencfg["ramdisk"],
|
||||
- "ramdisk", output_directory,
|
||||
- not_really)
|
||||
+ copy_from_image(fs, chosencfg["ramdisk"], "ramdisk",
|
||||
+ fd_ramdisk, path_ramdisk, not_really)
|
||||
except:
|
||||
if not not_really:
|
||||
- os.unlink(bootcfg["kernel"])
|
||||
+ os.unlink(path_kernel)
|
||||
raise
|
||||
+ bootcfg["ramdisk"] = path_ramdisk
|
||||
else:
|
||||
initrd = None
|
||||
+ if not not_really:
|
||||
+ os.unlink(path_ramdisk)
|
||||
|
||||
args = None
|
||||
if chosencfg["args"]:
|
||||
--
|
||||
2.42.0
|
||||
|
||||
126
xsa443-4.16-08.patch
Normal file
126
xsa443-4.16-08.patch
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
From 1548ad2291ec7a72ae6949c11d2e50cea135a48d Mon Sep 17 00:00:00 2001
|
||||
From: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Date: Mon, 25 Sep 2023 18:32:24 +0100
|
||||
Subject: [PATCH 08/11] tools/libfsimage: Export a new function to preload all
|
||||
plugins
|
||||
|
||||
This is work required in order to let pygrub operate in highly deprivileged
|
||||
chroot mode. This patch adds a function that preloads every plugin, hence
|
||||
ensuring that a on function exit, every shared library is loaded in memory.
|
||||
|
||||
The new "init" function is supposed to be used before depriv, but that's
|
||||
fine because it's not acting on untrusted data.
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
---
|
||||
tools/libfsimage/common/fsimage_plugin.c | 4 ++--
|
||||
tools/libfsimage/common/mapfile-GNU | 1 +
|
||||
tools/libfsimage/common/mapfile-SunOS | 1 +
|
||||
tools/libfsimage/common/xenfsimage.h | 8 ++++++++
|
||||
tools/pygrub/src/fsimage/fsimage.c | 15 +++++++++++++++
|
||||
5 files changed, 27 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/tools/libfsimage/common/fsimage_plugin.c b/tools/libfsimage/common/fsimage_plugin.c
|
||||
index de1412b4233a..d0cb9e96a654 100644
|
||||
--- a/tools/libfsimage/common/fsimage_plugin.c
|
||||
+++ b/tools/libfsimage/common/fsimage_plugin.c
|
||||
@@ -119,7 +119,7 @@ fail:
|
||||
return (-1);
|
||||
}
|
||||
|
||||
-static int load_plugins(void)
|
||||
+int fsi_init(void)
|
||||
{
|
||||
const char *fsdir = getenv("XEN_FSIMAGE_FSDIR");
|
||||
struct dirent *dp = NULL;
|
||||
@@ -180,7 +180,7 @@ int find_plugin(fsi_t *fsi, const char *path, const char *options)
|
||||
fsi_plugin_t *fp;
|
||||
int ret = 0;
|
||||
|
||||
- if (plugins == NULL && (ret = load_plugins()) != 0)
|
||||
+ if (plugins == NULL && (ret = fsi_init()) != 0)
|
||||
goto out;
|
||||
|
||||
for (fp = plugins; fp != NULL; fp = fp->fp_next) {
|
||||
diff --git a/tools/libfsimage/common/mapfile-GNU b/tools/libfsimage/common/mapfile-GNU
|
||||
index 26d4d7a69ec7..2d54d527d7f5 100644
|
||||
--- a/tools/libfsimage/common/mapfile-GNU
|
||||
+++ b/tools/libfsimage/common/mapfile-GNU
|
||||
@@ -1,6 +1,7 @@
|
||||
VERSION {
|
||||
libfsimage.so.1.0 {
|
||||
global:
|
||||
+ fsi_init;
|
||||
fsi_open_fsimage;
|
||||
fsi_close_fsimage;
|
||||
fsi_file_exists;
|
||||
diff --git a/tools/libfsimage/common/mapfile-SunOS b/tools/libfsimage/common/mapfile-SunOS
|
||||
index e99b90b65077..48deedb4252f 100644
|
||||
--- a/tools/libfsimage/common/mapfile-SunOS
|
||||
+++ b/tools/libfsimage/common/mapfile-SunOS
|
||||
@@ -1,5 +1,6 @@
|
||||
libfsimage.so.1.0 {
|
||||
global:
|
||||
+ fsi_init;
|
||||
fsi_open_fsimage;
|
||||
fsi_close_fsimage;
|
||||
fsi_file_exists;
|
||||
diff --git a/tools/libfsimage/common/xenfsimage.h b/tools/libfsimage/common/xenfsimage.h
|
||||
index 201abd54f23a..341883b2d71a 100644
|
||||
--- a/tools/libfsimage/common/xenfsimage.h
|
||||
+++ b/tools/libfsimage/common/xenfsimage.h
|
||||
@@ -35,6 +35,14 @@ extern C {
|
||||
typedef struct fsi fsi_t;
|
||||
typedef struct fsi_file fsi_file_t;
|
||||
|
||||
+/*
|
||||
+ * Optional initialization function. If invoked it loads the associated
|
||||
+ * dynamic libraries for the backends ahead of time. This is required if
|
||||
+ * the library is to run as part of a highly deprivileged executable, as
|
||||
+ * the libraries may not be reachable after depriv.
|
||||
+ */
|
||||
+int fsi_init(void);
|
||||
+
|
||||
fsi_t *fsi_open_fsimage(const char *, uint64_t, const char *);
|
||||
void fsi_close_fsimage(fsi_t *);
|
||||
|
||||
diff --git a/tools/pygrub/src/fsimage/fsimage.c b/tools/pygrub/src/fsimage/fsimage.c
|
||||
index 2ebbbe35df92..92fbf2851f01 100644
|
||||
--- a/tools/pygrub/src/fsimage/fsimage.c
|
||||
+++ b/tools/pygrub/src/fsimage/fsimage.c
|
||||
@@ -286,6 +286,15 @@ fsimage_getbootstring(PyObject *o, PyObject *args)
|
||||
return Py_BuildValue("s", bootstring);
|
||||
}
|
||||
|
||||
+static PyObject *
|
||||
+fsimage_init(PyObject *o, PyObject *args)
|
||||
+{
|
||||
+ if (!PyArg_ParseTuple(args, ""))
|
||||
+ return (NULL);
|
||||
+
|
||||
+ return Py_BuildValue("i", fsi_init());
|
||||
+}
|
||||
+
|
||||
PyDoc_STRVAR(fsimage_open__doc__,
|
||||
"open(name, [offset=off]) - Open the given file as a filesystem image.\n"
|
||||
"\n"
|
||||
@@ -297,7 +306,13 @@ PyDoc_STRVAR(fsimage_getbootstring__doc__,
|
||||
"getbootstring(fs) - Return the boot string needed for this file system "
|
||||
"or NULL if none is needed.\n");
|
||||
|
||||
+PyDoc_STRVAR(fsimage_init__doc__,
|
||||
+ "init() - Loads every dynamic library contained in xenfsimage "
|
||||
+ "into memory so that it can be used in chrooted environments.\n");
|
||||
+
|
||||
static struct PyMethodDef fsimage_module_methods[] = {
|
||||
+ { "init", (PyCFunction)fsimage_init,
|
||||
+ METH_VARARGS, fsimage_init__doc__ },
|
||||
{ "open", (PyCFunction)fsimage_open,
|
||||
METH_VARARGS|METH_KEYWORDS, fsimage_open__doc__ },
|
||||
{ "getbootstring", (PyCFunction)fsimage_getbootstring,
|
||||
--
|
||||
2.42.0
|
||||
|
||||
307
xsa443-4.16-09.patch
Normal file
307
xsa443-4.16-09.patch
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
From 4d331b0b914dfc17bd2d883bc55aeb798930832a Mon Sep 17 00:00:00 2001
|
||||
From: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Date: Mon, 25 Sep 2023 18:32:25 +0100
|
||||
Subject: [PATCH 09/11] tools/pygrub: Deprivilege pygrub
|
||||
|
||||
Introduce a --runas=<uid> flag to deprivilege pygrub on Linux and *BSDs. It
|
||||
also implicitly creates a chroot env where it drops a deprivileged forked
|
||||
process. The chroot itself is cleaned up at the end.
|
||||
|
||||
If the --runas arg is present, then pygrub forks, leaving the child to
|
||||
deprivilege itself, and waiting for it to complete. When the child exists,
|
||||
the parent performs cleanup and exits with the same error code.
|
||||
|
||||
This is roughly what the child does:
|
||||
1. Initialize libfsimage (this loads every .so in memory so the chroot
|
||||
can avoid bind-mounting /{,usr}/lib*
|
||||
2. Create a temporary empty chroot directory
|
||||
3. Mount tmpfs in it
|
||||
4. Bind mount the disk inside, because libfsimage expects a path, not a
|
||||
file descriptor.
|
||||
5. Remount the root tmpfs to be stricter (ro,nosuid,nodev)
|
||||
6. Set RLIMIT_FSIZE to a sensibly high amount (128 MiB)
|
||||
7. Depriv gid, groups and uid
|
||||
|
||||
With this scheme in place, the "output" files are writable (up to
|
||||
RLIMIT_FSIZE octets) and the exposed filesystem is immutable and contains
|
||||
the single only file we can't easily get rid of (the disk).
|
||||
|
||||
If running on Linux, the child process also unshares mount, IPC, and
|
||||
network namespaces before dropping its privileges.
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>
|
||||
Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
---
|
||||
tools/pygrub/setup.py | 2 +-
|
||||
tools/pygrub/src/pygrub | 162 +++++++++++++++++++++++++++++++++++++---
|
||||
2 files changed, 154 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/tools/pygrub/setup.py b/tools/pygrub/setup.py
|
||||
index b8f1dc4590cf..f16187b6d118 100644
|
||||
--- a/tools/pygrub/setup.py
|
||||
+++ b/tools/pygrub/setup.py
|
||||
@@ -17,7 +17,7 @@ xenfsimage = Extension("xenfsimage",
|
||||
pkgs = [ 'grub' ]
|
||||
|
||||
setup(name='pygrub',
|
||||
- version='0.6',
|
||||
+ version='0.7',
|
||||
description='Boot loader that looks a lot like grub for Xen',
|
||||
author='Jeremy Katz',
|
||||
author_email='katzj@redhat.com',
|
||||
diff --git a/tools/pygrub/src/pygrub b/tools/pygrub/src/pygrub
|
||||
index 91e2ec2ab105..7cea496ade08 100755
|
||||
--- a/tools/pygrub/src/pygrub
|
||||
+++ b/tools/pygrub/src/pygrub
|
||||
@@ -16,8 +16,11 @@ from __future__ import print_function
|
||||
|
||||
import os, sys, string, struct, tempfile, re, traceback, stat, errno
|
||||
import copy
|
||||
+import ctypes, ctypes.util
|
||||
import logging
|
||||
import platform
|
||||
+import resource
|
||||
+import subprocess
|
||||
|
||||
import curses, _curses, curses.textpad, curses.ascii
|
||||
import getopt
|
||||
@@ -27,10 +30,135 @@ import grub.GrubConf
|
||||
import grub.LiloConf
|
||||
import grub.ExtLinuxConf
|
||||
|
||||
-PYGRUB_VER = 0.6
|
||||
+PYGRUB_VER = 0.7
|
||||
FS_READ_MAX = 1024 * 1024
|
||||
SECTOR_SIZE = 512
|
||||
|
||||
+# Unless provided through the env variable PYGRUB_MAX_FILE_SIZE_MB, then
|
||||
+# this is the maximum filesize allowed for files written by the depriv
|
||||
+# pygrub
|
||||
+LIMIT_FSIZE = 128 << 20
|
||||
+
|
||||
+CLONE_NEWNS = 0x00020000 # mount namespace
|
||||
+CLONE_NEWNET = 0x40000000 # network namespace
|
||||
+CLONE_NEWIPC = 0x08000000 # IPC namespace
|
||||
+
|
||||
+def unshare(flags):
|
||||
+ if not sys.platform.startswith("linux"):
|
||||
+ print("skip_unshare reason=not_linux platform=%s", sys.platform, file=sys.stderr)
|
||||
+ return
|
||||
+
|
||||
+ libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
|
||||
+ unshare_prototype = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, use_errno=True)
|
||||
+ unshare = unshare_prototype(('unshare', libc))
|
||||
+
|
||||
+ if unshare(flags) < 0:
|
||||
+ raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno()))
|
||||
+
|
||||
+def bind_mount(src, dst, options):
|
||||
+ open(dst, "a").close() # touch
|
||||
+
|
||||
+ rc = subprocess.call(["mount", "--bind", "-o", options, src, dst])
|
||||
+ if rc != 0:
|
||||
+ raise RuntimeError("bad_mount: src=%s dst=%s opts=%s" %
|
||||
+ (src, dst, options))
|
||||
+
|
||||
+def downgrade_rlimits():
|
||||
+ # Wipe the authority to use unrequired resources
|
||||
+ resource.setrlimit(resource.RLIMIT_NPROC, (0, 0))
|
||||
+ resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
|
||||
+ resource.setrlimit(resource.RLIMIT_MEMLOCK, (0, 0))
|
||||
+
|
||||
+ # py2's resource module doesn't know about resource.RLIMIT_MSGQUEUE
|
||||
+ #
|
||||
+ # TODO: Use resource.RLIMIT_MSGQUEUE after python2 is deprecated
|
||||
+ if sys.platform.startswith('linux'):
|
||||
+ RLIMIT_MSGQUEUE = 12
|
||||
+ resource.setrlimit(RLIMIT_MSGQUEUE, (0, 0))
|
||||
+
|
||||
+ # The final look of the filesystem for this process is fully RO, but
|
||||
+ # note we have some file descriptor already open (notably, kernel and
|
||||
+ # ramdisk). In order to avoid a compromised pygrub from filling up the
|
||||
+ # filesystem we set RLIMIT_FSIZE to a high bound, so that the file
|
||||
+ # write permissions are bound.
|
||||
+ fsize = LIMIT_FSIZE
|
||||
+ if "PYGRUB_MAX_FILE_SIZE_MB" in os.environ.keys():
|
||||
+ fsize = os.environ["PYGRUB_MAX_FILE_SIZE_MB"] << 20
|
||||
+
|
||||
+ resource.setrlimit(resource.RLIMIT_FSIZE, (fsize, fsize))
|
||||
+
|
||||
+def depriv(output_directory, output, device, uid, path_kernel, path_ramdisk):
|
||||
+ # The only point of this call is to force the loading of libfsimage.
|
||||
+ # That way, we don't need to bind-mount it into the chroot
|
||||
+ rc = xenfsimage.init()
|
||||
+ if rc != 0:
|
||||
+ os.unlink(path_ramdisk)
|
||||
+ os.unlink(path_kernel)
|
||||
+ raise RuntimeError("bad_xenfsimage: rc=%d" % rc)
|
||||
+
|
||||
+ # Create a temporary directory for the chroot
|
||||
+ chroot = tempfile.mkdtemp(prefix=str(uid)+'-', dir=output_directory) + '/'
|
||||
+ device_path = '/device'
|
||||
+
|
||||
+ pid = os.fork()
|
||||
+ if pid:
|
||||
+ # parent
|
||||
+ _, rc = os.waitpid(pid, 0)
|
||||
+
|
||||
+ for path in [path_kernel, path_ramdisk]:
|
||||
+ # If the child didn't write anything, just get rid of it,
|
||||
+ # otherwise we end up consuming a 0-size file when parsing
|
||||
+ # systems without a ramdisk that the ultimate caller of pygrub
|
||||
+ # may just be unaware of
|
||||
+ if rc != 0 or os.path.getsize(path) == 0:
|
||||
+ os.unlink(path)
|
||||
+
|
||||
+ # Normally, unshare(CLONE_NEWNS) will ensure this is not required.
|
||||
+ # However, this syscall doesn't exist in *BSD systems and doesn't
|
||||
+ # auto-unmount everything on older Linux kernels (At least as of
|
||||
+ # Linux 4.19, but it seems fixed in 5.15). Either way,
|
||||
+ # recursively unmount everything if needed. Quietly.
|
||||
+ with open('/dev/null', 'w') as devnull:
|
||||
+ subprocess.call(["umount", "-f", chroot + device_path],
|
||||
+ stdout=devnull, stderr=devnull)
|
||||
+ subprocess.call(["umount", "-f", chroot],
|
||||
+ stdout=devnull, stderr=devnull)
|
||||
+ os.rmdir(chroot)
|
||||
+
|
||||
+ sys.exit(rc)
|
||||
+
|
||||
+ # By unsharing the namespace we're making sure it's all bulk-released
|
||||
+ # at the end, when the namespaces disappear. This means the kernel does
|
||||
+ # (almost) all the cleanup for us and the parent just has to remove the
|
||||
+ # temporary directory.
|
||||
+ unshare(CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWNET)
|
||||
+
|
||||
+ # Set sensible limits using the setrlimit interface
|
||||
+ downgrade_rlimits()
|
||||
+
|
||||
+ # We'll mount tmpfs on the chroot to ensure the deprivileged child
|
||||
+ # cannot affect the persistent state. It's RW now in order to
|
||||
+ # bind-mount the device, but note it's remounted RO after that.
|
||||
+ rc = subprocess.call(["mount", "-t", "tmpfs", "none", chroot])
|
||||
+ if rc != 0:
|
||||
+ raise RuntimeError("mount_tmpfs rc=%d dst=\"%s\"" % (rc, chroot))
|
||||
+
|
||||
+ # Bind the untrusted device RO
|
||||
+ bind_mount(device, chroot + device_path, "ro,nosuid,noexec")
|
||||
+
|
||||
+ rc = subprocess.call(["mount", "-t", "tmpfs", "-o", "remount,ro,nosuid,noexec,nodev", "none", chroot])
|
||||
+ if rc != 0:
|
||||
+ raise RuntimeError("remount_tmpfs rc=%d dst=\"%s\"" % (rc, chroot))
|
||||
+
|
||||
+ # Drop superpowers!
|
||||
+ os.chroot(chroot)
|
||||
+ os.chdir('/')
|
||||
+ os.setgid(uid)
|
||||
+ os.setgroups([uid])
|
||||
+ os.setuid(uid)
|
||||
+
|
||||
+ return device_path
|
||||
+
|
||||
def read_size_roundup(fd, size):
|
||||
if platform.system() != 'FreeBSD':
|
||||
return size
|
||||
@@ -736,7 +864,7 @@ if __name__ == "__main__":
|
||||
sel = None
|
||||
|
||||
def usage():
|
||||
- print("Usage: %s [-q|--quiet] [-i|--interactive] [-l|--list-entries] [-n|--not-really] [--output=] [--kernel=] [--ramdisk=] [--args=] [--entry=] [--output-directory=] [--output-format=sxp|simple|simple0] [--offset=] <image>" %(sys.argv[0],), file=sys.stderr)
|
||||
+ print("Usage: %s [-q|--quiet] [-i|--interactive] [-l|--list-entries] [-n|--not-really] [--output=] [--kernel=] [--ramdisk=] [--args=] [--entry=] [--output-directory=] [--output-format=sxp|simple|simple0] [--runas=] [--offset=] <image>" %(sys.argv[0],), file=sys.stderr)
|
||||
|
||||
def copy_from_image(fs, file_to_read, file_type, fd_dst, path_dst, not_really):
|
||||
if not_really:
|
||||
@@ -760,7 +888,8 @@ if __name__ == "__main__":
|
||||
os.write(fd_dst, data)
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
- os.unlink(path_dst)
|
||||
+ if path_dst:
|
||||
+ os.unlink(path_dst)
|
||||
del datafile
|
||||
sys.exit("Error writing temporary copy of "+file_type)
|
||||
dataoff += len(data)
|
||||
@@ -769,7 +898,7 @@ if __name__ == "__main__":
|
||||
opts, args = getopt.gnu_getopt(sys.argv[1:], 'qilnh::',
|
||||
["quiet", "interactive", "list-entries", "not-really", "help",
|
||||
"output=", "output-format=", "output-directory=", "offset=",
|
||||
- "entry=", "kernel=",
|
||||
+ "runas=", "entry=", "kernel=",
|
||||
"ramdisk=", "args=", "isconfig", "debug"])
|
||||
except getopt.GetoptError:
|
||||
usage()
|
||||
@@ -790,6 +919,7 @@ if __name__ == "__main__":
|
||||
not_really = False
|
||||
output_format = "sxp"
|
||||
output_directory = "/var/run/xen/pygrub/"
|
||||
+ uid = None
|
||||
|
||||
# what was passed in
|
||||
incfg = { "kernel": None, "ramdisk": None, "args": "" }
|
||||
@@ -813,6 +943,13 @@ if __name__ == "__main__":
|
||||
elif o in ("--output",):
|
||||
if a != "-":
|
||||
output = a
|
||||
+ elif o in ("--runas",):
|
||||
+ try:
|
||||
+ uid = int(a)
|
||||
+ except ValueError:
|
||||
+ print("runas value must be an integer user id")
|
||||
+ usage()
|
||||
+ sys.exit(1)
|
||||
elif o in ("--kernel",):
|
||||
incfg["kernel"] = a
|
||||
elif o in ("--ramdisk",):
|
||||
@@ -849,6 +986,10 @@ if __name__ == "__main__":
|
||||
if debug:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
+ if interactive and uid:
|
||||
+ print("In order to use --runas, you must also set --entry or -q", file=sys.stderr)
|
||||
+ sys.exit(1)
|
||||
+
|
||||
try:
|
||||
os.makedirs(output_directory, 0o700)
|
||||
except OSError as e:
|
||||
@@ -870,6 +1011,9 @@ if __name__ == "__main__":
|
||||
else:
|
||||
fd = os.open(output, os.O_WRONLY)
|
||||
|
||||
+ if uid:
|
||||
+ file = depriv(output_directory, output, file, uid, path_kernel, path_ramdisk)
|
||||
+
|
||||
# debug
|
||||
if isconfig:
|
||||
chosencfg = run_grub(file, entry, fs, incfg["args"])
|
||||
@@ -925,21 +1069,21 @@ if __name__ == "__main__":
|
||||
raise RuntimeError("Unable to find partition containing kernel")
|
||||
|
||||
copy_from_image(fs, chosencfg["kernel"], "kernel",
|
||||
- fd_kernel, path_kernel, not_really)
|
||||
+ fd_kernel, None if uid else path_kernel, not_really)
|
||||
bootcfg["kernel"] = path_kernel
|
||||
|
||||
if chosencfg["ramdisk"]:
|
||||
try:
|
||||
copy_from_image(fs, chosencfg["ramdisk"], "ramdisk",
|
||||
- fd_ramdisk, path_ramdisk, not_really)
|
||||
+ fd_ramdisk, None if uid else path_ramdisk, not_really)
|
||||
except:
|
||||
- if not not_really:
|
||||
- os.unlink(path_kernel)
|
||||
+ if not uid and not not_really:
|
||||
+ os.unlink(path_kernel)
|
||||
raise
|
||||
bootcfg["ramdisk"] = path_ramdisk
|
||||
else:
|
||||
initrd = None
|
||||
- if not not_really:
|
||||
+ if not uid and not not_really:
|
||||
os.unlink(path_ramdisk)
|
||||
|
||||
args = None
|
||||
--
|
||||
2.42.0
|
||||
|
||||
250
xsa443-4.16-10.patch
Normal file
250
xsa443-4.16-10.patch
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
From a5be7e8b054f586ad934e786232af29fdc6e3ead Mon Sep 17 00:00:00 2001
|
||||
From: Roger Pau Monne <roger.pau@citrix.com>
|
||||
Date: Mon, 25 Sep 2023 14:30:20 +0200
|
||||
Subject: [PATCH 10/11] libxl: add support for running bootloader in restricted
|
||||
mode
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Much like the device model depriv mode, add the same kind of support for the
|
||||
bootloader. Such feature allows passing a UID as a parameter for the
|
||||
bootloader to run as, together with the bootloader itself taking the necessary
|
||||
actions to isolate.
|
||||
|
||||
Note that the user to run the bootloader as must have the right permissions to
|
||||
access the guest disk image (in read mode only), and that the bootloader will
|
||||
be run in non-interactive mode when restricted.
|
||||
|
||||
If enabled bootloader restrict mode will attempt to re-use the user(s) from the
|
||||
QEMU depriv implementation if no user is provided on the configuration file or
|
||||
the environment. See docs/features/qemu-deprivilege.pandoc for more
|
||||
information about how to setup those users.
|
||||
|
||||
Bootloader restrict mode is not enabled by default as it requires certain
|
||||
setup to be done first (setup of the user(s) to use in restrict mode).
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Reviewed-by: Anthony PERARD <anthony.perard@citrix.com>
|
||||
---
|
||||
docs/man/xl.1.pod.in | 33 +++++++++++
|
||||
tools/libs/light/libxl_bootloader.c | 89 ++++++++++++++++++++++++++++-
|
||||
tools/libs/light/libxl_dm.c | 8 +--
|
||||
tools/libs/light/libxl_internal.h | 8 +++
|
||||
4 files changed, 131 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/docs/man/xl.1.pod.in b/docs/man/xl.1.pod.in
|
||||
index 45e1430aeb74..96e6fb1c32a3 100644
|
||||
--- a/docs/man/xl.1.pod.in
|
||||
+++ b/docs/man/xl.1.pod.in
|
||||
@@ -1976,6 +1976,39 @@ ignored:
|
||||
|
||||
=back
|
||||
|
||||
+=head1 ENVIRONMENT VARIABLES
|
||||
+
|
||||
+The following environment variables shall affect the execution of xl:
|
||||
+
|
||||
+=over 4
|
||||
+
|
||||
+=item LIBXL_BOOTLOADER_RESTRICT
|
||||
+
|
||||
+Attempt to restrict the bootloader after startup, to limit the
|
||||
+consequences of security vulnerabilities due to parsing guest
|
||||
+owned image files.
|
||||
+
|
||||
+See docs/features/qemu-deprivilege.pandoc for more information
|
||||
+on how to setup the unprivileged users.
|
||||
+
|
||||
+Note that running the bootloader in restricted mode also implies using
|
||||
+non-interactive mode, and the disk image must be readable by the
|
||||
+restricted user.
|
||||
+
|
||||
+Having this variable set is equivalent to enabling the option, even if the
|
||||
+value is 0.
|
||||
+
|
||||
+=item LIBXL_BOOTLOADER_USER
|
||||
+
|
||||
+When using bootloader_restrict, run the bootloader as this user. If
|
||||
+not set the default QEMU restrict users will be used.
|
||||
+
|
||||
+NOTE: Each domain MUST have a SEPARATE username.
|
||||
+
|
||||
+See docs/features/qemu-deprivilege.pandoc for more information.
|
||||
+
|
||||
+=back
|
||||
+
|
||||
=head1 SEE ALSO
|
||||
|
||||
The following man pages:
|
||||
diff --git a/tools/libs/light/libxl_bootloader.c b/tools/libs/light/libxl_bootloader.c
|
||||
index 1bc6e51827b9..d3a8a4a9ba59 100644
|
||||
--- a/tools/libs/light/libxl_bootloader.c
|
||||
+++ b/tools/libs/light/libxl_bootloader.c
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "libxl_osdeps.h" /* must come before any other headers */
|
||||
|
||||
+#include <pwd.h>
|
||||
#include <termios.h>
|
||||
#ifdef HAVE_UTMP_H
|
||||
#include <utmp.h>
|
||||
@@ -42,8 +43,71 @@ static void bootloader_arg(libxl__bootloader_state *bl, const char *arg)
|
||||
bl->args[bl->nargs++] = arg;
|
||||
}
|
||||
|
||||
-static void make_bootloader_args(libxl__gc *gc, libxl__bootloader_state *bl,
|
||||
- const char *bootloader_path)
|
||||
+static int bootloader_uid(libxl__gc *gc, domid_t guest_domid,
|
||||
+ const char *user, uid_t *intended_uid)
|
||||
+{
|
||||
+ struct passwd *user_base, user_pwbuf;
|
||||
+ int rc;
|
||||
+
|
||||
+ if (user) {
|
||||
+ rc = userlookup_helper_getpwnam(gc, user, &user_pwbuf, &user_base);
|
||||
+ if (rc) return rc;
|
||||
+
|
||||
+ if (!user_base) {
|
||||
+ LOGD(ERROR, guest_domid, "Couldn't find user %s", user);
|
||||
+ return ERROR_INVAL;
|
||||
+ }
|
||||
+
|
||||
+ *intended_uid = user_base->pw_uid;
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ /* Re-use QEMU user range for the bootloader. */
|
||||
+ rc = userlookup_helper_getpwnam(gc, LIBXL_QEMU_USER_RANGE_BASE,
|
||||
+ &user_pwbuf, &user_base);
|
||||
+ if (rc) return rc;
|
||||
+
|
||||
+ if (user_base) {
|
||||
+ struct passwd *user_clash, user_clash_pwbuf;
|
||||
+ uid_t temp_uid = user_base->pw_uid + guest_domid;
|
||||
+
|
||||
+ rc = userlookup_helper_getpwuid(gc, temp_uid, &user_clash_pwbuf,
|
||||
+ &user_clash);
|
||||
+ if (rc) return rc;
|
||||
+
|
||||
+ if (user_clash) {
|
||||
+ LOGD(ERROR, guest_domid,
|
||||
+ "wanted to use uid %ld (%s + %d) but that is user %s !",
|
||||
+ (long)temp_uid, LIBXL_QEMU_USER_RANGE_BASE,
|
||||
+ guest_domid, user_clash->pw_name);
|
||||
+ return ERROR_INVAL;
|
||||
+ }
|
||||
+
|
||||
+ *intended_uid = temp_uid;
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ rc = userlookup_helper_getpwnam(gc, LIBXL_QEMU_USER_SHARED, &user_pwbuf,
|
||||
+ &user_base);
|
||||
+ if (rc) return rc;
|
||||
+
|
||||
+ if (user_base) {
|
||||
+ LOGD(WARN, guest_domid, "Could not find user %s, falling back to %s",
|
||||
+ LIBXL_QEMU_USER_RANGE_BASE, LIBXL_QEMU_USER_SHARED);
|
||||
+ *intended_uid = user_base->pw_uid;
|
||||
+
|
||||
+ return 0;
|
||||
+ }
|
||||
+
|
||||
+ LOGD(ERROR, guest_domid,
|
||||
+ "Could not find user %s or range base pseudo-user %s, cannot restrict",
|
||||
+ LIBXL_QEMU_USER_SHARED, LIBXL_QEMU_USER_RANGE_BASE);
|
||||
+
|
||||
+ return ERROR_INVAL;
|
||||
+}
|
||||
+
|
||||
+static int make_bootloader_args(libxl__gc *gc, libxl__bootloader_state *bl,
|
||||
+ const char *bootloader_path)
|
||||
{
|
||||
const libxl_domain_build_info *info = bl->info;
|
||||
|
||||
@@ -61,6 +125,23 @@ static void make_bootloader_args(libxl__gc *gc, libxl__bootloader_state *bl,
|
||||
ARG(GCSPRINTF("--ramdisk=%s", info->ramdisk));
|
||||
if (info->cmdline && *info->cmdline != '\0')
|
||||
ARG(GCSPRINTF("--args=%s", info->cmdline));
|
||||
+ if (getenv("LIBXL_BOOTLOADER_RESTRICT") ||
|
||||
+ getenv("LIBXL_BOOTLOADER_USER")) {
|
||||
+ uid_t uid = -1;
|
||||
+ int rc = bootloader_uid(gc, bl->domid, getenv("LIBXL_BOOTLOADER_USER"),
|
||||
+ &uid);
|
||||
+
|
||||
+ if (rc) return rc;
|
||||
+
|
||||
+ assert(uid != -1);
|
||||
+ if (!uid) {
|
||||
+ LOGD(ERROR, bl->domid, "bootloader restrict UID is 0 (root)!");
|
||||
+ return ERROR_INVAL;
|
||||
+ }
|
||||
+ LOGD(DEBUG, bl->domid, "using uid %ld", (long)uid);
|
||||
+ ARG(GCSPRINTF("--runas=%ld", (long)uid));
|
||||
+ ARG("--quiet");
|
||||
+ }
|
||||
|
||||
ARG(GCSPRINTF("--output=%s", bl->outputpath));
|
||||
ARG("--output-format=simple0");
|
||||
@@ -79,6 +160,7 @@ static void make_bootloader_args(libxl__gc *gc, libxl__bootloader_state *bl,
|
||||
/* Sentinel for execv */
|
||||
ARG(NULL);
|
||||
|
||||
+ return 0;
|
||||
#undef ARG
|
||||
}
|
||||
|
||||
@@ -443,7 +525,8 @@ static void bootloader_disk_attached_cb(libxl__egc *egc,
|
||||
bootloader = bltmp;
|
||||
}
|
||||
|
||||
- make_bootloader_args(gc, bl, bootloader);
|
||||
+ rc = make_bootloader_args(gc, bl, bootloader);
|
||||
+ if (rc) goto out;
|
||||
|
||||
bl->openpty.ao = ao;
|
||||
bl->openpty.callback = bootloader_gotptys;
|
||||
diff --git a/tools/libs/light/libxl_dm.c b/tools/libs/light/libxl_dm.c
|
||||
index fc264a3a13a6..14b593110f7c 100644
|
||||
--- a/tools/libs/light/libxl_dm.c
|
||||
+++ b/tools/libs/light/libxl_dm.c
|
||||
@@ -80,10 +80,10 @@ static int libxl__create_qemu_logfile(libxl__gc *gc, char *name)
|
||||
* On error, return a libxl-style error code.
|
||||
*/
|
||||
#define DEFINE_USERLOOKUP_HELPER(NAME,SPEC_TYPE,STRUCTNAME,SYSCONF) \
|
||||
- static int userlookup_helper_##NAME(libxl__gc *gc, \
|
||||
- SPEC_TYPE spec, \
|
||||
- struct STRUCTNAME *resultbuf, \
|
||||
- struct STRUCTNAME **out) \
|
||||
+ int userlookup_helper_##NAME(libxl__gc *gc, \
|
||||
+ SPEC_TYPE spec, \
|
||||
+ struct STRUCTNAME *resultbuf, \
|
||||
+ struct STRUCTNAME **out) \
|
||||
{ \
|
||||
struct STRUCTNAME *resultp = NULL; \
|
||||
char *buf = NULL; \
|
||||
diff --git a/tools/libs/light/libxl_internal.h b/tools/libs/light/libxl_internal.h
|
||||
index cc27c72ecf30..8415d1feed16 100644
|
||||
--- a/tools/libs/light/libxl_internal.h
|
||||
+++ b/tools/libs/light/libxl_internal.h
|
||||
@@ -4864,6 +4864,14 @@ struct libxl__cpu_policy {
|
||||
struct xc_msr *msr;
|
||||
};
|
||||
|
||||
+struct passwd;
|
||||
+_hidden int userlookup_helper_getpwnam(libxl__gc*, const char *user,
|
||||
+ struct passwd *res,
|
||||
+ struct passwd **out);
|
||||
+_hidden int userlookup_helper_getpwuid(libxl__gc*, uid_t uid,
|
||||
+ struct passwd *res,
|
||||
+ struct passwd **out);
|
||||
+
|
||||
#endif
|
||||
|
||||
/*
|
||||
--
|
||||
2.42.0
|
||||
|
||||
157
xsa443-4.16-11.patch
Normal file
157
xsa443-4.16-11.patch
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
From 273cc7ecf0a66334f24f6f740bcd441b542b3323 Mon Sep 17 00:00:00 2001
|
||||
From: Roger Pau Monne <roger.pau@citrix.com>
|
||||
Date: Thu, 28 Sep 2023 12:22:35 +0200
|
||||
Subject: [PATCH 11/11] libxl: limit bootloader execution in restricted mode
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Introduce a timeout for bootloader execution when running in restricted mode.
|
||||
|
||||
Allow overwriting the default time out with an environment provided value.
|
||||
|
||||
This is part of XSA-443 / CVE-2023-34325
|
||||
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Reviewed-by: Anthony PERARD <anthony.perard@citrix.com>
|
||||
---
|
||||
docs/man/xl.1.pod.in | 8 ++++++
|
||||
tools/libs/light/libxl_bootloader.c | 40 +++++++++++++++++++++++++++++
|
||||
tools/libs/light/libxl_internal.h | 2 ++
|
||||
3 files changed, 50 insertions(+)
|
||||
|
||||
diff --git a/docs/man/xl.1.pod.in b/docs/man/xl.1.pod.in
|
||||
index 96e6fb1c32a3..8f056450a730 100644
|
||||
--- a/docs/man/xl.1.pod.in
|
||||
+++ b/docs/man/xl.1.pod.in
|
||||
@@ -2007,6 +2007,14 @@ NOTE: Each domain MUST have a SEPARATE username.
|
||||
|
||||
See docs/features/qemu-deprivilege.pandoc for more information.
|
||||
|
||||
+=item LIBXL_BOOTLOADER_TIMEOUT
|
||||
+
|
||||
+Timeout in seconds for bootloader execution when running in restricted mode.
|
||||
+Otherwise the build time default in LIBXL_BOOTLOADER_TIMEOUT will be used.
|
||||
+
|
||||
+If defined the value must be an unsigned integer between 0 and INT_MAX,
|
||||
+otherwise behavior is undefined. Setting to 0 disables the timeout.
|
||||
+
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
diff --git a/tools/libs/light/libxl_bootloader.c b/tools/libs/light/libxl_bootloader.c
|
||||
index d3a8a4a9ba59..a4beff42654c 100644
|
||||
--- a/tools/libs/light/libxl_bootloader.c
|
||||
+++ b/tools/libs/light/libxl_bootloader.c
|
||||
@@ -30,6 +30,8 @@ static void bootloader_keystrokes_copyfail(libxl__egc *egc,
|
||||
libxl__datacopier_state *dc, int rc, int onwrite, int errnoval);
|
||||
static void bootloader_display_copyfail(libxl__egc *egc,
|
||||
libxl__datacopier_state *dc, int rc, int onwrite, int errnoval);
|
||||
+static void bootloader_timeout(libxl__egc *egc, libxl__ev_time *ev,
|
||||
+ const struct timeval *requested_abs, int rc);
|
||||
static void bootloader_domaindeath(libxl__egc*, libxl__domaindeathcheck *dc,
|
||||
int rc);
|
||||
static void bootloader_finished(libxl__egc *egc, libxl__ev_child *child,
|
||||
@@ -297,6 +299,7 @@ void libxl__bootloader_init(libxl__bootloader_state *bl)
|
||||
bl->ptys[0].master = bl->ptys[0].slave = 0;
|
||||
bl->ptys[1].master = bl->ptys[1].slave = 0;
|
||||
libxl__ev_child_init(&bl->child);
|
||||
+ libxl__ev_time_init(&bl->time);
|
||||
libxl__domaindeathcheck_init(&bl->deathcheck);
|
||||
bl->keystrokes.ao = bl->ao; libxl__datacopier_init(&bl->keystrokes);
|
||||
bl->display.ao = bl->ao; libxl__datacopier_init(&bl->display);
|
||||
@@ -314,6 +317,7 @@ static void bootloader_cleanup(libxl__egc *egc, libxl__bootloader_state *bl)
|
||||
libxl__domaindeathcheck_stop(gc,&bl->deathcheck);
|
||||
libxl__datacopier_kill(&bl->keystrokes);
|
||||
libxl__datacopier_kill(&bl->display);
|
||||
+ libxl__ev_time_deregister(gc, &bl->time);
|
||||
for (i=0; i<2; i++) {
|
||||
libxl__carefd_close(bl->ptys[i].master);
|
||||
libxl__carefd_close(bl->ptys[i].slave);
|
||||
@@ -375,6 +379,7 @@ static void bootloader_stop(libxl__egc *egc,
|
||||
|
||||
libxl__datacopier_kill(&bl->keystrokes);
|
||||
libxl__datacopier_kill(&bl->display);
|
||||
+ libxl__ev_time_deregister(gc, &bl->time);
|
||||
if (libxl__ev_child_inuse(&bl->child)) {
|
||||
r = kill(bl->child.pid, SIGTERM);
|
||||
if (r) LOGED(WARN, bl->domid, "%sfailed to kill bootloader [%lu]",
|
||||
@@ -637,6 +642,25 @@ static void bootloader_gotptys(libxl__egc *egc, libxl__openpty_state *op)
|
||||
|
||||
struct termios termattr;
|
||||
|
||||
+ if (getenv("LIBXL_BOOTLOADER_RESTRICT") ||
|
||||
+ getenv("LIBXL_BOOTLOADER_USER")) {
|
||||
+ const char *timeout_env = getenv("LIBXL_BOOTLOADER_TIMEOUT");
|
||||
+ int timeout = timeout_env ? atoi(timeout_env)
|
||||
+ : LIBXL_BOOTLOADER_TIMEOUT;
|
||||
+
|
||||
+ if (timeout) {
|
||||
+ /* Set execution timeout */
|
||||
+ rc = libxl__ev_time_register_rel(ao, &bl->time,
|
||||
+ bootloader_timeout,
|
||||
+ timeout * 1000);
|
||||
+ if (rc) {
|
||||
+ LOGED(ERROR, bl->domid,
|
||||
+ "unable to register timeout for bootloader execution");
|
||||
+ goto out;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
pid_t pid = libxl__ev_child_fork(gc, &bl->child, bootloader_finished);
|
||||
if (pid == -1) {
|
||||
rc = ERROR_FAIL;
|
||||
@@ -702,6 +726,21 @@ static void bootloader_display_copyfail(libxl__egc *egc,
|
||||
libxl__bootloader_state *bl = CONTAINER_OF(dc, *bl, display);
|
||||
bootloader_copyfail(egc, "bootloader output", bl, 1, rc,onwrite,errnoval);
|
||||
}
|
||||
+static void bootloader_timeout(libxl__egc *egc, libxl__ev_time *ev,
|
||||
+ const struct timeval *requested_abs, int rc)
|
||||
+{
|
||||
+ libxl__bootloader_state *bl = CONTAINER_OF(ev, *bl, time);
|
||||
+ STATE_AO_GC(bl->ao);
|
||||
+
|
||||
+ libxl__ev_time_deregister(gc, &bl->time);
|
||||
+
|
||||
+ assert(libxl__ev_child_inuse(&bl->child));
|
||||
+ LOGD(ERROR, bl->domid, "killing bootloader because of timeout");
|
||||
+
|
||||
+ libxl__ev_child_kill_deregister(ao, &bl->child, SIGKILL);
|
||||
+
|
||||
+ bootloader_callback(egc, bl, rc);
|
||||
+}
|
||||
|
||||
static void bootloader_domaindeath(libxl__egc *egc,
|
||||
libxl__domaindeathcheck *dc,
|
||||
@@ -718,6 +757,7 @@ static void bootloader_finished(libxl__egc *egc, libxl__ev_child *child,
|
||||
STATE_AO_GC(bl->ao);
|
||||
int rc;
|
||||
|
||||
+ libxl__ev_time_deregister(gc, &bl->time);
|
||||
libxl__datacopier_kill(&bl->keystrokes);
|
||||
libxl__datacopier_kill(&bl->display);
|
||||
|
||||
diff --git a/tools/libs/light/libxl_internal.h b/tools/libs/light/libxl_internal.h
|
||||
index 8415d1feed16..a9581289f462 100644
|
||||
--- a/tools/libs/light/libxl_internal.h
|
||||
+++ b/tools/libs/light/libxl_internal.h
|
||||
@@ -103,6 +103,7 @@
|
||||
#define LIBXL_QMP_CMD_TIMEOUT 10
|
||||
#define LIBXL_STUBDOM_START_TIMEOUT 30
|
||||
#define LIBXL_QEMU_BODGE_TIMEOUT 2
|
||||
+#define LIBXL_BOOTLOADER_TIMEOUT 120
|
||||
#define LIBXL_XENCONSOLE_LIMIT 1048576
|
||||
#define LIBXL_XENCONSOLE_PROTOCOL "vt100"
|
||||
#define LIBXL_MAXMEM_CONSTANT 1024
|
||||
@@ -3738,6 +3739,7 @@ struct libxl__bootloader_state {
|
||||
libxl__openpty_state openpty;
|
||||
libxl__openpty_result ptys[2]; /* [0] is for bootloader */
|
||||
libxl__ev_child child;
|
||||
+ libxl__ev_time time;
|
||||
libxl__domaindeathcheck deathcheck;
|
||||
int nargs, argsspace;
|
||||
const char **args;
|
||||
--
|
||||
2.42.0
|
||||
|
||||
93
xsa444-4.16-1.patch
Normal file
93
xsa444-4.16-1.patch
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/svm: Fix asymmetry with AMD DR MASK context switching
|
||||
|
||||
The handling of MSR_DR{0..3}_MASK is asymmetric between PV and HVM guests.
|
||||
|
||||
HVM guests context switch in based on the guest view of DBEXT, whereas PV
|
||||
guest switch in base on the host capability. Both guest types leave the
|
||||
context dirty for the next vCPU.
|
||||
|
||||
This leads to the following issue:
|
||||
|
||||
* PV or HVM guest has debugging active (%dr7 + mask)
|
||||
* Switch-out deactivates %dr7 but leaves other state stale in hardware
|
||||
* Another HVM guest with masks unavailable has debugging active
|
||||
* Switch in loads %dr7 but leaves the mask MSRs alone
|
||||
|
||||
Now, the second guest's vCPU is operating in the context of the prior vCPU's
|
||||
mask MSR, while the environment the vCPU can see says there are no mask MSRs.
|
||||
|
||||
As a stopgap, adjust the HVM path to switch in the masks based on host
|
||||
capabilities rather than guest visibility (i.e. like the PV path). Adjustment
|
||||
of the intercepts still needs to be dependent on the guest visibility of
|
||||
DBEXT.
|
||||
|
||||
This is part of XSA-444 / CVE-2023-34327
|
||||
|
||||
Fixes: c097f54912d3 ("x86/SVM: support data breakpoint extension registers")
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/xen/arch/x86/hvm/svm/svm.c b/xen/arch/x86/hvm/svm/svm.c
|
||||
index a019d196e071..ba4069f9100a 100644
|
||||
--- a/xen/arch/x86/hvm/svm/svm.c
|
||||
+++ b/xen/arch/x86/hvm/svm/svm.c
|
||||
@@ -185,6 +185,10 @@ static void svm_save_dr(struct vcpu *v)
|
||||
v->arch.hvm.flag_dr_dirty = 0;
|
||||
vmcb_set_dr_intercepts(vmcb, ~0u);
|
||||
|
||||
+ /*
|
||||
+ * The guest can only have changed the mask MSRs if we previous dropped
|
||||
+ * intercepts. Re-read them from hardware.
|
||||
+ */
|
||||
if ( v->domain->arch.cpuid->extd.dbext )
|
||||
{
|
||||
svm_intercept_msr(v, MSR_AMD64_DR0_ADDRESS_MASK, MSR_INTERCEPT_RW);
|
||||
@@ -216,17 +220,25 @@ static void __restore_debug_registers(struct vmcb_struct *vmcb, struct vcpu *v)
|
||||
|
||||
ASSERT(v == current);
|
||||
|
||||
- if ( v->domain->arch.cpuid->extd.dbext )
|
||||
+ /*
|
||||
+ * Both the PV and HVM paths leave stale DR_MASK values in hardware on
|
||||
+ * context-switch-out. If we're activating %dr7 for the guest, we must
|
||||
+ * sync the DR_MASKs too, whether or not the guest can see them.
|
||||
+ */
|
||||
+ if ( boot_cpu_has(X86_FEATURE_DBEXT) )
|
||||
{
|
||||
- svm_intercept_msr(v, MSR_AMD64_DR0_ADDRESS_MASK, MSR_INTERCEPT_NONE);
|
||||
- svm_intercept_msr(v, MSR_AMD64_DR1_ADDRESS_MASK, MSR_INTERCEPT_NONE);
|
||||
- svm_intercept_msr(v, MSR_AMD64_DR2_ADDRESS_MASK, MSR_INTERCEPT_NONE);
|
||||
- svm_intercept_msr(v, MSR_AMD64_DR3_ADDRESS_MASK, MSR_INTERCEPT_NONE);
|
||||
-
|
||||
wrmsrl(MSR_AMD64_DR0_ADDRESS_MASK, v->arch.msrs->dr_mask[0]);
|
||||
wrmsrl(MSR_AMD64_DR1_ADDRESS_MASK, v->arch.msrs->dr_mask[1]);
|
||||
wrmsrl(MSR_AMD64_DR2_ADDRESS_MASK, v->arch.msrs->dr_mask[2]);
|
||||
wrmsrl(MSR_AMD64_DR3_ADDRESS_MASK, v->arch.msrs->dr_mask[3]);
|
||||
+
|
||||
+ if ( v->domain->arch.cpuid->extd.dbext )
|
||||
+ {
|
||||
+ svm_intercept_msr(v, MSR_AMD64_DR0_ADDRESS_MASK, MSR_INTERCEPT_NONE);
|
||||
+ svm_intercept_msr(v, MSR_AMD64_DR1_ADDRESS_MASK, MSR_INTERCEPT_NONE);
|
||||
+ svm_intercept_msr(v, MSR_AMD64_DR2_ADDRESS_MASK, MSR_INTERCEPT_NONE);
|
||||
+ svm_intercept_msr(v, MSR_AMD64_DR3_ADDRESS_MASK, MSR_INTERCEPT_NONE);
|
||||
+ }
|
||||
}
|
||||
|
||||
write_debugreg(0, v->arch.dr[0]);
|
||||
diff --git a/xen/arch/x86/traps.c b/xen/arch/x86/traps.c
|
||||
index f7992ff230b5..a142a63dd869 100644
|
||||
--- a/xen/arch/x86/traps.c
|
||||
+++ b/xen/arch/x86/traps.c
|
||||
@@ -2314,6 +2314,11 @@ void activate_debugregs(const struct vcpu *curr)
|
||||
if ( curr->arch.dr7 & DR7_ACTIVE_MASK )
|
||||
write_debugreg(7, curr->arch.dr7);
|
||||
|
||||
+ /*
|
||||
+ * Both the PV and HVM paths leave stale DR_MASK values in hardware on
|
||||
+ * context-switch-out. If we're activating %dr7 for the guest, we must
|
||||
+ * sync the DR_MASKs too, whether or not the guest can see them.
|
||||
+ */
|
||||
if ( boot_cpu_has(X86_FEATURE_DBEXT) )
|
||||
{
|
||||
wrmsrl(MSR_AMD64_DR0_ADDRESS_MASK, curr->arch.msrs->dr_mask[0]);
|
||||
72
xsa444-4.16-2.patch
Normal file
72
xsa444-4.16-2.patch
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Subject: x86/pv: Correct the auditing of guest breakpoint addresses
|
||||
|
||||
The use of access_ok() is buggy, because it permits access to the compat
|
||||
translation area. 64bit PV guests don't use the XLAT area, but on AMD
|
||||
hardware, the DBEXT feature allows a breakpoint to match up to a 4G aligned
|
||||
region, allowing the breakpoint to reach outside of the XLAT area.
|
||||
|
||||
Prior to c/s cda16c1bb223 ("x86: mirror compat argument translation area for
|
||||
32-bit PV"), the live GDT was within 4G of the XLAT area.
|
||||
|
||||
All together, this allowed a malicious 64bit PV guest on AMD hardware to place
|
||||
a breakpoint over the live GDT, and trigger a #DB livelock (CVE-2015-8104).
|
||||
|
||||
Introduce breakpoint_addr_ok() and explain why __addr_ok() happens to be an
|
||||
appropriate check in this case.
|
||||
|
||||
For Xen 4.14 and later, this is a latent bug because the XLAT area has moved
|
||||
to be on its own with nothing interesting adjacent. For Xen 4.13 and older on
|
||||
AMD hardware, this fixes a PV-trigger-able DoS.
|
||||
|
||||
This is part of XSA-444 / CVE-2023-34328.
|
||||
|
||||
Fixes: 65e355490817 ("x86/PV: support data breakpoint extension registers")
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
|
||||
diff --git a/xen/arch/x86/pv/misc-hypercalls.c b/xen/arch/x86/pv/misc-hypercalls.c
|
||||
index 5dade2472687..681c16108fd1 100644
|
||||
--- a/xen/arch/x86/pv/misc-hypercalls.c
|
||||
+++ b/xen/arch/x86/pv/misc-hypercalls.c
|
||||
@@ -68,7 +68,7 @@ long set_debugreg(struct vcpu *v, unsigned int reg, unsigned long value)
|
||||
switch ( reg )
|
||||
{
|
||||
case 0 ... 3:
|
||||
- if ( !access_ok(value, sizeof(long)) )
|
||||
+ if ( !breakpoint_addr_ok(value) )
|
||||
return -EPERM;
|
||||
|
||||
v->arch.dr[reg] = value;
|
||||
diff --git a/xen/include/asm-x86/debugreg.h b/xen/include/asm-x86/debugreg.h
|
||||
index c57914efc6e8..cc298265244b 100644
|
||||
--- a/xen/include/asm-x86/debugreg.h
|
||||
+++ b/xen/include/asm-x86/debugreg.h
|
||||
@@ -77,6 +77,26 @@
|
||||
asm volatile ( "mov %%db" #reg ",%0" : "=r" (__val) ); \
|
||||
__val; \
|
||||
})
|
||||
+
|
||||
+/*
|
||||
+ * Architecturally, %dr{0..3} can have any arbitrary value. However, Xen
|
||||
+ * can't allow the guest to breakpoint the Xen address range, so we limit the
|
||||
+ * guest to the lower canonical half, or above the Xen range in the higher
|
||||
+ * canonical half.
|
||||
+ *
|
||||
+ * Breakpoint lengths are specified to mask the low order address bits,
|
||||
+ * meaning all breakpoints are naturally aligned. With %dr7, the widest
|
||||
+ * breakpoint is 8 bytes. With DBEXT, the widest breakpoint is 4G. Both of
|
||||
+ * the Xen boundaries have >4G alignment.
|
||||
+ *
|
||||
+ * In principle we should account for HYPERVISOR_COMPAT_VIRT_START(d), but
|
||||
+ * 64bit Xen has never enforced this for compat guests, and there's no problem
|
||||
+ * (to Xen) if the guest breakpoints it's alias of the M2P. Skipping this
|
||||
+ * aspect simplifies the logic, and causes us not to reject a migrating guest
|
||||
+ * which operated fine on prior versions of Xen.
|
||||
+ */
|
||||
+#define breakpoint_addr_ok(a) __addr_ok(a)
|
||||
+
|
||||
long set_debugreg(struct vcpu *, unsigned int reg, unsigned long value);
|
||||
void activate_debugregs(const struct vcpu *);
|
||||
|
||||
65
xsa445-4.16.patch
Normal file
65
xsa445-4.16.patch
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
From 88fa5b0db062a8f2ccac4ba05ef75768b2b03e5a Mon Sep 17 00:00:00 2001
|
||||
From: Roger Pau Monne <roger.pau@citrix.com>
|
||||
Date: Wed, 11 Oct 2023 13:14:21 +0200
|
||||
Subject: [PATCH] iommu/amd-vi: use correct level for quarantine domain page
|
||||
tables
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The current setup of the quarantine page tables assumes that the quarantine
|
||||
domain (dom_io) has been initialized with an address width of
|
||||
DEFAULT_DOMAIN_ADDRESS_WIDTH (48).
|
||||
|
||||
However dom_io being a PV domain gets the AMD-Vi IOMMU page tables levels based
|
||||
on the maximum (hot pluggable) RAM address, and hence on systems with no RAM
|
||||
above the 512GB mark only 3 page-table levels are configured in the IOMMU.
|
||||
|
||||
On systems without RAM above the 512GB boundary amd_iommu_quarantine_init()
|
||||
will setup page tables for the scratch page with 4 levels, while the IOMMU will
|
||||
be configured to use 3 levels only. The page destined to be used as level 1,
|
||||
and to contain a directory of PTEs ends up being the address in a PTE itself,
|
||||
and thus level 1 page becomes the leaf page. Without the level mismatch it's
|
||||
level 0 page that should be the leaf page instead.
|
||||
|
||||
The level 1 page won't be used as such, and hence it's not possible to use it
|
||||
to gain access to other memory on the system. However that page is not cleared
|
||||
in amd_iommu_quarantine_init() as part of re-initialization of the device
|
||||
quarantine page tables, and hence data on the level 1 page can be leaked
|
||||
between device usages.
|
||||
|
||||
Fix this by making sure the paging levels setup by amd_iommu_quarantine_init()
|
||||
match the number configured on the IOMMUs.
|
||||
|
||||
Note that IVMD regions are not affected by this issue, as those areas are
|
||||
mapped taking the configured paging levels into account.
|
||||
|
||||
This is XSA-445 / CVE-2023-46835
|
||||
|
||||
Fixes: ea38867831da ('x86 / iommu: set up a scratch page in the quarantine domain')
|
||||
Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
Reviewed-by: Jan Beulich <jbeulich@suse.com>
|
||||
---
|
||||
xen/drivers/passthrough/amd/iommu_map.c | 4 +---
|
||||
1 file changed, 1 insertion(+), 3 deletions(-)
|
||||
|
||||
diff --git a/xen/drivers/passthrough/amd/iommu_map.c b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
index cf6f01b633e4..1b414a413b89 100644
|
||||
--- a/xen/drivers/passthrough/amd/iommu_map.c
|
||||
+++ b/xen/drivers/passthrough/amd/iommu_map.c
|
||||
@@ -654,9 +654,7 @@ static int fill_qpt(union amd_iommu_pte *this, unsigned int level,
|
||||
int amd_iommu_quarantine_init(struct pci_dev *pdev, bool scratch_page)
|
||||
{
|
||||
struct domain_iommu *hd = dom_iommu(dom_io);
|
||||
- unsigned long end_gfn =
|
||||
- 1ul << (DEFAULT_DOMAIN_ADDRESS_WIDTH - PAGE_SHIFT);
|
||||
- unsigned int level = amd_iommu_get_paging_mode(end_gfn);
|
||||
+ unsigned int level = hd->arch.amd.paging_mode;
|
||||
unsigned int req_id = get_dma_requestor_id(pdev->seg, pdev->sbdf.bdf);
|
||||
const struct ivrs_mappings *ivrs_mappings = get_ivrs_mappings(pdev->seg);
|
||||
int rc;
|
||||
|
||||
base-commit: 29efce0f8f10e381417a61f2f9988b40d4f6bcf0
|
||||
--
|
||||
2.30.2
|
||||
|
||||
115
xsa446.patch
Normal file
115
xsa446.patch
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
From 80d5aada598c3a800a350003d5d582931545e13c Mon Sep 17 00:00:00 2001
|
||||
From: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Date: Thu, 26 Oct 2023 14:37:38 +0100
|
||||
Subject: [PATCH] x86/spec-ctrl: Remove conditional IRQs-on-ness for INT
|
||||
$0x80/0x82 paths
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Before speculation defences, some paths in Xen could genuinely get away with
|
||||
being IRQs-on at entry. But XPTI invalidated this property on most paths, and
|
||||
attempting to maintain it on the remaining paths was a mistake.
|
||||
|
||||
Fast forward, and DO_SPEC_CTRL_COND_IBPB (protection for AMD BTC/SRSO) is not
|
||||
IRQ-safe, running with IRQs enabled in some cases. The other actions taken on
|
||||
these paths happen to be IRQ-safe.
|
||||
|
||||
Make entry_int82() and int80_direct_trap() unconditionally Interrupt Gates
|
||||
rather than Trap Gates. Remove the conditional re-adjustment of
|
||||
int80_direct_trap() in smp_prepare_cpus(), and have entry_int82() explicitly
|
||||
enable interrupts when safe to do so.
|
||||
|
||||
In smp_prepare_cpus(), with the conditional re-adjustment removed, the
|
||||
clearing of pv_cr3 is the only remaining action gated on XPTI, and it is out
|
||||
of place anyway, repeating work already done by smp_prepare_boot_cpu(). Drop
|
||||
the entire if() condition to avoid leaving an incorrect vestigial remnant.
|
||||
|
||||
Also drop comments which make incorrect statements about when its safe to
|
||||
enable interrupts.
|
||||
|
||||
This is XSA-446 / CVE-2023-46836
|
||||
|
||||
Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
|
||||
Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
|
||||
---
|
||||
xen/arch/x86/pv/traps.c | 4 ++--
|
||||
xen/arch/x86/smpboot.c | 14 --------------
|
||||
xen/arch/x86/x86_64/compat/entry.S | 2 ++
|
||||
xen/arch/x86/x86_64/entry.S | 1 -
|
||||
4 files changed, 4 insertions(+), 17 deletions(-)
|
||||
|
||||
diff --git a/xen/arch/x86/pv/traps.c b/xen/arch/x86/pv/traps.c
|
||||
index 74f333da7e1c..240d1a2db7a3 100644
|
||||
--- a/xen/arch/x86/pv/traps.c
|
||||
+++ b/xen/arch/x86/pv/traps.c
|
||||
@@ -139,11 +139,11 @@ void __init pv_trap_init(void)
|
||||
#ifdef CONFIG_PV32
|
||||
/* The 32-on-64 hypercall vector is only accessible from ring 1. */
|
||||
_set_gate(idt_table + HYPERCALL_VECTOR,
|
||||
- SYS_DESC_trap_gate, 1, entry_int82);
|
||||
+ SYS_DESC_irq_gate, 1, entry_int82);
|
||||
#endif
|
||||
|
||||
/* Fast trap for int80 (faster than taking the #GP-fixup path). */
|
||||
- _set_gate(idt_table + LEGACY_SYSCALL_VECTOR, SYS_DESC_trap_gate, 3,
|
||||
+ _set_gate(idt_table + LEGACY_SYSCALL_VECTOR, SYS_DESC_irq_gate, 3,
|
||||
&int80_direct_trap);
|
||||
|
||||
open_softirq(NMI_SOFTIRQ, nmi_softirq);
|
||||
diff --git a/xen/arch/x86/smpboot.c b/xen/arch/x86/smpboot.c
|
||||
index 3a1a659082c6..4c54ecbc91d7 100644
|
||||
--- a/xen/arch/x86/smpboot.c
|
||||
+++ b/xen/arch/x86/smpboot.c
|
||||
@@ -1158,20 +1158,6 @@ void __init smp_prepare_cpus(void)
|
||||
|
||||
stack_base[0] = (void *)((unsigned long)stack_start & ~(STACK_SIZE - 1));
|
||||
|
||||
- if ( opt_xpti_hwdom || opt_xpti_domu )
|
||||
- {
|
||||
- get_cpu_info()->pv_cr3 = 0;
|
||||
-
|
||||
-#ifdef CONFIG_PV
|
||||
- /*
|
||||
- * All entry points which may need to switch page tables have to start
|
||||
- * with interrupts off. Re-write what pv_trap_init() has put there.
|
||||
- */
|
||||
- _set_gate(idt_table + LEGACY_SYSCALL_VECTOR, SYS_DESC_irq_gate, 3,
|
||||
- &int80_direct_trap);
|
||||
-#endif
|
||||
- }
|
||||
-
|
||||
set_nr_sockets();
|
||||
|
||||
socket_cpumask = xzalloc_array(cpumask_t *, nr_sockets);
|
||||
diff --git a/xen/arch/x86/x86_64/compat/entry.S b/xen/arch/x86/x86_64/compat/entry.S
|
||||
index bd5abd8040bd..fcc3a721f147 100644
|
||||
--- a/xen/arch/x86/x86_64/compat/entry.S
|
||||
+++ b/xen/arch/x86/x86_64/compat/entry.S
|
||||
@@ -21,6 +21,8 @@ ENTRY(entry_int82)
|
||||
SPEC_CTRL_ENTRY_FROM_PV /* Req: %rsp=regs/cpuinfo, %rdx=0, Clob: acd */
|
||||
/* WARNING! `ret`, `call *`, `jmp *` not safe before this point. */
|
||||
|
||||
+ sti
|
||||
+
|
||||
CR4_PV32_RESTORE
|
||||
|
||||
GET_CURRENT(bx)
|
||||
diff --git a/xen/arch/x86/x86_64/entry.S b/xen/arch/x86/x86_64/entry.S
|
||||
index 5ca74f5f62b2..9a7b129aa7e4 100644
|
||||
--- a/xen/arch/x86/x86_64/entry.S
|
||||
+++ b/xen/arch/x86/x86_64/entry.S
|
||||
@@ -327,7 +327,6 @@ ENTRY(sysenter_entry)
|
||||
#ifdef CONFIG_XEN_SHSTK
|
||||
ALTERNATIVE "", "setssbsy", X86_FEATURE_XEN_SHSTK
|
||||
#endif
|
||||
- /* sti could live here when we don't switch page tables below. */
|
||||
pushq $FLAT_USER_SS
|
||||
pushq $0
|
||||
pushfq
|
||||
|
||||
base-commit: 7befef87cc9b1bb8ca15d866ce1ecd9165ccb58c
|
||||
prerequisite-patch-id: 142a87c707411d49e136c3fb76f1b14963ec6dc8
|
||||
--
|
||||
2.30.2
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue