public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/xen] f44: 4 security updates
@ 2026-06-12 20:16 Michael Young
  0 siblings, 0 replies; only message in thread
From: Michael Young @ 2026-06-12 20:16 UTC (permalink / raw)
  To: git-commits

            A new commit has been pushed.

            Repo   : rpms/xen
            Branch : f44
            Commit : 5166c93885c2b0d89ae8103359622e5e19a9a100
            Author : Michael Young <m.a.young@durham.ac.uk>
            Date   : 2026-06-12T21:13:57+01:00
            Stats  : +4066/-1 in 27 file(s)
            URL    : https://src.fedoraproject.org/rpms/xen/c/5166c93885c2b0d89ae8103359622e5e19a9a100?branch=f44

            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 fdb9fba..7656e6b 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: 3%{?dist}
+Release: 4%{?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
+* Fri Jun 12 2026 Michael Young <m.a.young@durham.ac.uk> - 4.21.1-4
+- 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]
+
 * Tue May 12 2026 Michael Young <m.a.young@durham.ac.uk> - 4.21.1-3
 - x86: CPU Opcode Cache corruption [XSA-490,CVE-2025-54518]
 

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 = &current->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 = &current->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 = &current->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 = &current->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 = &current->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
+

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-06-12 20:16 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-12 20:16 [rpms/xen] f44: 4 security updates Michael Young

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox