|
|
|
@ -0,0 +1,730 @@
|
|
|
|
|
//! Searches paths for the plugin name to find it's shared object file
|
|
|
|
|
use super::*;
|
|
|
|
|
use std::path::{
|
|
|
|
|
PathBuf,
|
|
|
|
|
Path,
|
|
|
|
|
};
|
|
|
|
|
use std::{
|
|
|
|
|
fs, io, ops,
|
|
|
|
|
borrow::{
|
|
|
|
|
Cow, Borrow,
|
|
|
|
|
},
|
|
|
|
|
//collections::HashMap,
|
|
|
|
|
};
|
|
|
|
|
use regex::{
|
|
|
|
|
Regex, RegexBuilder,
|
|
|
|
|
};
|
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
|
|
|
|
|
|
/// Specifies locations and associated rules of these locations for path lookups when searching for plugins.
|
|
|
|
|
pub mod locations {
|
|
|
|
|
use super::*;
|
|
|
|
|
use bitflags::*;
|
|
|
|
|
|
|
|
|
|
/// Describes a path an its trust level.
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
|
|
|
|
|
pub enum PathTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
Trusted(&'a str),
|
|
|
|
|
Untrusted(&'a str),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bitflags! {
|
|
|
|
|
/// The rules a file in an unstructed lookup path must qualify for it to be considered for plugin candidacy.
|
|
|
|
|
pub struct UntrustedPathRules : u8
|
|
|
|
|
{
|
|
|
|
|
/// No restrictions on the file.
|
|
|
|
|
///
|
|
|
|
|
/// # Note
|
|
|
|
|
/// Must always be 0.
|
|
|
|
|
const UNRESTRICTED = 0;
|
|
|
|
|
/// Name ends in valid string from `LOOKUP_NAME_MATCHES_IN_UNTRUSTED`.
|
|
|
|
|
const NAME_MATCH = 1 << 0;
|
|
|
|
|
/// Disallow symlinked files.
|
|
|
|
|
const NON_SYMLINK = 1 << 1;
|
|
|
|
|
/// Disqualify executable-marked files.
|
|
|
|
|
const NON_EXEC = 1 << 2;
|
|
|
|
|
/// Disqualify world-writable files.
|
|
|
|
|
const NON_WORLD_WRITABLE = 1 << 3;
|
|
|
|
|
/// Only qualify root-owned files (can be unioned with other `_OWNED_ONLY` constants)
|
|
|
|
|
const ROOT_OWNED_ONLY = 1 << 4;
|
|
|
|
|
/// Only qualify user-owned files (can be unioned with other `_OWNED_ONLY` constants)
|
|
|
|
|
const USER_OWNED_ONLY = 1 << 5;
|
|
|
|
|
/// Owned by user or by root.
|
|
|
|
|
const USER_OR_ROOT_OWNED = Self::ROOT_OWNED_ONLY.bits | Self::USER_OWNED_ONLY.bits;
|
|
|
|
|
/// Disqualify files that are accessible at all by anyone other than the current user (or root, which can accesss everything.)
|
|
|
|
|
const USER_ACCESSIBLE_ONLY = 1 << 6;
|
|
|
|
|
/// Disallow *all* untrusted files.
|
|
|
|
|
///
|
|
|
|
|
/// # Note
|
|
|
|
|
/// Must be highest value.
|
|
|
|
|
const INACCESSIBLE = 1 << 7;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl UntrustedPathRules
|
|
|
|
|
{
|
|
|
|
|
/// Contains function pointers for checking paths with every combination (**any**, not **all** in the mask) of rules in `UntrustedPathRules`.
|
|
|
|
|
///
|
|
|
|
|
/// Invalid functions will either:
|
|
|
|
|
/// * Panic if they are *never* supposed to be called in the pipeline (e.g `NAME_MATCH`.)
|
|
|
|
|
/// * Return false immediately.
|
|
|
|
|
pub const RULE_FUNCTION_CHECK_TABLE: [&'static (dyn Fn (&Path, &str) -> bool + 'static); 256] = Self::generate_rule_table();
|
|
|
|
|
|
|
|
|
|
/// Generate a table of 256 entries that which each single, or **OR**-combined mask entry of `UntrustedPathRules` has a corresponding path check function.
|
|
|
|
|
///
|
|
|
|
|
/// Invalid functions will either:
|
|
|
|
|
/// * Panic if they are *never* supposed to be called in the pipeline (e.g `NAME_MATCH`.)
|
|
|
|
|
/// * Return false immediately.
|
|
|
|
|
pub(super) const fn generate_rule_table() -> [&'static (dyn Fn (&Path, &str) -> bool + 'static); 256]
|
|
|
|
|
{
|
|
|
|
|
let mut rule_table = Self::generate_pruned_rule_table();
|
|
|
|
|
//TODO: Add in combinations via ORing its single-bit entry functions.
|
|
|
|
|
rule_table
|
|
|
|
|
}
|
|
|
|
|
/// Generate a table of 256 entries that which each **single** (not combinations of) entry of `UntrustedPathRules` has a corresponding path check function.
|
|
|
|
|
#[inline]
|
|
|
|
|
const fn generate_pruned_rule_table() -> [&'static (dyn Fn (&Path, &str) -> bool + 'static); 256]
|
|
|
|
|
{
|
|
|
|
|
type RuleCheckFunc<'a> = (dyn Fn(&Path, &str) -> bool + 'a);
|
|
|
|
|
#[inline]
|
|
|
|
|
const fn disallow(_: &Path, _: &str) -> bool { false }
|
|
|
|
|
|
|
|
|
|
let mut table: [&'static RuleCheckFunc; 256] = [&(disallow as fn(&Path, &str) -> bool); 256];
|
|
|
|
|
|
|
|
|
|
macro_rules! set {
|
|
|
|
|
($func:expr) => {
|
|
|
|
|
{
|
|
|
|
|
let func: &'static RuleCheckFunc<'static> = &($func);
|
|
|
|
|
func
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
($name:ident => $func:expr) => {
|
|
|
|
|
|
|
|
|
|
let bits = Self::$name.bits as usize;
|
|
|
|
|
|
|
|
|
|
// Fill in compositions based on previous entries into `table`.
|
|
|
|
|
let func: &'static RuleCheckFunc<'static> = set!($func);
|
|
|
|
|
/*let rv = {
|
|
|
|
|
let mut i: usize = 1;
|
|
|
|
|
//XXX: WHY doesn't this work???
|
|
|
|
|
while i < bits {
|
|
|
|
|
let prev = table[i];
|
|
|
|
|
|
|
|
|
|
table[bits | i] = set!(|path: &Path, name: &str| -> bool {
|
|
|
|
|
prev(path, name) && func(path, name)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
i += 1;
|
|
|
|
|
}*/
|
|
|
|
|
table[bits] = func
|
|
|
|
|
//};
|
|
|
|
|
//rv
|
|
|
|
|
};
|
|
|
|
|
($name:ident as ! $panic_msg:literal) => {
|
|
|
|
|
#[inline(never)]
|
|
|
|
|
#[cold]
|
|
|
|
|
fn _panic_with_msg() -> ! {
|
|
|
|
|
panic!($panic_msg)
|
|
|
|
|
}
|
|
|
|
|
set!($name => |_, _| _panic_with_msg())
|
|
|
|
|
};
|
|
|
|
|
/*FUCK THIS, I HATE ITERATON IN MACROS< WHY DOES IT HAVE TO BE SO RECURSIVELY LEAKILY RETARDED AAAA(@ compose ($f_name:ident, $f_file:ident) via $operator:tt $ignore:tt; $first:ident, $second:ident $($rest:tt)*)
|
|
|
|
|
=> (table[Self::$first.bits() as usize]($f_name, $f_file) $operator set!(@ compose ($f_name, $f_file) via $operator &&; Self::$second.bits(), $($rest)*));
|
|
|
|
|
(@ compose ($f_name:ident, $f_file:ident) $(via $operator:tt)?; $($first:ident)? $(,)?)
|
|
|
|
|
=> ($(table[Self::$first.bits() as usize])?);
|
|
|
|
|
($name:ident $(; use $operator:tt)? => $($other:ident),+) => {
|
|
|
|
|
set!($name => |path, filename| {
|
|
|
|
|
set!(@ compose (path, filename) via $($operator)? &&; $($other),+)
|
|
|
|
|
});
|
|
|
|
|
}*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// In lookups, use process EUID instead of UID.
|
|
|
|
|
const USE_EFFECTIVE_UID: bool = true;
|
|
|
|
|
|
|
|
|
|
/// Get UID or **E**UID.
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
fn get_uid<const E: bool>() -> u32
|
|
|
|
|
{
|
|
|
|
|
extern "C" {
|
|
|
|
|
fn getuid() -> u32; // uid_t
|
|
|
|
|
//XXX: Should we use process's UID or EUID? It seems we should use EUID.
|
|
|
|
|
fn geteuid() -> u32; // uid_t
|
|
|
|
|
}
|
|
|
|
|
// SAFETY: These are effectively pure.
|
|
|
|
|
unsafe {
|
|
|
|
|
if E {
|
|
|
|
|
geteuid()
|
|
|
|
|
} else {
|
|
|
|
|
getuid()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
fn check_uid<const CACHED: bool>(against: impl PartialEq<u32>) -> bool
|
|
|
|
|
{
|
|
|
|
|
//TODO: Allow these to be reset, somehow? Idk... Make them regular static atomics?
|
|
|
|
|
lazy_static! {
|
|
|
|
|
static ref USER_UID_CACHED: u32 = get_uid::<false>();
|
|
|
|
|
static ref USER_EUID_CACHED: u32 = get_uid::<true>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
against == if CACHED {
|
|
|
|
|
if USE_EFFECTIVE_UID {
|
|
|
|
|
*USER_EUID_CACHED
|
|
|
|
|
} else {
|
|
|
|
|
*USER_UID_CACHED
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
get_uid::<USE_EFFECTIVE_UID>()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
set!(NAME_MATCH as ! "This should already have been checked");
|
|
|
|
|
set!(NON_SYMLINK => |path: &Path, _| !path.is_symlink());
|
|
|
|
|
set!(NON_EXEC => |path, _| {
|
|
|
|
|
use std::os::unix::fs::PermissionsExt as _;
|
|
|
|
|
use readable_perms::{
|
|
|
|
|
User,
|
|
|
|
|
Bit,
|
|
|
|
|
};
|
|
|
|
|
const EXEC_BIT: u32 =
|
|
|
|
|
User::Group.mode(Bit::Execute)
|
|
|
|
|
| User::Other.mode(Bit::Execute)
|
|
|
|
|
| User::Owner.mode(Bit::Execute);
|
|
|
|
|
path.metadata()
|
|
|
|
|
.map(|meta| meta.permissions().mode() & EXEC_BIT == 0) //TODO: XXX: Test this.
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
});
|
|
|
|
|
set!(NON_WORLD_WRITABLE => |path, _| {
|
|
|
|
|
use readable_perms::{
|
|
|
|
|
PermissionsExt as _,
|
|
|
|
|
User,
|
|
|
|
|
Bit,
|
|
|
|
|
};
|
|
|
|
|
path.metadata()
|
|
|
|
|
.map(|meta| !meta.permissions().unix().has_mask(User::Other, Bit::Write))
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
});
|
|
|
|
|
set!(ROOT_OWNED_ONLY => |path, _| {
|
|
|
|
|
use std::os::unix::fs::MetadataExt as _;
|
|
|
|
|
path.metadata()
|
|
|
|
|
.map(|meta| meta.uid() == 0)
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
});
|
|
|
|
|
set!(USER_OWNED_ONLY => |path, _| {
|
|
|
|
|
use std::os::unix::fs::MetadataExt as _;
|
|
|
|
|
|
|
|
|
|
//let user_id = get_uid::<USE_EFFECTIVE_UID>();
|
|
|
|
|
path.metadata()
|
|
|
|
|
.map(move |meta| check_uid::<true>(meta.uid()))
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
});
|
|
|
|
|
set!(USER_ACCESSIBLE_ONLY => |path, _| {
|
|
|
|
|
//Only Owner should be able to read, write, or execute.
|
|
|
|
|
use std::os::unix::fs::PermissionsExt as _;
|
|
|
|
|
use readable_perms::{
|
|
|
|
|
User,
|
|
|
|
|
Bit,
|
|
|
|
|
};
|
|
|
|
|
const BIT_ANY: Bit =
|
|
|
|
|
Bit::Read
|
|
|
|
|
.union(Bit::Write)
|
|
|
|
|
.union(Bit::Execute);
|
|
|
|
|
const OTHER_ACCESSORS: u32 =
|
|
|
|
|
User::Group.mode(BIT_ANY)
|
|
|
|
|
| User::Other.mode(BIT_ANY);
|
|
|
|
|
path.metadata()
|
|
|
|
|
.map(|meta| !meta.permissions().mode() & OTHER_ACCESSORS == 0) //XXX: Test this
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
});
|
|
|
|
|
set!(USER_OR_ROOT_OWNED => |path, _| {
|
|
|
|
|
use std::os::unix::fs::MetadataExt as _;
|
|
|
|
|
|
|
|
|
|
//let user_id = get_uid::<USE_EFFECTIVE_UID>();
|
|
|
|
|
path.metadata()
|
|
|
|
|
.map(move |meta| match meta.uid() {
|
|
|
|
|
0 => true,
|
|
|
|
|
uid if check_uid::<true>(uid) => true,
|
|
|
|
|
_ => false
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
});
|
|
|
|
|
//set!(USER_OR_ROOT_OWNED; use || => USER_OWNED_ONLY, ROOT_OWNED_ONLY);
|
|
|
|
|
//TODO: Composition of functions should be done via OR, how to insert all possible compositions into table? We get confusing error messages when we try inside `set!()`
|
|
|
|
|
table
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for UntrustedPathRules
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
fn default() -> Self
|
|
|
|
|
{
|
|
|
|
|
Self::NAME_MATCH
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> AsRef<str> for PathTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
fn as_ref(&self) -> &str
|
|
|
|
|
{
|
|
|
|
|
match self {
|
|
|
|
|
Trusted(a) | Untrusted(a) => a
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
impl<'a> From<PathTrust<'a>> for &'a str
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
fn from(from: PathTrust<'a>) -> Self
|
|
|
|
|
{
|
|
|
|
|
from.into_str()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> From<PathTrust<'a>> for (&'a str, bool)
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
fn from(from: PathTrust<'a>) -> Self
|
|
|
|
|
{
|
|
|
|
|
let trusted = from.is_trusted();
|
|
|
|
|
(from.into_str(), trusted)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// impl<'a> AsRef<Path> for PathTrust<'a>
|
|
|
|
|
// {
|
|
|
|
|
// #[inline]
|
|
|
|
|
// fn as_ref(&self) -> &Path
|
|
|
|
|
// {
|
|
|
|
|
// Path::new(AsRef::<str>::as_ref(self))
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
use PathTrust::*;
|
|
|
|
|
// The ordering of these variables is the order in which the plugin is looked-up in. The first match found is used.
|
|
|
|
|
|
|
|
|
|
/// Should paths be recursively travelled in lookups. (0 is for trusted paths, 1 is for untrusted)
|
|
|
|
|
pub static LOOKUP_PATH_RECURSION: (bool, bool) = (true, true);
|
|
|
|
|
|
|
|
|
|
/// Should symlinks be followed in a recursive lookup. (0 is for trusted paths, 1 is for untrusted)
|
|
|
|
|
pub static LOOKUP_PATH_FOLLOW_SYMLINKS: (bool, bool) = (true, false);
|
|
|
|
|
|
|
|
|
|
/// Trust plugin file lookups in non-contained `LOOKUP_PATH_HOME_BASE_NAME`s.
|
|
|
|
|
pub static LOOKUP_PATH_TRUST_HOME: bool = false;
|
|
|
|
|
|
|
|
|
|
/// Base paths from user home to look-up plugin name.
|
|
|
|
|
pub static LOOKUP_PATH_HOME_BASE_NAME: &[PathTrust] = &[
|
|
|
|
|
Trusted(".rngcli/plugins/"),
|
|
|
|
|
Untrusted(".rngcli/").map_trust(LOOKUP_PATH_TRUST_HOME),
|
|
|
|
|
Untrusted(".config/rngcli/").map_trust(LOOKUP_PATH_TRUST_HOME),
|
|
|
|
|
Untrusted(".config/").map_trust(LOOKUP_PATH_TRUST_HOME),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// Extra valid lookup paths
|
|
|
|
|
pub static LOOKUP_PATH_EXTRA_PATHS: &[PathTrust] = &[
|
|
|
|
|
Trusted("/usr/share/rngcli/"),
|
|
|
|
|
Trusted("/usr/local/share/rngcli/")
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// `PATH`-like env-vars to look up a plugin's name.
|
|
|
|
|
pub static LOOKUP_PATH_ENV_VARS: &[PathTrust] = &[
|
|
|
|
|
Trusted("RNGCLI_PATH"),
|
|
|
|
|
Untrusted("PATH"),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// Allow lookup inside the current working directory.
|
|
|
|
|
pub static LOOKUP_PATH_ALLOW_CWD: bool = true;
|
|
|
|
|
|
|
|
|
|
/// Files with these regex-matched names are considered in all lookup contexts (`Trusted` and `Untrusted`.)
|
|
|
|
|
pub static LOOKUP_NAME_MATCHES_IN_TRUSTED: &[&'static str] = &[
|
|
|
|
|
r"\.rngp\.so$",
|
|
|
|
|
r"\.rngp$",
|
|
|
|
|
r"\.so$",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// Files with these regex-matched names are considered in `Untrusted` lookup contexts (if specified by `LOOKUP_UNTRUSTED_PATH_RULES`)
|
|
|
|
|
pub static LOOKUP_NAME_MATCHES_IN_UNTRUSTED: &[&'static str] = &[
|
|
|
|
|
r"\.rngp\.so$",
|
|
|
|
|
r"\.rngp$",
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
/// These rules must be met for lookups in untrusted paths.
|
|
|
|
|
pub static LOOKUP_UNTRUSTED_PATH_RULES: UntrustedPathRules =
|
|
|
|
|
UntrustedPathRules::NAME_MATCH
|
|
|
|
|
.union(UntrustedPathRules::NON_SYMLINK)
|
|
|
|
|
.union(UntrustedPathRules::NON_WORLD_WRITABLE);
|
|
|
|
|
|
|
|
|
|
impl<'a> PathTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
pub const fn into_str(self) -> &'a str
|
|
|
|
|
{
|
|
|
|
|
match self {
|
|
|
|
|
Trusted(a) | Untrusted(a) => a
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
pub fn into_path(self) -> &'a Path
|
|
|
|
|
{
|
|
|
|
|
match self {
|
|
|
|
|
Trusted(a) | Untrusted(a) => Path::new(a)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
pub fn as_path(&self) -> &Path
|
|
|
|
|
{
|
|
|
|
|
Path::new(self.as_ref())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
pub const fn is_trusted(self) -> bool
|
|
|
|
|
{
|
|
|
|
|
if let Trusted(_) = self {
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
pub const fn is_untrusted(self) -> bool
|
|
|
|
|
{
|
|
|
|
|
!self.is_trusted()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Map the trust level of this `PathTrust`.
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
pub const fn map_trust(self, trust: bool) -> Self
|
|
|
|
|
{
|
|
|
|
|
if trust { Trusted(self.into_str()) }
|
|
|
|
|
else { Untrusted(self.into_str()) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
struct LookupVisitor<P: AsRef<Path>>(P);
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct PathLookup {
|
|
|
|
|
name: String,
|
|
|
|
|
candidates: Vec<fs::DirEntry>,
|
|
|
|
|
trusted: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PathLookup
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn check_valid_path<const TRUSTED: bool>(path: impl AsRef<Path>) -> bool
|
|
|
|
|
{
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
fn regex_builder(pattern: impl AsRef<str>) -> Regex {
|
|
|
|
|
RegexBuilder::new(pattern.as_ref())
|
|
|
|
|
.case_insensitive(true)
|
|
|
|
|
.build().expect("Invalid regex in trusted pattern match array.")
|
|
|
|
|
}
|
|
|
|
|
/// Returns a function that returns `true` if any of the `matchers` match the input string.
|
|
|
|
|
#[inline]
|
|
|
|
|
const fn match_any<'a, I: 'a>(matchers: I) -> impl Fn(&str) -> bool + 'a
|
|
|
|
|
where I: IntoIterator<Item = &'a Regex> + Clone,
|
|
|
|
|
{
|
|
|
|
|
move |s: &str| -> bool {
|
|
|
|
|
let matchers = matchers.clone().into_iter();
|
|
|
|
|
for re in matchers {
|
|
|
|
|
if re.is_match(s) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/// Validate a file in an untrusted lookup.
|
|
|
|
|
#[inline]
|
|
|
|
|
fn validate_file_untrusted(path: &Path, filename: &str) -> bool
|
|
|
|
|
{
|
|
|
|
|
// By this point, `filename` has already matched successfully.
|
|
|
|
|
// Also Note: `filename` is just the basename of `path` as an `&str` (aliasing address-space.)
|
|
|
|
|
use locations::UntrustedPathRules as Rule;
|
|
|
|
|
use locations::LOOKUP_UNTRUSTED_PATH_RULES as RULES;
|
|
|
|
|
|
|
|
|
|
let mut bit = Rule::INACCESSIBLE.bits();
|
|
|
|
|
|
|
|
|
|
// No rules
|
|
|
|
|
if bit == 0 /* `Rule::UNRESTRICTED` */ {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
macro_rules! check {
|
|
|
|
|
($name:ident) => {
|
|
|
|
|
if RULES.contains(Rule::$name) {
|
|
|
|
|
Rule::RULE_FUNCTION_CHECK_TABLE[Rule::$name.bits() as usize](path, filename)
|
|
|
|
|
} else {
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
(try $($op:tt)? $name:ident) => {
|
|
|
|
|
if $($op)? check!($name) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// XXX: Ugh, for now, this shit must be checked independently.
|
|
|
|
|
check!(try ! USER_OR_ROOT_OWNED);
|
|
|
|
|
|
|
|
|
|
// Check rest of rules in sequence. Hopefully this will be unrolled since the first `if` statement is all const-fn on a static constant.
|
|
|
|
|
while bit > 0
|
|
|
|
|
{
|
|
|
|
|
// SAFETY: We know `INACCESSIBLE` is the largest
|
|
|
|
|
if RULES.contains(unsafe { Rule::from_bits_unchecked(bit) }) {
|
|
|
|
|
if !Rule::RULE_FUNCTION_CHECK_TABLE[bit as usize](path, filename) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
bit >>= 1;
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
lazy_static! {
|
|
|
|
|
static ref TRUSTED_EXT_REGEX_MAP: Vec<Regex> =
|
|
|
|
|
locations::LOOKUP_NAME_MATCHES_IN_TRUSTED.into_iter().map(regex_builder).collect();
|
|
|
|
|
static ref UNTRUSTED_EXT_REGEX_MAP: Vec<Regex> =
|
|
|
|
|
locations::LOOKUP_NAME_MATCHES_IN_UNTRUSTED.into_iter().map(regex_builder).collect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let path = path.as_ref();
|
|
|
|
|
|
|
|
|
|
// Check if the path:
|
|
|
|
|
// * Exists
|
|
|
|
|
// * Is not a directory
|
|
|
|
|
// NOTE: We don't use `.is_file()` here, because we want to support symlinks and pseudofiles in trusted contexts, which `is_file()` reports as false.
|
|
|
|
|
|
|
|
|
|
// TODO: XXX: Maybe in the future allow directories to be plugins; they would contain a manifest, and each `.so` file in the directory will be loaded in order specified by the directory's manifest.
|
|
|
|
|
if !path.exists() || path.is_dir() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attempt to extract the filename, return false if it is invalid UTF8.
|
|
|
|
|
let filename = match path.file_name()
|
|
|
|
|
.and_then(|os_fn| os_fn.to_str())
|
|
|
|
|
{
|
|
|
|
|
Some(f) => f,
|
|
|
|
|
_ => return false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Get the name matcher and rule checker for the file.
|
|
|
|
|
|
|
|
|
|
let (matcher, checker) = if TRUSTED {
|
|
|
|
|
// Trusted path lookup
|
|
|
|
|
(match_any(TRUSTED_EXT_REGEX_MAP.iter()), None)//&NoChecker)
|
|
|
|
|
} else {
|
|
|
|
|
// Untrusted path lookup
|
|
|
|
|
|
|
|
|
|
let matcher = if locations::LOOKUP_UNTRUSTED_PATH_RULES
|
|
|
|
|
.contains(locations::UntrustedPathRules::NAME_MATCH)
|
|
|
|
|
{
|
|
|
|
|
// Name must match
|
|
|
|
|
match_any(UNTRUSTED_EXT_REGEX_MAP.iter())
|
|
|
|
|
} else {
|
|
|
|
|
// Name needn't match
|
|
|
|
|
match_any(TRUSTED_EXT_REGEX_MAP.iter())
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
(matcher, Some(move |path| {
|
|
|
|
|
validate_file_untrusted(path, filename)
|
|
|
|
|
}))
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
matcher(filename) // Check filename first
|
|
|
|
|
&& match checker {
|
|
|
|
|
Some(untrusted) => untrusted(path), // Check extra Untrusted path rules next
|
|
|
|
|
_ => true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<P: AsRef<Path>> LookupVisitor<P>
|
|
|
|
|
{
|
|
|
|
|
pub fn start_lookup(&self, trusted: bool, name: impl Into<String>) -> io::Result<PathLookup>
|
|
|
|
|
{
|
|
|
|
|
Ok(PathLookup {
|
|
|
|
|
name: name.into(),
|
|
|
|
|
trusted,
|
|
|
|
|
candidates: fs::read_dir(&self.0)?.collect::<io::Result<Vec<_>>>()?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, T: ToOwned<Owned = PathBuf> + 'a> From<(T, bool)> for PluginTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
fn from((from, trust): (T, bool)) -> Self
|
|
|
|
|
{
|
|
|
|
|
(if trust {
|
|
|
|
|
Self::Trusted
|
|
|
|
|
} else {
|
|
|
|
|
Self::Untrusted
|
|
|
|
|
})(Cow::Owned(from.to_owned()))
|
|
|
|
|
.enforce_validity::<true>()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a> From<locations::PathTrust<'a>> for PluginTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
fn from(from: locations::PathTrust<'a>) -> Self
|
|
|
|
|
{
|
|
|
|
|
match from {
|
|
|
|
|
locations::PathTrust::Trusted(a) => PluginTrust::Trusted(Cow::from(Path::new(a))),
|
|
|
|
|
locations::PathTrust::Untrusted(a) => PluginTrust::Untrusted(Cow::from(Path::new(a))),
|
|
|
|
|
}.enforce_validity::<true>()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Denoted a plugin and if it was loaded from a trusted path or not
|
|
|
|
|
#[derive(Debug, Clone, Eq, Hash, PartialOrd, Ord)]
|
|
|
|
|
pub enum PluginTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
Trusted(Cow<'a, Path>),
|
|
|
|
|
Untrusted(Cow<'a, Path>)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> ops::Deref for PluginTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
type Target = Path;
|
|
|
|
|
#[inline]
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
self.as_path()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Borrow<Path> for PluginTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
fn borrow(&self) -> &Path
|
|
|
|
|
{
|
|
|
|
|
match self {
|
|
|
|
|
Self::Trusted(a) | Self::Untrusted(a) => a.borrow()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> AsRef<Path> for PluginTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
fn as_ref(&self) -> &Path
|
|
|
|
|
{
|
|
|
|
|
self.borrow()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> From<PluginTrust<'a>> for Cow<'a, Path>
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
fn from(from: PluginTrust<'a>) -> Self
|
|
|
|
|
{
|
|
|
|
|
match from {
|
|
|
|
|
PluginTrust::Trusted(a) | PluginTrust::Untrusted(a) => a
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> From<PluginTrust<'a>> for PathBuf
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
fn from(from: PluginTrust<'a>) -> Self {
|
|
|
|
|
let cow: Cow<Path> = from.into();
|
|
|
|
|
cow.into_owned()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, T: AsRef<Path> + ?Sized> PartialEq<T> for PluginTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
#[inline]
|
|
|
|
|
fn eq(&self, other: &T) -> bool
|
|
|
|
|
{
|
|
|
|
|
other.as_ref() == self.as_ref()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> PluginTrust<'a>
|
|
|
|
|
{
|
|
|
|
|
/// Was this plugin loaded from a trusted path?
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
pub fn is_trusted(&self) -> bool
|
|
|
|
|
{
|
|
|
|
|
if let Self::Trusted(_) = self {
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Was this plugin loaded from an untrusted path?
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn is_untrusted(&self) -> bool
|
|
|
|
|
{
|
|
|
|
|
!self.is_trusted()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the `Path`, trusted or otherwise.
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
fn as_path(&self) -> &Path
|
|
|
|
|
{
|
|
|
|
|
self.borrow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Map this path to another.
|
|
|
|
|
///
|
|
|
|
|
/// # Panics
|
|
|
|
|
/// If the output of `func` is not a file-like object or does not exist.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn map<F, T: Into<Cow<'a, Path>>>(self, func: F) -> Self
|
|
|
|
|
where F: FnOnce(Cow<'a, Path>) -> T
|
|
|
|
|
{
|
|
|
|
|
match self {
|
|
|
|
|
Self::Trusted(yes) => Self::Trusted(func(yes).into()),
|
|
|
|
|
Self::Untrusted(no) => Self::Untrusted(func(no).into())
|
|
|
|
|
}.enforce_validity::<false>()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
pub(super) fn is_valid(&self) -> bool
|
|
|
|
|
{
|
|
|
|
|
let path = self.as_path();
|
|
|
|
|
path.exists() && !path.is_dir() //XXX: Is this okay for validation for now? What about symlinked files?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline(always)]
|
|
|
|
|
fn enforce_validity<const DEBUG: bool>(self) -> Self
|
|
|
|
|
{
|
|
|
|
|
if !DEBUG || cfg!(debug_assertions) {
|
|
|
|
|
#[inline(never)]
|
|
|
|
|
#[cold]
|
|
|
|
|
fn _panic_invalid(path: impl Into<PathBuf>) -> !
|
|
|
|
|
{
|
|
|
|
|
panic!("Illegal plugin path name: {:?}", path.into())
|
|
|
|
|
}
|
|
|
|
|
if !self.is_valid() {
|
|
|
|
|
_panic_invalid(self)
|
|
|
|
|
} else {
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn lookup_plugin_name(name: impl AsRef<str>) -> impl IntoIterator<Item = PluginTrust<'static>> + Send + 'static
|
|
|
|
|
{
|
|
|
|
|
todo!("Create a LookupVisitor(<path>) for each path in `locations`, then call `start_lookup(<trusted path>, name)` on each *in order* specified in `locations`");
|
|
|
|
|
None::<PluginTrust> //TODO: ^ see above
|
|
|
|
|
}
|