vfree(fpu->fpstate);
 }
 
-/**
- * fpu_install_fpstate - Update the active fpstate in the FPU
- *
- * @fpu:       A struct fpu * pointer
- * @newfps:    A struct fpstate * pointer
- *
- * Returns:    A null pointer if the last active fpstate is the embedded
- *             one or the new fpstate is already installed;
- *             otherwise, a pointer to the old fpstate which has to
- *             be freed by the caller.
- */
-static struct fpstate *fpu_install_fpstate(struct fpu *fpu,
-                                          struct fpstate *newfps)
-{
-       struct fpstate *oldfps = fpu->fpstate;
-
-       if (fpu->fpstate == newfps)
-               return NULL;
-
-       fpu->fpstate = newfps;
-       return oldfps != &fpu->__fpstate ? oldfps : NULL;
-}
-
 /**
  * fpstate_realloc - Reallocate struct fpstate for the requested new features
  *
  *             of that task
  * @ksize:     The required size for the kernel buffer
  * @usize:     The required size for user space buffers
+ * @guest_fpu: Pointer to a guest FPU container. NULL for host allocations
  *
  * Note vs. vmalloc(): If the task with a vzalloc()-allocated buffer
  * terminates quickly, vfree()-induced IPIs may be a concern, but tasks
  * Returns: 0 on success, -ENOMEM on allocation error.
  */
 static int fpstate_realloc(u64 xfeatures, unsigned int ksize,
-                          unsigned int usize)
+                          unsigned int usize, struct fpu_guest *guest_fpu)
 {
        struct fpu *fpu = ¤t->thread.fpu;
        struct fpstate *curfps, *newfps = NULL;
        unsigned int fpsize;
+       bool in_use;
 
-       curfps = fpu->fpstate;
        fpsize = ksize + ALIGN(offsetof(struct fpstate, regs), 64);
 
        newfps = vzalloc(fpsize);
        newfps->user_size = usize;
        newfps->is_valloc = true;
 
+       /*
+        * When a guest FPU is supplied, use @guest_fpu->fpstate
+        * as reference independent whether it is in use or not.
+        */
+       curfps = guest_fpu ? guest_fpu->fpstate : fpu->fpstate;
+
+       /* Determine whether @curfps is the active fpstate */
+       in_use = fpu->fpstate == curfps;
+
+       if (guest_fpu) {
+               newfps->is_guest = true;
+               newfps->is_confidential = curfps->is_confidential;
+               newfps->in_use = curfps->in_use;
+               guest_fpu->xfeatures |= xfeatures;
+       }
+
        fpregs_lock();
        /*
-        * Ensure that the current state is in the registers before
-        * swapping fpstate as that might invalidate it due to layout
-        * changes.
+        * If @curfps is in use, ensure that the current state is in the
+        * registers before swapping fpstate as that might invalidate it
+        * due to layout changes.
         */
-       if (test_thread_flag(TIF_NEED_FPU_LOAD))
+       if (in_use && test_thread_flag(TIF_NEED_FPU_LOAD))
                fpregs_restore_userregs();
 
        newfps->xfeatures = curfps->xfeatures | xfeatures;
        newfps->user_xfeatures = curfps->user_xfeatures | xfeatures;
        newfps->xfd = curfps->xfd & ~xfeatures;
 
-       curfps = fpu_install_fpstate(fpu, newfps);
-
        /* Do the final updates within the locked region */
        xstate_init_xcomp_bv(&newfps->regs.xsave, newfps->xfeatures);
-       xfd_update_state(newfps);
 
+       if (guest_fpu) {
+               guest_fpu->fpstate = newfps;
+               /* If curfps is active, update the FPU fpstate pointer */
+               if (in_use)
+                       fpu->fpstate = newfps;
+       } else {
+               fpu->fpstate = newfps;
+       }
+
+       if (in_use)
+               xfd_update_state(fpu->fpstate);
        fpregs_unlock();
 
-       vfree(curfps);
+       /* Only free valloc'ed state */
+       if (curfps && curfps->is_valloc)
+               vfree(curfps);
+
        return 0;
 }
 
        return ret;
 }
 
-int xfd_enable_feature(u64 xfd_err)
+int __xfd_enable_feature(u64 xfd_err, struct fpu_guest *guest_fpu)
 {
        u64 xfd_event = xfd_err & XFEATURE_MASK_USER_DYNAMIC;
+       struct fpu_state_perm *perm;
        unsigned int ksize, usize;
        struct fpu *fpu;
 
        if (!xfd_event) {
-               pr_err_once("XFD: Invalid xfd error: %016llx\n", xfd_err);
+               if (!guest_fpu)
+                       pr_err_once("XFD: Invalid xfd error: %016llx\n", xfd_err);
                return 0;
        }
 
        spin_lock_irq(¤t->sighand->siglock);
 
        /* If not permitted let it die */
-       if ((xstate_get_host_group_perm() & xfd_event) != xfd_event) {
+       if ((xstate_get_group_perm(!!guest_fpu) & xfd_event) != xfd_event) {
                spin_unlock_irq(¤t->sighand->siglock);
                return -EPERM;
        }
 
        fpu = ¤t->group_leader->thread.fpu;
-       ksize = fpu->perm.__state_size;
-       usize = fpu->perm.__user_state_size;
+       perm = guest_fpu ? &fpu->guest_perm : &fpu->perm;
+       ksize = perm->__state_size;
+       usize = perm->__user_state_size;
+
        /*
         * The feature is permitted. State size is sufficient.  Dropping
         * the lock is safe here even if more features are added from
         * Try to allocate a new fpstate. If that fails there is no way
         * out.
         */
-       if (fpstate_realloc(xfd_event, ksize, usize))
+       if (fpstate_realloc(xfd_event, ksize, usize, guest_fpu))
                return -EFAULT;
        return 0;
 }
+
+int xfd_enable_feature(u64 xfd_err)
+{
+       return __xfd_enable_feature(xfd_err, NULL);
+}
+
 #else /* CONFIG_X86_64 */
 static inline int xstate_request_perm(unsigned long idx, bool guest)
 {