diff --git a/data/error_policies/fs_context.cas b/data/error_policies/fs_context.cas index 71f7acd3..bd89a16a 100644 --- a/data/error_policies/fs_context.cas +++ b/data/error_policies/fs_context.cas @@ -5,7 +5,10 @@ resource foo { fs_context(this, "proc", zap); fs_context(this, "sysfs", genfscon, "/zap", [bar]); + fs_context(this, "sysfs", genfscon, "/zap", [file bar]); + fs_context(this, "fs1", xattr, "/zap", [file dir]); + fs_context(this, "fs2", task, "/zap"); - // Policies must include at least one av rule - allow(domain, foo, file, [read]); + // Policies must include at least one av rule + allow(domain, foo, file, [read]); } diff --git a/data/error_policies/fs_context_dup.cas b/data/error_policies/fs_context_dup.cas index 76687c5c..33ecede3 100644 --- a/data/error_policies/fs_context_dup.cas +++ b/data/error_policies/fs_context_dup.cas @@ -7,8 +7,8 @@ resource foo { fs_context(this, "test", genfscon, "/zap/baa", [file]); - // Policies must include at least one av rule - allow(domain, foo, file, [read]); + // Policies must include at least one av rule + allow(domain, foo, file, [read]); } resource bar { diff --git a/data/policies/fs_context.cas b/data/policies/fs_context.cas index 93ebe58a..ca639902 100644 --- a/data/policies/fs_context.cas +++ b/data/policies/fs_context.cas @@ -10,8 +10,8 @@ resource foo { // TODO re-add when secilc check is in place // fs_context(this, "sysfs", genfscon, "/zap", [dir]); - // Policies must include at least one av rule - allow(domain, foo, file, [read]); + // Policies must include at least one av rule + allow(domain, foo, file, [read]); } // TODO re-add when secilc check is in place diff --git a/doc/TE.md b/doc/TE.md index 8b930eb4..056b9362 100644 --- a/doc/TE.md +++ b/doc/TE.md @@ -4,12 +4,12 @@ type enforcement, objects on the system (processes, files, sockets, etc) are each assigned a unique identifier known as a type. Policy rules specify how types may interact. -Type Enforcement policies have a subject, which attempts to perform some -action, an object which is acted upon, and a description of the action(s). In -SELinux this description is made up of an 'object class', which describes what -sort of resource the object is, and permissions (such as read or write) which -describe the access. Permissions are defined in relation to object classes (so -'listen' is a valid permission on sockets, but not on files). +Type Enforcement policies have a subject, which attempts to perform some action, +an object which is acted upon, and a description of the action(s). In SELinux +this description is made up of an 'object class', which describes what sort of +resource the object is, and permissions (such as read or write) which describe +the access. Permissions are defined in relation to object classes (so 'listen' +is a valid permission on sockets, but not on files). For more information, review the official SELinux documentation at https://selinuxproject.org/page/Main_Page @@ -22,8 +22,8 @@ types from the language perspective, however there are also additional types, described below. All other types in a policy must derive from one of the below types. -A 'domain' is a type describing a process on a Linux system and therefore may -be a subject in a TE rule.[1] Domains may also be the object in a TE rule. Two +A 'domain' is a type describing a process on a Linux system and therefore may be +a subject in a TE rule.[1] Domains may also be the object in a TE rule. Two common examples of this are: 1. Files in /proc typically have labels matching the process they describe. @@ -45,8 +45,8 @@ The block enclosed in curly braces may be used to specify access rules and member functions on this type. The rationale for this distinction between domains and resources is that it -allows us to take advantage of this semantic knowledge at compile time for -use in built in functions, syntax improvements and linting. +allows us to take advantage of this semantic knowledge at compile time for use +in built in functions, syntax improvements and linting. Cascade is statically typed and function calls must specify the types of their arguments. For arguments that may be either domains or resources, the "type" @@ -58,8 +58,8 @@ Additional built in types are listed below. Lists of objects of the same type are supported and specified by the syntax [type]. -[1] In the SELinux base policy language, non-domains may actually be subjects -in certain contexts (e.g. the filesystem associate permission). In Cascade we +[1] In the SELinux base policy language, non-domains may actually be subjects in +certain contexts (e.g. the filesystem associate permission). In Cascade we enforce the rule that only domains may be subjects and handle situations where resources are subjects through another mechanism (to be determined). @@ -86,10 +86,10 @@ extend foo { Policy authors can define functions using the keyword 'fn'. Functions defined inside the curly block for a type will be member functions of that type, and will be able to refer to the containing type using the keyword 'this'. To call -a member function, you use the `.` operator, with the name of the type, -followed by a `.`, followed by the function name. For example if the read() -function is defined on the `iptables_conf` resource, then it can be called -using `iptables_conf.read()`. +a member function, you use the `.` operator, with the name of the type, followed +by a `.`, followed by the function name. For example if the read() function is +defined on the `iptables_conf` resource, then it can be called using +`iptables_conf.read()`. If a function is called inside a curly block, Cascade will attempt to pass the type of the curly block as the first argument to the function. If this is not @@ -112,8 +112,8 @@ domain iptables { A function marked with the `virtual` keyword is not allowed to be called directly. The purpose of such functions is to be inherited and called on the child type. If a parent function is marked virtual the child *must* define it, -either explicitely or via a derive (if some parent implementation exists). -This is used to define an expected interface that children are guaranteed to +either explicitely or via a derive (if some parent implementation exists). This +is used to define an expected interface that children are guaranteed to implement. ### Type inheritance @@ -124,19 +124,19 @@ Child types inherit the following from their parents: When inheriting from multiple types, different types may provide member functions with conflicting names (in object oriented language parlance, this is -commonly refered to as "the diamond problem"). If that is the case, the -derived type *must* override that member function with one of its own. This -can be done by manually defining a new funtion (which may call parent classes -using the syntax classname::function_name()), or using the "derive" annotation. -See the section on annotations for details on deriving. +commonly refered to as "the diamond problem"). If that is the case, the derived +type *must* override that member function with one of its own. This can be done +by manually defining a new funtion (which may call parent classes using the +syntax classname::function_name()), or using the "derive" annotation. See the +section on annotations for details on deriving. The common case for type inheritance will be inheriting from virtual types, which conveniently allows reference to all derived types, automatically generates common resources, and reduces boilerplate by including common functions. -Inheriting from concrete types can be desirable for various applications, and -is made more useful than previous cloning implementations by the resource +Inheriting from concrete types can be desirable for various applications, and is +made more useful than previous cloning implementations by the resource association mechanism, but is not currently supported in the current version. ### Virtual types @@ -193,8 +193,8 @@ Associating a resource with a domain does three things: 1. It causes that resource to be grouped together with the domain when the domain is included in a module or full system policy -2. It calls any @associated_call member functions in the resource with the domain -as the first argument. +2. It calls any @associated_call member functions in the resource with the +domain as the first argument. 3. If the domain is inherited from, a child resource is automatically created and is associated with the child class. This resource is named `[child name].[resource name]`. @@ -209,11 +209,11 @@ Access vector rules define what happens on an access attempt. They are defined on the quadruple of: source type, target type, target object class, permissions. There are five kinds of access vector rules provided by Cascade: allow, -auditallow, dontaudit, neverallow, delete. Allow rules grant access. -Auditallow audit access when it is granted (by a separate allow rule). -Dontaudit rules disable auditing for denied access. Neverallow rules are a -compile time assertion that certain access should not be allowed. Delete rules -remove access if it is allowed elsewhere. +auditallow, dontaudit, neverallow, delete. Allow rules grant access. Auditallow +audit access when it is granted (by a separate allow rule). Dontaudit rules +disable auditing for denied access. Neverallow rules are a compile time +assertion that certain access should not be allowed. Delete rules remove access +if it is allowed elsewhere. These rules are defined by five built-in functions: allow(), audit(), dontaudit(), neverallow(), delete(). Note the rename from the SELinux base @@ -227,9 +227,9 @@ fn allow(domain source, type target, class obj_class, [permission] perm); ``` And likewise for audit(), dontaudit(), neverallow() and delete(). Lists of -sources and targets are intentionally omitted to encourage clarity and readability. -Policy authors are encouraged to create virtual types to specify groups and/or -declare their own functions grouping related av rules. +sources and targets are intentionally omitted to encourage clarity and +readability. Policy authors are encouraged to create virtual types to specify +groups and/or declare their own functions grouping related av rules. Note the use the makelist annotation to allow automatic coercion from a single class or permission to a list (See 'annotations' section). @@ -254,8 +254,8 @@ Default contexts specify what labels should be applied to objects that cannot store labels in extended attributes. For example, files on filesystems that do not support extended attributes would get default contexts. Additionally, certain non-file objects such as ports or packets will get contexts on creation -as defined by the policy (although packets have their own special handling -which is outside the scope of this document). +as defined by the policy (although packets have their own special handling which +is outside the scope of this document). Reference Policy implementations treat these types of labeling independently, which reflects how they are handled at a kernel/system level, but not how high @@ -277,32 +277,56 @@ TODO: Named resource transitions TODO: File context labeling -This is under discussion now. We agree that we should take advantage of the fact that we are parsing paths, not arbitrary strings to provide appropriate semantic checking. Mickael to provide motivating use case for query syntax approach. +This is under discussion now. We agree that we should take advantage of the +fact that we are parsing paths, not arbitrary strings to provide appropriate +semantic checking. Mickael to provide motivating use case for query syntax +approach. TODO: default contexts. This will be part of the next phase of design -TODO: file_contexts.subs_dist (although is it necessary with advanced file_context handling features?) +TODO: file_contexts.subs_dist (although is it necessary with advanced +file_context handling features?) ### Filesystem labeling -All filesystem are labeled using the following prototype: +All filesystems are labeled using the following prototype: -`fs_context(resource fs_label, string fs_name, fs_type type, path path, file_type [obj_class])` +`fs_context(resource fs_label, string fs_name, fs_type type, path path, +file_type [obj_class])` -* `fs_label` is label you wish to apply to the filesystem. -* `fs_name` is the OS recognized name for the filesystem. (e.g. ext4, proc, tmpfs) +* `fs_label` is the label you wish to apply to the filesystem. +* `fs_name` is the OS recognized name for the filesystem. (e.g. "ext4", "proc", + "tmpfs") * `fs_type` must be one of the following options: - * `xattr`: Filesystems supporting the extended attribute security.selinux. The labeling is persistent for filesystems that support extended attributes. - * `task`: For pseudo filesystems supporting task related services such as pipes and sockets. - * `trans`: For pseudo filesystems such as pseudo terminals and temporary objects. - * `genfscon`: Used to allocate a security context to filesystems that cannot support any of the previous file labeling options -* `path` is an optional path relative to root of the mounted filesystem. Currently this is only valid for the proc filesystem, all other types must be "/". If not given, the field will default to "/". -* `file_type` is an optional keyword representing a file type to apply the label to. Valid values are the same as in file_context function. If not given, [any] is assumed. - * Note: You must use SELinux userspace tools version 3.4 or newer to use this field. - -`xattr`, `task`, and `trans` all represent filesystems that support SELinux security contexts. The filesystem itself has a labeled applied to it as a whole, which is the `fs_label` provided in this function. All files on the filesystem also store their own SELinux security context in their own extended attributes. - -`genfscon` represents filesystems that do not support SELinux security contexts. Generally a filesystem has a single default security context, `fs_label` provided in this function, assigned by `genfscon` from the root (/) that is inherited by all files and directories on that filesystem. The exception to this is the /proc filesystem, where directories can be labeled with a specific security context. + * `xattr`: Filesystems supporting the extended attribute security.selinux. The + labeling is persistent for filesystems that support extended attributes. + * `task`: For pseudo filesystems supporting task related services such as + pipes and sockets. + * `trans`: For pseudo filesystems such as pseudo terminals and temporary + objects. + * `genfscon`: Used to allocate a security context to filesystems that cannot + support any of the previous file labeling options +* `path` is an optional path relative to root of the mounted filesystem. + Currently this is only valid for the proc filesystem, all other types must be + "/". If not given, the field will default to "/". +* `file_type` is an optional keyword representing a file type to apply the label + to. Valid values are the same as in file_context function. If not given, + [any] is assumed. + * Note: You must use SELinux userspace tools version 3.4 or newer to use this + field. + +`xattr`, `task`, and `trans` all represent filesystems that support SELinux +security contexts. The filesystem itself has a labeled applied to it as a +whole, which is the `fs_label` provided in this function. All files on the +filesystem also store their own SELinux security context in their own extended +attributes. + +`genfscon` represents filesystems that do not support SELinux security contexts. +Generally a filesystem has a single default security context, `fs_label` +provided in this function, assigned by `genfscon` from the root (/) that is +inherited by all files and directories on that filesystem. The exception to this +is the /proc filesystem, where directories can be labeled with a specific +security context. This call must be part of a resource block. @@ -325,8 +349,8 @@ let read_file_perms = [ read, open, getattr ]; ``` The name `read_file_perms` can be used elsewhere and will be replaced at compile -time with the list of permissions. The compiler infers the type of the -constant from what is assigned to it (this is case the type is [perm]). +time with the list of permissions. The compiler infers the type of the constant +from what is assigned to it (this is case the type is [perm]). ## Annotations @@ -344,8 +368,8 @@ Annotation lines do not end in a semicolon. ### derive annotation -The derive annotation tells the compiler to define member functions by using -the union of the parent class member functions of the same name. It takes the +The derive annotation tells the compiler to define member functions by using the +union of the parent class member functions of the same name. It takes the name(s) of function(s) to derive as arguments. ``` @@ -363,9 +387,9 @@ fn foo(...) { ``` The derive function may also be given the keyword "all" instead of function -names to derive all conflicting functions from parent classes using this -method. (This implies that the word "all" is reserved and cannot be used as a -function name). +names to derive all conflicting functions from parent classes using this method. +(This implies that the word "all" is reserved and cannot be used as a function +name). ### hint annotation @@ -393,15 +417,12 @@ Example using additional fields: domain foo { ... } ``` -The following arguments may be specified: -source: The scontext field of a denial -target: The tcontext field of a denial -class: The tclass field of a denial -perm: Any permission listed in a denial -hint: A text string to display when using newaudit2allow -attack: Set to True to indicate that this denial should be treated as a -possible security incident -cve: Reference a CVE associated with this denial +The following arguments may be specified: source: The scontext field of a denial +target: The tcontext field of a denial class: The tclass field of a denial perm: +Any permission listed in a denial hint: A text string to display when using +newaudit2allow attack: Set to True to indicate that this denial should be +treated as a possible security incident cve: Reference a CVE associated with +this denial ### ignore annotation @@ -415,11 +436,11 @@ if (condition) { ``` The above code would ordinarily display a warning because conditional -definitions can lead to unexpected behavior. If we wish to leave the code as -is and suppress the warning, we can do so via this annotation. Of course, -warnings are typically supplied for good reason, and you should seriously -consider whether you really want to suppress the warning rather than fixing -the underlying issue. +definitions can lead to unexpected behavior. If we wish to leave the code as is +and suppress the warning, we can do so via this annotation. Of course, warnings +are typically supplied for good reason, and you should seriously consider +whether you really want to suppress the warning rather than fixing the +underlying issue. ### makelist annotation @@ -441,14 +462,12 @@ baz(bar); // Converts to [bar] because of annotation. Would be a compiler error The alias annotation tells the compiler to provide an alternate name for referrering to the same item. -This is often used for interoperability. For example, if one is renaming a -type or function in an already deployed policy, one can provide an alias to the -old name for backwards compatibility during a transition period until labels or +This is often used for interoperability. For example, if one is renaming a type +or function in an already deployed policy, one can provide an alias to the old +name for backwards compatibility during a transition period until labels or callers have been updated. -`` -@alias(bar) -resource foo {} +`` @alias(bar) resource foo {} ``` ## Traits diff --git a/src/compile.rs b/src/compile.rs index a91a4585..d4d8cee9 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -49,7 +49,7 @@ pub fn generate_sexp( // TODO: The rest of compilation let cil_types = type_list_to_sexp(type_decl_list, type_map); let headers = generate_cil_headers(classlist, *system_configurations); - let cil_rules = rules_list_to_sexp(policy_rules); + let cil_rules = rules_list_to_sexp(policy_rules)?; let cil_macros = func_map_to_sexp(func_map)?; let sid_statements = generate_sid_rules(generate_sids("kernel_sid", "security_sid", "unlabeled_sid")); @@ -1509,11 +1509,12 @@ fn get_rules_vec_for_type(ti: &TypeInfo, s: sexp::Sexp, type_map: &TypeMap) -> V ret } -fn rules_list_to_sexp<'a, T>(rules: T) -> Vec +fn rules_list_to_sexp<'a, T>(rules: T) -> Result, ErrorItem> where T: IntoIterator>, { - rules.into_iter().map(|r| Sexp::from(&r)).collect() + let ret: Result, _> = rules.into_iter().map(|r| Sexp::try_from(&r)).collect(); + ret } fn generate_sids<'a>( diff --git a/src/internal_rep.rs b/src/internal_rep.rs index 18aea227..6d1cd3f9 100644 --- a/src/internal_rep.rs +++ b/src/internal_rep.rs @@ -18,7 +18,7 @@ use crate::ast::{ }; use crate::constants; use crate::context::Context as BlockContext; -use crate::error::{CascadeErrors, CompileError, ErrorItem, InternalError}; +use crate::error::{CascadeErrors, CompileError, ErrorItem, InternalError, InvalidFileSystemError}; use crate::obj_class::perm_list_to_sexp; const DEFAULT_USER: &str = "system_u"; @@ -1393,23 +1393,18 @@ pub struct FileSystemContextRule<'a> { pub context: Context<'a>, } -// TODO convert to TryFrom/try_from see comment below -impl From<&FileSystemContextRule<'_>> for sexp::Sexp { - fn from(f: &FileSystemContextRule) -> sexp::Sexp { +impl TryFrom<&FileSystemContextRule<'_>> for sexp::Sexp { + type Error = ErrorItem; + + fn try_from(f: &FileSystemContextRule) -> Result { match f.fscontext_type { - FSContextType::XAttr | FSContextType::Task | FSContextType::Trans => list(&[ + FSContextType::XAttr | FSContextType::Task | FSContextType::Trans => Ok(list(&[ atom_s("fsuse"), Sexp::Atom(Atom::S(f.fscontext_type.to_string())), atom_s(f.fs_name.trim_matches('"')), Sexp::from(&f.context), - ]), + ])), FSContextType::GenFSCon => { - // Since path is an optional arg and I don't want to get - // into unwrap issue we are doing an 'if let' here. The lack - // of path should be caught earlier, so if we don't have a path - // we will return an empty list. The more correct way to fix this - // is convert this to a try_from, but this causes issues with some - // of our match statements and mixing returns. if let Some(p) = &f.path { if let Some(file_type) = &f.file_type { // TODO add secilc check here. Right now our github pipeline @@ -1418,25 +1413,31 @@ impl From<&FileSystemContextRule<'_>> for sexp::Sexp { // REMEMBER TO UPDATE THE TESTS // if secilc/libsepol version is new enough { if false { - return list(&[ + return Ok(list(&[ atom_s("genfscon"), atom_s(f.fs_name.trim_matches('"')), atom_s(p.as_ref()), Sexp::Atom(Atom::S(file_type.to_string())), Sexp::from(&f.context), - ]); + ])); } } // We are purposefully falling through without an else to // reduce redundant lines of code - list(&[ + Ok(list(&[ atom_s("genfscon"), atom_s(f.fs_name.trim_matches('"')), atom_s(p.as_ref()), Sexp::from(&f.context), - ]) + ])) } else { - list(&[]) + Err(ErrorItem::InvalidFileSystem(InvalidFileSystemError::new( + &format!( + "Genfscon missing path.\n No path given for genfscon rule:\ + \n\tFilesystem name: {}\n\tContext: {}", + f.fs_name, f.context, + ), + ))) } } } @@ -1536,30 +1537,49 @@ fn call_to_fsc_rules<'a>( return Err(CascadeErrors::from(ErrorItem::Compile(CompileError::new( "Not a valid file system type", file, - fscontext_str.get_range(), //TODO error not showing correctly + fscontext_str.get_range(), "File system type must be 'xattr', 'task', 'trans', or 'genfscon'", )))); } }; - let regex_string = args_iter + let regex_string_arg = args_iter .next() - .ok_or_else(|| ErrorItem::Internal(InternalError::new()))? - .get_name_or_string(context)? - .to_string(); - let file_types = args_iter + .ok_or_else(|| ErrorItem::Internal(InternalError::new()))?; + let regex_string = regex_string_arg.get_name_or_string(context)?.to_string(); + let file_types_arg = args_iter .next() - .ok_or_else(|| ErrorItem::Internal(InternalError::new()))? - .get_list(context)?; + .ok_or_else(|| ErrorItem::Internal(InternalError::new()))?; + let file_types = file_types_arg.get_list(context)?; match fscontext_type { FSContextType::XAttr | FSContextType::Task | FSContextType::Trans => { - ret.push(FileSystemContextRule { - fscontext_type, - fs_name, - path: None, - file_type: None, - context: fs_context.clone(), - }); + // The 'regex_string_arg.get_range().is_none()' is a hacky way to + // to check if arg was actually provided or not. Since we set the + // default for regex_string to "/" this is the only way I could find + // to test if the actual arg was passed or not + if regex_string_arg.get_range().is_none() && file_types.is_empty() { + ret.push(FileSystemContextRule { + fscontext_type, + fs_name, + path: None, + file_type: None, + context: fs_context.clone(), + }); + } else if !file_types.is_empty() { + return Err(CascadeErrors::from(ErrorItem::Compile(CompileError::new( + "File types can only be provided for 'genfscon'", + file, + file_types_arg.get_range(), + "", + )))); + } else { + return Err(CascadeErrors::from(ErrorItem::Compile(CompileError::new( + "File path can only be provided for 'genfscon'", + file, + regex_string_arg.get_range(), + "", + )))); + } } FSContextType::GenFSCon => { if file_types.is_empty() { @@ -1967,7 +1987,7 @@ impl TryFrom<&FunctionInfo<'_>> for sexp::Sexp { ValidatedStatement::Call(c) => macro_cil.push(Sexp::from(&**c)), ValidatedStatement::AvRule(a) => macro_cil.push(Sexp::from(a)), ValidatedStatement::FcRule(f) => macro_cil.push(Sexp::from(f)), - ValidatedStatement::FscRule(fs) => macro_cil.push(Sexp::from(fs)), + ValidatedStatement::FscRule(fs) => macro_cil.push(Sexp::try_from(fs)?), ValidatedStatement::DomtransRule(d) => macro_cil.push(Sexp::from(d)), } } @@ -2094,7 +2114,7 @@ impl<'a> ValidatedStatement<'a> { .collect()) } else { Err(CascadeErrors::from(ErrorItem::Compile(CompileError::new( - "file_context() calls are only allowed in resources", + "fs_context() calls are only allowed in resources", file, c.name.get_range(), "Not allowed here", @@ -2157,14 +2177,15 @@ impl<'a> ValidatedStatement<'a> { } } -impl From<&ValidatedStatement<'_>> for sexp::Sexp { - fn from(statement: &ValidatedStatement) -> sexp::Sexp { +impl TryFrom<&ValidatedStatement<'_>> for sexp::Sexp { + type Error = ErrorItem; + fn try_from(statement: &ValidatedStatement) -> Result { match statement { - ValidatedStatement::Call(c) => Sexp::from(&**c), - ValidatedStatement::AvRule(a) => Sexp::from(a), - ValidatedStatement::FcRule(f) => Sexp::from(f), - ValidatedStatement::FscRule(fs) => Sexp::from(fs), - ValidatedStatement::DomtransRule(d) => Sexp::from(d), + ValidatedStatement::Call(c) => Ok(Sexp::from(&**c)), + ValidatedStatement::AvRule(a) => Ok(Sexp::from(a)), + ValidatedStatement::FcRule(f) => Ok(Sexp::from(f)), + ValidatedStatement::FscRule(fs) => Sexp::try_from(fs), + ValidatedStatement::DomtransRule(d) => Ok(Sexp::from(d)), } } } diff --git a/src/lib.rs b/src/lib.rs index f1ce5aa8..94305f47 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1158,7 +1158,7 @@ mod tests { #[test] fn invalid_fs_context() { - error_policy_test!("fs_context.cas", 5, ErrorItem::Compile(_)); + error_policy_test!("fs_context.cas", 8, ErrorItem::Compile(_)); } #[test]