Hi,
tom1000000 wrote:
CAn the level 3 process CHANGE GS?
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.
Not quite - user-level code can't load a descriptor that has DPL != 3 (except for the null selector).
tom1000000 wrote:
eg
mov eax, ds
mov gs, eax
Does the ahove code create a GPF?
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:
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;
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