diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c index 39e1c8626ab9..8a32a8398e1f 100644 --- a/arch/x86/kernel/fpu/xstate.c +++ b/arch/x86/kernel/fpu/xstate.c @@ -1178,6 +1178,15 @@ static int copy_from_buffer(void *dst, unsigned int offset, unsigned int size, } +/* + * Replace all user xfeatures with data from 'ubuf'. Features + * marked as init in 'ubuf' will be marked as init in the kernel + * buffer. Supervisor xfeatures will be preserved. + * + * Returns 0 on success. Non-zero return codes indicate that + * the kernel xsave buffer may have been left in an inconsistent + * state. Caller must reset fpstate to recover. + */ static int copy_uabi_to_xstate(struct fpstate *fpstate, const void *kbuf, const void __user *ubuf) { @@ -1214,30 +1223,30 @@ static int copy_uabi_to_xstate(struct fpstate *fpstate, const void *kbuf, } } + /* + * Leave only supervisor states in 'xfeatures'. User xfeature + * bits are set in the loop below. + */ + xsave->header.xfeatures &= XFEATURE_MASK_SUPERVISOR_ALL; + for (i = 0; i < XFEATURE_MAX; i++) { u64 mask = ((u64)1 << i); + void *dst = __raw_xsave_addr(xsave, i); - if (hdr.xfeatures & mask) { - void *dst = __raw_xsave_addr(xsave, i); - - offset = xstate_offsets[i]; - size = xstate_sizes[i]; - - if (copy_from_buffer(dst, offset, size, kbuf, ubuf)) - return -EFAULT; + if (!hdr.xfeatures & mask) { + /* Feature was marked as init in uabi buffer: */ + xsave->header.xfeatures &= ~mask; + continue } - } + /* Feature was present in uabi buffer: */ + xsave->header.xfeatures |= mask; - /* - * The state that came in from userspace was user-state only. - * Mask all the user states out of 'xfeatures': - */ - xsave->header.xfeatures &= XFEATURE_MASK_SUPERVISOR_ALL; + offset = xstate_offsets[i]; + size = xstate_sizes[i]; - /* - * Add back in the features that came in from userspace: - */ - xsave->header.xfeatures |= hdr.xfeatures; + if (copy_from_buffer(dst, offset, size, kbuf, ubuf)) + return -EFAULT; + } return 0; }