Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Protection rings within applications

18 views
Skip to first unread message

Marven Lee

unread,
Apr 5, 2012, 6:04:46 AM4/5/12
to
*Decided to cross-post to comp.arch as I've seen user-mode protection
rings previously mentioned and all the current talk of user-mode
interrupts and signal handling in the M68k thread is somewhat related,
so it might be of interest.

I've been thinking of splitting user processes into 2 or more protection
rings in my OS. This might be useful for some kind of virtualization
or running a shell in the more privileged part of a process and commands
within the least privileged ring of a process, possibly dividing the least
privileged ring into multiple sandboxes.

I've always assumed that privilege levels above user processes were
just slightly less privileged parts of the kernel. I guess that is how
the driver ring of OS/2 worked. I'm not sure how the rings of VMS
works, whether the 3 more privileged rings all global or if the supervisor
ring is per process or the VMS equivalent of a process?

So I've thought of some ways to implement application protection rings
and sandboxes using either segmentation or paging.

With segmentation on x86 it is relatively easy to split a process into two
rings using PL2 and PL3. If user mode spans from 0gb to 2gb then it is
possible to set the PL2 and PL3 code and data rings as:

PL0 code and data - base: 0gb limit: 4gb (kernel)
PL2 code and data - base: 0gb limit 2gb (user)
PL3 code and data - base: 0gb limit 1gb (user)

Then add a call_monitor() trap gate to call into the PL2 ring and
ensure the iopl field in the eflags register is 0.

A better option would be to use only PL3 segments for the two
user rings and adjust the base and limit of the PL3 segments using
two system calls, call_monitor() and return(). These could be
implemented as signals, with the addition of a new signal, sigmonitor.
All other unmasked signals would trap into the more privileged ring
so preemption would be possible.

int call_monitor (int call_idx, void *args);
void _sigreturn (&ret_context);

call_idx could be passed to the signal handler in the
siginfo->si_code field and a pointer to the args in the
siginfo->si_value.sival_ptr.

call_monitor() and other signals would expand the PL3 segment to the
full 0-2gb, obviously switch stacks and return into the signal handler.
Only the call_monitor() system call would be allowed from the
least privileged ring.

Returning from a signal via _sigreturn() would do the opposite,
setting the base and limit of the segment to 0-1gb and returning to
the stack within this ring.

For more flexibility ret_context can hold a base and limit value
and on each call to _sigreturn() the PL3 segment base and limits
can be adjusted. So the least privileged ring doesn't have to
be from 0-1gb, it can be from 64mb to 128mb for example.
The lower 1gb portion of the address space could be split into
several sandboxes and _sigreturn() would be used to switch to
a particular sandbox. The address space could be layed out
like this:

Monitor (segment base: 0 limit: 2gb)
...
Sandbox 2 (segment base: 128mb : limit 64mb)
Sandbox 2 (segment base: 64mb : limit 64mb)
Sandbox 1 (segment base: 0mb : limit 64mb)

The monitor would have to implement copyin() and copyout() functions
to access the data in the less privileged ring. These would have to catch
sigsegv signals using setjmp()/longjmp() for example.

As not every CPU has segmentation then to make it more portable
paging alone can be used to implement the protection rings in user
mode. Using 2 page directories per process it is possible to implement
similar protection rings:

Page Dir 1 - maps user 0 - 2gb , kernel 2-4gb
Page Dir 2 - maps user 0 - 1gb , kernel 2-4gb

Again two system calls, call_monitor() and _sigreturn() are used to
transfer between rings by switching page directories. Of course this
is slower than using just segments or altering the segment base and
limits. The page directory entries of the 2nd page directory need to
be altered whenever the base and limit of a sandbox changes. Also the
granularity of the sandbox is limited to 4mb or whatever number
of pages a page table holds.

The ret_context of _sigreturn() could have a relocation flag that indicates
that the pages of a sandbox should always be mapped starting at address
zero. For example if the sandbox exists between 16mb to 20mb, the
relocation flag would map it between 0mb to 4mb in the sandbox page
directory. That way addresses would all be relative to 0 from within the
sandbox.

I've read that user-mode Linux did something similar to some of the above,
protecting the guest OS by restricting the segment limits of guest OS
processes to 1GB and supporting a page directory per guest OS
process.

It's a pity x86-64 long mode doesn't support segmentation, It could
have allowed many large 4GB+ sandboxes in a single address space.
Perhaps a mode a bit like virtual-8086 mode could have been added
with a single base and limit.


--
Marv

Antoine Leca

unread,
Apr 5, 2012, 7:56:12 AM4/5/12
to
Marven Lee wrote:
> I've always assumed that privilege levels above user processes were
> just slightly less privileged parts of the kernel.

This is correct, but you can _also_ consider CPL1/2 processes as
"over-priviledged processes." This is particularly true when you
consider IOPL, which allow(ed) you to fine-grain the upper level where
hardware-driving processes are actually running, or even discriminate
among them.


> With segmentation on x86 it is relatively easy to split a process into two
> rings using PL2 and PL3.
<snip a description of a typical 3-level iAPX286 Operating system/>


> As not every CPU has segmentation then to make it more portable

This implies that pagination is "more portable" than segmentation. In
fact there are two different partitions between CPU architectures, each
with two sets: one being "the CPUs with segmentation, or without", and
the other being "the CPUs with pagination (MMU), or without." The fun of
the i386 architecture (not iAPX286, not amd64) is that it ends being on
both cases in the "with" set, but this is an awkward consequence of the
history, not really a feature. In the general case, as soon as the CPU
is decided you end up being in one or the other set (i.e paginated or
segmented); and if you are real serious about been portable, you need to
support independently both segmentation and pagination, at the general
design level (i.e. not at the applicative level of the various Intel
architecture as you are doing.)
I would argue this make the architecture much more complex, too complex.


> It's a pity x86-64 long mode doesn't support segmentation,

Huh?
Last time I looked, in long mode, any code running in 16-bit or 32-bit
segments are using the segmentation features of the processor. This
allows any process to run segmented, as long as the address space it
needs is less than (or equal to) 4GB. Which I still consider an awful
big address space for most processes, particularly when the interest is
put on hardware control or sandboxing, where having more than 2 levels
of protection might matter (this is in opposition to number crunching or
database management which are typical use of multi-TB-address-space
process, but where inter-process protection is not as much mandatory.)


> It could
> have allowed many large 4GB+ sandboxes in a single address space.
> Perhaps a mode a bit like virtual-8086 mode could have been added
> with a single base and limit.

Vanderpool and Pacifica do exactly that.


Antoine

Rod Pemberton

unread,
Apr 5, 2012, 9:16:30 AM4/5/12
to
"Marven Lee" <marv...@gmail.com> wrote in message
news:9u593l...@mid.individual.net...
...

> It's a pity x86-64 long mode doesn't support segmentation, It could
> have allowed many large 4GB+ sandboxes in a single address space.
> Perhaps a mode a bit like virtual-8086 mode could have been added
> with a single base and limit.
>

For x86-64, didn't Ring 1 and Ring 2 get tossed out also ... ?

I'm not sure about that and am not going to look it up right now.


Rod Pemberton


James Harris

unread,
Apr 5, 2012, 5:17:39 PM4/5/12
to
On Apr 5, 11:04 am, "Marven Lee" <marve...@gmail.com> wrote:

...

> I've been thinking of splitting user processes into 2 or more protection
> rings in my OS. This might be useful for some kind of virtualization
> or running a shell in the more privileged part of a process and commands
> within the least privileged ring of a process, possibly dividing the least
> privileged ring into multiple sandboxes.

I thought of something similar but eventually decided against it for
an OS design because

* not all architectures support more than two levels so you could lock
yourself in to those that do

* x86 PL=2 is a supervisor level and may have access to things you
don't want it to

Maybe you could keep the concept because you can segregate PL=3
routines and yet allow them to share resources under your control
using, I think, the LDT and page table privileges.

James

BGB

unread,
Apr 6, 2012, 3:05:56 PM4/6/12
to
not that I remember.

people don't really use rings 1 or 2, but probably there is not much to
gain by getting rid of them either.


I am partly in the camp though that sort of half wishes for 64 bit
segmentation (among a few other things), but am still faced with the
"even if it were implemented, would there be any common OS support or
ability to use it?" issue (a person would be probably better off just
faking it in userspace...).


things that were dropped IIRC:
segmentation (partial), still works for 32-bit stuff, but base/limit
doesn't work for 64-bits (partial exception for FS and GS, which have
special instructions);
TSS (partial), only does ring transitions and similar, can't be used for
task-switching;
single-byte INC/DEC forms (they became REX);
several misc instructions (mostly for BCD and similar), which apparently
Intel re-added with a flag to allow them;
...

there were also some other changes:
one of the [Abs32] forms was changed to [RIP+Rel32], forcing the second
(initially redundant) Abs32 form to be used instead;
...


in all, it could have been worse, although it does leave a little bit of
a "wish list" as well.



as for the original topic:
yes, a person can do this;
however, the rings may give access to certain privileged instructions,
so annoyingly are not as good for application security;
secondarily, they are not really a good fit for many security tasks;
...

apart from something like a compiler-enforced or runtime-enforced
security model, there is currently no good way to handle this.

for 64-bits though, something akin to "native client" could be done
though, with a special 32-bit sandbox being created, along with
validation to prove that the code doesn't leave the sandbox (or expose
weaknesses that could allow breaking out of the sandbox).

this would be essentially:
prove that the code can't itself access anything outside the sandbox and
can't exploit buffer-overflows or similar to escape the sandbox (a
little harder, but should be possible, one partial option being to
disallow "ret").


or such...

Marven Lee

unread,
Apr 9, 2012, 2:31:19 AM4/9/12
to

Antoine Leca wrote:
Marven Lee wrote:
>> I've always assumed that privilege levels above user processes were
>> just slightly less privileged parts of the kernel.
>
> This is correct, but you can _also_ consider CPL1/2 processes as
> "over-priviledged processes." This is particularly true when you
> consider IOPL, which allow(ed) you to fine-grain the upper level where
> hardware-driving processes are actually running, or even discriminate
> among them.

"Over-privileged processes" seems to be a good way of describing
them. I hadn't thought of protection rings like that, only as less
privileged kernel rings.


>> It's a pity x86-64 long mode doesn't support segmentation,
>
> Huh?
> Last time I looked, in long mode, any code running in 16-bit or 32-bit
> segments are using the segmentation features of the processor. This
> allows any process to run segmented, as long as the address space it
> needs is less than (or equal to) 4GB.

I thought segments were only available in legacy 32-bit mode and not
the 32-bit mode of long mode. Unless the OS switches between
long mode and legacy. I've not read the x86-64 docs in a long time.


>> It could have allowed many large 4GB+ sandboxes in a single address
>> space. Perhaps a mode a bit like virtual-8086 mode could have been
>> added with a single base and limit.
>
> Vanderpool and Pacifica do exactly that.

I've not read how Vanderpool and Pacifica work, I just assumed they'd
be terribly complicated. I don't think I've got a PC that supports them
either as my computers are quite old now. Tweaking my signal handling
code to either support two page directories or even segments seems
a lot easier :)


--
Marv

Joe keane

unread,
Apr 9, 2012, 5:08:39 PM4/9/12
to
In article <jlk5vg$v7g$1...@speranza.aioe.org>,
Rod Pemberton <do_no...@notemailnot.cmm> wrote:
>For x86-64, didn't Ring 1 and Ring 2 get tossed out also ... ?

No but all the programers said

'*please* make it *stop*!'
Message has been deleted

Marven Lee

unread,
Apr 13, 2012, 5:49:15 AM4/13/12
to
Morten Reistad wrote:
> Another path is the message-passing monitor QNX, where the core,
> kernel stuff is just a memory, task and paging manager plus message
> passing primitives; with everything like file systems, network
> stacks etc as priviliged user processes.

I've been rewriting my OS that I've not touched in years. It had
kernel-mode drivers using message passing functions that
were almost identical to that in AmigaOS. Recently I've been
tinkering with the code, stripping everything out and converting
it to a true microkernel. I've got the message passing working
but it doesn't do much yet as I've no drivers or servers.

The actual message passing is still similar to the Amiga's
message synchronization, PutMsg, GetMsg, ReplyMsg and Wait
but with additional ReadMsg / WriteMsg functions to transfer
the data between processes. The code seems to work,
I just need to get some servers/drivers written.

Anyway, I was thinking of using the first 31 user IDs as
protection rings, with the 32nd and above user IDs belonging
to normal users. Each ring will have a 32-bit permissions
bitmap of what other rings a process can receive messages
from. My thinking was that protection rings within a user
process would compliment these inter-process/user rings.

PS: I'm not sure how much of the memory management was
in the QNX kernel. IIRC QNX uses an interrupt model
kernel with a single kernel stack per processor. That's fine
for synchronization primitives in a microkernel and message
passing can be preempted and restarted, presumably by
restarting the message copying code from a fixed point
upon returning from an interrupt. Perhaps page tables
are maintained by the kernel but the more complicated
VM structures are maintained by the process manager.
Just a guess.

--
Marv


0 new messages