To protect object access we define a generic set of access operations
(read, write, execute, delete, and protect in the current
implementation2.4) and for each one we have a
Protection object specifying the protection for such operation.
<Off access operations. >= (U->)
// Operations which can be protected.
enum { OFF_OP_R=0, // Read
OFF_OP_W, // Write
OFF_OP_X, // eXecute
OFF_OP_D, // Deletion
OFF_OP_P // Protection
};
typedef int off_op_t;
// # of defined operations.
const natural_t OFF_NOPS = 5;
DefinesOFF_NOPS,OFF_OP_D,OFF_OP_P,OFF_OP_R,off_op_t,OFF_OP_W,OFF_OP_X(links are to index).
To combine individual operations (by a bit-or operation) to specify an
access mode (e.g. OFF_M_R|OFF_M_X for ``read and execute''
access mode) a different data type is provided.
<Off access mode. >= (U->)
// Operations as bits.
enum { OFF_M_R=0x1, OFF_M_W=0x2,
OFF_M_X=0x4, OFF_M_D=0x8, OFF_M_P=0x10 };
typedef int off_mode_t;
const off_mode_t OFF_M_ALL= OFF_M_R|OFF_M_W|OFF_M_X|OFF_M_D|OFF_M_P;
DefinesOFF_M_ALL,OFF_M_D,off_mode_t,OFF_M_P,OFF_M_R,OFF_M_W,OFF_M_X(links are to index).
Also, note that natural_t and other basic types which we will be
using through the document have to be defined in a portable way (i.e.
ensuring that they all have the same size across different platforms).
Three different objects are involved in protection:
Protection object will specify the protection of a given
Resource for different access modes.
Rights object shows the access rights held by the user
accessing the resource.
AccessChecker objects implement the policy by which
access is granted or denied for a given Protection, access
Rights and access mode.
\end{itemize}
The meaning of Protection is then defined by the AccessChecker
object. Every time the system wants to know whether access is granted
or denied, it calls the resource access_granted method (which is
delegated to the AccessChecker with the user Rights and access
op). To change the protection the (also delegated) method
protect is called instead.
<Off access checker. >= (U->) // Checks access for user operations on kernel resources. // class off_AccessChecker { public: // Checks for access rights for the given operation. static boolean_t access_granted( const off_op_t op, const off_Rights &r, const off_Protection &p ); // Changes protection. static void protect(off_Protection &old, const off_Protection &now, const off_mode_t m); };
Definesoff_AccessChecker(links are to index).
Resources that need to be protected will delegate its protection
control to a system-wide AccessChecker. Different implementations
of the off_Protection interface will provide various protection
mechanisms including access control lists and capabilities. The
protection information will be kept in _prot2.5 inside each object being protected.
<Off private members for protected objects. >= (U->) off_Protection _prot; // protection for this resource
Such member should be initialized with a user given protection p
at resource instantiation time.
<Off initialization for p protected objects. >= (U-> U->)
_prot=p;
<Off public methods for protected objects. >= (U->) // Returns the protection. const off_Protection &get_protection(void) const { return _prot; } // Checks access for access rights R and operation OP. inline boolean_t access_granted( const off_op_t op, const off_Rights &r ) { return off_AccessChecker::access_granted(op,r,_prot); } // Sets P as protection for M access mode. inline void protect(const off_Protection &p, const off_mode_t m=OFF_M_ALL) { off_AccessChecker::protect(_prot,p,m); }
The second one is exported to system users.
<Other public methods of off_uResource. >= (U->) [D->]
// Sets P as protection for M access mode.
void protect(const off_Protection &p, const off_mode_t m, const off_Rights &r);
The specific Protection mechanism being used can be changed
without disturbing the rest of the code. Once the best
protection mechanism be known empirically, the system can be optimized
by hardwiring it and avoiding dynamic dispatching.
Initially, a Protection is defined as a big random number per
operation. A special value is defined to grant access without further
checking. A Rights object is also a big random number. They are
used as capabilities. The AccessChecker considers the access to
be granted if Rights matches the numbers found in Protection
for the access mode being specified. We believe that any other
protection model can be quickly incorporated.
<Off protection. >= (U->)
// Random nb. used to protect operations.
typedef unsigned32_t off_cap_t;
// When set, grants access without further checking.
const off_cap_t OFF_CAP_ALLOW=0;
// Protection information for system resources.
//
class off_Protection {
public:
off_cap_t cap[OFF_NOPS]; // One random number per operation.
// Initializes a protection object.
// By default access is granted.
off_Protection(void){
for (natural_t i=0; i <OFF_NOPS; i++)
cap[i] = OFF_CAP_ALLOW;
}
<Other public methods of off_Protection. >
};
Definesoff_Protection(links are to index).
There is another constructor to set the same protection for every
access mode. Besides, it makes possible implicit conversion from
off_cap_t to Protection.
<Other public methods of off_Protection. >= (<-U) [D->]
// Initializes a protection object s.t. cap is used for every
// access mode.
off_Protection(const off_cap_t a_cap){
for (natural_t i=0; i <OFF_NOPS; i++)
cap[i] = a_cap;
}
This method generates a random protection.
<Other public methods of off_Protection. >+= (<-U) [<-D]
// Randomize this.
void randomize(void);
<off_Protection::randomize implementation. >= (U->)
// Randomize this.
void off_Protection::randomize(void)
{
off_cap_t a_cap=(off_cap_t)rand();
for (natural_t i=0; i <OFF_NOPS; i++)
cap[i] = a_cap;
}
<Off access rights. >= (U->) // Access rights for system users. // class off_Rights { public: off_cap_t cap; };
Definesoff_Rights(links are to index).
The implementation of AccessChecker methods is thus trivial if we
assume that valid access modes match with valid operations (i.e. an
access mode cannot contain most than one operation).
<off_AccessChecker::access_granted implementation. >= (U->)
// Checks access for access rights R and operation OP.
inline
boolean_t off_AccessChecker::access_granted( const off_op_t op,
const off_Rights &r ,
const off_Protection &p )
{
assert(off_op_valid(op));
return (r.cap == p.cap[op]);
}
Note how we used assert to ensure that op is valid. We will be
using it through the kernel code. Assertions are checked when
NDEBUG is not defined. We will also use kassert for a few
critical assertions which should be checked even when NDEBUG has
been defined.
<off_AccessChecker::protect implementation. >= (U->)
// Changes protection.
void off_AccessChecker::protect(off_Protection &old,
const off_Protection &now, const off_mode_t m)
{
assert(off_mode_valid(m));
if (OFF_M_R&m)
old.cap[OFF_OP_R] = now.cap[OFF_OP_R];
if (OFF_M_W&m)
old.cap[OFF_OP_W] = now.cap[OFF_OP_W];
if (OFF_M_X&m)
old.cap[OFF_OP_X] = now.cap[OFF_OP_X];
if (OFF_M_D&m)
old.cap[OFF_OP_D] = now.cap[OFF_OP_D];
if (OFF_M_P&m)
old.cap[OFF_OP_P] = now.cap[OFF_OP_P];
}
Finally, it should be clear that every system object exporting a
subset of methods to the user is responsible of calling
access_granted with the appropriate access mode at the beginning of
every exported operation. Otherwise protection will not take effect.
Methods like off_op_valid and off_mode_valid will be defined
for different data types so that assertions are easy to write.
<Off valid access operation and mode. >= (U->) extern inline boolean_t off_op_valid( const off_op_t op) { return (op >= OFF_OP_R && op <= OFF_OP_P); } extern inline boolean_t off_mode_valid( const off_mode_t m) { return (m&~OFF_M_ALL)==0; }
Definesoff_mode_valid,off_op_valid(links are to index).
\subsubsection{Protection \cpp{} source files}
Protection definitions are kept in a couple of files named
klib/prot.h and klib/prot.C.
<prot.h*>=
<Read the literate code instead warning. >
#ifndef __OFF_PROT_H
#define __OFF_PROT_H 1
<Off protection dependencies. >
<Off access operations. >
<Off access mode. >
<Off valid access operation and mode. >
<Off protection. >
<Off access rights. >
#ifdef __KERNEL__
<Off access checker. >
<off_AccessChecker::access_granted implementation. >
#endif // __KERNEL__
#endif // __OFF_PROT_H
<prot.C*>= <Read the literate code instead warning. > #include <klib/prot.h> // Exported interface. <off_Protection::randomizeimplementation. > <off_AccessChecker::protectimplementation. >
As we have seen, AccessChecker is used only inside the kernel. The
symbol __KERNEL__ will be defined only when compiling a kernel
image.
We used boolean_t, natural_t, and rand through the
code. We have to define such things.
<Off protection dependencies. >= (<-U) #include <assert.h> // for assert #include <flux/types.h> // for boolean_t natural_t et al. #include <stdlib.h> // for rand
Finally, every source file will include an advice for those willing to read the compiler source code instead of the literate source code (this file).
<Read the literate code instead warning. >= (<-U <-U U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U-> U->) // // === DO NOT READ OR MODIFY THIS FILE === // // This is a source file generated by the noweb literate // programming tool. // // If you have the chance read the literate source code instead. //