[apparmor] IPC syntax - again

John Johansen john.johansen at canonical.com
Wed Jun 26 18:00:52 UTC 2013


So this is long and has taken far to long to write, I am sure their
are things I have missed and some of the logic may be missing. In fact
I don't really consider this email complete but we need to make a
decision asap, so

Lets look at this at a more generic layer and then start applying it
to other types of ipc, and give some examples so can bring this
into the larger discussion.

Firstly ipc is all about communication between subjects, so everything that
isn't caps or resource control. There are several basic patterns and
features that distinguish different types of ipc beyond the specific
features of a given ipc.

Some general patterns (with ascii art!)

1. Direct Private
  +-------+                          +-------+
  |       |                          |       |
  |  P1   | <----------------------> |  P2   |
  |       |                          |       |
  |_______|                          |_______|


2. Direct Private Proxied (handle)
  +-------+                          +-------+
  |       |     +--+        +--+     |       |
  |  P1   | <-> |h1| <----> |h2| <-> |  P2   |
  |       |     |__|        |__|     |       |
  |_______|                          |_______|


3. Direct Shared Proxy (handle)
  +-------+                          +-------+
  |       |     +--+        +--+     |       |
  |  P1   | <-> |h1| <----> |h2| <-> |  P2   |
  |       |     |__|        |__|     |       |
  |_______|      ^                   |_______|
                 |
  +-------+      |
  |       |      |
  |  P3   | <----+
  |       |
  |_______|


4. Bus

  --------------------------------------------
      |                 |                |
      |                 |                |
  +-------+         +-------+        +-------+
  |       |         |       |        |       |
  |  P1   |         |  P2   |        |  P3   |
  |       |         |       |        |       |
  |_______|         |_______|        |_______|


5. Proxied Bus

  --------------------------------------------
      |                 |                |
     +--+              +--+             +--+
     |h1|              |h2|             |h3|
     |__|              |__|             |__|
      |                 |                |
  +-------+         +-------+        +-------+
  |       |         |       |        |       |
  |  P1   |         |  P2   |        |  P3   |
  |       |         |       |        |       |
  |_______|         |_______|        |_______|


6. Shared Proxied Bus

  --------------------------------------------
              |                          |
             +--+                       +--+
      +----> |h1| <-----+               |h3|
      |      |__|       |               |__|
      v                 v                |
  +-------+         +-------+        +-------+
  |       |         |       |        |       |
  |  P1   |         |  P2   |        |  P3   |
  |       |         |       |        |       |
  |_______|         |_______|        |_______|


7. Indirect Object
                    +------+
                    |      |
                +-> | Obj1 | <-+
                |   |______|   |
                |              |
                |              |
  +-------+     |              |     +-------+
  |       |     |              |     |       |
  |  P1   | <---+              +---> |  P2   |
  |       |                          |       |
  |_______|                          |_______|


8. Proxied Indirect Object
                    +------+
                    |      |
                +-> | Obj1 | <-+
                |   |______|   |
               +--+           +--+
               |h1|           |h2|
  +-------+    |__|           |__|   +-------+
  |       |     |              |     |       |
  |  P1   | <---+              +---> |  P2   |
  |       |                          |       |
  |_______|                          |_______|


9. Shared Proxied Indirect Object
                      +------+
                      |      |
                +---> | Obj1 | <---+
                |     |______|     |
               +--+               +--+
               |h1|               |h2|
  +-------+    |__|   +-------+   |__|   +-------+
  |       |     |     |       |    |     |       |
  |  P1   | <---+---> |  P3   |    +---> |  P2   |
  |       |           |       |          |       |
  |_______|           |_______|          |_______|


In practice we don't actually have to deal with all of these as most
resources are accessed via proxy (handle) and if the handle is not shared
as is the case for diagrams (2,5,8) there is a one to one mapping between
the proxy and the subject as such we can ignore the proxy in these case
as it has the same labeling as the subject it represents.

Beyond proxied or not, and direct vs indirect there are a few other
properties that can be considered

for none share object ipc, there is a local (subject) side and a remote
peer side
- is the peer address available
- is the peer labeling available
- can the local address and peer address be paired to enforce certain
  pairing of communication

Whether the ipc is named/has an address or is anonymous
- if there is a name is it well known/predictable or is it random.
- unpredictable/random names can not be leveraged for address/name based
  rules.
- can the name be changed/lost
  - eg file names can become "disconnected" when viewed from a different
    namespace

Is the ipc persistent or temporal. That is does the ipc object exist beyond
the lifetime of the subjects involved.
- if the object is temporal then its labeling can be based on the subjects
  using it.
- if the object is persistent then its labeling if based on subjects must
  be based on the set of subjects that could access it over its lifetime.

  This is actually no different than the temporal object its just the
  access to the object over its lifetime can be tracked more efficiently.
  Note: it is possible to store accesses on the persistent object in
  a manner similar to temporal but this has problems to policy reloads
  and reboots, etc.


When a shared bus or proxy is used, can the data be only read out by
a single subject (that is a read removes the data like in a pipe) or does
each subject get a copy (each can read the same data from a file). However
this is a property of the type of ipc and is not something that can
be controlled for in policy so can be ignored.

When a well known address is used, is there a separate instance communication
channel created between the subject and peer (think ip with accept providing
a unique channel shared on the same address).


Simplifying to a few rule patterns

All of this is a lot to deal with so lets try breaking it down and reducing
it more. And look at some basic rule patterns


Symmetry of AppArmor rules rules

AppArmor has taken the approach that no domain dominates another this
means that for subject A to be able to write to subject B, subject B
must be able to read from subject A. Only if both domains agree can
the communication occur.

This can be seen in file rules where, for A to write to B via a file.

  A {
    /foo w,
  }

  B {
    /foo r,
  }

A needs a rule to write to the file and B requires a rule to read from
the file.

This same pattern has been carried into the proposed dbus rules, and
is proposed for other forms of ipc as well. So any proposed syntax should
follow this.


Dealing with buses and shared handles (proxies)

We can deal with buses and shared handles in two ways. Either use an
explicit label and use rules that allow access to the explicit label, or
mediate access by checking against each domain separately.

When using an explicit label, the proxy or bus is labeled with the
explicit label and then each domain must be able to communicate with
the explicit label.
eg. If we have A, B, and C on a bus, and the bus is labeled X. Then
    for A, B, and C to be able to communicate over the bus, then they
    each must be able to communicate with X. Also of note is that X
    must be able to communicate with each of A, B, C (this is easily
    done by making X unconfined).

When using addresses we check against each of the separate domain involved.
Often the domain will have no restriction on who it communicates with
but just specifies an address, which means it can communicate with anyone
who can communicate with that address.
eg. if we have A, B, and C on a bus, for A to be able to write to the
     bus it must be able to write to B and to C, and B and C must be able
     to read from A.

     Or from an address pov A can communicate with the bus address X,
     B can communicate with bus address X, and C can communicate with
     bus address X. So they can all communicate with each other via X.

     If however D wanted to communicate with A over X but was not
     allowed to communicate with B or C, this would be disallowed as
     B and C are on the bus.  Unless the bus allows selective delivery
     of a message based off of a permission check.

The same basic logic applies to shared handles.  For a handle shared by
A and B to be able to communicate with C. Then A and B both must be able
to write to C and C be able to read from A and B.

Essentially this means we only need to deal with the question of
communication on a pair level within rules. The rules don't have to
directly deal with proxy or bus being shared. Though there are cases
where we must use the proxy as the subject because the subject isn't
directly available.


Shared objects
With shared objects there is no peer in the sense of tcp/ip. However the
other tasks/profiles that have access to the object can be thought of
as peers that the subject is communicating with through the object.

The address of the peers, is the same as the address of the subject
side of the communication, though it is possible to get different labeling
for each "peer".

So to communicate over a shared object the question again is if the
subject can communicate with each of the "peers"


Shared object vs. Peer

What I have been trying to get at is we essentially have 2 distinct
types of ipc: shared object, and true peer, that behave very similarly in
most ways.

In the shared object case there is a shared address, and a set of labels
on the object (whether explicit of implicit).

In the true peer case we have a subject side and a peer side, with
potentially different addresses. And a distinct labeling on each side.
The labeling is not much different than the shared object case. That
is the subject label is checked to see if it can communicate with the
peer and the peer checks if it can communicate with the subject.

If the subject is shared it may have a compound label but this does not
change the label check. It just requires each component be tested (expensive
but not different).

For the label case, there is no need to differentiate a subjects labeling
from the peers because the subjects label is ALWAYS the profile that
the rule is defined in.  So labeling does not require rules to identify
a peer vs. the subject.


The difference between shared object and true peer comes down to the
"address/naming" (and other variables like uid) and whether the subject
address can be paired with the peer address.  That is if it makes sense to
tie sending/receiving from/on a subject address to/from a specific peer
address.

Whether this makes sense partly comes down to how the peering is handled.
Lets assume we have a local handle (fd) that can be shared. In this
case we can bind an address and perform local operations on it (getsock ops,
etc). Without dealing with the question of whether the processes should
be able to communicate with the peer.

It is confusing to specify these local permissions in a rule that also
specifies a peer address. That is to say it does not make a lot of sense
to specify bind/acquire et al in rules with a peer address.

So a profile will require ipc rules without peer addresses. In this
case specifying the subject is redundant.  Though it may be worth doing
for consistancy, if we have rules that specify both subject and peer at
the same time.

For rules that specify a peer address the major permissions involved are
read/write/send/receive. In this case the permission means that the
subject can read FROM the peer, write TO the peer.

If we also throw in a subject address the meaning of the permission
  r subject=(addres=foo) peer=(address=bar),
means read on subject address=foo from peer address=bar.

This pairs the local address/handle to a specific peer address handle.

The question is how useful is this compared to not allowing pairing in
rules. That is the rule address subject/peer is infered from the
permissions on the rule.

eg. which is easier to understand
  aquire subject=(address=foo),
  rw subject=(address=foo) peer=(address=bar),

or
  aquire address=foo,
  rw address=bar,

the second case can simplify the syntax because it doesn't care about
tying the comminication with the peer to a specific address on the subject.
Admittedly we can simplify the rules from the pair syntax example to
have the same semantics as the 2nd example.
  aquire subject=(address=foo),
  rw peer=(address=bar),
  
and it is not a lot more complicated. The question is, is this small
increase in syntax complexity worth it for that extra restrictions it
can give us.

So where is pairing possible?
- It is not reasonable possible for dbus rules
- It does not make sense for none address base (anonymous) ipc where the
  labeling is implicit.
- It could make sense for tcp/ip where communication to the network
  from a server should happen over a particular interface, and possibly
  bound address.

  However consider that a bound address may not be information on a
  communication socket received from doing a bind. It is possible to
  use the local address at the accept point, but the socket that is
  handed off from the accept can then be passed around and further
  mediation based off of the subject address may not be possible.
  For this case considering the subject address only at accept may
  be enough but I need to look into it more.



One case where local label pairing could matter is when the subject
specifies a special label for the local socket.
  eg.
    profile A {
       bind address=foo label:=B,
       rw subject=(label=B) peer=(...),
    }
In this case the subject socket has a different labeling than the profile.
This means from a type enforcement perspective the permission decision
is changed from a type of

  subject object/peer permission

to the quad of
  subject proxy object/peer permission

or another possible interpretation is that the profile (A), is
defining rules for the proxy label (B).  That is we could rewrite
the profile A above as
   profile A {
       bind address=foo label:=B,
   }
   profile B {
      rw peer=(...),
   }

Now imagine that B is used in other profiles, in that case we must
process all profiles to find the rules for B.

Now what of B being defined as a profile with its own rules, now we
must look at all policy to find its full rule set. Or alternatively
look at all policy to find a conflict saying that other profiles
can not define rules for B.

Any of these cases are something that I would like to avoid. As such
I think if the subject specifies an alternate labeling for a socket
then any rules for that labeling should be explicitly defined in its
profile, not in the subjects (A) profile.



Side Note: On labeling vs addresses/access paths

There are times when using a label to control mediation is the natural
choice. There are other times when as a policy author the address/access path
is the natural choice.

The way our policy is setup is that the author can choose either and the
compiler and implicit labeling will just make it work. Admittedly this
isn't necessarily as "clean" as doing all labels but it makes authoring
and understanding policy easier.



So now lets move onto permission placement and some examples.

For permissions the question is where they should appear in the rule.
At the front, after keyword, embedded (discarded), or at the end.

  eg.
  front:             (send, receive) dbus subject=() peer=(),
  after keyword:     dbus (send, receive) subject=() peer=(),
  embedded:          dbus subject=() (send, receive) peer=(),
  at the end:        dbus subject=() peer=() (send, receive),

For examples what are some forms of ipc we can consider (sorry not an
exhaustive list)
  fds - are a handle/proxy for multiple forms of communication

  files - named, labeled, persistent, shared object, each subject can get
          its own copy.

  pipes - anonymous, temporal, shared proxy, with data available to single
          subject, peer NOT available
  fifo - named, temporal with name persisting, shared proxy with data
         available to single subject, peer NOT available

  signals - anonymous, temporal, direct private, peer label available
  ptrace - anonymous, temporal, direct private, peer label available

  network
  - unix domain
    - named - named, temporal with name persisting,
              separate instance per accept.  peer label available
    - abstract - named, temporal, shared proxy with separate instance per
                 accept, peer label available
    - anonymous - anonymous, temporal, shared proxy with separate instance per
                 accept, peer label available
  - tcp/ip - address, temporal, shared proxy with separate instance per
             accept, peer address available*,
  - dbus - address, temporal, shared proxy*, peer label available

  sys V
  - semaphores - named, persistent name/data, shared, No peer
  - message queues - named, persistent name/data, shared, No peer
  - shared memory - shared memory object

  mmapped anonymous memory - shared memory object
  mmapped files - shared memory object




Anonymous rules

So lets try tackling anonymous based ipc first. Anonymous ipc doesn't
have a name or address to base mediation off of. All that is available
is the type of communication, the permissions and the labeling. To do
any mediation of these beyond a coarse yes or no label rules are required.

Beyond the label it is possible to use other conditionals (uid, ...) to
provide even finer mediation but it will have the same basic format of
label.

So for a basic brain dead on/off of a form of ipc we follow the same basic
format we have done for network, file, etc. that is just the keyword

  ptrace,      	    	# direct private (has peer)
  signal,		# direct private (has peer)
  pipe,			# shared object via shared handle (no peer)
  network unix,		# direct shared handle (has peer)

Each of these will allow for communication of their respective types
without any control over with who.  Basically they say I can create
or communicate over a pipe with anyone (at least anyone who can
communicate with my labeling and has access to the pipe fd).

The network unix rule says even more because it allows communication over
named as well as anonymous unix sockets. For any ipc method that allows
for both named and anonymous communication we need to allow for a
way to indicate that only the anonymous type should be allowed. To achieve
this we could leverage the bind permission so that the unix socket can
not be bound to an address (but more on that later).


Now lets extend these to support permissions. There are just a couple
of possibilities available.

after keyword/trailing perm (same thing here)
  ptrace (read, attach),
  signal (send, receive),
  pipe rw,
  pipe (create, read, write),
  network unix rw,

perm after keyword, same as above except for network unix. If network
is considered the keyword then
  network rw unix,

leading perm
  (read, attach) ptrace,
  (send, receive) signal,
  rw pipe,
  (create, read, write) pipe,
  rw network unix,

nothing ground breaking here, except to not that perms after keyword
could get tricky. In some sense dbus could be thought of as (especially if
af_dbus had been accepted)

  network dbus,

at which point the is network the keyword or is dbus still the keyword?
As a future extension we could consider allowing pulling address families
out from network and writing rules without the network keyword

  unix rw,
  rw unix,

  inet rw,
  rw inet,

  ...

at which point we need to be very clear what perm after keyword means.
This is something to consider but not very important so lets move on.


other none address based controls.

Some of these rules have more controls than just the regular permission
or address.

signals - before you read further a couple of comments.
1. I don't expect permissions to be used with signals except when the
   permission is unidirectional. ie. allow sending of kill, but not
   receiving it.
2. I have included the rw alias for (send, receive) so what it looks
   like in the different combinations can be thought about.

   signal kill,			# list like capability rules
   signal hup kill,		# list like capability rules

   signal=(kill, hup),		# the signal keyword itself becomes the
                                  named selector. Not what we have
                                  been doing

   signal signal=(kill, hup),   # this just looks weird. I'll drop it
   signal set=(kill, hup),	# or perhaps some other word than 'set'

and now with the perm options
   signal kill hup (send, receive),	# I'm okay with this
   signal kill hup rw,		# I don't really like how the send,receive
                                  alias works here
   signal (send, receive) kill hup,	# okay
   signal rw kill hup,	       		# not so good

   (send, receive) signal kill hup,
   rw signal kill hup,

   # no perm after keyword in this grouping
   signal=(kill hup) (send, receive),
   signal=(kill hup) rw,
   (send, receive) signal=(kill hup),
   rw signal=(kill hup),	#this actually works okay

   signal set=(kill hup) (send, receive),
   signal set=(kill hup) rw,
   signal (send,receive) set=(kill hup),
   signal rw set=(kill hup),
   (send, receive) signal set=(kill hup),
   rw signal set=(kill hup),


And now for network (yes I purposely switched from unix for this one)
   network inet stream,
   network inet type=stream,

and now with some perms
   network inet stream rw,
   network inet stream (send, receive),

   network rw inet stream,		#same Q. about perm position
   network (send, receive) inet stream,

   rw network inet stream,
   (send, receive) network inet stream,


And now with label conditional, which brings in the possibility of peer
notation. Lets do pipe first because it doesn't have a peer.
   pipe label=foo,
   pipe label=foo rw,
   pipe rw label=foo,
   rw pipe label=foo,


   ptrace label=foo,		# peer implicit
   ptrace -> label=foo,		# I know discarded syntax
   ptrace peer=(label=foo),
   ptrace peer {label=foo},

   ptrace label=foo (read, attach),
   ptrace -> label=foo (read, attach),
   ptrace peer=(label=foo) (read, attach),
   ptrace peer {label=foo} (read, attach),

   ptrace (read, attach) label=foo,
   ptrace (read, attach) -> label=foo,
   ptrace (read, attach) peer=(label=foo),
   ptrace (read, attach) peer {label=foo},

   (read, attach) ptrace label=foo,
   (read, attach) ptrace -> label=foo,
   (read, attach) ptrace peer=(label=foo),
   (read, attach) ptrace peer {label=foo},


   signal label=foo,
   signal label=foo (send, receive),
   signal (send, receive) label=foo,
   (send, receive) signal label=foo,
   signal hup kill label=foo,
   signal=(hup, kill) label=foo,
   signal set=(hup, kill) label=foo,
   signal hup kill label=foo (send, receive),
   signal=(hup, kill) label=foo (send, receive),
   signal set=(hup, kill) label=foo (send, receive),
   signal (send, receive) hup kill label=foo,
   signal=(hup, kill) (send, receive) label=foo,
   signal (send, receive) set=(hup, kill) label=foo,
   (send, receive) signal hup kill label=foo,
   (send, receive) signal=(hup, kill) label=foo,
   (send, receive) signal set=(hup, kill) label=foo,


   signal -> label=foo,
   signal hup kill -> label=foo,
   signal=(kill, hup) -> label=foo,
   signal set=(kill, hup) -> label=foo,
   signal -> label=foo (send, receive),
   signal (send, receive) -> label=foo,
   (send, receive) signal -> label=foo,
   signal kill hup -> label=foo (send, receive),
   signal=(kill hup) -> label=foo (send, receive),
   signal set=(kill, hup) -> label=foo (send, receive) ,
   signal (send, receive) kill hup -> label=foo,
   signal (send, receive) set=(kill, hup) -> label=foo,
   (send, receive) signal kill hup -> label=foo,
   (send, receive) signal=(kill hup) -> label=foo,
   (send, receive) signal set=(kill, hup) -> label=foo,

   signal peer=(label=foo),
   signal hup kill peer=(label=foo),
   signal=(kill, hup) peer=(label=foo),
   signal set=(kill, hup) peer=(label=foo),
   signal peer=(label=foo) (send, receive),
   signal (send, receive) peer=(label=foo),
   (send, receive) signal peer=(label=foo),
   signal kill hup peer=(label=foo) (send, receive),
   signal=(kill hup) peer=(label=foo) (send, receive),
   signal set=(kill, hup) peer=(label=foo) (send, receive),
   signal (send, receive) kill hup peer=(label=foo),
   signal (send, receive) set=(kill, hup) peer=(label=foo),
   (send, receive) signal kill hup peer=(label=foo),
   (send, receive) signal=(kill hup) peer=(label=foo),
   (send, receive) signal set=(kill, hup) peer=(label=foo),

   signal peer {label=foo},
   signal hup kill peer {label=foo},
   signal=(kill, hup) peer {label=foo},
   signal set=(kill, hup) peer {label=foo},
   signal peer {label=foo} (send, receive),
   signal (send, receive) peer {label=foo},
   (send, receive) signal peer {label=foo},
   signal kill hup peer {label=foo} (send, receive),
   signal=(kill hup) peer {label=foo} (send, receive),
   signal set=(kill, hup) peer {label=foo} (send, receive),
   signal set=(kill, hup) peer {label=foo} (send, receive),
   signal (send, receive) kill hup peer {label=foo},
   signal (send, receive) set=(kill, hup) peer {label=foo},
   (send, receive) signal kill hup peer {label=foo},
   (send, receive) signal=(kill hup) peer {label=foo},
   (send, receive) signal set=(kill, hup) peer {label=foo},

   signal peer {label=foo},
   signal hup kill
          peer {label=foo},
   signal=(kill, hup)
          peer {label=foo},
   signal set=(kill, hup)
          peer {label=foo},
   signal peer {label=foo} (send, receive),
   signal (send, receive)
          peer {label=foo},
   (send, receive) signal peer {label=foo},
   signal kill hup peer {label=foo} (send, receive),
   signal=(kill hup) peer {label=foo} (send, receive),
   signal set=(kill, hup) peer {label=foo} (send, receive),
   signal set=(kill, hup) peer {label=foo} (send, receive),
   signal (send, receive) kill hup
          peer {label=foo},
   signal (send, receive) set=(kill, hup)
          peer {label=foo},
   (send, receive) signal kill hup
          peer {label=foo},
   (send, receive) signal=(kill hup)
          peer {label=foo},
   (send, receive) signal set=(kill, hup)
          peer {label=foo},

Fun isn't it (sorry if I missed a combination). This is starting to get
ugly and hard to distiguish.  I don't think anything new is really
falling out of this yet
   peer=()	# this just looks funny with label=foo etc. but I can
                  live with it
   peer { }	# this gets lost fairly easy, but it isn't so bad if
                  its written multiline (in the cases it makes sense)
                  and yes it could possily be broken out better
   receive perm with -> is a little weird (already established)



Now that we have some examples of anonymous ipc, lets look at ipc examples
with addresses. Lets start with shared objects since there is a single
address (no explicit peer).

  Firstly files, fifos, named unix domain sockets, mmaped files, are
  all treated as files at their base.

  /foo rw,
  rw /foo,

  file,

  not any file rule beyond the bare file rule above is currently not
  supported

  file /foo,
  file /foo rw,	
  file rw /foo,
  rw file /foo,

notice that the position of permissions relative to the keyword is not
defined.

   /foo label=bar rw,
   rw /foo label=bar,
   file label=bar,
   file /foo label=bar,
   file /foo label=bar rw,
   file rw /foo label=bar,
   rw file /foo label=bar,

   #this show setting a label on a file, and raises the question of
   #whether this should only show up in create/write rules or whether
   #an open operation can result in a relabel.  If the labeling only
   #happens on create then having the r permsission on the rule is
   #somewhat confusing.
   /foo label:=other rw,
   rw /foo label:=other,
   file label:=other,
   file /foo label:=other,
   file /foo label:=other rw,
   file rw /foo label:=other,
   rw file /foo label:=other,

   #this set raises the question about mixing a label conditional and
   #setting a files label.  This can only make sense if relabeling on
   #open is allowed.  Other wise there is no initial label to be conditional
   #on.
   /foo label:=other label=bar rw,
   rw /foo label:=other label=bar,
   file label:=other label=bar,
   file /foo label:=other label=bar,
   file /foo label:=other label=bar rw,
   file rw /foo label:=other label=bar,
   rw file /foo label:=other label=bar,


and now lets try with some qualifiers
   audit /foo rw,
   audit rw /foo,
   audit file,
   audit file /foo,
   audit file /foo rw,	
   audit file rw /foo,
   audit rw file /foo,
   audit /foo label=bar rw,
   audit rw /foo label=bar,
   audit file label=bar,
   audit file /foo label=bar,
   audit file /foo label=bar rw,
   audti file rw /foo label=bar,
   audit rw file /foo label=bar,
   audit /foo label:=other rw,
   audit rw /foo label:=other,
   audit file label:=other,
   audit file /foo label:=other,
   audit file /foo label:=other rw,
   audit file rw /foo label:=other,
   audit rw file /foo label:=other,
   audit /foo label:=other label=bar rw,
   audit rw /foo label:=other label=bar,
   audit file label:=other label=bar,
   audit file /foo label:=other label=bar,
   audit file /foo label:=other label=bar rw,
   audit file rw /foo label:=other label=bar,
   audit rw file /foo label:=other label=bar,


Now lets look at the alternate form of unix domain sockets, this would
be useful for cases where you want to allow binding a socket to a name
but not allow generic file acces.
   network unix /foo,
   network unix name=/foo,	#do we force name=
   network unix /foo rw,
   netwokk unix name=/foo rw,

now what of abstract sockets? They are the same as unix domain but
begin with \0. We could use this notation or chose an alternate way
of expressing it.
   network unix name=\0foo,
or maybe
   network unix abstract=foo,

Either way we can just ignore abstract for the rest of the examples as
it really is the same patter as regular unix domain sockets.

   network unix /foo,
   network unix name=/foo,
   network unix /foo rw,
   network unix name=/foo rw,
   network rw unix /foo,
   network rw unix name=/foo,
   rw network unix /foo,
   rw network unix name=/foo,

   # now with label at a specific access path
   network unix /foo label=bar,
   network unix name=/foo label=bar,
   network unix /foo label=bar rw,
   network unix name=/foo label=bar rw,
   network rw unix /foo label=bar,
   network rw unix name=/foo label=bar,
   rw network unix /foo label=bar,
   rw network unix name=/foo label=bar,

So did you notice that each example is only treating the socket like
a file? But the socket is NOT a file it has a true peer.  Lets try that
above rules with equiv rules using the pairing syntax.
   #network unix /foo,
   network unix subject=(/foo),
   network unix peer=(/foo),

   #network unix name=/foo,
   network unix subject=(name=/foo),
   network unix peer=(name=/foo),

   #network unix /foo rw,
   network unix subject=(/foo) rw,	# do we really want rw here or just bind
					# and should bind be required since
					# its implied by the subject.
   network unix peer=(/foo) rw,
   #however this rule can be tighten with peering to maybe just the
   #subject
   network unix subject=(/foo) (create, send, receive),
   #or peer
   network unix peer=(/foo) rw,
   #or both as separate
   network unix subject=(/foo) (create, send, receive),
   network unix peer=(/foo) rw,

   #network unix name=/foo rw,
   network unix subject=(name=/foo) rw,	# do we really want rw here or just bind
					# and should bind be required since
					# its implied by the subject.
   network unix peer=(name=/foo) rw,
   #however this rule can be tighten with peering to maybe just the
   #subject
   network unix subject=(name=/foo) (create, send, receive),
   #or peer
   network unix peer=(name=/foo) rw,
   #or both as separate
   network unix subject=(name=/foo) (create, send, receive),
   network unix peer=(name=/foo) rw,

   #network rw unix /foo,
   network rw unix subject=(/foo),	# do we really want rw here or just bind
					# and should bind be required since
					# its implied by the subject.
   network rw unix peer=(/foo),

   #network rw unix name=/foo,
   network rw unix subject=(/foo),	# do we really want rw here or just bind
					# and should bind be required since
					# its implied by the subject.
   network rw unix peer=(/foo),

   #rw network unix /foo,
   rw network unix subject=(/foo),	# do we really want rw here or just bind
					# and should bind be required since
					# its implied by the subject.
   rw network unix peer=(/foo),

   etc... 

I think there has been enough repeation of the pattern that we don't need
to continue. We have the dbus rule examples in the other thread though
without the leading perm. We could do a few unix tcp sockets but its
largely the same, as there is only a small set of the local address
that we can pair to a peer address.  And peering labels has the same
problem as pointed out above.

If people really want I can produce some tcp examples but atm I am
inclined to skip them.


Now lets zag a little and look at generic label based rules. Say I
want to write a generic communication rule to state a subject can
communicate with something with a specific label, and I don't care
what mechanism it uses.
  label=foo rw,
  rw label=foo,

and if I want to limit it to a specific type without an address it
is the same as anonymous ipc.
  pipe label=foo rw,
  pipe rw label=foo,
  rw pipe label=foo,

if we introduce pairing the rules are all similar just with peer=()
  peer=(label=foo) rw,
  rw peer=(label=foo),
  pipe peer=(label=foo) rw,
  pipe rw peer=(label=foo),
  rw pipe peer=(label=foo),


Basically the more I look at it, the less happy I am with pairing, for
several reason.
- it increases the language complexity
- it makes rules uglier and hard to follow
- sockets aren't really paired in the way the pair rule is trying
  to enforce.
  - the local subject address is often anonymous when we have a peer
    address.
  - the bound local address needs perms that aren't going to be specified
    with a peer address (bind, ...)
- the labeling within a pairing is weird when you are trying to be
  conditional based on the local label, that is different from
  that of the subject (profile).
- We have entities like unix domain sockets that exist as both files
  (via file rules) and socket. If we go with pairing based rules,
  then this can be confusing.

  Where if we use non pair addresses they can be basically the rules
  can basically be the same, its just a matter of whether the rule
  starts with file or network unix.

Vs. non-paired addressing
- it allows us to treat all ipc (file and not) with rules that follow
  the same basic pattern.
- we have some inconsistency around permissions and what should be
  allowed. ie should aquire/bind have to be in a separate rule from
  rw.

  I will note when we look at unix domain sockets as files we
  have this mixed behavior.  That is
    /foo rw,

  really means,
     /foo (create, bind, accept, listen, read, write),
     /foo rw,

  ie we have not made a distinction between what is local and what is
  peer.  Should we fix this for the network unix version, or keep
  it the same as files.  And if network unix is the same as files,
  what of dbus and other types?

  It would be good if we can be consistent.


I am inclined to go with non-paired addresses because I see very few
benefits to them, and lots of down sides.  Where non-paired do have
their problems and can not express certain things but are a lot simpler
and can have a consistancy that matches up with shared object rules.
Basically we treat all ipc the same.


Now for a few other issues around ipc.

Generally I think we should allow creation of an ipc method without
a rule/perm unless the creation also implies the binding of a name/address.
Unless their a specific rule the object is given the subjects label
and given permission to read/write its self as a peer.

That is

profile A {
  pipe label:=A (create),
  pipe label=A (read, write),		# could possibly combine
  ...

}

would be implied and the pipe rule would not be necessary

this allows for us to not have to specify rules about a profile creating
and communicating with it self, but also lets us over ride the default
behavior if we have a reason.

eg. we could have

profile A {
  pipe label:=foo (create),

or
  deny pipe (create),
}


Permissions
I am less than happy with the mapping of the letter perms to the
expanded permission set. But I don't see how we can do anything about
this.

there are cases we will want to be finer grained that what rwmlk
can give us.

Specifically I really dislike all the things w provides by default.
Okay enough whining about that.



More side notes:

On parsing leading permissions
I know steve does not like leading permissions because they make the
language harder to parse. This is true. The parser has to treat
unidentified word tokens as if they are permissions and use a semantic
check at a later step to fully parse the meaning of the token. This
is not hard to do but it does add some complexity to the parser.

As for humans, leading perms are sometimes easier and sometimes not.
But the permission is always in a predictable location.


On eager vs late binding

We could use eager binding for implicit labels but we don't because it
allows for more flexibility, and dynamism. A profile can limit access
to a resource based on an address path while and dynamically communicate
with other profiles, until one with a tighter restrictions actual joins
the communication at which point that dynamic tighter restriction is
applied.



More information about the AppArmor mailing list