Section 12.3. Design Principles


12.3. Design Principles

The reference policy is structured around several design principles. These principles are focused on achieving the goals of the project. Currently, most of these principles are enforced only through convention; as high-level development environments and tools evolve on top of the reference policy, we expect to see these principles start to be more strictly enforced by the build tools themselves.

12.3.1. Layering

As discussed in the next section, the reference project achieves most of its design goals through strong modularity. A weak, although still important, design principle of the reference policy is the layering of its modules. The layers provide a loose organizational structure for the modules that reflects the overall system architecture. Figure 12-1 depicts the layers currently defined for reference policy.

Figure 12-1. Reference policy layers and sample modules within a layer


In general, the reference policy tries to keep dependencies between modules within a layer or to a layer "below" the module's layer. We can find the layer directories, which contain the modules for each layer, in policy/modules/. The reference policy currently defines the following layers:

  • Kernel This layer contains policy modules that directly relate to the Linux kernel. This is the lowest layer of modules. Modules at this layer include policy statements for the kernel, devices, filesystems, and basic networking. Most of these modules will always be included in any type of policy.

  • System These are policy modules that are also usually included in a policy but do not directly support the kernel. Modules at this layer include policy for common libraries, login processes, and network management.

  • Services This layer contains policy modules for all services and daemons not part of the system layer. These modules range from cron, to sshd, to apache.

  • Admin This layer contains policy modules for administrative tools and commands that have their own domain type.

  • Apps This layer contains policy modules for all other programs that have their own domain type and policy module.

Again, the layering is not strictly enforced and is primarily useful as a way to organize the collection of modules. As you can see from Figure 12-1, some of the layers are really peer groupings rather than "layered" (that is, services, admin, and apps).

12.3.2. Modularity

Modularity is the strongest design principle of the reference policy. Although the example policy discussed in Chapter 11 has a notion of modules, these modules have loose conventions resulting in tightly coupled modules (primarily due to the use of global type and attribute names). In the reference policy, modules are required to be loosely coupled. This loose coupling is achieved through the enforcement of two strong design conventions: encapsulation and abstraction.

12.3.2.1. Encapsulation

Encapsulation is a reference policy modularity convention that requires that type and attribute names may only be used within a single module. In effect, type and attribute names may not be used as global names. Only the module that defines the type/attribute may reference the name directly. Any other module that would require the use of the type/attribute must do so through well-defined interfaces that the owning module defines.

For example, in the example policy, all types that are domain types are given the domain attribute. Every policy module simply has this knowledge built in and explicitly adds domain to the list of attributes for all domain types they define. If we decided to change how the concept of a domain was implemented in the policy (say by granting each type explicit rules or even by renaming the attribute), we would have to change every module that defines a domain type.

In the reference policy, a module called "domain" in the kernel layer defines the concept of a domain. It just so happens that this concept is implemented using the domain attribute as with the example policy. However, this implementation detail is private to the domain module and could be changed (for example, renamed) without impacting any other module source. Any module that wants to make one of its types a domain type would call an interface defined in the domain module:

domain_type(my_type)   # interface to make a type a domain type


The domain module interfaces in policy/modules/kernel/domain.if; we discuss this interface in more detail later in this chapter.

Encapsulation enables us to make the reference policy modules' implementation details private to the module resulting in loosely coupled modules.

12.3.2.2. Abstraction

Abstraction is a design goal where interfaces describe what abstract access they provide and not how they do it. The intent of reference policy interfaces is to describe what abstract access is given or system capability is being enabled with the interface. The policy statements required to enable that access should not be a concern of the interface caller. For example, the macro we discussed previously to make a type a domain type is called domain_type() and not add_domain_attribute(). The intent of the interface is to make a type a domain type; doing this by adding the domain attribute is just the private implementation detail of the domain_type() interface. This interface could have instead simply added explicit rules for each individual type provided with the interface, and we can still change that implementation if we choose without impacting other modules that use this interface.

As another example, to allow a directory to be used as a mount point we would call the file_mountpoint() macro in the "files" module. We do not need to know that the implementation of this interface applies the attribute mountpoint to all directory types called with this interface and then defines rules for the attribute to allow the type to be used as a mount point. As a policy writer, all we need to know is that the file_mountpoint() interface is how we allow a directory type to be a mount point.

Currently, the reference policy has a low-level of interfaces implemented within each module. Eventually higher-level abstractions will be developed through additional interfaces that combine the lower-level interfaces.

12.3.2.3. Module Files

As discussed earlier, within the reference policy source tree all modules are kept in policy/modules/[layer]/ where the layer is a directory whose name coincides with one of the layers discussed previously. Each module must consist of three related files, all of which have the same root name (that is, module name):

  • Private policy file (.te) This file contains the module private declarations and rules. In general, all module type and attribute declarations are contained in the .te file and the rules that give these types and attributes their core access.

  • External interface file (.if) This file contains the module interfaces. These interfaces are the means by which other modules access the types and attributes of this module.

  • Labeling policy file (.fc) This file contains the file context labeling statements relating to this module (see Chapter 10, "Object Labeling").

Because a strong requirement is that no type or attribute be global, only the .te and .if file for a given module may use the module's type/attribute names explicitly. All other references to a module's types and attributes must be via the module's interfaces.

12.3.2.4. Interfaces

As discussed previously, one of the most significant improvements implemented in the reference policy is the use of interface macros for gaining access to a type outside of the module in which the type is defined. Interfaces provide access to a module's policy resources (for example, to its privately declared types and attributes). All other modules needing a particular access use the same interface; therefore, the policy rules required for the access will be consistent across all users of the interface. Therefore, policy changes for access to a type require only a change in one place, rather than requiring changes to all the modules that use the type as is common in the example policy.

As noted above, interfaces are kept in a module's .if file and are implemented as macros. Currently, reference policy supports two kinds of interfaces: access interfaces and template interfaces.

The name we give each interface follows the convention of modname_purpose. So, for example, we can tell that the domain_type() interface is defined in the "domain" module and its purpose is to make a provided type a domain type. (We avoid the verbose name such as domain_domain_type() when the module name is also part of the purpose.)

12.3.2.4.1. Access Interfaces

The most common kind of interface is called an access interface. As its name implies, the purpose of an access interface is to provide some type of access that requires use of the module's private types and attributes. Access interfaces are implemented using the interface() macro. The domain_type() interface is an example of an access interface. Let's examine this interface more closely (see Listing 12-1).

Listing 12-1. Partial Interface Listing for domain_type Access Interface (domain.if)

1 ######################################## 2 ## <summary> 3 ## Make the specified type usable as a domain. 4 ## </summary> 5 ## <param name="type"> 6 ## Type to be used as a domain type. 7 ## </param> 8 interface('domain_type',' 9    domain_base_type($1) 10 11   # Use trusted objects in /dev 12   dev_rw_null_dev($1) 13   dev_rw_zero_dev($1) 14   term_use_controlling_term($1) 15 16   # read the root directory 17   files_list_root($1) 18 19   # send init a sigchld and signull 20   init_sigchld($1) 21   init_signull($1) 22 23   ifdef('targeted_policy',' 24         unconfined_use_fd($1) 25         unconfined_sigchld($1) 26   ') 27 28   tunable_policy('allow_ptrace',' 29         userdom_sigchld_sysadm($1) 30   ') 31 32   # allow any domain to connect to the LDAP server 33   optional_policy('ldap',' 34         ldap_use($1) 35   ') 36 ')

In line 8 of Listing 12-1, we see the interface() macro, which is implemented as an m4 macro, as are all macros in reference policy. The interface() macro is what we use to define an access interface. This and other macros that support the conventions and build process of reference policy (collectively called support macros) are located in one of the files in policy/support/ directory. The interface() macro handles the details of defining an interface and a central spot where debugging and other build information can be inserted into the results. Therefore, we must always use the interface() macro to define an access interface.

The purpose of the domain_type() interface is to allow the provided type (the only argument for this macro, $1) to be used as a domain type in the policy. We see a description of the interface and its arguments in lines 1 through 7. Reference policy uses XML to capture information about interfaces and other aspects of the policy for generation of documentation. In this case, we have a summary of the interface purpose and a list of its parameters, all of which will be included in a list of interfaces when the documentation is generated.

Lines 9, 12 through 14, 17, 20, and 21 are all calls to other interfaces. The name of an interface give us a hint of the module where the interface is defined. As noted, by convention, the first component of all interface names is the name (or partial name) of the module in which the interface is defined. For example, the interfaces dev_rw_null_dev() and dev_rw_zero_dev() are defined in the "devices" module (policy/modules/kernel/devices.if) and the interface files_list_root() in the "files" modules (policy/modules/kernel/files.if). We can examine each of these interfaces to see how they are implemented or we can examine the interface documentation if all we want is a description of each interface.

Note

The command make html creates the reference policy documentation including the interface descriptions; open doc/html/index.html with a browser to see the documentation.


Lines 23 through 26 show a use of the m4 ifdef statement. Although ifdef was commonly used in the example policy, for reference policy the use of ifdef is greatly limited by convention (see the sidebar on page 184 for more information). In this case, we call additional interfaces (from the targeted policy-specific "unconfined" module) if we are building a targeted policy. The symbol targeted_policy is defined as part of the build process based on the options in build.conf, which we discuss later in this chapter.

Lines 28 through 30 show another support macro, tunable_policy(). The purpose of this macro is to allow conditional behavior based on the value of a defined tunable. As noted previously, tunables, which are intended as build/install time policy options, are defined in policy/global_tunables. Currently, tunables are implemented using Booleans, but eventually with loadable modules we expect the implementation of tunables to differ from conditional policy Booleans. In this case, we have a tunable allow_ptrace, which when true allows the administrative user domain(s) to debug any other user domain type.

Finally, let's examine lines 33 through 35 of Listing 12-1, where we have an example of the optional_policy() support macro. This macro enables us to optionally call an interface depending on whether a module is included in the policy. This support macro implements this capability differently depending on whether a monolithic policy, base module, or loadable module is being built. Nonetheless, the concept from a policy writer's perspective is the same. If the module is being included in the build process (in this case, the ldap module), the interface ldap_use() is also called.

Allowed Uses of ifdef

In the reference policy, the m4 ifdef statement may only be used, by convention, for a limited set of defined conditions. These become hard-coded implementation variations within the policy build process. All other forms of policy options must use either the native conditional policy statements (if) based on Booleans defined in policy/global_booleans or one of the reference policy support macros such as tunable_policy() for tunables defined in policy/global_tunables or optional_policy() for optional policy statements based on the name of a module. These support macros allow us to change the implementation of these concepts to better support the build process and development tools in the future.

The only allowed use of ifdef in reference policy is with the following defines:

targeted_policy

This is defined when a targeted policy is being built.

strict_policy

This is defined when a strict policy is being built.

enable_mls

This is defined when the optional MLS policy is being built.

enable_mcs

This is defined when the optional MLS features are being used to build an MCS policy.

hide_broken_symptoms

This is used to control dontaudit rules; we place all such rules that mask expected denial audit messages (that is, due to access we intentionally did not allow but we expect the program to attempt even though it is not needed). These dontaudit rules help remove benign "false positive" audit messages we expect to see during normal operation.

direct_sysadm_daemon

This enables us to determine whether the policy permits the system administrator user domains to directly control daemons that otherwise are started and controlled by init. Note that if this is disabled, the administrator may still control daemons with the run_init tool.

distro_tunable

One of several distribution tunables can be set for policy variations specific to a particular distribution of Linux. For example, redhat is the tunable for Fedora Core (FC) and Red Hat Enterprise Linux (RHEL) systems, and gentoo is the tunable for Gentoo systems.



12.3.2.4.2. Template Interfaces

The second type of interface is a template interface, which is far less common than an access interface. A template interface is necessary when two modules share responsibility for one or more types. We call this kind of a type a derived type. A derived type name is derived from the calling module's type. From a logical perspective, the derived type is considered a private type of the calling module. However, the definition of the derived type and the core set of rules that define access for that type are implemented in a template interface in another module (that is, the called module). This is necessary because the called module is creating access rules for policy permissions only it understands, but on behalf of the calling module. In all cases, the derived type name is partly based on a name provided by the calling module and a name provided by the called module. Neither fully knows the name; this keeps our abstraction intact and allows us to change the template interface without impacting the calling module.

An example of a derived type and a template interface can be found in the ssh module, which implements the client and server policy rules for the Secure Shell service (sshd). We should find this module in policy/modules/services/ssh.*. In particular, we want to examine the template interface ssh_per_userdomain_template(), a partial listing of which is shown in Listing 12-2.

Listing 12-2. Partial Interface Listing for ssh_per_userdomain_template Interface (ssh.if)

 1 #######################################  2 ## <summary>  3 ## The per user domain template for the ssh module.  4 ## </summary>  5 ## <desc>  6 ## <p>  7 ## This template creates a derived domains which are used  8 ## for ssh client sessions and user ssh agents.  A derived  9 ## type is also created to protect the user ssh keys. 10 ## </p> 11 ## <p> 12 ## This template is invoked automatically for each user and 13 ## generally does not need to be invoked directly 14 ## by policy writers. 15 ## </p> 16 ## </desc> 17 ## <param name="userdomain_prefix"> 18 ## The prefix of the user domain (for example, user 19 ## is the prefix for user_t). 20 ## </param> 21 ## <param name="user_domain"> 22 ## The type of the user domain. 23 ## </param> 24 ## <param name="user_role"> 25 ## The role associated with the user domain. 26 ## </param> 27 template('ssh_per_userdomain_template',' 28    ############################## 29    # Declarations 30 31    type $1_home_ssh_t; 32    userdom_home_file($1,$1_home_ssh_t) 33    role $3 types $1_ssh_t; 34 35    type $1_ssh_t; 36    domain_type($1_ssh_t) 37    domain_entry_file($1_ssh_t,ssh_exec_t) 38 39    type $1_ssh_agent_t; 40    domain_type($1_ssh_agent_t) 41    domain_entry_file($1_ssh_agent_t,ssh_agent_exec_t) 42    role $3 types $1_ssh_agent_t; 43 44    type $1_ssh_keysign_t; #, nscd_client_domain; 45    domain_type($1_ssh_keysign_t) 46    domain_entry_file($1_ssh_keysign_t,ssh_keysign_exec_t) 47    role $3 types $1_ssh_keysign_t; 48 49    # Private policy for each derived types not shown 50    #     see policy/modules/ssh.if 51 52    # remainder not shown... 53 ')

The ssh_per_userdomain_template() interface creates a per-domain set of derived types that allow each domain to have private types for their ssh sessions and cryptographic keys. Because the ssh module cannot (and should not for modularity reasons) know all the possible domain types that need a per-domain ssh type, it cannot possible directly create the rules necessary in its .te file. Likewise, any given module that wants an ssh private type for its domains cannot (and again should not) possibly know how to implement ssh private session and key types. Thus the need for a template interface.

As you can see from Listing 12-2, this interface takes three arguments: the base type name prefix (for example, for the domain type user_t we would provide the prefix user), the user domain type (for example, user_t), and the primary role associated with the user domain. A template interface has two primary sections. The first section is where the derived types are created (using the provided type name prefix). We can see these declarations in lines 31 through 47 of Listing 12-2. For example, line 35 defines the main derived domain type. If the prefix were user, the derived domain type would be user_ssh_t and that would be the domain type for the ssh client when the domain user_t runs it (as implemented by this interface). As you can see, three other derived types are also created for various aspects of an ssh session.

The second part of a template interface is the private rules for the derived type. This is where rules are defined for all derived types of this kind, and it is the one place we need to change them. We do not examine these rules in this book, but you are encouraged to do so on your own. Although template interfaces are uncommon, they are valuable to simplify certain types of policy writing.




SELinux by Example(c) Using Security Enhanced Linux
SELinux by Example: Using Security Enhanced Linux
ISBN: 0131963694
EAN: 2147483647
Year: 2007
Pages: 154

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net