diff options
Diffstat (limited to 'arch/x86/kernel/fpu/xstate.c')
| -rw-r--r-- | arch/x86/kernel/fpu/xstate.c | 264 | 
1 files changed, 213 insertions, 51 deletions
| diff --git a/arch/x86/kernel/fpu/xstate.c b/arch/x86/kernel/fpu/xstate.c index c24ac1efb12d..f1d5476c9022 100644 --- a/arch/x86/kernel/fpu/xstate.c +++ b/arch/x86/kernel/fpu/xstate.c @@ -483,6 +483,30 @@ int using_compacted_format(void)  	return boot_cpu_has(X86_FEATURE_XSAVES);  } +/* Validate an xstate header supplied by userspace (ptrace or sigreturn) */ +int validate_xstate_header(const struct xstate_header *hdr) +{ +	/* No unknown or supervisor features may be set */ +	if (hdr->xfeatures & (~xfeatures_mask | XFEATURE_MASK_SUPERVISOR)) +		return -EINVAL; + +	/* Userspace must use the uncompacted format */ +	if (hdr->xcomp_bv) +		return -EINVAL; + +	/* +	 * If 'reserved' is shrunken to add a new field, make sure to validate +	 * that new field here! +	 */ +	BUILD_BUG_ON(sizeof(hdr->reserved) != 48); + +	/* No reserved bits may be set */ +	if (memchr_inv(hdr->reserved, 0, sizeof(hdr->reserved))) +		return -EINVAL; + +	return 0; +} +  static void __xstate_dump_leaves(void)  {  	int i; @@ -867,7 +891,7 @@ const void *get_xsave_field_ptr(int xsave_state)  {  	struct fpu *fpu = ¤t->thread.fpu; -	if (!fpu->fpstate_active) +	if (!fpu->initialized)  		return NULL;  	/*  	 * fpu__save() takes the CPU's xstate registers @@ -921,38 +945,129 @@ int arch_set_user_pkey_access(struct task_struct *tsk, int pkey,  #endif /* ! CONFIG_ARCH_HAS_PKEYS */  /* + * Weird legacy quirk: SSE and YMM states store information in the + * MXCSR and MXCSR_FLAGS fields of the FP area. That means if the FP + * area is marked as unused in the xfeatures header, we need to copy + * MXCSR and MXCSR_FLAGS if either SSE or YMM are in use. + */ +static inline bool xfeatures_mxcsr_quirk(u64 xfeatures) +{ +	if (!(xfeatures & (XFEATURE_MASK_SSE|XFEATURE_MASK_YMM))) +		return false; + +	if (xfeatures & XFEATURE_MASK_FP) +		return false; + +	return true; +} + +/*   * This is similar to user_regset_copyout(), but will not add offset to   * the source data pointer or increment pos, count, kbuf, and ubuf.   */ -static inline int xstate_copyout(unsigned int pos, unsigned int count, -				 void *kbuf, void __user *ubuf, -				 const void *data, const int start_pos, -				 const int end_pos) +static inline void +__copy_xstate_to_kernel(void *kbuf, const void *data, +			unsigned int offset, unsigned int size, unsigned int size_total)  { -	if ((count == 0) || (pos < start_pos)) -		return 0; +	if (offset < size_total) { +		unsigned int copy = min(size, size_total - offset); -	if (end_pos < 0 || pos < end_pos) { -		unsigned int copy = (end_pos < 0 ? count : min(count, end_pos - pos)); +		memcpy(kbuf + offset, data, copy); +	} +} -		if (kbuf) { -			memcpy(kbuf + pos, data, copy); -		} else { -			if (__copy_to_user(ubuf + pos, data, copy)) -				return -EFAULT; +/* + * Convert from kernel XSAVES compacted format to standard format and copy + * to a kernel-space ptrace buffer. + * + * It supports partial copy but pos always starts from zero. This is called + * from xstateregs_get() and there we check the CPU has XSAVES. + */ +int copy_xstate_to_kernel(void *kbuf, struct xregs_state *xsave, unsigned int offset_start, unsigned int size_total) +{ +	unsigned int offset, size; +	struct xstate_header header; +	int i; + +	/* +	 * Currently copy_regset_to_user() starts from pos 0: +	 */ +	if (unlikely(offset_start != 0)) +		return -EFAULT; + +	/* +	 * The destination is a ptrace buffer; we put in only user xstates: +	 */ +	memset(&header, 0, sizeof(header)); +	header.xfeatures = xsave->header.xfeatures; +	header.xfeatures &= ~XFEATURE_MASK_SUPERVISOR; + +	/* +	 * Copy xregs_state->header: +	 */ +	offset = offsetof(struct xregs_state, header); +	size = sizeof(header); + +	__copy_xstate_to_kernel(kbuf, &header, offset, size, size_total); + +	for (i = 0; i < XFEATURE_MAX; i++) { +		/* +		 * Copy only in-use xstates: +		 */ +		if ((header.xfeatures >> i) & 1) { +			void *src = __raw_xsave_addr(xsave, 1 << i); + +			offset = xstate_offsets[i]; +			size = xstate_sizes[i]; + +			/* The next component has to fit fully into the output buffer: */ +			if (offset + size > size_total) +				break; + +			__copy_xstate_to_kernel(kbuf, src, offset, size, size_total);  		} + +	} + +	if (xfeatures_mxcsr_quirk(header.xfeatures)) { +		offset = offsetof(struct fxregs_state, mxcsr); +		size = MXCSR_AND_FLAGS_SIZE; +		__copy_xstate_to_kernel(kbuf, &xsave->i387.mxcsr, offset, size, size_total); +	} + +	/* +	 * Fill xsave->i387.sw_reserved value for ptrace frame: +	 */ +	offset = offsetof(struct fxregs_state, sw_reserved); +	size = sizeof(xstate_fx_sw_bytes); + +	__copy_xstate_to_kernel(kbuf, xstate_fx_sw_bytes, offset, size, size_total); + +	return 0; +} + +static inline int +__copy_xstate_to_user(void __user *ubuf, const void *data, unsigned int offset, unsigned int size, unsigned int size_total) +{ +	if (!size) +		return 0; + +	if (offset < size_total) { +		unsigned int copy = min(size, size_total - offset); + +		if (__copy_to_user(ubuf + offset, data, copy)) +			return -EFAULT;  	}  	return 0;  }  /*   * Convert from kernel XSAVES compacted format to standard format and copy - * to a ptrace buffer. It supports partial copy but pos always starts from + * to a user-space buffer. It supports partial copy but pos always starts from   * zero. This is called from xstateregs_get() and there we check the CPU   * has XSAVES.   */ -int copyout_from_xsaves(unsigned int pos, unsigned int count, void *kbuf, -			void __user *ubuf, struct xregs_state *xsave) +int copy_xstate_to_user(void __user *ubuf, struct xregs_state *xsave, unsigned int offset_start, unsigned int size_total)  {  	unsigned int offset, size;  	int ret, i; @@ -961,7 +1076,7 @@ int copyout_from_xsaves(unsigned int pos, unsigned int count, void *kbuf,  	/*  	 * Currently copy_regset_to_user() starts from pos 0:  	 */ -	if (unlikely(pos != 0)) +	if (unlikely(offset_start != 0))  		return -EFAULT;  	/* @@ -977,8 +1092,7 @@ int copyout_from_xsaves(unsigned int pos, unsigned int count, void *kbuf,  	offset = offsetof(struct xregs_state, header);  	size = sizeof(header); -	ret = xstate_copyout(offset, size, kbuf, ubuf, &header, 0, count); - +	ret = __copy_xstate_to_user(ubuf, &header, offset, size, size_total);  	if (ret)  		return ret; @@ -992,25 +1106,30 @@ int copyout_from_xsaves(unsigned int pos, unsigned int count, void *kbuf,  			offset = xstate_offsets[i];  			size = xstate_sizes[i]; -			ret = xstate_copyout(offset, size, kbuf, ubuf, src, 0, count); +			/* The next component has to fit fully into the output buffer: */ +			if (offset + size > size_total) +				break; +			ret = __copy_xstate_to_user(ubuf, src, offset, size, size_total);  			if (ret)  				return ret; - -			if (offset + size >= count) -				break;  		}  	} +	if (xfeatures_mxcsr_quirk(header.xfeatures)) { +		offset = offsetof(struct fxregs_state, mxcsr); +		size = MXCSR_AND_FLAGS_SIZE; +		__copy_xstate_to_user(ubuf, &xsave->i387.mxcsr, offset, size, size_total); +	} +  	/*  	 * Fill xsave->i387.sw_reserved value for ptrace frame:  	 */  	offset = offsetof(struct fxregs_state, sw_reserved);  	size = sizeof(xstate_fx_sw_bytes); -	ret = xstate_copyout(offset, size, kbuf, ubuf, xstate_fx_sw_bytes, 0, count); - +	ret = __copy_xstate_to_user(ubuf, xstate_fx_sw_bytes, offset, size, size_total);  	if (ret)  		return ret; @@ -1018,55 +1137,98 @@ int copyout_from_xsaves(unsigned int pos, unsigned int count, void *kbuf,  }  /* - * Convert from a ptrace standard-format buffer to kernel XSAVES format - * and copy to the target thread. This is called from xstateregs_set() and - * there we check the CPU has XSAVES and a whole standard-sized buffer - * exists. + * Convert from a ptrace standard-format kernel buffer to kernel XSAVES format + * and copy to the target thread. This is called from xstateregs_set().   */ -int copyin_to_xsaves(const void *kbuf, const void __user *ubuf, -		     struct xregs_state *xsave) +int copy_kernel_to_xstate(struct xregs_state *xsave, const void *kbuf)  {  	unsigned int offset, size;  	int i; -	u64 xfeatures; -	u64 allowed_features; +	struct xstate_header hdr;  	offset = offsetof(struct xregs_state, header); -	size = sizeof(xfeatures); +	size = sizeof(hdr); -	if (kbuf) { -		memcpy(&xfeatures, kbuf + offset, size); -	} else { -		if (__copy_from_user(&xfeatures, ubuf + offset, size)) -			return -EFAULT; +	memcpy(&hdr, kbuf + offset, size); + +	if (validate_xstate_header(&hdr)) +		return -EINVAL; + +	for (i = 0; i < XFEATURE_MAX; i++) { +		u64 mask = ((u64)1 << i); + +		if (hdr.xfeatures & mask) { +			void *dst = __raw_xsave_addr(xsave, 1 << i); + +			offset = xstate_offsets[i]; +			size = xstate_sizes[i]; + +			memcpy(dst, kbuf + offset, size); +		} +	} + +	if (xfeatures_mxcsr_quirk(hdr.xfeatures)) { +		offset = offsetof(struct fxregs_state, mxcsr); +		size = MXCSR_AND_FLAGS_SIZE; +		memcpy(&xsave->i387.mxcsr, kbuf + offset, size);  	}  	/* -	 * Reject if the user sets any disabled or supervisor features: +	 * 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; + +	/* +	 * Add back in the features that came in from userspace:  	 */ -	allowed_features = xfeatures_mask & ~XFEATURE_MASK_SUPERVISOR; +	xsave->header.xfeatures |= hdr.xfeatures; -	if (xfeatures & ~allowed_features) +	return 0; +} + +/* + * Convert from a ptrace or sigreturn standard-format user-space buffer to + * kernel XSAVES format and copy to the target thread. This is called from + * xstateregs_set(), as well as potentially from the sigreturn() and + * rt_sigreturn() system calls. + */ +int copy_user_to_xstate(struct xregs_state *xsave, const void __user *ubuf) +{ +	unsigned int offset, size; +	int i; +	struct xstate_header hdr; + +	offset = offsetof(struct xregs_state, header); +	size = sizeof(hdr); + +	if (__copy_from_user(&hdr, ubuf + offset, size)) +		return -EFAULT; + +	if (validate_xstate_header(&hdr))  		return -EINVAL;  	for (i = 0; i < XFEATURE_MAX; i++) {  		u64 mask = ((u64)1 << i); -		if (xfeatures & mask) { +		if (hdr.xfeatures & mask) {  			void *dst = __raw_xsave_addr(xsave, 1 << i);  			offset = xstate_offsets[i];  			size = xstate_sizes[i]; -			if (kbuf) { -				memcpy(dst, kbuf + offset, size); -			} else { -				if (__copy_from_user(dst, ubuf + offset, size)) -					return -EFAULT; -			} +			if (__copy_from_user(dst, ubuf + offset, size)) +				return -EFAULT;  		}  	} +	if (xfeatures_mxcsr_quirk(hdr.xfeatures)) { +		offset = offsetof(struct fxregs_state, mxcsr); +		size = MXCSR_AND_FLAGS_SIZE; +		if (__copy_from_user(&xsave->i387.mxcsr, ubuf + offset, size)) +			return -EFAULT; +	} +  	/*  	 * The state that came in from userspace was user-state only.  	 * Mask all the user states out of 'xfeatures': @@ -1076,7 +1238,7 @@ int copyin_to_xsaves(const void *kbuf, const void __user *ubuf,  	/*  	 * Add back in the features that came in from userspace:  	 */ -	xsave->header.xfeatures |= xfeatures; +	xsave->header.xfeatures |= hdr.xfeatures;  	return 0;  } |