Ran into an interesting discussion recently where a forum user posted
a question I was unable to answer.
Can you help?
Basically, forum user stated with Segmented Memory Model, what stops
userland apps messing about with other userland apps?
For example, with a direct memory write such as mov 0x11, BadGuy where
0x11 is GoodGuy's residing program?
I couldn't for the life of me think of any Segmentation protection
mechanisms which stopped Ring 3 apps having full access to all of the
RAM inside Ring 3.
Whereas, in Paging, is there any additional protection mechanisms to
stop this? Please explain! It's driving me crazy...
Kind regards,
Catcalls
To access the memory, the segment must be defined, this means - update to G/LDT, this means - Ring 0.
This is how Win16 worked.
--
Maxim S. Shatskih
Windows DDK MVP
ma...@storagecraft.com
http://www.storagecraft.com
Thanks Maxim. That makes perfect sense.
> Ran into an interesting discussion recently where a forum user posted
> a question I was unable to answer.
>
> Can you help?
The options and permutations are complex and I'm not sure I've grasped
them all but I'll have a go. Someone else will hopefully correct
anything I get wrong.
> Basically, forum user stated with Segmented Memory Model, what stops
> userland apps messing about with other userland apps?
You can set up segments that give a user access to specific chunks of
memory. For example, say your data segment register, DS, points to
0x8000 and the segment it identifies goes on to 0x9fff. Then the
user's zero location would be 0x8000 and he couldn't access above
0x1fff in the data segment.
> For example, with a direct memory write such as mov 0x11, BadGuy where
> 0x11 is GoodGuy's residing program?
GoodGuy's data areas would not overlap those of BadGuys - unless they
wanted to share storage. In the example above, GoodGuy could have as
his data segment addresses 0x0040_0000 to 0x0040_3fff, say. Then
location zero in his data segment would be private to him.
> I couldn't for the life of me think of any Segmentation protection
> mechanisms which stopped Ring 3 apps having full access to all of the
> RAM inside Ring 3.
Check how selectors are made up and how they index into the two memory
descriptor tables: GDT and LDT. Also, a user program could form its
own selectors so it's important to keep private pl=3 descriptors out
of the GDT. Put them in the LDT and, in your scenario, give each user
a separate LDT.
> Whereas, in Paging, is there any additional protection mechanisms to
> stop this? Please explain! It's driving me crazy...
Paging translates addresses via the page tables. If only the kernel
can modify those tables they can be set up so that they only ever
point to private memory or specific shared space. When you switch to
another user change to using that new user's page tables. Keep the
kernel space present in all sets of page tables so the kernel can
always be called and access its data without having to alter page
structures first. (Once paging is enabled it controls kernel access
too.)
Intel segmentation has the disadvantage that segment ids are not part
of normal addresses so unless you are using segments just for the
basic memory areas - instructions, stack, data - you have to
explicitly load segment selectors which is a pain and has led to
complex memory models.
Paging has the disadvantages that much more memory is required to
control access and it doesn't range check pointers automatically.
On balance people have shied away from Intel/AMD general segmentation
and rely on paging. X86-64 has done away with general segmentation
altogether and retains only a vestigial form. So, its normal to stick
with paging. Then as long as your code range checks its pointers your
programs won't lose out.
James
Sure
> Basically, forum user stated with Segmented Memory Model, what stops
> userland apps messing about with other userland apps?
>
In each selector you can define the base and limit,
userland application cannot add its own selector, or use a lower ring
selector directly to access sensitive memory.
> For example, with a direct memory write such as mov 0x11, BadGuy where
> 0x11 is GoodGuy's residing program?
The simple solution is to provide different base to different
application.
However, see below.
>
> I couldn't for the life of me think of any Segmentation protection
> mechanisms which stopped Ring 3 apps having full access to all of the
> RAM inside Ring 3.
LDTR can be used so that each application don't see other.
>
> Whereas, in Paging, is there any additional protection mechanisms to
> stop this? Please explain! It's driving me crazy...
The funny thing is, you can have *BOTH* paging and segmentation.
The common practice, is resort to the simplest segmentation model -
flat.
This is mainly for the reason to easily port to other architectures,
where segmentation is not common.
However, there is nothing to stop you to, say, have a few dedicated
selector for different usages.
Linux, for example, use gs internally to access kernel data which have
its base pointed non-zero.
(and I think this is why x86-64 retain the base+limit for FS, GS)
By the way Intel seems decided to phase out segmentation support and
optimize for otherwise flat model.
segmentation may still exist for a while, like the ancient 8088
instructions, however they may not be optimized.
>
> Kind regards,
>
> Catcalls
AFAIK, the main use of FS and GS at this point is for using them to
point at thread-local storage and similar...
IIRC:
Win32 uses FS, and Win64 uses GS, in either case, they point to the TEB.
actually, they may well both exist at the same time, since I have before
made an observation:
apparently, with 32-bit processes (in Win64), both 32 and 64 bit code
exists in the same process, me suspecting that there is
some-as-of-yet-unknown mechanism to jump between 32 and 64-bit mode
in-process.
actually, I noted the above when checking for something else:
I was seeing if the PE/COFF images were direct-loaded into memory (by
Windows), and apparently they are (I guess for most sections apart from
maybe ".bss" RVA==file-offset...).
yeah, on Linux, GS is used, but from what I could tell by looking
online, there was no real consistent physical layout for what resides
there (apparently it depends on the particular version of the kernel or
similar...).
I think I saw something about TLS being stored at negative offsets (rel
GS), but I forget the specifics.
or such...
Probably just prefixed instructions. Look at wow64.dll disassembly.
looking around some more online, it seems to be via far calls and iretd...
I guess little prevents one from, say, building the mode-transition
thunks manually for their own uses, and then making mixed-mode programs,
but this could get nasty quickly...
or such...
That evokes interesting things to do, but i cannot provide any docs
on the subject nor i tried to disassemble the WOW lib.
As you know something worth to be read about WOW mechanism, please
share it.
Thankfully,
Cheers,
--
.::mrk::.
x64 Assembly Lab
http://sites.google.com/site/x64lab
not too much of direct value, but did find:
http://zachsaw.blogspot.com/2010/11/wow64-bug-getthreadcontext-may-return.html
http://www.nynaeve.net/?p=129
http://www.nynaeve.net/?p=131
and a long list:
http://www.nynaeve.net/index.php?cat=10&paged=2
and:
http://www.nynaeve.net/?page_id=67
...
one can look for anything else of relevance.
dunno of much better at this point, I have not found much, and one can
infer around the holes with their own knowledge and info from MSDN and
the Intel docs and similar...
or such...
In protected mode, everything has a segment selector. This is loaded
into the segment registers, cs, ds, es, fs, gs and ss. Every segment
selector points to the LDT Local Descriptor Table, or GDT Global
Descriptor Table entry. The descriptor entry contains information
about the type of segment, its base address in linear memory, and its
limit (how many bytes from that starting point in memory can be
accessed without causing a fault).
In this way, every process is isolated from every other process within
the same ring. If the OS sets it up properly, the rules imposed by
the CPU ensure that no program can ever read, write or execute
anything outside of its allocated resources.
- Rick C. Hodgin
AIUI ring 3 segment descriptors should be in the LDT. There's nothing
to stop a process creating its own selectors and, while most would
generate an exception on use, some could end up pointing to slots in
the GDT. If PL=3 descriptors are created only in the LDT and the LDT
is changed in each task switch there's no danger of a task seeing the
PL=3 descriptors of another process.
The GDT is fine for PL=0 selectors, though.
I've never tried it but it seems that the verr and verw instructions
can check whether a forged selector refers to a valid descriptor so
the process forging the selectors does not have to just take a stab in
the dark but can cycle through possible PL=3 selectors without fear of
being terminated.
http://pdos.csail.mit.edu/6.828/2006/readings/i386/VERR.htm
All the more reason for keeping PL=3 descriptors in separate LDTs.
Can anyone correct me on the above?
James
True.
> In this way, every process is isolated from every other process within
> the same ring.
Uh... Are you describing multi-core or single-core?
For x86, processes are only separated if the OS is implemented that way.
For 32-bit single-core, only one CS selector and one DS selector, etc., or
"process" is active at any point in time. I.e., processes are only isolated
if "something" is switching the relevant segments, e.g., via CS and DS
selectors, for each process *AND* each segment's memory region is restricted
to prevent overlap by setting the base and limit of the descriptors.
"something" is up to the OS implementor. It may switch or it may not.
> If the OS sets it up properly, the rules imposed by
> the CPU ensure that no program can ever read, write or execute
> anything outside of its allocated resources.
>
True, but...
The "allocated resources" of one program may be the same as another. x86
segmentation features allows sharing as well as isolation of varying
degrees. There can be much, little, or no sharing. There can be much,
little, or no isolation. E.g., for a novice OS, or an OS in the early
stages of development, or a single-user OS, there is no need for memory
protection. So, CS and DS can be setup just once to map all of memory and
never be changed. The same CS and DS can be used for the OS or for an
executing program. From the cpu's perspective, both of them together are a
"single process", i.e., same CS and DS. From your perspective, they are two
different processes, i.e., different activity being performed by each piece
of code.
Rod Pemberton
Any core. The OS is responsible for not creating overlapping memory
regions for processes (tasks) that are running, unless it's part of
the procedural requirements of the language, such as DLLs operating in
a single area of memory but being readable by all. But that's a
design trait, not a limitation or requirement of the CPU, just an
ability.
> For x86, processes are only separated if the OS is implemented that way.
Perhaps I wasn't wholly clear in that, but my next sentence above
indicated: "If the OS sets it up properly...." then that protection
mechanism is in place.
> For 32-bit single-core, only one CS selector and one DS selector, etc., or
> "process" is active at any point in time. I.e., processes are only isolated
> if "something" is switching the relevant segments, e.g., via CS and DS
> selectors, for each process *AND* each segment's memory region is restricted
> to prevent overlap by setting the base and limit of the descriptors.
> "something" is up to the OS implementor. It may switch or it may not.
>
> > If the OS sets it up properly, the rules imposed by
> > the CPU ensure that no program can ever read, write or execute
> > anything outside of its allocated resources.
>
> True, but...
>
> The "allocated resources" of one program may be the same as another.
> x86 segmentation features allows sharing as well as isolation of varying
> degrees. There can be much, little, or no sharing. There can be much,
> little, or no isolation. E.g., for a novice OS, or an OS in the early
> stages of development, or a single-user OS, there is no need for memory
> protection. So, CS and DS can be setup just once to map all of memory and
> never be changed. The same CS and DS can be used for the OS or for an
> executing program. From the cpu's perspective, both of them together are a
> "single process", i.e., same CS and DS. From your perspective, they are two
> different processes, i.e., different activity being performed by each piece
> of code.
Rod, I don't think the original poster's concerns were how it could be
setup to be shared, but rather how it could be setup to be a distinct
and non-overlapping protection mechanism, and without paging. That is
what I described.
- Rick C. Hodgin
These are design traits. It depends on how you want to implement it.
There is no right way or wrong way, but there are ways that lend
themselves more naturally to further expansion in a system design.
But if all you're building is a tiny kernel to run some embedded
process, then whatever works and is easy is the right way. Setting up
only a GDT can make the design much simpler.
It is only the concerns relating to fully extensible operating system
designs (for full and varied use by many) that come in and dictate
pretty straight-forwardly how it SHOULD be designed. You'll learn
that very quickly by thinking the matter through.
> I've never tried it but it seems that the verr and verw instructions
> can check whether a forged selector refers to a valid descriptor so
> the process forging the selectors does not have to just take a stab in
> the dark but can cycle through possible PL=3 selectors without fear of
> being terminated.
>
> http://pdos.csail.mit.edu/6.828/2006/readings/i386/VERR.htm
>
> All the more reason for keeping PL=3 descriptors in separate LDTs.
>
> Can anyone correct me on the above?
>
> James
The segment selector's descriptor backs into its own PL, so it doesn't
matter where they're located. It's just a design trait. Global or
local, it works the same in practice. And by convention, the way you
describe it is far more common, if not the rule. But an incredibly
simple embedded system design can enter protected mode once, setup the
selectors, and return to real mode and never alter them, and their
values will be honored for the remainder of the CPU's up-time. It's
what's called un-real mode, which is a real mode with a full 4GB limit
using the 32-bit regs.
- Rick C. Hodgin
If someone is building a tiny kernel and writing all the apps then
it's less of an issue but in the general case someone else may write
the apps so security in the kernel is desirable rather than just a
design choice.
My comment was that if selectors could be forged - i.e. made up by a
PL=3 task - then I was querying whether it was secure to leave PL=3
descriptors in the GDT.
> It is only the concerns relating to fully extensible operating system
> designs (for full and varied use by many) that come in and dictate
> pretty straight-forwardly how it SHOULD be designed. You'll learn
> that very quickly by thinking the matter through.
>
> > I've never tried it but it seems that the verr and verw instructions
> > can check whether a forged selector refers to a valid descriptor so
> > the process forging the selectors does not have to just take a stab in
> > the dark but can cycle through possible PL=3 selectors without fear of
> > being terminated.
>
> > http://pdos.csail.mit.edu/6.828/2006/readings/i386/VERR.htm
>
> > All the more reason for keeping PL=3 descriptors in separate LDTs.
>
> > Can anyone correct me on the above?
...
> The segment selector's descriptor backs into its own PL, so it doesn't
> matter where they're located. It's just a design trait. Global or
> local, it works the same in practice. And by convention, the way you
> describe it is far more common, if not the rule. But an incredibly
> simple embedded system design can enter protected mode once, setup the
> selectors, and return to real mode and never alter them, and their
> values will be honored for the remainder of the CPU's up-time. It's
> what's called un-real mode, which is a real mode with a full 4GB limit
> using the 32-bit regs.
Do you mean it's always/intrinsically safe to leave PL=3 descriptors
for other tasks in the GDT? If so how would you prevent them from
being referred to by tasks other than those to which they belong?
James
Yes. That was my point. The design of your OS reveals itself exactly to you based on your target needs. Simple needs, simple design. Complex needs, complex design. Middle needs, middle design.
> My comment was that if selectors could be forged - i.e. made up by a
> PL=3 task - then I was querying whether it was secure to leave PL=3
> descriptors in the GDT.
Of course they can be forged, but any ring-x code desiring to take input from some lesser privilege ring would always test for it, making it a non-issue in terms of illegal access. The CPU would allow it, but the required use of available security features designed specifically for that purpose would negate it.
> Do you mean it's always/intrinsically safe to leave PL=3 descriptors
> for other tasks in the GDT? If so how would you prevent them from
> being referred to by tasks other than those to which they belong?
If they're in the GDT, nothing would prevent any of them from being utilized by any equal level ring code as any process of the same ring can use any valid values assigned to that ring. This is one of the beauties of its design. Typically if you leave selectors in the GDT for PL=3 tasks they are read-only values assigned by the OS made easily visible for all processes, similar to what the /proc "device" is for Linux. There, the OS can maintain the clock, date, tick counts, CPU and memory information, processes running, CPU utilization, all kinds of stuff.
However, your original question was whether or not the memory could be isolated without using paging, and it can, even when using the GDT, by not having overlapping memory areas for the base / limit assigned in the descriptor, and by setting them up as code or data as execute-only or read-only, respectively. Can that memory still be used by any equal ring process? Yes. Can it be written to? Depending on how you set it up, yes or no. Is it as good as using the LDTs? Depending on your OS design, probably no, but possibly yes. You could, for example, when switching a task, also switch a GDT's descriptor from read-only to read-write before the task switches. Hideous, but possible.
The LDT removes any possibility of other tasks reading or writing data not assigned to it, save the several errata present on the x86 CPUs which cause some issues near segment limit boundaries, etc.
Having all user-level selectors assigned to their LDT will map only those descriptors to that task's LDT table, and if the OS set it all up properly, they will never overlap or interfere with anything else.
Again, these are all design considerations depending on the scope of your OS.
- Rick C. Hodgin