x86: PER_CPU segment improvements
cover x86: PER_CPU segment improvements
x86 sets up a per-cpu GDT entry so that vgetcpu can use LSL on it to determine the CPU number and node.
This series, in little baby steps, cleans up that code and sets the accessed and 32-bit flags on the segment.
The accessed bit prevents user code from setting the accessed bit on its own, and making the segment 32-bit prevents concerns about shenanigans involving CPU oddities with 16-bit data segments.
The latter isn't a real problem -- if it were a 16-bit read/write segment, it could be used to bypass espfix64, but fortunately RO segments can't be loaded into SS.
Andy Lutomirski (3):
x86,vdso: Change the PER_CPU segment to use struct desc_struct x86,vdso: Make the PER_CPU segment start out accessed x86,vdso: Make the PER_CPU segment 32 bits
arch/x86/kernel/vsyscall_64.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-)
Signature · 1 line
1.9.3
PATCH 1/3 x86,vdso: Change the PER_CPU segment to use struct desc_struct
This makes it easier to see what's going on. It produces exactly the same segment descriptor as the old code.
--- arch/x86/kernel/vsyscall_64.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-)
arch/x86/kernel/vsyscall_64.c +11 -7
@@ -289,7 +289,7 @@ sigsegv:*/ static void vsyscall_set_cpu (int cpu) { -unsigned long d; +struct desc_struct d;unsigned long node =0 ;#ifdef CONFIG_NUMA node =cpu_to_node (cpu);
@@ -298,13 +298,17 @@ static void vsyscall_set_cpu(int cpu)write_rdtscp_aux ((node <<12 ) | cpu);/* -* Store cpu number in limit so that it can be loaded quickly -* in user space in vgetcpu. (12 bits for the CPU and 8 bits for the node) +* Store cpu number in limit so that it can be loaded +* quickly in user space in vgetcpu. (12 bits for the CPU +* and 8 bits for the node) */ - d =0x0f40000000000ULL ; - d |= cpu; - d |= (node &0xf ) <<12 ; - d |= (node >>4 ) <<48 ; +memset (&d,0 ,sizeof (d)); + d.limit0 = cpu | ((node &0xf ) <<12 ); + d.limit = node >>4 ; + d.type =4 ;/* RO data, expand down */ + d.dpl =3 ;/* Visible to user code */ + d.s =1 ;/* Non a system segment */ + d.p =1 ;/* Present */ write_gdt_entry (get_cpu_gdt_table (cpu),GDT_ENTRY_PER_CPU , &d,DESCTYPE_S ); }
Signature · 1 line
1.9.3
PATCH 2/3 x86,vdso: Make the PER_CPU segment start out accessed
In reply to Andy LutomirskiThe first userspace attempt to read or write the PER_CPU segment will write the accessed bit to the GDT. This is visible to userspace using the LAR instruction.
Set the segment's accessed bit at boot to keep all userspace GDT access idempotent.
--- arch/x86/kernel/vsyscall_64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/x86/kernel/vsyscall_64.c +1 -1
@@ -305,7 +305,7 @@ static void vsyscall_set_cpu(int cpu)memset (&d,0 ,sizeof (d)); d.limit0 = cpu | ((node &0xf ) <<12 ); d.limit = node >>4 ; - d.type =4 ;/* RO data, expand down */ + d.type =5 ;/* RO data, expand down, accessed */ d.dpl =3 ;/* Visible to user code */ d.s =1 ;/* Non a system segment */ d.p =1 ;/* Present */
Signature · 1 line
1.9.3
PATCH 3/3 x86,vdso: Make the PER_CPU segment 32 bits
In reply to Andy LutomirskiIMO users ought not to be able to use 16-bit segments without using modify_ldt. Fortunately, it's impossible to break espfix64 by loading the PER_CPU segment into SS because it's a read-only segment, but marking it 32-bit seems less fragile.
--- arch/x86/kernel/vsyscall_64.c | 1 + 1 file changed, 1 insertion(+)
arch/x86/kernel/vsyscall_64.c +1 -0
@@ -309,6 +309,7 @@ static void vsyscall_set_cpu(int cpu) d.dpl =3 ;/* Visible to user code */ d.s =1 ;/* Non a system segment */ d.p =1 ;/* Present */ + d.d =1 ;/* 32-bit */ write_gdt_entry (get_cpu_gdt_table (cpu),GDT_ENTRY_PER_CPU , &d,DESCTYPE_S ); }
Signature · 1 line
1.9.3