mirror of
https://github.com/uutils/coreutils
synced 2024-12-04 18:39:52 +00:00
runcon: added implementation and tests.
This commit is contained in:
parent
4ef35d4a96
commit
7010dfd939
11 changed files with 731 additions and 8 deletions
|
@ -91,6 +91,7 @@ rerast
|
|||
rollup
|
||||
sed
|
||||
selinuxenabled
|
||||
sestatus
|
||||
wslpath
|
||||
xargs
|
||||
|
||||
|
|
|
@ -162,6 +162,7 @@ blocksize
|
|||
canonname
|
||||
chroot
|
||||
dlsym
|
||||
execvp
|
||||
fdatasync
|
||||
freeaddrinfo
|
||||
getaddrinfo
|
||||
|
|
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -125,9 +125,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
|
@ -384,6 +384,7 @@ dependencies = [
|
|||
"uu_relpath",
|
||||
"uu_rm",
|
||||
"uu_rmdir",
|
||||
"uu_runcon",
|
||||
"uu_seq",
|
||||
"uu_shred",
|
||||
"uu_shuf",
|
||||
|
@ -1675,9 +1676,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||
|
||||
[[package]]
|
||||
name = "selinux"
|
||||
version = "0.2.1"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa2f705dd871c2eb90888bb2d44b13218b34f5c7318c3971df62f799d0143eb"
|
||||
checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
|
@ -2795,6 +2796,19 @@ dependencies = [
|
|||
"uucore_procs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_runcon"
|
||||
version = "0.0.7"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"fts-sys",
|
||||
"libc",
|
||||
"selinux",
|
||||
"thiserror",
|
||||
"uucore",
|
||||
"uucore_procs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_seq"
|
||||
version = "0.0.7"
|
||||
|
|
|
@ -189,6 +189,7 @@ feat_require_unix_utmpx = [
|
|||
# "feat_require_selinux" == set of utilities depending on SELinux.
|
||||
feat_require_selinux = [
|
||||
"chcon",
|
||||
"runcon",
|
||||
]
|
||||
## (alternate/newer/smaller platforms) feature sets
|
||||
# "feat_os_unix_fuchsia" == set of utilities which can be built/run on the "Fuchsia" OS (refs: <https://fuchsia.dev>; <https://en.wikipedia.org/wiki/Google_Fuchsia>)
|
||||
|
@ -241,7 +242,7 @@ clap = { version = "2.33", features = ["wrap_help"] }
|
|||
lazy_static = { version="1.3" }
|
||||
textwrap = { version="0.14", features=["terminal_size"] }
|
||||
uucore = { version=">=0.0.9", package="uucore", path="src/uucore" }
|
||||
selinux = { version="0.2.1", optional = true }
|
||||
selinux = { version="0.2.3", optional = true }
|
||||
# * uutils
|
||||
uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" }
|
||||
#
|
||||
|
@ -313,6 +314,7 @@ realpath = { optional=true, version="0.0.7", package="uu_realpath", path="src/uu
|
|||
relpath = { optional=true, version="0.0.7", package="uu_relpath", path="src/uu/relpath" }
|
||||
rm = { optional=true, version="0.0.7", package="uu_rm", path="src/uu/rm" }
|
||||
rmdir = { optional=true, version="0.0.7", package="uu_rmdir", path="src/uu/rmdir" }
|
||||
runcon = { optional=true, version="0.0.7", package="uu_runcon", path="src/uu/runcon" }
|
||||
seq = { optional=true, version="0.0.7", package="uu_seq", path="src/uu/seq" }
|
||||
shred = { optional=true, version="0.0.7", package="uu_shred", path="src/uu/shred" }
|
||||
shuf = { optional=true, version="0.0.7", package="uu_shuf", path="src/uu/shuf" }
|
||||
|
|
|
@ -157,7 +157,8 @@ UNIX_PROGS := \
|
|||
who
|
||||
|
||||
SELINUX_PROGS := \
|
||||
chcon
|
||||
chcon \
|
||||
runcon
|
||||
|
||||
ifneq ($(OS),Windows_NT)
|
||||
PROGS := $(PROGS) $(UNIX_PROGS)
|
||||
|
@ -216,6 +217,7 @@ TEST_PROGS := \
|
|||
realpath \
|
||||
rm \
|
||||
rmdir \
|
||||
runcon \
|
||||
seq \
|
||||
sort \
|
||||
split \
|
||||
|
|
|
@ -365,8 +365,8 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
|||
|
||||
| Done | Semi-Done | To Do |
|
||||
|-----------|-----------|--------|
|
||||
| arch | cp | runcon |
|
||||
| base32 | date | stty |
|
||||
| arch | cp | stty |
|
||||
| base32 | date | |
|
||||
| base64 | dd | |
|
||||
| basename | df | |
|
||||
| basenc | expr | |
|
||||
|
@ -426,6 +426,7 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
|||
| relpath | | |
|
||||
| rm | | |
|
||||
| rmdir | | |
|
||||
| runcon | | |
|
||||
| seq | | |
|
||||
| shred | | |
|
||||
| shuf | | |
|
||||
|
|
27
src/uu/runcon/Cargo.toml
Normal file
27
src/uu/runcon/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "uu_runcon"
|
||||
version = "0.0.7"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "runcon ~ (uutils) run command with specified security context"
|
||||
homepage = "https://github.com/uutils/coreutils"
|
||||
repository = "https://github.com/uutils/coreutils/tree/master/src/uu/runcon"
|
||||
keywords = ["coreutils", "uutils", "cli", "utility"]
|
||||
categories = ["command-line-utilities"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
path = "src/runcon.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "2.33", features = ["wrap_help"] }
|
||||
uucore = { version = ">=0.0.9", package="uucore", path="../../uucore", features=["entries", "fs", "perms"] }
|
||||
uucore_procs = { version = ">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
|
||||
selinux = { version = "0.2" }
|
||||
fts-sys = { version = "0.2" }
|
||||
thiserror = { version = "1.0" }
|
||||
libc = { version = "0.2" }
|
||||
|
||||
[[bin]]
|
||||
name = "runcon"
|
||||
path = "src/main.rs"
|
73
src/uu/runcon/src/errors.rs
Normal file
73
src/uu/runcon/src/errors.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use std::ffi::OsString;
|
||||
use std::fmt::Write;
|
||||
use std::io;
|
||||
use std::str::Utf8Error;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub(crate) enum Error {
|
||||
#[error("No command is specified")]
|
||||
MissingCommand,
|
||||
|
||||
#[error("SELinux is not enabled")]
|
||||
SELinuxNotEnabled,
|
||||
|
||||
#[error(transparent)]
|
||||
NotUTF8(#[from] Utf8Error),
|
||||
|
||||
#[error(transparent)]
|
||||
CommandLine(#[from] clap::Error),
|
||||
|
||||
#[error("{operation} failed")]
|
||||
SELinux {
|
||||
operation: &'static str,
|
||||
source: selinux::errors::Error,
|
||||
},
|
||||
|
||||
#[error("{operation} failed")]
|
||||
Io {
|
||||
operation: &'static str,
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
#[error("{operation} failed on '{}'", .operand1.to_string_lossy())]
|
||||
Io1 {
|
||||
operation: &'static str,
|
||||
operand1: OsString,
|
||||
source: io::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub(crate) fn from_io(operation: &'static str, source: io::Error) -> Self {
|
||||
Self::Io { operation, source }
|
||||
}
|
||||
|
||||
pub(crate) fn from_io1(
|
||||
operation: &'static str,
|
||||
operand1: impl Into<OsString>,
|
||||
source: io::Error,
|
||||
) -> Self {
|
||||
Self::Io1 {
|
||||
operation,
|
||||
operand1: operand1.into(),
|
||||
source,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_selinux(operation: &'static str, source: selinux::errors::Error) -> Self {
|
||||
Self::SELinux { operation, source }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn report_full_error(mut err: &dyn std::error::Error) -> String {
|
||||
let mut desc = String::with_capacity(256);
|
||||
write!(&mut desc, "{}", err).unwrap();
|
||||
while let Some(source) = err.source() {
|
||||
err = source;
|
||||
write!(&mut desc, ": {}", err).unwrap();
|
||||
}
|
||||
desc.push('.');
|
||||
desc
|
||||
}
|
1
src/uu/runcon/src/main.rs
Normal file
1
src/uu/runcon/src/main.rs
Normal file
|
@ -0,0 +1 @@
|
|||
uucore_procs::main!(uu_runcon);
|
450
src/uu/runcon/src/runcon.rs
Normal file
450
src/uu/runcon/src/runcon.rs
Normal file
|
@ -0,0 +1,450 @@
|
|||
// spell-checker:ignore (vars) RFILE
|
||||
|
||||
use uucore::{show_error, show_usage_error};
|
||||
|
||||
use clap::{App, Arg};
|
||||
use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::{CStr, CString, OsStr, OsString};
|
||||
use std::os::raw::c_char;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::{io, ptr};
|
||||
|
||||
mod errors;
|
||||
|
||||
use errors::{report_full_error, Error, Result};
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const ABOUT: &str = "Run command with specified security context.";
|
||||
const DESCRIPTION: &str = "Run COMMAND with completely-specified CONTEXT, or with current or \
|
||||
transitioned security context modified by one or more of \
|
||||
LEVEL, ROLE, TYPE, and USER.\n\n\
|
||||
If none of --compute, --type, --user, --role or --range is specified, \
|
||||
then the first argument is used as the complete context.\n\n\
|
||||
Note that only carefully-chosen contexts are likely to successfully run.\n\n\
|
||||
With neither CONTEXT nor COMMAND are specified, \
|
||||
then this prints the current security context.";
|
||||
|
||||
pub mod options {
|
||||
pub const COMPUTE: &str = "compute";
|
||||
|
||||
pub const USER: &str = "user";
|
||||
pub const ROLE: &str = "role";
|
||||
pub const TYPE: &str = "type";
|
||||
pub const RANGE: &str = "range";
|
||||
}
|
||||
|
||||
// This list is NOT exhaustive. This command might perform an `execvp()` to run
|
||||
// a different program. When that happens successfully, the exit status of this
|
||||
// process will be the exit status of that program.
|
||||
mod error_exit_status {
|
||||
pub const SUCCESS: i32 = libc::EXIT_SUCCESS;
|
||||
pub const NOT_FOUND: i32 = 127;
|
||||
pub const COULD_NOT_EXECUTE: i32 = 126;
|
||||
pub const ANOTHER_ERROR: i32 = libc::EXIT_FAILURE;
|
||||
}
|
||||
|
||||
fn get_usage() -> String {
|
||||
format!(
|
||||
"{0} [CONTEXT COMMAND [ARG...]]\n \
|
||||
{0} [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [ARG...]",
|
||||
uucore::execution_phrase()
|
||||
)
|
||||
}
|
||||
|
||||
pub fn uumain(args: impl uucore::Args) -> i32 {
|
||||
let usage = get_usage();
|
||||
|
||||
let config = uu_app().usage(usage.as_ref());
|
||||
|
||||
let options = match parse_command_line(config, args) {
|
||||
Ok(r) => r,
|
||||
Err(r) => {
|
||||
if let Error::CommandLine(ref r) = r {
|
||||
match r.kind {
|
||||
clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => {
|
||||
println!("{}", r);
|
||||
return error_exit_status::SUCCESS;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
show_usage_error!("{}.\n", r);
|
||||
return error_exit_status::ANOTHER_ERROR;
|
||||
}
|
||||
};
|
||||
|
||||
match &options.mode {
|
||||
CommandLineMode::Print => {
|
||||
if let Err(r) = print_current_context() {
|
||||
show_error!("{}", report_full_error(&r));
|
||||
return error_exit_status::ANOTHER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
CommandLineMode::PlainContext { context, command } => {
|
||||
let (exit_status, err) =
|
||||
if let Err(err) = get_plain_context(context).and_then(set_next_exec_context) {
|
||||
(error_exit_status::ANOTHER_ERROR, err)
|
||||
} else {
|
||||
// On successful execution, the following call never returns,
|
||||
// and this process image is replaced.
|
||||
execute_command(command, &options.arguments)
|
||||
};
|
||||
|
||||
show_error!("{}", report_full_error(&err));
|
||||
return exit_status;
|
||||
}
|
||||
|
||||
CommandLineMode::CustomContext {
|
||||
compute_transition_context,
|
||||
user,
|
||||
role,
|
||||
the_type,
|
||||
range,
|
||||
command,
|
||||
} => {
|
||||
if let Some(command) = command {
|
||||
let (exit_status, err) = if let Err(err) = get_custom_context(
|
||||
*compute_transition_context,
|
||||
user.as_deref(),
|
||||
role.as_deref(),
|
||||
the_type.as_deref(),
|
||||
range.as_deref(),
|
||||
command,
|
||||
)
|
||||
.and_then(set_next_exec_context)
|
||||
{
|
||||
(error_exit_status::ANOTHER_ERROR, err)
|
||||
} else {
|
||||
// On successful execution, the following call never returns,
|
||||
// and this process image is replaced.
|
||||
execute_command(command, &options.arguments)
|
||||
};
|
||||
|
||||
show_error!("{}", report_full_error(&err));
|
||||
return exit_status;
|
||||
} else if let Err(r) = print_current_context() {
|
||||
show_error!("{}", report_full_error(&r));
|
||||
return error_exit_status::ANOTHER_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error_exit_status::SUCCESS
|
||||
}
|
||||
|
||||
pub fn uu_app() -> App<'static, 'static> {
|
||||
App::new(uucore::util_name())
|
||||
.version(VERSION)
|
||||
.about(ABOUT)
|
||||
.after_help(DESCRIPTION)
|
||||
.arg(
|
||||
Arg::with_name(options::COMPUTE)
|
||||
.short("c")
|
||||
.long(options::COMPUTE)
|
||||
.takes_value(false)
|
||||
.help("Compute process transition context before modifying."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::USER)
|
||||
.short("u")
|
||||
.long(options::USER)
|
||||
.takes_value(true)
|
||||
.value_name("USER")
|
||||
.help("Set user USER in the target security context."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::ROLE)
|
||||
.short("r")
|
||||
.long(options::ROLE)
|
||||
.takes_value(true)
|
||||
.value_name("ROLE")
|
||||
.help("Set role ROLE in the target security context."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::TYPE)
|
||||
.short("t")
|
||||
.long(options::TYPE)
|
||||
.takes_value(true)
|
||||
.value_name("TYPE")
|
||||
.help("Set type TYPE in the target security context."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name(options::RANGE)
|
||||
.short("l")
|
||||
.long(options::RANGE)
|
||||
.takes_value(true)
|
||||
.value_name("RANGE")
|
||||
.help("Set range RANGE in the target security context."),
|
||||
)
|
||||
.arg(Arg::with_name("ARG").multiple(true))
|
||||
// Once "ARG" is parsed, everything after that belongs to it.
|
||||
//
|
||||
// This is not how POSIX does things, but this is how the GNU implementation
|
||||
// parses its command line.
|
||||
.setting(clap::AppSettings::TrailingVarArg)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CommandLineMode {
|
||||
Print,
|
||||
|
||||
PlainContext {
|
||||
context: OsString,
|
||||
command: OsString,
|
||||
},
|
||||
|
||||
CustomContext {
|
||||
/// Compute process transition context before modifying.
|
||||
compute_transition_context: bool,
|
||||
|
||||
/// Use the current context with the specified user.
|
||||
user: Option<OsString>,
|
||||
|
||||
/// Use the current context with the specified role.
|
||||
role: Option<OsString>,
|
||||
|
||||
/// Use the current context with the specified type.
|
||||
the_type: Option<OsString>,
|
||||
|
||||
/// Use the current context with the specified range.
|
||||
range: Option<OsString>,
|
||||
|
||||
// `command` can be `None`, in which case we're dealing with this syntax:
|
||||
// runcon [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE]
|
||||
//
|
||||
// This syntax is undocumented, but it is accepted by the GNU implementation,
|
||||
// so we do the same for compatibility.
|
||||
command: Option<OsString>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Options {
|
||||
mode: CommandLineMode,
|
||||
arguments: Vec<OsString>,
|
||||
}
|
||||
|
||||
fn parse_command_line(config: App, args: impl uucore::Args) -> Result<Options> {
|
||||
let matches = config.get_matches_from_safe(args)?;
|
||||
|
||||
let compute_transition_context = matches.is_present(options::COMPUTE);
|
||||
|
||||
let mut args = matches
|
||||
.values_of_os("ARG")
|
||||
.unwrap_or_default()
|
||||
.map(OsString::from);
|
||||
|
||||
if compute_transition_context
|
||||
|| matches.is_present(options::USER)
|
||||
|| matches.is_present(options::ROLE)
|
||||
|| matches.is_present(options::TYPE)
|
||||
|| matches.is_present(options::RANGE)
|
||||
{
|
||||
// runcon [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] [COMMAND [args]]
|
||||
|
||||
let mode = CommandLineMode::CustomContext {
|
||||
compute_transition_context,
|
||||
user: matches.value_of_os(options::USER).map(Into::into),
|
||||
role: matches.value_of_os(options::ROLE).map(Into::into),
|
||||
the_type: matches.value_of_os(options::TYPE).map(Into::into),
|
||||
range: matches.value_of_os(options::RANGE).map(Into::into),
|
||||
command: args.next(),
|
||||
};
|
||||
|
||||
Ok(Options {
|
||||
mode,
|
||||
arguments: args.collect(),
|
||||
})
|
||||
} else if let Some(context) = args.next() {
|
||||
// runcon CONTEXT COMMAND [args]
|
||||
|
||||
args.next()
|
||||
.ok_or(Error::MissingCommand)
|
||||
.map(move |command| Options {
|
||||
mode: CommandLineMode::PlainContext { context, command },
|
||||
arguments: args.collect(),
|
||||
})
|
||||
} else {
|
||||
// runcon
|
||||
|
||||
Ok(Options {
|
||||
mode: CommandLineMode::Print,
|
||||
arguments: Vec::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn print_current_context() -> Result<()> {
|
||||
let op = "Getting security context of the current process";
|
||||
let context = SecurityContext::current(false).map_err(|r| Error::from_selinux(op, r))?;
|
||||
|
||||
let context = context
|
||||
.to_c_string()
|
||||
.map_err(|r| Error::from_selinux(op, r))?;
|
||||
|
||||
if let Some(context) = context {
|
||||
let context = context.as_ref().to_str()?;
|
||||
println!("{}", context);
|
||||
} else {
|
||||
println!();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_next_exec_context(context: OpaqueSecurityContext) -> Result<()> {
|
||||
let c_context = context
|
||||
.to_c_string()
|
||||
.map_err(|r| Error::from_selinux("Creating new context", r))?;
|
||||
|
||||
let sc = SecurityContext::from_c_str(&c_context, false);
|
||||
|
||||
if sc.check() != Some(true) {
|
||||
let ctx = OsStr::from_bytes(c_context.as_bytes());
|
||||
let err = io::ErrorKind::InvalidInput.into();
|
||||
return Err(Error::from_io1("Checking security context", ctx, err));
|
||||
}
|
||||
|
||||
sc.set_for_next_exec()
|
||||
.map_err(|r| Error::from_selinux("Setting new security context", r))
|
||||
}
|
||||
|
||||
fn get_plain_context(context: &OsStr) -> Result<OpaqueSecurityContext> {
|
||||
if selinux::kernel_support() == selinux::KernelSupport::Unsupported {
|
||||
return Err(Error::SELinuxNotEnabled);
|
||||
}
|
||||
|
||||
let c_context = os_str_to_c_string(context)?;
|
||||
|
||||
OpaqueSecurityContext::from_c_str(&c_context)
|
||||
.map_err(|r| Error::from_selinux("Creating new context", r))
|
||||
}
|
||||
|
||||
fn get_transition_context(command: &OsStr) -> Result<SecurityContext> {
|
||||
// Generate context based on process transition.
|
||||
let sec_class = SecurityClass::from_name("process")
|
||||
.map_err(|r| Error::from_selinux("Getting process security class", r))?;
|
||||
|
||||
// Get context of file to be executed.
|
||||
let file_context = match SecurityContext::of_path(command, true, false) {
|
||||
Ok(Some(context)) => context,
|
||||
|
||||
Ok(None) => {
|
||||
let err = io::Error::from_raw_os_error(libc::ENODATA);
|
||||
return Err(Error::from_io1("getfilecon", command, err));
|
||||
}
|
||||
|
||||
Err(r) => {
|
||||
let op = "Getting security context of command file";
|
||||
return Err(Error::from_selinux(op, r));
|
||||
}
|
||||
};
|
||||
|
||||
let process_context = SecurityContext::current(false)
|
||||
.map_err(|r| Error::from_selinux("Getting security context of the current process", r))?;
|
||||
|
||||
// Compute result of process transition.
|
||||
process_context
|
||||
.of_labeling_decision(&file_context, sec_class, "")
|
||||
.map_err(|r| Error::from_selinux("Computing result of process transition", r))
|
||||
}
|
||||
|
||||
fn get_initial_custom_opaque_context(
|
||||
compute_transition_context: bool,
|
||||
command: &OsStr,
|
||||
) -> Result<OpaqueSecurityContext> {
|
||||
let context = if compute_transition_context {
|
||||
get_transition_context(command)?
|
||||
} else {
|
||||
SecurityContext::current(false).map_err(|r| {
|
||||
Error::from_selinux("Getting security context of the current process", r)
|
||||
})?
|
||||
};
|
||||
|
||||
let c_context = context
|
||||
.to_c_string()
|
||||
.map_err(|r| Error::from_selinux("Getting security context", r))?
|
||||
.unwrap_or_else(|| Cow::Owned(CString::default()));
|
||||
|
||||
OpaqueSecurityContext::from_c_str(c_context.as_ref())
|
||||
.map_err(|r| Error::from_selinux("Creating new context", r))
|
||||
}
|
||||
|
||||
fn get_custom_context(
|
||||
compute_transition_context: bool,
|
||||
user: Option<&OsStr>,
|
||||
role: Option<&OsStr>,
|
||||
the_type: Option<&OsStr>,
|
||||
range: Option<&OsStr>,
|
||||
command: &OsStr,
|
||||
) -> Result<OpaqueSecurityContext> {
|
||||
use OpaqueSecurityContext as OSC;
|
||||
type SetNewValueProc = fn(&OSC, &CStr) -> selinux::errors::Result<()>;
|
||||
|
||||
if selinux::kernel_support() == selinux::KernelSupport::Unsupported {
|
||||
return Err(Error::SELinuxNotEnabled);
|
||||
}
|
||||
|
||||
let osc = get_initial_custom_opaque_context(compute_transition_context, command)?;
|
||||
|
||||
let list: &[(Option<&OsStr>, SetNewValueProc, &'static str)] = &[
|
||||
(user, OSC::set_user, "Setting security context user"),
|
||||
(role, OSC::set_role, "Setting security context role"),
|
||||
(the_type, OSC::set_type, "Setting security context type"),
|
||||
(range, OSC::set_range, "Setting security context range"),
|
||||
];
|
||||
|
||||
for &(new_value, method, op) in list {
|
||||
if let Some(new_value) = new_value {
|
||||
let c_new_value = os_str_to_c_string(new_value)?;
|
||||
method(&osc, &c_new_value).map_err(|r| Error::from_selinux(op, r))?;
|
||||
}
|
||||
}
|
||||
Ok(osc)
|
||||
}
|
||||
|
||||
/// The actual return type of this function should be `Result<!, (i32, Error)>`
|
||||
/// However, until the *never* type is stabilized, one way to indicate to the
|
||||
/// compiler the only valid return type is to say "if this returns, it will
|
||||
/// always return an error".
|
||||
fn execute_command(command: &OsStr, arguments: &[OsString]) -> (i32, Error) {
|
||||
let c_command = match os_str_to_c_string(command) {
|
||||
Ok(v) => v,
|
||||
Err(r) => return (error_exit_status::ANOTHER_ERROR, r),
|
||||
};
|
||||
|
||||
let argv_storage: Vec<CString> = match arguments
|
||||
.iter()
|
||||
.map(AsRef::as_ref)
|
||||
.map(os_str_to_c_string)
|
||||
.collect::<Result<_>>()
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(r) => return (error_exit_status::ANOTHER_ERROR, r),
|
||||
};
|
||||
|
||||
let mut argv: Vec<*const c_char> = Vec::with_capacity(arguments.len().saturating_add(2));
|
||||
argv.push(c_command.as_ptr());
|
||||
argv.extend(argv_storage.iter().map(AsRef::as_ref).map(CStr::as_ptr));
|
||||
argv.push(ptr::null());
|
||||
|
||||
unsafe { libc::execvp(c_command.as_ptr(), argv.as_ptr()) };
|
||||
|
||||
let err = io::Error::last_os_error();
|
||||
let exit_status = if err.kind() == io::ErrorKind::NotFound {
|
||||
error_exit_status::NOT_FOUND
|
||||
} else {
|
||||
error_exit_status::COULD_NOT_EXECUTE
|
||||
};
|
||||
|
||||
let err = Error::from_io1("Executing command", command, err);
|
||||
(exit_status, err)
|
||||
}
|
||||
|
||||
fn os_str_to_c_string(s: &OsStr) -> Result<CString> {
|
||||
CString::new(s.as_bytes())
|
||||
.map_err(|_r| Error::from_io("CString::new()", io::ErrorKind::InvalidInput.into()))
|
||||
}
|
151
tests/by-util/test_runcon.rs
Normal file
151
tests/by-util/test_runcon.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
// spell-checker:ignore (jargon) xattributes
|
||||
|
||||
#![cfg(feature = "feat_selinux")]
|
||||
|
||||
use crate::common::util::*;
|
||||
|
||||
// TODO: Check the implementation of `--compute` somehow.
|
||||
|
||||
#[test]
|
||||
fn version() {
|
||||
new_ucmd!().arg("--version").succeeds();
|
||||
new_ucmd!().arg("-V").succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
new_ucmd!().arg("--help").succeeds();
|
||||
new_ucmd!().arg("-h").succeeds();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn print() {
|
||||
new_ucmd!().succeeds();
|
||||
|
||||
for &flag in &["-c", "--compute"] {
|
||||
new_ucmd!().arg(flag).succeeds();
|
||||
}
|
||||
|
||||
for &flag in &[
|
||||
"-t", "--type", "-u", "--user", "-r", "--role", "-l", "--range",
|
||||
] {
|
||||
new_ucmd!().args(&[flag, "example"]).succeeds();
|
||||
new_ucmd!().args(&[flag, "example1,example2"]).succeeds();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid() {
|
||||
new_ucmd!().arg("invalid").fails().code_is(1);
|
||||
|
||||
let args = &[
|
||||
"unconfined_u:unconfined_r:unconfined_t:s0",
|
||||
"inexistent-file",
|
||||
];
|
||||
new_ucmd!().args(args).fails().code_is(127);
|
||||
|
||||
let args = &["invalid", "/bin/true"];
|
||||
new_ucmd!().args(args).fails().code_is(1);
|
||||
|
||||
let args = &["--compute", "inexistent-file"];
|
||||
new_ucmd!().args(args).fails().code_is(1);
|
||||
|
||||
let args = &["--compute", "--compute"];
|
||||
new_ucmd!().args(args).fails().code_is(1);
|
||||
|
||||
// clap has an issue that makes this test fail: https://github.com/clap-rs/clap/issues/1543
|
||||
// TODO: Enable this code once the issue is fixed in the clap version we're using.
|
||||
//new_ucmd!().arg("--compute=example").fails().code_is(1);
|
||||
|
||||
for &flag in &[
|
||||
"-t", "--type", "-u", "--user", "-r", "--role", "-l", "--range",
|
||||
] {
|
||||
new_ucmd!().arg(flag).fails().code_is(1);
|
||||
|
||||
let args = &[flag, "example", flag, "example"];
|
||||
new_ucmd!().args(args).fails().code_is(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_context() {
|
||||
let ctx = "unconfined_u:unconfined_r:unconfined_t:s0-s0";
|
||||
new_ucmd!().args(&[ctx, "/bin/true"]).succeeds();
|
||||
new_ucmd!().args(&[ctx, "/bin/false"]).fails().code_is(1);
|
||||
|
||||
let output = new_ucmd!().args(&[ctx, "sestatus", "-v"]).succeeds();
|
||||
let r = get_sestatus_context(output.stdout());
|
||||
assert_eq!(r, "unconfined_u:unconfined_r:unconfined_t:s0");
|
||||
|
||||
let ctx = "system_u:unconfined_r:unconfined_t:s0-s0";
|
||||
new_ucmd!().args(&[ctx, "/bin/true"]).succeeds();
|
||||
|
||||
let ctx = "system_u:system_r:unconfined_t:s0";
|
||||
let output = new_ucmd!().args(&[ctx, "sestatus", "-v"]).succeeds();
|
||||
assert_eq!(get_sestatus_context(output.stdout()), ctx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_context() {
|
||||
let t_ud = "unconfined_t";
|
||||
let u_ud = "unconfined_u";
|
||||
let r_ud = "unconfined_r";
|
||||
|
||||
new_ucmd!().args(&["--compute", "/bin/true"]).succeeds();
|
||||
|
||||
let args = &["--compute", "/bin/false"];
|
||||
new_ucmd!().args(args).fails().code_is(1);
|
||||
|
||||
let args = &["--type", t_ud, "/bin/true"];
|
||||
new_ucmd!().args(args).succeeds();
|
||||
|
||||
let args = &["--compute", "--type", t_ud, "/bin/true"];
|
||||
new_ucmd!().args(args).succeeds();
|
||||
|
||||
let args = &["--user=system_u", "/bin/true"];
|
||||
new_ucmd!().args(args).succeeds();
|
||||
|
||||
let args = &["--compute", "--user=system_u", "/bin/true"];
|
||||
new_ucmd!().args(args).succeeds();
|
||||
|
||||
let args = &["--role=system_r", "/bin/true"];
|
||||
new_ucmd!().args(args).succeeds();
|
||||
|
||||
let args = &["--compute", "--role=system_r", "/bin/true"];
|
||||
new_ucmd!().args(args).succeeds();
|
||||
|
||||
new_ucmd!().args(&["--range=s0", "/bin/true"]).succeeds();
|
||||
|
||||
let args = &["--compute", "--range=s0", "/bin/true"];
|
||||
new_ucmd!().args(args).succeeds();
|
||||
|
||||
for &(ctx, u, r) in &[
|
||||
("unconfined_u:unconfined_r:unconfined_t:s0", u_ud, r_ud),
|
||||
("system_u:unconfined_r:unconfined_t:s0", "system_u", r_ud),
|
||||
("unconfined_u:system_r:unconfined_t:s0", u_ud, "system_r"),
|
||||
("system_u:system_r:unconfined_t:s0", "system_u", "system_r"),
|
||||
] {
|
||||
let args = &["-t", t_ud, "-u", u, "-r", r, "-l", "s0", "sestatus", "-v"];
|
||||
|
||||
let output = new_ucmd!().args(args).succeeds();
|
||||
assert_eq!(get_sestatus_context(output.stdout()), ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sestatus_context(output: &[u8]) -> &str {
|
||||
let re = regex::bytes::Regex::new(r#"Current context:\s*(\S+)\s*"#)
|
||||
.expect("Invalid regular expression");
|
||||
|
||||
output
|
||||
.split(|&b| b == b'\n')
|
||||
.find(|&b| b.starts_with(b"Current context:"))
|
||||
.and_then(|line| {
|
||||
re.captures_iter(line)
|
||||
.next()
|
||||
.and_then(|c| c.get(1))
|
||||
.as_ref()
|
||||
.map(regex::bytes::Match::as_bytes)
|
||||
})
|
||||
.and_then(|bytes| std::str::from_utf8(bytes).ok())
|
||||
.expect("Output of sestatus is unexpected")
|
||||
}
|
Loading…
Reference in a new issue