Now I'm under working on access control feature with engine framework.
The git repository is at:
  http://github.com/kaigai/memcached-selinux

Under the development, I could find some matters to implement such kind
of features on the engine framework.

(1). We have no server API to obtain socket file descriptor.

SELinux provides an API to get security context of the peer process which
connects to the server process. This API takes an argument to inform socket
file descriptor.
However, memcached encapsulates conn->sfd, and we have no official interface
to translate the given cookie pointer to the socket file descriptor.
As a workaround, I cast the cookie into conn, and refer the socket.

I need a server API to obtain a socket file for the given cookie.


(2). Order of the invocation of callback functions.

The SELinux module wants to use engine specific data region in the conn
structure, it defines the following data structure which also provide
a capability to sore engine specific data of the secondary module.

  typedef struct selinux_connect
  {
      security_id_t   sid;    /* security context of the client process */
      void           *data;   /* private data for the secondary engine */
  } selinux_connect_t;

The selinux_connect_t object is allocated and initialized at ON_CONNECT
callback, and it allows secondary module to register its engine specific
data. Then, it is released at ON_DISCONNECT callback.

It means ON_CONNECT callback of SELinux must be invoked earlier than any
other modules, and ON_DISCONNECT callback of SELinux must be invoked last.
But we have no way to ensure the order of callback invocations.

Now, I did a hack depending on internals of memcached.
The memcached calls a callback function earlier registered later, so
I registered ON_DISCONNECT callback prior to initialization of the secondary
engine, then I registered ON_CONNECT callback after the initialization.

  http://github.com/kaigai/memcached-selinux/blob/selinux/selinux_engine.c#L310

As long as the secondary engine registers its callbacks in the initialize()
handler, it will work well.


(3). ENGINE_HANDLE is not delivered to a few engine handlers.

The engine framework does not deliver ENGINE_HANDLE pointer to the following
handlers.
 - item_get_cas()
 - item_set_cas()
 - item_get_key()
 - item_get_data()
 - item_get_clsid()

If we use the first dozens bytes of the item data field to store security
context of the item, we have to specify how many first bytes will be used
to store the security attribute.
The ENGINE_HANDLE can contain some of meaningful information, and here is
no reason why we cannot deliver it.


(4). The way to store per item security attribute.

It is the most headache matter for me.

A straightforward idea is to store security context of the item as a part
of data field.
For example, when we receive a pair of key="aaaa" and value="xyzxyz",
selinux engine can modify the given data field, then it delivers the modified
key/value pair. If the default security context is "classified", it may be
modified to "classified\0xyzxyz", for instance.
The secondary module will store the given key / modified value pair as is,
even if it has backing storage system.

User    -----+
             |  key = "aaaa", value="xyzxyz"
             V
           Memcached
             |             <-- nbytes -->
             |   |  aaaa  |    xyzxyz    |
             |   |________|______________|
             |       key      value
             |
             V
           selinux engine
             |             <--- nbytes + 11 ------>
             |   |  aaaa  |  classified\0 | xyzxyz |
             |   |________|________________________|
             |       key              value
             V
           secondary engine (such as default_engine)

However, here is a headache.
The secondary engine assumes the data field of the item is numeric on
CAS operations, although the primary engine modified the data field to
store its security attribute.

One idea is to add "offset" as an argument of the engine handlers.
In above example, the value has 17 bytes in total. 11 bytes of them are used
to selinux engine, and rest of them are used to the secondary engine.

If item_set_cas() handler has an offset argument to inform secondary engine
how many bytes are already consumed by the primary engine, we can store the
modified value correctly, even if selinux engine modified it.

In addition, the core memcached should not refer it->nbytes directly,
because it->nbytes is the total length of the item with value and security
context. The item_get_data() should return both of data pointer and nbytes
that engine modules want to show the core memcached. In this case, it should
return 6, not 17.


(5). The remove() method

When its definition is modified to deliver the key, not item itself?
The memcached calls ->get() method prior to ->remove() invocations, although
the given request never wants to read contents of the item.

Thanks,
-- 
KaiGai Kohei <[email protected]>

Reply via email to