Files
SukiSU-Ultra/userspace/ksud/src/sepolicy.rs
2023-02-01 19:12:25 +08:00

738 lines
22 KiB
Rust

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::{path::Path, 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<Vec<PolicyStatement<'a>>>
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<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a NormalPerm<'a>) -> Result<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a XPerm<'a>) -> Result<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a TypeState<'a>) -> Result<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a Type<'a>) -> Result<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a TypeAttr<'a>) -> Result<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a Attr<'a>) -> Result<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a TypeTransition<'a>) -> Result<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a TypeChange<'a>) -> Result<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(perm: &'a GenFsCon<'a>) -> Result<Self> {
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<AtomicStatement> {
type Error = anyhow::Error;
fn try_from(value: &'a PolicyStatement) -> Result<Self> {
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() as *const libc::c_char,
}
}
impl From<AtomicStatement> 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),
}
}
}
#[cfg(target_os = "android")]
fn apply_one_rule<'a>(statement: &'a PolicyStatement<'a>) -> Result<()> {
let policies: Vec<AtomicStatement> = 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(())
}
#[cfg(not(target_os = "android"))]
fn apply_one_rule<'a>(_statement: &'a PolicyStatement<'a>) -> Result<()> {
unimplemented!()
}
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<P: AsRef<Path>>(path: P) -> Result<()> {
let input = std::fs::read_to_string(path)?;
live_patch(&input)
}