In a 64 bit program the selector:offset used to get the stack protector is fs:0x28, where fs=0. This poses no problem because in 64 bit we have the MSR fs_base (which is set to point to the TLS) and the GDT is completely ignored.
But with 32 bit program the stack protector is read from gs:0x14. Running over a 64 bit system we have gs=0x63, on a 32 bit system gs=0x33. Here there are no MSRs because they were introduced in x86_64, so the GDT plays an important role here.
Dissecting this values we get for both cases a RPL=3 (which was expected), the descriptor table selector indicates GDT (LDT is not used in linux) and the selector points to the entry with index 12 for 64 bits and index 6 for 32 bits.
Using a kernel module I was able to check that this entry in 64-bit linux is NULL! So I don't understand how the address of the TLS is resolved.
The relevant part of the kernel module is the following:
void gdtread()
{
struct desc_ptr gdtr;
seg_descriptor* gdt_entry = NULL;
uint16_t tr;
int i;
asm("str %0" : "=m"(tr));
native_store_gdt(&gdtr); // equiv. to asm("sgdt %0" : "=m"(gdtr));
printk("GDT address: 0x%px, GDT size: %d bytes = %i entries\n",
(void*)gdtr.address, gdtr.size + 1, (gdtr.size + 1) / 8);
gdt_entry = (seg_descriptor*)gdtr.address;
for(i = 0; i < (gdtr.size + 1) / 8; i++)
{
if(tr >> 3 == i)
printk("Entry #%i:\t<--- TSS (RPL = %i)", i, tr & 3);
else
printk("Entry #%i:", i);
if(!((uint64_t*)gdt_entry)[i])
{
printk("\tNULL");
continue;
}
if(gdt_entry[i].s)
user_segment_desc(&gdt_entry[i]);
else
system_segment_desc((sys_seg_descriptor*)&gdt_entry[i++]);
}
}
Which outputs the following on a 64-bit system:
[ 3817.191065] GDT address: 0xfffffe0000001000, GDT size: 128 bytes = 16 entries
[ 3817.191073] Entry #0:
[ 3817.191075] NULL
[ 3817.191078] Entry #1:
[ 3817.191081] Raw: 0x00cf9b000000ffff
[ 3817.191084] Base: 0x00000000
[ 3817.191088] Limit: 0xfffff
[ 3817.191091] Flags: 0xc09b
[ 3817.191096] Type = 0xb (Code, non conforming, readable, accessed)
[ 3817.191100] S = 0 (user)
[ 3817.191103] DPL = 0
[ 3817.191105] P = 1 (present)
[ 3817.191109] AVL = 0
[ 3817.191112] L = 0 (legacy mode)
[ 3817.191115] D/B = 1
[ 3817.191118] G = 1 (KiB)
[ 3817.191121] Entry #2:
[ 3817.191124] Raw: 0x00af9b000000ffff
[ 3817.191127] Base: 0x00000000
[ 3817.191130] Limit: 0xfffff
[ 3817.191133] Flags: 0xa09b
[ 3817.191137] Type = 0xb (Code, non conforming, readable, accessed)
[ 3817.191141] S = 0 (user)
[ 3817.191144] DPL = 0
[ 3817.191146] P = 1 (present)
[ 3817.191149] AVL = 0
[ 3817.191152] L = 1 (long mode)
[ 3817.191155] D/B = 0
[ 3817.191157] G = 1 (KiB)
[ 3817.191160] Entry #3:
[ 3817.191163] Raw: 0x00cf93000000ffff
[ 3817.191166] Base: 0x00000000
[ 3817.191169] Limit: 0xfffff
[ 3817.191171] Flags: 0xc093
[ 3817.191175] Type = 0x3 (Data, expand down, writable, accessed)
[ 3817.191178] S = 0 (user)
[ 3817.191181] DPL = 0
[ 3817.191183] P = 1 (present)
[ 3817.191186] AVL = 0
[ 3817.191189] L = 0
[ 3817.191191] D/B = 1
[ 3817.191194] G = 1 (KiB)
[ 3817.191197] Entry #4:
[ 3817.191199] Raw: 0x00cffb000000ffff
[ 3817.191202] Base: 0x00000000
[ 3817.191205] Limit: 0xfffff
[ 3817.191207] Flags: 0xc0fb
[ 3817.191211] Type = 0xb (Code, non conforming, readable, accessed)
[ 3817.191214] S = 0 (user)
[ 3817.191217] DPL = 3
[ 3817.191219] P = 1 (present)
[ 3817.191222] AVL = 0
[ 3817.191224] L = 0 (legacy mode)
[ 3817.191227] D/B = 1
[ 3817.191230] G = 1 (KiB)
[ 3817.191233] Entry #5:
[ 3817.191235] Raw: 0x00cff3000000ffff
[ 3817.191238] Base: 0x00000000
[ 3817.191241] Limit: 0xfffff
[ 3817.191243] Flags: 0xc0f3
[ 3817.191246] Type = 0x3 (Data, expand down, writable, accessed)
[ 3817.191250] S = 0 (user)
[ 3817.191252] DPL = 3
[ 3817.191255] P = 1 (present)
[ 3817.191258] AVL = 0
[ 3817.191260] L = 0
[ 3817.191262] D/B = 1
[ 3817.191265] G = 1 (KiB)
[ 3817.191268] Entry #6:
[ 3817.191270] Raw: 0x00affb000000ffff
[ 3817.191273] Base: 0x00000000
[ 3817.191276] Limit: 0xfffff
[ 3817.191278] Flags: 0xa0fb
[ 3817.191281] Type = 0xb (Code, non conforming, readable, accessed)
[ 3817.191284] S = 0 (user)
[ 3817.191287] DPL = 3
[ 3817.191289] P = 1 (present)
[ 3817.191292] AVL = 0
[ 3817.191295] L = 1 (long mode)
[ 3817.191298] D/B = 0
[ 3817.191300] G = 1 (KiB)
[ 3817.191303] Entry #7:
[ 3817.191306] NULL
[ 3817.191308] Entry #8: <--- TSS (RPL = 0)
[ 3817.191312] Raw: 0x00000000fffffe0000008b0030004087
[ 3817.191316] Base: 0xfffffe0000003000
[ 3817.191321] Limit: 0x04087
[ 3817.191324] Flags: 0x008b
[ 3817.191327] Type = 0xb (Busy 64-bit TSS)
[ 3817.191331] S = 1 (system)
[ 3817.191333] DPL = 0
[ 3817.191336] P = 1 (present)
[ 3817.191339] AVL = 0
[ 3817.191341] L = 0
[ 3817.191344] D/B = 0
[ 3817.191347] G = 0 (B)
[ 3817.191349] Entry #10:
[ 3817.191352] NULL
[ 3817.191355] Entry #11:
[ 3817.191358] NULL
[ 3817.191360] Entry #12:
[ 3817.191362] NULL
[ 3817.191365] Entry #13:
[ 3817.191367] NULL
[ 3817.191369] Entry #14:
[ 3817.191372] NULL
[ 3817.191374] Entry #15:
[ 3817.191377] Raw: 0x0040f50000000000
[ 3817.191380] Base: 0x00000000
[ 3817.191382] Limit: 0x00000
[ 3817.191385] Flags: 0x40f5
[ 3817.191389] Type = 0x5 (Data, expand up, read only, accessed)
[ 3817.191392] S = 0 (user)
[ 3817.191395] DPL = 3
[ 3817.191397] P = 1 (present)
[ 3817.191400] AVL = 0
[ 3817.191403] L = 0
[ 3817.191405] D/B = 1
[ 3817.191408] G = 0 (B)
I haven't tried this module on a 32 bit system yet, but I'm on my way.
So, to make the question clear: how does the gs segment selector work in a 32-bit program running on a 64-bit linux kernel?