mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 19:17:43 +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
|
rollup
|
||||||
sed
|
sed
|
||||||
selinuxenabled
|
selinuxenabled
|
||||||
|
sestatus
|
||||||
wslpath
|
wslpath
|
||||||
xargs
|
xargs
|
||||||
|
|
||||||
|
|
|
@ -162,6 +162,7 @@ blocksize
|
||||||
canonname
|
canonname
|
||||||
chroot
|
chroot
|
||||||
dlsym
|
dlsym
|
||||||
|
execvp
|
||||||
fdatasync
|
fdatasync
|
||||||
freeaddrinfo
|
freeaddrinfo
|
||||||
getaddrinfo
|
getaddrinfo
|
||||||
|
|
22
Cargo.lock
generated
22
Cargo.lock
generated
|
@ -125,9 +125,9 @@ checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitvec"
|
name = "bitvec"
|
||||||
|
@ -384,6 +384,7 @@ dependencies = [
|
||||||
"uu_relpath",
|
"uu_relpath",
|
||||||
"uu_rm",
|
"uu_rm",
|
||||||
"uu_rmdir",
|
"uu_rmdir",
|
||||||
|
"uu_runcon",
|
||||||
"uu_seq",
|
"uu_seq",
|
||||||
"uu_shred",
|
"uu_shred",
|
||||||
"uu_shuf",
|
"uu_shuf",
|
||||||
|
@ -1675,9 +1676,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selinux"
|
name = "selinux"
|
||||||
version = "0.2.1"
|
version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aa2f705dd871c2eb90888bb2d44b13218b34f5c7318c3971df62f799d0143eb"
|
checksum = "1cf704a543fe60d898f3253f1cc37655d0f0e9cdb68ef6230557e0e031b80608"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2795,6 +2796,19 @@ dependencies = [
|
||||||
"uucore_procs",
|
"uucore_procs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uu_runcon"
|
||||||
|
version = "0.0.7"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"fts-sys",
|
||||||
|
"libc",
|
||||||
|
"selinux",
|
||||||
|
"thiserror",
|
||||||
|
"uucore",
|
||||||
|
"uucore_procs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uu_seq"
|
name = "uu_seq"
|
||||||
version = "0.0.7"
|
version = "0.0.7"
|
||||||
|
|
|
@ -189,6 +189,7 @@ feat_require_unix_utmpx = [
|
||||||
# "feat_require_selinux" == set of utilities depending on SELinux.
|
# "feat_require_selinux" == set of utilities depending on SELinux.
|
||||||
feat_require_selinux = [
|
feat_require_selinux = [
|
||||||
"chcon",
|
"chcon",
|
||||||
|
"runcon",
|
||||||
]
|
]
|
||||||
## (alternate/newer/smaller platforms) feature sets
|
## (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>)
|
# "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" }
|
lazy_static = { version="1.3" }
|
||||||
textwrap = { version="0.14", features=["terminal_size"] }
|
textwrap = { version="0.14", features=["terminal_size"] }
|
||||||
uucore = { version=">=0.0.9", package="uucore", path="src/uucore" }
|
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
|
# * uutils
|
||||||
uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" }
|
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" }
|
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" }
|
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" }
|
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" }
|
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" }
|
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" }
|
shuf = { optional=true, version="0.0.7", package="uu_shuf", path="src/uu/shuf" }
|
||||||
|
|
|
@ -157,7 +157,8 @@ UNIX_PROGS := \
|
||||||
who
|
who
|
||||||
|
|
||||||
SELINUX_PROGS := \
|
SELINUX_PROGS := \
|
||||||
chcon
|
chcon \
|
||||||
|
runcon
|
||||||
|
|
||||||
ifneq ($(OS),Windows_NT)
|
ifneq ($(OS),Windows_NT)
|
||||||
PROGS := $(PROGS) $(UNIX_PROGS)
|
PROGS := $(PROGS) $(UNIX_PROGS)
|
||||||
|
@ -216,6 +217,7 @@ TEST_PROGS := \
|
||||||
realpath \
|
realpath \
|
||||||
rm \
|
rm \
|
||||||
rmdir \
|
rmdir \
|
||||||
|
runcon \
|
||||||
seq \
|
seq \
|
||||||
sort \
|
sort \
|
||||||
split \
|
split \
|
||||||
|
|
|
@ -365,8 +365,8 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
||||||
|
|
||||||
| Done | Semi-Done | To Do |
|
| Done | Semi-Done | To Do |
|
||||||
|-----------|-----------|--------|
|
|-----------|-----------|--------|
|
||||||
| arch | cp | runcon |
|
| arch | cp | stty |
|
||||||
| base32 | date | stty |
|
| base32 | date | |
|
||||||
| base64 | dd | |
|
| base64 | dd | |
|
||||||
| basename | df | |
|
| basename | df | |
|
||||||
| basenc | expr | |
|
| basenc | expr | |
|
||||||
|
@ -426,6 +426,7 @@ To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
||||||
| relpath | | |
|
| relpath | | |
|
||||||
| rm | | |
|
| rm | | |
|
||||||
| rmdir | | |
|
| rmdir | | |
|
||||||
|
| runcon | | |
|
||||||
| seq | | |
|
| seq | | |
|
||||||
| shred | | |
|
| shred | | |
|
||||||
| shuf | | |
|
| 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…
Add table
Add a link
Reference in a new issue