Hi George,
["pro bono" day so I've got to get my *ss out the door, RSN]
On 11/21/2013 5:16 AM, George Neuner wrote:
> On Tue, 19 Nov 2013 16:49:10 -0700, Don Y<this@isnotme.com> wrote:
>
>> OK. But, did the *kernel* make the decision as to whether to
>> pass the message (IDL) on to the service (encoded in the
>> ticket) based on an examination of that field? Or, did the
>> kernel pass it on to the service and rely on the *server* to
>> decide "You aren't the right person"? (in addition to
>> "You don't have the right permission(s)"?)
>
> Yes and no. Remember, to make ANY system call requires a ticket.
>
> Normally, a process is created with a set of default tickets that
> enable it to access basic services (filesystem, network, etc.). The
> default tickets are held by the kernel and can be chosen by the parent
> when the process is created.
>
> The default ticket for any particular call is assumed unless the
> process explicitly presents a different one when the call is made.
>
> However, the kernel IPC mechanism doesn't validate the message other
> than to check that the server address on the ticket is good. E.g., if
> the process has no ticket for the socket service,, it will be
> prevented from even trying to contact the socket service.
Ticket encodes "server port" in a virtual sense. I.e., for a "name
service" analogy, the "server" portion would be "DNS" (not a specific
IP address). The system was responsible for locating and tracking
which "processor" was currently "hosting" that service.
(i.e., DNS might be "29" -- today! And, running on 10.10.2.27 -- NOW!)
If you needed a special ticket to use the socket service, how did you
talk to *any* service as you had no knowledge of what was "local" vs.
"remote"? I.e., that was the appeal of FLIP!
I.e., the kernel had to invoke the RPC if the service port IN YOUR
TICKET was located on a remote host. (recall Amoeba used smallish
processors originally. 16MB nominal size physical memory and
NO paging!)
>> A [Mach] "send once" right is a send right that is automatically
>> destroyed by the kernel when used. I.e., send *one* message,
>> no more.
>
> You can do similar with numbered use tickets/capabilities in Amoeba.
>
> Everything you can think of has an implementation analogue in either
> system. You're just hung up on kernel enforcement.
Exactly! -------------------------^^^^^^^^^^^^^^^^^^
You could implement some silly VM scheme whereby any task (thread)
could write anywhere in physical memory -- and, give the owner of
that piece of memory the power to allow or inhibit the write.
This would allow you to implement protected memory domains whereby
task A can't stomp on task B's memory.
It would give you an even finer grained (i.e., BETTER!) scheme than
current systems use! A task could opt to let another task write on
one *byte* and not the byte next to it! You could share objects
at whatever granularity you choose!
BUT, IT WOULD REQUIRE THE "owning" TASK TO PROCESS EACH INCOMING WRITE
ATTEMPT! If tasks all were well-behaved, not a problem -- each write
attempt would be a DESIRED one!
OTOH, a rogue/adversary could intentionally sit there doing "bad"
things. Your memory integrity won't br compromised because you
were implemented correctly. But, he's SPENDING YOUR DIME! His
actions are causing you to have to burn cycles invalidating his
accesses. You can't "block" his actions (in this hypothetical model).
So, while it is fine-grained and *could* give you great performance
advantages in a BENEVOLENT environment, it proves to be a vulnerability
in an environment where adversaries (or, just "buggy" programs) reside.
I can put a "perfect" firewall on my home internet connection. But,
that doesn't stop adversaries from consuming my incoming (and, as such,
outgoing) bandwidth! If that firewall executes *in* my host, then
I am burning CPU cycles AT SOMEONE ELSE'S BIDDING.
> E.g., you could design an Amoeba based system where all the system
> capabilities were held by [or accessible to] a "validation server" to
> which all client calls are directed. The validation server would
> authenticate the client's ticket and then hand off the "connection" to
> the appropriate server.
Perfect! *If* that validation server executes using the *client's*
resources! I.e., you present a (bogus) credential and *it* executes
in your time slot (e.g., as a library instead of a server). So, if
you want to waste YOUR time presenting bogus credentials, fine.
You don't tie up network resources or resources in the object's
server just to invalidate your request.
*That* is my goal. You and only you pay for your actions -- whether
they are careless mistakes or deliberate attacks! I.e., you can't
"steal" from the system.
Note any *server* that is used to do this can potentially be overwhelmed
by "non-benevolent" actors presenting it with bogus credentials KNOWING
that the server will have to spend CPU cycles (that it *could* have
spent on other, LEGITIMATE services for "benevolent" actors!)
"As is", I don't see any way of doing this in Amoeba. It was *intended*
to have the servers validate tickets/capabilities -- so the kernel
didn't have to understand "what they meant", just "where to send them".
>> Permissions aren't a "bit set" that
>> you can add to or subtract from. I.e., there's no limit to
>> the number of individual permissions implemented by a single
>> "Handle". Nor do the permissions associated with one Handle
>> have to be the same as the set supported for some other Handle
>> (of the same object type).
>
> Permissions in Amoeba aren't a "bit set" either ... unless you want
> them to be. You have a 32 bit field in the ticket to use however you
> want. The field can represent a bit map, or an enumerated value, or be
> divided into multiple fields.
>
> If you don't need to validate ticket holders, you've got another 96
> bits in the v2 user id and 2nd signature field to play with.
>
> There is no *practical* limit to the set of privileges an Amoeba
> capability can administer or that a ticket can claim.
In my scheme, the permissions are implied *in* the Handle. If the
server backing a particular Handle is willing to implement a set
of N methods for *that* Handle, then the Holder has those permissions.
If it chooses to only implement M methods -- possibly even *different*
methods -- for another Handle, there is no inconsistency.
Everything has a context and only makes sense in that context.
>>> [Same model as Amoeba: first connect, then accept or reject.]
>>
>> The NORMA server made ports transparent. You didn't know
>> whether an IDL was going to result in an "IPC" or an "RPC".
>> You just "talked to a port for which you had send right(s)"
>> and the kernel did the rest.
>>
>> No separate permission required to use the network.
>
> That's not my point.
>
> To do much of anything, a Mach server has to register a public service
> port with send rights - which any task in the network can scan for and
> try to connect to. Aside from limiting the count of available send
> rights to the port, there is no way to prevent anyone from connecting
> to it.
No. That's the way Mach was *applied* -- because they were looking
towards "UNIX as an application". *Obviously* (?) you need to be
able to find services!
Or, *do* you?
What's to stop me from creating a task with the following namespace:
CurrentTime
Display
AND NOTHING MORE!
Further, the Handles that I endow the task with are:
(CurrentTime, read)
(Display, write)
I.e., even though the IDL for the "Time" object supports a "set_time()"
method, the task can't use it (it can *try* to invoke it but the server
that backs the Time object will return NO_PERMISSION if it does!
This task just spends it's life running:
while(1) {
now = get_time(); // uses CurrentTime
segments = decode_7segments(now);
display(segments); // uses Display
}
> Only *after* determining that the connection is unwanted can the
> server decide what to do about it. Using MIG didn't affect this: you
> couldn't encode port ids into the client using IDL - the only way for
> a client to find the server was to look up its registered public port.
But the client doesn't have to even have access to a particular server's
public port!
E.g., there is no reason you ("application") should have access to
the device driver for the disk subsystem. Why should I even let
you try to *find* it?
In *your* namespace (that YOUR creator sought fit to establish
KNOWING WHAT YOU SHOULD BE RESPONSIBLE FOR DOING), there is no
entry called "DiskSubsystem"!
Because there is no entry, you can scream all you want but never get
a message to that server! No connection possible!
If you *should* have the ability to connect to a server (e.g., the
server backing the Time object, above), *it* decides whether to
allow particular requests (method invocations) based on "permissions"
that have been associated with the "Handle" (incoming port) on which
your messages arrive.
YOU CAN BE SEEN TO ABUSE THIS. Abuse is something that a server
determines -- not the client *nor* the system! 500 attempts to write()
a file that has been closed may not be abuse. An attempt to turn
off the system's power, OTOH, *may* be! (i.e., you have permission to
EXAMINE the current power status: on, battery, off -- not change it!
Your attempt to do so indicates a significant bug or a deliberate
attack!)
In that event, the server can pull the plug on your Handle -- i.e.,
kill the port on which YOUR requests are received. If you manage
to acquire another Handle, it can kill the port FROM WHICH your
Handles were issued (even if some other task actually was responsible
for creating them and passing them on to you -- an accomplice? Or,
just someone who "trusted you more than they should have!").
The point is, the system can take steps to isolate your potential
for damage. Can prevent you (eventually) from abusing resources.
>>>> The only way for any of us to know a ticket (capability) has
>>>> been revoked is to try to *use* it. If I use mine or you
>>>> use yours, we get told THAT capability has been revoked.
>>>> But these other (nameless) two guys in my example have no idea
>>>> that this has happened (unless one of us knows to tell BOTH
>>>> of them).
>>>
>>> They get told when they try to use it.
>>>
>>> That's an administrative issue which has little to do with mechanism.
>>> In your system, revoked ports just evaporate leaving clients equally
>>> clueless unless *somebody* deigns to tell them why.
>>
>> No. There are two port-related "notification mechanisms" in Mach
>> (i.e., concerned with things that happen in the normal course of
>> a port's life).
>>
>> When the last "send right" *to* a particular port dies (is destroyed,
>> etc.), a "No More Senders" notification is delivered to the task
>> holding the receive right. I.e., no one is interested in the
>> object that you represent any longer. THERE IS NO WAY FOR ANYONE
>> TO REFERENCE IT (other than you!). Please feel free to deinstantiate
>> it and GC it's resources.
>
> That tells the server all existing clients have disconnected ... it
> does NOT tell new clients they can't connect.
Ah, but it does! ALL THE REFERENCES TO THE OBJECT ARE GONE!
The object still exists IN THE SERVER, but no one can *talk*
to "it" (the representation of the object) any more!
I.e., it's like:
object = &foo;
...
object = NULL; (or, some random number)
*object = .... won't work (at least, not on *foo*!)
So, why *keep* foo around if no one can access it?
>> When the receive right to a port dies (i.e., when the server that
>> is handling the incoming messages SENDed to that port dies or
>> destroys the port), a "Dead Name" notification is sent to holders
>> of send rights. I.e., the object that you were referencing
>> no longer exists. Don't bother trying to access it!
>
> This tells the server or client the other side has disconnected. It
> does not tell them why.
It *can* tell the clients that the *server* is no longer backing
the object (i.e., the object no longer exists).
Mach used to have a "dead name notification" whereby all actors
holding ports (Handles) that referenced that port (object) were
notified (sent an exception) of the death.
But, this uses a lot of resources and is seldom necessary. I.e.,
why not just wait for the actors to "discover" this fact when
they try to operate on that corresponding "port"? (the port
is *marked* as "dead" but the task isn't "informed" of the
death).
Imagine some well known port dying and having to notify hundreds
of actors of this fact. Chances are, most will say, "Gee, that's
too bad! Where should I send the FLOWERS??"
I.e., there's usually nothing they can DO about it. All you've
told them is that, IN THE FUTURE, when/if they try to contact that
port/Handle/object, they will receive a DEAD_NAME result in whichever
method they invoke.
OTOH, for cases where it is important to notify a "Holder" of
this sort of "revoked capabilty", the server that was holding that
"receive right" (i.e., the port that just died) can *choose*
to asynchronously notify the actor on the other end.
The actor on the other end can also choose to ignore this notification.
AND, things still work "predictably" in each case! If/wehn you try to
access the "dead name", the method fails. Too bad, so sad.
>>> Your clients don't know their port privileges have been revoked until
>>> they try to use their handles. Someone could be sitting on a useless
>>> handle for hours, days or weeks (how long since last reboot?). How is
>>> that *substantively* different from a cached ticket in Amoeba?
>>
>> Above.
>
> You assume that both tasks [server and client] have implemented
> notify_server and do-mach-notify-<whatever>. Unless both sides are
> using MIG, you can't assume any notifications will be sent, received
> or handled - it isn't *required*.
The kernel does the no-more-senders notification. Of course, the
server doesn't have to *use* this information. If it has plenty
of resources, it might just choose to mark the port and GC it
later. Or, it might decide to free all the resources associated with
backing that object. E.g., if the object is a file and it has
writes pending, it might mark the buffers to be flushed, then
return them to the heap. If it has read-ahead in anticipation of
future read() methods, it can just drop that memory (because no
one can send read() methods to that object any more!)
Dead names have more flexibility.
In either case, kernel just provides means to tell folks what is
happening. Amoeba can't do this because it doesn't know where
tickets are "hiding".
> If a task doesn't implement the relevant notification handler, it
> won't know anything has happened until it gets an error trying to use
> the port.
For a "dead name", yes, exactly. If a task can *tolerate* not
knowing until that point, then why bother *telling* it? You
could also have tasks that *never* try to talk to an object
even if they have been GIVEN a Handle to it!
E.g., if you never write to (your) stderr, what do you care if the
file (or process!) it represents has gone away?
>>>> In mach, deleting the port (Handle) deletes all instances of it.
>>>> And, since the kernel knows where those instances are, those
>>>> Holders can be notified when that happens.
>>>
>>> Only if they were coded respond to the appropriate signal. And Mach
>>> didn't allow signaling a task on a remote host.
>>
>> If a task doesn't care to deal with "port death", it doesn't have
>> to. If it is important to the task's proper operation, it will!
>
> You're assuming again.
I *said* so! :> "If a task doesn't care to deal with 'port death'..."
OTOH, if it *does*, then there needs to be a mechanism whereby the task
can be *told* about this -- because it can't "see" what is happening to
a port unless it deliberately tries to "use" it.
>>>> The "BSDSS" (Single Server).
>>>
>>> Was it really the SS version?
>>
>> Bad choice of names.
>
> Ok. I misunderstood you to mean it was BSD's single user kernel - not
> simply Mach's "single server" model.
BSDSS was an old BSD (license required) kernel ported to run basically
as a single Mach "task". I.e., slide Mach *under* an existing BSD
UNIX. You gain "nothing" -- except the ability to run other things
(even other OS's!) ALONGSIDE that "UNIX"! E.g., you could run BSDSS
and POE (DOS server) on the same hardware at the same time -- not
"DOS under BSD".
Mach-UX was a more modern, less encumbered, kernel running in much
the same way. Recall, about this time, the USL lawsuits were making
life difficult for the UN*X clones.
Lites was a similar project.
Mach-US was the "application" that set out to illustrate why the mk
approach would be "better" for systems than the monolithic kernel
approaches had been, historically. It decomposed "UNIX" into a
bunch of different "services" -- typically executing in UserLand.
Then, tied them together *via* the microkernel -- instead of
letting them rid in a single container atop it (as the single servers
had).
Unfortunately, Mach-US was too late and folks had already moved on.
Trying to retrofit a reasonably mature system (UN*X) into a
totally different framework (mk) was a losing proposition from the
start. *Obviously*, the mechanisms developed *in* UNIX to handle
<whatever> had been tweeked to fit it's idea of how a kernel
should be built.
To realize any significant advantages, you'd have to rethink what
a "UNIX" should *be* -- not what it *is*!
>> But, I'm *reasonably* sure "systems" will be more physically
>> distributed. More processors. etc. And, forcing people to
>> use things like Berkeley sockets for all those comms is like
>> forcing a sprinter to wear scuba flippers! (Limbo/Inferno
>> was the final piece of the puzzle to fall into place. Many
>> of its ideas map naturally to this framework -- though with
>> an entirely different implementation! :< Now, I just have
>> to beat it all into submission! :> )
>
> I really don't like network agnosticism. It simplifies coding for
> simple minded tasks, but it makes doing real time applications like
> audio/video streaming, and remote monitoring/control a b*tch because
> you don't have visibility to adjust to changing network conditions.
>
> I hate RPC for the same reason. I often use loopback connections to
> communicate between co-hosted tasks. At the very least, using a
> network API reminds me that the tasks may, in fact, be separated by a
> network. Sometimes it doesn't matter, but sometimes it does.
I'm treating the network in much the same way that PC designers
treat the PCI/memory busses.
Could you design a "centralized" (non-networked) system with predictable
responses if anyone could open the machine and add any BUS MASTERING
hardware they wanted? :> How could you reliably know when/if you could
access particular I/O's, etc.
I.e., don't design for an "open" network onto which random, uncontrolled
entities can be added at will. Instead, use it as a "really long bus"
with devices having known characteristics along it's length. Just like
when you design a system KNOWING which PCI cards will be inside!
OTOH, you don't want to have to have a bunch of different mechanisms
to access different devices *on* that bus! I.e., memory is memory
regardless of whether its a buffer being used on a SCSI controller
or an mbuf in the kernel!
> I had to deal with an extreme example of this in a component SRT
> system I worked on back before high speed Internet was common. The
> system was designed for use on a LAN, but the components were
> separable and sometimes were used that way. The system could be run
> successfully over dial-up lines [but it had to *know* which of its
> components were on dial-up].
>
> The problem was, at that time, businesses often interconnected LANs
> using dial-up bridge routers: an apparently local LAN address could,
> in fact, be in another city! [Or even another state!!] Many of these
> bridges could be configured to ignore keep-alive packets and hang up
> if you weren't sending real data. The next real message then would be
> delayed - sometimes for many seconds - while the dial-up link was
> reestablished. To save on phone bills, IT managers tended to
> configure their bridges to shut down phone links quickly.
>
> The story is long and I won't bore you with it ... suffice to say that
> things became a lot more complicated having to deal with the potential
> for a customer to configure the system for a LAN and then try to run
> it distributed over dial-up bridges. That caused me [and others] an
> awful lot of headaches.
If your system had a *requirement* for a particular transit time, then
its up to the implementors to guarantee that. You can't expect me to
spool real time HD video to a 360K floppy disk! :>
OTOH, you don't have to record video on *tape* (historically)!
Avail yourself of whatever new technologies offer -- knowing what
*new* capabilities this affords AND new limitations!
I.e., splicing acetate film was a lot easier than digital video!
All you needed was a sharp knife and some tape -- no high speed
computers and fancy software...
[C is ready. Off we go! :> ]