Skip to content

Commit

Permalink
add 'specific' scopes to Defaults entries (parsing only)
Browse files Browse the repository at this point in the history
  • Loading branch information
squell committed Oct 30, 2024
1 parent 07348eb commit f390f7f
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 13 deletions.
40 changes: 37 additions & 3 deletions src/sudoers/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,19 @@ pub enum Directive {
HostAlias(Defs<Hostname>) = HARDENED_ENUM_VALUE_1,
CmndAlias(Defs<Command>) = HARDENED_ENUM_VALUE_2,
RunasAlias(Defs<UserSpecifier>) = HARDENED_ENUM_VALUE_3,
Defaults(Vec<(String, ConfigValue)>) = HARDENED_ENUM_VALUE_4,
Defaults(Vec<(String, ConfigValue)>, ConfigScope) = HARDENED_ENUM_VALUE_4,
}

/// AST object for the 'context' (host, user, cmnd, runas) of a Defaults directive
#[repr(u32)]
pub enum ConfigScope {
// "Defaults entries are parsed in the following order:
// generic, host and user Defaults first, then runas Defaults and finally command defaults."
Generic,
Host(SpecList<Hostname>),

Check warning on line 127 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

field `0` is never read

Check warning on line 127 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

field `0` is never read

Check warning on line 127 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test

field `0` is never read

Check failure on line 127 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / clippy

field `0` is never read
User(SpecList<UserSpecifier>),

Check warning on line 128 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

field `0` is never read

Check warning on line 128 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

field `0` is never read

Check warning on line 128 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test

field `0` is never read

Check failure on line 128 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / clippy

field `0` is never read
RunAs(SpecList<UserSpecifier>),

Check warning on line 129 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

field `0` is never read

Check warning on line 129 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

field `0` is never read

Check warning on line 129 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test

field `0` is never read

Check failure on line 129 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / clippy

field `0` is never read
Command(SpecList<SimpleCommand>),

Check warning on line 130 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

field `0` is never read

Check warning on line 130 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

field `0` is never read

Check warning on line 130 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / build-and-test

field `0` is never read

Check failure on line 130 in src/sudoers/ast.rs

View workflow job for this annotation

GitHub Actions / clippy

field `0` is never read
}

pub type TextEnum = crate::defaults::StrEnum<'static>;
Expand Down Expand Up @@ -499,7 +511,7 @@ impl Parse for Sudo {
if let Some(users) = maybe(try_nonterminal::<SpecList<_>>(stream))? {
// element 1 always exists (parse_list fails on an empty list)
let key = &users[0];
if let Some(directive) = maybe(get_directive(key, stream))? {
if let Some(directive) = maybe(get_directive(key, stream, start_pos))? {
if users.len() != 1 {
unrecoverable!(pos = start_pos, stream, "invalid user name list");
}
Expand Down Expand Up @@ -582,6 +594,7 @@ impl<T> Many for Def<T> {
fn get_directive(
perhaps_keyword: &Spec<UserSpecifier>,
stream: &mut impl CharStream,
begin_pos: (usize, usize),
) -> Parsed<Directive> {
use super::ast::Directive::*;
use super::ast::Meta::*;
Expand All @@ -596,7 +609,28 @@ fn get_directive(
"Host_Alias" => make(HostAlias(expect_nonterminal(stream)?)),
"Cmnd_Alias" | "Cmd_Alias" => make(CmndAlias(expect_nonterminal(stream)?)),
"Runas_Alias" => make(RunasAlias(expect_nonterminal(stream)?)),
"Defaults" => make(Defaults(expect_nonterminal(stream)?)),
"Defaults" => {
let allow_scope_modifier = stream.get_pos().0 == begin_pos.0
&& stream.get_pos().1 - begin_pos.1 == "Defaults".len();

let scope = if allow_scope_modifier {
if is_syntax('@', stream)? {
ConfigScope::Host(expect_nonterminal(stream)?)
} else if is_syntax(':', stream)? {
ConfigScope::User(expect_nonterminal(stream)?)
} else if is_syntax('!', stream)? {
ConfigScope::Command(expect_nonterminal(stream)?)
} else if is_syntax('>', stream)? {
ConfigScope::RunAs(expect_nonterminal(stream)?)
} else {
ConfigScope::Generic
}
} else {
ConfigScope::Generic
};

make(Defaults(expect_nonterminal(stream)?, scope))
}
_ => reject(),
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/sudoers/ast_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ mod names {
const DESCRIPTION: &'static str = "path to binary (or sudoedit)";
}

impl UserFriendly for tokens::SimpleCommand {
const DESCRIPTION: &'static str = "path to binary (or sudoedit)";
}

impl UserFriendly
for (
SpecList<tokens::Hostname>,
Expand Down
2 changes: 1 addition & 1 deletion src/sudoers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ fn analyze(
Sudo::Decl(CmndAlias(mut def)) => cfg.aliases.cmnd.1.append(&mut def),
Sudo::Decl(RunasAlias(mut def)) => cfg.aliases.runas.1.append(&mut def),

Sudo::Decl(Defaults(params)) => {
Sudo::Decl(Defaults(params, scope)) => {

Check warning on line 645 in src/sudoers/mod.rs

View workflow job for this annotation

GitHub Actions / build-and-test-msrv

unused variable: `scope`

Check warning on line 645 in src/sudoers/mod.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

unused variable: `scope`

Check warning on line 645 in src/sudoers/mod.rs

View workflow job for this annotation

GitHub Actions / build-and-test-minimal

unused variable: `scope`

Check warning on line 645 in src/sudoers/mod.rs

View workflow job for this annotation

GitHub Actions / build-and-test

unused variable: `scope`

Check failure on line 645 in src/sudoers/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

unused variable: `scope`
for (name, value) in params {
set_default(cfg, name, value)
}
Expand Down
2 changes: 1 addition & 1 deletion src/sudoers/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ fn defaults_regression() {
#[test]
fn specific_defaults() {
assert!(parse_line("Defaults !use_pty").is_decl());
assert!(try_parse_line("Defaults!use_pty").is_none()); // this succeeds right now but should fail
assert!(try_parse_line("Defaults!use_pty").is_none());
assert!(parse_line("Defaults!/bin/bash !use_pty").is_decl());
assert!(try_parse_line("Defaults!/bin/bash!use_pty").is_none());
assert!(try_parse_line("Defaults !/bin/bash !use_pty").is_none());
Expand Down
45 changes: 37 additions & 8 deletions src/sudoers/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,21 +152,23 @@ impl Token for AliasName {

/// A struct that represents valid command strings; this can contain escape sequences and are
/// limited to 1024 characters.
pub type Command = (glob::Pattern, Option<Box<[String]>>);
pub type Command = (SimpleCommand, Option<Box<[String]>>);

/// A type that is specific to 'only commands', that can only happen in "Defaults!command" contexts;
/// which is essentially a subset of "Command"
pub type SimpleCommand = glob::Pattern;

impl Token for Command {
const MAX_LEN: usize = 1024;

fn construct(s: String) -> Result<Self, String> {
let cvt_err = |pat: Result<_, glob::PatternError>| {
pat.map_err(|err| format!("wildcard pattern error {err}"))
};

// the tokenizer should not give us a token that consists of only whitespace
let mut cmd_iter = s.split_whitespace();
let mut cmd = cmd_iter.next().unwrap().to_string();
let cmd = cmd_iter.next().unwrap().to_string();
let mut args = cmd_iter.map(String::from).collect::<Vec<String>>();

let command = SimpleCommand::construct(cmd)?;

let argpat = if args.is_empty() {
// if no arguments are mentioned, anything is allowed
None
Expand All @@ -178,6 +180,32 @@ impl Token for Command {
Some(args.into_boxed_slice())
};

Ok((command, argpat))
}

// all commands start with "/" except "sudoedit"
fn accept_1st(c: char) -> bool {
SimpleCommand::accept_1st(c)
}

fn accept(c: char) -> bool {
SimpleCommand::accept(c) || c == ' '
}

const ALLOW_ESCAPE: bool = SimpleCommand::ALLOW_ESCAPE;
fn escaped(c: char) -> bool {
SimpleCommand::escaped(c)
}
}

impl Token for SimpleCommand {
const MAX_LEN: usize = 1024;

fn construct(mut cmd: String) -> Result<Self, String> {
let cvt_err = |pat: Result<_, glob::PatternError>| {
pat.map_err(|err| format!("wildcard pattern error {err}"))
};

// record if the cmd ends in a slash and remove it if it does
let is_dir = cmd.ends_with('/') && {
cmd.pop();
Expand All @@ -197,7 +225,7 @@ impl Token for Command {
cmd.push_str("/*");
}

Ok((cvt_err(glob::Pattern::new(&cmd))?, argpat))
cvt_err(glob::Pattern::new(&cmd))
}

// all commands start with "/" except "sudoedit"
Expand All @@ -211,11 +239,12 @@ impl Token for Command {

const ALLOW_ESCAPE: bool = true;
fn escaped(c: char) -> bool {
matches!(c, '\\' | ',' | ':' | '=' | '#')
matches!(c, '\\' | ',' | ':' | '=' | '#' | ' ')
}
}

impl Many for Command {}
impl Many for SimpleCommand {}

pub struct DefaultName(pub String);

Expand Down

0 comments on commit f390f7f

Please sign in to comment.