On 11/9/2021 11:03 AM, Karl MacMillan wrote:
Daniel,
I'll just say - thanks for including me. It's interesting to see your
work. I've got some comments below inline.
On Mon, Nov 8, 2021 at 3:43 PM Daniel Burgener
<dburgener@xxxxxxxxxxxxxxxxxxx <mailto:dburgener@xxxxxxxxxxxxxxxxxxx>>
wrote:
I definitely have less CIL expertise than you, so please correct me
if I
have anything inaccurate about CIL. I don't think there are any
fundamental differences/incompatibilities between CIL inheritance and
Cascade inheritance, at least not in the big picture. They're both
generally aiming at solving a similar problem in a similar manner.
They
both inherit rules and create name mangled derived types for children.
In contrast to CIL, Cascade intermingles attributes and
inheritance. So
if I inherit from a parent, I automatically have an attribute for all
children that I can reference elsewhere.
That's certainly interesting and I see the appeal. I'll be curious to
see how that works out long term. I think one of the reasons that CIL
inheritance was separate from that originally was because attributes are
not great at expressing access access within a set of types that is
private vs they should all have interrelated access. I apologize for not
having looked at your proposed language closely enough to know the
semantics in detail (I read through the documentation, but was not able
to understand how some cases would work).
For example, let's say you have a "base class" for all services that can
be started by init, can write to the logging facilities (syslog /
journal), and have a pid file. The challenge that I see in that case is
that upon inheritance, the children sometimes get access to the
resources of the parents (i.e., init can exec them and they can still
write to syslog / journal), but sometimes they get _copies_ of the
resources with distinct access that is separated in the children (the
pid files).
That's hard to express with attributes and I think it's different from
normal object-oriented programming in some ways. Maybe you can express
it with static members vs instance members, but then it feels like you
need to "instantiate" the types vs this declarative language?
I agree that this is a challenge. I *think* our model handles it
cleanly, although we haven't had the "stress test" that implementing a
full system policy in it will undoubtedly provide and I'd be surprised
if we don't come across some interesting scenarios we haven't given
enough thought to.
A lot of this is handled with the @associate annotation which allows for
automatic deriving and access of the child. In your example, I would do
something like:
virtual resource init_pid {
@associated_call
fn use_init_pid(domain source) {
this.manage(source);
}
}
@associate([init_pid])
virtual domain init_daemon_domain {
syslog.write(); // defined elsewhere
}
domain my_service inherits init_daemon {}
The key thing here with respect to what we're discussing is that when we
inherit from init_daemon_domain we get a *copy* of all of
init_daemon_domain's associated resources, and the @associated_call hook
is called for each child on the copy. So in this situation, my_service
would inherit the syslog permissions (and the init domain elsewhere
could reference all init_daemon_domains), but in terms of the policy
that becomes unique to each child, that is defined in the
@associated_call annotated function (and there can be multiple such
functions if you desire), and it's called under the hood for all the
children.
This conversation has also reminded me that we intended to have an
@noinherit annotation for things that wouldn't be passed on to the
children. For example, there may be scenarios where it's beneficial to
have init_daemon_domain associate with a resource and have the children
*not* get copies of such a resource. That particular feature got lost
in the shuffle, so I may need to loop back on it.
Even without that though, I think the association mechanism here hits
the key points on this challenge, and covers at least the common cases
pretty cleanly. Let me know if you think something is missing here.
Maybe you handle all of this?
In terms of optional blocks, Chris and I have argued about optional a
few times, and part of the reason there's nothing about optional so far
is because we'd like to solicit community feedback there. My personal
view is that explicitly marking statements as optional is tedious and
error prone. But I don't have a really slam dunk "clearly better"
solution to propose yet at this point. I have a few ideas, but they
all
need to be more thoroughly thought out and there are some holes and
issues to address. I viewed this feature as non-essential for an
initial proof of concept, and am glad that we can have design
discussions about it in the open.
So in short, yes, I think the ability to differentiate that certain
rules are required for a module to work and others are optional is
definitely needed. But I'm not yet set on what the best way to do that
is. One key goal here is that a developer of a component should be
able
to add policy with fairly minimal knowledge of SELinux. I'd prefer it
if most of the time they could do the right thing without knowing about
optional, but I'm not yet sure what the best/cleanest way to achieve
that will be.
Optional blocks have, in practice, been a huge source of errors I
believe.
> [snip]
I agree pretty much 100% with everything you've said here about the
usage and historical limitations of optional. Thanks for the added
historical context from before my time.
On the topic of packaging, I'll admit that I'm sparse on details without
an actual implementation at this point, so the packaging aspect of
Cascade is currently vaporware, but my hope is that an integrated
packaging and build system framework, inspired by rust's cargo can solve
a lot of the packaging problems much more cleanly (by inserting optional
blocks for install order problems during compilation rather than in
source for example).
I like the suggestion of a question mark operator for optional policy
quite a bit. Although I'm really interested in exploring whether it's
possible to just treat the entirety of it as a policy level dependency
resolution problem. Basically annotate with modules a particular module
depends on vs which are optional - the distinction being that the
optional marking is done at the module level rather than the
rule/interface call level. That might be less heavy-weight and error
prone to use while still providing the needed flexibility.
Anyway - forgive me crashing in with all of my opinions. I hope my
background on the problems of optionals might at least be helpful.
Opinions are what I'm seeking. :) And yes, I think all of your thoughts
here are quite helpful. I think the attributes concern is a good one
that we aimed to address, and can continue to refine as needed, and the
optional thoughts are extremely helpful, since as I mentioned in my
prior e-mail, the overall support for differentiating which policy is
required and which is optional is a must-have in my view, but how
exactly to specify it in a way that is clear is somewhat challenging and
subjective. Added info on historical context and use cases is a
valuable input to that discussion.
-Daniel