Skip to content
This repository has been archived by the owner on May 24, 2023. It is now read-only.
Markus Linnala edited this page Jun 9, 2021 · 7 revisions

SELINUX COMMON INTERMEDIATE LANGUAGE MOTIVATION AND DESIGN

Authors:

Karl MacMillan <[email protected]>

Caleb Case <[email protected]>

Josh Brindle <[email protected]>

Chad Sellers <[email protected]>

Status

This design document is currently a proposal and is being distributed for questions, comments, and criticism.

Version: 1.0

Introduction

The SELinux Common Intermediate Language (CIL) is designed to be a language that sits between one or more high level policy languages (such as the current module language) and the low-level kernel policy representation. The intermediate language provides several benefits:

  • Enables the creation of multiple high-level languages that can both consume and produce language constructs with more features than the raw kernel policy (e.g., interfaces). Pushing these features into CIL enables cross-language interaction.
  • Eases the creation of high-level languages, encouraging the creation of more domain specific policy languages (e.g., CDS Framework, Lobster, and Shrimp).
  • Provides a semantically rich representation suitable for policy analysis, allowing the analysis of the output of multiple high-level languages using a single analysis tool set without losing needed high-level information.

Design Philosophy

CIL is guided by several key decision principles:

  • Be an intermediate language - provide rich semantics needed for cross-language interaction but not for convenience. If a feature can be handled by a high-level language without sacrificing cross-language interoperability leave the feature out. Less is more.
  • Facilitate easy parsing and generation - provide clear, simple syntax that is easy to parse and to generate by high-level compilers, analysis tools, and policy generation tools. Machine processing should be prioritized higher than human processing when there is a conflict as humans should be reading and writing high-level languages instead.
  • Fully and faithfully represent the kernel language - the ultimate goal of CIL is the generation of the policy that will be enforced by the kernel. That policy must be full represented so that all of the policy can be represented in CIL. And that representation should not adorn, obscure, or otherwise hide the kernel policy. CIL should allow additional high-level language semantics but should not abstract away the essence of the kernel enforcement. Be C (portable assembler) not a pure functional language (which hides how the processor actually works).
  • The only good binary file format is a non-existent one - CIL is meant for a source policy oriented world, so assume and leverage that. The only binary policy format moving forward should be for communication with the kernel.
  • Enable backwards compatibility but don't be a slave to it - source, but not binary, compatibility with existing policies is a goal but not an absolute requirement. Where necessary it is assumed that manual or automated policy conversion will be required to move to enable the freedom needed to make CIL compelling.
  • Don't fix what isn't broken - CIL is an opportunity to make bold changes to SELinux policy, but there is no reason to re-think core concepts that are working well. All changes to existing language constructs need a clear and compelling reason. One key aspect of the current policy to retain is it's order-independent, declarative style.
  • No more M4 - the pervasive use of M4 and pre-processing in general has eased policy creation, but the side-effects cause many additional problems. CIL should eliminate the need for a pre-processor.
  • Shift more compilation work to happen per-module instead of globally - the current toolchain performance is often driven by the size of the policy and the need to have the entire policy loaded to do much of the processing. If possible, make it possible to do more compilation of one module at a time to increase performance. At the very least, clearly identify and manage language constructs that cause work on the global policy.

Goals and Primary Features

CIL is meant to enable several features that are currently difficult or impossible to achieve with the current policy languages and tools. While generality is always a goal, with CIL there are also several well-known and clear motivating language needs.

  • Policy customization without breaking updates - one of the challenges in SELinux is allowing a system builder or administrator to change the access allowed on a system - including removing unwanted access - while not preventing the application of future policy updates from the vendor. It is desirable, therefore, to allow an administrator to make changes to vendor policy without necessitating the direct modification of the shipped policy files. This is most clearly seen when an administrator wants to remove access allowed by a vendor policy that is not already controlled by a policy boolean.
  • Interfaces as a first class feature - interfaces, and macros before them, have been a successful mechanism to allow policy authors to define related sets of access and easily grant that access to new types. However, this success has been hampered by interfaces existing solely as pre-processor constructs, preventing compilers, management tools, and analysis tools from understanding them. This has many unintended consequences, including the need to recompile all modules to include the changes to an interface. Interfaces or some similar construct should become first class language features.
  • Rich policy relationships - templates, interfaces, and attributes are currently the only means of quickly creating new types or sets of types with commonly needed access. However, use of these constructs require up-front design by the policy developer, limiting their use by system builders and administrators to rapidly create or mold existing policy. Policy authors need language features to create new types or modules based upon existing ones with large or small changes. These features should allow ad-hoc creation of new policy modules or types related to existing types.
  • Support for policy management - semanage and related tools currently make policy modifications using private data stores and code to directly manipulate the binary policy format before it is generated for loading into the kernel. These tools should be able to generate and consume CIL to accomplish the same goals.

Motivating Use Cases and Requirements

Actors

There are several types of SELinux policy authors:

  • Policy Architect - creates widely used, broadly applicable "reference" policies. Acts as a reviewer and maintainer for policy created by other policy authors. Is interested in configurability, generality, maintainability, and backwards compatibility.
  • Distribution Policy Maintainer - adapts an upstream policy to a particular Linux distribution. Often creates new policy or adapts existing policy to a distribution or particular versions of an application. Is interested in rapidly closing bug reports without sacrificing security. Must make practical trade-offs constantly in a maintainable way.
  • Appliance Policy Maintainer - creates custom, single use systems adapted from existing Linux distributions. Maintains whole system policies based upon reference or distribution policies. Is concerned with absolute security but also has to pull application updates from a distribution. Wants to make deep modifications to the policies but doesn't want to miss policy updates made in conjunction with software changes.
  • Re-spinner - wants to create a re-spin distribution with custom policy. The policy might meet unique security or functional goals. The re-spinner has many of the same concerns as the appliance builder, but he must also allow modifications to his policy by end users or administrators of system built from his respun distribution. Is likely more concerned with compatibility with upstream policies and distributions.
  • Professional Administrators - professional administrators maintain production Linux systems and often combine a distribution, hardware, and third-party open source or proprietary software into a solution. This administrator will likely have deep knowledge of Linux systems from a functional point of view, but may lack knowledge of programming or Linux internals. They are concerned with creating a secure system, want to make changes to the distribution policy, and may create and maintain custom policy modules. They will likely want to track distribution software and policy updates and will have infrastructure to test updates before rolling into production.
  • Slacker administrators - these administrators will have similar concerns to the professional administrators, but will likely lack test infrastructure, professionalism, and time to make customizations. They are likely more concerned with making things work.
  • Desktop users - users that want to make minor policy modifications to make their laptops or desktops work. They likely want the distribution to handle making things secure for them - they just want to tweak things for their personal setup.

Other CIL users include:

  • Domain specific policy creators - designers and implementers of languages that target CIL.
  • Analysis tools developers
  • SELinux infrastructure - tools, such as semanage, that will make policy modifications through CIL.
  • Policy analyst

Use Cases

Removing access from existing domains

  • Spencer (professional administrator) has a relatively stock Fedora system running Apache and Postfix in his office. They system has two network cards and Spencer wants to restrict apache to communicating on eth0 (the internet connected network card) and postfix to eth1 (the internal network).
  • Chad (professional administrator) has a system that runs amanda for backup. He wants to prevent the backup program from storing his private keys and will instead rely on a custom backup procedure to protect these. He wants to remove the ability for amanda to read all files.
    Chad also wants to remove access to the private key from all other domains except his webserver.
  • Brandon (a re-spin policy maintainer) wants to replace all calls to corenet_tcp_sendrecv_all_nodes with calls to corenet_tcp_sendrecv_generic_node so that he can relabel network interfaces to revoke access.
  • Brandon (a re-spin policy maintainer) wants to replace the call to corenet_tcp_connect_all_ports with corenet_tcp_connect_generic_port and corenet_tcp_connect_xserver_port. He wants all calls to that interface - including those from modules added by the administrator - to include the modified access.

Adding policy

  • Chad (professional administrator) wants to add the postgrey policy module (postgrey is a postfix grey listing service). This module is not shipped by his distribution, so he needs to add it from the upstream reference policy. However, the module was not developed for his distribution, so he needs to adapt it slightly by and adding some file contexts and adding some missing access (postfix_spool_t sock_file access and postfix_spool_t dir access)
    Chad's wants to make certain that his module is active even if the distribution includes the module in an update but also wants to have the distribution module installed if shipped. He then can examine the distribution provided module, add any of his custom updates, and convert to using the distribution policy.

Creating policy based upon existing policy

  • Brandon (appliance policy developer) wants to ship a system that runs two ssh daemons, each confined to communication on a single network interface to a known list of network addresses. Additionally, he wants to control the domains reachable by the ssh daemon (e.g., different confined users). Unfortunately, the current ssh daemon policy is not templated to allow this and Brandon doesn't want to reimplement the policies from scratch. He would rather create to new ssh daemon modules based upon a subset of the access allowed in the original policy. He then wants to make certain that the more powerful original ssh daemon types are not active in the loaded policy.
    In this example, each ssh module will have a set of types (for the process, pid files, etc.) and the types within a module should have the same access relative to each. Additionally, the domain types for each ssh daemon should have almost the same access to types outside of the module (with the exception of the unique access to achieve the security goals). However, each ssh module should not have access to the types in the other ssh modules.
  • Dan (distribution policy maintainer) wants to create a new unconfined type that unconditionally has the execmem permission. He wants this type to have the same access as the current unconfined type, including the same type transitions out of the domain. However, the type should have separate entrypoints and default transitions into the domain.
  • Marshall (an appliance policy maintainer) wants to create a domain for virtual machines and wants to create new instances of this domain on demand (e.g., vm0, vm1). Each vm domain will have slightly different access to removable devices and networking. This is similar to the goals of sVirt.
  • Marshall (an appliance policy maintainer) wants to create new roles but with substantially different access from the roles that come with the upstream policy he is modifying. He wants to create new role templates based upon the existing templates with large changes to the allowed access.

Policy Abstractions

  • Chris (a policy architect) wants to simplify common file and permission sets so that a policy author can write file : rw_file_perms instead of file : { read write getattr . . . }.
  • Karl (a policy language designer) wants to simplify the object classes and permissions so that a policy author can simple write file : rwx (for read, write, execute) or net : s (for send). He also wants to allow a policy author to use 'inline assembler' to refer to exact permissions when he so desires.
  • Chris (a policy architect) wants to generate policy based upon parameters. For example, he wants to generate a standard set of levels and categories based on a build time parameter giving the number of each.
  • _TODO_ - finish the abstractions needed including roles, users, etc.

Backwards Compatibility

  • Handling new permissions without changing individual modules.

Adapting Policy

  • Chad (a professional administrator with a strange love of solaris) wants to locate all home directories at /export/home instead of home. He wants to easily change all of the user file labeling to reflect this change.

Tools (e.g., semanage)

  • Currently semanage is additive - in other words it can add a port label or change a port label that it currently manages, but it cannot override a specific port label added via a module. This behavior should change to allow semanage to override all policy.
  • Add / remove / view labeling policy including:
  1. file contexts
  2. network interfaces
  3. ports
  4. login contexts
  • Add / remove / view linux user to SELinux user assignments and SELinux user to role assignments.
  • TODO - add the rest of the semanage needs.

Design Overview

The design aims to provide simplicity in several ways:

  1. The syntax is extremely regular and easy to parse being based upon s-expressions.
  2. The statements are reduced to the bare minimum. There is one - and only one - way to express any given syntax.
  3. The statements are unambiguous and overlap in very well defined ways. This is in contrast to the current language where a statement, such as a role statement, might be a declaration, a further definition, or both depending on context.

The language, like the existing policy languages, is declarative. It removes all of the ordering constraints from the previous languages. Finally, the language is meant to be processed in source form as a single compilation unit - there is no module-by-module compilation. This has advantages (no need for compiled disk representation, better error reporting, simpler processing) with the primary disadvantage of space. However, this is not a problem in practice as the linking process for the binary policy modules required the entire representation in memory as well. It is, in many ways, a natural result of the declarative nature of the language.

In many ways, this design document describes what is different between the current language and CIL. For example, types have exactly the same semantics as they currently do, CIL simply uses a different syntax for declaring and referencing them. Consequently, no space is spent describing the semantics of types and only a small amount of space spent discussing the new syntax separate from interaction with new CIL features. Contrastingly, CIL has new constructs for creating, managing, and traversing namespace. There is a corresponding amount of space describing the semantics of those features.

When referring to current semantics it is important to note that there are currently three separate policy languages in common usage: the reference policy syntax created in M4 (which includes interfaces and templates), the module syntax understood by checkmodule, and what is commonly called the kernel policy which is the policy understood by checkpolicy. In general, CIL preserves the current kernel policy almost unchanged (just with different syntax) and layers on features from the module language, reference policy, and novel new features. When discussing current semantics, if the context is not clear attempts will be made to clarify which policy language is being referenced.

Language Reference

This chapter is a reference manual for the CIL language.

Basic Syntax

The CIL language is composed of a handful of symbols tied together using a LISP inspired syntax for easy parsing. The symbol types are:

  1. Symbol - character sequence without spaces and a restricted character set (e.g., type, httpd_t, and user_r).
  2. Quoted String - a character sequence enclosed in quotes used to store arbitrary strings (e.g., "^/usr/shared/*.?").
  3. IPv4 and IPv6 Addresses - IP addresses for both v4 and v6 addressing. The symbol and number types are intentionally restricted to allow easy identification of IP addresses for easy parsing and syntax checking (e.g., 127.0.0.1).
  4. Numbers - numbers are simple integers (e.g., 443).
  5. Variables - variables are symbols that begin with a dollar sign ($foo) that are used in transforms. Regular symbol can never begin with dollar to distinguish this usage.

S-Expressions

The CIL syntax is composed of S-expressions, the core syntax of LISP. S-expressions have many advantages including ease of parsing and generation, relative human readability, clear rules, a natural tree structure, and compactness. S-expressions where chosen over the more fashionable XML, JSON, or similar choices primarily for compactness and human readability without sacrificing machine processing. Additionally, much of the later additions to XML, such as namespaces, have resulted in a surprisingly complex format without addressing core ambiguity (e.g., choosing attributes vs. tags). The primary downside to s-expressions is that, despite their simplicity, most programming environments don't include a default parser.

S-expressions consist entirely of white space separated lists contained within parentheses. The lists can contain symbols (strings without space, quotes, or other special symbols), quoted strings that contain virtually any character, and other lists. For example, consider the following S-expression:

	(type process)

This S-expression is a single list containing two symbols ("type" and "process"). This expression hints at how this simple syntax will be turned into a policy language - more on that later. Next is a slightly more complex example:

	(block foo
		(allow a b (file (read write))))

This example contains a list that contains two symbols ("block" and "foo") and a nested list. The nested list contains four more symbols ("allow", "a", "b", "file") and another list with two symbols ("read" and "write"). The nesting can be used to create complex trees of expressions, such as the following:

 	(block foo
 		(type bar)
 		(booleanif (and a b)
                        (true
 			        (allow a b (file (read write)))))
 		(booleanif a
                        (true
 			        (allow a c (dir (read)))))
 		(type bar))

CIL does not currently use any of the other special syntax often used by LISP dialects like Scheme or Common Lisp, such as "'" or "#:keyword". The syntax as defined reserves most of the commonly used symbols if such syntax is needed in the future.

Basic Token Types

Symbols

Symbols are charactar sequences (strings) without whitespace not enclosed in quotes, such as the two symbols ("foo" and "bar123") in the example below:

	(foo bar123)

The allowable characters in a symbol are the upper and lowercase ascii letters (a-zA-Z), ascii digits (0-9), and special characters ("*$-_%@+!."). Notable characters not allowed include single and double quotes (" and '). Symbols must start with a letter.

Symbols are terminated by whitespace or parentheses (at the end of lists or at the beginning of nested lists). Whitespace separation is most typically spaces as in the example above. However, newlines and tabs are also exceptable, as in:

 	(type
 		bar)

Symbols are used as both built-in language keywords and user-defined identifiers.

Variables

Variables are used for variable binding in transform statements. For example:

	(in http
		(transform (type $foo)
			(type foo)
			(typeattributetypes domain (foo))))

In this example, the transform processes all type declarations in the http namespace and the variable foo is set to the name of the type being processed (the example then re-declares the type and adds the domain type-attribute).

The syntax for variables is to prefix the symbol with a dollar sign.

Quoted Strings

Quoted strings are character sequences surrounded in single quotes ("), for example:

 	(filecon "/bin" "/ls" file context)

Quoted strings can contain any character without escaping except quote, open parenthesis, close parenthesis.

Comments

Comments are denoted by the semicolon (;). Any text after a semicolon - whether on the beginning of the line or in the middle - is considered a comment. For example, consider the following which contains two comment:

	; this is a comment
	(+ 2 3) ; prefix notation feels so retro

IP Addresses

IP Addresses can be either in the form of IPv4 addresses or IPv6 addresses. They are included as a basic symbol rather than processed as quoted strings to allow type inference more often and to provide syntax checking.

Numbers

Numbers are character sequences containing on 0-9 (e.g., 42). Only fixed-point numbers are supported and they are treated as 32-bit integers internally. They are seldom used - portcon being the primary example.

Forms

While S-expressions define all of the basic syntax for CIL, they are not a complete language by themselves. Layered on top of the S-expressions are forms, which are simply lists that start with special symbols. For example, below is the type form which defines a type:

	(type foo)

Forms each have specific syntax which will form the bulk of the syntax documentation.

Namespaces

"Namespaces are one honking great idea -- let's do more of those!" - The Zen of Python, by Tim Peters

Namespace control in SELinux policy is currently very limited, leading to the use of conventions to aid in modularity and avoid name conflicts. CIL includes additional namespace features that will improve on the current conventions and will also be used as a building block for some of the other CIL features (notably transforms).

Current Namespace Management

The SELinux kernel policy maintains separate namespaces for types and type-attributes, roles, users, booleans, levels, categories, classes, and common permissions. This namespace separation means that it is possible for a type and a role to have the same name. In current policy development practice this property is not widely used,but it will continue to be preserved in CIL by maintaining the separate namespaces.

Beyond the separation of names by symbol type, the kernel policy language offers no syntax to further divide the namespaces. Currently, SELinux policies manage the relatively flat namespaces through conventions. Modularity is achieved through the use of underscore (e.g., "httpd_log_t"). Additionally, the type, role, and user namespaces are denoted through suffices (e.g., "sysadm_u", "sysadm_r", and "sysadm_t").

These conventions, while largely helpful, are not understood or enforced by the policy toolchain. Without more complete namespace management as a language feature, it is not possible to perform namespace aware operations (such as copying a module or concisely searching for declarations within a namespace).

Type Hierarchy

The type hierarchy feature allows a policy developer to constrain the allowable access for a type based upon the to access granted to a parent type. The parent / child relationship can be created implicitly through the use of dot (".") in the type names (there is also an explicit typebound statement for creating this relationship). In this scheme, if a type has a dot in the name (e.g., apache.cgi_script), then the parent type is the type proceeding the dot (apache in this example). In this example, the allowable access of cgi_script must be a subset of the access granted to apache. This mechanism is currently used to constrain dynamic transitions. It was originally intended to form part of the policy access control mechanism.

Type hierarchy is relevant here because the dot operator looks much like a namespace operator in other languages. It is not - there is no way to create a new namespace with the type hierarchy; all of the dot separated names are type names not namespaces. However, the appearance of this syntax and the use of dot meant that the introduction of either a different operator for namespace traversal or the reuse of the dot would have been confusing.

Fortunately, the use of the type hierarchy has shown that it is not desirable to have the hierarchical relationships be implicitly defined through naming, primarily because it requires up-front design to use the mechanism. Instead the typebound statement is a clearer and more flexible means to explicitly make a type a parent of another type. This allows an administrator or other policy author to introduce hierarchical constraints to existing policies and types.

Introduction to CIL Namespace Management

Given this, CIL will reclaim the dot operator as a namespace operator and will not support implicit type hierarchy. It will still be possible for a high-level language to retain support for this behavior by introducing a different namespace operator (perhaps even using underscore) and emitting a typebound statement based upon type naming. However, this practice is not recommended.

The primary namespace related features in CIL are blocks, the in keyword, and the dot namespace operator. Blocks are used to declare namespaces, in allows modification of namespaces after declaration, and the dot operator is used to refer to symbols in namespaces.

Global Namespace

There is an implicit, global namespace which is always present and is treated somewhat specially for symbol resolution. Any symbol declared outside of a any block is in the global namespace. When referencing symbols, if the symbol name lacks a dot and does not occur in the current namespace, the global namespace is consulted for that symbol.

The global namespace can also be explicitly referenced by pre-fixing a symbol name with a . (e.g., .globaltype).

Block

Blocks declare new namespaces and allow the addition of policy constructs to that namespace.

The block form consists of the keyword block, a symbol identifying the namespace, and a list of policy statements (all policy statements are valid within a block and there can be zero or more statements in the list). For example:

 	(block httpd
 		(type process)
 		(type log)
 
 		(allow process file.log (dir (getattr read))))

This declares and enters the httpd namespace. Declarations within the block are part of the httpd namespace. Symbol names without a dot are resolved in the local namespace first and then the global namespace if a match is not found. Symbols not in the namespace are referenced by their fully qualified name, which includes any containing namespaces separated by a dot (e.g., file.log in the allow rule).

All symbols have a fully qualified name. In the example above, the fully qualified name of the type "process" is "httpd.process".

Declarations can only use a local name - the use of the namespace operator in a declaration will result in an error.

One note is that the name of the block is also available as a local symbol (see blockinherit below for motivation).

Blocks can be arbitrarily nested, for example:

 	(block httpd
 		(block cgi
 			(type process)
 			(role role)))

This example declares a type in the "cgi" namespace, which is a member of the "httpd" namespace, leading to the fully qualified name of httpd.cgi.process and httpd.cgi.role.

Note the role identifier. It is usable as a name here because it is not in the root of the namespace. It is not allowed to have symbols with the same name as any of the keywords, but this restriction only applies to the fully qualified name, not to a namespace local usage.

In the kernel these namespaces are treated as flat, or more accurately the kernel performs no operations that are aware of divisions within the namespaces.

Blocks can be inherited (see below for an introduction to inheritance) using the blockinherit statement. For example:

	(block myapp
		(type process)
		(type log)
		(allow process log (file (append))))

	(block anotherapp
		(blockinherit myapp)
		(allow process log (file (write))))
	; The resulting policy
	; (block anotherapp
	;	(type process)
	;	(type log)
	;	(allow process log (file (append write))))

In this example the block anotherapp inherits from the block myapp. The result is a block with two types and a union of the access between those types that is allowed in both blocks.

This example illustrates several important aspects of block inheritance:

  • Symbol declarations (user, role, type, category, sensitivity, level, context, class, classet, permissionset, and ipaddr), if a symbol with the same local name does not exist, results a new declaration within the child block of a symbol. If a local symbol already exists then there is no re-declaration or error. This, in effect, merges all duplicate declarations in the child and all parents into a single declaration (something similar happens child blocks, macros, and optionals).
  • For symbols in all other statements (whether access rules such as allow, associations such as roletype, or labeling such as context or nodecon) local names in the parent are replaced with references to local declarations in the child. The result is that access between local symbols is kept local but access to other symbols remains the same (see below for more information of who this feature works with fully qualified names).

Below is an example showing the merging of declarations:

	(type init_t)
	(block daemon
		(type process) 
		(type pidfile)
		(allow process pidfile (file (write)))
		(allow init_t process (process (transition signal)))
		(allow process init_t (process (sigchild))))

	(block logger
		(blockabstract logger)
		(type process)
		(type log)
		(allow process log (file (getattr append))))

	(block myapp
		(blockinherit daemon)
		(blockinherit logger))

In this example, two abstract blocks are create - daemon and logger - and those blocks declare types and an access pattern between those types. When the block myapp inherits from both of these blocks the type that both blocks declares (process) is merged. The result is that myapp.process can write to myapp.pidfile and append to myapp.log (in addition to the access to init_t).

Taking advantage of this mechanism will require a standard naming convention for local types, which would break the current naming convention of the reference policy. It is suggested that compatibility with the existing reference policy be handled through aliases. For example:

	(block httpd
		(type process)
		(type content)
		(type exec))
	(typealias httpd.process httpd_t)
	(typealias httpd.exec httpd_exec_t)
	(typealias httpd.content web_content_t)

Namespaces and similar constructs are also merged in block inheritance. For example:

	(type init_t)
	(block daemon
		(type process) 
		(type pidfile)
		(allow process pidfile (file (write)))
		(allow init_t process (process (transition signal)))
		(allow process init_t (process (sigchild)))
		(macro manage ((type d))
			(allow d pidfile (file (*)))))

	(block logger
		(blockabstract logger)
		(type process)
		(type log)
		(allow process log (file (getattr append)))
		(macro manage ((type d))
			(allow d log (file (*)))))

	(block myapp
		(blockinherit daemon)
		(blockinherit logger)

		(type private_data)

		(macro manage ((type d))
			(allow d private_data (file (*)))))

In this example each of the parent blocks (daemon and logger) define a macro called manage in addition to the myapp block. Inheritance causes all of the macros to be merged into a single macro with the union of the access. This same process happens for child blocks, optionals, and ifdefs (it is not necessary to merge conditional policy as overlapping conditional blocks is normal and expected). If the arguments for the macro do not match and error is produced.

Below is an example showing how the local symbol name rewriting works for contexts and associations.

	(type g)
	(block parent
		(blockabstract parent)
		(user u)
		(role r)
		(type t)
		(roletype r t)
		(sensitivity s)

		(context local (u r t ((s) (s))))
		(context mixed (u r g ((s) (s)))))

	(block child
		(blockinherit parent))
	; The block child now has the following context (using fully qualified names):
	;   (context child.local (child.u child.r child.t ((child.s) (child.s))))
	;   (context child.mixed (child.u child.r .g ((child.s) (child.s))))
	; The following role association is also present:
	;   (roletype child.r child.t)

The local symbol rewriting intentional only works for local symbol names; for statements that use a fully qualified name that resolves to a local name that access is not rewritten. This allows a policy writer to block certain statements from being inherited. For example:

	(block parent
		(type process)
		(type a)
		(type b)

		(allow process a (file (read)))
		(allow parent.process parent.b (file (read))))


	(block child
		(blockinherit parent))

In this example, the child block would have a rule allowing child.process to access child.a from the inherited allow rule in the parent that used local names. However, it would not have a rule allowing child.process to access child.b because that rule in the parent block used fully qualified names.

This mechanism can even be used with mixed local and fully qualified symbols. For example:

	(block apache
		(type process)
		(type webcontent)

		(allow process webcontent (file (read)))
		(allow apache.process webcontent (file (read))))

	(block myapache
		(blockinherit apache))

In this example, myapache would have a rule that allow myapache.process to read myapache.webcontent files inherited from the rule in the apache block that used local symbols. The apache block would also have a rule that allow apache.process to access both apache.webcontent and myapache.webcontent because of the rule that mixed the fully qualified process name with the local name.

Child blocks are also inherited through the blockinherit mechanism - it will cause the creation of a new block within the child block similarly to a symbol declaration.

The full syntax of the blockinherit statement is the keyword blockinherit, a symbol for the child block, and a symbol for the parent block. All policy statements are inherited through the blockinherit statement.

The blockinheritfilter statement can be used to prevent inheritance of policy statements. For example:

	(block logger
		(blockabstract logger)
		(type process)
		(type log)
		(allow process log (file (getattr append write))))

	(block myapp
		(blockinherit logger)
		(blockinheritfilter myapp logger
			(allow porcess log (file (write)))))

In this example, write access to myapp.log is not inherited because of the blockinheritfilter statement.

The full syntax of the blockinheritfilter statement is the keyword blockinheritfilter, a symbol for the child block, a symbol for the parent block, and a list of policy statements. All statements can be filtered in blockinheritfilter.

The blockabstract statement marks a block as abstract, meaning that the block will not be present in the final policy. The full syntax is the keywork blockabstract followed by a name for the block.

Other Namespaces Like Constructs

There are other constructs, such as macros and conditionals, that resemble namespaces but are not. Consider the following example:

 	(block httpd
 		(type process)
 		(type log_file)
 		(macro read_log_files (domain:type)
 			(allow domain log_file (file (read getattr))))

In this example, if the macro was truly a new namespace then the symbols domain and log_file would not resolve to those in the httpd block but rather, if they existed, in the global block. The current namespace operators are not sufficient to work around the challenge posed by converting these constructs to namespaces as it requires referring to the containing block without knowledge of the full namespace tree. While it would be possible to provide a relative namespace operator (allowing references to a parent namespace) it is more natural to simply not enter a new namespace for macros, conditionals, and optionals.

Entering Namespaces

In addition to entering a newly declared namespace using the block statement, it is possible to enter a namespace after declaration using the in keyword. For example,

 	(in httpd
 		allow process log_file file (unlink))

This example adds an allow rule to the httpd block. All of the symbols are processed as if they were part of the original block declaration.

The in keyword is necessary to separate declaration from modification, preventing a policy author from inadvertently declaring a block if the expected modules are not present.

The in keyword will most commonly be used at the top-level and will be used to traverse arbitrarily deep into the namespace tree. However, in statements can be nested. For example:

	(in httpd
		; some rules here
		(in httpd.read_log_files
			(allow $domain log (file (read)))))

When nesting, the in keyword can only traverse further into namespace tree. The name, though it may contain dots, is treated relative to the current location in the tree. The in statement cannot be used within blocks or other than the top-level except for within other in statements.

In addition to entering namespaces, it is possible to use the in keyword to enter other named, namespace-like constructs. Most importantly it can be used to enter a macro for modification. For example:

 	(in httpd.read_log_files
 		(allow domain log_file (file (open))))

Note that this means that the namespace for all named namespace like constructs is the same to prevent name collisions. That means, for example, that it is not possible to have an macro and a block with the same fully qualified name.

However, it is not possible to enter unnamed constructs, such as conditionals. There is no loss for conditionals; the policy author simply enters the containing namespace and uses a new conditional with the same conditional expression. The declarative syntax ensures that the resulting policy is the same.

The new optional syntax is also not effected as optionals have names.

In addition to the in keyword, namespaces can be entered using the traverse statement. Traverse allows iteration over a tree of namespaces. For example:

	(block daemons
		(block sshd
			(type process)
			; other declarations and rules
			)
		(block httpd
			(type process)
			; other declarations and rules
			)

	(traverse (daemons.)
		(allow process sometype (file (read))))

In this example, an allow rule is added to the sshd and httpd blocks using the traverse statement. The traverse statement starts with a list of namespaces to be traversed (after the keyword) and a set of policy statements that will be inserted into each namespace entered. Conceptually, in can be thought of as a set of in statements with the same body. The advantage of traverse is that the policy author does not need to know (or write) all of the namespaces. This, of course, also makes traverse challenging to use correctly.

This example also demonstrates the use of the trailing dot to control whether the beginning namespace of the traversal is entered or not. If the namespace symbols has a trailing dot then only the child namespaces will be entered. Without the dot, the named namespace will be entered in addition to all of the children.

As mentioned, it is possible to include a list of namespaces in the traverse statement. For example:

	(traverse (sshd httpd)
		(allow process sometype (file (read))))

This example traverses both the sshd and httpd namespaces (entering both because of the lack of the trailing dot). This example has the same effect as the previous example because sshd and httpd are the only children of daemon. However, they would diverge if other child namespaces were added to daemon.

Traverse will continue down the namespace tree all the way to the leaves. For example:

	(block a
		(block b
			(block c)))

	(traverse a
		(allow process file (file (read))))

In this example, the traverse statement will enter and add the allow rule to the a, b, and c blocks. There is no way to control the depth of the traversal. The only recourse is to use in statements instead if control over the depth of traversal is needed.

The full syntax of the traverse statement is the keyword traverse, a list of namespaces, and any number of policy statements. All policy statements are valid. The list of namespaces can reference the namespaces in two ways: 1. If it is just a symbol name (e.g., daemons) then the traversal starts at that namespace. 2. If the symbol contains a trailing dot (e.g., daemons.) then traversal skips the starting namespace and only includes the child namespaces.

Namespace Operator and Symbol Resolution

The dot namespace operator is used to traverse namespaces for any statement that references (not declares) an identifier. The dot is used to traverse namespaces to refer to symbols. For example:

 	(allow process httpd.log_file (file (read write)))

In this example, the second type is in the httpd namespace and the dot operator is used to refer to that type. The other type, process, is in the local namespace. Assuming that the httpd namespace is declared in the global namespace, then the httpd.log_file symbol is fully qualified.

There are two forms of name resolution, one for local names and one for qualified names. Qualified names have a dot and can either be partially specified (if they refer to child namespaces) or fully qualified if they begin in the global namespace.

Local name resolution is as follows:

  1. First the local namespace is searched for the symbol and, if found, resolution is stopped. Local resolution may require examining an inheritance hierarchy for the symbol declaration or expanding macros. Traversing the inheritance hierarchy is fundamentally different from traversing the namespace hierarchy. A symbol declared in the parent (inheritance-wise) is still declared in the local namespace, it is just that the declaration happens indirectly through the inheritance mechanism.
  2. The global namespace is searched - if the symbol is found resolution is stopped.
  3. If the symbol has not been found, then an error is produced.

Qualified name resolution is as follows:

  1. The first component of the name is selected and the local namespace is searched for blocks with that name. If a block is found, then resolution continues at step 3 otherwise the next step is performed. If the symbol begins with a dot (e.g. is global), resolution begins at step 2).
  2. The global namespace is searched for a block with the name of the first component (or second if the symbol started with a dot). If a block is found, then resolution continues. If not, an error is produced.
  3. At this step, a namespace matching the first component of the name has been found, either in a child block or the global namespace. If there is only one other component, step 4 is performed. Otherwise, for components until the final component the blocks are identified recursively. If at any point a block is not found an error is thrown.
  4. For the final component the appropriate local declaration is searched in the final namespace, either finding the symbol or producing an error.

Here is an example of all forms of qualified resolution:

	(block other
		(type process))

	(block foo
		(type process)
		(block bar
			(type baz))

		; bar.baz is partially qualified
		(allow process bar.baz (file (read)))

		; other.process is fully specified
		(allow other.process bar.baz (file (read)))

		; and this forces resolution to start at the global namespace but 
		; has the exact same result as the rule above.
		(allow .other.process bar.baz (file (read))))

For qualified name resolution only blocks are searched for all components other than the last. Namespace-like statements are not searched. To see the impact of this, consider the following example:

	(block apache
		(type process)
		(type log)
		(macro declare_cgi ()
			(type cgi)
			(allow process cgi (process (signull)))
			(allow process log (file (read write)))))

	; This is just a normal macro call
	(block webapp
		(call declare_cgi))

	; There is no way to do this
	; (type foo)
	; (allow apache.declare_cgi.cgi foo (file (read)))

	; What you likely want is
	(in apache.declare_cgi
		(allow cgi foo (file (read))))

Parent namespaces are not searched for symbol resolution and there is no syntax to explicitly reference a parent namespace. Symbols are either local, in a child namespace, or global. This makes the mechanism simpler, preserves the explicit nature of policy development, and works well with how namespaces are intended to be used. This means that rules that reference a child and parent namespace should reside in the parent namespace. For example,

	(block apache
		(type process)
		(type log)
		(block cgi
			(type process))
		; These rules belong in the parent namespace
		(allow cgi.process process (process (signull)))
		(allow cgi.process log (file (read write))))

	; Now for some inheritance - I want to create a new cgi process
	(block webapp
		(blockinherit apache.cgi)
	; Here is the resulting policy - this is what we would want
	(block webapp
		(type process)
		(allow process apache.process (process (signull)))
		(allow process apache.log (file (read write))))

The symbol "." can be pre-fixed to a symbol to explicitly reference the global namespace for global symbols similarly to how qualified names are prefixed. For example:

 	(typeattribute domain)
 	(block ircd
 		(type domain)
 		(type log_file)
 		(allow domain log_file (file (read write create unlink)))
 		(allow .domain log_file (file (read)))

Here there are two domain symbols: the local domain type and the global domain type-attribute. The use of the global namespace allows referencing both symbols. This example also shows that local names can shadow global names.

In some statements, wildcards (such as *) can be used. For example:

	(neverallow files * process (transition))

This example shows a neverallow rule referring to all types through the star operator.

The available operators are:

  • All (*) - all symbols of a given type (symbol type is inferred through context).
  • Namespace (name.*) - all symbols of a given type within the namespace referenced. This will not traverse through sub-namespaces (e.g., apache.* would not include symbols in apache.cgi if apache.cgi was a block).

Wildcards are available for use in neverallow rules, filter statements, traverse, transform, and delete.

Declarations and Definitions

Declarations create a new object and introduce a an identifier for into the appropriate namespace. Declarations may not define properties for the new object - separate definitions may are used for that purpose. Consider the following:

 	(type user_t)
 	(typeattribute domain)
 	(typeattributetypes domain (user_t))
 	(role user_r)

This example declares a type ("user_t"), a type-attribute ("domain"), and a role ("user_r"). It also adds the type "user_t" to the type-attribute "domain", further defining the type-attribute.

Declarations simply add a name into a namespace without allowing optional further definition. For example, it is not possible in CIL to add a type-attribute to a type as part of its declaration. This restriction simplifies the language and matches current practice in real world policies.

Declarations can only use a local name within a namespace - the use of a fully qualified name in a declaration will result in an error. Declaring an object in an existing namespace should use the in statement.

A given symbol can only be declared once - a repeated declaration results in an error.

Definitions provide further information about a symbol declared elsewhere. Declarations require a separate symbol declaration; if a corresponding declaration is not present the definition will cause an error. For example:

 	(type process)
 	(typeattributetypes domain (process))

The typeattributetypes statement further defines a type (in this example, adding the domain type-attribute to the type).

Unlike declarations, definitions can be repeated many times in differing parts of the policy. A repeated definition has no effect. Definitions can use either local or global names and it will likely be somewhat common to use both in a single statement.

Reference Declarations

The one exception to the declaration / definition split is in the declaration of objects that are references to other objects or containers for several other objects. For example, consider the following example:

	(type foo)
	(typealias foo bar)

Here a type named foo is declared and then a typealias is both declared (i.e., the name bar is introduced into the current namespace) and then defined to point to the foo type.

This form is allowed for compactness and, more importantly, to prevent the declaration of objects that are not valid. A typealias that does not point to a type is not meaningful and merging the declaration and definition makes it impossible to incorrectly define a typealias.

Another example is categoryset:

	(category c0)
	(category c1)
	(categoryset allcats c0 c1)

	(category c2)
	(categoryset allcats c2)

Here the categoryset object - which is a set of categories - is declared and defined initially to contain c0 and c1 and then redefined later to also include the category c2. This usage avoids the introduction of yet more statements and, because categoryset does not declare a new object that will be a part of the kernel policy, it is grouped with the reference declarations and allowed to merge declaration and definition.

Anonymous Objects

In CIL it is possible to declare names for objects that currently cannot be named in the current language, such as security contexts. This mechanism is an outgrowth of adding declaration syntax for these objects for use in macros. For example, consider the following example,

	(context node_default_label (system_u system_r node_t ((s0) (s0))))
	(nodecon (127.0.0.1) (255.255.255.0) node_default_label)

In this example, the named context node_default_label is declared and then used in a nodecon statement. The unnamed objects for the two ip addresses are declared inline.

For these objects - which include contexts and ip addresses - that have named and unnamed declarations there are three declaration forms:

  1. Explicit anonymous declarations - this form uses a declaration syntax (e.g., (ipaddr 127.0.0.1)) but does not include a name. This form is most typically used when passing the item as an parameter in a call statement (e.g., (call default_label_node (ipaddr 127.0.0.1))). This form exists to avoid type inference for anonymous objects.
  2. Anonymous declarations - the anonymous form is used when the object type can be reliably inferred, most often positionally (e.g., (nodecon 127.0.0.1 255.255.255.0 (system_u system_r node_t (s0) (s0)))). In this form, the declaration keyword and the object name is omitted.
  3. Named declarations - this is the form above where the keyword is used along with a name for the object.

Inheritance

Inheritance is used in CIL to allow the declaration of new policy constructs based upon other constructs. This allows - for example - the creation of a new type with similar access to an existing type, the cloning of existing policy blocks, and the ad-hoc creation of policy modules with minor differences to existing policy modules. The mechanism is meant to be used both during up-front design (for example, expanding the concept of standard attributes, such as domain, to blocks) and for ad-hoc policy modification by administrators. As such, it includes semantics to replace existing constructs in addition to creating new ones.

Below is a basic example demonstrating type inheritance:

	(type domain)
	
	(allow domain self (process (sigchild)))
	(typeabstract domain)

	(type myprocess)
	(typeinherit myprocess domain)

	; The resulting access allowed to myprocess
	; (allow myprocess self (process (sigchild)))

The syntax for inheritance follows the rest of CIL; each symbol type has a unique inheritance statement and related syntax. The detailed syntax for each symbol type will be described later.

This example also demonstrates how inheritance allows the use of other symbols as an access template. As such, sometimes it is desirable to make a symbol a template only and prevent it from being used on a running system to label objects. These are called abstract symbols (declared using the typeabstract statement above) and are, in many ways, similar to the existing attribute mechanism.

Below is a more complex example showing block inheritance:

	(block daemon
		(blockabstract daemon)
		(type process)
		(type exec)
		(type log)
		(allow process exec (file (entrypoint)))
		(allow process log (file (read getattr write)))
		(allow process .init_t (process (sigchild)))
		(macro execute ((type caller))
			(allow caller exec (file (execute)))
			(allow caller process (process (transition)))))

	(block apache
		(blockinherit daemon)
		(type cgi_script)
		(type cgi_exec)
		(allow process cgi_script (process (transition)))
		(allow process cgi_exec (file (execute)))
		(allow cgi_script cgi_exec (file (entrypoint)))
		(allow cgi_script log (file (read getattr write))))
	

	(block ntpd
		(blockinherit daemon)
		(allow process log (file (create))))

	; resulting policy
	; (block ntpd
	;	(type process)
	;	(type exec)
	;	(type log)
	;	(allow process exec (file (entrypoint)))
	;	(allow process log (file (read getattr write)))
	;	(allow process .init_t (process (sigchild)))
	;	(allow process log (file (create))))

In this example, the abstract block daemon is created. As an abstract block it is not instantiated in the final kernel policy but is instead available for use as a template for inheritance. The apache and ntpd blocks are then created and inherit from the daemon block. Block inheritance results in type declarations in addition to access being allowed.

The most important aspect of block inheritance is how the inter- and intra-type access is handled. Access between types declared in the block are allowed only between the types in a single child. For example, the apache.process type has no access to the ntpd.process type while it does have access to apache.exec and apache.log just as in the daemon block. Access to types declared outside of the block, such as the access granted to init_t, is the same amongst all children. This decision - which splits access into "public" and "private" access - allows block inheritance to be used to quickly create new blocks. Without this split, all children would have access to each other and block inheritance would have very limited use.

The inheritance mechanism allows the creation of inheritance trees, including inheriting access through many levels and inheritance from multiple parents. For example:

	(type domain)
	(allow domain domain (process (signal)))

	(type daemon)
	(typeinherit daemon domain)
	(allow init daemon (process (transition)))

	(type network_daemon)
	; rules to allow networking

	(type apache)
	; this statement inherits all of the access of daemon and domain
	(typeinherit apache daemon)
	; this statement inherits all of the access of network_daemon. Because
	; CIL (and SELinux) is a declarative language multiple inheritance is
	; safe and unambiguous.
	(typeinherit apache network_daemon)

When designing the inheritance mechanism several core use cases were considered that had conflicting needs in terms of what was inherited from the parent. For example, consider inheriting from a block that contained the policy for an ssh daemon. A policy author might wish to split the ssh block into two new blocks, which each block accessing only a single network interface. In that case it would be help to automatically exclude all labeling information and transition rules because the two new blocks would need to share a single executable in addition to filtering out the access to the network that was not needed for each new block. In contrast, the policy author might instead wish to make the ssh block a template to more easily write policy for two separate ssh daemon implementations. Here it would be helpful to include the transition and networking rules but still exclude the labeling information.

To accommodate the varying use cases the inheritance mechanism, by default, inheritances all statements related to a symbol (even those that simply reference that symbol) but allows the filtering of statements. Either entire statement types or particular statements specified by wild cards can be filtered during inheritance. The filtering only applies to the inheritance, making it possible to broadly filter access upon inheritance but selectively add back a portion of that access. Additionally, the filtering mechanism was designed in such a way that core inheritance patterns can be codified using macros. That will allow high-level languages to share inheritance patterns and make policy authoring easier as inheritance can lead to invalid policy (e.g., inheritance can easily create type transition rules that conflict). Note that using the raw inheritance mechanism will seldom be what a policy author will want to do as it is very likely to result in invalid policy.

For example:

	(type domain)
	(type binfile)
	(type configfile)
	(allow domain binfile (file (read getattr execute)))
	(allow domain configfile (file (read getattr)))

	(type myprocess)
	(typeinherit myprocess domain)
	(typeinheritfilter myprocess domain
		(allow domain binfile (file (read getattr execute))))

In this example, the type myprocess inherits the access of domain except for execute, read, and getattr to the binfile type. The typeinheritfilter statement (like all filter statements) is a list of policy statements that are not inherited. The statements in the filter refer to access in terms of the parent type. The inclusion of the child symbol is to clearly identify the exact inheritance relationship being filtered.

Only statements that are valid for a given symbol type are valid in the filter statement for that inheritance statement (e.g., it is not valid to filter roletype statements during typeinherit). The filtering applies only to that particular inheritance relationship (i.e., only between the parent and child) but it filters the statements regardless of how they came to be associated with the symbol (e.g., macros, direct statements, or inheritance between the parent and it's ancestors).

The filter statement can perform semantic matches, not just syntactic. In the example above it is possible to except only part of the access allowed by a given allow rule:

	(typeinherit myprocess domain)
	(typeinheritfilter myprocess domain
		(allow domain binfile (file (read getattr))))

This example would prevent just execute access to be excluded from the access inherited from domain.

It is also possible to more broadly filter access by all of a statement type or using wildcards. For example, the following example uses the star operator to exclude all execute permission:

	(typeinherit myprocess domain)
	(typeinheritfilter myprocess domain
		(allow domain * (file (execute))))

This example excludes all execute permissions on files for all types. This mechanism is made more useful by the fact the filter operates on the exclusively on access being inherited. It doesn't preclude granting the same access later directly to the type inheriting access. For example:

	(type domain)
	(type binfile)
	(type sbinfile)
	(allow domain binfile (file (read getattr execute)))
	(allow domain sbinfile (file (read getattr execute)))

	(type myprocess)
	(typeinherit myprocess domain)
	(typeinheritfilter myprocess domain
			(allow domain * (file (execute))))
	(allow myprocess sbinfile (file (execute)))

In this example, all execute access is filtered from the inheritance and then a subset is later directly granted to myprocess. This is most useful for larger policies and ad-hoc changes by administrators.

Filtering an entire statement type is done by listing the statement keyword in the filter statement, for example:

	(typeinheritfilter myprocess domain typetransition)

This example excludes all typetransition statements that reference domain at all.

While the use of a separate filter statement for each inheritance statement makes the inheritance more verbose, it allows the use of inheritance and filtering in macros. For example:

	(macro transition_filter ((type existing) (type new))
		(typeinheritfilter new existing
			; remove transitions to the domain (but not from)
			(typetransition * * process existing)
			(allow * existing (process (transition)))
			(allow existing * (file (entrypoint)))))

	(macro network_filter ((type existing) (type new))
		(typeinheritfilter new existing
			(allow existing * (netif (*)))
			(allow existing * (node (*)))
			(allow existing * (socket (*)))
			(allow existing * (tcp_socket (*)))
			(allow existing * (udp_socket (*)))
			(allow existing * (rawip_socket (*)))))

	(macro split_domain ((type existing) (type newa) (type newb))
		(typeinherit newa existing)
		(call transition_filter (existing newa))
		(typeinherit newb existing)
		(call transition_filter (existing newb)))

	(macro transition ((type src) (type exec) (type dst))
		(allow src exec (file (getattr execute)))
		(allow src dst (process (transition)))
		(allow dst exec (file (entrypoint)))
		(typetransition src exec process dst))

	(type sshd)
	(type sshd_exec)
	(type init)
	; rules giving sshd access
	(typetransition init sshd_exec process sshd)

	(type internet_sshd)
	(type intranet_sshd)

	(type internet_initscript)
	(type intranet_initscript)

	(call split_domain (sshd internet_sshd intranet_sshd))
	(call network_filter (internet_sshd))
	(call network_filter (intranet_sshd))

	(call transition (internet_initscript sshd_exec internet_sshd))
	(call transition (intranet_initscript sshd_exec intranet_sshd))

	; allow desired networking for each sshd domain

Filtering automatically removes related statements to make the inherited policy internally consistent. For example, an entire type is excluded in a blockinheritfilter then all of the rules and other statements that reference that type are also automatically filtered. For example:

	(block daemon
		(type process)
		(type pidfile)
		(type log)

		

Filter Syntax

The various filter statements share the same basic syntax: a keyword (e.g., typeinheritfilter), the child symbol, the parent symbol, and a list of statements to be filtered. In general, most any policy statement can be in the filter list. The main difference between standard policy statements and filter statements:

  • The filter statements can use wildcards (e.g., * or blockname.*) to more broadly match. For example, "(allow * foo (* (*)))" would match any allow rule with foo as the target type. The wildcards can appear in any position that accepts a symbol in the statement.
  • The filter statements should refer to the parent symbol name and not the child symbol name.
  • The filtering is focused on semantics rather than policy syntax and structure. So, for example, it is not possible to filter some of the allow rules from a block (as the namespace operators are not available). Instead a policy author would typically filter based on symbol name (e.g., (allow ssshd.* sometype (* (*)))). This more closely matches the intended use cases and makes it easier to be assured that all of the undesired access has been removed by not requiring the author to know where in the policy the access was granted. Combined with wildcards this makes it possible to quickly filter large amounts of access.
  • All statements can be listed by keyword (e.g., typetransition) to exclude that statement type entirely. Namespaces or namespace-like statements can be used in a shorted form (e.g., macro some_macro) to exclude that entire named statement (this includes block, macro, optional, and ifdef but not if/else).

Which statements are available in a specific filter statement depends on the symbol (typically any statement that references that symbol excluding symbol declarations can be used in a filter statement).

The filtering of statements based upon statement location is not allowed to make the creation of filters less error prone and to reduce the complexity of the compiler. To demonstrate the trade-off, below is an example of what was not allowed and some workarounds:

	(type webfiles)

	(block httpd
		(type process)

		(allow process webfiles (file (read getattr write create))))

	(type intranetfiles)
	(typeinherit intranetfiles webfiles)

	; It is not possible to do this
	; (typeinheritfilter intranetfiles webfiles
	;	(block httpd
	;		(allow process webfiles (* (*)))))
	; The same applies for optional, ifdef, macro, and if

	; It is possible filter based upon the types in the block (or any other identifier)
	(typeinheritfilter intranetfiles webfiles
		(allow httpd.* webfiles (* (*))))
	
	; It is also possible to filter from the entire block
	(typeinheritfilter intranetfiles webfiles
		(block httpd))

In this example, the goal is to filter the access of httpd.process to intranetfiles. However, it is not possible to filter only the allow rules present in the httpd block and allow that access to be granted in other locations. Instead, it is possible to either exclude the entire httpd block or to block filter that access regardless of where it was allowed.

Where this doesn't work particularly well is for statements in blocks unrelated to symbols declared in a block. There the only choice is to filter the entire block. The advantage is not having to parse and handle complex, namespace-like statements within the filter statements.

Type Declaration and Definition

The type statement declares a new type within the current namespace. The syntax is the keyword type followed by a symbol for the name of the new type. For example:

	(type user_t)

This statement declares the type user_t in the current namespace.

Types can have alias - alternate symbol names - and attributes. Defining these additional properties of a type are done with the typeattributetypes and typealias statements (type-attribute declarations are handled elsewhere).

The typeattributetypes statement adds one or more types (or other type-attributes) to a type-attribute. For example:

 	(type process)
        (typeattribute domain)
 	(typeattributetypes domain (process))

In this example, the type process has the type-attribute domain added (or alternatively, this can be thought of as adding the process type to the domain attribute).

Type typeattributes types statement can also remove one or more types (or type-attributes) from a type attribute by prepended a dash (-) to the type or type-attribute name. For example:

        (type shadow)
        (typeattribute files)
        (typeattribute nonsecurityfiles)
        (typeattributetypes nonsecurityfiles (files -shadow))

In this examples, nonsecurityfiles is a typeattribute associated with all files except for the shadow type.

The typeattributetypes statement consists of the keyword typeattributetypes followed by a symbol for the type-attribute and a list of types, type aliass, or type-attribute. A dash (-) may prepend any of this to remove a type, type-alias, or type-attribute from a type-attribute. It is important to note that removing a type-attribute from a type-attribute removes all types associated with the type-attribute, even if the type-attribute that is removed wasn't specifically added to the type-attribute.

The typealias statement adds another name for a type into a namespace. In this way it is both a declaration and a definition. Consider the following example:

	(block httpd
		(type process)
		(typealias process control_process))

In this example the httpd.process is aliased to the type httpd.control_process. Notice that both symbols are local in this example.

As aliases will commonly be used for compatibility, it will be normal to introduce an alias into a namespace other than the one used for the original declaration. To preserve consistency with the rule that declarations use only local name, the new symbol can only be local. However, the existing type can be in another namespace. For example:

	(block httpd
		(type process))
	(typealias httpd.process httpd_t)

The syntax of the typealias statement is the keyword typealias, the name of the existing type (local or fully-qualified name), and finally the name of the new symbol (local name only).

The typebounds is used to limit the access that may be granted to a type to the access of a parent type. For example, consider the following example:

	(type parent)
	(type somefile)
	(allow parent somefile (file (read)))

	(type child)
	(typebounds parent child)
	
	; this rule is allowed
	(allow child somefile (file (read)))
	; this rule is an error
	; (allow child somefile (file (write)))

Here, the type child is limited to the access of the parent type parent through the typebounds statement.

The full syntax of the typebounds statement is the keyword typebounds followed by a symbol for the parent type and finally a symbol for the child type. Any number of typebounds statements may reference a parent or child type and repeated statements with the same parent and child does not cause an error.

Permissive types are types whose access is not restricted at runtime by the kernel but instead only have access denials audited. Here is an example that makes a type permissve:

	(type mydomain)
	(typepermissive mydomain)

Here the type mydomain is declared and is then made permissive.

The full syntax of the typepermissive statement is the keyword typepermissive followed by a symbol name for the type to be made permissive. Repeated typepermissive statements with the same type do not cause an error.

The typeinherit statement is used to inherit the access granted to another type (see the introduction to inheritance earlier in this document). For example, consider the following example:

	(type domain)
	(type syslog)
	(allow domain syslog (file (read write getattr)))

	(type myprocess)
	(typeinherit myprocess domain)
	; my process can now write to files of type syslog

In this example the type domain is created and granted some access. The type myprocess is then created and the typeinherit statement is used to grant the same access to it.

Inheritance will cause the new symbol to appear in every statement that the original symbol appears in (by default - typeinheritfilter can alter this behavior). This includes both the access that a type as a source and as a target. For example:

	(type configfile)
	(type init)
	(allow init configfile (file (read getattr)))

	(type apache_config)
	(typeinherit apache_config configfile)
	; init now has access to apache_config

It also includes transition rules:

	(type user_t)
	(type passwd_exec_t)
	(type passwd_t)

	(typetransition user_t passwd_exec_t process passwd_t)

	(type new_t)
	(typeinherit new_t user_t)
	; when new_t executes files of type passwd_exec_t it will attempt a transition to passwd_t

labeling rules:

	(nodecon (127.0.0.1) (255.255.255.255) (system_u object_r node_lo_t ((s0) (s0))))

	(type new_node)
	(typeinherit new_node node_lo_t)
	; The new_node type now has a copy of the nodecon above - this will cause
	; a conflict without the following:
	(typeabstract node_lo_t)

and even macros and call:

	(macro access_mytype ((type d))
		(allow d mytype (file (read write))))

	(type othertype)
	(typeinherit othertype mytype)
	; callers to access_mytype will now receive file read and write access to mytype and othertype
	; this could be suppressed with:
	;   (typeinheritfilter othertype mytype
	;  	  (macro access_mytype))
	; or:
	;   (typeinheritfilter othertype mytype
	;	  macro)

	(type some_domain)
	(call access_mytype (some_domain))

	(type other_domain)
	(typeinherit other_domain some_domain)
	; other_domain now includes a call to access_mytype. This can be
	; filtered with:
	;   (typeinheritfilter other_domain some_domain
	;	(call access_mytype (some_domain)))

The full syntax for typeinherit is the keyword typeinherit followed by a symbol for the type which will inherit access (i.e., the child) and a symbol for the type from which access will be inherited (i.e., the parent). The typeinherit statement does not create a new type - the child type must already exist.

The full list of statements that typeinherit will inherit (which is the full list of statements that can contain a type with the exception of type, typealias, and typeabstract):

  • Type Declaration: typeattributetypes, typebounds, typepermissive
  • Role Declaration: roletype
  • Block: block
  • Access Vector Rules: allow, auditallow, dontaudit, neverallow
  • Transition Rules: typetransition, typemember, typechange
  • Constraints: constrain, validatetrans
  • General Labeling: context
  • File Labeling: filecon, genfscon
  • XSM Labeling: pirqcon, iomemcon, ioportcon, pcidevicecon
  • Initial SIDS: initialsid
  • Network Labeling: netifcon, portcon, nodecon
  • Macros: macro, call

The full syntax for the typeinheritfilter statement is the keyword typeinheritfilter, a symbol for the child type, a symbol for the parent type, and a list of statements that will be filtered. The available statement types are the same as the list of statements that are inherited through the typeinherit statement.

The typeabstract statement converts a type into an abstract type, which will not be present in the generated kernel policy. Abstract types are typically used with typeinherit. An example is below:

	(type sshd)
	(typeabstract sshd)

Once a type is marked as abstract then all statements that refer to that type, including as part of a security context, will be excluded from the final policy. This may result in an invalid policy (e.g., the removal of a required initial SID) or a policy with unintended consequences (e.g., an incorrect label for a file because of a removed filecon statement.

The full syntax of the typeabstract statement is the keyword typeabstract followed by a symbol for the type to be made abstract.

Type Attributes

The typeattribute declaration declares a new type-attribute within the current namespace. For example:

	(typeattribute domain)

The syntax of the typeattribute statement is the keyword typeattribute followed by a local symbol name for the new type-attribute.

Further definition of type-attributes is done with the typeattributetypes statement covered above.

Users

Users are managed using two basic statements: user declaration and a user definition statement to authorize roles for a user.

The user statement declares a user, for example:

	(user sysadm_u)
	(role sysadm_r)
	(type sshd)

	(context sshd_ctx (sysadm_u sysadm_r sshd ((s0) (s0))))

This example shows the declaration of the user sysadm_u.

The full syntax of the user statement is the keyword user followed by a symbol for the new user name.

The userrole statement authorizes a role for a user, allowing that role to be present in a security context with a user. For example, the following userrole statement would be required to make the preceding example correct:

	(userrole sysadm_u sysadm_r)

Users can also be defined through inheritance. For example:

	(user staff_u)
	(userrole staff_u staff_r)

	(user admin_u)
	(userinherit admin_u staff_u)
	; admin_u is now authorized for the staff_r role

In this example the admin_u user is declared and then inherits from the staff_u user, making admin_u authorized for the staff_r role.

The full syntax of the userinherit statement is the keyword userinherit, a symbol for the user inheriting access (i.e., the child user), and finally a symbol for the user from whom access is inherited (i.e., the parent user). The full list of statements that will be inherited is:

  • User Declaration: userrole
  • Constraints: constrain, validatetrans
  • General Labeling: context
  • Block: block
  • File Labeling: fsuse, filecon, genfscon
  • XSM Labeling: pirqcon, iomemcon, ioportcon, pcidevicecon
  • Initial SIDS: initialsid
  • Network Labeling: netifcon, portcon, nodecon
  • Macros: macro, call

The userinheritfilter statement can be used to filter access during inheritance. For example:

	(user staff_u)
	(userrole staff_u staff_r)
	(userrole staff_u admin_r)

	(user admin_u)
	(userinherit admin_u staff_u)
	(userinheritfilter admin_u staff_u
		(userrole staff_u admin_r))
	; admin_u is now authorized _only_ for the staff_r role

This example suppresses the admin_r role authorization for the admin_u user during inheritance from staff_u.

The full syntax of the userinheritfilter statement is the keyword userinheritfilter, a symbol for the child user, a symbol for the parent user, and a list of policy statements that will be filter. The available policy statements that can be used in the filter statement are the same as those inherited by default.

The userabstract statement marks a user as abstract and the user - and any statement that references that user - will not appear in the final policy. For example:

	(user staff_u)
	(userrole staff_u staff_r)
	(userabstract staff_u)

	(user admin_u)
	(userinherit admin_u staff_u)

In this example, the user staff_u is made abstract. This will cause the user and the role authorization for staff_r to not be generated for the kernel policy.

The full syntax of the userabstract statement is the keyword userabstract followed by a symbol for the user to be made abstract.

Roles

Roles are managed using three basic statements: a role declaration statement, a role definition statement that authorizes types for a role, and a role dominance statement that controls relationships between roles.

Roles are declared using the role statement as follows:

	(role user_r)

This example declares the role user_r in the global namespace. Like other declarations, the role statement use local names only and introduces names into the current namespace.

The role statement is the keyword role followed by a symbol for the new role. The symbol must be a local symbol. Note that the role keyword can be used as a symbol for non-global symbols, including roles.

Types can be authorized for a role using the roletype statement. For example:

	(role user_r)
	(type user_t)
	(roletype user_r user_t)

The roletype statement above authorizes the type user_t to be present in a security context with the role user_r. The roletype statement replaces the repeated role statement in the current policy languages to separation declaration and definition.

The syntax of the roletype statement is the keyword roletype followed by a symbol for the role and a symbol for the type.

Role dominance causes a role to gain all of the type authorizations from another role. For example:

	(role sysadm_r)

	(role staff_r)
	(roletype staff_r staff_t)

	(roledominance sysadm_r staff_r)
	; sysadmin_r is not authroized for staff_t

The full syntax of the role dominance statement is the keyword roledominance, a symbol for the dominating role, and a symbol for the dominated role.

Role inheritance allows the definition of a role based upon an existing role. For example:

	(role sysadm_r)

	(role staff_r)
	(roletype staff_r staff_t)
	
	(roleinherit sysadm_r staff_r)

In this example, the sysadm_r role inherits from the staff_r gaining authorization for the staff_t type. Role inheritance, while somewhat similar to role dominance, is much broader and will cause inheritance from all symbols which reference a role.

The full list of statements inherited through role inheritance is:

  • User Declaration: userrole
  • Role Declaration: roletype, roledominance
  • Block: block
  • Transition Rules: roletransition
  • Role Rules: roleallow
  • Constraints: constrain, validatetrans
  • General Labeling: context
  • File Labeling: fsuse, filecon, genfscon
  • XSM Labeling: pirqcon, iomemcon, ioportcon, pcidevicecon
  • Initial SIDS: initialsid
  • Network Labeling: netifcon, portcon, nodecon
  • Macros: macro, call

The full syntax of the roleinherit statement is the keyword roleinherit, a symbol for the role inheriting access (i.e., the child role), and a symbol for the role from which access will be inherited (i.e., the parent role).

The roleinheritfilter statement filters statements during inheritance. For example:

	(role staff_r)
	(role admin_r)
	(roletype staff_r staff_t)
	(roletype admin_r admin_t)
	(roledominance staff_r admin_r)
	

	(role superuser_r)
	(roleinherit superuser_r staff_r)
	(roleinheritfilter superuser_r staff_r
		(roledominance staff_r *))
	; superuser_r is now authorized for staff_t but does not dominate admin_r
	; and is not authorized for admin_t

In this example the role superuser_r inherits from staff_r but the roleinheritfilter filters all dominance relations with staff_r as the dominating role (dominance where staff_r is dominated would still be inherited).

The full syntax of the roleinheritfilter statement is the keyword roleinheritfilter, a symbol for the child role, a symbol for the parent role, and a list of policy statements. The list of policy statements is the same as the list of inherited policy statements.

The roleabstract statement marks a role as abstract and the role - and any statement that references that role - will not appear in the final policy. For example:

	(role sysadm_r)
	(roleabstract sysadm_r)

In this example the sysadm_r role is marked as abstract.

The full syntax of the roleabstract statement is the keyword roleabstract followed by a symbol for the role.

Role allow and transitions are handling later in the rules section of the document.

Object Class and Permissions

Class

	(class file (read write execute))

Permissionset

	(permissionset read_perms (open getattr read))

permissionsets are treated as a list of strings and are not associated with an object class until they are used. read_perms could be used with a number of different object classes:

	(allow a b (file read_perms))
	(allow a b (sock_file read_perms))

permissionsets can also be used in class declarations:

	(class file read_perms)

Classpermissionset

	(classpermissionset file_read_perms (file (open getattr read)))
	;or 
	(classpermissionset file_read_perms (file read_perms))

The classpermissionset creates an association between an object class and a set of permissions and is used in avrules, such as:

	(allow a b file_read_perms)
	; or anonymously
	(allow a b (file read_perms))
	; and with an anonymous permissionset
	(allow a b (file (open getattr read)))

Booleans

	(bool foo true)
	(bool baz false)
	(bool what false)

Tunable

	(tunable bar true)
	(tunable baz false)

Rules

Access Vector Rules

	; Allow
	(allow foo bar (file (read write)))
	; Only allow * for permissions in AV rules.
	(allow foo bar (file (*)))

	; Auditallow

	(auditallow foo bar (file (read write)))

	; Auditdeny - no auditdeny!

	; Dontaudit - hate the name, but who cares?

	(dontaudit foo bar (file (read write)))

Neverallow

  • Any wildcards here?

Transition Rules

Role Transition

	(role staff_r)
	(role sysadm_r)
	(roletransition staff_r sudo_exec_t sysadm_r)

Type Transition

	; Process transition
	(type user_t)
	(type passwd_t)
	(type process_exec)

	(typetransition user_t passwd_exec_t process passwd_t)

	; File transition
	(type passwd_t)
	(type tmp_t)
	(type passwd_tmp_t)

	(typetransition passwd_t tmp_t file passwd_tmp_t)

Type Member

	(type sysadm_t)
	(type tty_device_t)
	(type sysadm_tty_device_t)

	(typemember sysadm_t tty_device_t chr_file sysadm_tty_device_t)

Type Change

	(type sysadm_t)
	(type tty_device_t)
	(type sysadm_tty_device_t)

	(typechange sysadm_t tty_device_t chr_file sysadm_tty_device_t)

Role Allow

	(role staff_r sysadm_r)
	(roleallow staff_r sysadm_r)

Runtime and Compile Time Conditional Rules

Tuanbles conditionals are a form of preproccessing and are among the first statements to be expanded. They are purely buildtime. Rules inside a tunable conditional are only put into the binary policy if the tunable evaluates to true.

Boolean conditionals differ from tunables in that they are build time rules. They expressions and assoicated rules are put into the binary, and evaluated in the kernel, and thus, can be evaluated at runtime.

Tunables and booleans are defined using the 'tunable' and 'boolean' statements:

(tunable tun true)
(boolean bool false)

The syntax for boolean and tunable conditions have the exact same syntax. The only difference is that boolean conditionals start with the 'booleanif' keyword and tunable conditionals start with the 'tunableif' keyword:

(booleanif EXPR
	(true
		(allow a b (file (read))))
	(false
		(allow a b (file (read write)))))

(tunableif EXPR
	(true
		(allow a b (file (read))))
	(false
		(allow a b (file (read write)))))

The 'true' and 'false' keywords, which can only be used directly inside tunableif or booleanif statements, specifiy what rules to enable when the conditional expression evaluates to true or false.

The expression can be of the following form:

expr = (and expr expr) |
       (or expr expr) |
       (eq expr expr) |
       (neq expr expr) |
       (xor expr expr) |
       (not expr) |
       boolean

NOTE: because of kernel limitations, only the following CIL statements can be used inside a booleanif:

(allow)
(auditallow)
(dontaudit)
(typetransition)
(typechange)
(typemember)

Because tunableif statments do not go into the kernel, they do not have this limitation.

Examples

(booleanif (neq foo baz)
	(true
		(allow a b (file (read)))))

(booleanif (and foo bar)
	(true
		(allow a b (file (read)))))

(booleanif (and (and foo bar) what)
	(true
		(allow a b (file (read)))))

(booleanif bar
	(true
		(allow a b (file (read)))))

Optional Rules

Optional rules are used to define a set of rules that might (expectantly) fail because types are defined in another CIL policy file that doesn't exist. If at any point the declaration for a symbol cannot be found, the optional rule is disabled.

	; automatic dependencies
	; Always has a name associated with the block.
	(optional apache
		(allow foo bar (file (write))))

	(optional foobar
		(type whatever)
		(allow whatever foo (file (write))))

NOTE: Because of the ordering in which optional rules are parse, tunable statments (not tunableif statements) and macro statements cannot be defined inside optional statements.

Constraints

MLS and MLS Constraints

The MLS portion of the policy includes statements for declaring sensitivities and categories, controlling the association of sensitivities and categories in levels, defining the relationship of levels, declaring named levels, and MLS related constraints.

Below is a short but complete example of an MLS policy.

	; Declare four sensitivities
	(sensitivity s0)
	(sensitivity s1)
	(sensitivity s2)
	(sensitivity s3)

	; Assign aliases
	(sensitivityalias s0 unclass)
	(sensitivityalias s1 confidential)
	(sensitivityalias s2 secret)
	(sensitivityalias s3 topsecret)

	; Define the relationship between the sensitivities - it is possible to use a single
	; dominance statement or multiple statements (to allow addition of sensitivities in
	; modules. Taken together, all of the statements should form an ordered set.
	(dominance s0 s1 s2)
	(dominance secret topsecret)

	; Declare four categories
	(category c0)
	(category c1)
	(category c2)
	(category c3)

	; Assign category aliases
	(categoryalias c0 red)
	(categoryalias c1 blue)
	(categoryalias c2 green)
	(categoryalias c3 white)

	; Order the categories - like dominance this can be done with as many statements
	; as needed to create an ordered set. This ordering is only used for category
	; ranges - there is no other relationship between the categories.
	(categoryorder c0 c1)
	(categoryorder blue green white)

	; Declare a named category set
	(categoryset allcats (red white))

	; Associate categories and sensitivities
	; This associates s0 with 1 category
	(sensitivitycategory s0 (c0))
	; Here is two disjoint categories
	(sensitivitycategory s1 (c0 green))
	; Here is a range and a single
	(sensitivitycategory s2 (c0 (green c3)))
	; Here is a named range
	(sensitivitycategory s3 allcats)

	; some same constraint statements
	(mlsconstrain (file dir) (create relabelto) (eq l2 h2))
	(mlsconstrain (file) (write) (domby l1 l2))

	; Create some named level
	(level SystemHigh (s3 allcats))
	(level PrettyLow (s1 (c0 c2)))
	(level ReallyLow (s0))

	; Use the level in a named context
	(context netif_high (system_u system_r node_t (SystemHigh SystemHigh)))

	; Use that context in a labeling statement
	(netifcon eth1 netif_high (system_u system_r packet_t (SystemHigh SystemHigh)))

Sensitivity

Declaration and definition of sensitivities is accomplished through three statements: sensitivity, sensitivityalias, and dominance.

The sensitivity statement declares a new, named sensitivity. For example:

	(sensitivity s0)

This example declares the sensitivity s0.

Like all declarations, sensitivities can only use local names and introduce names into the current namespace. While it is expected that most sensitivities will be declared in the global namespace, it is possible to declare them elsewhere. For example:

	(block extra_mls
		(sensitivity s0))

Here the sensitivity extra_mls.s0 is declared.

The full syntax of the sensitivity statement is the keyword sensitivity followed by the local name of the new sensitivity.

The sensitivityalias adds another name for a sensitivity into a namespace. Consider the following example:

	(sensitivity s0)
	(block extra_mls
		(sensitivityalias s0 Low))

This example declares the new name extra_mls.Low for the sensitivity s0. Like all alias statements, the new name can only be a local name while the existing sensitivity can be either local or fully specified.

The dominance statement is used to order sensitivities. The semantics of dominance in CIL are the same as current SELinux policy and are not covered here in detail. Consider the following example:

	(sensitivity s0)
	(sensitivity s1)
	(sensitivity s2)
	
	(dominance (s0 s1))
	(dominance (s1 s2))

In this example, s1 is made dominance of s0 and s2 dominant of s1, creating an ordered set of s0, s1, s2.

The dominance statement can order any number of sensitivities, not just two. For example, the following example has the same effect as the preceding example.

	(sensitivity s0)
	(sensitivity s1)
	(sensitivity s2)
	
	(dominance (s0 s1 s2))

The full syntax of the dominance statement is the keyword dominance followed by two or more sensitivities. The sensitivities are listed in order of increasing dominance. The last sensitivity will dominate all preceding sensitivities.

A correct policy declares the dominance relationship for all sensitivities through one or more dominance statements. Failure of a sensitivity to appear in a dominance statement will produce a compilation error. Restatement of dominance with overlapping dominance statements is not an error.

Category

Categories are declared and defined using three statements that correspond roughly to the three statements relating to sensitivities. The statements are category, categoryalias, categoryorder, and categoryset

The category statement declares a new category. Consider the following example:

	(category c0)

	(block extra_mls
		(category c1))

This example declares two categories, c0 and extra_mls.c1.

The full syntax of the category statement is the keyword category followed by a local name for the new category.

The categoryalias statement adds a new name for an existing category into a namespace. For example:

	(category c0)
	(categoryalias c0 ManhattanProject)

This example adds the alias ManhattanProject for the category c0.

The full syntax of the categoryalias statement is the keyword categoryalias followed by a local or fully-specified name of the existing category and a local name for the alias.

The categoryorder statement is used to order category statements. Category ordering is used for category ranges in levels; ordering introduces no security relevant relationship between categories. While not strictly needed semantically, the ordering of categories and use of category ranges allows for more concise statement of levels and correspondingly compact storage of those levels.

Consider the following example:

	(category c0)
	(category c1)
	(category c2)
	(category c3)

	(categoryorder (c0 c1))
	(categoryorder (c1 c2 c3))

In this example the four categories c0, c1, c2, c3 are ordered using two categoryorder statements. Like the dominance statement, categoryorder statements can have two or greater categories specified.

The categoryorder statement replaces the implicit ordering in the current SELinux policy language and allows for the safe addition of categories in modules. Like the dominance statement, all categories must be ordered or a compilation error is generated. Restatement of the same ordering with overlapping categoryorder statements is not an error.

The final category statement - categoryset - provides a means to create a named set of categories. In the current SELinux language, category sets can only be defined implicitly as part of context declarations. This mechanism was added for convenience and to provide a syntax for passing category sets as parameters to macros.

Consider the following example:

	(category c0)
	(category c1)
	(category c2)
	(category c3)

	(categoryorder (c0 c1))
	(categoryorder (c1 c2 c3))

	(categoryset some_cats (c2 c0))
	(categoryset allcats ((c0 c3)))

In this example, two category sets are defined. The some_cats set contains the disjoint categories c2 and c0. The allcats set contains all categories defined using a category range.

The full syntax for the categoryset statement is the keyword categoryset followed by a list of categories or category ranges. Categories ranges are represented by a list with two symbols for categories. Any number of individual categories or category ranges my be a part of a set.

The categoryset statement may be repeated multiple times with the same categoryset name. The resulting set is the union of all of the categoryset statements for that named categoryset.

Levels

Levels are created using two statements: the level statement and the sensitivitycategory statement. The sensitivitycategory statements is used to allow categories to be associated with a sensitivity in a level and the level statement is used to declare named levels.

The sensitivitycategory authorizes categories to be associated with a sensitivity in a level statement. This statement replaces the existing level statement in the SELinux policy. While the authors were reluctant to change the meaning of an existing statement, this decision regularizes the language and removes the current overloading of the term level (which is typically used to refer to a discrete level rather than the authorization of a category associations). The somewhat unfortunately name - sensitivitycategory - mirrors the typeattributetypes, roletype, and userrole statements.

Considering the following example:

	(category c0)
	(category c1)
	(sensitivity s0)

	(sensitivitycategory s0 (c0))
	(level somelevel (s0 (c0)))

	; this level is invalid
	(level invalid (s0 (c1)))

This statement associates an anonymous category set (containing just c0) with the sensitivity s0. It then creates a valid level named somelevel with the sensitivity s0 and a category set containing c0. The level invalid is not a valid level because it contains the level s0 and the category c1.

The full syntax of the sensitivitycategory statement is the keyword sensitivitycategory followed by a symbol for the sensitivity and a named or anonymous category set. All symbols can reference either local or fully qualified sensitivities and categories.

The following example demonstrates the use of a named category and anonymous category sets:

	(category c0)
	(category c1)
	(category c2)
	(category c3)
	(categoryorder (c0 c1 c2 c3))

	(categoryset allcats ((c0 c3)))
	(categoryset somecats (c0 c2 c3))

	(sensitivity s0)
	(sensitivity s1)
	(dominance (s0 s1))

	; Use of a named category set - no parentheses are needed.
	(sensitivitycategory s1 allcats)

	; A more complex anonymous category set
	(sensitivitycategory s0 (c0 (c2 c3)))

In this example, the sensitivity s1 is allowed to associate with all categories through the use of the allcats named category set. The sensitivity s0 is allowed to associate with all categories except c1 through an anonymous category set.

The level statement is used to create named levels that can be used a parameters to macros or as part of security contexts. The change of the level statement, as noted, was done to regularize the language and to make the syntax for macro parameters natural.

The level statement creates a discrete level with an associated name. It does not allow the associate of the categories with the sensitivity; if there is not a sensitivitycategory statement allowing the association of all of the categories with the sensitivity in a level statement an error is generated. Consider the following statement:

	(category c0)
	(category c1)
	(category c2)
	(category c3)
	(categoryorder (c0 c1 c2 c3))

	(categoryset allcats ((c0 c3)))
	(categoryset somecats (c0 c2 c3))

	(sensitivity s0)
	(sensitivity s1)
	(dominance (s0 s1))

	(macro labellocalhost ((level low) (level high))
		(nodecon (127.0.0.1) (255.255.255.0) (system_u system_r localnode_t (low high))))
	
	(level low (s0))
	(level high (s1 allcats))

	(call labellocalhost (low high))

	(level otherlevel (s1 (c0 (c2 c3))))

This example shows the creation of three levels: low, high, and otherlevel. The low level shows the declaration of a level with no categories. The high level shows the use of a named category set. Finally, otherlevel shows the use of an anonymous category set.

This example also shows the declaration of a macro that accepts two levels as parameters and the use of those levels in a security context.

The full syntax of the level statement is the keyword level followed by a symbol for the level name, a local or fully qualified name for the sensitivity, and a named or anonymous category set.

MLS Constraints

MLS constraints are used to define how the MLS policy will be enforced. The syntax is very similar to constraints. Consider the following example:

	(mlsconstrain (file)
		(write create setattr relabelfrom append unlink link rename mounton)
		(or (domby l1 l2) (eq t1 mlsfilewritedown)))

This example shows an mlsconstrain statement that prevents processes from writing, creating, renaming, etc. files below their level unless the process type has the attribtue mlsfilewritedown.

The full syntax for the mlsconstrain statement is the keyword mlsconstrain followed by a list of objects classes, a list of permissions, and a mls constraint expression. The constraint expression is in the same prefix form as conditional and constraint expressions.

The constraint operators are eq, neq, dom, domby, incomp The constraint operands are t1, r1, u1, l1, h1, t2, r2, u2, l2, and h2

The semantics of the mlsconstrain statement are unaltered from the current SELinux policy language and are not described here.

Labeling

Security context

Security contexts in CIL leverage s-expressions for easy parsing and, therefore, do not use the colon separators currently used in the policy language or runtime tools. The authors believe that this difference - and potential learning curve - is worth the gain in simplicity.

A full security context is as follows:

	(user_u user_r user_t ((s0) (s0)))

Note that this is not a full example - most security contexts cannot stand alone. They are only used as part of other statements, typically labeling statements. The context statement creates a security context composed of the user, role, type, current level, and clearance level. The equivalent runtime security context would be user_u:user_r:user_t:s0-s0. Contexts in CIL are always fully specified including both the current and clearance levels.

A more complete example is below:

	(nodecon (127.0.0.1) (255.255.255.255) (system_u object_r node_lo_t ((s0) (s0))))

This example is a nodecon labeling statement with a security context.

In these examples, the security context is being declared inline and, because it can be identified by contextual clues, no keyword is used to indicate the declaration of a security context. In addition to the inline, anonymous security context it is possible to declare a named security context. Consider the following example:

	(context localhost_node_label (system_u object_r node_lo_t ((s0) (s0))))
	(nodecon (127.0.0.1) (255.255.255.0) localhost_node_label)

This example shows the declaration of the named security context localhost_node_label. The named context is then used within a nodecon statement. For statements that reference security contexts named contexts or anonymous contexts can be used differentiated by the type of the reference: either a single symbol (for name security contexts) and a list for anonymous contexts.

The full syntax of the context declarations is the keyword context, a symbol for the name of the context, followed by symbols for the user, role, type, and finally the MLS portion. The MLS portion consists of a list for the current and clearance levels. The level lists contain one sensitivity and 0 or more categories or category ranges. Category ranges are represented by a list.

The syntax for anonymous contexts declarations is the same with the omission of the context keyword and symbol for the name. While this document does not specify implementation details, it is assumed that anonymous contexts will be stored in the same way as named contexts with a special name applied. It is not clear whether automatic merging of anonymous contexts that are identical under a single, anonymous name is a worthwhile optimization.

Some additional examples:

	; Categories - these are non-contiguous, individual categories
	(context user_label (user_u user_r user_t ((s0 (c0 c1 c5)) (s1))))

	; Category range - this includes all of the categories between c0 and c2
	(context user_label_cats (user_u user_r user_t ((s0 (c0 c2)) (s1))))

	; Multiple category ranges
	(context staff_label (user_u user_r staff_t (s0 (c0 c2) c4 (c8 c20)) (s1 (c0 c100)))))

Network Labeling

	; nodecon
	(nodecon (127.0.0.1) (255.255.255.0) (system_u object_r node_lo_t ((s0) (s0))))

	; netifcon
	(netifcon eth0 (system_u object_r default_netif_t ((s0) (s0))) (system_u object_r default_packet_t ((s0) (s0))))

	; portcon
	(portcon tcp 80 (system_u object_r http_port_t ((s0) (s0))))
	(portcon tcp (1 1023) (system_u object_r reserved_port_t ((s0) (s0))))

	; IP addresses are the same as current policy with . and : formatting

File Labeling

fsuse

	(fsuse xattr ext3 (system_u object_r fs_t ((s0) (s0))))
	(fsuse task sockfs (system_u object_r fs_t ((s0) (s0))))
	(fsuse transition devpts (system_u object_r fs_t ((s0) (s0))))

genfscon

	(genfscon proc / (system_u object_r proc_t ((s0) (s0))))

filecon

        (filecon root path type context)
	(filecon "/bin" "/ls" file (system_u object_r proc_t ((s0) (s0))))

Where root is the file path root, path is the path to the file, and type is one of the following:

  • file
  • dir
  • char
  • block
  • socket
  • pipe
  • symlink
  • any

Initial SIDS

        (sid sidname)
	(sidcontext sidname context)

XSM

pirqcon

        (pirqcon pirq context)
        (pirqcon 32 (system_u object_r pirq_t ((s0) (s0))))

iomemcon

        (iomemcon iomem context)
        (iomemcon 32 (system_u object_r iomem_t ((s0) (s0))))

        (iomemcon (low high) context)
        (iomemcon (20 30) (system_u object_r iomem_t ((s0) (s0))))

ioportcon

        (ioportcon ioport context)
        (ioportcon 32 (system_u object_r ioport_t ((s0) (s0))))

        (ioportcon (low high) context)
        (ioportcon (20 30) (system_u object_r ioport_t ((s0) (s0))))

pcidevicecon

        (pcidevicecon device context)
        (pcidevicecon 32 (system_u object_r pcidevice_t ((s0) (s0))))

High Level Constructs

Macros

Macros are a mechanism meant to allow the implementation of features such as interfaces and templates that are currently used within the reference policy to simplify policy development. Macros are more general than reference policy interfaces (they allow declarations for example) but they are safer than templates (parameters are typed).

In the design of CIL several mechanisms that were broadly similar to macros were considered including allowing blocks to accept parameters and be instantiated multiple times, interfaces that closely matched the interfaces provided by the reference policy, and string-based templating. The macro facility presented here was ultimately chosen as the solution because the offered flexibility and generality with relative safety.

Broadly, macros allow the policy author to partially specify a set of policy statements and allow those statements to be instantiated later using parameters at the call site to produce fully specified policy. That is, they are a limited templating mechanism. This somewhat tortured description is meant to emphasize that macros are not function calls; they do not break the declarative nature of the policy, allow flow control, or have return values.

The macro features are composed of two statements - the macro declaration statement and the call statement for instantiating a macro. Consider the following example:

	(block apache
		(type process)
		(type exec)
		(macro signull ((type domain))
			(allow domain process (process (signull))))

	(block admin
		(type mytype)
		(call apache.signull (mytype)))

In this example a macro is declared - apache.signull - that takes a type as a parameter. Within the macro, types from the surrounding namespace are referenced in addition to the passed in type (called domain). No special syntax is required to reference parameter names and those names are, unlike other symbol declarations, allowed to shadow existing types in the same namespace.

When the macro is called below using the call statement the rules are expanded using the type mytype in place of process. The result of the macro instantiation would be as follows:

	(block admin
		(type mytype)
		(allow mytype apache.process (process (signull))))

Notice that process in the macro declaration is interpreted in the declaring namespace rather than the calling namespace. The resolution order is: 1) symbols declared in the macro, 2) parameters, 3) normal namespace resolution in the declaring scope. Also of note is that parameter names and macro local declarations can shadow other symbol names just as local names typically can.

Macros, unlike reference policy interfaces, can contain any policy statement including declarations. All statements are added to the caller namespace. For example:

	(block daemon
		(macro declare_daemon ()
			(type process)
			(type exec)
			(type log)
			; many rules ommitted
			(allow process log (file (read))))

	(block apache
		(call declare_daemon))

This example declares a set of types for a daemon in the calling namespace. Note that this can also be accomplished by inheriting from a block and the authors recommend that approach in general.

Other symbol types may be used a parameters as well, for example:

	(block apache
		(type process)
		(macro trans ((type domain) (role r))
			(allow domain process (process (transition)))
			(roletype r process))

The syntax for the macro declaration is the keyword macro, a symbol for the macro name, the list of parameters, and finally the statements for the macro.

The parameter list can contain 0 or more parameters. The symbol type of the parameter is indicated with a declaration like syntax (e.g., "((type domain) (role user_role))"). The symbol type is one of the keywords for a symbol declaration or declaration like statements such as contexts. The full list of acceptable parameter types is:

  • Type: (type domain)
  • Role: (role user_role)
  • User: (user user_role)
  • Sensitivity: (sensitivity low_sens)
  • Category: (category some_cat) - this can only be a single category.
  • Category Set: (categoryset low_cats) - this must be a set of categories.
  • Level: (level some_level)
  • Object Class: (class someclass) - a single object class
  • Permission Set: (permissionset perms) - one or more permissions
  • IP Address (ipaddress some_address) - IP v4 or v6 address There is no way to pass a single permission.

Calling macros is done using the call statement. For example:

	(block myapp
		(type process)
		(call make_domain (process)))

In this example, the macro make_domain (declared elsewhere) is called with the process type from the local namespace.

Call parameters are typed and are interpreted according to the parameter types in the macro declaration. This allows the use of anonymous objects, such as contexts or permission sets, if desired. For example, consider the following example which shows calling the same macro with named and anonymous permission sets.

	(permissionset create_write (create write link append))
	(call some_macro (create_write))
	(call some_macro ((create write link append)))

In this example, the macro some_macro is called first with the named permission set create_write and then again with an anonymous permission_set. In general, for parameter types that can be named or anonymous the named argument is a bare symbol while the anonymous object is enclosed in parentheses.

Below is an example declaring and calling a macro with each parameter type (with anonymous and named objects as appropriate):

	(macro type_macro ((type foo))
		(allow foo bar (file (write))))
	(type mytype)
	(call type_macro (mytype))

	(macro user_macro ((user foo))
		(userrole foo sysadm_r))
	(user bob)
	(call user_macro (bob))

	(macro role_macro ((role foo))
		(roletype foo sysadm_t))
	(role sysadm_r)
	(call role_macro (sysadm_r))

	; Note the use of parentheses to convert the sensitivity into
	; an anonymous level in the context statement
	(macro sens_macro ((sensitivity low))
		(context low (system_u system_r some_t ((low) (low)))))
	(sensitivity s0)
	(call sens_macro (s0))

	(macro cat_macro ((category cat))
		(context low (system_u system_r some_t ((s0 cat) (s0 cat)))))
	(category c0)
	(call cat_macro (c0))

	(macro catset_macro ((categoryset catset))
		(context low (system_u system_r some_t ((s0 catset) (s0 catset)))))
	(categorset some_cats c0 c1 (c5 c22))
	; Call with named category set
	(call catset_macro (some_cats))
	; Call with anonymous category set - note the need for parentheses
	(call catset_macro ((c0 c1 (c5 c22))))

	(macro level_macro ((level lev))
		(context low (system_u system_r some_t (lev lev))))
	; Call with a named level
	(level low (s0 (c0 (c4 c7))))
	(call level_macro (low))
	; Call with an anonymous level
	(call level_macro ((s0 (c0 (c4 c7)))))

	(macro class_macro ((class c))
		(allow foo bar (c (read write))))
	(call class_macro (file))

	; TODO - update when classset is finalized
	(macro classset_macro ((class c))
		(allow foo bar (c (read))))
	(classset dirfile (dir file)
		(read
			(file (read getattr))
			(dir (read getattr)))
		(write 
			(file (write append create link))
			(dir (write append create link)))
		(execute
			(file (execute))
			(dir (read))))
	(class classset_macro file)
	(class classset_macro dirfile)

	(macro perm_macro ((permissionset perms))
		(allow foo bar (file perms)))
	; Call with named set
	(permissionset read_append (read getattr append))
	(call perm_macro (read_append))
	; Call with anonymous set
	(call perm_macro ((read getattr append)))

Additional examples:

	(macro node_label ((type t) (ipaddress node) (ipaddress netmask) (sensitivity low))
			   (categoryset low_cat) (sensitivity high) (categoryset high_cat))
		"Label a node - this is an example to show how to pass many parameter types"
		(nodecon node netmask (system_u system_r t ((low low_cat) (high high_cat)))))

	(call node_label (foo_t (192.168.1.1) (255.255.255.0) s0 c0 s1 ((c0 c254))))

Delete

The deletion statement removes CIL statements and fixes any dangling references. It can be used to remove both declarations and access. The delete statement, like the inheritance filters, contains a list of policy statements that will be removed from the final policy. For example:

	(block apache
		(type process)
		(type webcontent)

		(allow process webcontent (file (read getattr write))))

	(delete
		(allow apache.process apache.webcontent (file (write))))

This example deletes write access for apache.process to apache.webcontent.

Delete shares the same basic features and semantics as the inheritance filters. The difference is that the inheritance filters only suppress the inheritance of access (i.e., it only applies to the set of access granted by inheritance and that access can be re-added by other means) while delete works on the entire policy and the deletion is permanent. The permanence of the deletion is required to preserve the declarative, unordered nature of the policy and makes the deletion mechanism safer to use. The nature of the deletion mechanism means that it is only appropriate for use by administrators. The use of delete by policy maintainers or architects would prevent administrators from ever re-adding the access.

Deletion allows the removal of symbol declarations and will automatically fix all references to make the policy internally consistent (though this may make the policy incomplete). For example:

	(block apache
		(type process)
		(type accesslog)
		(type webcontent)

		(allow process accesslog (file (append)))

		(allow process webcontent (file (read getattr write))))

	(delete
		(type apache.accesslog))

In this example the type apache.access log is deleted, resulting in the removal of the allow rule in the apache block referencing this type. All policy statements that reference symbols will be removed in this way including symbol associations (e.g., typeattributetypes), rules (e.g., allow), and labeling (e.g., filecon).

Like the inheritance filters, deletion does not allow for the removal of access only in a specific syntactic location (e.g., in a single block or in a macro). If that facility is needed transform can be used.

Transform

Transform allows for the selective rewriting of policy statements. It is, in many ways, the most powerful CIL statement, but that power comes with complexity and the possibility of creating invalid policy because the transform manipulations are largely syntactic.

The general form of transform is a search for a policy statement and a set of statements that replace all of the statements that were found. For example:

	(in passwd
		(transform (allow $domain .shadow (file $perms))
			(allow domain .shadow (file perms))
			(auditallow domain .shadow (file perms))))

In this example, the passwd block is entered and every occurrence of an allow rule allowing access to files of type shadow is replaced with the same allow rule and a corresponding auditallow rule.

This example begins with entry into a namespace ("(in passwd"). Transforms work within a namespace and given the intended use of transform this form will be typical. Next is the transform statement it self. The first line is the transform keyword followed by a policy statement:

	(transform (allow $domain .shadow (file $perms))

Each transform statement begins with a single policy statement. The statement is used to search the current namespace and to bind symbols from that symbol for use in the body of the transform. In this statement, the statement is an allow rule. For each symbol in the allow rule that is not

(in apache (type tmp) (transform (type $foo) (type foo) (allow foo tmp (file (write))))) }}} In this example, the apache block is entered, a type type named tmp is added to the block, and then a transform

; Add auditallow rules for writing to shadow (traverse . (transform (allow $domain shadow (file (write))) (allow domain shadow (file (write))) (auditallow domain shadow (file (write)))))

(traverse apache (transform (call all_nodes $domain) (call default_nodes (domain))))

; replace all of the contexts that contain foo_t with bar_t (traverse apache (transform (filecon $root $path $options (con $user $role foo_t $level)) (filecon $root $path $options (con $user $role bar_t $level)))) }}}

Keyword Reference

  • Type Declaration: type, typeattributetypes, typealias, typebounds, typepermissive, typeinherit, typeabstract, typeinheritfilter
  • Attribute Declaration: typeattribute, typeattributeinherit, typeattributeabstract, typeattributeinheritfilter
  • User Declaration: user, userrole, userinherit, userabstract, userinheritfilter
  • Role Declaration: role, roletype, roledominance, roleinherit, roleabstract, roleinheritfilter
  • Object Classes: class, classet, permissionset, classid (??? assign ids)
  • Block: block, blockinherit, blockabstract
  • Namespaces: in, traverse, global
  • Access Vector Rules: allow, auditallow, dontaudit, neverallow
  • Transition Rules: roletransition, typetransition, typemember, typechange
  • Role Rules: roleallow
  • Conditional and Tunable Rules: if, ifdef, else, optional, neq, eq, xor
  • MLS: sensitivity, sensitivityalias, dominance, category, categoryalias, categoryorder, categoryset, sensitivitycategory, mlsconstrain, level, l1, l2, h1, h2, dom, domby, incomp
  • Constraints: constrain, validatetrans, u1, u2, u3, r1, r2, r3, t1, t2, t3, l1, l2, h1, h2, not, and, or, !, &, |, source, target
  • General Labeling: context, ipaddr
  • File Labeling: fsuse, filecon, genfscon
  • XSM Labeling: pirqcon, iomemcon, ioportcon, pcidevicecon
  • Initial SIDS: initialsid
  • Network Labeling: netifcon, portcon, nodecon
  • Macros: macro, call
  • Policy capabilities: policycap
  • ?? transforms

TODO

  • Requires completion: users, roles, booleans, tunables, inheritance, AV rules, Transition Rules, Conditional / Optional / Tunable, transforms, abstract
  • New: objects, constraints, file labeling, network labeling, initial sids, xsm labeling, policycap
  • Updates: MLS Constraints (multiple object classes), macros (parens)

Open Questions

  • Colons (:) in symbols - should they be allowed?
  • Preserve common permissions?
  • Should typealias be allowed to introduce names into other namespace? The current answer is no, but it might be confusing rather than nicely consistent.
  • When entering macros should we force a restating of all or some parameters? Currently, you can just use the parameters, but it looks a little odd.
  • Should it be possible to make empty category sets - see the macro example that expects categories.
  • Should there be a statement for category ranges separate from sets? The only advantage would be restricting down to a range rather than an arbitrary set. Current the answer is no and there is no example of where it would be needed.
  • Should category sets be able to be nested. The main reason to do this is to reference a category set plus a few more categories (current answer is no):
	(category c0)
	(category c1)
	(category c2)
	(category c3)
	(categoryorder (c0 c1 c2 c3))

	(categoryset somecats (c0 c2 c3))

	(sensitivity s0)
	(sensitivity s1)

	; Nested categories would allow us to use a names set with more cats
	(sensitivitycategory s0 (somecats c1))
  • Do category sets and categories share a namespace - if we allow nesting, then they should.
  • Is it really an error for a sensitivity to not appear in a dominance statement? When does incomp apply in mlsconstrain?
  • Should there be a way to access parent namespaces - here is an example of why you might want to:
	(block apache
		(type process)
		(type log)
		(block cgi
			(type process))
		; these rules can't be in the cgi block because we can't access parent
		(allow cgi.process process (process (signull)))
		(allow cgi.process log (file (read write))))

	; Now for some inheritance - I want to create a new cgi process
	(block webapp
		(blockinherit apache.cgi)
	; Here is the resulting policy - this is what we would want
	(block webapp
		(type process)
		(allow process apache.process (process (signull)))
		(allow process apache.log (file (read write))))

	; However - if we had written this instead
	(block apache
		(type process)
		(type log)
		(block cgi
			(type process)
			; these rules use full names because we can't refer to the parent
			(allow process apache.process (process (signull)))
			(allow process apache.log (file (read write)))))
	; The cgi inheritance now doesn't work because of the fully
	; qualified symbols (the webapp block above) and
	; apache doesn't do what we intend either
	(block myapache
		; we would now have myapache.cgi that could write to apache.log but not myapache.log
		; The first version (with the rules in apache) would work fine.
		(blockinherit apache))

There are three solutions to this - 1) document and tell people not to do version 2, 2) 'detect' the relationship between cgi and apache and fixup those rules as well, or 3) give some syntax to refer to the parent namespace. I'm for 1 - just say that you should only access 'down' the namespace hierarchy, not up and that is currently what is specified.

  • Do we need the ability to pass a list of object classes to macros (different from the classset facility). Current answer is no.
  • Are explicit, anonymous objects necessary?
  • How are named contexts handled for inheritance? Current answer is that they are inherited and conflicts can happen.
  • Should tcp / udp be keywords for portcon?
  • Should portcon sorting or explicit ordering be introduced?
  • Is there a need for more complex ways of defining attributes to deal with the lack of sets in rules (e.g., to allow the creation of an attribute that has domain except unconfined or domain)? Should this be done with attributeinheritance? Possible example:
	(attribute notdomain)
	(attributedefine notdomain (~ domain))

	(attribute contentfiles)
	(attributedefine contentfiles apache.content samba.content)

Resolved: - Keywords as identifiers (role role) - resolved, this is allowed for non-global namespace identifiers. - Type hierarchy operator - this is resolved, dot will be reclaimed and only explicit hierarchy will be retained. - Named optionals and entering with the in keyword. Optionals are aproblem because they are processed as a block (the whole optional isenabled or disabled as a block) but there is no way to refer to anoptional after declaration. Possible solutions to allow in andtransformations include naming optionals or processing themstatement-by-statement (meaning that adding optionals to a namespacelater is semantically the same). Fix - use named conditionals. - Should we force a preceding dot (.foo_type) to refer to symbols inthe global namespace if we are not in currently in the globalnamespace? Resolution - this is optional syntax. - Shortened names (attr) or full names (attribute)? Resolution - no, this is not worth it. Just use long names. - Should it be possible to pass security contexts into macros (resolution - yes)? Should the macro parameter syntax become ((type foo) (con user_u user_r user_t))? With type still optional (resolution - yes to the first but type is never optional)? - Should there be a way to pass category sets to a macro? Resolved, yes.

Extended Examples

TODO - This example is not currently correct. Update!!! {{{ (block apache (type process) (alias apache apache_t)(type log) (type pid) (type user_cgi) (typeattribute httpd_sys_content) (typeattribute cgi_script)

(interface read_log (x:type) (allow x log (file dir) (read getattr)))

(typeattributetypes cgi_script (user_cgi)) (domain_type process)

(allow process log (file (read write)))

(corenet_tcp_sendrecv_all_nodes process))

; object classes (class file (read write open))

; local.cil

(type my_t)

(allow my_t apache.log (file dir) (read getattr))

; replace all calls to corenet_tcp_sendrecv_all_nodes (template (match block[@name=apache]/ifcall[@ifname=corenet_tcp_sendrecv_all_nodes]) (ifcall corenet_tcp_sendrecv_generic_nodes (value-of @ifname)))

; replace using a slightly different syntax (template (match block[@name=apache]) (for-each (select ifcall[@ifname=corenet_tcp_sendrecv_all_nodes)) (ifcall corenet_tcp_sendrecv_generic_nodes (value-of @ifname))))

; IN

(in apache (allow process bar (file (write))))

(in apache.read_log (allow x log (file (write))))

}}}

(optional ((block apache)) (call apache.foo (bar)))

- Add ifdeclared and line-by-line optional - remove default type on paramater

The Code

To check out the in-progress CIL compiler, just:

git clone https://github.com/SELinuxProject/cil

Or download the v0.1 release at https://raw.githubusercontent.com/wiki/SELinuxProject/cil/files/secilc-0.1.tar.gz

CIL Quick Reference

CilContainers Container statements (block, macro, optional, etc)

CilUsers User Statements

CilRoles Role Statements

CilTypes Type Enforcement and Attribute Statements

CilTeRules Type Enforcement Rules

CilAvRules Access Vector Rules

CilConstraints Constraints

Clone this wiki locally