public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Michael Young <m.a.young@durham.ac.uk>
To: git-commits@fedoraproject.org
Subject: [rpms/xen] rawhide: 4 security updates
Date: Thu, 18 Jun 2026 20:15:56 GMT [thread overview]
Message-ID: <178181375620.1.3012939153567001614.rpms-xen-0c18c23c4c54@fedoraproject.org> (raw)
A new commit has been pushed.
Repo : rpms/xen
Branch : rawhide
Commit : 0c18c23c4c540b04358f824f0130e82c7734c2ab
Author : Michael Young <m.a.young@durham.ac.uk>
Date : 2026-06-18T21:14:50+01:00
Stats : +4066/-1 in 27 file(s)
URL : https://src.fedoraproject.org/rpms/xen/c/0c18c23c4c540b04358f824f0130e82c7734c2ab?branch=rawhide
Log:
4 security updates
x86 HVM I/O port list traversal [XSA-491, CVE-2026-42487]
domctl lock open to abuse [XSA-492, CVE-2026-42489, CVE-2026-42490]
Arm: Completion of memory accesses not guaranteed by completion of a TLBI
[XSA-493, CVE-2025-10263]
x86: mismatched mapcache metadata [XSA-494, CVE-2026-42488]
---
diff --git a/xen.spec b/xen.spec
index 68100b4..a9bf097 100644
--- a/xen.spec
+++ b/xen.spec
@@ -51,7 +51,7 @@
Summary: Xen is a virtual machine monitor
Name: xen
Version: 4.21.1
-Release: 5%{?dist}
+Release: 6%{?dist}
# Automatically converted from old format: GPLv2+ and LGPLv2+ and BSD - review is highly recommended.
License: GPL-2.0-or-later AND LicenseRef-Callaway-LGPLv2+ AND LicenseRef-Callaway-BSD
URL: http://xen.org/
@@ -84,6 +84,32 @@ Patch13: xsa484.patch
Patch14: xsa486.patch
Patch15: xen.git-90b20547b756a5cf9b0fec9fb0de5b361e8bf4c3.patch
Patch16: xsa490-4.21.patch
+Patch17: xsa491-4.21.patch
+Patch18: xsa492-4.21-01.patch
+Patch19: xsa492-4.21-02.patch
+Patch20: xsa492-4.21-03.patch
+Patch21: xsa492-4.21-04.patch
+Patch22: xsa492-4.21-05.patch
+Patch23: xsa492-4.21-06.patch
+Patch24: xsa492-4.21-07.patch
+Patch25: xsa492-4.21-08.patch
+Patch26: xsa492-4.21-09.patch
+Patch27: xsa492-4.21-10.patch
+Patch28: xsa492-4.21-11.patch
+Patch29: xsa492-4.21-12.patch
+Patch30: xsa492-4.21-13.patch
+Patch31: xsa492-4.21-14.patch
+Patch32: xsa492-4.21-15.patch
+Patch33: xsa492-4.21-16.patch
+Patch34: xsa492-4.21-17.patch
+Patch35: xsa492-4.21-18.patch
+Patch36: xsa492-4.21-19.patch
+Patch37: xsa492-4.21-20.patch
+Patch38: xsa493-4.21-01.patch
+Patch39: xsa493-4.21-02.patch
+Patch40: xsa493-4.21-03.patch
+Patch41: xsa493-4.21-04.patch
+Patch42: xsa494-4.21.patch
# build using Fedora seabios and ipxe packages for roms
@@ -275,6 +301,32 @@ This package contains files used in testing the xen builds
%patch 14 -p1
%patch 15 -p1
%patch 16 -p1
+%patch 17 -p1
+%patch 18 -p1
+%patch 19 -p1
+%patch 20 -p1
+%patch 21 -p1
+%patch 22 -p1
+%patch 23 -p1
+%patch 24 -p1
+%patch 25 -p1
+%patch 26 -p1
+%patch 27 -p1
+%patch 28 -p1
+%patch 29 -p1
+%patch 30 -p1
+%patch 31 -p1
+%patch 32 -p1
+%patch 33 -p1
+%patch 34 -p1
+%patch 35 -p1
+%patch 36 -p1
+%patch 37 -p1
+%patch 38 -p1
+%patch 39 -p1
+%patch 40 -p1
+%patch 41 -p1
+%patch 42 -p1
# stubdom sources
cp -v %{SOURCE10} %{SOURCE11} %{SOURCE12} %{SOURCE13} %{SOURCE14} %{SOURCE15} stubdom
@@ -830,6 +882,13 @@ fi
%{_libexecdir}/xen/tests/*
%changelog
+* Thu Jun 18 2026 Michael Young <m.a.young@durham.ac.uk> - 4.21.1-6
+- x86 HVM I/O port list traversal [XSA-491, CVE-2026-42487]
+- domctl lock open to abuse [XSA-492, CVE-2026-42489, CVE-2026-42490]
+- Arm: Completion of memory accesses not guaranteed by completion of a TLBI
+ [XSA-493, CVE-2025-10263]
+- x86: mismatched mapcache metadata [XSA-494, CVE-2026-42488]
+
* Sat Jun 13 2026 Yaakov Selkowitz <yselkowi@redhat.com> - 4.21.1-5
- Rebuilt for openssl 4.0
diff --git a/xsa491-4.21.patch b/xsa491-4.21.patch
new file mode 100644
index 0000000..d1ebc1a
--- /dev/null
+++ b/xsa491-4.21.patch
@@ -0,0 +1,211 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: x86/HVM: add locking to I/O port translation list traversal
+
+XEN_DOMCTL_ioport_mapping is usable by DM stubdoms, and hence we can't
+assume the list to be left unaltered while the guest (really: the
+hypervisor on behalf of the guest) is accessing it.
+
+This is XSA-491 / CVE-2026-42487.
+
+Fixes: 192c4dabc344 ("domctl and p2m changes for PCI passthru")
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/arch/x86/domctl.c
++++ b/xen/arch/x86/domctl.c
+@@ -663,6 +663,7 @@ long arch_do_domctl(
+ "ioport_map:add: dom%d gport=%x mport=%x nr=%x\n",
+ d->domain_id, fgp, fmp, np);
+
++ write_lock(&hvm->g2m_ioport_lock);
+ list_for_each_entry(g2m_ioport, &hvm->g2m_ioport_list, list)
+ if (g2m_ioport->mport == fmp )
+ {
+@@ -684,11 +685,14 @@ long arch_do_domctl(
+ g2m_ioport->np = np;
+ list_add_tail(&g2m_ioport->list, &hvm->g2m_ioport_list);
+ }
++ write_unlock(&hvm->g2m_ioport_lock);
+ if ( !ret )
+ ret = ioports_permit_access(d, fmp, fmp + np - 1);
+ if ( ret && !found && g2m_ioport )
+ {
++ write_lock(&hvm->g2m_ioport_lock);
+ list_del(&g2m_ioport->list);
++ write_unlock(&hvm->g2m_ioport_lock);
+ xfree(g2m_ioport);
+ }
+ }
+@@ -697,6 +701,8 @@ long arch_do_domctl(
+ printk(XENLOG_G_INFO
+ "ioport_map:remove: dom%d gport=%x mport=%x nr=%x\n",
+ d->domain_id, fgp, fmp, np);
++
++ write_lock(&hvm->g2m_ioport_lock);
+ list_for_each_entry(g2m_ioport, &hvm->g2m_ioport_list, list)
+ if ( g2m_ioport->mport == fmp )
+ {
+@@ -704,6 +710,8 @@ long arch_do_domctl(
+ xfree(g2m_ioport);
+ break;
+ }
++ write_unlock(&hvm->g2m_ioport_lock);
++
+ ret = ioports_deny_access(d, fmp, fmp + np - 1);
+ if ( ret && is_hardware_domain(currd) )
+ printk(XENLOG_ERR
+--- a/xen/arch/x86/hvm/emulate.c
++++ b/xen/arch/x86/hvm/emulate.c
+@@ -160,7 +160,6 @@ void hvmemul_cancel(struct vcpu *v)
+ hvio->mmio_insn_bytes = 0;
+ hvio->mmio_access = (struct npfec){};
+ hvio->mmio_retry = false;
+- hvio->g2m_ioport = NULL;
+
+ hvmemul_cache_disable(v);
+ }
+--- a/xen/arch/x86/hvm/hvm.c
++++ b/xen/arch/x86/hvm/hvm.c
+@@ -610,6 +610,7 @@ int hvm_domain_initialise(struct domain
+ spin_lock_init(&d->arch.hvm.irq_lock);
+ spin_lock_init(&d->arch.hvm.uc_lock);
+ spin_lock_init(&d->arch.hvm.write_map.lock);
++ rwlock_init(&d->arch.hvm.g2m_ioport_lock);
+ rwlock_init(&d->arch.hvm.mmcfg_lock);
+ INIT_LIST_HEAD(&d->arch.hvm.write_map.list);
+ INIT_LIST_HEAD(&d->arch.hvm.g2m_ioport_list);
+--- a/xen/arch/x86/hvm/io.c
++++ b/xen/arch/x86/hvm/io.c
+@@ -143,36 +143,56 @@ bool handle_pio(uint16_t port, unsigned
+ return true;
+ }
+
+-static bool cf_check g2m_portio_accept(
+- const struct hvm_io_handler *handler, const ioreq_t *p)
++/* NB: Returns with the lock held in the success case. */
++static const struct g2m_ioport *g2m_portio_find_and_lock(struct hvm_domain *hvm,
++ uint64_t addr,
++ uint32_t size)
+ {
+- struct vcpu *curr = current;
+- const struct hvm_domain *hvm = &curr->domain->arch.hvm;
+- struct hvm_vcpu_io *hvio = &curr->arch.hvm.hvm_io;
+- struct g2m_ioport *g2m_ioport;
+- unsigned int start, end;
++ const struct g2m_ioport *g2m_ioport;
++
++ read_lock(&hvm->g2m_ioport_lock);
+
+ list_for_each_entry( g2m_ioport, &hvm->g2m_ioport_list, list )
+ {
+- start = g2m_ioport->gport;
+- end = start + g2m_ioport->np;
+- if ( (p->addr >= start) && (p->addr + p->size <= end) )
+- {
+- hvio->g2m_ioport = g2m_ioport;
+- return 1;
+- }
++ unsigned int start = g2m_ioport->gport;
++
++ if ( addr >= start && addr + size <= start + g2m_ioport->np )
++ return g2m_ioport;
+ }
+
+- return 0;
++ read_unlock(&hvm->g2m_ioport_lock);
++
++ return NULL;
++}
++
++static bool cf_check g2m_portio_accept(
++ const struct hvm_io_handler *handler, const ioreq_t *p)
++{
++ struct hvm_domain *hvm = ¤t->domain->arch.hvm;
++ const struct g2m_ioport *g2m_ioport =
++ g2m_portio_find_and_lock(hvm, p->addr, p->size);
++
++ if ( !g2m_ioport )
++ return false;
++
++ read_unlock(&hvm->g2m_ioport_lock);
++
++ return true;
+ }
+
+ static int cf_check g2m_portio_read(
+ const struct hvm_io_handler *handler, uint64_t addr, uint32_t size,
+ uint64_t *data)
+ {
+- struct hvm_vcpu_io *hvio = ¤t->arch.hvm.hvm_io;
+- const struct g2m_ioport *g2m_ioport = hvio->g2m_ioport;
+- unsigned int mport = (addr - g2m_ioport->gport) + g2m_ioport->mport;
++ struct hvm_domain *hvm = ¤t->domain->arch.hvm;
++ const struct g2m_ioport *g2m_ioport =
++ g2m_portio_find_and_lock(hvm, addr, size);
++ unsigned int mport;
++
++ if ( !g2m_ioport )
++ return X86EMUL_RETRY;
++
++ mport = addr - g2m_ioport->gport + g2m_ioport->mport;
+
+ switch ( size )
+ {
+@@ -189,6 +209,8 @@ static int cf_check g2m_portio_read(
+ BUG();
+ }
+
++ read_unlock(&hvm->g2m_ioport_lock);
++
+ return X86EMUL_OKAY;
+ }
+
+@@ -196,9 +218,15 @@ static int cf_check g2m_portio_write(
+ const struct hvm_io_handler *handler, uint64_t addr, uint32_t size,
+ uint64_t data)
+ {
+- struct hvm_vcpu_io *hvio = ¤t->arch.hvm.hvm_io;
+- const struct g2m_ioport *g2m_ioport = hvio->g2m_ioport;
+- unsigned int mport = (addr - g2m_ioport->gport) + g2m_ioport->mport;
++ struct hvm_domain *hvm = ¤t->domain->arch.hvm;
++ const struct g2m_ioport *g2m_ioport =
++ g2m_portio_find_and_lock(hvm, addr, size);
++ unsigned int mport;
++
++ if ( !g2m_ioport )
++ return X86EMUL_RETRY;
++
++ mport = addr - g2m_ioport->gport + g2m_ioport->mport;
+
+ switch ( size )
+ {
+@@ -215,6 +243,8 @@ static int cf_check g2m_portio_write(
+ BUG();
+ }
+
++ read_unlock(&hvm->g2m_ioport_lock);
++
+ return X86EMUL_OKAY;
+ }
+
+--- a/xen/arch/x86/include/asm/hvm/domain.h
++++ b/xen/arch/x86/include/asm/hvm/domain.h
+@@ -125,6 +125,7 @@ struct hvm_domain {
+
+ /* List of guest to machine IO ports mapping. */
+ struct list_head g2m_ioport_list;
++ rwlock_t g2m_ioport_lock;
+
+ /* List of MMCFG regions trapped by Xen. */
+ struct list_head mmcfg_regions;
+--- a/xen/arch/x86/include/asm/hvm/vcpu.h
++++ b/xen/arch/x86/include/asm/hvm/vcpu.h
+@@ -54,8 +54,6 @@ struct hvm_vcpu_io {
+ unsigned long msix_unmask_address;
+ unsigned long msix_snoop_address;
+ unsigned long msix_snoop_gpa;
+-
+- const struct g2m_ioport *g2m_ioport;
+ };
+
+ struct nestedvcpu {
diff --git a/xsa492-4.21-01.patch b/xsa492-4.21-01.patch
new file mode 100644
index 0000000..7244ebd
--- /dev/null
+++ b/xsa492-4.21-01.patch
@@ -0,0 +1,264 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: sched: use sequence counter to enlighten vcpu_runstate_get()
+
+Subsequently XEN_DOMCTL_getdomaininfo will want to invoke the function
+without holding a lock, thus allowing parallel execution of potentially
+many instances. As was learned from 228ab9992ffb ("domctl: improve
+locking during domain destruction"), reverted by d0887cc6b16e, such
+parallelism can result in severe lock contention on any (previously)
+inner lock. To avoid taking that risk replace the use of the scheduler
+lock in vcpu_runstate_get() by a newly introduced sequence counter.
+Convert the "no lock if current" property to "use a local counter
+instance", thus guaranteeing the loop to exit after the first iteration.
+
+Skeleton and commentary of the seqcount implementation based on /
+derived from Linux 6.11-rc.
+
+To have runstate_seq placed next to runstate in struct vcpu, without
+introducing a new obvious padding hole, yet while keeping the latter
+adjacent to runstate_guest{,_area} as well, move runstate down a little.
+
+This is part of XSA-492.
+
+Requested-by: Andrew Cooper <andrew.cooper3@citrix.com>
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+Reviewed-by: Juergen Gross <jgross@suse.com>
+
+--- a/xen/common/sched/core.c
++++ b/xen/common/sched/core.c
+@@ -281,13 +281,18 @@ static inline void vcpu_runstate_change(
+ }
+
+ delta = new_entry_time - v->runstate.state_entry_time;
+- if ( delta > 0 )
++
++ /* Serialization: ->schedule_lock (see ASSERT() above). */
++ with_seq_write(&v->runstate_seq)
+ {
+- v->runstate.time[v->runstate.state] += delta;
+- v->runstate.state_entry_time = new_entry_time;
+- }
++ if ( delta > 0 )
++ {
++ v->runstate.time[v->runstate.state] += delta;
++ v->runstate.state_entry_time = new_entry_time;
++ }
+
+- v->runstate.state = new_state;
++ v->runstate.state = new_state;
++ }
+ }
+
+ void sched_guest_idle(void (*idle) (void), unsigned int cpu)
+@@ -307,30 +312,18 @@ void sched_guest_idle(void (*idle) (void
+ void vcpu_runstate_get(const struct vcpu *v,
+ struct vcpu_runstate_info *runstate)
+ {
+- spinlock_t *lock;
+- s_time_t delta;
+- struct sched_unit *unit;
++ struct seqcount seq = SEQCNT_ZERO();
++ const struct seqcount *s = likely(v == current) ? &seq : &v->runstate_seq;
+
+- rcu_read_lock(&sched_res_rculock);
+-
+- /*
+- * Be careful in case of an idle vcpu: the assignment to a unit might
+- * change even with the scheduling lock held, so be sure to use the
+- * correct unit for locking in order to avoid triggering an ASSERT() in
+- * the unlock function.
+- */
+- unit = is_idle_vcpu(v) ? get_sched_res(v->processor)->sched_unit_idle
+- : v->sched_unit;
+- lock = likely(v == current) ? NULL : unit_schedule_lock_irq(unit);
+- memcpy(runstate, &v->runstate, sizeof(*runstate));
+- delta = NOW() - runstate->state_entry_time;
+- if ( delta > 0 )
+- runstate->time[runstate->state] += delta;
+-
+- if ( unlikely(lock != NULL) )
+- unit_schedule_unlock_irq(lock, unit);
++ until_seq_read(s)
++ {
++ s_time_t delta;
+
+- rcu_read_unlock(&sched_res_rculock);
++ *runstate = v->runstate;
++ delta = NOW() - runstate->state_entry_time;
++ if ( delta > 0 )
++ runstate->time[runstate->state] += delta;
++ }
+ }
+
+ uint64_t get_cpu_idle_time(unsigned int cpu)
+--- a/xen/include/xen/sched.h
++++ b/xen/include/xen/sched.h
+@@ -16,6 +16,7 @@
+ #include <xen/radix-tree.h>
+ #include <xen/multicall.h>
+ #include <xen/nospec.h>
++#include <xen/seqcount.h>
+ #include <xen/tasklet.h>
+ #include <xen/mm.h>
+ #include <xen/smp.h>
+@@ -198,7 +199,6 @@ struct vcpu
+
+ struct sched_unit *sched_unit;
+
+- struct vcpu_runstate_info runstate;
+ #ifndef CONFIG_COMPAT
+ # define runstate_guest(v) ((v)->runstate_guest)
+ XEN_GUEST_HANDLE(vcpu_runstate_info_t) runstate_guest; /* guest address */
+@@ -210,6 +210,8 @@ struct vcpu
+ } runstate_guest; /* guest address */
+ #endif
+ struct guest_area runstate_guest_area;
++ struct vcpu_runstate_info runstate;
++ struct seqcount runstate_seq;
+ unsigned int new_state;
+
+ /* Has the FPU been initialised? */
+--- /dev/null
++++ b/xen/include/xen/seqcount.h
+@@ -0,0 +1,139 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++#ifndef XEN_SEQCOUNT_H
++#define XEN_SEQCOUNT_H
++
++#include <xen/lib.h>
++#include <xen/nospec.h>
++
++#include <asm/atomic.h>
++#include <asm/system.h>
++
++/*
++ * Sequence counters (seqcount_t)
++ *
++ * This is the raw counting mechanism, without any writer protection.
++ *
++ * Write side critical sections must be serialized (and non-preemptible).
++ *
++ * If readers can be invoked from interrupt contexts, interrupts must also
++ * be respectively disabled before entering the write section.
++ *
++ * This mechanism can't be used if the protected data contains pointers,
++ * as the writer can invalidate a pointer that a reader is following.
++ */
++struct seqcount {
++ unsigned int sequence;
++};
++
++/*
++ * SEQCNT_ZERO() - initializer for seqcount_t
++ * @name: Name of the struct seqcount instance
++ */
++#define SEQCNT_ZERO() { .sequence = 0 }
++
++static inline unsigned int seqprop_sequence(const struct seqcount *s)
++{
++ return ACCESS_ONCE(s->sequence);
++}
++
++/*
++ * read_seqcount_begin() - begin a seqcount read critical section
++ * @s: Pointer to struct seqcount
++ *
++ * Return: count to be passed to read_seqcount_retry()
++ */
++static inline unsigned int _read_seqcount_begin(const struct seqcount *s)
++{
++ unsigned int seq;
++
++ while ((seq = seqprop_sequence(s)) & 1)
++ cpu_relax();
++
++ smp_rmb();
++
++ return seq;
++}
++
++static always_inline unsigned int read_seqcount_begin(const struct seqcount *s)
++{
++ unsigned int seq = _read_seqcount_begin(s);
++
++ block_lock_speculation();
++
++ return seq;
++}
++
++/*
++ * read_seqcount_retry() - end a seqcount read critical section
++ * @s: Pointer to struct seqcount
++ * @start: count, from read_seqcount_begin()
++ *
++ * read_seqcount_retry closes the read critical section of given struct
++ * seqcount. If the critical section was invalid, it must be ignored
++ * (and typically retried).
++ *
++ * Return: true if a read section retry is required, else false
++ */
++static inline bool _read_seqcount_retry(const struct seqcount *s,
++ unsigned int start)
++{
++ smp_rmb();
++ return unlikely(seqprop_sequence(s) != start);
++}
++
++static always_inline bool read_seqcount_retry(const struct seqcount *s,
++ unsigned int start)
++{
++ return lock_evaluate_nospec(_read_seqcount_retry(s, start));
++}
++
++/* Loops until a consistent count has been observed across the loop body. */
++#define until_seq_read(seq) \
++ for ( unsigned int retry_ = 1, count_; \
++ retry_ && (count_ = read_seqcount_begin(seq), true); \
++ retry_ = read_seqcount_retry(seq, count_) )
++
++/*
++ * write_seqcount_begin() - start a struct seqcount write side critical section
++ * @s: Pointer to struct seqcount
++ *
++ * Context: sequence counter write side sections must be serialized.
++ * If readers can be invoked from interrupt context, interrupts must be
++ * respectively disabled.
++ */
++static inline void write_seqcount_begin(struct seqcount *s)
++{
++ add_sized(&s->sequence, 1);
++ smp_wmb();
++}
++
++/*
++ * write_seqcount_end() - end a struct seqcount write side critical section
++ * @s: Pointer to seqcount
++ */
++static inline void write_seqcount_end(struct seqcount *s)
++{
++ smp_wmb();
++ add_sized(&s->sequence, 1);
++}
++
++/*
++ * Not really a loop, but we need write_seqcount_{begin,end}() in the correct
++ * position.
++ */
++#define with_seq_write(seq) \
++ for ( bool once_ = true; \
++ once_ && (write_seqcount_begin(seq), true); \
++ (write_seqcount_end(seq), once_ = false) )
++
++#endif /* XEN_SEQCOUNT_H */
++
++/*
++ * Local variables:
++ * mode: C
++ * c-file-style: "BSD"
++ * c-basic-offset: 4
++ * tab-width: 4
++ * indent-tabs-mode: nil
++ * End:
++ */
diff --git a/xsa492-4.21-02.patch b/xsa492-4.21-02.patch
new file mode 100644
index 0000000..75ca8ca
--- /dev/null
+++ b/xsa492-4.21-02.patch
@@ -0,0 +1,104 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl: handle XEN_DOMCTL_getdomaininfo without acquiring domctl lock
+
+getdomaininfo() is not called under consistently the same lock. Thus,
+with caller side locking irrelevant, it can as well be called with the
+domctl lock not held. (Callers not pausing the domain they want to
+retrieve information for already need to be aware that not all of the
+data returned can be relied on as being consistent; most data will also
+be stale by the time the caller gets to look at it.)
+
+Move the handling not only ahead of acquiring the lock, but also ahead
+of the XSM check, leveraging that the sub-op has its own hook.
+
+While moving, convert an assignment to an assertion: The domain in
+question was determined from the field which previously was "updated".
+
+This is part of XSA-492.
+
+Fixes: 5513bd0b4675 ("add xenstore domain flag to hypervisor")
+Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -318,6 +318,26 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ break;
+ }
+
++ /* Handle sub-ops not requiring the domctl lock. */
++ switch ( op->cmd )
++ {
++ case XEN_DOMCTL_getdomaininfo:
++ ret = xsm_getdomaininfo(XSM_XS_PRIV, d);
++ if ( !ret )
++ {
++ getdomaininfo(d, &op->u.getdomaininfo);
++
++ ASSERT(op->domain == op->u.getdomaininfo.domain);
++ copyback = true;
++ }
++
++ goto domctl_out_unlock_domonly;
++
++ default:
++ /* Everything else handled further down. */
++ break;
++ }
++
+ ret = xsm_domctl(XSM_OTHER, d, op->cmd,
+ /* SSIDRef only applicable for cmd == createdomain */
+ op->u.createdomain.ssidref);
+@@ -516,17 +536,6 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ copyback = 1;
+ break;
+
+- case XEN_DOMCTL_getdomaininfo:
+- ret = xsm_getdomaininfo(XSM_XS_PRIV, d);
+- if ( ret )
+- break;
+-
+- getdomaininfo(d, &op->u.getdomaininfo);
+-
+- op->domain = op->u.getdomaininfo.domain;
+- copyback = 1;
+- break;
+-
+ case XEN_DOMCTL_getvcpucontext:
+ {
+ vcpu_guest_context_u c = { .nat = NULL };
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -172,9 +172,13 @@ static XSM_INLINE int cf_check xsm_domct
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_unbind_pt_irq:
+ return xsm_default_action(XSM_DM_PRIV, current->domain, d);
+- case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
+ return xsm_default_action(XSM_XS_PRIV, current->domain, d);
++
++ case XEN_DOMCTL_getdomaininfo:
++ ASSERT_UNREACHABLE();
++ return -EILSEQ;
++
+ default:
+ return xsm_default_action(XSM_PRIV, current->domain, d);
+ }
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -682,8 +682,12 @@ static int cf_check flask_domctl(struct
+ */
+ return avc_current_has_perm(ssidref, SECCLASS_DOMAIN, DOMAIN__CREATE, NULL);
+
+- /* These have individual XSM hooks (common/domctl.c) */
++ /* These have individual XSM hooks and don't make it here. */
+ case XEN_DOMCTL_getdomaininfo:
++ ASSERT_UNREACHABLE();
++ return -EILSEQ;
++
++ /* These have individual XSM hooks (common/domctl.c) */
+ case XEN_DOMCTL_scheduler_op:
+ case XEN_DOMCTL_irq_permission:
+ case XEN_DOMCTL_iomem_permission:
diff --git a/xsa492-4.21-03.patch b/xsa492-4.21-03.patch
new file mode 100644
index 0000000..5a0db22
--- /dev/null
+++ b/xsa492-4.21-03.patch
@@ -0,0 +1,87 @@
+From: Daniel P. Smith <dpsmith@apertussolutions.com>
+Subject: domctl: protect locking for get_domain_state
+
+When DOMID_INVALID is passed, the dom exec handler lock is being taken
+without any check that the domain is even allowed to take the lock. This
+allows for an unauthorized domain to DoS the get_domain_state domctl op.
+Move to consider the op effectively being called against the hypervisor.
+Thus it is the target of the call being invoked to identify the last
+domain with a state change. The subsequent check of whether the source
+domain is allowed the state of the last domain to change state is still
+relevant.
+
+This is part of XSA-492.
+
+Signed-off-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/tools/flask/policy/modules/xenstore.te
++++ b/tools/flask/policy/modules/xenstore.te
+@@ -14,6 +14,7 @@ allow xenstore_t xen_t:xen writeconsole;
+ # Xenstore queries domaininfo on all domains
+ allow xenstore_t domain_type:domain getdomaininfo;
+ allow xenstore_t domain_type:domain2 get_domain_state;
++allow xenstore_t domxen_t:domain2 get_domain_state;
+
+ # As a shortcut, the following 3 rules are used instead of adding a domain_comms
+ # rule between xenstore_t and every domain type that talks to xenstore
+--- a/xen/common/domain.c
++++ b/xen/common/domain.c
+@@ -216,12 +216,8 @@ int get_domain_state(struct xen_domctl_g
+ if ( info->pad0 )
+ return -EINVAL;
+
+- if ( d )
++ if ( d != dom_xen )
+ {
+- rc = xsm_get_domain_state(XSM_XS_PRIV, d);
+- if ( rc )
+- return rc;
+-
+ set_domain_state_info(info, d);
+
+ return 0;
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -304,13 +304,19 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ fallthrough;
+ case XEN_DOMCTL_test_assign_device:
+ case XEN_DOMCTL_vm_event_op:
+- case XEN_DOMCTL_get_domain_state:
+ if ( op->domain == DOMID_INVALID )
+ {
+ d = NULL;
+ break;
+ }
+ fallthrough;
++ case XEN_DOMCTL_get_domain_state:
++ if ( op->domain == DOMID_INVALID )
++ {
++ d = dom_xen;
++ break;
++ }
++ fallthrough;
+ default:
+ d = rcu_lock_domain_by_id(op->domain);
+ if ( !d )
+@@ -863,7 +869,9 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ break;
+
+ case XEN_DOMCTL_get_domain_state:
+- ret = get_domain_state(&op->u.get_domain_state, d, &op->domain);
++ ret = xsm_get_domain_state(XSM_XS_PRIV, d);
++ if ( !ret )
++ ret = get_domain_state(&op->u.get_domain_state, d, &op->domain);
+ if ( !ret )
+ copyback = true;
+ break;
+@@ -876,7 +884,7 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ domctl_lock_release();
+
+ domctl_out_unlock_domonly:
+- if ( d && d != dom_io )
++ if ( d && !is_system_domain(d) )
+ rcu_unlock_domain(d);
+
+ if ( copyback && __copy_to_guest(u_domctl, op, 1) )
diff --git a/xsa492-4.21-04.patch b/xsa492-4.21-04.patch
new file mode 100644
index 0000000..481ff5d
--- /dev/null
+++ b/xsa492-4.21-04.patch
@@ -0,0 +1,81 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl: handle XEN_DOMCTL_get_domain_state without acquiring domctl lock
+
+get_domain_state() uses its own locking. Thus, with caller side locking
+irrelevant, it can as well be called with the domctl lock not held.
+
+Move the handling not only ahead of acquiring the lock, but also ahead
+of the XSM check, leveraging that the sub-op has its own hook.
+
+This is part of XSA-492.
+
+Fixes: 3ad3df1bd0aa ("xen: add new domctl get_domain_state")
+Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -339,6 +339,14 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+
+ goto domctl_out_unlock_domonly;
+
++ case XEN_DOMCTL_get_domain_state:
++ ret = xsm_get_domain_state(XSM_XS_PRIV, d);
++ if ( !ret )
++ ret = get_domain_state(&op->u.get_domain_state, d, &op->domain);
++ if ( !ret )
++ copyback = true;
++ goto domctl_out_unlock_domonly;
++
+ default:
+ /* Everything else handled further down. */
+ break;
+@@ -868,14 +876,6 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ ret = -EOPNOTSUPP;
+ break;
+
+- case XEN_DOMCTL_get_domain_state:
+- ret = xsm_get_domain_state(XSM_XS_PRIV, d);
+- if ( !ret )
+- ret = get_domain_state(&op->u.get_domain_state, d, &op->domain);
+- if ( !ret )
+- copyback = true;
+- break;
+-
+ default:
+ ret = arch_do_domctl(op, d, u_domctl);
+ break;
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -172,10 +172,9 @@ static XSM_INLINE int cf_check xsm_domct
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_unbind_pt_irq:
+ return xsm_default_action(XSM_DM_PRIV, current->domain, d);
+- case XEN_DOMCTL_get_domain_state:
+- return xsm_default_action(XSM_XS_PRIV, current->domain, d);
+
+ case XEN_DOMCTL_getdomaininfo:
++ case XEN_DOMCTL_get_domain_state:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -684,6 +684,7 @@ static int cf_check flask_domctl(struct
+
+ /* These have individual XSM hooks and don't make it here. */
+ case XEN_DOMCTL_getdomaininfo:
++ case XEN_DOMCTL_get_domain_state:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+
+@@ -694,7 +695,6 @@ static int cf_check flask_domctl(struct
+ case XEN_DOMCTL_memory_mapping:
+ case XEN_DOMCTL_set_target:
+ case XEN_DOMCTL_vm_event_op:
+- case XEN_DOMCTL_get_domain_state:
+
+ /* These have individual XSM hooks (arch/../domctl.c) */
+ case XEN_DOMCTL_bind_pt_irq:
diff --git a/xsa492-4.21-05.patch b/xsa492-4.21-05.patch
new file mode 100644
index 0000000..cb7beaa
--- /dev/null
+++ b/xsa492-4.21-05.patch
@@ -0,0 +1,156 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domain: locking for iomem_caps accesses
+
+In order to be able to pull at least the XEN_DOMCTL_iomem_mapping handling
+out of the domctl-locked region, a separate (per-domain) lock is needed to
+synchronize in particular with XEN_DOMCTL_iomem_permission.
+
+Locking is added only as far as domctl-s are affected. Uses presently
+outside of the domctl lock may want dealing with subsequently (perhaps
+limited to non-__init code).
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/common/domain.c
++++ b/xen/common/domain.c
+@@ -518,10 +518,15 @@ static int late_hwdom_init(struct domain
+ * may be modified after this hypercall returns if a more complex
+ * device model is desired.
+ */
++ write_lock(&dom0->caps_lock);
+ rangeset_swap(d->irq_caps, dom0->irq_caps);
+ rangeset_swap(d->iomem_caps, dom0->iomem_caps);
+ #ifdef CONFIG_X86
+ rangeset_swap(d->arch.ioport_caps, dom0->arch.ioport_caps);
++#endif
++ write_unlock(&dom0->caps_lock);
++
++#ifdef CONFIG_X86
+ setup_io_bitmap(d);
+ setup_io_bitmap(dom0);
+ #endif
+@@ -873,6 +878,7 @@ struct domain *domain_create(domid_t dom
+ rspin_lock_init_prof(d, domain_lock);
+ rspin_lock_init_prof(d, page_alloc_lock);
+ spin_lock_init(&d->hypercall_deadlock_mutex);
++ rwlock_init(&d->caps_lock);
+ INIT_PAGE_LIST_HEAD(&d->page_list);
+ INIT_PAGE_LIST_HEAD(&d->extra_page_list);
+ INIT_PAGE_LIST_HEAD(&d->xenpage_list);
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -267,6 +267,35 @@ static struct vnuma_info *vnuma_init(con
+ return ERR_PTR(ret);
+ }
+
++void iocaps_double_lock(struct domain *d, bool write)
++{
++ struct domain *currd = current->domain;
++
++ if ( d->domain_id > currd->domain_id )
++ read_lock(&currd->caps_lock);
++
++ if ( write )
++ write_lock(&d->caps_lock);
++ else
++ read_lock(&d->caps_lock);
++
++ if ( d->domain_id < currd->domain_id )
++ read_lock(&currd->caps_lock);
++}
++
++void iocaps_double_unlock(struct domain *d, bool write)
++{
++ struct domain *currd = current->domain;
++
++ if ( d != currd )
++ read_unlock(&currd->caps_lock);
++
++ if ( write )
++ write_unlock(&d->caps_lock);
++ else
++ read_unlock(&d->caps_lock);
++}
++
+ static bool is_stable_domctl(uint32_t cmd)
+ {
+ return cmd == XEN_DOMCTL_get_domain_state;
+@@ -687,6 +716,8 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ if ( (mfn + nr_mfns - 1) < mfn ) /* wrap? */
+ break;
+
++ iocaps_double_lock(d, true);
++
+ if ( !iomem_access_permitted(current->domain,
+ mfn, mfn + nr_mfns - 1) ||
+ xsm_iomem_permission(XSM_HOOK, d, mfn, mfn + nr_mfns - 1, allow) )
+@@ -695,6 +726,8 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ ret = iomem_permit_access(d, mfn, mfn + nr_mfns - 1);
+ else
+ ret = iomem_deny_access(d, mfn, mfn + nr_mfns - 1);
++
++ iocaps_double_unlock(d, true);
+ break;
+ }
+
+@@ -719,19 +752,15 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ break;
+ #endif
+
++ iocaps_double_lock(d, false);
++
+ ret = -EPERM;
+ if ( !iomem_access_permitted(current->domain, mfn, mfn_end) ||
+- !iomem_access_permitted(d, mfn, mfn_end) )
+- break;
+-
+- ret = xsm_iomem_mapping(XSM_HOOK, d, mfn, mfn_end, add);
+- if ( ret )
+- break;
+-
+- if ( !paging_mode_translate(d) )
+- break;
+-
+- if ( add )
++ !iomem_access_permitted(d, mfn, mfn_end) ||
++ (ret = xsm_iomem_mapping(XSM_HOOK, d, mfn, mfn_end, add)) ||
++ !paging_mode_translate(d) )
++ /* Nothing. */;
++ else if ( add )
+ {
+ printk(XENLOG_G_DEBUG
+ "memory_map:add: dom%d gfn=%lx mfn=%lx nr=%lx\n",
+@@ -755,6 +784,8 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ "memory_map: error %ld removing dom%d access to [%lx,%lx]\n",
+ ret, d->domain_id, mfn, mfn_end);
+ }
++
++ iocaps_double_unlock(d, false);
+ break;
+ }
+
+--- a/xen/include/xen/iocap.h
++++ b/xen/include/xen/iocap.h
+@@ -12,6 +12,9 @@
+ #include <asm/iocap.h>
+ #include <asm/p2m.h>
+
++void iocaps_double_lock(struct domain *d, bool write);
++void iocaps_double_unlock(struct domain *d, bool write);
++
+ static inline int iomem_permit_access(struct domain *d, unsigned long s,
+ unsigned long e)
+ {
+--- a/xen/include/xen/sched.h
++++ b/xen/include/xen/sched.h
+@@ -536,6 +536,7 @@ struct domain
+ #endif
+
+ /* I/O capabilities (access to IRQs and memory-mapped I/O). */
++ rwlock_t caps_lock;
+ struct rangeset *iomem_caps;
+ struct rangeset *irq_caps;
+
diff --git a/xsa492-4.21-06.patch b/xsa492-4.21-06.patch
new file mode 100644
index 0000000..c9e0061
--- /dev/null
+++ b/xsa492-4.21-06.patch
@@ -0,0 +1,84 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: x86/domain: locking for ioport_caps accesses
+
+In order to be able to pull at least the XEN_DOMCTL_ioport_mapping
+handling out of the domctl-locked region, the new separate (per-domain)
+lock is used to synchronize in particular with
+XEN_DOMCTL_ioport_permission.
+
+Locking is added only as far as domctl-s are affected. Uses presently
+outside of the domctl lock may want dealing with subsequently (perhaps
+limited to non-__init code).
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/arch/x86/domctl.c
++++ b/xen/arch/x86/domctl.c
+@@ -233,6 +233,8 @@ long arch_do_domctl(
+ unsigned int np = domctl->u.ioport_permission.nr_ports;
+ int allow = domctl->u.ioport_permission.allow_access;
+
++ iocaps_double_lock(d, true);
++
+ if ( (fp + np) <= fp || (fp + np) > MAX_IOPORTS )
+ ret = -EINVAL;
+ else if ( !ioports_access_permitted(currd, fp, fp + np - 1) ||
+@@ -242,6 +244,8 @@ long arch_do_domctl(
+ ret = ioports_permit_access(d, fp, fp + np - 1);
+ else
+ ret = ioports_deny_access(d, fp, fp + np - 1);
++
++ iocaps_double_unlock(d, true);
+ break;
+ }
+
+@@ -648,16 +652,13 @@ long arch_do_domctl(
+ break;
+ }
+
+- ret = -EPERM;
+- if ( !ioports_access_permitted(currd, fmp, fmp + np - 1) )
+- break;
+-
+- ret = xsm_ioport_mapping(XSM_HOOK, d, fmp, fmp + np - 1, add);
+- if ( ret )
+- break;
+-
+ hvm = &d->arch.hvm;
+- if ( add )
++ iocaps_double_lock(d, true);
++
++ if ( !ioports_access_permitted(currd, fmp, fmp + np - 1) ||
++ (ret = xsm_ioport_mapping(XSM_HOOK, d, fmp, fmp + np - 1, add)) )
++ ret = ret ?: -EPERM;
++ else if ( add )
+ {
+ printk(XENLOG_G_INFO
+ "ioport_map:add: dom%d gport=%x mport=%x nr=%x\n",
+@@ -718,6 +720,8 @@ long arch_do_domctl(
+ "ioport_map: error %ld denying dom%d access to [%x,%x]\n",
+ ret, d->domain_id, fmp, fmp + np - 1);
+ }
++
++ iocaps_double_unlock(d, true);
+ break;
+ }
+
+--- a/xen/arch/x86/setup.c
++++ b/xen/arch/x86/setup.c
+@@ -2339,9 +2339,12 @@ void __hwdom_init setup_io_bitmap(struct
+ return;
+
+ bitmap_fill(d->arch.hvm.io_bitmap, 0x10000);
++
++ read_lock(&d->caps_lock);
+ if ( rangeset_report_ranges(d->arch.ioport_caps, 0, 0x10000,
+ io_bitmap_cb, d) )
+ BUG();
++ read_unlock(&d->caps_lock);
+
+ /*
+ * We need to trap 4-byte accesses to 0xcf8 (see admin_io_okay(),
diff --git a/xsa492-4.21-07.patch b/xsa492-4.21-07.patch
new file mode 100644
index 0000000..e343773
--- /dev/null
+++ b/xsa492-4.21-07.patch
@@ -0,0 +1,202 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domain: locking for irq_caps accesses
+
+In order to be able to pull at least the XEN_DOMCTL_{,un}bind_pt_irq
+handling out of the domctl-locked region, a separate (per-domain) lock is
+needed to synchronize in particular with XEN_DOMCTL_{irq,gsi}_permission.
+
+Locking is added only as far as domctl-s are affected. Uses presently
+outside of the domctl lock may want dealing with subsequently (perhaps
+limited to non-__init code).
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+Reviewed-by: Julien Grall <julien@xen.org>
+
+--- a/xen/arch/arm/domctl.c
++++ b/xen/arch/arm/domctl.c
+@@ -76,6 +76,7 @@ long arch_do_domctl(struct xen_domctl *d
+ case XEN_DOMCTL_bind_pt_irq:
+ {
+ int rc;
++ struct domain *currd = current->domain;
+ struct xen_domctl_bind_pt_irq *bind = &domctl->u.bind_pt_irq;
+ uint32_t irq = bind->u.spi.spi;
+ uint32_t virq = bind->machine_irq;
+@@ -107,21 +108,26 @@ long arch_do_domctl(struct xen_domctl *d
+ if ( rc )
+ return rc;
+
+- if ( !irq_access_permitted(current->domain, irq) )
+- return -EPERM;
++ read_lock(&currd->caps_lock);
+
+- if ( !vgic_reserve_virq(d, virq) )
+- return -EBUSY;
+-
+- rc = route_irq_to_guest(d, virq, irq, "routed IRQ");
+- if ( rc )
+- vgic_free_virq(d, virq);
++ if ( !irq_access_permitted(currd, irq) )
++ rc = -EPERM;
++ else if ( !vgic_reserve_virq(d, virq) )
++ rc = -EBUSY;
++ else
++ {
++ rc = route_irq_to_guest(d, virq, irq, "routed IRQ");
++ if ( rc )
++ vgic_free_virq(d, virq);
++ }
+
++ read_unlock(&currd->caps_lock);
+ return rc;
+ }
+ case XEN_DOMCTL_unbind_pt_irq:
+ {
+ int rc;
++ struct domain *currd = current->domain;
+ struct xen_domctl_bind_pt_irq *bind = &domctl->u.bind_pt_irq;
+ uint32_t irq = bind->u.spi.spi;
+ uint32_t virq = bind->machine_irq;
+@@ -138,16 +144,15 @@ long arch_do_domctl(struct xen_domctl *d
+ if ( rc )
+ return rc;
+
+- if ( !irq_access_permitted(current->domain, irq) )
+- return -EPERM;
+-
+- rc = release_guest_irq(d, virq);
+- if ( rc )
+- return rc;
++ read_lock(&currd->caps_lock);
+
+- vgic_free_virq(d, virq);
++ if ( !irq_access_permitted(currd, irq) )
++ rc = -EPERM;
++ else if ( !(rc = release_guest_irq(d, virq)) )
++ vgic_free_virq(d, virq);
+
+- return 0;
++ read_unlock(&currd->caps_lock);
++ return rc;
+ }
+
+ case XEN_DOMCTL_vuart_op:
+--- a/xen/arch/x86/domctl.c
++++ b/xen/arch/x86/domctl.c
+@@ -267,16 +267,17 @@ long arch_do_domctl(
+ break;
+ }
+
+- ret = -EPERM;
++ iocaps_double_lock(d, true);
++
+ if ( !irq_access_permitted(currd, irq) ||
+ xsm_irq_permission(XSM_HOOK, d, irq, flags) )
+- break;
+-
+- if ( flags )
++ ret = -EPERM;
++ else if ( flags )
+ ret = irq_permit_access(d, irq);
+ else
+ ret = irq_deny_access(d, irq);
+
++ iocaps_double_unlock(d, true);
+ break;
+ }
+
+@@ -579,20 +580,27 @@ long arch_do_domctl(
+ break;
+
+ irq = domain_pirq_to_irq(d, bind->machine_irq);
+- ret = -EPERM;
+- if ( irq <= 0 || !irq_access_permitted(currd, irq) )
+- break;
++ if ( irq <= 0 )
++ ret = -EPERM;
+
+- ret = -ESRCH;
+- if ( is_iommu_enabled(d) )
++ read_lock(&currd->caps_lock);
++
++ if ( !irq_access_permitted(currd, irq) )
++ ret = -EPERM;
++ else if ( is_iommu_enabled(d) )
+ {
+ pcidevs_lock();
+ ret = pt_irq_create_bind(d, bind);
+ pcidevs_unlock();
++
++ if ( ret < 0 )
++ printk(XENLOG_G_ERR "pt_irq_create_bind failed (%ld) for %pd\n",
++ ret, d);
+ }
+- if ( ret < 0 )
+- printk(XENLOG_G_ERR "pt_irq_create_bind failed (%ld) for dom%d\n",
+- ret, d->domain_id);
++ else
++ ret = -ESRCH;
++
++ read_unlock(&currd->caps_lock);
+ break;
+ }
+
+@@ -605,23 +613,26 @@ long arch_do_domctl(
+ if ( !is_hvm_domain(d) )
+ break;
+
+- ret = -EPERM;
+- if ( irq <= 0 || !irq_access_permitted(currd, irq) )
+- break;
+-
+ ret = xsm_unbind_pt_irq(XSM_HOOK, d, bind);
+ if ( ret )
+ break;
+
+- if ( is_iommu_enabled(d) )
++ read_lock(&currd->caps_lock);
++
++ if ( !irq_access_permitted(currd, irq) )
++ ret = -EPERM;
++ else if ( is_iommu_enabled(d) )
+ {
+ pcidevs_lock();
+ ret = pt_irq_destroy_bind(d, bind);
+ pcidevs_unlock();
++
++ if ( ret < 0 )
++ printk(XENLOG_G_ERR "pt_irq_destroy_bind failed (%ld) for %pd\n",
++ ret, d);
+ }
+- if ( ret < 0 )
+- printk(XENLOG_G_ERR "pt_irq_destroy_bind failed (%ld) for dom%d\n",
+- ret, d->domain_id);
++
++ read_unlock(&currd->caps_lock);
+ break;
+ }
+
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -695,6 +695,9 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ ret = -EINVAL;
+ break;
+ }
++
++ iocaps_double_lock(d, true);
++
+ irq = pirq_access_permitted(current->domain, pirq);
+ if ( !irq || xsm_irq_permission(XSM_HOOK, d, irq, allow) )
+ ret = -EPERM;
+@@ -702,6 +705,8 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ ret = irq_permit_access(d, irq);
+ else
+ ret = irq_deny_access(d, irq);
++
++ iocaps_double_unlock(d, true);
+ break;
+ }
+ #endif
diff --git a/xsa492-4.21-08.patch b/xsa492-4.21-08.patch
new file mode 100644
index 0000000..4aefbf4
--- /dev/null
+++ b/xsa492-4.21-08.patch
@@ -0,0 +1,85 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: XSM/Flask: split the .iomem_mapping() hook
+
+It's used twice in entirely different situations. The use in do_domctl()
+wants to become an ordinary XSM_DM_PRIV invocation, while the one in vPCI
+code need to remain XSM_HOOK (it may plausibly become XSM_TARGET). For
+Flask, the same backing function will continue to be used for the time
+being.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+
+--- a/xen/drivers/vpci/header.c
++++ b/xen/drivers/vpci/header.c
+@@ -67,7 +67,7 @@ static int cf_check map_range(
+ return -EPERM;
+ }
+
+- rc = xsm_iomem_mapping(XSM_HOOK, map->d, map_mfn, m_end, map->map);
++ rc = xsm_iomem_mapping_vpci(XSM_HOOK, map->d, map_mfn, m_end, map->map);
+ if ( rc )
+ {
+ printk(XENLOG_G_WARNING
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -580,6 +580,13 @@ static XSM_INLINE int cf_check xsm_iomem
+ return xsm_default_action(action, current->domain, d);
+ }
+
++static XSM_INLINE int cf_check xsm_iomem_mapping_vpci(
++ XSM_DEFAULT_ARG struct domain *d, uint64_t s, uint64_t e, uint8_t allow)
++{
++ XSM_ASSERT_ACTION(XSM_HOOK);
++ return xsm_default_action(action, current->domain, d);
++}
++
+ static XSM_INLINE int cf_check xsm_pci_config_permission(
+ XSM_DEFAULT_ARG struct domain *d, uint32_t machine_bdf, uint16_t start,
+ uint16_t end, uint8_t access)
+--- a/xen/include/xsm/xsm.h
++++ b/xen/include/xsm/xsm.h
+@@ -118,6 +118,8 @@ struct xsm_ops {
+ uint8_t allow);
+ int (*iomem_mapping)(struct domain *d, uint64_t s, uint64_t e,
+ uint8_t allow);
++ int (*iomem_mapping_vpci)(struct domain *d, uint64_t s, uint64_t e,
++ uint8_t allow);
+ int (*pci_config_permission)(struct domain *d, uint32_t machine_bdf,
+ uint16_t start, uint16_t end, uint8_t access);
+
+@@ -523,6 +525,12 @@ static inline int xsm_iomem_mapping(
+ return alternative_call(xsm_ops.iomem_mapping, d, s, e, allow);
+ }
+
++static inline int xsm_iomem_mapping_vpci(
++ xsm_default_t def, struct domain *d, uint64_t s, uint64_t e, uint8_t allow)
++{
++ return alternative_call(xsm_ops.iomem_mapping_vpci, d, s, e, allow);
++}
++
+ static inline int xsm_pci_config_permission(
+ xsm_default_t def, struct domain *d, uint32_t machine_bdf, uint16_t start,
+ uint16_t end, uint8_t access)
+--- a/xen/xsm/dummy.c
++++ b/xen/xsm/dummy.c
+@@ -76,6 +76,7 @@ static const struct xsm_ops __initconst_
+ .irq_permission = xsm_irq_permission,
+ .iomem_permission = xsm_iomem_permission,
+ .iomem_mapping = xsm_iomem_mapping,
++ .iomem_mapping_vpci = xsm_iomem_mapping_vpci,
+ .pci_config_permission = xsm_pci_config_permission,
+ .get_vnumainfo = xsm_get_vnumainfo,
+
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -1950,6 +1950,7 @@ static const struct xsm_ops __initconst_
+ .irq_permission = flask_irq_permission,
+ .iomem_permission = flask_iomem_permission,
+ .iomem_mapping = flask_iomem_mapping,
++ .iomem_mapping_vpci = flask_iomem_mapping,
+ .pci_config_permission = flask_pci_config_permission,
+
+ .resource_plug_core = flask_resource_plug_core,
diff --git a/xsa492-4.21-09.patch b/xsa492-4.21-09.patch
new file mode 100644
index 0000000..96e9403
--- /dev/null
+++ b/xsa492-4.21-09.patch
@@ -0,0 +1,194 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl: handle XEN_DOMCTL_memory_mapping without acquiring domctl lock
+
+With dedicated locking added, the domctl lock isn't required here anymore.
+Move the re-purposed dedicated XSM check as early as possible.
+
+Minimal "modernization": Switch "add" to bool and use %pd in log messages.
+
+This is part of XSA-492.
+
+Fixes: fda49f9b3fbb ("Add build option to allow more hypercalls from stubdoms")
+Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -376,6 +376,66 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ copyback = true;
+ goto domctl_out_unlock_domonly;
+
++ case XEN_DOMCTL_memory_mapping:
++ {
++ unsigned long gfn = op->u.memory_mapping.first_gfn;
++ unsigned long mfn = op->u.memory_mapping.first_mfn;
++ unsigned long nr_mfns = op->u.memory_mapping.nr_mfns;
++ unsigned long mfn_end = mfn + nr_mfns - 1;
++ bool add = op->u.memory_mapping.add_mapping;
++
++ ret = -EINVAL;
++ if ( mfn_end < mfn || /* Wrap? */
++ ((mfn | mfn_end) >> (paddr_bits - PAGE_SHIFT)) ||
++ (gfn + nr_mfns - 1) < gfn ) /* Wrap? */
++ goto domctl_out_unlock_domonly;
++
++ ret = xsm_iomem_mapping(XSM_DM_PRIV, d, mfn, mfn_end, add);
++ if ( ret || !paging_mode_translate(d) )
++ goto domctl_out_unlock_domonly;
++
++#ifndef CONFIG_X86 /* XXX ARM!? */
++ ret = -E2BIG;
++ /* Must break hypercall up as this could take a while. */
++ if ( nr_mfns > 64 )
++ goto domctl_out_unlock_domonly;
++#endif
++
++ iocaps_double_lock(d, false);
++
++ ret = -EPERM;
++ if ( !iomem_access_permitted(current->domain, mfn, mfn_end) ||
++ !iomem_access_permitted(d, mfn, mfn_end) )
++ /* Nothing. */;
++ else if ( add )
++ {
++ printk(XENLOG_G_DEBUG
++ "memory_map:add: %pd gfn=%lx mfn=%lx nr=%lx\n",
++ d, gfn, mfn, nr_mfns);
++
++ ret = map_mmio_regions(d, _gfn(gfn), nr_mfns, _mfn(mfn));
++ if ( ret < 0 )
++ printk(XENLOG_G_WARNING
++ "memory_map:fail: %pd gfn=%lx mfn=%lx nr=%lx ret:%ld\n",
++ d, gfn, mfn, nr_mfns, ret);
++ }
++ else
++ {
++ printk(XENLOG_G_DEBUG
++ "memory_map:remove: %pd gfn=%lx mfn=%lx nr=%lx\n",
++ d, gfn, mfn, nr_mfns);
++
++ ret = unmap_mmio_regions(d, _gfn(gfn), nr_mfns, _mfn(mfn));
++ if ( ret < 0 && is_hardware_domain(current->domain) )
++ printk(XENLOG_ERR
++ "memory_map: error %ld removing %pd access to [%lx,%lx]\n",
++ ret, d, mfn, mfn_end);
++ }
++
++ iocaps_double_unlock(d, false);
++ goto domctl_out_unlock_domonly;
++ }
++
+ default:
+ /* Everything else handled further down. */
+ break;
+@@ -736,64 +796,6 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ break;
+ }
+
+- case XEN_DOMCTL_memory_mapping:
+- {
+- unsigned long gfn = op->u.memory_mapping.first_gfn;
+- unsigned long mfn = op->u.memory_mapping.first_mfn;
+- unsigned long nr_mfns = op->u.memory_mapping.nr_mfns;
+- unsigned long mfn_end = mfn + nr_mfns - 1;
+- int add = op->u.memory_mapping.add_mapping;
+-
+- ret = -EINVAL;
+- if ( mfn_end < mfn || /* wrap? */
+- ((mfn | mfn_end) >> (paddr_bits - PAGE_SHIFT)) ||
+- (gfn + nr_mfns - 1) < gfn ) /* wrap? */
+- break;
+-
+-#ifndef CONFIG_X86 /* XXX ARM!? */
+- ret = -E2BIG;
+- /* Must break hypercall up as this could take a while. */
+- if ( nr_mfns > 64 )
+- break;
+-#endif
+-
+- iocaps_double_lock(d, false);
+-
+- ret = -EPERM;
+- if ( !iomem_access_permitted(current->domain, mfn, mfn_end) ||
+- !iomem_access_permitted(d, mfn, mfn_end) ||
+- (ret = xsm_iomem_mapping(XSM_HOOK, d, mfn, mfn_end, add)) ||
+- !paging_mode_translate(d) )
+- /* Nothing. */;
+- else if ( add )
+- {
+- printk(XENLOG_G_DEBUG
+- "memory_map:add: dom%d gfn=%lx mfn=%lx nr=%lx\n",
+- d->domain_id, gfn, mfn, nr_mfns);
+-
+- ret = map_mmio_regions(d, _gfn(gfn), nr_mfns, _mfn(mfn));
+- if ( ret < 0 )
+- printk(XENLOG_G_WARNING
+- "memory_map:fail: dom%d gfn=%lx mfn=%lx nr=%lx ret:%ld\n",
+- d->domain_id, gfn, mfn, nr_mfns, ret);
+- }
+- else
+- {
+- printk(XENLOG_G_DEBUG
+- "memory_map:remove: dom%d gfn=%lx mfn=%lx nr=%lx\n",
+- d->domain_id, gfn, mfn, nr_mfns);
+-
+- ret = unmap_mmio_regions(d, _gfn(gfn), nr_mfns, _mfn(mfn));
+- if ( ret < 0 && is_hardware_domain(current->domain) )
+- printk(XENLOG_ERR
+- "memory_map: error %ld removing dom%d access to [%lx,%lx]\n",
+- ret, d->domain_id, mfn, mfn_end);
+- }
+-
+- iocaps_double_unlock(d, false);
+- break;
+- }
+-
+ case XEN_DOMCTL_settimeoffset:
+ domain_set_time_offset(d, op->u.settimeoffset.time_offset_seconds);
+ break;
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -168,13 +168,13 @@ static XSM_INLINE int cf_check xsm_domct
+ switch ( cmd )
+ {
+ case XEN_DOMCTL_ioport_mapping:
+- case XEN_DOMCTL_memory_mapping:
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_unbind_pt_irq:
+ return xsm_default_action(XSM_DM_PRIV, current->domain, d);
+
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
++ case XEN_DOMCTL_memory_mapping:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+
+@@ -576,7 +576,7 @@ static XSM_INLINE int cf_check xsm_iomem
+ static XSM_INLINE int cf_check xsm_iomem_mapping(
+ XSM_DEFAULT_ARG struct domain *d, uint64_t s, uint64_t e, uint8_t allow)
+ {
+- XSM_ASSERT_ACTION(XSM_HOOK);
++ XSM_ASSERT_ACTION(XSM_DM_PRIV);
+ return xsm_default_action(action, current->domain, d);
+ }
+
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -685,6 +685,7 @@ static int cf_check flask_domctl(struct
+ /* These have individual XSM hooks and don't make it here. */
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
++ case XEN_DOMCTL_memory_mapping:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+
+@@ -692,7 +693,6 @@ static int cf_check flask_domctl(struct
+ case XEN_DOMCTL_scheduler_op:
+ case XEN_DOMCTL_irq_permission:
+ case XEN_DOMCTL_iomem_permission:
+- case XEN_DOMCTL_memory_mapping:
+ case XEN_DOMCTL_set_target:
+ case XEN_DOMCTL_vm_event_op:
+
diff --git a/xsa492-4.21-10.patch b/xsa492-4.21-10.patch
new file mode 100644
index 0000000..6406a19
--- /dev/null
+++ b/xsa492-4.21-10.patch
@@ -0,0 +1,97 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl: handle XEN_DOMCTL_ioport_mapping without acquiring domctl lock
+
+With dedicated locking added, the domctl lock isn't required here anymore.
+As the handling is in arch-specific code (x86 only), almost no code is
+being moved, but a 2nd (extensible to other sub-ops) invocation of
+arch_do_domctl() is being added. Move just the re-purposed dedicated XSM
+check as early as possible.
+
+In flask_domctl() don't put #ifdef around the moved case label.
+
+This is part of XSA-492.
+
+Fixes: fda49f9b3fbb ("Add build option to allow more hypercalls from stubdoms")
+Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+
+--- a/xen/arch/x86/domctl.c
++++ b/xen/arch/x86/domctl.c
+@@ -663,12 +663,15 @@ long arch_do_domctl(
+ break;
+ }
+
++ ret = xsm_ioport_mapping(XSM_DM_PRIV, d, fmp, fmp + np - 1, add);
++ if ( ret )
++ break;
++
+ hvm = &d->arch.hvm;
+ iocaps_double_lock(d, true);
+
+- if ( !ioports_access_permitted(currd, fmp, fmp + np - 1) ||
+- (ret = xsm_ioport_mapping(XSM_HOOK, d, fmp, fmp + np - 1, add)) )
+- ret = ret ?: -EPERM;
++ if ( !ioports_access_permitted(currd, fmp, fmp + np - 1) )
++ ret = -EPERM;
+ else if ( add )
+ {
+ printk(XENLOG_G_INFO
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -436,6 +436,10 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ goto domctl_out_unlock_domonly;
+ }
+
++ case XEN_DOMCTL_ioport_mapping:
++ ret = arch_do_domctl(op, d, u_domctl);
++ goto domctl_out_unlock_domonly;
++
+ default:
+ /* Everything else handled further down. */
+ break;
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -167,13 +167,13 @@ static XSM_INLINE int cf_check xsm_domct
+ XSM_ASSERT_ACTION(XSM_OTHER);
+ switch ( cmd )
+ {
+- case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_unbind_pt_irq:
+ return xsm_default_action(XSM_DM_PRIV, current->domain, d);
+
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
++ case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_memory_mapping:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+@@ -772,7 +772,7 @@ static XSM_INLINE int cf_check xsm_iopor
+ static XSM_INLINE int cf_check xsm_ioport_mapping(
+ XSM_DEFAULT_ARG struct domain *d, uint32_t s, uint32_t e, uint8_t allow)
+ {
+- XSM_ASSERT_ACTION(XSM_HOOK);
++ XSM_ASSERT_ACTION(XSM_DM_PRIV);
+ return xsm_default_action(action, current->domain, d);
+ }
+
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -685,6 +685,7 @@ static int cf_check flask_domctl(struct
+ /* These have individual XSM hooks and don't make it here. */
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
++ case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_memory_mapping:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+@@ -703,7 +704,6 @@ static int cf_check flask_domctl(struct
+ /* These have individual XSM hooks (arch/x86/domctl.c) */
+ case XEN_DOMCTL_shadow_op:
+ case XEN_DOMCTL_ioport_permission:
+- case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_gsi_permission:
+ #endif
+ #ifdef CONFIG_HAS_PASSTHROUGH
diff --git a/xsa492-4.21-11.patch b/xsa492-4.21-11.patch
new file mode 100644
index 0000000..647fd5a
--- /dev/null
+++ b/xsa492-4.21-11.patch
@@ -0,0 +1,128 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl: handle XEN_DOMCTL_{,un}bind_pt_irq without acquiring domctl lock
+
+With dedicated locking added, the domctl lock isn't required here anymore.
+(It also already isn't used when pt_irq_{create,destroy}_bind() are
+invoked for PVH Dom0.) As the handling is in arch-specific code, no code
+is being moved, but the 2nd (extensible to other sub-ops like the ones
+here) invocation of arch_do_domctl() is being re-used.
+
+This is part of XSA-492.
+
+Fixes: fda49f9b3fbb ("Add build option to allow more hypercalls from stubdoms")
+Reported-by: Andrew Cooper <andrew.cooper3@citrix.com>
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Acked-by: Julien Grall <julien@xen.org>
+
+--- a/xen/arch/arm/domctl.c
++++ b/xen/arch/arm/domctl.c
+@@ -104,7 +104,7 @@ long arch_do_domctl(struct xen_domctl *d
+ if ( rc )
+ return rc;
+
+- rc = xsm_bind_pt_irq(XSM_HOOK, d, bind);
++ rc = xsm_bind_pt_irq(XSM_DM_PRIV, d, bind);
+ if ( rc )
+ return rc;
+
+@@ -140,7 +140,7 @@ long arch_do_domctl(struct xen_domctl *d
+ if ( irq != virq )
+ return -EINVAL;
+
+- rc = xsm_unbind_pt_irq(XSM_HOOK, d, bind);
++ rc = xsm_unbind_pt_irq(XSM_DM_PRIV, d, bind);
+ if ( rc )
+ return rc;
+
+--- a/xen/arch/x86/domctl.c
++++ b/xen/arch/x86/domctl.c
+@@ -575,7 +575,7 @@ long arch_do_domctl(
+ if ( !is_hvm_domain(d) )
+ break;
+
+- ret = xsm_bind_pt_irq(XSM_HOOK, d, bind);
++ ret = xsm_bind_pt_irq(XSM_DM_PRIV, d, bind);
+ if ( ret )
+ break;
+
+@@ -613,7 +613,7 @@ long arch_do_domctl(
+ if ( !is_hvm_domain(d) )
+ break;
+
+- ret = xsm_unbind_pt_irq(XSM_HOOK, d, bind);
++ ret = xsm_unbind_pt_irq(XSM_DM_PRIV, d, bind);
+ if ( ret )
+ break;
+
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -437,6 +437,8 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ }
+
+ case XEN_DOMCTL_ioport_mapping:
++ case XEN_DOMCTL_bind_pt_irq:
++ case XEN_DOMCTL_unbind_pt_irq:
+ ret = arch_do_domctl(op, d, u_domctl);
+ goto domctl_out_unlock_domonly;
+
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -168,13 +168,11 @@ static XSM_INLINE int cf_check xsm_domct
+ switch ( cmd )
+ {
+ case XEN_DOMCTL_bind_pt_irq:
+- case XEN_DOMCTL_unbind_pt_irq:
+- return xsm_default_action(XSM_DM_PRIV, current->domain, d);
+-
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
+ case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_memory_mapping:
++ case XEN_DOMCTL_unbind_pt_irq:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+
+@@ -541,14 +539,14 @@ static XSM_INLINE int cf_check xsm_unmap
+ static XSM_INLINE int cf_check xsm_bind_pt_irq(
+ XSM_DEFAULT_ARG struct domain *d, struct xen_domctl_bind_pt_irq *bind)
+ {
+- XSM_ASSERT_ACTION(XSM_HOOK);
++ XSM_ASSERT_ACTION(XSM_DM_PRIV);
+ return xsm_default_action(action, current->domain, d);
+ }
+
+ static XSM_INLINE int cf_check xsm_unbind_pt_irq(
+ XSM_DEFAULT_ARG struct domain *d, struct xen_domctl_bind_pt_irq *bind)
+ {
+- XSM_ASSERT_ACTION(XSM_HOOK);
++ XSM_ASSERT_ACTION(XSM_DM_PRIV);
+ return xsm_default_action(action, current->domain, d);
+ }
+
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -683,10 +683,12 @@ static int cf_check flask_domctl(struct
+ return avc_current_has_perm(ssidref, SECCLASS_DOMAIN, DOMAIN__CREATE, NULL);
+
+ /* These have individual XSM hooks and don't make it here. */
++ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
+ case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_memory_mapping:
++ case XEN_DOMCTL_unbind_pt_irq:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+
+@@ -697,9 +699,6 @@ static int cf_check flask_domctl(struct
+ case XEN_DOMCTL_set_target:
+ case XEN_DOMCTL_vm_event_op:
+
+- /* These have individual XSM hooks (arch/../domctl.c) */
+- case XEN_DOMCTL_bind_pt_irq:
+- case XEN_DOMCTL_unbind_pt_irq:
+ #ifdef CONFIG_X86
+ /* These have individual XSM hooks (arch/x86/domctl.c) */
+ case XEN_DOMCTL_shadow_op:
diff --git a/xsa492-4.21-12.patch b/xsa492-4.21-12.patch
new file mode 100644
index 0000000..c19d1e1
--- /dev/null
+++ b/xsa492-4.21-12.patch
@@ -0,0 +1,172 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl: handle XEN_DOMCTL_io{mem,port}_permission without acquiring domctl lock
+
+With dedicated locking added, the domctl lock isn't required here anymore.
+As the I/O port handling is in arch-specific code (x86 only), no code is
+being moved, but the 2nd invocation of arch_do_domctl() is re-used. Move
+the re-purposed dedicated XSM checks as early as possible.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+
+--- a/xen/arch/x86/domctl.c
++++ b/xen/arch/x86/domctl.c
+@@ -233,12 +233,17 @@ long arch_do_domctl(
+ unsigned int np = domctl->u.ioport_permission.nr_ports;
+ int allow = domctl->u.ioport_permission.allow_access;
+
++ ret = -EINVAL;
++ if ( (fp + np) <= fp || (fp + np) > MAX_IOPORTS )
++ break;
++
++ ret = xsm_ioport_permission(XSM_PRIV, d, fp, fp + np - 1, allow);
++ if ( ret )
++ break;
++
+ iocaps_double_lock(d, true);
+
+- if ( (fp + np) <= fp || (fp + np) > MAX_IOPORTS )
+- ret = -EINVAL;
+- else if ( !ioports_access_permitted(currd, fp, fp + np - 1) ||
+- xsm_ioport_permission(XSM_HOOK, d, fp, fp + np - 1, allow) )
++ if ( !ioports_access_permitted(currd, fp, fp + np - 1) )
+ ret = -EPERM;
+ else if ( allow )
+ ret = ioports_permit_access(d, fp, fp + np - 1);
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -376,6 +376,34 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ copyback = true;
+ goto domctl_out_unlock_domonly;
+
++ case XEN_DOMCTL_iomem_permission:
++ {
++ unsigned long mfn = op->u.iomem_permission.first_mfn;
++ unsigned long nr_mfns = op->u.iomem_permission.nr_mfns;
++ bool allow = op->u.iomem_permission.allow_access;
++
++ ret = -EINVAL;
++ if ( (mfn + nr_mfns - 1) < mfn ) /* Wrap? */
++ goto domctl_out_unlock_domonly;
++
++ ret = xsm_iomem_permission(XSM_PRIV, d, mfn, mfn + nr_mfns - 1, allow);
++ if ( ret )
++ goto domctl_out_unlock_domonly;
++
++ iocaps_double_lock(d, true);
++
++ if ( !iomem_access_permitted(current->domain,
++ mfn, mfn + nr_mfns - 1) )
++ ret = -EPERM;
++ else if ( allow )
++ ret = iomem_permit_access(d, mfn, mfn + nr_mfns - 1);
++ else
++ ret = iomem_deny_access(d, mfn, mfn + nr_mfns - 1);
++
++ iocaps_double_unlock(d, true);
++ goto domctl_out_unlock_domonly;
++ }
++
+ case XEN_DOMCTL_memory_mapping:
+ {
+ unsigned long gfn = op->u.memory_mapping.first_gfn;
+@@ -436,6 +464,7 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ goto domctl_out_unlock_domonly;
+ }
+
++ case XEN_DOMCTL_ioport_permission:
+ case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_unbind_pt_irq:
+@@ -777,31 +806,6 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ }
+ #endif
+
+- case XEN_DOMCTL_iomem_permission:
+- {
+- unsigned long mfn = op->u.iomem_permission.first_mfn;
+- unsigned long nr_mfns = op->u.iomem_permission.nr_mfns;
+- int allow = op->u.iomem_permission.allow_access;
+-
+- ret = -EINVAL;
+- if ( (mfn + nr_mfns - 1) < mfn ) /* wrap? */
+- break;
+-
+- iocaps_double_lock(d, true);
+-
+- if ( !iomem_access_permitted(current->domain,
+- mfn, mfn + nr_mfns - 1) ||
+- xsm_iomem_permission(XSM_HOOK, d, mfn, mfn + nr_mfns - 1, allow) )
+- ret = -EPERM;
+- else if ( allow )
+- ret = iomem_permit_access(d, mfn, mfn + nr_mfns - 1);
+- else
+- ret = iomem_deny_access(d, mfn, mfn + nr_mfns - 1);
+-
+- iocaps_double_unlock(d, true);
+- break;
+- }
+-
+ case XEN_DOMCTL_settimeoffset:
+ domain_set_time_offset(d, op->u.settimeoffset.time_offset_seconds);
+ break;
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -170,7 +170,9 @@ static XSM_INLINE int cf_check xsm_domct
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
++ case XEN_DOMCTL_iomem_permission:
+ case XEN_DOMCTL_ioport_mapping:
++ case XEN_DOMCTL_ioport_permission:
+ case XEN_DOMCTL_memory_mapping:
+ case XEN_DOMCTL_unbind_pt_irq:
+ ASSERT_UNREACHABLE();
+@@ -567,7 +569,7 @@ static XSM_INLINE int cf_check xsm_irq_p
+ static XSM_INLINE int cf_check xsm_iomem_permission(
+ XSM_DEFAULT_ARG struct domain *d, uint64_t s, uint64_t e, uint8_t allow)
+ {
+- XSM_ASSERT_ACTION(XSM_HOOK);
++ XSM_ASSERT_ACTION(XSM_PRIV);
+ return xsm_default_action(action, current->domain, d);
+ }
+
+@@ -763,7 +765,7 @@ static XSM_INLINE int cf_check xsm_priv_
+ static XSM_INLINE int cf_check xsm_ioport_permission(
+ XSM_DEFAULT_ARG struct domain *d, uint32_t s, uint32_t e, uint8_t allow)
+ {
+- XSM_ASSERT_ACTION(XSM_HOOK);
++ XSM_ASSERT_ACTION(XSM_PRIV);
+ return xsm_default_action(action, current->domain, d);
+ }
+
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -686,7 +686,9 @@ static int cf_check flask_domctl(struct
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
++ case XEN_DOMCTL_iomem_permission:
+ case XEN_DOMCTL_ioport_mapping:
++ case XEN_DOMCTL_ioport_permission:
+ case XEN_DOMCTL_memory_mapping:
+ case XEN_DOMCTL_unbind_pt_irq:
+ ASSERT_UNREACHABLE();
+@@ -695,14 +697,12 @@ static int cf_check flask_domctl(struct
+ /* These have individual XSM hooks (common/domctl.c) */
+ case XEN_DOMCTL_scheduler_op:
+ case XEN_DOMCTL_irq_permission:
+- case XEN_DOMCTL_iomem_permission:
+ case XEN_DOMCTL_set_target:
+ case XEN_DOMCTL_vm_event_op:
+
+ #ifdef CONFIG_X86
+ /* These have individual XSM hooks (arch/x86/domctl.c) */
+ case XEN_DOMCTL_shadow_op:
+- case XEN_DOMCTL_ioport_permission:
+ case XEN_DOMCTL_gsi_permission:
+ #endif
+ #ifdef CONFIG_HAS_PASSTHROUGH
diff --git a/xsa492-4.21-13.patch b/xsa492-4.21-13.patch
new file mode 100644
index 0000000..91ce1ae
--- /dev/null
+++ b/xsa492-4.21-13.patch
@@ -0,0 +1,163 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl: handle XEN_DOMCTL_{irq,gsi}_permission without acquiring domctl lock
+
+With dedicated locking added, the domctl lock isn't required here anymore.
+As the GSI handling is in arch-specific code (x86 only), no code is being
+moved there; the 2nd invocation of arch_do_domctl() is re-used. Move the
+re-purposed (XSM_HOOK -> XSM_PRIV, as xsm_domctl() is now bypassed)
+dedicated XSM checks as early as possible.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/arch/x86/domctl.c
++++ b/xen/arch/x86/domctl.c
+@@ -272,10 +272,13 @@ long arch_do_domctl(
+ break;
+ }
+
++ ret = xsm_irq_permission(XSM_PRIV, d, irq, flags);
++ if ( ret )
++ break;
++
+ iocaps_double_lock(d, true);
+
+- if ( !irq_access_permitted(currd, irq) ||
+- xsm_irq_permission(XSM_HOOK, d, irq, flags) )
++ if ( !irq_access_permitted(currd, irq) )
+ ret = -EPERM;
+ else if ( flags )
+ ret = irq_permit_access(d, irq);
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -464,8 +464,41 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ goto domctl_out_unlock_domonly;
+ }
+
++#ifdef CONFIG_HAS_PIRQ
++ case XEN_DOMCTL_irq_permission:
++ {
++ unsigned int pirq = op->u.irq_permission.pirq, irq;
++ bool allow = op->u.irq_permission.allow_access;
++
++ ret = -EINVAL;
++ if ( pirq >= current->domain->nr_pirqs )
++ goto domctl_out_unlock_domonly;
++
++ irq = domain_pirq_to_irq(current->domain, pirq);
++
++ ret = -EPERM;
++ if ( irq )
++ ret = xsm_irq_permission(XSM_PRIV, d, irq, allow);
++ if ( ret )
++ goto domctl_out_unlock_domonly;
++
++ iocaps_double_lock(d, true);
++
++ if ( !irq_access_permitted(current->domain, irq) )
++ ret = -EPERM;
++ else if ( allow )
++ ret = irq_permit_access(d, irq);
++ else
++ ret = irq_deny_access(d, irq);
++
++ iocaps_double_unlock(d, true);
++ goto domctl_out_unlock_domonly;
++ }
++#endif
++
+ case XEN_DOMCTL_ioport_permission:
+ case XEN_DOMCTL_ioport_mapping:
++ case XEN_DOMCTL_gsi_permission:
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_unbind_pt_irq:
+ ret = arch_do_domctl(op, d, u_domctl);
+@@ -779,33 +812,6 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ }
+ break;
+
+-#ifdef CONFIG_HAS_PIRQ
+- case XEN_DOMCTL_irq_permission:
+- {
+- unsigned int pirq = op->u.irq_permission.pirq, irq;
+- int allow = op->u.irq_permission.allow_access;
+-
+- if ( pirq >= current->domain->nr_pirqs )
+- {
+- ret = -EINVAL;
+- break;
+- }
+-
+- iocaps_double_lock(d, true);
+-
+- irq = pirq_access_permitted(current->domain, pirq);
+- if ( !irq || xsm_irq_permission(XSM_HOOK, d, irq, allow) )
+- ret = -EPERM;
+- else if ( allow )
+- ret = irq_permit_access(d, irq);
+- else
+- ret = irq_deny_access(d, irq);
+-
+- iocaps_double_unlock(d, true);
+- break;
+- }
+-#endif
+-
+ case XEN_DOMCTL_settimeoffset:
+ domain_set_time_offset(d, op->u.settimeoffset.time_offset_seconds);
+ break;
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -170,9 +170,11 @@ static XSM_INLINE int cf_check xsm_domct
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
++ case XEN_DOMCTL_gsi_permission:
+ case XEN_DOMCTL_iomem_permission:
+ case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_ioport_permission:
++ case XEN_DOMCTL_irq_permission:
+ case XEN_DOMCTL_memory_mapping:
+ case XEN_DOMCTL_unbind_pt_irq:
+ ASSERT_UNREACHABLE();
+@@ -562,7 +564,7 @@ static XSM_INLINE int cf_check xsm_unmap
+ static XSM_INLINE int cf_check xsm_irq_permission(
+ XSM_DEFAULT_ARG struct domain *d, int pirq, uint8_t allow)
+ {
+- XSM_ASSERT_ACTION(XSM_HOOK);
++ XSM_ASSERT_ACTION(XSM_PRIV);
+ return xsm_default_action(action, current->domain, d);
+ }
+
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -686,9 +686,11 @@ static int cf_check flask_domctl(struct
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_getdomaininfo:
+ case XEN_DOMCTL_get_domain_state:
++ case XEN_DOMCTL_gsi_permission:
+ case XEN_DOMCTL_iomem_permission:
+ case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_ioport_permission:
++ case XEN_DOMCTL_irq_permission:
+ case XEN_DOMCTL_memory_mapping:
+ case XEN_DOMCTL_unbind_pt_irq:
+ ASSERT_UNREACHABLE();
+@@ -696,14 +698,12 @@ static int cf_check flask_domctl(struct
+
+ /* These have individual XSM hooks (common/domctl.c) */
+ case XEN_DOMCTL_scheduler_op:
+- case XEN_DOMCTL_irq_permission:
+ case XEN_DOMCTL_set_target:
+ case XEN_DOMCTL_vm_event_op:
+
+ #ifdef CONFIG_X86
+ /* These have individual XSM hooks (arch/x86/domctl.c) */
+ case XEN_DOMCTL_shadow_op:
+- case XEN_DOMCTL_gsi_permission:
+ #endif
+ #ifdef CONFIG_HAS_PASSTHROUGH
+ /*
diff --git a/xsa492-4.21-14.patch b/xsa492-4.21-14.patch
new file mode 100644
index 0000000..2b13377
--- /dev/null
+++ b/xsa492-4.21-14.patch
@@ -0,0 +1,179 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl/XSM: drop vm_event_control hook
+
+Integrate the checking with xsm_domctl(). Care needs to be taken with the
+GET_VERSION sub-op, which may be invoked with DOMID_INVALID, and which has
+been (and continues to be) bypassing XSM checking.
+
+Since the latter two parameters were unused, monitor_domctl() invoking the
+hook was actually redundant with the earlier xsm_domctl() (as can be seen
+nicely from the hunks changing xsm/flask/hooks.c).
+
+As a positive side effect, permissions are then checked at the same early
+point with and without Flask.
+
+While folding XEN_DOMCTL_monitor_op and XEN_DOMCTL_vm_event_op in
+flask_domctl(), also fold in XEN_DOMCTL_set_access_required.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -496,6 +496,23 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ }
+ #endif
+
++ case XEN_DOMCTL_vm_event_op:
++ if ( op->u.vm_event_op.op == XEN_VM_EVENT_GET_VERSION )
++ {
++ /* No XSM check (and potentially d == NULL) here. */
++ ret = vm_event_domctl(d, &op->u.vm_event_op);
++ if ( !ret )
++ copyback = true;
++ goto domctl_out_unlock_domonly;
++ }
++ if ( !d )
++ {
++ ret = -ESRCH;
++ goto domctl_out_unlock_domonly;
++ }
++ /* Other sub-ops handled further down. */
++ break;
++
+ case XEN_DOMCTL_ioport_permission:
+ case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_gsi_permission:
+--- a/xen/common/monitor.c
++++ b/xen/common/monitor.c
+@@ -30,16 +30,11 @@
+
+ int monitor_domctl(struct domain *d, struct xen_domctl_monitor_op *mop)
+ {
+- int rc;
+ bool requested_status = false;
+
+ if ( unlikely(current->domain == d) ) /* no domain_pause() */
+ return -EPERM;
+
+- rc = xsm_vm_event_control(XSM_PRIV, d, mop->op, mop->event);
+- if ( unlikely(rc) )
+- return rc;
+-
+ switch ( mop->op )
+ {
+ case XEN_DOMCTL_MONITOR_OP_ENABLE:
+--- a/xen/common/vm_event.c
++++ b/xen/common/vm_event.c
+@@ -603,11 +603,10 @@ int vm_event_domctl(struct domain *d, st
+
+ /* All other subops need to target a real domain. */
+ if ( unlikely(d == NULL) )
+- return -ESRCH;
+-
+- rc = xsm_vm_event_control(XSM_PRIV, d, vec->mode, vec->op);
+- if ( rc )
+- return rc;
++ {
++ ASSERT_UNREACHABLE();
++ return -EILSEQ;
++ }
+
+ if ( unlikely(d == current->domain) ) /* no domain_pause() */
+ {
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -652,13 +652,6 @@ static XSM_INLINE int cf_check xsm_hvm_a
+ }
+ }
+
+-static XSM_INLINE int cf_check xsm_vm_event_control(
+- XSM_DEFAULT_ARG struct domain *d, int mode, int op)
+-{
+- XSM_ASSERT_ACTION(XSM_PRIV);
+- return xsm_default_action(action, current->domain, d);
+-}
+-
+ #ifdef CONFIG_VM_EVENT
+ static XSM_INLINE int cf_check xsm_mem_access(XSM_DEFAULT_ARG struct domain *d)
+ {
+--- a/xen/include/xsm/xsm.h
++++ b/xen/include/xsm/xsm.h
+@@ -157,8 +157,6 @@ struct xsm_ops {
+ int (*hvm_altp2mhvm_op)(struct domain *d, uint64_t mode, uint32_t op);
+ int (*get_vnumainfo)(struct domain *d);
+
+- int (*vm_event_control)(struct domain *d, int mode, int op);
+-
+ #ifdef CONFIG_VM_EVENT
+ int (*mem_access)(struct domain *d);
+ #endif
+@@ -657,12 +655,6 @@ static inline int xsm_get_vnumainfo(xsm_
+ return alternative_call(xsm_ops.get_vnumainfo, d);
+ }
+
+-static inline int xsm_vm_event_control(
+- xsm_default_t def, struct domain *d, int mode, int op)
+-{
+- return alternative_call(xsm_ops.vm_event_control, d, mode, op);
+-}
+-
+ #ifdef CONFIG_VM_EVENT
+ static inline int xsm_mem_access(xsm_default_t def, struct domain *d)
+ {
+--- a/xen/xsm/dummy.c
++++ b/xen/xsm/dummy.c
+@@ -116,8 +116,6 @@ static const struct xsm_ops __initconst_
+ .remove_from_physmap = xsm_remove_from_physmap,
+ .map_gmfn_foreign = xsm_map_gmfn_foreign,
+
+- .vm_event_control = xsm_vm_event_control,
+-
+ #ifdef CONFIG_VM_EVENT
+ .mem_access = xsm_mem_access,
+ #endif
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -699,7 +699,6 @@ static int cf_check flask_domctl(struct
+ /* These have individual XSM hooks (common/domctl.c) */
+ case XEN_DOMCTL_scheduler_op:
+ case XEN_DOMCTL_set_target:
+- case XEN_DOMCTL_vm_event_op:
+
+ #ifdef CONFIG_X86
+ /* These have individual XSM hooks (arch/x86/domctl.c) */
+@@ -793,9 +792,8 @@ static int cf_check flask_domctl(struct
+ return current_has_perm(d, SECCLASS_DOMAIN, DOMAIN__TRIGGER);
+
+ case XEN_DOMCTL_set_access_required:
+- return current_has_perm(d, SECCLASS_DOMAIN2, DOMAIN2__VM_EVENT);
+-
+ case XEN_DOMCTL_monitor_op:
++ case XEN_DOMCTL_vm_event_op:
+ return current_has_perm(d, SECCLASS_DOMAIN2, DOMAIN2__VM_EVENT);
+
+ case XEN_DOMCTL_debug_op:
+@@ -1368,11 +1366,6 @@ static int cf_check flask_hvm_altp2mhvm_
+ return current_has_perm(d, SECCLASS_HVM, HVM__ALTP2MHVM_OP);
+ }
+
+-static int cf_check flask_vm_event_control(struct domain *d, int mode, int op)
+-{
+- return current_has_perm(d, SECCLASS_DOMAIN2, DOMAIN2__VM_EVENT);
+-}
+-
+ #ifdef CONFIG_VM_EVENT
+ static int cf_check flask_mem_access(struct domain *d)
+ {
+@@ -1971,8 +1964,6 @@ static const struct xsm_ops __initconst_
+ .do_xsm_op = do_flask_op,
+ .get_vnumainfo = flask_get_vnumainfo,
+
+- .vm_event_control = flask_vm_event_control,
+-
+ #ifdef CONFIG_VM_EVENT
+ .mem_access = flask_mem_access,
+ #endif
diff --git a/xsa492-4.21-15.patch b/xsa492-4.21-15.patch
new file mode 100644
index 0000000..ac87f3b
--- /dev/null
+++ b/xsa492-4.21-15.patch
@@ -0,0 +1,108 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl/XSM: pass full struct xen_domctl to xsm_domctl()
+
+Subsequently some sub-ops will want to inspect their sub-sub-ops. Plus
+this way we don't need to pass SSIDref separately anymore for
+domain_create.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+
+--- a/xen/arch/x86/mm/paging.c
++++ b/xen/arch/x86/mm/paging.c
+@@ -735,7 +735,7 @@ long do_paging_domctl_cont(
+ if ( d == NULL )
+ return -ESRCH;
+
+- ret = xsm_domctl(XSM_OTHER, d, op.cmd, 0 /* SSIDref not applicable */);
++ ret = xsm_domctl(XSM_OTHER, d, &op);
+ if ( !ret )
+ {
+ if ( domctl_lock_acquire() )
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -526,9 +526,7 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ break;
+ }
+
+- ret = xsm_domctl(XSM_OTHER, d, op->cmd,
+- /* SSIDRef only applicable for cmd == createdomain */
+- op->u.createdomain.ssidref);
++ ret = xsm_domctl(XSM_OTHER, d, op);
+ if ( ret )
+ goto domctl_out_unlock_domonly;
+
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -162,10 +162,10 @@ static XSM_INLINE int cf_check xsm_set_t
+ }
+
+ static XSM_INLINE int cf_check xsm_domctl(
+- XSM_DEFAULT_ARG struct domain *d, unsigned int cmd, uint32_t ssidref)
++ XSM_DEFAULT_ARG struct domain *d, struct xen_domctl *op)
+ {
+ XSM_ASSERT_ACTION(XSM_OTHER);
+- switch ( cmd )
++ switch ( op->cmd )
+ {
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_getdomaininfo:
+--- a/xen/include/xsm/xsm.h
++++ b/xen/include/xsm/xsm.h
+@@ -61,7 +61,7 @@ struct xsm_ops {
+ int (*sysctl_scheduler_op)(int op);
+ #endif
+ int (*set_target)(struct domain *d, struct domain *e);
+- int (*domctl)(struct domain *d, unsigned int cmd, uint32_t ssidref);
++ int (*domctl)(struct domain *d, struct xen_domctl *op);
+ int (*sysctl)(int cmd);
+ int (*readconsole)(uint32_t clear);
+
+@@ -260,9 +260,9 @@ static inline int xsm_set_target(
+ }
+
+ static inline int xsm_domctl(xsm_default_t def, struct domain *d,
+- unsigned int cmd, uint32_t ssidref)
++ struct xen_domctl *op)
+ {
+- return alternative_call(xsm_ops.domctl, d, cmd, ssidref);
++ return alternative_call(xsm_ops.domctl, d, op);
+ }
+
+ static inline int xsm_sysctl(xsm_default_t def, int cmd)
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -667,10 +667,9 @@ static int cf_check flask_set_target(str
+ return rc;
+ }
+
+-static int cf_check flask_domctl(struct domain *d, unsigned int cmd,
+- uint32_t ssidref)
++static int cf_check flask_domctl(struct domain *d, struct xen_domctl *op)
+ {
+- switch ( cmd )
++ switch ( op->cmd )
+ {
+ case XEN_DOMCTL_createdomain:
+ /*
+@@ -680,7 +679,8 @@ static int cf_check flask_domctl(struct
+ * Note that d is NULL because we haven't even allocated memory for it
+ * this early in XEN_DOMCTL_createdomain.
+ */
+- return avc_current_has_perm(ssidref, SECCLASS_DOMAIN, DOMAIN__CREATE, NULL);
++ return avc_current_has_perm(op->u.createdomain.ssidref, SECCLASS_DOMAIN,
++ DOMAIN__CREATE, NULL);
+
+ /* These have individual XSM hooks and don't make it here. */
+ case XEN_DOMCTL_bind_pt_irq:
+@@ -855,7 +855,7 @@ static int cf_check flask_domctl(struct
+ return current_has_perm(d, SECCLASS_DOMAIN2, DOMAIN2__SET_LLC_COLORS);
+
+ default:
+- return avc_unknown_permission("domctl", cmd);
++ return avc_unknown_permission("domctl", op->cmd);
+ }
+ }
+
diff --git a/xsa492-4.21-16.patch b/xsa492-4.21-16.patch
new file mode 100644
index 0000000..2cb8619
--- /dev/null
+++ b/xsa492-4.21-16.patch
@@ -0,0 +1,112 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl/XSM: drop scheduler_op hook
+
+Integrate the checking with xsm_domctl(), now that it has the full op
+struct passed. As a positive side effect, permissions are then checked at
+the same early point with and without Flask.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Reviewed-by: Juergen Gross <jgross@suse.com>
+
+--- a/xen/common/sched/core.c
++++ b/xen/common/sched/core.c
+@@ -2074,10 +2074,6 @@ long sched_adjust(struct domain *d, stru
+ {
+ long ret;
+
+- ret = xsm_domctl_scheduler_op(XSM_HOOK, d, op->cmd);
+- if ( ret )
+- return ret;
+-
+ if ( op->sched_id != dom_scheduler(d)->sched_id )
+ return -EINVAL;
+
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -141,13 +141,6 @@ static XSM_INLINE int cf_check xsm_getdo
+ return xsm_default_action(action, current->domain, d);
+ }
+
+-static XSM_INLINE int cf_check xsm_domctl_scheduler_op(
+- XSM_DEFAULT_ARG struct domain *d, int cmd)
+-{
+- XSM_ASSERT_ACTION(XSM_HOOK);
+- return xsm_default_action(action, current->domain, d);
+-}
+-
+ static XSM_INLINE int cf_check xsm_sysctl_scheduler_op(XSM_DEFAULT_ARG int cmd)
+ {
+ XSM_ASSERT_ACTION(XSM_HOOK);
+--- a/xen/include/xsm/xsm.h
++++ b/xen/include/xsm/xsm.h
+@@ -56,7 +56,6 @@ struct xsm_ops {
+ struct xen_domctl_getdomaininfo *info);
+ int (*domain_create)(struct domain *d, uint32_t ssidref);
+ int (*getdomaininfo)(struct domain *d);
+- int (*domctl_scheduler_op)(struct domain *d, int op);
+ #ifdef CONFIG_SYSCTL
+ int (*sysctl_scheduler_op)(int op);
+ #endif
+@@ -240,12 +239,6 @@ static inline int xsm_get_domain_state(x
+ return alternative_call(xsm_ops.get_domain_state, d);
+ }
+
+-static inline int xsm_domctl_scheduler_op(
+- xsm_default_t def, struct domain *d, int cmd)
+-{
+- return alternative_call(xsm_ops.domctl_scheduler_op, d, cmd);
+-}
+-
+ #ifdef CONFIG_SYSCTL
+ static inline int xsm_sysctl_scheduler_op(xsm_default_t def, int cmd)
+ {
+--- a/xen/xsm/dummy.c
++++ b/xen/xsm/dummy.c
+@@ -18,7 +18,6 @@ static const struct xsm_ops __initconst_
+ .security_domaininfo = xsm_security_domaininfo,
+ .domain_create = xsm_domain_create,
+ .getdomaininfo = xsm_getdomaininfo,
+- .domctl_scheduler_op = xsm_domctl_scheduler_op,
+ #ifdef CONFIG_SYSCTL
+ .sysctl_scheduler_op = xsm_sysctl_scheduler_op,
+ #endif
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -609,7 +609,7 @@ static int cf_check flask_getdomaininfo(
+ return current_has_perm(d, SECCLASS_DOMAIN, DOMAIN__GETDOMAININFO);
+ }
+
+-static int cf_check flask_domctl_scheduler_op(struct domain *d, int op)
++static int flask_domctl_scheduler_op(struct domain *d, int op)
+ {
+ switch ( op )
+ {
+@@ -697,7 +697,6 @@ static int cf_check flask_domctl(struct
+ return -EILSEQ;
+
+ /* These have individual XSM hooks (common/domctl.c) */
+- case XEN_DOMCTL_scheduler_op:
+ case XEN_DOMCTL_set_target:
+
+ #ifdef CONFIG_X86
+@@ -745,6 +744,9 @@ static int cf_check flask_domctl(struct
+ case XEN_DOMCTL_setdomainhandle:
+ return current_has_perm(d, SECCLASS_DOMAIN, DOMAIN__SETDOMAINHANDLE);
+
++ case XEN_DOMCTL_scheduler_op:
++ return flask_domctl_scheduler_op(d, op->u.scheduler_op.cmd);
++
+ case XEN_DOMCTL_set_ext_vcpucontext:
+ case XEN_DOMCTL_set_vcpu_msrs:
+ case XEN_DOMCTL_setvcpucontext:
+@@ -1884,7 +1886,6 @@ static const struct xsm_ops __initconst_
+ .security_domaininfo = flask_security_domaininfo,
+ .domain_create = flask_domain_create,
+ .getdomaininfo = flask_getdomaininfo,
+- .domctl_scheduler_op = flask_domctl_scheduler_op,
+ #ifdef CONFIG_SYSCTL
+ .sysctl_scheduler_op = flask_sysctl_scheduler_op,
+ #endif
diff --git a/xsa492-4.21-17.patch b/xsa492-4.21-17.patch
new file mode 100644
index 0000000..99542df
--- /dev/null
+++ b/xsa492-4.21-17.patch
@@ -0,0 +1,124 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl/XSM: drop shadow_control_op hook
+
+Integrate the checking with xsm_domctl(), now that it has the full op
+struct passed. As a positive side effect, permissions are then checked at
+the same early point with and without Flask.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+
+--- a/xen/arch/x86/mm/paging.c
++++ b/xen/arch/x86/mm/paging.c
+@@ -677,10 +677,6 @@ int paging_domctl(struct domain *d, stru
+ return -EBUSY;
+ }
+
+- rc = xsm_shadow_control(XSM_HOOK, d, sc->op);
+- if ( rc )
+- return rc;
+-
+ /* Code to handle log-dirty. Note that some log dirty operations
+ * piggy-back on shadow operations. For example, when
+ * XEN_DOMCTL_SHADOW_OP_OFF is called, it first checks whether log dirty
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -682,13 +682,6 @@ static XSM_INLINE int cf_check xsm_do_mc
+ return xsm_default_action(action, current->domain, NULL);
+ }
+
+-static XSM_INLINE int cf_check xsm_shadow_control(
+- XSM_DEFAULT_ARG struct domain *d, uint32_t op)
+-{
+- XSM_ASSERT_ACTION(XSM_HOOK);
+- return xsm_default_action(action, current->domain, d);
+-}
+-
+ static XSM_INLINE int cf_check xsm_mem_sharing_op(
+ XSM_DEFAULT_ARG struct domain *d, struct domain *cd, int op)
+ {
+--- a/xen/include/xsm/xsm.h
++++ b/xen/include/xsm/xsm.h
+@@ -172,7 +172,6 @@ struct xsm_ops {
+
+ #ifdef CONFIG_X86
+ int (*do_mca)(void);
+- int (*shadow_control)(struct domain *d, uint32_t op);
+ int (*mem_sharing_op)(struct domain *d, struct domain *cd, int op);
+ int (*apic)(struct domain *d, int cmd);
+ int (*machine_memory_map)(void);
+@@ -680,12 +679,6 @@ static inline int xsm_do_mca(xsm_default
+ return alternative_call(xsm_ops.do_mca);
+ }
+
+-static inline int xsm_shadow_control(
+- xsm_default_t def, struct domain *d, uint32_t op)
+-{
+- return alternative_call(xsm_ops.shadow_control, d, op);
+-}
+-
+ static inline int xsm_mem_sharing_op(
+ xsm_default_t def, struct domain *d, struct domain *cd, int op)
+ {
+--- a/xen/xsm/dummy.c
++++ b/xen/xsm/dummy.c
+@@ -130,7 +130,6 @@ static const struct xsm_ops __initconst_
+ .platform_op = xsm_platform_op,
+ #ifdef CONFIG_X86
+ .do_mca = xsm_do_mca,
+- .shadow_control = xsm_shadow_control,
+ .mem_sharing_op = xsm_mem_sharing_op,
+ .apic = xsm_apic,
+ .machine_memory_map = xsm_machine_memory_map,
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -40,6 +40,7 @@
+
+ #ifdef CONFIG_X86
+ #include <asm/pv/shim.h>
++static int flask_shadow_control(struct domain *d, unsigned int op);
+ #else
+ #define pv_shim false
+ #endif
+@@ -699,10 +700,6 @@ static int cf_check flask_domctl(struct
+ /* These have individual XSM hooks (common/domctl.c) */
+ case XEN_DOMCTL_set_target:
+
+-#ifdef CONFIG_X86
+- /* These have individual XSM hooks (arch/x86/domctl.c) */
+- case XEN_DOMCTL_shadow_op:
+-#endif
+ #ifdef CONFIG_HAS_PASSTHROUGH
+ /*
+ * These have individual XSM hooks
+@@ -787,6 +784,11 @@ static int cf_check flask_domctl(struct
+ case XEN_DOMCTL_get_address_size:
+ return current_has_perm(d, SECCLASS_DOMAIN, DOMAIN__GETADDRSIZE);
+
++#ifdef CONFIG_X86
++ case XEN_DOMCTL_shadow_op:
++ return flask_shadow_control(d, op->u.shadow_op.op);
++#endif
++
+ case XEN_DOMCTL_mem_sharing_op:
+ return current_has_perm(d, SECCLASS_HVM, HVM__MEM_SHARING);
+
+@@ -1603,7 +1605,7 @@ static int cf_check flask_do_mca(void)
+ return domain_has_xen(current->domain, XEN__MCA_OP);
+ }
+
+-static int cf_check flask_shadow_control(struct domain *d, uint32_t op)
++static int flask_shadow_control(struct domain *d, unsigned int op)
+ {
+ uint32_t perm;
+
+@@ -1999,7 +2001,6 @@ static const struct xsm_ops __initconst_
+ .platform_op = flask_platform_op,
+ #ifdef CONFIG_X86
+ .do_mca = flask_do_mca,
+- .shadow_control = flask_shadow_control,
+ .mem_sharing_op = flask_mem_sharing_op,
+ .apic = flask_apic,
+ .machine_memory_map = flask_machine_memory_map,
diff --git a/xsa492-4.21-18.patch b/xsa492-4.21-18.patch
new file mode 100644
index 0000000..1d82124
--- /dev/null
+++ b/xsa492-4.21-18.patch
@@ -0,0 +1,94 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl: handle XEN_DOMCTL_get_device_group without acquiring domctl lock
+
+iommu_get_device_group() uses its own locking. Thus, with caller side
+locking irrelevant, it can as well be called with the domctl lock not
+held.
+
+Move the handling not only ahead of acquiring the lock, but also ahead
+of the XSM check, leveraging that the sub-op has its own hook.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -513,6 +513,10 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ /* Other sub-ops handled further down. */
+ break;
+
++ case XEN_DOMCTL_get_device_group:
++ ret = iommu_do_domctl(op, d, u_domctl);
++ goto domctl_out_unlock_domonly;
++
+ case XEN_DOMCTL_ioport_permission:
+ case XEN_DOMCTL_ioport_mapping:
+ case XEN_DOMCTL_gsi_permission:
+@@ -918,7 +922,6 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ case XEN_DOMCTL_assign_device:
+ case XEN_DOMCTL_test_assign_device:
+ case XEN_DOMCTL_deassign_device:
+- case XEN_DOMCTL_get_device_group:
+ ret = iommu_do_domctl(op, d, u_domctl);
+ break;
+
+--- a/xen/drivers/passthrough/pci.c
++++ b/xen/drivers/passthrough/pci.c
+@@ -1620,7 +1620,7 @@ static int iommu_get_device_group(
+ if ( (pdev->seg != seg) || ((b == bus) && (df == devfn)) )
+ continue;
+
+- if ( xsm_get_device_group(XSM_HOOK, (seg << 16) | (b << 8) | df) )
++ if ( xsm_get_device_group(XSM_PRIV, (seg << 16) | (b << 8) | df) )
+ continue;
+
+ sdev_id = iommu_call(ops, get_device_group_id, seg, b, df);
+@@ -1690,7 +1690,7 @@ int iommu_do_pci_domctl(
+ u32 max_sdevs;
+ XEN_GUEST_HANDLE_64(uint32) sdevs;
+
+- ret = xsm_get_device_group(XSM_HOOK, domctl->u.get_device_group.machine_sbdf);
++ ret = xsm_get_device_group(XSM_PRIV, domctl->u.get_device_group.machine_sbdf);
+ if ( ret )
+ break;
+
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -162,6 +162,7 @@ static XSM_INLINE int cf_check xsm_domct
+ {
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_getdomaininfo:
++ case XEN_DOMCTL_get_device_group:
+ case XEN_DOMCTL_get_domain_state:
+ case XEN_DOMCTL_gsi_permission:
+ case XEN_DOMCTL_iomem_permission:
+@@ -401,7 +402,7 @@ static XSM_INLINE int cf_check xsm_get_v
+ static XSM_INLINE int cf_check xsm_get_device_group(
+ XSM_DEFAULT_ARG uint32_t machine_bdf)
+ {
+- XSM_ASSERT_ACTION(XSM_HOOK);
++ XSM_ASSERT_ACTION(XSM_PRIV);
+ return xsm_default_action(action, current->domain, NULL);
+ }
+
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -686,6 +686,7 @@ static int cf_check flask_domctl(struct
+ /* These have individual XSM hooks and don't make it here. */
+ case XEN_DOMCTL_bind_pt_irq:
+ case XEN_DOMCTL_getdomaininfo:
++ case XEN_DOMCTL_get_device_group:
+ case XEN_DOMCTL_get_domain_state:
+ case XEN_DOMCTL_gsi_permission:
+ case XEN_DOMCTL_iomem_permission:
+@@ -705,7 +706,6 @@ static int cf_check flask_domctl(struct
+ * These have individual XSM hooks
+ * (drivers/passthrough/{pci,device_tree.c)
+ */
+- case XEN_DOMCTL_get_device_group:
+ case XEN_DOMCTL_test_assign_device:
+ case XEN_DOMCTL_assign_device:
+ case XEN_DOMCTL_deassign_device:
diff --git a/xsa492-4.21-19.patch b/xsa492-4.21-19.patch
new file mode 100644
index 0000000..54a1117
--- /dev/null
+++ b/xsa492-4.21-19.patch
@@ -0,0 +1,378 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl/XSM: drop {,de}assign_{,dt}device hooks
+
+Integrate the checking with xsm_domctl(). As a positive side effect,
+permissions are then checked at the same early point with and without
+Flask. As the DT device path needs fetching earlier (but must not be
+double fetched), cache it in a private field of the public interface
+struct.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -325,6 +325,10 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ case XEN_DOMCTL_deassign_device:
+ if ( op->domain == DOMID_IO )
+ {
++#ifdef CONFIG_HAS_DEVICE_TREE_DISCOVERY
++ if ( op->u.assign_device.dev == XEN_DOMCTL_DEV_DT )
++ op->u.assign_device.u.dt.dev = NULL;
++#endif
+ d = dom_io;
+ break;
+ }
+@@ -332,6 +336,11 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ return -ESRCH;
+ fallthrough;
+ case XEN_DOMCTL_test_assign_device:
++#ifdef CONFIG_HAS_DEVICE_TREE_DISCOVERY
++ if ( op->u.assign_device.dev == XEN_DOMCTL_DEV_DT )
++ op->u.assign_device.u.dt.dev = NULL;
++ fallthrough;
++#endif
+ case XEN_DOMCTL_vm_event_op:
+ if ( op->domain == DOMID_INVALID )
+ {
+--- a/xen/drivers/passthrough/device_tree.c
++++ b/xen/drivers/passthrough/device_tree.c
+@@ -340,15 +340,15 @@ int iommu_do_dt_domctl(struct xen_domctl
+ if ( (d && d->is_dying) || domctl->u.assign_device.flags )
+ break;
+
+- ret = dt_find_node_by_gpath(domctl->u.assign_device.u.dt.path,
+- domctl->u.assign_device.u.dt.size,
+- &dev);
+- if ( ret )
+- break;
+-
+- ret = xsm_assign_dtdevice(XSM_HOOK, d, dt_node_full_name(dev));
+- if ( ret )
+- break;
++ dev = domctl->u.assign_device.u.dt.dev;
++ if ( !dev )
++ {
++ ret = dt_find_node_by_gpath(domctl->u.assign_device.u.dt.path,
++ domctl->u.assign_device.u.dt.size,
++ &dev);
++ if ( ret )
++ break;
++ }
+
+ if ( domctl->cmd == XEN_DOMCTL_test_assign_device )
+ {
+@@ -396,15 +396,15 @@ int iommu_do_dt_domctl(struct xen_domctl
+ if ( domctl->u.assign_device.flags )
+ break;
+
+- ret = dt_find_node_by_gpath(domctl->u.assign_device.u.dt.path,
+- domctl->u.assign_device.u.dt.size,
+- &dev);
+- if ( ret )
+- break;
+-
+- ret = xsm_deassign_dtdevice(XSM_HOOK, d, dt_node_full_name(dev));
+- if ( ret )
+- break;
++ dev = domctl->u.assign_device.u.dt.dev;
++ if ( !dev )
++ {
++ ret = dt_find_node_by_gpath(domctl->u.assign_device.u.dt.path,
++ domctl->u.assign_device.u.dt.size,
++ &dev);
++ if ( ret )
++ break;
++ }
+
+ if ( d == dom_io )
+ {
+--- a/xen/drivers/passthrough/pci.c
++++ b/xen/drivers/passthrough/pci.c
+@@ -1740,10 +1740,6 @@ int iommu_do_pci_domctl(
+
+ machine_sbdf = domctl->u.assign_device.u.pci.machine_sbdf;
+
+- ret = xsm_assign_device(XSM_HOOK, d, machine_sbdf);
+- if ( ret )
+- break;
+-
+ seg = machine_sbdf >> 16;
+ bus = PCI_BUS(machine_sbdf);
+ devfn = PCI_DEVFN(machine_sbdf);
+@@ -1785,10 +1781,6 @@ int iommu_do_pci_domctl(
+
+ machine_sbdf = domctl->u.assign_device.u.pci.machine_sbdf;
+
+- ret = xsm_deassign_device(XSM_HOOK, d, machine_sbdf);
+- if ( ret )
+- break;
+-
+ seg = machine_sbdf >> 16;
+ bus = PCI_BUS(machine_sbdf);
+ devfn = PCI_DEVFN(machine_sbdf);
+--- a/xen/include/public/domctl.h
++++ b/xen/include/public/domctl.h
+@@ -575,7 +575,10 @@ struct xen_domctl_assign_device {
+ } pci;
+ struct {
+ uint32_t size; /* Length of the path */
+- XEN_GUEST_HANDLE_64(char) path; /* path to the device tree node */
++ XEN_GUEST_HANDLE_64(char) path; /* Path to the device tree node */
++#ifdef __XEN__
++ struct dt_device_node *dev; /* Resolved device node of the above */
++#endif
+ } dt;
+ } u;
+ };
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -405,40 +405,8 @@ static XSM_INLINE int cf_check xsm_get_d
+ XSM_ASSERT_ACTION(XSM_PRIV);
+ return xsm_default_action(action, current->domain, NULL);
+ }
+-
+-static XSM_INLINE int cf_check xsm_assign_device(
+- XSM_DEFAULT_ARG struct domain *d, uint32_t machine_bdf)
+-{
+- XSM_ASSERT_ACTION(XSM_HOOK);
+- return xsm_default_action(action, current->domain, d);
+-}
+-
+-static XSM_INLINE int cf_check xsm_deassign_device(
+- XSM_DEFAULT_ARG struct domain *d, uint32_t machine_bdf)
+-{
+- XSM_ASSERT_ACTION(XSM_HOOK);
+- return xsm_default_action(action, current->domain, d);
+-}
+-
+ #endif /* HAS_PASSTHROUGH && HAS_PCI */
+
+-#if defined(CONFIG_HAS_PASSTHROUGH) && defined(CONFIG_HAS_DEVICE_TREE_DISCOVERY)
+-static XSM_INLINE int cf_check xsm_assign_dtdevice(
+- XSM_DEFAULT_ARG struct domain *d, const char *dtpath)
+-{
+- XSM_ASSERT_ACTION(XSM_HOOK);
+- return xsm_default_action(action, current->domain, d);
+-}
+-
+-static XSM_INLINE int cf_check xsm_deassign_dtdevice(
+- XSM_DEFAULT_ARG struct domain *d, const char *dtpath)
+-{
+- XSM_ASSERT_ACTION(XSM_HOOK);
+- return xsm_default_action(action, current->domain, d);
+-}
+-
+-#endif /* HAS_PASSTHROUGH && HAS_DEVICE_TREE_DISCOVERY */
+-
+ static XSM_INLINE int cf_check xsm_resource_plug_core(XSM_DEFAULT_VOID)
+ {
+ XSM_ASSERT_ACTION(XSM_HOOK);
+--- a/xen/include/xsm/xsm.h
++++ b/xen/include/xsm/xsm.h
+@@ -124,13 +124,6 @@ struct xsm_ops {
+
+ #if defined(CONFIG_HAS_PASSTHROUGH) && defined(CONFIG_HAS_PCI)
+ int (*get_device_group)(uint32_t machine_bdf);
+- int (*assign_device)(struct domain *d, uint32_t machine_bdf);
+- int (*deassign_device)(struct domain *d, uint32_t machine_bdf);
+-#endif
+-
+-#if defined(CONFIG_HAS_PASSTHROUGH) && defined(CONFIG_HAS_DEVICE_TREE_DISCOVERY)
+- int (*assign_dtdevice)(struct domain *d, const char *dtpath);
+- int (*deassign_dtdevice)(struct domain *d, const char *dtpath);
+ #endif
+
+ int (*resource_plug_core)(void);
+@@ -533,35 +526,8 @@ static inline int xsm_get_device_group(x
+ {
+ return alternative_call(xsm_ops.get_device_group, machine_bdf);
+ }
+-
+-static inline int xsm_assign_device(
+- xsm_default_t def, struct domain *d, uint32_t machine_bdf)
+-{
+- return alternative_call(xsm_ops.assign_device, d, machine_bdf);
+-}
+-
+-static inline int xsm_deassign_device(
+- xsm_default_t def, struct domain *d, uint32_t machine_bdf)
+-{
+- return alternative_call(xsm_ops.deassign_device, d, machine_bdf);
+-}
+ #endif /* HAS_PASSTHROUGH && HAS_PCI) */
+
+-#if defined(CONFIG_HAS_PASSTHROUGH) && defined(CONFIG_HAS_DEVICE_TREE_DISCOVERY)
+-static inline int xsm_assign_dtdevice(
+- xsm_default_t def, struct domain *d, const char *dtpath)
+-{
+- return alternative_call(xsm_ops.assign_dtdevice, d, dtpath);
+-}
+-
+-static inline int xsm_deassign_dtdevice(
+- xsm_default_t def, struct domain *d, const char *dtpath)
+-{
+- return alternative_call(xsm_ops.deassign_dtdevice, d, dtpath);
+-}
+-
+-#endif /* HAS_PASSTHROUGH && HAS_DEVICE_TREE_DISCOVERY */
+-
+ static inline int xsm_resource_plug_pci(xsm_default_t def, uint32_t machine_bdf)
+ {
+ return alternative_call(xsm_ops.resource_plug_pci, machine_bdf);
+--- a/xen/xsm/dummy.c
++++ b/xen/xsm/dummy.c
+@@ -81,13 +81,6 @@ static const struct xsm_ops __initconst_
+
+ #if defined(CONFIG_HAS_PASSTHROUGH) && defined(CONFIG_HAS_PCI)
+ .get_device_group = xsm_get_device_group,
+- .assign_device = xsm_assign_device,
+- .deassign_device = xsm_deassign_device,
+-#endif
+-
+-#if defined(CONFIG_HAS_PASSTHROUGH) && defined(CONFIG_HAS_DEVICE_TREE_DISCOVERY)
+- .assign_dtdevice = xsm_assign_dtdevice,
+- .deassign_dtdevice = xsm_deassign_dtdevice,
+ #endif
+
+ .resource_plug_core = xsm_resource_plug_core,
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -45,6 +45,17 @@ static int flask_shadow_control(struct d
+ #define pv_shim false
+ #endif
+
++#ifdef CONFIG_HAS_PASSTHROUGH
++#ifdef CONFIG_HAS_PCI
++static int flask_assign_device(struct domain *d, unsigned int machine_bdf);
++static int flask_deassign_device(struct domain *d, unsigned int machine_bdf);
++#endif
++#ifdef CONFIG_HAS_DEVICE_TREE_DISCOVERY
++static int flask_assign_dtdevice(struct domain *d, const char *dtpath);
++static int flask_deassign_dtdevice(struct domain *d, const char *dtpath);
++#endif
++#endif /* CONFIG_HAS_PASSTHROUGH */
++
+ static uint32_t domain_sid(const struct domain *dom)
+ {
+ struct domain_security_struct *dsec = dom->ssid;
+@@ -700,16 +711,6 @@ static int cf_check flask_domctl(struct
+
+ /* These have individual XSM hooks (common/domctl.c) */
+ case XEN_DOMCTL_set_target:
+-
+-#ifdef CONFIG_HAS_PASSTHROUGH
+- /*
+- * These have individual XSM hooks
+- * (drivers/passthrough/{pci,device_tree.c)
+- */
+- case XEN_DOMCTL_test_assign_device:
+- case XEN_DOMCTL_assign_device:
+- case XEN_DOMCTL_deassign_device:
+-#endif
+ return 0;
+
+ case XEN_DOMCTL_destroydomain:
+@@ -789,6 +790,49 @@ static int cf_check flask_domctl(struct
+ return flask_shadow_control(d, op->u.shadow_op.op);
+ #endif
+
++#ifdef CONFIG_HAS_PASSTHROUGH
++
++ case XEN_DOMCTL_test_assign_device:
++ case XEN_DOMCTL_assign_device:
++ case XEN_DOMCTL_deassign_device:
++ switch ( op->u.assign_device.dev )
++ {
++#ifdef CONFIG_HAS_PCI
++ case XEN_DOMCTL_DEV_PCI:
++ return op->cmd != XEN_DOMCTL_deassign_device
++ ? flask_assign_device(
++ d, op->u.assign_device.u.pci.machine_sbdf)
++ : flask_deassign_device(
++ d, op->u.assign_device.u.pci.machine_sbdf);
++#endif
++
++#ifdef CONFIG_HAS_DEVICE_TREE_DISCOVERY
++ case XEN_DOMCTL_DEV_DT:
++ {
++ struct dt_device_node *dev;
++ int ret = dt_find_node_by_gpath(op->u.assign_device.u.dt.path,
++ op->u.assign_device.u.dt.size,
++ &dev);
++
++ if ( ret )
++ return ret;
++
++ op->u.assign_device.u.dt.dev = dev;
++
++ return op->cmd != XEN_DOMCTL_deassign_device
++ ? flask_assign_dtdevice(d, dt_node_full_name(dev))
++ : flask_deassign_dtdevice(d, dt_node_full_name(dev));
++ }
++#endif
++
++ default:
++ /* Unknown type. */
++ break;
++ }
++ return avc_unknown_permission("assign_device", op->cmd);
++
++#endif /* CONFIG_HAS_PASSTHROUGH */
++
+ case XEN_DOMCTL_mem_sharing_op:
+ return current_has_perm(d, SECCLASS_HVM, HVM__MEM_SHARING);
+
+@@ -1416,7 +1460,7 @@ static int flask_test_assign_device(uint
+ return avc_current_has_perm(rsid, SECCLASS_RESOURCE, RESOURCE__STAT_DEVICE, NULL);
+ }
+
+-static int cf_check flask_assign_device(struct domain *d, uint32_t machine_bdf)
++static int flask_assign_device(struct domain *d, uint32_t machine_bdf)
+ {
+ uint32_t dsid, rsid;
+ int rc = -EPERM;
+@@ -1446,7 +1490,7 @@ static int cf_check flask_assign_device(
+ return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, dperm, &ad);
+ }
+
+-static int cf_check flask_deassign_device(
++static int flask_deassign_device(
+ struct domain *d, uint32_t machine_bdf)
+ {
+ uint32_t rsid;
+@@ -1478,7 +1522,7 @@ static int flask_test_assign_dtdevice(co
+ NULL);
+ }
+
+-static int cf_check flask_assign_dtdevice(struct domain *d, const char *dtpath)
++static int flask_assign_dtdevice(struct domain *d, const char *dtpath)
+ {
+ uint32_t dsid, rsid;
+ int rc = -EPERM;
+@@ -1508,7 +1552,7 @@ static int cf_check flask_assign_dtdevic
+ return avc_has_perm(dsid, rsid, SECCLASS_RESOURCE, dperm, &ad);
+ }
+
+-static int cf_check flask_deassign_dtdevice(
++static int flask_deassign_dtdevice(
+ struct domain *d, const char *dtpath)
+ {
+ uint32_t rsid;
+@@ -1989,13 +2033,6 @@ static const struct xsm_ops __initconst_
+
+ #if defined(CONFIG_HAS_PASSTHROUGH) && defined(CONFIG_HAS_PCI)
+ .get_device_group = flask_get_device_group,
+- .assign_device = flask_assign_device,
+- .deassign_device = flask_deassign_device,
+-#endif
+-
+-#if defined(CONFIG_HAS_PASSTHROUGH) && defined(CONFIG_HAS_DEVICE_TREE_DISCOVERY)
+- .assign_dtdevice = flask_assign_dtdevice,
+- .deassign_dtdevice = flask_deassign_dtdevice,
+ #endif
+
+ .platform_op = flask_platform_op,
diff --git a/xsa492-4.21-20.patch b/xsa492-4.21-20.patch
new file mode 100644
index 0000000..bfd10a9
--- /dev/null
+++ b/xsa492-4.21-20.patch
@@ -0,0 +1,123 @@
+From: Jan Beulich <jbeulich@suse.com>
+Subject: domctl: handle XEN_DOMCTL_set_target without acquiring domctl lock
+
+The only locking required here is that between checking d->target and
+setting it. To avoid the need for an explicit lock, use cmpxchgptr() to
+update d->target.
+
+Move the handling not only ahead of acquiring the lock, but also ahead
+of the XSM check, leveraging that the sub-op has its own hook.
+
+This is part of XSA-492.
+
+Signed-off-by: Jan Beulich <jbeulich@suse.com>
+Acked-by: Daniel P. Smith <dpsmith@apertussolutions.com>
+Reviewed-by: Roger Pau Monné <roger.pau@citrix.com>
+
+--- a/xen/common/domctl.c
++++ b/xen/common/domctl.c
+@@ -505,6 +505,30 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ }
+ #endif
+
++ case XEN_DOMCTL_set_target:
++ {
++ struct domain *e = get_domain_by_id(op->u.set_target.target);
++
++ ret = -ESRCH;
++ if ( !e )
++ goto domctl_out_unlock_domonly;
++
++ if ( d == e )
++ ret = -EINVAL;
++ else if ( !is_hvm_domain(e) )
++ ret = -EOPNOTSUPP;
++ else
++ ret = xsm_set_target(XSM_PRIV, d, e);
++
++ /* Hold reference on @e until we destroy @d. */
++ if ( !ret && cmpxchgptr(&d->target, NULL, e) )
++ ret = -EINVAL;
++
++ if ( ret )
++ put_domain(e);
++ goto domctl_out_unlock_domonly;
++ }
++
+ case XEN_DOMCTL_vm_event_op:
+ if ( op->u.vm_event_op.op == XEN_VM_EVENT_GET_VERSION )
+ {
+@@ -844,36 +868,6 @@ long do_domctl(XEN_GUEST_HANDLE_PARAM(xe
+ domain_set_time_offset(d, op->u.settimeoffset.time_offset_seconds);
+ break;
+
+- case XEN_DOMCTL_set_target:
+- {
+- struct domain *e;
+-
+- ret = -ESRCH;
+- e = get_domain_by_id(op->u.set_target.target);
+- if ( e == NULL )
+- break;
+-
+- ret = -EINVAL;
+- if ( (d == e) || (d->target != NULL) )
+- {
+- put_domain(e);
+- break;
+- }
+-
+- ret = -EOPNOTSUPP;
+- if ( is_hvm_domain(e) )
+- ret = xsm_set_target(XSM_HOOK, d, e);
+- if ( ret )
+- {
+- put_domain(e);
+- break;
+- }
+-
+- /* Hold reference on @e until we destroy @d. */
+- d->target = e;
+- break;
+- }
+-
+ case XEN_DOMCTL_subscribe:
+ d->suspend_evtchn = op->u.subscribe.port;
+ break;
+--- a/xen/include/xsm/dummy.h
++++ b/xen/include/xsm/dummy.h
+@@ -150,7 +150,7 @@ static XSM_INLINE int cf_check xsm_sysct
+ static XSM_INLINE int cf_check xsm_set_target(
+ XSM_DEFAULT_ARG struct domain *d, struct domain *e)
+ {
+- XSM_ASSERT_ACTION(XSM_HOOK);
++ XSM_ASSERT_ACTION(XSM_PRIV);
+ return xsm_default_action(action, current->domain, NULL);
+ }
+
+@@ -170,6 +170,7 @@ static XSM_INLINE int cf_check xsm_domct
+ case XEN_DOMCTL_ioport_permission:
+ case XEN_DOMCTL_irq_permission:
+ case XEN_DOMCTL_memory_mapping:
++ case XEN_DOMCTL_set_target:
+ case XEN_DOMCTL_unbind_pt_irq:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+--- a/xen/xsm/flask/hooks.c
++++ b/xen/xsm/flask/hooks.c
+@@ -705,14 +705,11 @@ static int cf_check flask_domctl(struct
+ case XEN_DOMCTL_ioport_permission:
+ case XEN_DOMCTL_irq_permission:
+ case XEN_DOMCTL_memory_mapping:
++ case XEN_DOMCTL_set_target:
+ case XEN_DOMCTL_unbind_pt_irq:
+ ASSERT_UNREACHABLE();
+ return -EILSEQ;
+
+- /* These have individual XSM hooks (common/domctl.c) */
+- case XEN_DOMCTL_set_target:
+- return 0;
+-
+ case XEN_DOMCTL_destroydomain:
+ return current_has_perm(d, SECCLASS_DOMAIN, DOMAIN__DESTROY);
+
diff --git a/xsa493-4.21-01.patch b/xsa493-4.21-01.patch
new file mode 100644
index 0000000..c06b9a5
--- /dev/null
+++ b/xsa493-4.21-01.patch
@@ -0,0 +1,311 @@
+From 2e21b5301765de353c06081eee953255bf327176 Mon Sep 17 00:00:00 2001
+From: Michal Orzel <michal.orzel@amd.com>
+Date: Tue, 14 Apr 2026 10:11:24 +0200
+Subject: xen/arm64: flushtlb: Optimize ARM64_WORKAROUND_REPEAT_TLBI
+
+The ARM64_WORKAROUND_REPEAT_TLBI workaround is used to mitigate several
+errata where broadcast TLBI;DSB sequences don't provide all the
+architecturally required synchronization. The workaround performs more
+work than necessary, and can have significant overhead. This patch
+optimizes the workaround, as explained below.
+
+1. All relevant errata only affect the ordering and/or completion of
+ memory accesses which have been translated by an invalidated TLB
+ entry. The actual invalidation of TLB entries is unaffected.
+
+2. The existing workaround is applied to both broadcast and local TLB
+ invalidation, whereas for all relevant errata it is only necessary to
+ apply a workaround for broadcast invalidation.
+
+3. The existing workaround replaces every TLBI with a TLBI;DSB;TLBI
+ sequence, whereas for all relevant errata it is only necessary to
+ execute a single additional TLBI;DSB sequence after any number of
+ TLBIs are completed by a DSB.
+
+ For example, for a sequence of batched TLBIs:
+
+ TLBI <op1>[, <arg1>]
+ TLBI <op2>[, <arg2>]
+ TLBI <op3>[, <arg3>]
+ DSB ISH
+
+ ... the existing workaround will expand this to:
+
+ TLBI <op1>[, <arg1>]
+ DSB ISH // additional
+ TLBI <op1>[, <arg1>] // additional
+ TLBI <op2>[, <arg2>]
+ DSB ISH // additional
+ TLBI <op2>[, <arg2>] // additional
+ TLBI <op3>[, <arg3>]
+ DSB ISH // additional
+ TLBI <op3>[, <arg3>] // additional
+ DSB ISH
+
+ ... whereas it is sufficient to have:
+
+ TLBI <op1>[, <arg1>]
+ TLBI <op2>[, <arg2>]
+ TLBI <op3>[, <arg3>]
+ DSB ISH
+ TLBI <opX>[, <argX>] // additional
+ DSB ISH // additional
+
+ Using a single additional TLBI and DSB at the end of the sequence can
+ have significantly lower overhead as each DSB which completes a TLBI
+ must synchronize with other PEs in the system, with potential
+ performance effects both locally and system-wide.
+
+4. The existing workaround repeats each specific TLBI operation, whereas
+ for all relevant errata it is sufficient for the additional TLBI to
+ use *any* operation which will be broadcast, regardless of which
+ translation regime or stage of translation the operation applies to.
+
+ For example, for a single TLBI:
+
+ TLBI ALLE2IS
+ DSB ISH
+
+ ... the existing workaround will expand this to:
+
+ TLBI ALLE2IS
+ DSB ISH
+ TLBI ALLE2IS // additional
+ DSB ISH // additional
+
+ ... whereas it is sufficient to have:
+
+ TLBI ALLE2IS
+ DSB ISH
+ TLBI VALE1IS, XZR // additional
+ DSB ISH // additional
+
+ As the additional TLBI doesn't have to match a specific earlier TLBI,
+ the additional TLBI can be implemented in separate code, with no
+ memory of the earlier TLBIs. The additional TLBI can also use a
+ cheaper TLBI operation.
+
+5. The existing workaround is applied to both Stage-1 and Stage-2 TLB
+ invalidation, whereas for all relevant errata it is only necessary to
+ apply a workaround for Stage-1 invalidation.
+
+ Architecturally, TLBI operations which invalidate only Stage-2
+ information (e.g. IPAS2E1IS) are not required to invalidate TLB
+ entries which combine information from Stage-1 and Stage-2
+ translation table entries, and consequently may not complete memory
+ accesses translated by those combined entries. In these cases,
+ completion of memory accesses is only guaranteed after subsequent
+ invalidation of Stage-1 information (e.g. VMALLE1IS).
+
+Rework the workaround logic as follows:
+ - add TLB_HELPER_LOCAL() to be used for local TLB ops without a
+ workaround,
+ - modify TLB_HELPER() workaround to use tlbi vale2is, xzr as a second
+ TLBI,
+ - drop TLB_HELPER_VA(). It's used only by __flush_xen_tlb_one_local
+ which is local and does not need workaround and by
+ __flush_xen_tlb_one. In the latter case, since it's used in a loop,
+ we don't need a workaround in the middle. Add __tlb_repeat_sync with
+ a workaround to be used at the end after DSB and before final ISB,
+ - TLBI VALE2IS passing XZR is used as an additional TLBI. While there is
+ an identity mapping there, it's used very rarely. The performance
+ impact is therefore negligible. If things change in the future, we
+ can revisit the decision.
+
+Signed-off-by: Michal Orzel <michal.orzel@amd.com>
+Reviewed-by: Luca Fancellu <luca.fancellu@arm.com>
+Reviewed-by: Julien Grall <jgrall@amazon.com>
+(cherry picked from commit 7c502d7591519135765b8041cbd1c70e56e5a0b9)
+
+diff --git a/xen/arch/arm/include/asm/arm32/flushtlb.h b/xen/arch/arm/include/asm/arm32/flushtlb.h
+index 61c25a318998..5483be08fbbe 100644
+--- a/xen/arch/arm/include/asm/arm32/flushtlb.h
++++ b/xen/arch/arm/include/asm/arm32/flushtlb.h
+@@ -57,6 +57,9 @@ static inline void __flush_xen_tlb_one(vaddr_t va)
+ asm volatile(STORE_CP32(0, TLBIMVAHIS) : : "r" (va) : "memory");
+ }
+
++/* Only for ARM64_WORKAROUND_REPEAT_TLBI */
++static inline void __tlb_repeat_sync(void) {}
++
+ #endif /* __ASM_ARM_ARM32_FLUSHTLB_H__ */
+ /*
+ * Local variables:
+diff --git a/xen/arch/arm/include/asm/arm64/flushtlb.h b/xen/arch/arm/include/asm/arm64/flushtlb.h
+index 3b99c11b50d1..1606b26bf28a 100644
+--- a/xen/arch/arm/include/asm/arm64/flushtlb.h
++++ b/xen/arch/arm/include/asm/arm64/flushtlb.h
+@@ -12,9 +12,14 @@
+ * ARM64_WORKAROUND_REPEAT_TLBI:
+ * Modification of the translation table for a virtual address might lead to
+ * read-after-read ordering violation.
+- * The workaround repeats TLBI+DSB ISH operation for all the TLB flush
+- * operations. While this is strictly not necessary, we don't want to
+- * take any risk.
++ * The workaround repeats TLBI+DSB ISH operation for broadcast TLB flush
++ * operations. The workaround is not needed for local operations.
++ *
++ * It is sufficient for the additional TLBI to use *any* operation which will
++ * be broadcast, regardless of which translation regime or stage of translation
++ * the operation applies to. TLBI VALE2IS is used passing XZR. While there is
++ * an identity mapping there, it's only used during suspend/resume, CPU on/off,
++ * so the impact (performance if any) is negligible.
+ *
+ * For Xen page-tables the ISB will discard any instructions fetched
+ * from the old mappings.
+@@ -26,69 +31,90 @@
+ * Note that for local TLB flush, using non-shareable (nsh) is sufficient
+ * (see D5-4929 in ARM DDI 0487H.a). Although, the memory barrier in
+ * for the workaround is left as inner-shareable to match with Linux
+- * v6.1-rc8.
++ * v6.19.
+ */
+-#define TLB_HELPER(name, tlbop, sh) \
++#define TLB_HELPER_LOCAL(name, tlbop) \
+ static inline void name(void) \
+ { \
+ asm_inline volatile ( \
+- "dsb " # sh "st;" \
++ "dsb nshst;" \
+ "tlbi " # tlbop ";" \
+- ALTERNATIVE( \
+- "nop; nop;", \
+- "dsb ish;" \
+- "tlbi " # tlbop ";", \
+- ARM64_WORKAROUND_REPEAT_TLBI, \
+- CONFIG_ARM64_WORKAROUND_REPEAT_TLBI) \
+- "dsb " # sh ";" \
++ "dsb nsh;" \
+ "isb;" \
+ : : : "memory"); \
+ }
+
+-/*
+- * FLush TLB by VA. This will likely be used in a loop, so the caller
+- * is responsible to use the appropriate memory barriers before/after
+- * the sequence.
+- *
+- * See above about the ARM64_WORKAROUND_REPEAT_TLBI sequence.
+- */
+-#define TLB_HELPER_VA(name, tlbop) \
+-static inline void name(vaddr_t va) \
+-{ \
+- asm_inline volatile ( \
+- "tlbi " # tlbop ", %0;" \
+- ALTERNATIVE( \
+- "nop; nop;", \
+- "dsb ish;" \
+- "tlbi " # tlbop ", %0;", \
+- ARM64_WORKAROUND_REPEAT_TLBI, \
+- CONFIG_ARM64_WORKAROUND_REPEAT_TLBI) \
+- : : "r" (va >> PAGE_SHIFT) : "memory"); \
++#define TLB_HELPER(name, tlbop) \
++static inline void name(void) \
++{ \
++ asm_inline volatile ( \
++ "dsb ishst;" \
++ "tlbi " # tlbop ";" \
++ ALTERNATIVE( \
++ "nop; nop;", \
++ "dsb ish;" \
++ "tlbi vale2is, xzr;", \
++ ARM64_WORKAROUND_REPEAT_TLBI, \
++ CONFIG_ARM64_WORKAROUND_REPEAT_TLBI) \
++ "dsb ish;" \
++ "isb;" \
++ : : : "memory"); \
+ }
+
+ /* Flush local TLBs, current VMID only. */
+-TLB_HELPER(flush_guest_tlb_local, vmalls12e1, nsh)
++TLB_HELPER_LOCAL(flush_guest_tlb_local, vmalls12e1)
+
+ /* Flush innershareable TLBs, current VMID only */
+-TLB_HELPER(flush_guest_tlb, vmalls12e1is, ish)
++TLB_HELPER(flush_guest_tlb, vmalls12e1is)
+
+ /* Flush local TLBs, all VMIDs, non-hypervisor mode */
+-TLB_HELPER(flush_all_guests_tlb_local, alle1, nsh)
++TLB_HELPER_LOCAL(flush_all_guests_tlb_local, alle1)
+
+ /* Flush innershareable TLBs, all VMIDs, non-hypervisor mode */
+-TLB_HELPER(flush_all_guests_tlb, alle1is, ish)
++TLB_HELPER(flush_all_guests_tlb, alle1is)
+
+ /* Flush all hypervisor mappings from the TLB of the local processor. */
+-TLB_HELPER(flush_xen_tlb_local, alle2, nsh)
++TLB_HELPER_LOCAL(flush_xen_tlb_local, alle2)
++
++#undef TLB_HELPER_LOCAL
++#undef TLB_HELPER
++
++/*
++ * FLush TLB by VA. This will likely be used in a loop, so the caller
++ * is responsible to use the appropriate memory barriers before/after
++ * the sequence.
++ */
+
+ /* Flush TLB of local processor for address va. */
+-TLB_HELPER_VA(__flush_xen_tlb_one_local, vae2)
++static inline void __flush_xen_tlb_one_local(vaddr_t va)
++{
++ asm_inline volatile (
++ "tlbi vae2, %0" : : "r" (va >> PAGE_SHIFT) : "memory");
++}
+
+ /* Flush TLB of all processors in the inner-shareable domain for address va. */
+-TLB_HELPER_VA(__flush_xen_tlb_one, vae2is)
++static inline void __flush_xen_tlb_one(vaddr_t va)
++{
++ asm_inline volatile (
++ "tlbi vae2is, %0" : : "r" (va >> PAGE_SHIFT) : "memory");
++}
+
+-#undef TLB_HELPER
+-#undef TLB_HELPER_VA
++/*
++ * ARM64_WORKAROUND_REPEAT_TLBI:
++ * For all relevant erratas it is only necessary to execute a single
++ * additional TLBI;DSB sequence after any number of TLBIs are completed by DSB.
++ */
++static inline void __tlb_repeat_sync(void)
++{
++ asm_inline volatile (
++ ALTERNATIVE(
++ "nop; nop;",
++ "tlbi vale2is, xzr;"
++ "dsb ish;",
++ ARM64_WORKAROUND_REPEAT_TLBI,
++ CONFIG_ARM64_WORKAROUND_REPEAT_TLBI)
++ : : : "memory");
++}
+
+ #endif /* __ASM_ARM_ARM64_FLUSHTLB_H__ */
+ /*
+diff --git a/xen/arch/arm/include/asm/flushtlb.h b/xen/arch/arm/include/asm/flushtlb.h
+index e45fb6d97b02..c292c3c00d29 100644
+--- a/xen/arch/arm/include/asm/flushtlb.h
++++ b/xen/arch/arm/include/asm/flushtlb.h
+@@ -65,6 +65,7 @@ static inline void flush_xen_tlb_range_va(vaddr_t va,
+ va += PAGE_SIZE;
+ }
+ dsb(ish); /* Ensure the TLB invalidation has completed */
++ __tlb_repeat_sync();
+ isb();
+ }
+
+diff --git a/xen/arch/arm/include/asm/mmu/layout.h b/xen/arch/arm/include/asm/mmu/layout.h
+index 19c0ec63a59a..feafc14ebfda 100644
+--- a/xen/arch/arm/include/asm/mmu/layout.h
++++ b/xen/arch/arm/include/asm/mmu/layout.h
+@@ -23,6 +23,10 @@
+ *
+ * Reserved to identity map Xen
+ *
++ * Note: As part of ARM64_WORKAROUND_REPEAT_TLBI, VA 0 is used for an extra
++ * TLBI operation given its rare use (only identity mapping) and thus
++ * negligible performance impact.
++ *
+ * 0x00000a0000000000 - 0x00000a7fffffffff (512GB, L0 slot [20])
+ * (Relative offsets)
+ * 0 - 2M Unmapped
diff --git a/xsa493-4.21-02.patch b/xsa493-4.21-02.patch
new file mode 100644
index 0000000..f80119c
--- /dev/null
+++ b/xsa493-4.21-02.patch
@@ -0,0 +1,71 @@
+From 7e70b87512c966248b1e8453d9ac54c643c06f44 Mon Sep 17 00:00:00 2001
+From: Michal Orzel <michal.orzel@amd.com>
+Date: Fri, 22 May 2026 09:35:55 +0200
+Subject: xen/arm: Sync missing definitions for Arm CPUs with Linux
+
+Synchronize with Linux kernel 7.0 definitions for the following CPUs:
+ - Cortex-A76AE,
+ - Cortex-A78AE,
+ - Cortex-X1C,
+ - Cortex-X3,
+ - Neoverse-V2,
+ - Cortex-X4,
+ - Neoverse-V3AE,
+ - Neoverse-V3,
+ - Cortex-X925.
+
+These will be used for errata detection in subsequent patches.
+
+Signed-off-by: Michal Orzel <michal.orzel@amd.com>
+Reviewed-by: Julien Grall <julien@xen.org>
+
+diff --git a/xen/arch/arm/include/asm/processor.h b/xen/arch/arm/include/asm/processor.h
+index ec23fd098b63..907778683b08 100644
+--- a/xen/arch/arm/include/asm/processor.h
++++ b/xen/arch/arm/include/asm/processor.h
+@@ -89,13 +89,22 @@
+ #define ARM_CPU_PART_CORTEX_A76 0xD0B
+ #define ARM_CPU_PART_NEOVERSE_N1 0xD0C
+ #define ARM_CPU_PART_CORTEX_A77 0xD0D
++#define ARM_CPU_PART_CORTEX_A76AE 0xD0E
+ #define ARM_CPU_PART_NEOVERSE_V1 0xD40
+ #define ARM_CPU_PART_CORTEX_A78 0xD41
++#define ARM_CPU_PART_CORTEX_A78AE 0xD42
+ #define ARM_CPU_PART_CORTEX_X1 0xD44
+ #define ARM_CPU_PART_CORTEX_A710 0xD47
+ #define ARM_CPU_PART_CORTEX_X2 0xD48
+ #define ARM_CPU_PART_NEOVERSE_N2 0xD49
+ #define ARM_CPU_PART_CORTEX_A78C 0xD4B
++#define ARM_CPU_PART_CORTEX_X1C 0xD4C
++#define ARM_CPU_PART_CORTEX_X3 0xD4E
++#define ARM_CPU_PART_NEOVERSE_V2 0xD4F
++#define ARM_CPU_PART_CORTEX_X4 0xD82
++#define ARM_CPU_PART_NEOVERSE_V3AE 0xD83
++#define ARM_CPU_PART_NEOVERSE_V3 0xD84
++#define ARM_CPU_PART_CORTEX_X925 0xD85
+
+ #define MIDR_CORTEX_A12 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A12)
+ #define MIDR_CORTEX_A17 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A17)
+@@ -110,13 +119,22 @@
+ #define MIDR_CORTEX_A76 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A76)
+ #define MIDR_NEOVERSE_N1 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_N1)
+ #define MIDR_CORTEX_A77 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A77)
++#define MIDR_CORTEX_A76AE MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A76AE)
+ #define MIDR_NEOVERSE_V1 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V1)
+ #define MIDR_CORTEX_A78 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A78)
++#define MIDR_CORTEX_A78AE MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A78AE)
+ #define MIDR_CORTEX_X1 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X1)
+ #define MIDR_CORTEX_A710 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A710)
+ #define MIDR_CORTEX_X2 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X2)
+ #define MIDR_NEOVERSE_N2 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_N2)
+ #define MIDR_CORTEX_A78C MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A78C)
++#define MIDR_CORTEX_X1C MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X1C)
++#define MIDR_CORTEX_X3 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X3)
++#define MIDR_NEOVERSE_V2 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V2)
++#define MIDR_CORTEX_X4 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X4)
++#define MIDR_NEOVERSE_V3AE MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V3AE)
++#define MIDR_NEOVERSE_V3 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V3)
++#define MIDR_CORTEX_X925 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X925)
+
+ /* MPIDR Multiprocessor Affinity Register */
+ #define _MPIDR_UP (30)
diff --git a/xsa493-4.21-03.patch b/xsa493-4.21-03.patch
new file mode 100644
index 0000000..86bae68
--- /dev/null
+++ b/xsa493-4.21-03.patch
@@ -0,0 +1,37 @@
+From c0f7b40fdbb986b3cf470ed51f3878261e33f9cb Mon Sep 17 00:00:00 2001
+From: Michal Orzel <michal.orzel@amd.com>
+Date: Fri, 22 May 2026 09:35:56 +0200
+Subject: xen/arm: Add C1-Ultra definitions
+
+Add processor definitions for C1-Ultra. These will be used for errata
+detection in subsequent patches.
+
+These values can be found in the C1-Ultra TRM:
+
+ https://developer.arm.com/documentation/108014/0100/
+
+... in section A.5.1 ("MIDR_EL1, Main ID Register").
+
+Signed-off-by: Michal Orzel <michal.orzel@amd.com>
+Reviewed-by: Julien Grall <julien@xen.org>
+
+diff --git a/xen/arch/arm/include/asm/processor.h b/xen/arch/arm/include/asm/processor.h
+index 907778683b08..72745cca62bc 100644
+--- a/xen/arch/arm/include/asm/processor.h
++++ b/xen/arch/arm/include/asm/processor.h
+@@ -105,6 +105,7 @@
+ #define ARM_CPU_PART_NEOVERSE_V3AE 0xD83
+ #define ARM_CPU_PART_NEOVERSE_V3 0xD84
+ #define ARM_CPU_PART_CORTEX_X925 0xD85
++#define ARM_CPU_PART_C1_ULTRA 0xD8C
+
+ #define MIDR_CORTEX_A12 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A12)
+ #define MIDR_CORTEX_A17 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A17)
+@@ -135,6 +136,7 @@
+ #define MIDR_NEOVERSE_V3AE MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V3AE)
+ #define MIDR_NEOVERSE_V3 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V3)
+ #define MIDR_CORTEX_X925 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X925)
++#define MIDR_C1_ULTRA MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_C1_ULTRA)
+
+ /* MPIDR Multiprocessor Affinity Register */
+ #define _MPIDR_UP (30)
diff --git a/xsa493-4.21-04.patch b/xsa493-4.21-04.patch
new file mode 100644
index 0000000..b59ee74
--- /dev/null
+++ b/xsa493-4.21-04.patch
@@ -0,0 +1,37 @@
+From 6af67aeca418bffb807424eb3415fab59e581733 Mon Sep 17 00:00:00 2001
+From: Michal Orzel <michal.orzel@amd.com>
+Date: Fri, 22 May 2026 09:35:57 +0200
+Subject: xen/arm: Add C1-Premium definitions
+
+Add processor definitions for C1-Premium. These will be used for errata
+detection in subsequent patches.
+
+These values can be found in the C1-Premium TRM:
+
+ https://developer.arm.com/documentation/109416/0100/
+
+... in section A.5.1 ("MIDR_EL1, Main ID Register").
+
+Signed-off-by: Michal Orzel <michal.orzel@amd.com>
+Reviewed-by: Julien Grall <julien@xen.org>
+
+diff --git a/xen/arch/arm/include/asm/processor.h b/xen/arch/arm/include/asm/processor.h
+index 72745cca62bc..25c5762c6706 100644
+--- a/xen/arch/arm/include/asm/processor.h
++++ b/xen/arch/arm/include/asm/processor.h
+@@ -106,6 +106,7 @@
+ #define ARM_CPU_PART_NEOVERSE_V3 0xD84
+ #define ARM_CPU_PART_CORTEX_X925 0xD85
+ #define ARM_CPU_PART_C1_ULTRA 0xD8C
++#define ARM_CPU_PART_C1_PREMIUM 0xD90
+
+ #define MIDR_CORTEX_A12 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A12)
+ #define MIDR_CORTEX_A17 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_A17)
+@@ -137,6 +138,7 @@
+ #define MIDR_NEOVERSE_V3 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_NEOVERSE_V3)
+ #define MIDR_CORTEX_X925 MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_CORTEX_X925)
+ #define MIDR_C1_ULTRA MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_C1_ULTRA)
++#define MIDR_C1_PREMIUM MIDR_CPU_MODEL(ARM_CPU_IMP_ARM, ARM_CPU_PART_C1_PREMIUM)
+
+ /* MPIDR Multiprocessor Affinity Register */
+ #define _MPIDR_UP (30)
diff --git a/xsa494-4.21.patch b/xsa494-4.21.patch
new file mode 100644
index 0000000..d52a0f1
--- /dev/null
+++ b/xsa494-4.21.patch
@@ -0,0 +1,404 @@
+From 579016a359741044c9076bf0884e1dbab00ab080 Mon Sep 17 00:00:00 2001
+From: Roger Pau Monne <roger.pau@citrix.com>
+Date: Mon, 16 Mar 2026 11:03:22 +0100
+Subject: [PATCH] x86/mm: accurately track which vCPU page-tables are loaded
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Neither current nor curr_vcpu per-CPU fields accurately track which
+page-tables are loaded. There are corner cases when dealing with shadow
+paging failures that switch to the idle vCPU page-tables without changing
+current or curr_vcpu per-CPU fields.
+
+Introduce a new per-CPU field that attempts to track which vCPU page-tables
+are loaded. Update such tracking when cr3 is changed, and do so in a
+region with interrupts disabled, as to avoid handling interrupts with a
+mismatch between the vCPU tracking field and the loaded page-tables.
+
+As a result of this newly more accurate tracking the mapcache override
+functionality can be removed: the dom0 PV builder was the only user of it,
+and it's updated here to properly signal which vCPU page-tables are loaded
+in the calls to switch_cr3_cr4().
+
+Note the EFI page-tables have the Xen owned L4 slots copied from the idle
+page-tables, so for the effects of the mapcache the EFI page-tables could
+use the idle mapcache if it had one. Pass the idle vCPU in the
+switch_cr3_cr4() call that switches to the runtime EFI page-tables.
+
+There are known issues with the use of mapcache in NMI context. This patch
+does not alter the behaviour.
+
+This is CVE-2026-42488 / XSA-494.
+
+Fixes: fb0ff49fe9f7 ("x86/shadow: defer releasing of PV's top-level shadow reference")
+Signed-off-by: Roger Pau Monné <roger.pau@citrix.com>
+Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
+---
+ xen/arch/x86/domain_page.c | 48 ++++++++++++----------------
+ xen/arch/x86/flushtlb.c | 5 ++-
+ xen/arch/x86/include/asm/domain.h | 1 -
+ xen/arch/x86/include/asm/flushtlb.h | 2 +-
+ xen/arch/x86/include/asm/processor.h | 3 ++
+ xen/arch/x86/mm.c | 4 +--
+ xen/arch/x86/pv/dom0_build.c | 12 +++----
+ xen/arch/x86/pv/domain.c | 13 ++++++--
+ xen/arch/x86/smpboot.c | 1 +
+ xen/common/efi/common-stub.c | 5 ---
+ xen/common/efi/runtime.c | 21 +++++-------
+ xen/include/xen/efi.h | 1 -
+ 12 files changed, 54 insertions(+), 62 deletions(-)
+
+diff --git a/xen/arch/x86/domain_page.c b/xen/arch/x86/domain_page.c
+index eac5e3304fb8..72c00194f315 100644
+--- a/xen/arch/x86/domain_page.c
++++ b/xen/arch/x86/domain_page.c
+@@ -18,48 +18,40 @@
+ #include <asm/hardirq.h>
+ #include <asm/setup.h>
+
+-static DEFINE_PER_CPU(struct vcpu *, override);
+-
+ static inline struct vcpu *mapcache_current_vcpu(void)
+ {
+- /* In the common case we use the mapcache of the running VCPU. */
+- struct vcpu *v = this_cpu(override) ?: current;
+-
+- /*
+- * When current isn't properly set up yet, this is equivalent to
+- * running in an idle vCPU (callers must check for NULL).
+- */
+- if ( !v )
+- return NULL;
++ struct vcpu *v = this_cpu(pgtable_vcpu);
++ struct vcpu *curr = current;
+
+ /*
+- * When using efi runtime page tables, we have the equivalent of the idle
+- * domain's page tables but current may point at another domain's VCPU.
+- * Return NULL as though current is not properly set up yet.
++ * During early boot pgtable_vcpu is not set, callers must handle NULL.
++ * Non-PV domains don't have a mapcache, the directmap covers all physical
++ * address space.
+ */
+- if ( efi_rs_using_pgtables() )
++ if ( !v || !is_pv_vcpu(v) )
+ return NULL;
+
+ /*
+- * If guest_table is NULL, and we are running a paravirtualised guest,
+- * then it means we are running on the idle domain's page table and must
+- * therefore use its mapcache.
++ * If we are in a lazy context-switch state from a PV vCPU do a full switch
++ * to the idle vCPU now, otherwise an incoming FLUSH_VCPU_STATE IPI would
++ * change the page tables under our feet an invalidate any in-use mapcache
++ * entries.
+ */
+- if ( unlikely(pagetable_is_null(v->arch.guest_table)) && is_pv_vcpu(v) )
++ if ( unlikely(this_cpu(curr_vcpu) != curr) )
+ {
+- /* If we really are idling, perform lazy context switch now. */
+- if ( (v = idle_vcpu[smp_processor_id()]) == current )
+- sync_local_execstate();
++ ASSERT(curr == idle_vcpu[smp_processor_id()]);
++ sync_local_execstate();
+ /* We must now be running on the idle page table. */
+ ASSERT(cr3_pa(read_cr3()) == __pa(idle_pg_table));
+ }
+
+- return v;
+-}
+-
+-void __init mapcache_override_current(struct vcpu *v)
+-{
+- this_cpu(override) = v;
++ /*
++ * At this point we can guarantee Xen is not in lazy context switch: either
++ * the code above will have synced the state, or an incoming
++ * FLUSH_VCPU_STATE IPI has done so behind our back. Use ACCESS_ONCE to
++ * ensure the compiler never returns the locally cached pgtable_vcpu value.
++ */
++ return ACCESS_ONCE(this_cpu(pgtable_vcpu));
+ }
+
+ #define mapcache_l2_entry(e) ((e) >> PAGETABLE_ORDER)
+diff --git a/xen/arch/x86/flushtlb.c b/xen/arch/x86/flushtlb.c
+index 09e676c151fa..928bca66b433 100644
+--- a/xen/arch/x86/flushtlb.c
++++ b/xen/arch/x86/flushtlb.c
+@@ -111,7 +111,9 @@ static void do_tlb_flush(void)
+ local_irq_restore(flags);
+ }
+
+-void switch_cr3_cr4(unsigned long cr3, unsigned long cr4)
++DEFINE_PER_CPU(struct vcpu *, pgtable_vcpu);
++
++void switch_cr3_cr4(struct vcpu *v, unsigned long cr3, unsigned long cr4)
+ {
+ unsigned long flags, old_cr4;
+ u32 t = 0;
+@@ -155,6 +157,7 @@ void switch_cr3_cr4(unsigned long cr3, unsigned long cr4)
+ if ( (old_cr4 & X86_CR4_PCIDE) > (cr4 & X86_CR4_PCIDE) )
+ cr3 |= X86_CR3_NOFLUSH;
+ write_cr3(cr3);
++ this_cpu(pgtable_vcpu) = v;
+
+ if ( old_cr4 != cr4 )
+ write_cr4(cr4);
+diff --git a/xen/arch/x86/include/asm/domain.h b/xen/arch/x86/include/asm/domain.h
+index 828f42c3e448..10d2b9fe2546 100644
+--- a/xen/arch/x86/include/asm/domain.h
++++ b/xen/arch/x86/include/asm/domain.h
+@@ -75,7 +75,6 @@ struct mapcache_domain {
+
+ int mapcache_domain_init(struct domain *d);
+ int mapcache_vcpu_init(struct vcpu *v);
+-void mapcache_override_current(struct vcpu *v);
+
+ /* x86/64: toggle guest between kernel and user modes. */
+ void toggle_guest_mode(struct vcpu *v);
+diff --git a/xen/arch/x86/include/asm/flushtlb.h b/xen/arch/x86/include/asm/flushtlb.h
+index 7bcbca2b7f31..345677eb72ae 100644
+--- a/xen/arch/x86/include/asm/flushtlb.h
++++ b/xen/arch/x86/include/asm/flushtlb.h
+@@ -104,7 +104,7 @@ static inline void invlpg(const void *p)
+ }
+
+ /* Write pagetable base and implicitly tick the tlbflush clock. */
+-void switch_cr3_cr4(unsigned long cr3, unsigned long cr4);
++void switch_cr3_cr4(struct vcpu *v, unsigned long cr3, unsigned long cr4);
+
+ /* flush_* flag fields: */
+ /*
+diff --git a/xen/arch/x86/include/asm/processor.h b/xen/arch/x86/include/asm/processor.h
+index 2e087c625770..d2cacdfedb74 100644
+--- a/xen/arch/x86/include/asm/processor.h
++++ b/xen/arch/x86/include/asm/processor.h
+@@ -328,6 +328,9 @@ DECLARE_PER_CPU(struct tss_page, tss_page);
+
+ DECLARE_PER_CPU(root_pgentry_t *, root_pgt);
+
++/* vCPU of the currently loaded page-tables. */
++DECLARE_PER_CPU(struct vcpu *, pgtable_vcpu);
++
+ extern void write_ptbase(struct vcpu *v);
+
+ /* PAUSE (encoding: REP NOP) is a good thing to insert into busy-wait loops. */
+diff --git a/xen/arch/x86/mm.c b/xen/arch/x86/mm.c
+index 2b23bf2e7a75..d02c9862d387 100644
+--- a/xen/arch/x86/mm.c
++++ b/xen/arch/x86/mm.c
+@@ -535,7 +535,7 @@ void write_ptbase(struct vcpu *v)
+ cpu_info->pv_cr3 = __pa(this_cpu(root_pgt));
+ if ( new_cr4 & X86_CR4_PCIDE )
+ cpu_info->pv_cr3 |= get_pcid_bits(v, true);
+- switch_cr3_cr4(v->arch.cr3, new_cr4);
++ switch_cr3_cr4(v, v->arch.cr3, new_cr4);
+ }
+ else
+ {
+@@ -543,7 +543,7 @@ void write_ptbase(struct vcpu *v)
+ cpu_info->use_pv_cr3 = false;
+ cpu_info->xen_cr3 = 0;
+ /* switch_cr3_cr4() serializes. */
+- switch_cr3_cr4(v->arch.cr3, new_cr4);
++ switch_cr3_cr4(v, v->arch.cr3, new_cr4);
+ cpu_info->pv_cr3 = 0;
+ }
+ }
+diff --git a/xen/arch/x86/pv/dom0_build.c b/xen/arch/x86/pv/dom0_build.c
+index 37729091dfaa..42bc530c0f0d 100644
+--- a/xen/arch/x86/pv/dom0_build.c
++++ b/xen/arch/x86/pv/dom0_build.c
+@@ -828,8 +828,7 @@ static int __init dom0_construct(const struct boot_domain *bd)
+ update_cr3(v);
+
+ /* We run on dom0's page tables for the final part of the build process. */
+- switch_cr3_cr4(cr3_pa(v->arch.cr3), read_cr4());
+- mapcache_override_current(v);
++ switch_cr3_cr4(v, cr3_pa(v->arch.cr3), read_cr4());
+
+ /* Copy the OS image and free temporary buffer. */
+ elf.dest_base = (void*)vkern_start;
+@@ -838,8 +837,7 @@ static int __init dom0_construct(const struct boot_domain *bd)
+ rc = elf_load_binary(&elf);
+ if ( rc < 0 )
+ {
+- mapcache_override_current(NULL);
+- switch_cr3_cr4(current->arch.cr3, read_cr4());
++ switch_cr3_cr4(current, current->arch.cr3, read_cr4());
+ printk("Failed to load the kernel binary\n");
+ goto out;
+ }
+@@ -850,8 +848,7 @@ static int __init dom0_construct(const struct boot_domain *bd)
+ if ( (parms.virt_hypercall < v_start) ||
+ (parms.virt_hypercall >= v_end) )
+ {
+- mapcache_override_current(NULL);
+- switch_cr3_cr4(current->arch.cr3, read_cr4());
++ switch_cr3_cr4(current, current->arch.cr3, read_cr4());
+ printk("Invalid HYPERCALL_PAGE field in ELF notes.\n");
+ return -EINVAL;
+ }
+@@ -992,8 +989,7 @@ static int __init dom0_construct(const struct boot_domain *bd)
+ #endif
+
+ /* Return to idle domain's page tables. */
+- mapcache_override_current(NULL);
+- switch_cr3_cr4(current->arch.cr3, read_cr4());
++ switch_cr3_cr4(current, current->arch.cr3, read_cr4());
+
+ update_domain_wallclock_time(d);
+
+diff --git a/xen/arch/x86/pv/domain.c b/xen/arch/x86/pv/domain.c
+index ef4f442e7332..d9e52f5f88f3 100644
+--- a/xen/arch/x86/pv/domain.c
++++ b/xen/arch/x86/pv/domain.c
+@@ -451,6 +451,8 @@ static void _toggle_guest_pt(struct vcpu *v)
+ pagetable_t old_shadow;
+ unsigned long cr3;
+
++ ASSERT(local_irq_is_enabled());
++
+ v->arch.flags ^= TF_kernel_mode;
+ guest_update = v->arch.flags & TF_kernel_mode;
+ old_shadow = update_cr3(v);
+@@ -473,15 +475,22 @@ static void _toggle_guest_pt(struct vcpu *v)
+ {
+ cr3 &= ~X86_CR3_NOFLUSH;
+
++ local_irq_disable();
+ 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;
++
++ cr3 = idle_vcpu[v->processor]->arch.cr3;
++ this_cpu(pgtable_vcpu) = idle_vcpu[v->processor];
+ }
++
++ write_cr3(cr3);
++ local_irq_enable();
+ }
+- write_cr3(cr3);
++ else
++ write_cr3(cr3);
+
+ if ( !pagetable_is_null(old_shadow) )
+ shadow_put_top_level(v->domain, old_shadow);
+diff --git a/xen/arch/x86/smpboot.c b/xen/arch/x86/smpboot.c
+index 27628800a821..b37feab3bef4 100644
+--- a/xen/arch/x86/smpboot.c
++++ b/xen/arch/x86/smpboot.c
+@@ -1063,6 +1063,7 @@ static int cpu_smpboot_alloc(unsigned int cpu)
+
+ info->current_vcpu = idle_vcpu[cpu]; /* set_current() */
+ per_cpu(curr_vcpu, cpu) = idle_vcpu[cpu];
++ per_cpu(pgtable_vcpu, cpu) = idle_vcpu[cpu];
+
+ gdt = per_cpu(gdt, cpu) ?: alloc_xenheap_pages(0, memflags);
+ if ( gdt == NULL )
+diff --git a/xen/common/efi/common-stub.c b/xen/common/efi/common-stub.c
+index 77f138a6c574..7b12005bea3f 100644
+--- a/xen/common/efi/common-stub.c
++++ b/xen/common/efi/common-stub.c
+@@ -7,11 +7,6 @@ bool efi_enabled(unsigned int feature)
+ return false;
+ }
+
+-bool efi_rs_using_pgtables(void)
+-{
+- return false;
+-}
+-
+ unsigned long efi_get_time(void)
+ {
+ BUG();
+diff --git a/xen/common/efi/runtime.c b/xen/common/efi/runtime.c
+index 30d649ca5c1b..feb09acf754c 100644
+--- a/xen/common/efi/runtime.c
++++ b/xen/common/efi/runtime.c
+@@ -49,7 +49,6 @@ const CHAR16 *__read_mostly efi_fw_vendor;
+ const EFI_RUNTIME_SERVICES *__read_mostly efi_rs;
+ #ifndef CONFIG_ARM /* TODO - disabled until implemented on ARM */
+ static DEFINE_SPINLOCK(efi_rs_lock);
+-static unsigned int efi_rs_on_cpu = NR_CPUS;
+ #endif
+
+ UINTN __read_mostly efi_memmap_size;
+@@ -92,6 +91,11 @@ struct efi_rs_state efi_rs_enter(void)
+ if ( mfn_eq(efi_l4_mfn, INVALID_MFN) )
+ return state;
+
++ /*
++ * If in lazy idle context switch state sync now to avoid an incoming
++ * FLUSH_VCPU_STATE IPI changing the loaded page-tables.
++ */
++ sync_local_execstate();
+ state.cr3 = read_cr3();
+ save_fpu_enable();
+ asm volatile ( "fnclex; fldcw %0" :: "m" (fcw) );
+@@ -99,8 +103,6 @@ struct efi_rs_state efi_rs_enter(void)
+
+ spin_lock(&efi_rs_lock);
+
+- efi_rs_on_cpu = smp_processor_id();
+-
+ /* prevent fixup_page_fault() from doing anything */
+ irq_enter();
+
+@@ -115,7 +117,8 @@ struct efi_rs_state efi_rs_enter(void)
+ lgdt(&gdt_desc);
+ }
+
+- switch_cr3_cr4(mfn_to_maddr(efi_l4_mfn), read_cr4());
++ switch_cr3_cr4(idle_vcpu[smp_processor_id()], mfn_to_maddr(efi_l4_mfn),
++ read_cr4());
+
+ /*
+ * At the time of writing (2022), no UEFI firwmare is CET-IBT compatible.
+@@ -143,7 +146,7 @@ void efi_rs_leave(struct efi_rs_state *state)
+ if ( state->msr_s_cet )
+ wrmsrl(MSR_S_CET, state->msr_s_cet);
+
+- switch_cr3_cr4(state->cr3, read_cr4());
++ switch_cr3_cr4(curr, state->cr3, read_cr4());
+ if ( is_pv_vcpu(curr) && !is_idle_vcpu(curr) )
+ {
+ struct desc_ptr gdt_desc = {
+@@ -154,18 +157,10 @@ void efi_rs_leave(struct efi_rs_state *state)
+ lgdt(&gdt_desc);
+ }
+ irq_exit();
+- efi_rs_on_cpu = NR_CPUS;
+ spin_unlock(&efi_rs_lock);
+ vcpu_restore_fpu_nonlazy(curr, true);
+ }
+
+-bool efi_rs_using_pgtables(void)
+-{
+- return !mfn_eq(efi_l4_mfn, INVALID_MFN) &&
+- (smp_processor_id() == efi_rs_on_cpu) &&
+- (read_cr3() == mfn_to_maddr(efi_l4_mfn));
+-}
+-
+ unsigned long efi_get_time(void)
+ {
+ EFI_TIME time;
+diff --git a/xen/include/xen/efi.h b/xen/include/xen/efi.h
+index 723cb8085270..9953197ee553 100644
+--- a/xen/include/xen/efi.h
++++ b/xen/include/xen/efi.h
+@@ -40,7 +40,6 @@ extern bool efi_secure_boot;
+
+ void efi_init_memory(void);
+ bool efi_boot_mem_unused(unsigned long *start, unsigned long *end);
+-bool efi_rs_using_pgtables(void);
+ unsigned long efi_get_time(void);
+ void efi_halt_system(void);
+ void efi_reset_system(bool warm);
+--
+2.53.0
+
reply other threads:[~2026-06-18 20:15 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178181375620.1.3012939153567001614.rpms-xen-0c18c23c4c54@fedoraproject.org \
--to=m.a.young@durham.ac.uk \
--cc=git-commits@fedoraproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox