CCL is a language for configuring CerbNG access control rules. It has been designed to provide flexible and convenient mechanism for describing CerbNG behavior in analysing and performing system call access control.
CCL is very different from "language" used for configuring the original Cerb. The syntax resembles syntax of C programming language. C programmers will be familiar with most constructs of CCL. You should know C to understand this guide after first reading, because it is rather terse.
You write a program in CCL using text editor of your choice. When the program is finished, you can `execute' it using cbctl utility, which compiles the code and loads it into kernel. Sample cbctl invocation:
# cbctl -f myrules.cb
-f switch makes cbctl read rules from file following it (by default rules are read from standard input). -p switch turns off cpp(1) preprocessing of the rules file before loading it. Other switches are: -l for listing the rules for verification, -t displays CCL parsing tree for the file before loading it, -n prevents loading the rules into kernel (it doesn't block listing the rules with -l and -t switches though). For full option listing run cbctl -h.
CCL is a high level language. It operates on objects which are really low level kernel structures, but lets you see them from a "bird's eye view".
A program in CCL consists of a series of statements executed from top to bottom. The only exception to this rule are conditionals which are described below. Statements are separated with semicolons. Some statements have side-effects (like assignments and system calls), others just return value, which can be used for comparisons, as one of arguments in function call or as operand for one of built-in operators.
The single most important element of CCL syntax are conditionals. CerbNG's main purpose if filtering access to system calls, so comparing syscall numbers to predefined list, validating syscall arguments and process variables is main part of CerbNG programming.
CCL conditionals look like those in C, namely:
if (some condition) { statement_a; statement_b; ... } else { statement_m; statement_n; statement_o; ... }
(the else { ... } part is optional, braces enclosing statement blocks are mandatory)
Conditional itself is a statement, thus conditionals can and often are nested. Conditionals do not return value. Depending on value returned by condition check, the first or the second part of conditional statement series is executed. The only false value in CCL is 0. Any other value is equivalent to boolean truth. There is no special type for logical truth and false in CCL.
Examples of conditions:
Expression | Boolean Value |
---|---|
1 < 2 | true |
0 | false |
2 - 5 | true |
function() | equal to value returned by function call |
"some string" | true |
CCL is a full blown programming language. It has its limitations, but lets you do many things which other programming languages can. Syntax of expressions as you can see above is similar to expressions in C language. Other expressions are also similar:
Expression | Explanation |
---|---|
function(x, y, z) | Function call with arguments |
return 0 | Returning value to userland |
retval = call() | Execution of current syscall and assignment of returned value to some variable |
reg[0] = reg[1]& 0xf0f0 | Assignment of internal register array |
There are three basic types of objects in CCL: integers, strings and variables.
Integers can be signed or unsinged. Integers can be obtained directly ie. as written value (1, -4, and so on) or indirectly as a result of function call or operation. There are many arithmetical and logical operators which take integers as arguments. Most of them are identical to C operators (for full list of operators, their precedence and binding see Appendix A). Each operator having integers as arguments returns integer. Some examples of expressions using integers:
Expression | Result | Comment |
---|---|---|
1- 2 * 3 | -5 | Result of operations on positive numbers can be negative |
1 + (11 % 3) | 3 | Precedence of operations can be explicitly set using parentheses |
7 / 2 | 3 | Division operator returns floor of real result. No floating point numbers support |
1 > 5 | 0 | Comparisons return number |
! 9 | 0 | Logical operators return numbers |
Strings are texts enclosed in double apostroph signs. The only three available string operations are equality comparison, concatenation and matching. Checking string equality is done using == operator. Concatenation of strings is done by the compiler, so no variables can be used as arguments. Matching strings to patterns takes form of expression @ pattern. For convenience there is an operator checking of given string and pattern don't match - !@. String expressions examples:
Expression | Result | Comment |
---|---|---|
"aaa" == "b" | 0 | false |
"aa" == "aa" | 1 | true |
"foobar" @ "fo*b?r" | 1 | true |
"foo" "bar" | "foobar" | concatenation |
Arrays are variables containing one or more values. Consecutive values are referenced by indexing variable name. Like array[<element number>]. Arrays may contain elements of different types.
Variables in Cerb are not declared. Their type is type of value assigned to them.
Cerb for storing things uses so called registers. Registers are available by array named reg. You can assign and retrieve each register by accessing consecutive array slots, like this:
reg[0] = call(); return reg[0];
Using registers is not convenient, since you have to memorize the indices every time you utilize them. CCL compiler alleviates the problem mapping variable names of your choice to registers. Thus expressions like:
myvar = 10;
are equivalent to accessing register associated with particular variable. Registers for variable mappings are allocated from 16 up. Registers 0-15 are so called scratch registers. You can use them for storing temporary values. Maximum number of registers is defined as CB_NREGS_G in file kcerb/cerb_globals.h.
There is a special class of registers - global registers. You reference them as elements of array greg. They are different from standard registers by their persistence. Values stored in global registers are kept between consecutive Cerb entry points. One simple example of their usage is counting how many times some syscall have been run to limit number of syscall invocations (eg. keep a network daemon from binding to socket more than once). Number of global registers is defined as CB_NGREGS_G in file kcerb/cerb_globals.h
Most syscalls have some arguments, which modify their actions. Arguments of each syscall are available through array named arg. This array is indexed from 0 to number of arguments of currently analyzed syscall. You can check and modify arguments of syscalls reading and assigning elements of this array.
Assigning and reading of some variables has predefined meaning. These variables, modify current process environment. For full list see Appendix B.
(growing precedence)
Operators | Operands | Binding |
---|---|---|
= | binary | right |
|| | binary | left |
&& | binary | left |
| | binary | left |
^ | binary | left |
& | binary | left |
== != | binary | left |
@ !@ (pattern matching) | binary | left |
< <= > >= | binary | left |
<< >> | binary | left (available only during compilation) |
+ - | binary | left (available only during compilation) |
* / % | binary | left (available only during compilation) |
! ~ - | unary | right |
All of the above operators work identically to C language operators
Variable name | Description |
---|---|
syscall (UDEF) | Number of currently intercepted syscall. If return value is equal SYS_MAXSYSCALL, it means that CerbNG rules have just been loaded and one can take any initialization steps needed |
syscallname (STR) | Name of currently intercepted syscall |
errno (DEF) | Error number of last operation (it is not return value of executed syscall) |
pname (STR) | Current process name |
pid (UDEF) | Process ID |
ppid (UDEF) | Parent process ID |
pgid (UDEF) | ID of primary group process owner belongs to |
ruid (UDEF) | Real user ID of process owner |
rgid (UDEF) | Real user group ID of process owner |
euid (UDEF) | Effective user ID of process owner |
egid (UDEF) | Effective group ID of process owner |
svuid (UDEF) | Saved user ID of process owner |
svgid (UDEF) | Saved group ID of process owner |
groups (UDEFPTR) | GID Numbers of all groups process owner is member of |
umask (UDEF) | Umask value of current process |
login (STR) | Login name of session owner |
prison (ST_PRISON) | Pointer to prison structure of current process (if it is executed in jail). Available functions for this structure: isjailed(), getjailip(), getjailhost() |
cdir (STR) | Current directory of process |
rdir (STR) | Root directory of current process |
jdir (STR) | Jail directory of current process |
retval0 (DEF) | Return value of syscall |
retval1 (DEF) | Additional return value of syscall (used if COMPAT_43 or COMPAT_SUNOS are defined) |
fname (STR) | File name of executed image |
finode (UDEF) | Inode number of current process executable file |
fdev (UDEF) | Device number of current process executable file |
fuid (UDEF) | User ID of current process executable file |
fgid (UDEF) | Group ID of current process executable file |
fmode (UDEF) | Mode bits of current process executable file (chmod(1)) |
ftype (UDEF) | Type of current process executable file (stat(2)) (XXX) |
fflags (UDEF) | Flags of current process executable file (chflags(1)) |
fnlinks (UDEF) | Number of hard links of current process executable file |
fsize (UDEF) | Size of current process executable file |