Skip to content

Commit

Permalink
Parse dir object keywords
Browse files Browse the repository at this point in the history
In addition to the string path parsing that we currently support there are 3 keywords that need special handling:
- execdirs
- systemdirs
- untrusted

This implementes a type that wraps up these three and the string type to add support for the keywords.

Closes ctc-oss#588
  • Loading branch information
jw3 committed May 16, 2023
1 parent 710f7de commit bd9b8d1
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/rules/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "fapolicy-rules"
description = "Rule support for fapolicyd"
license = "MPL-2.0"
version = "0.4.5"
version = "0.4.6"
edition = "2018"

[lib]
Expand Down
50 changes: 50 additions & 0 deletions crates/rules/src/dir_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright Concurrent Technologies Corporation 2023
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use std::fmt::{Display, Formatter};

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum DirType {
/// Match a directory using the full path to the directory. Its recommended to end with the /
/// to ensure it matches a directory.
Path(String),
/// The execdirs option will match against the following list of directories:
/// /usr/, /bin/, /sbin/, /lib/, /lib64/, /usr/libexec/
ExecDirs,
/// The systemdirs option will match against the same list as execdirs but also includes /etc/.
SystemDirs,
/// The untrusted option will look up the current executable's full path in the rpm database to see
/// if the executable is known to the system. The rule will trigger if the file in question is not
/// in the trust database. This option is deprecated in favor of using obj_trust with execute per‐
/// mission when writing rules.
Untrusted,
}

impl Display for DirType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
DirType::Path(v) => f.write_str(v),
DirType::ExecDirs => f.write_str("execdirs"),
DirType::SystemDirs => f.write_str("systemdirs"),
DirType::Untrusted => f.write_str("untrusted"),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn display() {
assert_eq!(format!("{}", DirType::Path("/foo/".to_string())), "/foo/");
assert_eq!(format!("{}", DirType::ExecDirs), "execdirs");
assert_eq!(format!("{}", DirType::SystemDirs), "systemdirs");
assert_eq!(format!("{}", DirType::Untrusted), "untrusted");
}
}
1 change: 1 addition & 0 deletions crates/rules/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod ops;
pub mod parser;

mod decision;
mod dir_type;
mod file_type;
mod linter;
mod object;
Expand Down
9 changes: 6 additions & 3 deletions crates/rules/src/linter/findings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

use crate::db::{Entry, DB};
use crate::dir_type::DirType;
use crate::Rule;
use is_executable::IsExecutable;
use std::path::PathBuf;
Expand Down Expand Up @@ -86,8 +87,10 @@ pub fn l003_object_path_missing(_: usize, r: &Rule, _db: &DB) -> Option<String>
.filter_map(|p| match p {
Part::Device(p) if is_missing(p) => Some(path_does_not_exist_message("device", p)),
Part::Path(p) if is_missing(p) => Some(path_does_not_exist_message("file", p)),
Part::Dir(p) if is_missing(p) => Some(path_does_not_exist_message("dir", p)),
Part::Dir(p) if !is_dir(p) => Some(wrong_type_message("dir")),
Part::Dir(DirType::Path(p)) if is_missing(p) => {
Some(path_does_not_exist_message("dir", p))
}
Part::Dir(DirType::Path(p)) if !is_dir(p) => Some(wrong_type_message("dir")),
Part::Device(p) | Part::Path(p) if !is_file(p) => Some(wrong_type_message("file")),
_ => None,
})
Expand Down Expand Up @@ -117,7 +120,7 @@ pub fn l005_object_dir_missing_trailing_slash(_: usize, r: &Rule, _db: &DB) -> O
.parts
.iter()
.filter_map(|p| match p {
Part::Dir(p) if !p.ends_with('/') => Some(L005_MESSAGE.to_string()),
Part::Dir(DirType::Path(p)) if !p.ends_with('/') => Some(L005_MESSAGE.to_string()),
_ => None,
})
.collect::<Vec<String>>()
Expand Down
13 changes: 10 additions & 3 deletions crates/rules/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use std::fmt::{Display, Formatter};

use crate::dir_type::DirType;
use crate::{bool_to_c, ObjPart, Rvalue};

/// # Object
Expand Down Expand Up @@ -66,7 +67,7 @@ pub enum Part {
/// - `untrusted`
///
/// ### See the `dir` option under Subject for an explanation of these keywords.
Dir(String),
Dir(DirType),
/// This option matches against the mime type of the file being accessed. See `ftype` under Subject for more information on determining the mime type.
FileType(Rvalue),
/// This is the full path to the file that will be accessed. Globbing is not supported. You may also use the special keyword `untrusted` to match on the subject not being listed in the rpm database.
Expand Down Expand Up @@ -122,7 +123,10 @@ mod tests {
#[test]
fn display() {
assert_eq!(format!("{}", Part::All), "all");
assert_eq!(format!("{}", Part::Dir("/foo".into())), "dir=/foo");
assert_eq!(
format!("{}", Part::Dir(DirType::Path("/foo".into()))),
"dir=/foo"
);
assert_eq!(
format!("{}", Part::Device("/dev/cdrom".into())),
"device=/dev/cdrom"
Expand All @@ -145,7 +149,10 @@ mod tests {
assert_eq!(
format!(
"{}",
Object::new(vec![Part::Dir("/foo".into()), Part::Trust(true)])
Object::new(vec![
Part::Dir(DirType::Path("/foo".into())),
Part::Trust(true)
])
),
"dir=/foo trust=1"
);
Expand Down
1 change: 1 addition & 0 deletions crates/rules/src/parser/errat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ impl<'a> From<RuleParseError<StrTrace<'a>>> for ErrorAt<StrTrace<'a>> {
return ErrorAt::<StrTrace<'a>>::new_with_len(e, t, v.current.len())
}
ExpectedFileType(t) => t,
ExpectedAbsoluteDirPath(t) => t,

Nom(t, _) => t,
};
Expand Down
3 changes: 3 additions & 0 deletions crates/rules/src/parser/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ pub enum RuleParseError<I> {
ExpectedBoolean(I, I),
ExpectedFileType(I),

ExpectedAbsoluteDirPath(I),

Nom(I, ErrorKind),
}

Expand Down Expand Up @@ -76,6 +78,7 @@ impl Display for RuleParseError<Trace<&str>> {
ExpectedPattern(_) => f.write_str("Expected pattern"),
ExpectedBoolean(_, _) => f.write_str("Expected boolean (0, 1) value"),
ExpectedFileType(_) => f.write_str("Expected mime file type"),
ExpectedAbsoluteDirPath(_) => f.write_str("Expected absolute dir path"),
e @ Nom(_, _) => f.write_fmt(format_args!("{:?}", e)),
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/rules/src/parser/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use nom::bytes::complete::tag;

use nom::character::complete::{alpha1, multispace0};

use crate::dir_type::DirType;
use nom::sequence::{delimited, terminated};

use crate::object::Part as ObjPart;
Expand All @@ -33,7 +34,7 @@ fn obj_part(i: StrTrace) -> TraceResult<ObjPart> {
.map_err(|_: nom::Err<TraceError>| nom::Err::Error(ExpectedFilePath(i))),

"dir" => filepath(ii)
.map(|(ii, d)| (ii, ObjPart::Dir(d.current.to_string())))
.map(|(ii, d)| (ii, ObjPart::Dir(DirType::Path(d.current.to_string()))))
.map_err(|_: nom::Err<TraceError>| nom::Err::Error(ExpectedDirPath(i))),

"ftype" => filetype(ii)
Expand Down
38 changes: 38 additions & 0 deletions crates/rules/src/parser/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::parser::object;
use crate::parser::subject;
use crate::parser::trace::Trace;

use crate::dir_type::DirType;
use crate::{Object, Rvalue, Subject};
use nom::IResult;

Expand All @@ -43,6 +44,17 @@ pub(crate) fn filepath(i: StrTrace) -> TraceResult<StrTrace> {
nom::bytes::complete::is_not(" \t\n")(i)
}

pub(crate) fn dir_type(i: StrTrace) -> TraceResult<DirType> {
match is_not(" \t\n")(i) {
Ok((r, v)) if v.current == "execdirs" => Ok((r, DirType::ExecDirs)),
Ok((r, v)) if v.current == "systemdirs" => Ok((r, DirType::SystemDirs)),
Ok((r, v)) if v.current == "untrusted" => Ok((r, DirType::Untrusted)),
Ok((r, v)) if v.current.starts_with("/") => Ok((r, DirType::Path(v.current.to_string()))),
Ok((_, _)) => Err(nom::Err::Error(ExpectedAbsoluteDirPath(i))),
Err(e) => Err(e),
}
}

// todo;; this should be mimetype
pub(crate) fn filetype(i: StrTrace) -> TraceResult<Rvalue> {
nom::bytes::complete::is_not(" \t\n")(i)
Expand Down Expand Up @@ -93,6 +105,8 @@ pub(crate) fn end_of_rule(i: StrTrace) -> nom::IResult<StrTrace, (), RuleParseEr
#[cfg(test)]
mod tests {
use super::*;
use crate::dir_type::DirType;
use assert_matches::assert_matches;

#[test]
fn parse_trust_flag() {
Expand All @@ -101,4 +115,28 @@ mod tests {
assert_eq!(None, trust_flag("2".into()).ok());
assert_eq!(None, trust_flag("foo".into()).ok());
}

#[test]
fn parse_dir_flag() {
assert_eq!(
dir_type("systemdirs".into()).unwrap().1,
DirType::SystemDirs
);
assert_eq!(dir_type("execdirs".into()).unwrap().1, DirType::ExecDirs);
assert_eq!(dir_type("untrusted".into()).unwrap().1, DirType::Untrusted);
assert_eq!(
dir_type("/mydir/".into()).unwrap().1,
DirType::Path("/mydir/".to_string())
);

assert_matches!(
dir_type("reldir/".into()),
Err(nom::Err::Error(ExpectedAbsoluteDirPath(_)))
);

assert_matches!(
dir_type("./reldir/".into()),
Err(nom::Err::Error(ExpectedAbsoluteDirPath(_)))
);
}
}
6 changes: 5 additions & 1 deletion crates/rules/src/parser/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub fn parse_with_error_message(i: StrTrace) -> Result<Rule, String> {

#[cfg(test)]
mod tests {
use crate::dir_type::DirType;
use crate::{Decision, ObjPart, Object, Permission, Rvalue, SubjPart, Subject};

use super::*;
Expand Down Expand Up @@ -101,7 +102,10 @@ mod tests {
assert_eq!(Decision::DenyAudit, r.dec);
assert_eq!(Permission::Open, r.perm);
assert_eq!(Subject::from(SubjPart::Exe("/usr/bin/ssh".into())), r.subj);
assert_eq!(Object::from(ObjPart::Dir("/opt".into())), r.obj);
assert_eq!(
Object::from(ObjPart::Dir(DirType::Path("/opt".into()))),
r.obj
);
assert!(rem.is_empty());

let (rem, r) = parse("deny_audit perm=any all : ftype=application/x-bad-elf".into())
Expand Down

0 comments on commit bd9b8d1

Please sign in to comment.