* [PATCH v4 1/1] x86/ia32: Leave NULL selector values 0~3 as is
@ 2024-11-26 18:45 Xin Li (Intel)
2024-12-12 18:44 ` Xin Li
2025-02-27 21:54 ` [tip: x86/asm] x86/ia32: Leave NULL selector values 0~3 unchanged tip-bot2 for Xin Li (Intel)
0 siblings, 2 replies; 6+ messages in thread
From: Xin Li (Intel) @ 2024-11-26 18:45 UTC (permalink / raw)
To: linux-kernel
Cc: tglx, mingo, bp, dave.hansen, x86, hpa, andrew.cooper3, brgerst,
ebiederm
The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
and 1 of a segment selector, i.e., the RPL bits, are NOT used to index
GDT, selector values 0~3 all point to the NULL descriptor, thus values
0, 1, 2 and 3 are all valid NULL selector values.
When a NULL selector value is to be loaded into a segment register,
reload_segments() sets its RPL bits. Later IRET zeros ES, FS, GS, and
DS segment registers if any of them is found to have any nonzero NULL
selector value. The two operations offset each other to actually effect
a nop.
Besides, zeroing of RPL in NULL selector values is an information leak
in pre-FRED systems as userspace can spot any interrupt/exception by
loading a nonzero NULL selector, and waiting for it to become zero.
But there is nothing software can do to prevent it before FRED.
ERETU, the only legit instruction to return to userspace from kernel
under FRED, by design does NOT zero any segment register to avoid this
problem behavior.
As such, leave NULL selector values 0~3 as is.
Do the same on 32-bit kernel as well.
Signed-off-by: Xin Li (Intel) <xin@zytor.com>
---
Changes since v3:
* Rename usrseg() to fixup_rpl() to match its intent (Andrew Cooper).
* A few comment improvements (Andrew Cooper).
Changes since v2:
* No, don't zero non-zero NULL selector values, essentially revert
to v1 (Andrew Cooper).
Changes since v1:
* Normalize non-zero NULL selector values to 0 (Eric W. Biederman).
* Apply the same normalization logic in a 32bit kernel (Eric W.
Biederman).
---
arch/x86/kernel/signal_32.c | 62 +++++++++++++++++++++++++------------
1 file changed, 43 insertions(+), 19 deletions(-)
diff --git a/arch/x86/kernel/signal_32.c b/arch/x86/kernel/signal_32.c
index ef654530bf5a..1e275268d256 100644
--- a/arch/x86/kernel/signal_32.c
+++ b/arch/x86/kernel/signal_32.c
@@ -33,25 +33,55 @@
#include <asm/smap.h>
#include <asm/gsseg.h>
+/*
+ * The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
+ * and 1 of a segment selector, i.e., the RPL bits, are NOT used to index
+ * GDT, selector values 0~3 all point to the NULL descriptor, thus values
+ * 0, 1, 2 and 3 are all valid NULL selector values.
+ *
+ * However IRET zeros ES, FS, GS, and DS segment registers if any of them
+ * is found to have any nonzero NULL selector value, which can be used by
+ * userspace in pre-FRED systems to spot any interrupt/exception by loading
+ * a nonzero NULL selector and waiting for it to become zero. Before FRED
+ * there is nothing software can do to prevent such an information leak.
+ *
+ * ERETU, the only legit instruction to return to userspace from kernel
+ * under FRED, by design does NOT zero any segment register to avoid this
+ * problem behavior.
+ *
+ * As such, leave NULL selector values 0~3 as is.
+ */
+static inline u16 fixup_rpl(u16 sel)
+{
+ return sel <= 3 ? sel : sel | 3;
+}
+
#ifdef CONFIG_IA32_EMULATION
#include <asm/unistd_32_ia32.h>
static inline void reload_segments(struct sigcontext_32 *sc)
{
- unsigned int cur;
+ u16 cur;
+ /*
+ * Reload fs and gs if they have changed in the signal
+ * handler. This does not handle long fs/gs base changes in
+ * the handler, but does not clobber them at least in the
+ * normal case.
+ */
savesegment(gs, cur);
- if ((sc->gs | 0x03) != cur)
- load_gs_index(sc->gs | 0x03);
+ if (fixup_rpl(sc->gs) != cur)
+ load_gs_index(fixup_rpl(sc->gs));
savesegment(fs, cur);
- if ((sc->fs | 0x03) != cur)
- loadsegment(fs, sc->fs | 0x03);
+ if (fixup_rpl(sc->fs) != cur)
+ loadsegment(fs, fixup_rpl(sc->fs));
+
savesegment(ds, cur);
- if ((sc->ds | 0x03) != cur)
- loadsegment(ds, sc->ds | 0x03);
+ if (fixup_rpl(sc->ds) != cur)
+ loadsegment(ds, fixup_rpl(sc->ds));
savesegment(es, cur);
- if ((sc->es | 0x03) != cur)
- loadsegment(es, sc->es | 0x03);
+ if (fixup_rpl(sc->es) != cur)
+ loadsegment(es, fixup_rpl(sc->es));
}
#define sigset32_t compat_sigset_t
@@ -105,18 +135,12 @@ static bool ia32_restore_sigcontext(struct pt_regs *regs,
regs->orig_ax = -1;
#ifdef CONFIG_IA32_EMULATION
- /*
- * Reload fs and gs if they have changed in the signal
- * handler. This does not handle long fs/gs base changes in
- * the handler, but does not clobber them at least in the
- * normal case.
- */
reload_segments(&sc);
#else
- loadsegment(gs, sc.gs);
- regs->fs = sc.fs;
- regs->es = sc.es;
- regs->ds = sc.ds;
+ loadsegment(gs, fixup_rpl(sc.gs));
+ regs->fs = fixup_rpl(sc.fs);
+ regs->es = fixup_rpl(sc.es);
+ regs->ds = fixup_rpl(sc.ds);
#endif
return fpu__restore_sig(compat_ptr(sc.fpstate), 1);
base-commit: 6ff908de1eafb53f31db75d929b7566a87847d2d
--
2.47.0
^ permalink raw reply related [flat|nested] 6+ messages in thread* Re: [PATCH v4 1/1] x86/ia32: Leave NULL selector values 0~3 as is
2024-11-26 18:45 [PATCH v4 1/1] x86/ia32: Leave NULL selector values 0~3 as is Xin Li (Intel)
@ 2024-12-12 18:44 ` Xin Li
2025-02-14 6:56 ` Xin Li
2025-02-27 21:54 ` [tip: x86/asm] x86/ia32: Leave NULL selector values 0~3 unchanged tip-bot2 for Xin Li (Intel)
1 sibling, 1 reply; 6+ messages in thread
From: Xin Li @ 2024-12-12 18:44 UTC (permalink / raw)
To: linux-kernel
Cc: tglx, mingo, bp, dave.hansen, x86, hpa, andrew.cooper3, brgerst,
ebiederm
On 11/26/2024 10:45 AM, Xin Li (Intel) wrote:
> The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
> and 1 of a segment selector, i.e., the RPL bits, are NOT used to index
> GDT, selector values 0~3 all point to the NULL descriptor, thus values
> 0, 1, 2 and 3 are all valid NULL selector values.
>
> When a NULL selector value is to be loaded into a segment register,
> reload_segments() sets its RPL bits. Later IRET zeros ES, FS, GS, and
> DS segment registers if any of them is found to have any nonzero NULL
> selector value. The two operations offset each other to actually effect
> a nop.
>
> Besides, zeroing of RPL in NULL selector values is an information leak
> in pre-FRED systems as userspace can spot any interrupt/exception by
> loading a nonzero NULL selector, and waiting for it to become zero.
> But there is nothing software can do to prevent it before FRED.
>
> ERETU, the only legit instruction to return to userspace from kernel
> under FRED, by design does NOT zero any segment register to avoid this
> problem behavior.
>
> As such, leave NULL selector values 0~3 as is.
Hi Andrew,
Do you have any more comments?
Thanks!
Xin
>
> Do the same on 32-bit kernel as well.
>
> Signed-off-by: Xin Li (Intel) <xin@zytor.com>
> ---
>
> Changes since v3:
> * Rename usrseg() to fixup_rpl() to match its intent (Andrew Cooper).
> * A few comment improvements (Andrew Cooper).
>
> Changes since v2:
> * No, don't zero non-zero NULL selector values, essentially revert
> to v1 (Andrew Cooper).
>
> Changes since v1:
> * Normalize non-zero NULL selector values to 0 (Eric W. Biederman).
> * Apply the same normalization logic in a 32bit kernel (Eric W.
> Biederman).
> ---
> arch/x86/kernel/signal_32.c | 62 +++++++++++++++++++++++++------------
> 1 file changed, 43 insertions(+), 19 deletions(-)
>
> diff --git a/arch/x86/kernel/signal_32.c b/arch/x86/kernel/signal_32.c
> index ef654530bf5a..1e275268d256 100644
> --- a/arch/x86/kernel/signal_32.c
> +++ b/arch/x86/kernel/signal_32.c
> @@ -33,25 +33,55 @@
> #include <asm/smap.h>
> #include <asm/gsseg.h>
>
> +/*
> + * The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
> + * and 1 of a segment selector, i.e., the RPL bits, are NOT used to index
> + * GDT, selector values 0~3 all point to the NULL descriptor, thus values
> + * 0, 1, 2 and 3 are all valid NULL selector values.
> + *
> + * However IRET zeros ES, FS, GS, and DS segment registers if any of them
> + * is found to have any nonzero NULL selector value, which can be used by
> + * userspace in pre-FRED systems to spot any interrupt/exception by loading
> + * a nonzero NULL selector and waiting for it to become zero. Before FRED
> + * there is nothing software can do to prevent such an information leak.
> + *
> + * ERETU, the only legit instruction to return to userspace from kernel
> + * under FRED, by design does NOT zero any segment register to avoid this
> + * problem behavior.
> + *
> + * As such, leave NULL selector values 0~3 as is.
> + */
> +static inline u16 fixup_rpl(u16 sel)
> +{
> + return sel <= 3 ? sel : sel | 3;
> +}
> +
> #ifdef CONFIG_IA32_EMULATION
> #include <asm/unistd_32_ia32.h>
>
> static inline void reload_segments(struct sigcontext_32 *sc)
> {
> - unsigned int cur;
> + u16 cur;
>
> + /*
> + * Reload fs and gs if they have changed in the signal
> + * handler. This does not handle long fs/gs base changes in
> + * the handler, but does not clobber them at least in the
> + * normal case.
> + */
> savesegment(gs, cur);
> - if ((sc->gs | 0x03) != cur)
> - load_gs_index(sc->gs | 0x03);
> + if (fixup_rpl(sc->gs) != cur)
> + load_gs_index(fixup_rpl(sc->gs));
> savesegment(fs, cur);
> - if ((sc->fs | 0x03) != cur)
> - loadsegment(fs, sc->fs | 0x03);
> + if (fixup_rpl(sc->fs) != cur)
> + loadsegment(fs, fixup_rpl(sc->fs));
> +
> savesegment(ds, cur);
> - if ((sc->ds | 0x03) != cur)
> - loadsegment(ds, sc->ds | 0x03);
> + if (fixup_rpl(sc->ds) != cur)
> + loadsegment(ds, fixup_rpl(sc->ds));
> savesegment(es, cur);
> - if ((sc->es | 0x03) != cur)
> - loadsegment(es, sc->es | 0x03);
> + if (fixup_rpl(sc->es) != cur)
> + loadsegment(es, fixup_rpl(sc->es));
> }
>
> #define sigset32_t compat_sigset_t
> @@ -105,18 +135,12 @@ static bool ia32_restore_sigcontext(struct pt_regs *regs,
> regs->orig_ax = -1;
>
> #ifdef CONFIG_IA32_EMULATION
> - /*
> - * Reload fs and gs if they have changed in the signal
> - * handler. This does not handle long fs/gs base changes in
> - * the handler, but does not clobber them at least in the
> - * normal case.
> - */
> reload_segments(&sc);
> #else
> - loadsegment(gs, sc.gs);
> - regs->fs = sc.fs;
> - regs->es = sc.es;
> - regs->ds = sc.ds;
> + loadsegment(gs, fixup_rpl(sc.gs));
> + regs->fs = fixup_rpl(sc.fs);
> + regs->es = fixup_rpl(sc.es);
> + regs->ds = fixup_rpl(sc.ds);
> #endif
>
> return fpu__restore_sig(compat_ptr(sc.fpstate), 1);
>
> base-commit: 6ff908de1eafb53f31db75d929b7566a87847d2d
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH v4 1/1] x86/ia32: Leave NULL selector values 0~3 as is
2024-12-12 18:44 ` Xin Li
@ 2025-02-14 6:56 ` Xin Li
2025-02-14 14:01 ` Andrew Cooper
0 siblings, 1 reply; 6+ messages in thread
From: Xin Li @ 2025-02-14 6:56 UTC (permalink / raw)
To: linux-kernel
Cc: tglx, mingo, bp, dave.hansen, x86, hpa, andrew.cooper3, brgerst,
ebiederm
On 12/12/2024 10:44 AM, Xin Li wrote:
> On 11/26/2024 10:45 AM, Xin Li (Intel) wrote:
>> The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
>> and 1 of a segment selector, i.e., the RPL bits, are NOT used to index
>> GDT, selector values 0~3 all point to the NULL descriptor, thus values
>> 0, 1, 2 and 3 are all valid NULL selector values.
>>
>> When a NULL selector value is to be loaded into a segment register,
>> reload_segments() sets its RPL bits. Later IRET zeros ES, FS, GS, and
>> DS segment registers if any of them is found to have any nonzero NULL
>> selector value. The two operations offset each other to actually effect
>> a nop.
>>
>> Besides, zeroing of RPL in NULL selector values is an information leak
>> in pre-FRED systems as userspace can spot any interrupt/exception by
>> loading a nonzero NULL selector, and waiting for it to become zero.
>> But there is nothing software can do to prevent it before FRED.
>>
>> ERETU, the only legit instruction to return to userspace from kernel
>> under FRED, by design does NOT zero any segment register to avoid this
>> problem behavior.
>>
>> As such, leave NULL selector values 0~3 as is.
>
> Hi Andrew,
>
> Do you have any more comments?
Hi Andrew,
Are you okay to give a review-by to this patch?
Thanks!
Xin
>>
>> Do the same on 32-bit kernel as well.
>>
>> Signed-off-by: Xin Li (Intel) <xin@zytor.com>
>> ---
>>
>> Changes since v3:
>> * Rename usrseg() to fixup_rpl() to match its intent (Andrew Cooper).
>> * A few comment improvements (Andrew Cooper).
>>
>> Changes since v2:
>> * No, don't zero non-zero NULL selector values, essentially revert
>> to v1 (Andrew Cooper).
>>
>> Changes since v1:
>> * Normalize non-zero NULL selector values to 0 (Eric W. Biederman).
>> * Apply the same normalization logic in a 32bit kernel (Eric W.
>> Biederman).
>> ---
>> arch/x86/kernel/signal_32.c | 62 +++++++++++++++++++++++++------------
>> 1 file changed, 43 insertions(+), 19 deletions(-)
>>
>> diff --git a/arch/x86/kernel/signal_32.c b/arch/x86/kernel/signal_32.c
>> index ef654530bf5a..1e275268d256 100644
>> --- a/arch/x86/kernel/signal_32.c
>> +++ b/arch/x86/kernel/signal_32.c
>> @@ -33,25 +33,55 @@
>> #include <asm/smap.h>
>> #include <asm/gsseg.h>
>> +/*
>> + * The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
>> + * and 1 of a segment selector, i.e., the RPL bits, are NOT used to
>> index
>> + * GDT, selector values 0~3 all point to the NULL descriptor, thus
>> values
>> + * 0, 1, 2 and 3 are all valid NULL selector values.
>> + *
>> + * However IRET zeros ES, FS, GS, and DS segment registers if any of
>> them
>> + * is found to have any nonzero NULL selector value, which can be
>> used by
>> + * userspace in pre-FRED systems to spot any interrupt/exception by
>> loading
>> + * a nonzero NULL selector and waiting for it to become zero. Before
>> FRED
>> + * there is nothing software can do to prevent such an information leak.
>> + *
>> + * ERETU, the only legit instruction to return to userspace from kernel
>> + * under FRED, by design does NOT zero any segment register to avoid
>> this
>> + * problem behavior.
>> + *
>> + * As such, leave NULL selector values 0~3 as is.
>> + */
>> +static inline u16 fixup_rpl(u16 sel)
>> +{
>> + return sel <= 3 ? sel : sel | 3;
>> +}
>> +
>> #ifdef CONFIG_IA32_EMULATION
>> #include <asm/unistd_32_ia32.h>
>> static inline void reload_segments(struct sigcontext_32 *sc)
>> {
>> - unsigned int cur;
>> + u16 cur;
>> + /*
>> + * Reload fs and gs if they have changed in the signal
>> + * handler. This does not handle long fs/gs base changes in
>> + * the handler, but does not clobber them at least in the
>> + * normal case.
>> + */
>> savesegment(gs, cur);
>> - if ((sc->gs | 0x03) != cur)
>> - load_gs_index(sc->gs | 0x03);
>> + if (fixup_rpl(sc->gs) != cur)
>> + load_gs_index(fixup_rpl(sc->gs));
>> savesegment(fs, cur);
>> - if ((sc->fs | 0x03) != cur)
>> - loadsegment(fs, sc->fs | 0x03);
>> + if (fixup_rpl(sc->fs) != cur)
>> + loadsegment(fs, fixup_rpl(sc->fs));
>> +
>> savesegment(ds, cur);
>> - if ((sc->ds | 0x03) != cur)
>> - loadsegment(ds, sc->ds | 0x03);
>> + if (fixup_rpl(sc->ds) != cur)
>> + loadsegment(ds, fixup_rpl(sc->ds));
>> savesegment(es, cur);
>> - if ((sc->es | 0x03) != cur)
>> - loadsegment(es, sc->es | 0x03);
>> + if (fixup_rpl(sc->es) != cur)
>> + loadsegment(es, fixup_rpl(sc->es));
>> }
>> #define sigset32_t compat_sigset_t
>> @@ -105,18 +135,12 @@ static bool ia32_restore_sigcontext(struct
>> pt_regs *regs,
>> regs->orig_ax = -1;
>> #ifdef CONFIG_IA32_EMULATION
>> - /*
>> - * Reload fs and gs if they have changed in the signal
>> - * handler. This does not handle long fs/gs base changes in
>> - * the handler, but does not clobber them at least in the
>> - * normal case.
>> - */
>> reload_segments(&sc);
>> #else
>> - loadsegment(gs, sc.gs);
>> - regs->fs = sc.fs;
>> - regs->es = sc.es;
>> - regs->ds = sc.ds;
>> + loadsegment(gs, fixup_rpl(sc.gs));
>> + regs->fs = fixup_rpl(sc.fs);
>> + regs->es = fixup_rpl(sc.es);
>> + regs->ds = fixup_rpl(sc.ds);
>> #endif
>> return fpu__restore_sig(compat_ptr(sc.fpstate), 1);
>>
>> base-commit: 6ff908de1eafb53f31db75d929b7566a87847d2d
>
>
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH v4 1/1] x86/ia32: Leave NULL selector values 0~3 as is
2025-02-14 6:56 ` Xin Li
@ 2025-02-14 14:01 ` Andrew Cooper
2025-02-19 7:55 ` Xin Li
0 siblings, 1 reply; 6+ messages in thread
From: Andrew Cooper @ 2025-02-14 14:01 UTC (permalink / raw)
To: Xin Li, linux-kernel
Cc: tglx, mingo, bp, dave.hansen, x86, hpa, brgerst, ebiederm
On 14/02/2025 6:56 am, Xin Li wrote:
> On 12/12/2024 10:44 AM, Xin Li wrote:
>> On 11/26/2024 10:45 AM, Xin Li (Intel) wrote:
>>> The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
>>> and 1 of a segment selector, i.e., the RPL bits, are NOT used to index
>>> GDT, selector values 0~3 all point to the NULL descriptor, thus values
>>> 0, 1, 2 and 3 are all valid NULL selector values.
>>>
>>> When a NULL selector value is to be loaded into a segment register,
>>> reload_segments() sets its RPL bits. Later IRET zeros ES, FS, GS, and
>>> DS segment registers if any of them is found to have any nonzero NULL
>>> selector value. The two operations offset each other to actually
>>> effect
>>> a nop.
>>>
>>> Besides, zeroing of RPL in NULL selector values is an information leak
>>> in pre-FRED systems as userspace can spot any interrupt/exception by
>>> loading a nonzero NULL selector, and waiting for it to become zero.
>>> But there is nothing software can do to prevent it before FRED.
>>>
>>> ERETU, the only legit instruction to return to userspace from kernel
>>> under FRED, by design does NOT zero any segment register to avoid this
>>> problem behavior.
>>>
>>> As such, leave NULL selector values 0~3 as is.
>>
>> Hi Andrew,
>>
>> Do you have any more comments?
>
> Hi Andrew,
>
> Are you okay to give a review-by to this patch?
Apologies.
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v4 1/1] x86/ia32: Leave NULL selector values 0~3 as is
2025-02-14 14:01 ` Andrew Cooper
@ 2025-02-19 7:55 ` Xin Li
0 siblings, 0 replies; 6+ messages in thread
From: Xin Li @ 2025-02-19 7:55 UTC (permalink / raw)
To: Andrew Cooper, linux-kernel
Cc: tglx, mingo, bp, dave.hansen, x86, hpa, brgerst, ebiederm
On 2/14/2025 6:01 AM, Andrew Cooper wrote:
> On 14/02/2025 6:56 am, Xin Li wrote:
>> On 12/12/2024 10:44 AM, Xin Li wrote:
>>> On 11/26/2024 10:45 AM, Xin Li (Intel) wrote:
>>>> The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
>>>> and 1 of a segment selector, i.e., the RPL bits, are NOT used to index
>>>> GDT, selector values 0~3 all point to the NULL descriptor, thus values
>>>> 0, 1, 2 and 3 are all valid NULL selector values.
>>>>
>>>> When a NULL selector value is to be loaded into a segment register,
>>>> reload_segments() sets its RPL bits. Later IRET zeros ES, FS, GS, and
>>>> DS segment registers if any of them is found to have any nonzero NULL
>>>> selector value. The two operations offset each other to actually
>>>> effect
>>>> a nop.
>>>>
>>>> Besides, zeroing of RPL in NULL selector values is an information leak
>>>> in pre-FRED systems as userspace can spot any interrupt/exception by
>>>> loading a nonzero NULL selector, and waiting for it to become zero.
>>>> But there is nothing software can do to prevent it before FRED.
>>>>
>>>> ERETU, the only legit instruction to return to userspace from kernel
>>>> under FRED, by design does NOT zero any segment register to avoid this
>>>> problem behavior.
>>>>
>>>> As such, leave NULL selector values 0~3 as is.
>>>
>>> Hi Andrew,
>>>
>>> Do you have any more comments?
>>
>> Hi Andrew,
>>
>> Are you okay to give a review-by to this patch?
>
> Apologies.
>
> Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
Thank you very much!
Thanks!
Xin
^ permalink raw reply [flat|nested] 6+ messages in thread
* [tip: x86/asm] x86/ia32: Leave NULL selector values 0~3 unchanged
2024-11-26 18:45 [PATCH v4 1/1] x86/ia32: Leave NULL selector values 0~3 as is Xin Li (Intel)
2024-12-12 18:44 ` Xin Li
@ 2025-02-27 21:54 ` tip-bot2 for Xin Li (Intel)
1 sibling, 0 replies; 6+ messages in thread
From: tip-bot2 for Xin Li (Intel) @ 2025-02-27 21:54 UTC (permalink / raw)
To: linux-tip-commits
Cc: Xin Li (Intel), Ingo Molnar, Andrew Cooper, Linus Torvalds,
Andy Lutomirski, Brian Gerst, Peter Zijlstra, x86, linux-kernel
The following commit has been merged into the x86/asm branch of tip:
Commit-ID: ad546940b5991d3e141238cd80a6d1894b767184
Gitweb: https://git.kernel.org/tip/ad546940b5991d3e141238cd80a6d1894b767184
Author: Xin Li (Intel) <xin@zytor.com>
AuthorDate: Tue, 26 Nov 2024 10:45:28 -08:00
Committer: Ingo Molnar <mingo@kernel.org>
CommitterDate: Thu, 27 Feb 2025 22:46:11 +01:00
x86/ia32: Leave NULL selector values 0~3 unchanged
The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
and 1 of a segment selector, i.e., the RPL bits, are NOT used to index
GDT, selector values 0~3 all point to the NULL descriptor, thus values
0, 1, 2 and 3 are all valid NULL selector values.
When a NULL selector value is to be loaded into a segment register,
reload_segments() sets its RPL bits. Later IRET zeros ES, FS, GS, and
DS segment registers if any of them is found to have any nonzero NULL
selector value. The two operations offset each other to actually effect
a nop.
Besides, zeroing of RPL in NULL selector values is an information leak
in pre-FRED systems as userspace can spot any interrupt/exception by
loading a nonzero NULL selector, and waiting for it to become zero.
But there is nothing software can do to prevent it before FRED.
ERETU, the only legit instruction to return to userspace from kernel
under FRED, by design does NOT zero any segment register to avoid this
problem behavior.
As such, leave NULL selector values 0~3 unchanged and close the leak.
Do the same on 32-bit kernel as well.
Signed-off-by: Xin Li (Intel) <xin@zytor.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Andrew Cooper <andrew.cooper3@citrix.com>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Brian Gerst <brgerst@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/20241126184529.1607334-1-xin@zytor.com
---
arch/x86/kernel/signal_32.c | 62 ++++++++++++++++++++++++------------
1 file changed, 43 insertions(+), 19 deletions(-)
diff --git a/arch/x86/kernel/signal_32.c b/arch/x86/kernel/signal_32.c
index ef65453..98123ff 100644
--- a/arch/x86/kernel/signal_32.c
+++ b/arch/x86/kernel/signal_32.c
@@ -33,25 +33,55 @@
#include <asm/smap.h>
#include <asm/gsseg.h>
+/*
+ * The first GDT descriptor is reserved as 'NULL descriptor'. As bits 0
+ * and 1 of a segment selector, i.e., the RPL bits, are NOT used to index
+ * GDT, selector values 0~3 all point to the NULL descriptor, thus values
+ * 0, 1, 2 and 3 are all valid NULL selector values.
+ *
+ * However IRET zeros ES, FS, GS, and DS segment registers if any of them
+ * is found to have any nonzero NULL selector value, which can be used by
+ * userspace in pre-FRED systems to spot any interrupt/exception by loading
+ * a nonzero NULL selector and waiting for it to become zero. Before FRED
+ * there was nothing software could do to prevent such an information leak.
+ *
+ * ERETU, the only legit instruction to return to userspace from kernel
+ * under FRED, by design does NOT zero any segment register to avoid this
+ * problem behavior.
+ *
+ * As such, leave NULL selector values 0~3 unchanged.
+ */
+static inline u16 fixup_rpl(u16 sel)
+{
+ return sel <= 3 ? sel : sel | 3;
+}
+
#ifdef CONFIG_IA32_EMULATION
#include <asm/unistd_32_ia32.h>
static inline void reload_segments(struct sigcontext_32 *sc)
{
- unsigned int cur;
+ u16 cur;
+ /*
+ * Reload fs and gs if they have changed in the signal
+ * handler. This does not handle long fs/gs base changes in
+ * the handler, but does not clobber them at least in the
+ * normal case.
+ */
savesegment(gs, cur);
- if ((sc->gs | 0x03) != cur)
- load_gs_index(sc->gs | 0x03);
+ if (fixup_rpl(sc->gs) != cur)
+ load_gs_index(fixup_rpl(sc->gs));
savesegment(fs, cur);
- if ((sc->fs | 0x03) != cur)
- loadsegment(fs, sc->fs | 0x03);
+ if (fixup_rpl(sc->fs) != cur)
+ loadsegment(fs, fixup_rpl(sc->fs));
+
savesegment(ds, cur);
- if ((sc->ds | 0x03) != cur)
- loadsegment(ds, sc->ds | 0x03);
+ if (fixup_rpl(sc->ds) != cur)
+ loadsegment(ds, fixup_rpl(sc->ds));
savesegment(es, cur);
- if ((sc->es | 0x03) != cur)
- loadsegment(es, sc->es | 0x03);
+ if (fixup_rpl(sc->es) != cur)
+ loadsegment(es, fixup_rpl(sc->es));
}
#define sigset32_t compat_sigset_t
@@ -105,18 +135,12 @@ static bool ia32_restore_sigcontext(struct pt_regs *regs,
regs->orig_ax = -1;
#ifdef CONFIG_IA32_EMULATION
- /*
- * Reload fs and gs if they have changed in the signal
- * handler. This does not handle long fs/gs base changes in
- * the handler, but does not clobber them at least in the
- * normal case.
- */
reload_segments(&sc);
#else
- loadsegment(gs, sc.gs);
- regs->fs = sc.fs;
- regs->es = sc.es;
- regs->ds = sc.ds;
+ loadsegment(gs, fixup_rpl(sc.gs));
+ regs->fs = fixup_rpl(sc.fs);
+ regs->es = fixup_rpl(sc.es);
+ regs->ds = fixup_rpl(sc.ds);
#endif
return fpu__restore_sig(compat_ptr(sc.fpstate), 1);
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-02-27 21:54 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-11-26 18:45 [PATCH v4 1/1] x86/ia32: Leave NULL selector values 0~3 as is Xin Li (Intel)
2024-12-12 18:44 ` Xin Li
2025-02-14 6:56 ` Xin Li
2025-02-14 14:01 ` Andrew Cooper
2025-02-19 7:55 ` Xin Li
2025-02-27 21:54 ` [tip: x86/asm] x86/ia32: Leave NULL selector values 0~3 unchanged tip-bot2 for Xin Li (Intel)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox