Security policy language

Updated: April 19, 2023

Because the simplest way to develop a security policy is to use secpolgenerate to generate an initial policy, your initial use of the security policy language is usually to help interpret the types and rules that the tool generates. Later, you can use the language when you revise the policy to address any gaps in security, correct errors, or edit it for simplicity.

You can add white spaces to your security file statements which can help make it easier to read but are ignored by the compiler. Comments are indicated by a # (pound sign) and extend to the rest of the line.

Defining types

The type statement declares a security type by name. In the following example, screen_t is the type:

# Type to represent the screen process 
type screen_t;
Type names may contain alphanumeric characters and underscores. They cannot start with a number. A hyphen in a type name will generate an error when the policy is compiled.
Note: The type name default is reserved, must be declared only if it is referenced, and is associated with type ID 0. QNX strongly recommends that you don't use default as the type for any process.

Granting procmgr abilities

Rules with the ability keyword grant an ability to a type. For example, the following rule gives the type server the abilities that allow it to map physical memory over some range, switch to user IDs 4, 6 and 23, and create dynamic abilities:

allow server self:ability {
    mem_phys:0x1200000-0x24000000
    setuid:4,6,23
    able_create
};

For a list of abilities, see Abilities.”

Ability ranges

As shown in the previous example allow rule, a rule that grants an ability can include a range. You can specify a single range or a comma-separated list of ranges. For the majority of abilities, ranges are numeric and consist of a range start and end value separated by a hyphen. The start and end are represented in decimal, octal, or hexadecimal with conventional notation.

If only the start of range is specified with no hyphen, the start and end values are the same. If the end is omitted but the hyphen is still present, the end of the range is equal to the largest possible value.

A range for the settypeid and channel_connect abilities uses names rather than numbers because these abilities reference types, which have a numeric value that can change. For example:

allow server self:ability {
    # Grant mem_phys over the range 1024 to 4096 and 
    # 0x1200000 to 0x24000000
    mem_phys:1024-4096,0x1200000-0x24000000
    # Grant setuid for 4, 5, 6, 23 and 96 and above
    setuid:4-6,23,96-
    # Grant settypeid for the type ids associated with types
    # server1 and server2
    settypeid:server1,server2
    # Grant channel_connect for type server3
    channel_connect:server3
    };

You can add spaces when you specify the range (e.g., to make it easier to read). It is not necessary to specify all ranges for a given ability in a single rule. If multiple rules specify ranges for the same ability, the individual ranges are combined.

Named ranges

You can also specify ability ranges using a name. This is useful when the range values are not known until the system boots (for example, address and interrupt ranges for devices).

The range names and properties are defined in a range name file and are resolved to their actual values when the policy is pushed to procnto.

To use named ranges in a policy, you declare the ranges and then use them with the appropriate abilities. For example:
range gpu_mem;
range gpu_int;

allow screen_t self:ability {
    mem_phys: gpu_mem
    interrupt: gpu_int
    setuid: gpu_mem
};

The ranges for an ability can contain both named and unnamed ranges.

To generate policies that use named ranges, use secpolgenerate with the -r option. To push them, use secpolpush with -r.

For information on the format that the named range file uses, see Named range file.”

Granting abilities to non-root processes

By default, a process gets the abilities specified in an allow statement only if it is root (i.e., when it has an effective user ID of 0). To grant them when the process is non-root (effective user ID other than 0) as well, include the nonroot option. For example:

allow server self:ability {
    nonroot
    mem_phys:0x1200000-0x24000000
    setuid:4,6,23
    able_create
};
The nonroot option grants abilities to both root and non-root processes. You cannot grant an ability to non-root processes only.

Unlocking abilities

The unlocked option allows a process to change its abilities from those granted via its security policy type. Although this option does not appear in generated policies, you can add it as required to accommodate special cases where the configuration of abilities needs to be flexible.

For example, you need to restrict a processes uses of mem_phys to a named memory range from the asinfo section of the syspage, but the range of addresses changes each time the system is booted. If the mem_phys ability is unlocked, the exact range can be configured at runtime.

allow server self:ability {
    unlock
    mem_phys:0x1200000-0x24000000
};
If any rule unlocks a particular ability it remains unlocked irrespective of any rules that may follow.
Note: To allow the security policy to fully control abilities without causing failures when processes try to configure their abilities themselves, run procnto with the -bl ("el") option. In future releases, this behavior will be the default. For more information, see procnto* in the Utilities Reference.

Changing whether abilities are inherited

By default, abilities that policies grant to a process are inherited by any child process. The noinherit keyword is available to prevent these abilities from being inherited. However, the default_spawn_type keyword is a simpler, more flexible way to configure inheritability (see Forcing a type change when spawning).

Custom abilities

In some cases, a process needs a custom ability (sometimes called a dynamic ability). This type of ability must be declared in the security policy. For example, if server also needs to be able to bind to a privileged TCP port:
ability network/bind/privport;
 
allow server self:ability {
    network/bind/privport
};

Rules for connecting to channels

By default, a process of any type is only allowed to connect to channels that have the type default. But because the type of a channel defaults to the type of the process that creates it, channel types are usually not default. Most channels require explicit rules to allow processes to connect to them.

The channel class supports the connect permission, which controls a process's ability to connect to a channel on the same node.

For example, the following allow statement allows a process of type mm_client to connect to a channel on the same node that has the type mm_server:

allow mm_client mm_server:channel connect;

Allow a process to attach one of its channels to a path

An allow_attach rule allows a process to use resmgr_attach() to attach one of its own channels to a path. You can also specify the type for the channel after the process attaches it. If a process has multiple channels, this rule allows you to apply different rules to each channel. Paths can include * (a wildcard that can represent any name) and … (ellipsis that indicates any path below its location is a match).

In the following example, the rule allows io-pkt to attach to many paths under /dev/socket. The type of any channel it attaches is socket_t:

allow_attach io_pkt_t /dev/socket/* socket_t;

The following rule allows a process with the type unrestricted_t to attach a channel to any path. It does not change the type of the channel:

allow_attach unrestricted_t /…;

Allow a process to create a symbolic link or attach the channel of another process

An allow_link rule allows a process to create a procmgr symbolic link or attach the channel of another process to a path. It affects the ability of a process to use the functions pathmgr_symlink() and pathmgr_link() (when the channel involved belongs to another process). Paths can include * (wildcard) and … (ellipsis that indicates any path below its location is a match).

In the following example, the rule allows a process of type type1_t to replace the dynamic linker by pointing it to somewhere other than /proc/boot/ldqnx-64.so.2:

allow_link type1_t /usr/lib/ldqnx-64.so.2;

Forcing a type change when spawning

The default_spawn_type keyword indicates that the process is required to change types on spawn. If the process explicitly provides a type, there is no change in behavior; if it doesn't, it changes to the new type specified in the rule.

For example, the following rule requires a type1_t process to switch to type2_t when it spawns (unless the process changes type on its own):

default_spawn_type type1_t type2_t; 

Using custom classes and actions

Security policies also work with custom classes of permissions that you define.

For example, the following statement defines the class file and its permissions:

class file { read write delete execute };

The following rule grants permissions using the new class file:

allow ptype ftype : file read;
The system checks whether the permissions you grant are associated with the class you've specified. For example, using the file class example, the following statement generates an error:
allow ptype ftype : file connect;
Continuing with the file class example, a resource manager can use the following code to check if an operation is permitted:
static secpol_permission_t *s_file_read_permission;
static secpol_permission_t *s_file_write_permission;
void
init()
{ 
    s_file_read_permission = secpol_get_permission(handle, "file", "read", 0);
    s_file_write_permission = secpol_get_permission(handle, "file", "write", 0);
}

bool
can_read(const struct _msg_info *info, uint32_t ftype)
{
    return secpol_check_permission(info, ftype, s_file_read_permission) == 0;
}

While a resource manager can specify any name for a class or permission, it is strongly recommended that each resource manager use a unique class name. In principle, only the combination of class name and permission has to be unique. However, using the same class name for objects that do different things could result in inappropriate permissions being available to a type via a class.

The self type

The type name self is reserved and specifies that the type of the object acted on is the same as the type of the process taking the action.

For example, the following rule grants an ability to random_t:

allow random_t self:ability { io }; 

This rule has an effect that is equivalent to the following rule:

allow random_t random_t:ability { io };
However, there is no advantage to specifying the object type by name, and using self prevents a possible mismatch of types when, for example, the process type is a set of types.

Using self can help simplify rules that specify a set of types or use attributes that group types (see Using an attribute to group types).

Derived types

Processes often need to be able to change types as a way to drop privileges after they start up and when they spawn child processes. However, because the types a system uses are specific to each system, specifying the type to change to would require a command line option or equivalent mechanism.

To meet the requirement without a new command line option, a type ID that is determined by the process's current type and the name passed in (i.e., a derived type) is used with the secpol_transition_type() and secpol_posix_spawnattr_settypeid(). Using derived types allows a process to hard code names that are then used to derive a final type from its current type.

A derive_type statement uses the following syntax:
derive_type type1 name type2;
For example, if a process is running as type1 and makes the following function call, it switches to type2:
secpol_transition_type(NULL, "name", 0);
If it is running as some other type and there is a matching derive rule, it switches to the type specified by that rule.
You can specify sets of source types and names in a derive_type rule. For example, the following statement sets a process to type3 when the process is either type1 or type2 and uses either the run or init string in a call to secpol_transition_type():
derive_type { type1 type2 } { run init } type3;

Specifying which privileges can be acquired by switching type

Types with the settypeid ability can switch to the types specified by that ability. The secpolcompile utility prevents the creation of a security policy that unintentionally allows a process to gain privileges that it should not have. If switching its type gives a process one or more additional privileges, those privileges must be specified using gain_priv.

In the following example, the server type can switch to server1 or server2. By switching type (to server1, server2, or both), it gains the pathspace ability. The gain_priv ability specifies that this additional privilege is allowed:

allow server self:ability {
    # Grant settypeid for the type ids associated with types 
    # server1 and server2
    settypeid:server1,server2
    # Grant channel_connect for type server3
    channel_connect:server3
    # Allow a switch to a type that has the pathspace ability
    gain_priv:pathspace
};
The policy compiler generates the following output related to gain_priv:
  • A warning for any privileges that gain_priv specifies but the type does not gain by switching its type to one specified by settypeid.
  • An error for any privileges that the type gains by switching type that are not specified by gain_priv.

To see which type specified by settypeid is associated with a privilege specified by gain_priv, comment out gain_priv in the policy and compile it. The error messages indicate the type associated with each missing privilege.

Specifying custom class permissions with gain_priv

To specify a custom permission with gain_priv, use a string with the following format:
class:perm:type
where a * can replace perm, type, or both to indicate all permissions or all types.
For example, a class file has permissions read and write. The following statement allows a process to gain the privilege to read files of type file1_t:
gain_priv: file:read:file1_t
Using the same example, the following statement allows the type to gain all privileges for type file1_t:
gain_priv: file:*:file1_t

Using an attribute to group types

You can use the attribute keyword to group a set of types that have some security attributes in common. Attribute names must be different than type names. Use a comma as shown in the following syntax to associate an attribute name with a type name after you declare it:

type type-name [, attribute-name] ... ;

When you specify an attribute for a type, the type gets any permissions that are granted to the attribute. Types can be associated with multiple attributes. Attributes do not have type IDs.

In this example, the following section of a security policy could be rewritten using an attribute and the reserved type self:

allow secure1_t secure1_t : channel connect;
allow secure2_t secure2_t : channel connect;
allow secure3_t secure3_t : channel connect;

For example:

attribute secure;
type secure1_t, secure;
type secure2_t, secure;
type secure3_t, secure;
allow secure self : channel connect;
You can use attributes when you specify sets of process types or object types, or a combination of types and attributes.

Apply default rules to types

The type default_rules allows you to give privileges to other types by default. Only types that have an allow rule in their definition get the default privileges.

For example, the following rule gives the spawn ability to all applicable types:

allow default_rules self:ability spawn;

If you don't create any rules that assign privileges to default_rules, by default it is given all unprivileged abilities. To disable this behavior, create the following rule. It makes sure types get no privileges via default_rules:

allow default_rules self:ability {};

Rules for sets of types, targets, and classes

You can revise or create rules to specify multiple types, multiple permissions, or both (rules generated by secpolgenerate specify only individual types).

The effect of this type of allow statement is equivalent to creating separate allow statements from all combinations of the set of process types, the set of object types (i.e., the target of the process's action), and the set of permissions.

For example, the following rule assigns permissions to two types that are applied to processes. The permission is for connecting to a channel that is one of two types:

allow { type1 type2 } { type3 type4 }:channel connect;

The following set of rules creates permissions that are equivalent to the example allow statement:

allow type1 type3:channel connect;
allow type1 type4:channel connect;
allow type2 type3:channel connect;
allow type2 type4:channel connect;

You can also specify sets of custom classes, as long as all the classes in the set support the permissions the rule specifies (see Using custom classes and actions).

Example of an uncompiled policy file (secpol.txt)

The following example policy content controls the use of the path /dev/screen by the screen service and its clients:
# Type to represent the screen process
type screen_t;
# Type to represent screen clients (you might want more than one type)
type screen_client_t;
        
allow_attach screen_t /dev/screen;
allow screen_client_t screen_t : channel connect;
        
This policy declares the type screen_t (to use when starting the screen service) and screen_client_t (to use when starting its clients). The allow_attach rule states that any process labeled with screen_t is allowed to attach a channel to the path /dev/screen. After this policy has been installed in procnto, it is not possible to attach any path that is not explicitly allowed, including procmgr symbolic links.

The example shows the simpler form of the allow_attach statement where there is no change in the type associated with the channel being attached. Channels inherit the type of the process that owns them; this means that this channel will also have a type of screen_t. By default, no process is able to connect to a channel with type screen_t. To make the channel useful, an allow statement is added that allows a process with the type screen_client_t to connect to a channel that has a type screen_t.