chcon: reduce the number of unsafe blocks.

This commit is contained in:
Koutheir Attouchi 2021-08-06 09:52:34 -04:00
parent fe539a6dea
commit 83a515e4c3
8 changed files with 407 additions and 574 deletions

10
Cargo.lock generated
View file

@ -713,9 +713,9 @@ checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5"
[[package]]
name = "filetime"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98"
dependencies = [
"cfg-if 1.0.0",
"libc",
@ -1631,9 +1631,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "selinux"
version = "0.1.3"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd525eeb189eb26c8471463186bba87644e3d8a9c7ae392adaf9ec45ede574bc"
checksum = "1aa2f705dd871c2eb90888bb2d44b13218b34f5c7318c3971df62f799d0143eb"
dependencies = [
"bitflags",
"libc",
@ -2033,7 +2033,7 @@ dependencies = [
"clap",
"fts-sys",
"libc",
"selinux-sys",
"selinux",
"thiserror",
"uucore",
"uucore_procs",

View file

@ -241,7 +241,7 @@ clap = { version = "2.33", features = ["wrap_help"] }
lazy_static = { version="1.3" }
textwrap = { version="=0.11.0", features=["term_size"] } # !maint: [2020-05-10; rivy] unstable crate using undocumented features; pinned currently, will review
uucore = { version=">=0.0.9", package="uucore", path="src/uucore" }
selinux = { version="0.1.3", optional = true }
selinux = { version="0.2.1", optional = true }
# * uutils
uu_test = { optional=true, version="0.0.7", package="uu_test", path="src/uu/test" }
#

View file

@ -17,7 +17,7 @@ path = "src/chcon.rs"
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-sys = { version = "0.5" }
selinux = { version = "0.2" }
fts-sys = { version = "0.2" }
thiserror = { version = "1.0" }
libc = { version = "0.2" }

View file

@ -5,14 +5,18 @@
use uucore::{executable, show_error, show_usage_error, show_warning};
use clap::{App, Arg};
use selinux::{OpaqueSecurityContext, SecurityContext};
use std::borrow::Cow;
use std::ffi::{CStr, CString, OsStr, OsString};
use std::fmt::Write;
use std::os::raw::{c_char, c_int};
use std::os::raw::c_int;
use std::path::{Path, PathBuf};
use std::{fs, io, ptr, slice};
use std::{fs, io};
type Result<T> = std::result::Result<T, Error>;
mod errors;
mod fts;
use errors::*;
static VERSION: &str = env!("CARGO_PKG_VERSION");
static ABOUT: &str = "Change the SELinux security context of each FILE to CONTEXT. \n\
@ -81,15 +85,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
let context = match &options.mode {
CommandLineMode::ReferenceBased { reference } => {
let result = selinux::FileContext::new(reference, true)
.and_then(|r| {
if r.is_empty() {
Err(io::Error::from_raw_os_error(libc::ENODATA))
} else {
Ok(r)
}
})
.map_err(|r| Error::io1("Getting security context", reference, r));
let result = match SecurityContext::of_path(reference, true, false) {
Ok(Some(context)) => Ok(context),
Ok(None) => {
let err = io::Error::from_raw_os_error(libc::ENODATA);
Err(Error::from_io1("Getting security context", reference, err))
}
Err(r) => Err(Error::from_selinux("Getting security context", r)),
};
match result {
Err(r) => {
@ -102,33 +107,24 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
}
CommandLineMode::ContextBased { context } => {
match selinux::SecurityContext::security_check_context(context)
.map_err(|r| Error::io1("Checking security context", context, r))
{
Err(r) => {
show_error!("{}.", report_full_error(&r));
return libc::EXIT_FAILURE;
}
let c_context = match os_str_to_c_string(context) {
Ok(context) => context,
Ok(Some(false)) => {
Err(_r) => {
show_error!("Invalid security context '{}'.", context.to_string_lossy());
return libc::EXIT_FAILURE;
}
Ok(Some(true)) | Ok(None) => {}
}
let c_context = if let Ok(value) = os_str_to_c_string(context) {
value
} else {
show_error!("Invalid security context '{}'.", context.to_string_lossy());
return libc::EXIT_FAILURE;
};
SELinuxSecurityContext::String(c_context)
if SecurityContext::from_c_str(&c_context, false).check() == Some(false) {
show_error!("Invalid security context '{}'.", context.to_string_lossy());
return libc::EXIT_FAILURE;
}
SELinuxSecurityContext::String(Some(c_context))
}
CommandLineMode::Custom { .. } => SELinuxSecurityContext::default(),
CommandLineMode::Custom { .. } => SELinuxSecurityContext::String(None),
};
let root_dev_ino = if options.preserve_root && options.recursive_mode.is_recursive() {
@ -282,65 +278,6 @@ pub fn uu_app() -> App<'static, 'static> {
.arg(Arg::with_name("FILE").multiple(true).min_values(1))
}
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
}
#[derive(thiserror::Error, Debug)]
enum Error {
#[error("No context is specified")]
MissingContext,
#[error("No files are specified")]
MissingFiles,
#[error("{0}")]
ArgumentsMismatch(String),
#[error(transparent)]
CommandLine(#[from] clap::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 {
fn io1(operation: &'static str, operand1: impl Into<OsString>, source: io::Error) -> Self {
Self::Io1 {
operation,
operand1: operand1.into(),
source,
}
}
#[cfg(unix)]
fn io1_c_str(operation: &'static str, operand1: &CStr, source: io::Error) -> Self {
if operand1.to_bytes().is_empty() {
Self::Io { operation, source }
} else {
use std::os::unix::ffi::OsStrExt;
Self::io1(operation, OsStr::from_bytes(operand1.to_bytes()), source)
}
}
}
#[derive(Debug)]
struct Options {
verbose: bool,
@ -500,39 +437,29 @@ fn process_files(
root_dev_ino: Option<(libc::ino_t, libc::dev_t)>,
) -> Vec<Error> {
let fts_options = options.recursive_mode.fts_open_options();
let mut fts = match fts::FTS::new(options.files.iter(), fts_options, None) {
let mut fts = match fts::FTS::new(options.files.iter(), fts_options) {
Ok(fts) => fts,
Err(source) => {
return vec![Error::Io {
operation: "fts_open()",
source,
}]
}
Err(err) => return vec![err],
};
let mut results = vec![];
let mut errors = Vec::default();
loop {
match fts.read_next_entry() {
Ok(true) => {
if let Err(err) = process_file(options, context, &mut fts, root_dev_ino) {
results.push(err);
errors.push(err);
}
}
Ok(false) => break,
Err(source) => {
results.push(Error::Io {
operation: "fts_read()",
source,
});
Err(err) => {
errors.push(err);
break;
}
}
}
results
errors
}
fn process_file(
@ -541,46 +468,40 @@ fn process_file(
fts: &mut fts::FTS,
root_dev_ino: Option<(libc::ino_t, libc::dev_t)>,
) -> Result<()> {
let entry = fts.last_entry_mut().unwrap();
let mut entry = fts.last_entry_ref().unwrap();
let file_full_name = if entry.fts_path.is_null() {
None
} else {
let fts_path_size = usize::from(entry.fts_pathlen).saturating_add(1);
// SAFETY: `entry.fts_path` is a non-null pointer that is assumed to be valid.
let bytes = unsafe { slice::from_raw_parts(entry.fts_path.cast(), fts_path_size) };
CStr::from_bytes_with_nul(bytes).ok()
}
.ok_or_else(|| Error::Io {
operation: "File name validation",
source: io::ErrorKind::InvalidInput.into(),
let file_full_name = entry.path().map(PathBuf::from).ok_or_else(|| {
Error::from_io("File name validation", io::ErrorKind::InvalidInput.into())
})?;
let fts_access_path = ptr_to_c_str(entry.fts_accpath)
.map_err(|r| Error::io1_c_str("File name validation", file_full_name, r))?;
let fts_access_path = entry.access_path().ok_or_else(|| {
let err = io::ErrorKind::InvalidInput.into();
Error::from_io1("File name validation", &file_full_name, err)
})?;
let err = |s, k: io::ErrorKind| Error::io1_c_str(s, file_full_name, k.into());
let err = |s, k: io::ErrorKind| Error::from_io1(s, &file_full_name, k.into());
let fts_err = |s| {
let r = io::Error::from_raw_os_error(entry.fts_errno);
Err(Error::io1_c_str(s, file_full_name, r))
let r = io::Error::from_raw_os_error(entry.errno());
Err(Error::from_io1(s, &file_full_name, r))
};
// SAFETY: If `entry.fts_statp` is not null, then is is assumed to be valid.
let file_dev_ino = unsafe { entry.fts_statp.as_ref() }
.map(|stat| (stat.st_ino, stat.st_dev))
.ok_or_else(|| err("Getting meta data", io::ErrorKind::InvalidInput))?;
let file_dev_ino = if let Some(stat) = entry.stat() {
(stat.st_ino, stat.st_dev)
} else {
return Err(err("Getting meta data", io::ErrorKind::InvalidInput));
};
let mut result = Ok(());
match c_int::from(entry.fts_info) {
match entry.flags() {
fts_sys::FTS_D => {
if options.recursive_mode.is_recursive() {
if root_dev_ino_check(root_dev_ino, file_dev_ino) {
// This happens e.g., with "chcon -R --preserve-root ... /"
// and with "chcon -RH --preserve-root ... symlink-to-root".
root_dev_ino_warn(file_full_name);
root_dev_ino_warn(&file_full_name);
// Tell fts not to traverse into this hierarchy.
let _ignored = fts.set(fts_sys::FTS_SKIP);
@ -607,8 +528,8 @@ fn process_file(
// that modify permissions, it is possible that the file in question is accessible when
// control reaches this point. So, if this is the first time we've seen the FTS_NS for
// this file, tell fts_read to stat it "again".
if entry.fts_level == 0 && entry.fts_number == 0 {
entry.fts_number = 1;
if entry.level() == 0 && entry.number() == 0 {
entry.set_number(1);
let _ignored = fts.set(fts_sys::FTS_AGAIN);
return Ok(());
}
@ -621,8 +542,8 @@ fn process_file(
fts_sys::FTS_DNR => result = fts_err("Reading directory"),
fts_sys::FTS_DC => {
if cycle_warning_required(options.recursive_mode.fts_open_options(), entry) {
emit_cycle_warning(file_full_name);
if cycle_warning_required(options.recursive_mode.fts_open_options(), &entry) {
emit_cycle_warning(&file_full_name);
return Err(err("Reading cyclic directory", io::ErrorKind::InvalidData));
}
}
@ -630,11 +551,11 @@ fn process_file(
_ => {}
}
if c_int::from(entry.fts_info) == fts_sys::FTS_DP
if entry.flags() == fts_sys::FTS_DP
&& result.is_ok()
&& root_dev_ino_check(root_dev_ino, file_dev_ino)
{
root_dev_ino_warn(file_full_name);
root_dev_ino_warn(&file_full_name);
result = Err(err("Modifying root path", io::ErrorKind::PermissionDenied));
}
@ -656,24 +577,10 @@ fn process_file(
result
}
fn set_file_security_context(
path: &Path,
context: *const c_char,
follow_symbolic_links: bool,
) -> Result<()> {
let mut file_context = selinux::FileContext::from_ptr(context as *mut c_char);
if file_context.context.is_null() {
Err(io::Error::from(io::ErrorKind::InvalidInput))
} else {
file_context.set_for_file(path, follow_symbolic_links)
}
.map_err(|r| Error::io1("Setting security context", path, r))
}
fn change_file_context(
options: &Options,
context: &SELinuxSecurityContext,
file: &CStr,
path: &Path,
) -> Result<()> {
match &options.mode {
CommandLineMode::Custom {
@ -682,91 +589,88 @@ fn change_file_context(
the_type,
range,
} => {
let path = PathBuf::from(c_str_to_os_string(file));
let file_context = selinux::FileContext::new(&path, options.affect_symlink_referent)
.map_err(|r| Error::io1("Getting security context", &path, r))?;
let err0 = || -> Result<()> {
// If the file doesn't have a context, and we're not setting all of the context
// components, there isn't really an obvious default. Thus, we just give up.
let op = "Applying partial security context to unlabeled file";
let err = io::ErrorKind::InvalidInput.into();
Err(Error::from_io1(op, path, err))
};
// If the file doesn't have a context, and we're not setting all of the context
// components, there isn't really an obvious default. Thus, we just give up.
if file_context.is_empty() {
return Err(Error::io1(
"Applying partial security context to unlabeled file",
path,
io::ErrorKind::InvalidInput.into(),
));
}
let file_context =
match SecurityContext::of_path(path, options.affect_symlink_referent, false) {
Ok(Some(context)) => context,
let mut se_context = selinux::SecurityContext::new(file_context.as_ptr())
.map_err(|r| Error::io1("Creating security context", &path, r))?;
Ok(None) => return err0(),
Err(r) => return Err(Error::from_selinux("Getting security context", r)),
};
if let Some(user) = user {
se_context
.set_user(user)
.map_err(|r| Error::io1("Setting security context user", &path, r))?;
}
let c_file_context = match file_context.to_c_string() {
Ok(Some(context)) => context,
if let Some(role) = role {
se_context
.set_role(role)
.map_err(|r| Error::io1("Setting security context role", &path, r))?;
}
Ok(None) => return err0(),
Err(r) => return Err(Error::from_selinux("Getting security context", r)),
};
if let Some(the_type) = the_type {
se_context
.set_type(the_type)
.map_err(|r| Error::io1("Setting security context type", &path, r))?;
}
let se_context =
OpaqueSecurityContext::from_c_str(c_file_context.as_ref()).map_err(|_r| {
let err = io::ErrorKind::InvalidInput.into();
Error::from_io1("Creating security context", path, err)
})?;
if let Some(range) = range {
se_context
.set_range(range)
.map_err(|r| Error::io1("Setting security context range", &path, r))?;
type SetValueProc = fn(&OpaqueSecurityContext, &CStr) -> selinux::errors::Result<()>;
let list: &[(&Option<OsString>, SetValueProc)] = &[
(user, OpaqueSecurityContext::set_user),
(role, OpaqueSecurityContext::set_role),
(the_type, OpaqueSecurityContext::set_type),
(range, OpaqueSecurityContext::set_range),
];
for (new_value, set_value_proc) in list {
if let Some(new_value) = new_value {
let c_new_value = os_str_to_c_string(new_value).map_err(|_r| {
let err = io::ErrorKind::InvalidInput.into();
Error::from_io1("Creating security context", path, err)
})?;
set_value_proc(&se_context, &c_new_value)
.map_err(|r| Error::from_selinux("Setting security context user", r))?;
}
}
let context_string = se_context
.str_bytes()
.map_err(|r| Error::io1("Getting security context", &path, r))?;
.to_c_string()
.map_err(|r| Error::from_selinux("Getting security context", r))?;
if !file_context.is_empty() && file_context.as_bytes() == context_string {
if c_file_context.as_ref().to_bytes() == context_string.as_ref().to_bytes() {
Ok(()) // Nothing to change.
} else {
set_file_security_context(
&path,
context_string.as_ptr().cast(),
options.affect_symlink_referent,
)
SecurityContext::from_c_str(&context_string, false)
.set_for_path(path, options.affect_symlink_referent, false)
.map_err(|r| Error::from_selinux("Setting security context", r))
}
}
CommandLineMode::ReferenceBased { .. } | CommandLineMode::ContextBased { .. } => {
let path = PathBuf::from(c_str_to_os_string(file));
let ctx_ptr = context.as_ptr() as *mut c_char;
set_file_security_context(&path, ctx_ptr, options.affect_symlink_referent)
if let Some(c_context) = context.to_c_string()? {
SecurityContext::from_c_str(c_context.as_ref(), false)
.set_for_path(path, options.affect_symlink_referent, false)
.map_err(|r| Error::from_selinux("Setting security context", r))
} else {
let err = io::ErrorKind::InvalidInput.into();
Err(Error::from_io1("Setting security context", path, err))
}
}
}
}
#[cfg(unix)]
fn c_str_to_os_string(s: &CStr) -> OsString {
use std::os::unix::ffi::OsStringExt;
OsString::from_vec(s.to_bytes().to_vec())
}
#[cfg(unix)]
pub(crate) fn os_str_to_c_string(s: &OsStr) -> io::Result<CString> {
pub(crate) fn os_str_to_c_string(s: &OsStr) -> Result<CString> {
use std::os::unix::ffi::OsStrExt;
CString::new(s.as_bytes()).map_err(|_r| io::ErrorKind::InvalidInput.into())
}
/// SAFETY:
/// - If `p` is not null, then it is assumed to be a valid null-terminated C string.
/// - The returned `CStr` must not live more than the data pointed-to by `p`.
fn ptr_to_c_str<'s>(p: *const c_char) -> io::Result<&'s CStr> {
ptr::NonNull::new(p as *mut c_char)
.map(|p| unsafe { CStr::from_ptr(p.as_ptr()) })
.ok_or_else(|| io::ErrorKind::InvalidInput.into())
CString::new(s.as_bytes())
.map_err(|_r| Error::from_io("CString::new()", io::ErrorKind::InvalidInput.into()))
}
/// Call `lstat()` to get the device and inode numbers for `/`.
@ -776,7 +680,7 @@ fn get_root_dev_ino() -> Result<(libc::ino_t, libc::dev_t)> {
fs::symlink_metadata("/")
.map(|md| (md.ino(), md.dev()))
.map_err(|r| Error::io1("std::fs::symlink_metadata", "/", r))
.map_err(|r| Error::from_io1("std::fs::symlink_metadata", "/", r))
}
fn root_dev_ino_check(
@ -786,8 +690,8 @@ fn root_dev_ino_check(
root_dev_ino.map_or(false, |root_dev_ino| root_dev_ino == dir_dev_ino)
}
fn root_dev_ino_warn(dir_name: &CStr) {
if dir_name.to_bytes() == b"/" {
fn root_dev_ino_warn(dir_name: &Path) {
if dir_name.as_os_str() == "/" {
show_warning!(
"It is dangerous to operate recursively on '/'. \
Use --{} to override this failsafe.",
@ -810,370 +714,37 @@ fn root_dev_ino_warn(dir_name: &CStr) {
// However, when invoked with "-P -R", it deserves a warning.
// The fts_options parameter records the options that control this aspect of fts's behavior,
// so test that.
fn cycle_warning_required(fts_options: c_int, entry: &fts_sys::FTSENT) -> bool {
fn cycle_warning_required(fts_options: c_int, entry: &fts::EntryRef) -> bool {
// When dereferencing no symlinks, or when dereferencing only those listed on the command line
// and we're not processing a command-line argument, then a cycle is a serious problem.
((fts_options & fts_sys::FTS_PHYSICAL) != 0)
&& (((fts_options & fts_sys::FTS_COMFOLLOW) == 0) || entry.fts_level != 0)
&& (((fts_options & fts_sys::FTS_COMFOLLOW) == 0) || entry.level() != 0)
}
fn emit_cycle_warning(file_name: &CStr) {
fn emit_cycle_warning(file_name: &Path) {
show_warning!(
"Circular directory structure.\n\
This almost certainly means that you have a corrupted file system.\n\
NOTIFY YOUR SYSTEM MANAGER.\n\
The following directory is part of the cycle '{}'.",
file_name.to_string_lossy()
file_name.display()
)
}
#[derive(Debug)]
enum SELinuxSecurityContext {
File(selinux::FileContext),
String(CString),
enum SELinuxSecurityContext<'t> {
File(SecurityContext<'t>),
String(Option<CString>),
}
impl Default for SELinuxSecurityContext {
fn default() -> Self {
Self::String(CString::default())
}
}
impl SELinuxSecurityContext {
#[cfg(unix)]
fn as_ptr(&self) -> *const c_char {
impl<'t> SELinuxSecurityContext<'t> {
fn to_c_string(&self) -> Result<Option<Cow<CStr>>> {
match self {
SELinuxSecurityContext::File(context) => context.as_ptr(),
SELinuxSecurityContext::String(context) => context.to_bytes_with_nul().as_ptr().cast(),
}
}
}
mod fts {
use std::ffi::{CStr, CString, OsStr};
use std::os::raw::c_int;
use std::{io, iter, ptr};
use super::os_str_to_c_string;
pub(crate) type FTSOpenCallBack = unsafe extern "C" fn(
arg1: *mut *const fts_sys::FTSENT,
arg2: *mut *const fts_sys::FTSENT,
) -> c_int;
#[derive(Debug)]
pub(crate) struct FTS {
fts: ptr::NonNull<fts_sys::FTS>,
entry: *mut fts_sys::FTSENT,
}
impl FTS {
pub(crate) fn new<I>(
paths: I,
options: c_int,
compar: Option<FTSOpenCallBack>,
) -> io::Result<Self>
where
I: IntoIterator,
I::Item: AsRef<OsStr>,
{
let files_paths = paths
.into_iter()
.map(|s| os_str_to_c_string(s.as_ref()))
.collect::<io::Result<Vec<_>>>()?;
if files_paths.is_empty() {
return Err(io::ErrorKind::InvalidInput.into());
}
let path_argv = files_paths
.iter()
.map(CString::as_ref)
.map(CStr::as_ptr)
.chain(iter::once(ptr::null()))
.collect::<Vec<_>>();
// SAFETY: We assume calling fts_open() is safe:
// - `path_argv` is an array holding at least one path, and null-terminated.
let r = unsafe { fts_sys::fts_open(path_argv.as_ptr().cast(), options, compar) };
let fts = ptr::NonNull::new(r).ok_or_else(io::Error::last_os_error)?;
Ok(Self {
fts,
entry: ptr::null_mut(),
})
}
pub(crate) fn read_next_entry(&mut self) -> io::Result<bool> {
// SAFETY: We assume calling fts_read() is safe with a non-null `fts`
// pointer assumed to be valid.
self.entry = unsafe { fts_sys::fts_read(self.fts.as_ptr()) };
if self.entry.is_null() {
let r = io::Error::last_os_error();
if let Some(0) = r.raw_os_error() {
Ok(false)
} else {
Err(r)
}
} else {
Ok(true)
}
}
pub(crate) fn last_entry_mut(&mut self) -> Option<&mut fts_sys::FTSENT> {
// SAFETY: If `self.entry` is not null, then is is assumed to be valid.
unsafe { self.entry.as_mut() }
}
pub(crate) fn set(&mut self, instr: c_int) -> io::Result<()> {
let fts = self.fts.as_ptr();
let entry = self
.last_entry_mut()
.ok_or_else(|| io::Error::from(io::ErrorKind::UnexpectedEof))?;
// SAFETY: We assume calling fts_set() is safe with non-null `fts`
// and `entry` pointers assumed to be valid.
if unsafe { fts_sys::fts_set(fts, entry, instr) } == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
impl Drop for FTS {
fn drop(&mut self) {
// SAFETY: We assume calling fts_close() is safe with a non-null `fts`
// pointer assumed to be valid.
unsafe { fts_sys::fts_close(self.fts.as_ptr()) };
}
}
}
mod selinux {
use std::ffi::OsStr;
use std::os::raw::c_char;
use std::path::Path;
use std::{io, ptr, slice};
use super::os_str_to_c_string;
#[derive(Debug)]
pub(crate) struct SecurityContext(ptr::NonNull<selinux_sys::context_s_t>);
impl SecurityContext {
pub(crate) fn new(context_str: *const c_char) -> io::Result<Self> {
if context_str.is_null() {
Err(io::ErrorKind::InvalidInput.into())
} else {
// SAFETY: We assume calling context_new() is safe with
// a non-null `context_str` pointer assumed to be valid.
let p = unsafe { selinux_sys::context_new(context_str) };
ptr::NonNull::new(p)
.ok_or_else(io::Error::last_os_error)
.map(Self)
}
}
pub(crate) fn is_selinux_enabled() -> bool {
// SAFETY: We assume calling is_selinux_enabled() is always safe.
unsafe { selinux_sys::is_selinux_enabled() != 0 }
}
pub(crate) fn security_check_context(context: &OsStr) -> io::Result<Option<bool>> {
let c_context = os_str_to_c_string(context)?;
// SAFETY: We assume calling security_check_context() is safe with
// a non-null `context` pointer assumed to be valid.
if unsafe { selinux_sys::security_check_context(c_context.as_ptr()) } == 0 {
Ok(Some(true))
} else if Self::is_selinux_enabled() {
Ok(Some(false))
} else {
Ok(None)
}
}
pub(crate) fn str_bytes(&self) -> io::Result<&[u8]> {
// SAFETY: We assume calling context_str() is safe with
// a non-null `context` pointer assumed to be valid.
let p = unsafe { selinux_sys::context_str(self.0.as_ptr()) };
if p.is_null() {
Err(io::ErrorKind::InvalidInput.into())
} else {
let len = unsafe { libc::strlen(p.cast()) }.saturating_add(1);
Ok(unsafe { slice::from_raw_parts(p.cast(), len) })
}
}
pub(crate) fn set_user(&mut self, user: &OsStr) -> io::Result<()> {
let c_user = os_str_to_c_string(user)?;
// SAFETY: We assume calling context_user_set() is safe with non-null
// `context` and `user` pointers assumed to be valid.
if unsafe { selinux_sys::context_user_set(self.0.as_ptr(), c_user.as_ptr()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub(crate) fn set_role(&mut self, role: &OsStr) -> io::Result<()> {
let c_role = os_str_to_c_string(role)?;
// SAFETY: We assume calling context_role_set() is safe with non-null
// `context` and `role` pointers assumed to be valid.
if unsafe { selinux_sys::context_role_set(self.0.as_ptr(), c_role.as_ptr()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub(crate) fn set_type(&mut self, the_type: &OsStr) -> io::Result<()> {
let c_type = os_str_to_c_string(the_type)?;
// SAFETY: We assume calling context_type_set() is safe with non-null
// `context` and `the_type` pointers assumed to be valid.
if unsafe { selinux_sys::context_type_set(self.0.as_ptr(), c_type.as_ptr()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
pub(crate) fn set_range(&mut self, range: &OsStr) -> io::Result<()> {
let c_range = os_str_to_c_string(range)?;
// SAFETY: We assume calling context_range_set() is safe with non-null
// `context` and `range` pointers assumed to be valid.
if unsafe { selinux_sys::context_range_set(self.0.as_ptr(), c_range.as_ptr()) } == 0 {
Ok(())
} else {
Err(io::Error::last_os_error())
}
}
}
impl Drop for SecurityContext {
fn drop(&mut self) {
// SAFETY: We assume calling context_free() is safe with
// a non-null `context` pointer assumed to be valid.
unsafe { selinux_sys::context_free(self.0.as_ptr()) }
}
}
#[derive(Debug)]
pub(crate) struct FileContext {
pub context: *mut c_char,
pub len: usize,
pub allocated: bool,
}
impl FileContext {
pub(crate) fn new(path: &Path, follow_symbolic_links: bool) -> io::Result<Self> {
let c_path = os_str_to_c_string(path.as_os_str())?;
let mut context: *mut c_char = ptr::null_mut();
// SAFETY: We assume calling getfilecon()/lgetfilecon() is safe with
// non-null `path` and `context` pointers assumed to be valid.
let len = if follow_symbolic_links {
unsafe { selinux_sys::getfilecon(c_path.as_ptr(), &mut context) }
} else {
unsafe { selinux_sys::lgetfilecon(c_path.as_ptr(), &mut context) }
};
if len == -1 {
let err = io::Error::last_os_error();
if let Some(libc::ENODATA) = err.raw_os_error() {
Ok(Self::default())
} else {
Err(err)
}
} else if context.is_null() {
Ok(Self::default())
} else {
Ok(Self {
context,
len: len as usize,
allocated: true,
})
}
}
pub(crate) fn from_ptr(context: *mut c_char) -> Self {
if context.is_null() {
Self::default()
} else {
// SAFETY: We assume calling strlen() is safe with a non-null
// `context` pointer assumed to be valid.
let len = unsafe { libc::strlen(context) };
Self {
context,
len,
allocated: false,
}
}
}
pub(crate) fn set_for_file(
&mut self,
path: &Path,
follow_symbolic_links: bool,
) -> io::Result<()> {
let c_path = os_str_to_c_string(path.as_os_str())?;
// SAFETY: We assume calling setfilecon()/lsetfilecon() is safe with
// non-null `path` and `context` pointers assumed to be valid.
let r = if follow_symbolic_links {
unsafe { selinux_sys::setfilecon(c_path.as_ptr(), self.context) }
} else {
unsafe { selinux_sys::lsetfilecon(c_path.as_ptr(), self.context) }
};
if r == -1 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
pub(crate) fn as_ptr(&self) -> *const c_char {
self.context
}
pub(crate) fn is_empty(&self) -> bool {
self.context.is_null() || self.len == 0
}
pub(crate) fn as_bytes(&self) -> &[u8] {
if self.context.is_null() {
&[]
} else {
// SAFETY: `self.0.context` is a non-null pointer that is assumed to be valid.
unsafe { slice::from_raw_parts(self.context.cast(), self.len) }
}
}
}
impl Default for FileContext {
fn default() -> Self {
Self {
context: ptr::null_mut(),
len: 0,
allocated: false,
}
}
}
impl Drop for FileContext {
fn drop(&mut self) {
if self.allocated && !self.context.is_null() {
// SAFETY: We assume calling freecon() is safe with a non-null
// `context` pointer assumed to be valid.
unsafe { selinux_sys::freecon(self.context) }
}
Self::File(context) => context
.to_c_string()
.map_err(|r| Error::from_selinux("SELinuxSecurityContext::to_c_string()", r)),
Self::String(context) => Ok(context.as_deref().map(Cow::Borrowed)),
}
}
}

View file

@ -0,0 +1,71 @@
use std::ffi::OsString;
use std::fmt::Write;
use std::io;
pub(crate) type Result<T> = std::result::Result<T, Error>;
#[derive(thiserror::Error, Debug)]
pub(crate) enum Error {
#[error("No context is specified")]
MissingContext,
#[error("No files are specified")]
MissingFiles,
#[error("{0}")]
ArgumentsMismatch(String),
#[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
}

193
src/uu/chcon/src/fts.rs Normal file
View file

@ -0,0 +1,193 @@
use std::ffi::{CStr, CString, OsStr};
use std::marker::PhantomData;
use std::os::raw::{c_int, c_long, c_short};
use std::path::Path;
use std::ptr::NonNull;
use std::{io, iter, ptr, slice};
use crate::errors::{Error, Result};
use crate::os_str_to_c_string;
#[derive(Debug)]
pub(crate) struct FTS {
fts: ptr::NonNull<fts_sys::FTS>,
entry: Option<ptr::NonNull<fts_sys::FTSENT>>,
_phantom_data: PhantomData<fts_sys::FTSENT>,
}
impl FTS {
pub(crate) fn new<I>(paths: I, options: c_int) -> Result<Self>
where
I: IntoIterator,
I::Item: AsRef<OsStr>,
{
let files_paths: Vec<CString> = paths
.into_iter()
.map(|s| os_str_to_c_string(s.as_ref()))
.collect::<Result<_>>()?;
if files_paths.is_empty() {
return Err(Error::from_io(
"FTS::new()",
io::ErrorKind::InvalidInput.into(),
));
}
let path_argv: Vec<_> = files_paths
.iter()
.map(CString::as_ref)
.map(CStr::as_ptr)
.chain(iter::once(ptr::null()))
.collect();
// SAFETY: We assume calling fts_open() is safe:
// - `path_argv` is an array holding at least one path, and null-terminated.
// - `compar` is None.
let fts = unsafe { fts_sys::fts_open(path_argv.as_ptr().cast(), options, None) };
let fts = ptr::NonNull::new(fts)
.ok_or_else(|| Error::from_io("fts_open()", io::Error::last_os_error()))?;
Ok(Self {
fts,
entry: None,
_phantom_data: PhantomData,
})
}
pub(crate) fn last_entry_ref(&mut self) -> Option<EntryRef> {
self.entry.map(move |entry| EntryRef::new(self, entry))
}
pub(crate) fn read_next_entry(&mut self) -> Result<bool> {
// SAFETY: We assume calling fts_read() is safe with a non-null `fts`
// pointer assumed to be valid.
let new_entry = unsafe { fts_sys::fts_read(self.fts.as_ptr()) };
self.entry = NonNull::new(new_entry);
if self.entry.is_none() {
let r = io::Error::last_os_error();
if let Some(0) = r.raw_os_error() {
Ok(false)
} else {
Err(Error::from_io("fts_read()", r))
}
} else {
Ok(true)
}
}
pub(crate) fn set(&mut self, instr: c_int) -> Result<()> {
let fts = self.fts.as_ptr();
let entry = self
.entry
.ok_or_else(|| Error::from_io("FTS::set()", io::ErrorKind::UnexpectedEof.into()))?;
// SAFETY: We assume calling fts_set() is safe with non-null `fts`
// and `entry` pointers assumed to be valid.
if unsafe { fts_sys::fts_set(fts, entry.as_ptr(), instr) } == -1 {
Err(Error::from_io("fts_set()", io::Error::last_os_error()))
} else {
Ok(())
}
}
}
impl Drop for FTS {
fn drop(&mut self) {
// SAFETY: We assume calling fts_close() is safe with a non-null `fts`
// pointer assumed to be valid.
unsafe { fts_sys::fts_close(self.fts.as_ptr()) };
}
}
#[derive(Debug)]
pub(crate) struct EntryRef<'fts> {
pub(crate) pointer: ptr::NonNull<fts_sys::FTSENT>,
_fts: PhantomData<&'fts FTS>,
_phantom_data: PhantomData<fts_sys::FTSENT>,
}
impl<'fts> EntryRef<'fts> {
fn new(_fts: &'fts FTS, entry: ptr::NonNull<fts_sys::FTSENT>) -> Self {
Self {
pointer: entry,
_fts: PhantomData,
_phantom_data: PhantomData,
}
}
fn as_ref(&self) -> &fts_sys::FTSENT {
// SAFETY: `self.pointer` is a non-null pointer that is assumed to be valid.
unsafe { self.pointer.as_ref() }
}
fn as_mut(&mut self) -> &mut fts_sys::FTSENT {
// SAFETY: `self.pointer` is a non-null pointer that is assumed to be valid.
unsafe { self.pointer.as_mut() }
}
pub(crate) fn flags(&self) -> c_int {
c_int::from(self.as_ref().fts_info)
}
pub(crate) fn errno(&self) -> c_int {
self.as_ref().fts_errno
}
pub(crate) fn level(&self) -> c_short {
self.as_ref().fts_level
}
pub(crate) fn number(&self) -> c_long {
self.as_ref().fts_number
}
pub(crate) fn set_number(&mut self, new_number: c_long) {
self.as_mut().fts_number = new_number;
}
pub(crate) fn path(&self) -> Option<&Path> {
let entry = self.as_ref();
if entry.fts_pathlen == 0 {
return None;
}
NonNull::new(entry.fts_path)
.map(|path_ptr| {
let path_size = usize::from(entry.fts_pathlen).saturating_add(1);
// SAFETY: `entry.fts_path` is a non-null pointer that is assumed to be valid.
unsafe { slice::from_raw_parts(path_ptr.as_ptr().cast(), path_size) }
})
.and_then(|bytes| CStr::from_bytes_with_nul(bytes).ok())
.map(c_str_to_os_str)
.map(Path::new)
}
pub(crate) fn access_path(&self) -> Option<&Path> {
ptr::NonNull::new(self.as_ref().fts_accpath)
.map(|path_ptr| {
// SAFETY: `entry.fts_accpath` is a non-null pointer that is assumed to be valid.
unsafe { CStr::from_ptr(path_ptr.as_ptr()) }
})
.map(c_str_to_os_str)
.map(Path::new)
}
pub(crate) fn stat(&self) -> Option<&libc::stat> {
ptr::NonNull::new(self.as_ref().fts_statp).map(|stat_ptr| {
// SAFETY: `entry.fts_statp` is a non-null pointer that is assumed to be valid.
unsafe { stat_ptr.as_ref() }
})
}
}
#[cfg(unix)]
fn c_str_to_os_str(s: &CStr) -> &OsStr {
use std::os::unix::ffi::OsStrExt;
OsStr::from_bytes(s.to_bytes())
}

View file

@ -18,7 +18,7 @@ path = "src/id.rs"
clap = { version = "2.33", features = ["wrap_help"] }
uucore = { version=">=0.0.9", package="uucore", path="../../uucore", features=["entries", "process"] }
uucore_procs = { version=">=0.0.6", package="uucore_procs", path="../../uucore_procs" }
selinux = { version="0.1.3", optional = true }
selinux = { version="0.2.1", optional = true }
[[bin]]
name = "id"

View file

@ -40,8 +40,6 @@
extern crate uucore;
use clap::{crate_version, App, Arg};
#[cfg(all(target_os = "linux", feature = "selinux"))]
use selinux;
use std::ffi::CStr;
use uucore::entries::{self, Group, Locate, Passwd};
use uucore::error::UResult;