[18/22] x86/fpu: Update xstate's PKRU value on write_pkru()
diff mbox series

Message ID 20190109114744.10936-19-bigeasy@linutronix.de
State New
Headers show
Series
  • [v6] x86: load FPU registers on return to userland
Related show

Commit Message

Sebastian Andrzej Siewior Jan. 9, 2019, 11:47 a.m. UTC
During the context switch the xstate is loaded which also includes the
PKRU value.
If xstate is restored on return to userland it is required that the
PKRU value in xstate is the same as the one in the CPU.

Save the PKRU in xstate during modification.

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
 arch/x86/include/asm/pgtable.h | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

Comments

Dave Hansen Jan. 23, 2019, 5:28 p.m. UTC | #1
On 1/9/19 3:47 AM, Sebastian Andrzej Siewior wrote:
> +	pk = get_xsave_addr(&current->thread.fpu.state.xsave, XFEATURE_PKRU);
> +	/*
> +	 * The PKRU value in xstate needs to be in sync with the value that is
> +	 * written to the CPU. The FPU restore on return to userland would
> +	 * otherwise load the previous value again.
> +	 */
> +	__fpregs_changes_begin();
> +	if (pk)
> +		pk->pkru = pkru;
> +	__write_pkru(pkru);
> +	__fpregs_changes_end();
>  }

I'm not sure this is right.

The "if (pk)" check is basically to see if there was a location for
XFEATURE_PKRU in the XSAVE buffer.  The only way this can be false in
the current code is if the "init optimization" is in play and
XFEATURE_PKRU was in the init state (all 0's for PKRU).

If it were in the init state, we need to take it *out* of the init
state, both in the buffer and in the registers.  The __write_pkru()
obviously does this for the registers, but "pk->pkru = pkru" is not
enough for the XSAVE buffer.  xsave->header.xfeatures (aka. XSTATE_BV)
also needs to have XFEATURE_PKRU set.  Otherwise, two calls to this
function in succession would break.

	pk = get_xsave_addr(...xsave, XFEATURE_PKRU);
	pk->pkru = pkru;
	__write_pkru(pkru);

	pk = get_xsave_addr(...xsave, XFEATURE_PKRU);
	/* 'pk' is still NULL, won't see 'pkru' set */

I *think* just setting

	xsave->header.xfeatures |= XFEATURE_MASK_PKRU;

will fix this.  I thought we did that whole dance somewhere else in the
code, but I don't see it right now.  Might have been in some other patch.

Patch
diff mbox series

diff --git a/arch/x86/include/asm/pgtable.h b/arch/x86/include/asm/pgtable.h
index 40616e8052924..5eed44798ae95 100644
--- a/arch/x86/include/asm/pgtable.h
+++ b/arch/x86/include/asm/pgtable.h
@@ -23,6 +23,8 @@ 
 
 #ifndef __ASSEMBLY__
 #include <asm/x86_init.h>
+#include <asm/fpu/xstate.h>
+#include <asm/fpu/api.h>
 
 extern pgd_t early_top_pgt[PTRS_PER_PGD];
 int __init __early_make_pgtable(unsigned long address, pmdval_t pmd);
@@ -133,8 +135,22 @@  static inline u32 read_pkru(void)
 
 static inline void write_pkru(u32 pkru)
 {
-	if (boot_cpu_has(X86_FEATURE_OSPKE))
-		__write_pkru(pkru);
+	struct pkru_state *pk;
+
+	if (!boot_cpu_has(X86_FEATURE_OSPKE))
+		return;
+
+	pk = get_xsave_addr(&current->thread.fpu.state.xsave, XFEATURE_PKRU);
+	/*
+	 * The PKRU value in xstate needs to be in sync with the value that is
+	 * written to the CPU. The FPU restore on return to userland would
+	 * otherwise load the previous value again.
+	 */
+	__fpregs_changes_begin();
+	if (pk)
+		pk->pkru = pkru;
+	__write_pkru(pkru);
+	__fpregs_changes_end();
 }
 
 static inline int pte_young(pte_t pte)