Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

x86/fpu/xstate: Adjust xstate copying logic for user ABI

== Background ==

As feature positions in the userspace XSAVE buffer do not always align
with their feature numbers, the XSAVE format conversion needs to be
reconsidered to align with the revised xstate size calculation logic.

* For signal handling, XSAVE and XRSTOR are used directly to save and
restore extended registers.

* For ptrace, KVM, and signal returns (for 32-bit frame), the kernel
copies data between its internal buffer and the userspace XSAVE buffer.
If memcpy() were used for these cases, existing offset helpers — such
as __raw_xsave_addr() or xstate_offsets[] — would be sufficient to
handle the format conversion.

== Problem ==

When copying data from the compacted in-kernel buffer to the
non-compacted userspace buffer, the function follows the
user_regset_get2_fn() prototype. This means it utilizes struct membuf
helpers for the destination buffer. As defined in regset.h, these helpers
update the memory pointer during the copy process, enforcing sequential
writes within the loop.

Since xstate components are processed sequentially, any component whose
buffer position does not align with its feature number has an issue.

== Solution ==

Replace for_each_extended_xfeature() with the newly introduced
for_each_extended_xfeature_in_order(). This macro ensures xstate
components are handled in the correct order based on their actual
positions in the destination buffer, rather than their feature numbers.

Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Oleg Nesterov <oleg@redhat.com>
Link: https://lore.kernel.org/r/20250320234301.8342-5-chang.seok.bae@intel.com

authored by

Chang S. Bae and committed by
Ingo Molnar
cbe8e4da a758ae28

+9 -9
+9 -9
arch/x86/kernel/fpu/xstate.c
··· 1107 1107 const unsigned int off_mxcsr = offsetof(struct fxregs_state, mxcsr); 1108 1108 struct xregs_state *xinit = &init_fpstate.regs.xsave; 1109 1109 struct xregs_state *xsave = &fpstate->regs.xsave; 1110 + unsigned int zerofrom, i, xfeature; 1110 1111 struct xstate_header header; 1111 - unsigned int zerofrom; 1112 1112 u64 mask; 1113 - int i; 1114 1113 1115 1114 memset(&header, 0, sizeof(header)); 1116 1115 header.xfeatures = xsave->header.xfeatures; ··· 1178 1179 */ 1179 1180 mask = header.xfeatures; 1180 1181 1181 - for_each_extended_xfeature(i, mask) { 1182 + for_each_extended_xfeature_in_order(i, mask) { 1183 + xfeature = xfeature_uncompact_order[i]; 1182 1184 /* 1183 1185 * If there was a feature or alignment gap, zero the space 1184 1186 * in the destination buffer. 1185 1187 */ 1186 - if (zerofrom < xstate_offsets[i]) 1187 - membuf_zero(&to, xstate_offsets[i] - zerofrom); 1188 + if (zerofrom < xstate_offsets[xfeature]) 1189 + membuf_zero(&to, xstate_offsets[xfeature] - zerofrom); 1188 1190 1189 - if (i == XFEATURE_PKRU) { 1191 + if (xfeature == XFEATURE_PKRU) { 1190 1192 struct pkru_state pkru = {0}; 1191 1193 /* 1192 1194 * PKRU is not necessarily up to date in the ··· 1197 1197 membuf_write(&to, &pkru, sizeof(pkru)); 1198 1198 } else { 1199 1199 membuf_write(&to, 1200 - __raw_xsave_addr(xsave, i), 1201 - xstate_sizes[i]); 1200 + __raw_xsave_addr(xsave, xfeature), 1201 + xstate_sizes[xfeature]); 1202 1202 } 1203 1203 /* 1204 1204 * Keep track of the last copied state in the non-compacted 1205 1205 * target buffer for gap zeroing. 1206 1206 */ 1207 - zerofrom = xstate_offsets[i] + xstate_sizes[i]; 1207 + zerofrom = xstate_offsets[xfeature] + xstate_sizes[xfeature]; 1208 1208 } 1209 1209 1210 1210 out: