Helllo,
I am being extremely lazy and could probably work this question out for myself. However I'm sure there are some gurus who can tell me the answer of the top of their head.
Say the kernel puts a data selector with Prviledge Level 0 into GS.
Then you iret back to a level 3 process.
The level 3 process can't use GS to access data.
CAn the level 3 process CHANGE GS?
eg
mov eax, ds
mov gs, eax
Does the ahove code create a GPF?
I am pretty sure it would but want to make sure.
Thanks
Tom
A question about selectors
- Pype.Clicker
- Member
- Posts: 5964
- Joined: Wed Oct 18, 2006 2:31 am
- Location: In a galaxy, far, far away
- Contact:
Re:A question about selectors
the level 3 code can put whatever value it wish in whatever segment register it feels like. As long as it doesn't *use* the register for addressing, no exception is thrown, afaik.
Re:A question about selectors
Hi,
This poses a problem for my OS where GS is used as a fast way to find CPU information (and the other data segment registers are expected to be flat/unmodified), so I spent some time trying to figure out a way of preventing user-level code from changing GS or any other data segment register. This is mainly because I don't want to reload data segment registers every time the CPU changes from CPL=3 to CPL=0 - it's extra overhead that shouldn't ever be needed.
As always the most efficient way of doing anything is to do nothing. If I did nothing at all everything would work fine until someone tried some malicious code (or tried some buggy code that was very unlucky). This would be a big security hole - anyone could crash the entire OS with a couple bytes of dodgy code. IMHO doing nothing isn't an option.
I considered marking all DPL=3 descriptors as not present, so that there wouldn't be any valid DPL=3 descriptors that the user-level code can use to overwrite GS (or any other data segment register) with. This won't work because you have to have valid GDT entries for user-level CS and SS when the CPU returns to user-level code from the kernel.
So, which descriptors could malicious user-level code use? For my OS there's one data descriptor with DPL=3 which is used by DS, ES, FS and user-level SS. There's also the user-level CS descriptor, and the null descriptor.
If user-level code changes any data segment register to the null descriptor and then any software tries to use that data segment register, the CPU will generate a GPF. It's relatively easy to detect this and correct the effected data segment register within the GPF handler. This leaves the DPL=3 data descriptor and code descriptor.
To prevent "mov ax,cs; mov ds,ax; mov gs,ax" you have to use execute only code segments (readable code segments can be loaded into data segment registers). This also means that user-level code can't do something like "mov eax,[cs:somewhere]", which doesn't really matter.
Now the only security problem is the DPL=3 data segment register. For my OS DS, ES, FS and user-level SS are set to this descriptor anyway, so this only effects GS. Luckily, my CPU information structure is less than 1 page and it's limit is set to 4 Kb. Also the first page in all address spaces is always marked as "not present" (to catch bad pointers, etc). Therefore, if user-level code did "mov ax,ds; mov gs,ax" it will work, but when the kernel tries to use GS it will generate a page fault. The page fault handler detects this, corrects GS and returns (the kernel code continues without knowing anything was ever wrong).
SUMMARY
You can't prevent user-level code from messing up any data segment register/s, but you may be able to handle it gracefully (without adding any overhead) with some trickery.
Cheers,
Brendan
tom1000000 wrote: CAn the level 3 process CHANGE GS?
Not quite - user-level code can't load a descriptor that has DPL != 3 (except for the null selector).Pype.Clicker wrote: the level 3 code can put whatever value it wish in whatever segment register it feels like. As long as it doesn't *use* the register for addressing, no exception is thrown, afaik.
It depends on what DS is (but normally it'd work without any GPF). The easiest place to look for this is the entry for the MOV instruction in Intel's instruction set reference:tom1000000 wrote: eg
mov eax, ds
mov gs, eax
Does the ahove code create a GPF?
Code: Select all
IF DS, ES, FS, or GS is loaded with non-null selector;
THEN
IF segment selector index is outside descriptor table limits
OR segment is not a data or readable code segment
OR ((segment is a data or nonconforming code segment)
AND (both RPL and CPL > DPL))
THEN #GP(selector);
IF segment not marked present
THEN #NP(selector);
ELSE
SegmentRegister = segment selector;
SegmentRegister = segment descriptor;
FI;
FI;
IF DS, ES, FS, or GS is loaded with a null selector;
THEN
SegmentRegister = segment selector;
SegmentRegister = segment descriptor;
FI;
As always the most efficient way of doing anything is to do nothing. If I did nothing at all everything would work fine until someone tried some malicious code (or tried some buggy code that was very unlucky). This would be a big security hole - anyone could crash the entire OS with a couple bytes of dodgy code. IMHO doing nothing isn't an option.
I considered marking all DPL=3 descriptors as not present, so that there wouldn't be any valid DPL=3 descriptors that the user-level code can use to overwrite GS (or any other data segment register) with. This won't work because you have to have valid GDT entries for user-level CS and SS when the CPU returns to user-level code from the kernel.
So, which descriptors could malicious user-level code use? For my OS there's one data descriptor with DPL=3 which is used by DS, ES, FS and user-level SS. There's also the user-level CS descriptor, and the null descriptor.
If user-level code changes any data segment register to the null descriptor and then any software tries to use that data segment register, the CPU will generate a GPF. It's relatively easy to detect this and correct the effected data segment register within the GPF handler. This leaves the DPL=3 data descriptor and code descriptor.
To prevent "mov ax,cs; mov ds,ax; mov gs,ax" you have to use execute only code segments (readable code segments can be loaded into data segment registers). This also means that user-level code can't do something like "mov eax,[cs:somewhere]", which doesn't really matter.
Now the only security problem is the DPL=3 data segment register. For my OS DS, ES, FS and user-level SS are set to this descriptor anyway, so this only effects GS. Luckily, my CPU information structure is less than 1 page and it's limit is set to 4 Kb. Also the first page in all address spaces is always marked as "not present" (to catch bad pointers, etc). Therefore, if user-level code did "mov ax,ds; mov gs,ax" it will work, but when the kernel tries to use GS it will generate a page fault. The page fault handler detects this, corrects GS and returns (the kernel code continues without knowing anything was ever wrong).
SUMMARY
You can't prevent user-level code from messing up any data segment register/s, but you may be able to handle it gracefully (without adding any overhead) with some trickery.
Cheers,
Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Re:A question about selectors
No, it would first generate a compile error. There's no way you can fit eax into gs.tom1000000 wrote: eg
mov eax, ds
mov gs, eax
Does the ahove code create a GPF?
If you use ax, you can use it, of course you can. If you load it with an invalid value you will GPF. If you load it with a non-CPL3 segment, it GPF's. You could of course load them, then remove the CPL3 registers, and then jump to user code (very very ugly). This doesn't fix anything though, the user can still load 0 into them (which is always legal, you just can't use the segment before reloading it again).
Protect them in some way, use a state segment for proc/thread/process/etc.
Re:A question about selectors
Candy you are wrong, at least in the case of NASM. Eax DOES fit into GS . Read the NASM manual (mov instruction) for proof.Candy wrote:
No, it would first generate a compile error. There's no way you can fit eax into gs.
If you use ax, you can use it, of course you can. If you load it with an invalid value you will GPF. If you load it with a non-CPL3 segment, it GPF's. You could of course load them, then remove the CPL3 registers, and then jump to user code (very very ugly). This doesn't fix anything though, the user can still load 0 into them (which is always legal, you just can't use the segment before reloading it again).
Protect them in some way, use a state segment for proc/thread/process/etc.
Re:A question about selectors
Code: Select all
8C / r MOV r/m16,Sreg** Move segment register to r/m16
8E / r MOV Sreg,r/m16** Move r/m16 to segment register
It doesn't matter what the NASM manual says, the Intel manuals are the definitive text. NASM encodes 'mov gs, eax' as 'mov gs, ax', presumably by assuming your code is wrong. There is NO way to encode 'mov gs, eax' or 'mov eax, gs' even with size prefixes.
NASM's disassembler assumes eax in 32-bit mode if it doesn't see a size prefix. However that doesn't mean that's what is actually happening.
Code: Select all
0x66 0x8C 0xE8
0x8C 0xE8
Re:A question about selectors
If you would be so kind as to explain to me how you cram the 32 bits of the EAX register into a 16-bit selector? The only solution would be that it implicitly used AX (which might work), or that it'd explicitly used AX (which should usually do what you want). It uses it explicitly, as Curufir explained, and NASM does indeed assume you want ax.tom1000000 wrote: Candy you are wrong, at least in the case of NASM. Eax DOES fit into GS . Read the NASM manual (mov instruction) for proof.
Try disassembling the code. Not even an operand size prefix can help you with this.