From 3413f4a4fed6355149e5df9fa9bc9d3e37c5a111 Mon Sep 17 00:00:00 2001 From: tiann Date: Tue, 31 Jan 2023 18:47:15 +0800 Subject: [PATCH] ksud: sepolicy support --- userspace/ksud/Cargo.lock | 13 + userspace/ksud/Cargo.toml | 2 + userspace/ksud/src/cli.rs | 27 +- userspace/ksud/src/ksu.rs | 3 +- userspace/ksud/src/main.rs | 1 + userspace/ksud/src/sepolicy.rs | 731 +++++++++++++++++++++++++++++++++ 6 files changed, 773 insertions(+), 4 deletions(-) create mode 100644 userspace/ksud/src/sepolicy.rs diff --git a/userspace/ksud/Cargo.lock b/userspace/ksud/Cargo.lock index b55e0a0a..288e0d31 100644 --- a/userspace/ksud/Cargo.lock +++ b/userspace/ksud/Cargo.lock @@ -316,6 +316,17 @@ dependencies = [ "typenum", ] +[[package]] +name = "derive-new" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.6" @@ -593,6 +604,7 @@ dependencies = [ "anyhow", "clap", "const_format", + "derive-new", "encoding", "env_logger", "extattr", @@ -602,6 +614,7 @@ dependencies = [ "jwalk", "libc", "log", + "nom", "regex", "retry", "serde", diff --git a/userspace/ksud/Cargo.toml b/userspace/ksud/Cargo.toml index 9b3c91e2..499b7744 100644 --- a/userspace/ksud/Cargo.toml +++ b/userspace/ksud/Cargo.toml @@ -27,6 +27,8 @@ android-properties = { version = "0.2.2", features = ["bionic-deprecated"] } extattr = "1.0.0" jwalk = "0.8.1" is_executable = "1.0.1" +nom = "7" +derive-new = "0.5" [profile.release] strip = true diff --git a/userspace/ksud/src/cli.rs b/userspace/ksud/src/cli.rs index f534572f..6d9d5558 100644 --- a/userspace/ksud/src/cli.rs +++ b/userspace/ksud/src/cli.rs @@ -35,7 +35,10 @@ enum Commands { Install, /// SELinux policy Patch tool - Sepolicy, + Sepolicy { + #[command(subcommand)] + command: Sepolicy, + }, /// For developers Debug { @@ -68,6 +71,21 @@ enum Debug { Test, } +#[derive(clap::Subcommand, Debug)] +enum Sepolicy { + /// Patch sepolicy + Patch { + /// sepolicy statements + sepolicy: String, + }, + + /// Apply sepolicy from file + Apply { + /// sepolicy file path + file: String, + }, +} + #[derive(clap::Subcommand, Debug)] enum Module { /// Install module @@ -118,7 +136,10 @@ pub fn run() -> Result<()> { } } Commands::Install => event::install(), - Commands::Sepolicy => todo!(), + Commands::Sepolicy { command } => match command { + Sepolicy::Patch { sepolicy } => crate::sepolicy::live_patch(&sepolicy), + Sepolicy::Apply { file } => crate::sepolicy::apply_file(&file), + }, Commands::Services => event::on_services(), Commands::Debug { command } => match command { @@ -133,7 +154,7 @@ pub fn run() -> Result<()> { Ok(()) } Debug::Su => crate::ksu::grant_root(), - Debug::Test => todo!(), + Debug::Test => todo!() }, }; diff --git a/userspace/ksud/src/ksu.rs b/userspace/ksud/src/ksu.rs index b65b21cc..bf56f777 100644 --- a/userspace/ksud/src/ksu.rs +++ b/userspace/ksud/src/ksu.rs @@ -3,7 +3,7 @@ use anyhow::{ensure, Result}; use std::os::unix::process::CommandExt; -const KERNEL_SU_OPTION: u32 = 0xDEADBEEF; +pub const KERNEL_SU_OPTION: u32 = 0xDEADBEEF; const CMD_GRANT_ROOT: u64 = 0; // const CMD_BECOME_MANAGER: u64 = 1; @@ -13,6 +13,7 @@ const CMD_GET_VERSION: u64 = 2; // const CMD_GET_ALLOW_LIST: u64 = 5; // const CMD_GET_DENY_LIST: u64 = 6; const CMD_REPORT_EVENT: u64 = 7; +pub const CMD_SET_SEPOLICY: u64 = 8; const EVENT_POST_FS_DATA: u64 = 1; const EVENT_BOOT_COMPLETED: u64 = 2; diff --git a/userspace/ksud/src/main.rs b/userspace/ksud/src/main.rs index 14d8e91d..f954ecd0 100644 --- a/userspace/ksud/src/main.rs +++ b/userspace/ksud/src/main.rs @@ -7,6 +7,7 @@ mod ksu; mod module; mod restorecon; mod utils; +mod sepolicy; fn main() -> anyhow::Result<()> { cli::run() diff --git a/userspace/ksud/src/sepolicy.rs b/userspace/ksud/src/sepolicy.rs new file mode 100644 index 00000000..68ee171b --- /dev/null +++ b/userspace/ksud/src/sepolicy.rs @@ -0,0 +1,731 @@ +use anyhow::Result; +use derive_new::new; +use nom::{ + branch::alt, + bytes::complete::{tag, take_while, take_while1, take_while_m_n}, + character::{ + complete::{space0, space1}, + is_alphanumeric, + }, + combinator::map, + sequence::Tuple, + IResult, Parser, +}; +use std::vec; + +type SeObject<'a> = Vec<&'a str>; + +fn is_sepolicy_char(c: char) -> bool { + is_alphanumeric(c as u8) || c == '_' || c == '-' +} + +fn parse_single_word(input: &str) -> IResult<&str, &str> { + take_while1(is_sepolicy_char).parse(input) +} + +fn parse_bracket_objs(input: &str) -> IResult<&str, SeObject> { + let (input, (_, words, _)) = ( + tag("{"), + take_while_m_n(1, 100, |c: char| is_sepolicy_char(c) || c.is_whitespace()), + tag("}"), + ) + .parse(input)?; + Ok((input, words.split_whitespace().collect())) +} + +fn parse_single_obj(input: &str) -> IResult<&str, SeObject> { + let (input, word) = take_while1(is_sepolicy_char).parse(input)?; + Ok((input, vec![word])) +} + +fn parse_star(input: &str) -> IResult<&str, SeObject> { + let (input, _) = tag("*").parse(input)?; + Ok((input, vec!["*"])) +} + +// 1. a single sepolicy word +// 2. { obj1 obj2 obj3 ...} +// 3. * +fn parse_seobj(input: &str) -> IResult<&str, SeObject> { + let (input, strs) = alt((parse_single_obj, parse_bracket_objs, parse_star)).parse(input)?; + Ok((input, strs)) +} + +fn parse_seobj_no_star(input: &str) -> IResult<&str, SeObject> { + let (input, strs) = alt((parse_single_obj, parse_bracket_objs)).parse(input)?; + Ok((input, strs)) +} + +trait SeObjectParser<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> + where + Self: Sized; +} + +#[derive(Debug, PartialEq, Eq, new)] +struct NormalPerm<'a> { + op: &'a str, + source: SeObject<'a>, + target: SeObject<'a>, + class: SeObject<'a>, + perm: SeObject<'a>, +} + +#[derive(Debug, PartialEq, Eq, new)] +struct XPerm<'a> { + op: &'a str, + source: SeObject<'a>, + target: SeObject<'a>, + class: SeObject<'a>, + operation: &'a str, + perm_set: &'a str, +} + +#[derive(Debug, PartialEq, Eq, new)] +struct TypeState<'a> { + op: &'a str, + stype: SeObject<'a>, +} + +#[derive(Debug, PartialEq, Eq, new)] +struct TypeAttr<'a> { + stype: SeObject<'a>, + sattr: SeObject<'a>, +} + +#[derive(Debug, PartialEq, Eq, new)] +struct Type<'a> { + name: &'a str, + attrs: SeObject<'a>, +} + +#[derive(Debug, PartialEq, Eq, new)] +struct Attr<'a> { + name: &'a str, +} + +#[derive(Debug, PartialEq, Eq, new)] +struct TypeTransition<'a> { + source: &'a str, + target: &'a str, + class: &'a str, + default_type: &'a str, + object_name: Option<&'a str>, +} + +#[derive(Debug, PartialEq, Eq, new)] +struct TypeChange<'a> { + op: &'a str, + source: &'a str, + target: &'a str, + class: &'a str, + default_type: &'a str, +} + +#[derive(Debug, PartialEq, Eq, new)] +struct GenFsCon<'a> { + fs_name: &'a str, + partial_path: &'a str, + fs_context: &'a str, +} + +#[derive(Debug)] +enum PolicyStatement<'a> { + // "allow *source_type *target_type *class *perm_set" + // "deny *source_type *target_type *class *perm_set" + // "auditallow *source_type *target_type *class *perm_set" + // "dontaudit *source_type *target_type *class *perm_set" + NormalPerm(NormalPerm<'a>), + + // "allowxperm *source_type *target_type *class operation xperm_set" + // "auditallowxperm *source_type *target_type *class operation xperm_set" + // "dontauditxperm *source_type *target_type *class operation xperm_set" + XPerm(XPerm<'a>), + + // "permissive ^type" + // "enforce ^type" + TypeState(TypeState<'a>), + + // "type type_name ^(attribute)" + Type(Type<'a>), + + // "typeattribute ^type ^attribute" + TypeAttr(TypeAttr<'a>), + + // "attribute ^attribute" + Attr(Attr<'a>), + + // "type_transition source_type target_type class default_type (object_name)" + TypeTransition(TypeTransition<'a>), + + // "type_change source_type target_type class default_type" + // "type_member source_type target_type class default_type" + TypeChange(TypeChange<'a>), + + // "genfscon fs_name partial_path fs_context" + GenFsCon(GenFsCon<'a>), +} + +impl<'a> SeObjectParser<'a> for NormalPerm<'a> { + fn parse(input: &'a str) -> IResult<&str, Self> { + let (input, op) = alt(( + tag("allow"), + tag("deny"), + tag("auditallow"), + tag("dontaudit"), + ))(input)?; + + let (input, _) = space0(input)?; + let (input, source) = parse_seobj(input)?; + let (input, _) = space0(input)?; + let (input, target) = parse_seobj(input)?; + let (input, _) = space0(input)?; + let (input, class) = parse_seobj(input)?; + let (input, _) = space0(input)?; + let (input, perm) = parse_seobj(input)?; + Ok((input, NormalPerm::new(op, source, target, class, perm))) + } +} + +impl<'a> SeObjectParser<'a> for XPerm<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> { + let (input, op) = alt(( + tag("allowxperm"), + tag("auditallowxperm"), + tag("dontauditxperm"), + ))(input)?; + + let (input, _) = space0(input)?; + let (input, source) = parse_seobj(input)?; + let (input, _) = space0(input)?; + let (input, target) = parse_seobj(input)?; + let (input, _) = space0(input)?; + let (input, class) = parse_seobj(input)?; + let (input, _) = space0(input)?; + let (input, operation) = parse_single_word(input)?; + let (input, _) = space0(input)?; + let (input, perm_set) = parse_single_word(input)?; + + Ok(( + input, + XPerm::new(op, source, target, class, operation, perm_set), + )) + } +} + +impl<'a> SeObjectParser<'a> for TypeState<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> { + let (input, op) = alt((tag("permissive"), tag("enforce")))(input)?; + + let (input, _) = space1(input)?; + let (input, stype) = parse_seobj_no_star(input)?; + + Ok((input, TypeState::new(op, stype))) + } +} + +impl<'a> SeObjectParser<'a> for Type<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> { + let (input, _) = tag("type")(input)?; + let (input, _) = space1(input)?; + let (input, name) = parse_single_word(input)?; + + if input.is_empty() { + return Ok((input, Type::new(name, vec!["domain"]))); // default to domain + } + + let (input, _) = space1(input)?; + let (input, attrs) = parse_seobj_no_star(input)?; + + Ok((input, Type::new(name, attrs))) + } +} + +impl<'a> SeObjectParser<'a> for TypeAttr<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> { + let (input, _) = alt((tag("typeattribute"), tag("attradd")))(input)?; + let (input, _) = space1(input)?; + let (input, stype) = parse_seobj_no_star(input)?; + let (input, _) = space1(input)?; + let (input, attr) = parse_seobj_no_star(input)?; + + Ok((input, TypeAttr::new(stype, attr))) + } +} + +impl<'a> SeObjectParser<'a> for Attr<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> { + let (input, _) = tag("attribute")(input)?; + let (input, _) = space1(input)?; + let (input, attr) = parse_single_word(input)?; + + Ok((input, Attr::new(attr))) + } +} + +impl<'a> SeObjectParser<'a> for TypeTransition<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> { + let (input, _) = alt((tag("type_transition"), tag("name_transition")))(input)?; + let (input, _) = space1(input)?; + let (input, source) = parse_single_word(input)?; + let (input, _) = space1(input)?; + let (input, target) = parse_single_word(input)?; + let (input, _) = space1(input)?; + let (input, class) = parse_single_word(input)?; + let (input, _) = space1(input)?; + let (input, default) = parse_single_word(input)?; + + if input.is_empty() { + return Ok(( + input, + TypeTransition::new(source, target, class, default, None), + )); + } + + let (input, _) = space1(input)?; + let (input, object) = parse_single_word(input)?; + + Ok(( + input, + TypeTransition::new(source, target, class, default, Some(object)), + )) + } +} + +impl<'a> SeObjectParser<'a> for TypeChange<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> { + let (input, op) = alt((tag("type_change"), tag("type_member")))(input)?; + let (input, _) = space1(input)?; + let (input, source) = parse_single_word(input)?; + let (input, _) = space1(input)?; + let (input, target) = parse_single_word(input)?; + let (input, _) = space1(input)?; + let (input, class) = parse_single_word(input)?; + let (input, _) = space1(input)?; + let (input, default) = parse_single_word(input)?; + + Ok((input, TypeChange::new(op, source, target, class, default))) + } +} + +impl<'a> SeObjectParser<'a> for GenFsCon<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> + where + Self: Sized, + { + let (input, _) = tag("genfscon")(input)?; + let (input, _) = space1(input)?; + let (input, fs) = parse_single_word(input)?; + let (input, _) = space1(input)?; + let (input, path) = parse_single_word(input)?; + let (input, _) = space1(input)?; + let (input, context) = parse_single_word(input)?; + Ok((input, GenFsCon::new(fs, path, context))) + } +} + +impl<'a> PolicyStatement<'a> { + fn parse(input: &'a str) -> IResult<&'a str, Self> { + let (input, _) = space0(input)?; + let (input, statement) = alt(( + map(NormalPerm::parse, PolicyStatement::NormalPerm), + map(XPerm::parse, PolicyStatement::XPerm), + map(TypeState::parse, PolicyStatement::TypeState), + map(Type::parse, PolicyStatement::Type), + map(TypeAttr::parse, PolicyStatement::TypeAttr), + map(Attr::parse, PolicyStatement::Attr), + map(TypeTransition::parse, PolicyStatement::TypeTransition), + map(TypeChange::parse, PolicyStatement::TypeChange), + map(GenFsCon::parse, PolicyStatement::GenFsCon), + ))(input)?; + let (input, _) = space0(input)?; + let (input, _) = take_while(|c| c == ';')(input)?; + let (input, _) = space0(input)?; + Ok((input, statement)) + } +} + +fn parse_sepolicy<'a, 'b>(input: &'b str) -> Result>> +where + 'b: 'a, +{ + let mut statements = vec![]; + + for line in input.split(['\n', ';']) { + if let Ok((_, statement)) = PolicyStatement::parse(line.trim()) { + statements.push(statement); + } + } + Ok(statements) +} + +const SEPOLICY_MAX_LEN: usize = 128; + +const CMD_NORMAL_PERM: u32 = 1; +const CMD_XPERM: u32 = 2; +const CMD_TYPE_STATE: u32 = 3; +const CMD_TYPE: u32 = 4; +const CMD_TYPE_ATTR: u32 = 5; +const CMD_ATTR: u32 = 6; +const CMD_TYPE_TRANSITION: u32 = 7; +const CMD_TYPE_CHANGE: u32 = 8; +const CMD_GENFSCON: u32 = 9; + +#[derive(Debug)] +enum PolicyObject { + All, // for "*", stand for all objects, and is NULL in ffi + One([u8; SEPOLICY_MAX_LEN]), + None, +} + +impl Default for PolicyObject { + fn default() -> Self { + PolicyObject::None + } +} + +impl TryFrom<&str> for PolicyObject { + type Error = anyhow::Error; + fn try_from(s: &str) -> Result { + anyhow::ensure!(s.len() <= SEPOLICY_MAX_LEN, "policy object too long"); + if s == "*" { + return Ok(PolicyObject::All); + } + let mut buf = [0u8; SEPOLICY_MAX_LEN]; + buf[..s.len()].copy_from_slice(s.as_bytes()); + Ok(PolicyObject::One(buf)) + } +} + +/// atomic statement, such as: allow domain1 domain2:file1 read; +/// normal statement would be expand to atomic statement, for example: +/// allow domain1 domain2:file1 { read write }; would be expand to two atomic statement +/// allow domain1 domain2:file1 read;allow domain1 domain2:file1 write; +#[derive(Debug, new)] +struct AtomicStatement { + cmd: u32, + subcmd: u32, + sepol1: PolicyObject, + sepol2: PolicyObject, + sepol3: PolicyObject, + sepol4: PolicyObject, + sepol5: PolicyObject, + sepol6: PolicyObject, + sepol7: PolicyObject, +} + +impl<'a> TryFrom<&'a NormalPerm<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(perm: &'a NormalPerm<'a>) -> Result { + let mut result = vec![]; + let subcmd = match perm.op { + "allow" => 1, + "deny" => 2, + "auditallow" => 3, + "dontaudit" => 4, + _ => 0, + }; + for &s in &perm.source { + for &t in &perm.target { + for &c in &perm.class { + for &p in &perm.perm { + result.push(AtomicStatement { + cmd: CMD_NORMAL_PERM, + subcmd, + sepol1: s.try_into()?, + sepol2: t.try_into()?, + sepol3: c.try_into()?, + sepol4: p.try_into()?, + sepol5: PolicyObject::None, + sepol6: PolicyObject::None, + sepol7: PolicyObject::None, + }); + } + } + } + } + Ok(result) + } +} + +impl<'a> TryFrom<&'a XPerm<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(perm: &'a XPerm<'a>) -> Result { + let mut result = vec![]; + let subcmd = match perm.op { + "allowxperm" => 1, + "auditallowxperm" => 2, + "dontauditxperm" => 3, + _ => 0, + }; + for &s in &perm.source { + for &t in &perm.target { + for &c in &perm.class { + result.push(AtomicStatement { + cmd: CMD_XPERM, + subcmd, + sepol1: s.try_into()?, + sepol2: t.try_into()?, + sepol3: c.try_into()?, + sepol4: perm.operation.try_into()?, + sepol5: perm.perm_set.try_into()?, + sepol6: PolicyObject::None, + sepol7: PolicyObject::None, + }); + } + } + } + Ok(result) + } +} + +impl<'a> TryFrom<&'a TypeState<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(perm: &'a TypeState<'a>) -> Result { + let mut result = vec![]; + let subcmd = match perm.op { + "permissive" => 1, + "enforcing" => 2, + _ => 0, + }; + for &t in &perm.stype { + result.push(AtomicStatement { + cmd: CMD_TYPE_STATE, + subcmd, + sepol1: t.try_into()?, + sepol2: PolicyObject::None, + sepol3: PolicyObject::None, + sepol4: PolicyObject::None, + sepol5: PolicyObject::None, + sepol6: PolicyObject::None, + sepol7: PolicyObject::None, + }); + } + Ok(result) + } +} + +impl<'a> TryFrom<&'a Type<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(perm: &'a Type<'a>) -> Result { + let mut result = vec![]; + for &attr in &perm.attrs { + result.push(AtomicStatement { + cmd: CMD_TYPE, + subcmd: 0, + sepol1: perm.name.try_into()?, + sepol2: attr.try_into()?, + sepol3: PolicyObject::None, + sepol4: PolicyObject::None, + sepol5: PolicyObject::None, + sepol6: PolicyObject::None, + sepol7: PolicyObject::None, + }); + } + Ok(result) + } +} + +impl<'a> TryFrom<&'a TypeAttr<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(perm: &'a TypeAttr<'a>) -> Result { + let mut result = vec![]; + for &t in &perm.stype { + for &attr in &perm.sattr { + result.push(AtomicStatement { + cmd: CMD_TYPE_ATTR, + subcmd: 0, + sepol1: t.try_into()?, + sepol2: attr.try_into()?, + sepol3: PolicyObject::None, + sepol4: PolicyObject::None, + sepol5: PolicyObject::None, + sepol6: PolicyObject::None, + sepol7: PolicyObject::None, + }); + } + } + Ok(result) + } +} + +impl<'a> TryFrom<&'a Attr<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(perm: &'a Attr<'a>) -> Result { + let mut result = vec![]; + result.push(AtomicStatement { + cmd: CMD_ATTR, + subcmd: 0, + sepol1: perm.name.try_into()?, + sepol2: PolicyObject::None, + sepol3: PolicyObject::None, + sepol4: PolicyObject::None, + sepol5: PolicyObject::None, + sepol6: PolicyObject::None, + sepol7: PolicyObject::None, + }); + Ok(result) + } +} + +impl<'a> TryFrom<&'a TypeTransition<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(perm: &'a TypeTransition<'a>) -> Result { + let mut result = vec![]; + let obj = match perm.object_name { + Some(obj) => obj.try_into()?, + None => PolicyObject::None, + }; + result.push(AtomicStatement { + cmd: CMD_TYPE_TRANSITION, + subcmd: 0, + sepol1: perm.source.try_into()?, + sepol2: perm.target.try_into()?, + sepol3: perm.class.try_into()?, + sepol4: perm.default_type.try_into()?, + sepol5: obj, + sepol6: PolicyObject::None, + sepol7: PolicyObject::None, + }); + Ok(result) + } +} + +impl<'a> TryFrom<&'a TypeChange<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(perm: &'a TypeChange<'a>) -> Result { + let mut result = vec![]; + let subcmd = match perm.op { + "type_change" => 1, + "type_member" => 2, + _ => 0, + }; + result.push(AtomicStatement { + cmd: CMD_TYPE_CHANGE, + subcmd, + sepol1: perm.source.try_into()?, + sepol2: perm.target.try_into()?, + sepol3: perm.class.try_into()?, + sepol4: perm.default_type.try_into()?, + sepol5: PolicyObject::None, + sepol6: PolicyObject::None, + sepol7: PolicyObject::None, + }); + Ok(result) + } +} + +impl<'a> TryFrom<&'a GenFsCon<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(perm: &'a GenFsCon<'a>) -> Result { + let result = vec![AtomicStatement { + cmd: CMD_GENFSCON, + subcmd: 0, + sepol1: perm.fs_name.try_into()?, + sepol2: perm.partial_path.try_into()?, + sepol3: perm.fs_context.try_into()?, + sepol4: PolicyObject::None, + sepol5: PolicyObject::None, + sepol6: PolicyObject::None, + sepol7: PolicyObject::None, + }]; + Ok(result) + } +} + +impl<'a> TryFrom<&'a PolicyStatement<'a>> for Vec { + type Error = anyhow::Error; + fn try_from(value: &'a PolicyStatement) -> Result { + match value { + PolicyStatement::NormalPerm(perm) => perm.try_into(), + PolicyStatement::XPerm(perm) => perm.try_into(), + PolicyStatement::TypeState(perm) => perm.try_into(), + PolicyStatement::Type(perm) => perm.try_into(), + PolicyStatement::TypeAttr(perm) => perm.try_into(), + PolicyStatement::Attr(perm) => perm.try_into(), + PolicyStatement::TypeTransition(perm) => perm.try_into(), + PolicyStatement::TypeChange(perm) => perm.try_into(), + PolicyStatement::GenFsCon(perm) => perm.try_into(), + } + } +} + +//////////////////////////////////////////////////////////////// +/// for C FFI to call kernel interface +/////////////////////////////////////////////////////////////// + +#[derive(Debug)] +#[repr(C)] +struct FfiPolicy { + cmd: u32, + subcmd: u32, + sepol1: *const libc::c_char, + sepol2: *const libc::c_char, + sepol3: *const libc::c_char, + sepol4: *const libc::c_char, + sepol5: *const libc::c_char, + sepol6: *const libc::c_char, + sepol7: *const libc::c_char, +} + +fn to_c_ptr(pol: &PolicyObject) -> *const libc::c_char { + match pol { + PolicyObject::None => std::ptr::null(), + PolicyObject::All => std::ptr::null(), + PolicyObject::One(s) => s.as_ptr(), + } +} + +impl From for FfiPolicy { + fn from(policy: AtomicStatement) -> FfiPolicy { + FfiPolicy { + cmd: policy.cmd, + subcmd: policy.subcmd, + sepol1: to_c_ptr(&policy.sepol1), + sepol2: to_c_ptr(&policy.sepol2), + sepol3: to_c_ptr(&policy.sepol3), + sepol4: to_c_ptr(&policy.sepol4), + sepol5: to_c_ptr(&policy.sepol5), + sepol6: to_c_ptr(&policy.sepol6), + sepol7: to_c_ptr(&policy.sepol7), + } + } +} + +fn apply_one_rule<'a>(statement: &'a PolicyStatement<'a>) -> Result<()> { + let policies: Vec = statement.try_into()?; + + for policy in policies { + let mut result: u32 = 0; + let cpolicy = FfiPolicy::from(policy); + unsafe { + libc::prctl( + crate::ksu::KERNEL_SU_OPTION as i32, + crate::ksu::CMD_SET_SEPOLICY, + 0, + &cpolicy as *const _ as *const libc::c_void, + &mut result as *mut _ as *mut libc::c_void, + ); + } + + if result != crate::ksu::KERNEL_SU_OPTION { + log::warn!("apply rule failed: {result}"); + } + } + + Ok(()) +} + +pub fn live_patch(policy: &str) -> Result<()> { + let result = parse_sepolicy(policy.trim())?; + for statement in result { + println!("{statement:?}"); + apply_one_rule(&statement)?; + } + Ok(()) +} + +pub fn apply_file(path: &str) -> Result<()> { + let input = std::fs::read_to_string(path)?; + live_patch(&input) +}