Skip to content

Commit a247c07

Browse files
committed
mknod: label the node at creation instead of relabelling it after
Adds uucore::selinux::FsCreateContext, an RAII guard around setfscreatecon, so the kernel applies the context as part of mknod, as GNU does.
1 parent a014036 commit a247c07

2 files changed

Lines changed: 141 additions & 14 deletions

File tree

src/uu/mknod/src/mknod.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,25 @@ struct Config {
7373
}
7474

7575
fn mknod(file_name: &str, config: Config) -> i32 {
76+
// Label the node at creation, as GNU does; relabelling after leaves a window.
77+
#[cfg(all(feature = "selinux", any(target_os = "android", target_os = "linux")))]
78+
let _selinux_guard = if config.set_security_context {
79+
let mode = config.file_type.as_sflag().bits() | config.mode.bits();
80+
match uucore::selinux::FsCreateContext::new(
81+
std::path::Path::new(file_name),
82+
Some(mode),
83+
config.context.as_ref(),
84+
) {
85+
Ok(guard) => Some(guard),
86+
Err(e) => {
87+
let _ = writeln!(io::stderr(), "mknod: {e}");
88+
return 1;
89+
}
90+
}
91+
} else {
92+
None
93+
};
94+
7695
// set umask to 0 and store previous umask
7796
let have_prev_umask = if config.use_umask {
7897
None
@@ -103,20 +122,6 @@ fn mknod(file_name: &str, config: Config) -> i32 {
103122
);
104123
}
105124

106-
// Apply SELinux context if requested
107-
#[cfg(all(feature = "selinux", any(target_os = "android", target_os = "linux")))]
108-
if config.set_security_context
109-
&& let Err(e) = uucore::selinux::set_selinux_security_context(
110-
std::path::Path::new(file_name),
111-
config.context.as_ref(),
112-
)
113-
{
114-
// if it fails, delete the file
115-
let _ = std::fs::remove_file(file_name);
116-
let _ = writeln!(io::stderr(), "mknod: {e}");
117-
return 1;
118-
}
119-
120125
// Apply SMACK context if requested
121126
#[cfg(all(feature = "smack", target_os = "linux"))]
122127
if config.set_security_context

src/uucore/src/lib/features/selinux.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//! Set of functions to manage SELinux security contexts
77
88
use std::error::Error;
9+
use std::marker::PhantomData;
910
use std::path::Path;
1011

1112
use crate::translate;
@@ -62,6 +63,127 @@ pub fn is_selinux_enabled() -> bool {
6263
selinux::kernel_support() != selinux::KernelSupport::Unsupported
6364
}
6465

66+
/// Sets the context the kernel applies to objects created by this thread, and
67+
/// restores the policy default when dropped (`setfscreatecon`).
68+
///
69+
/// Unlike [`set_selinux_security_context`], which relabels after creating, the
70+
/// object is never visible with the wrong context. Not [`Send`]: the context is
71+
/// per-thread state.
72+
///
73+
/// # Examples
74+
///
75+
/// ```no_run
76+
/// use std::path::Path;
77+
/// use uucore::selinux::FsCreateContext;
78+
///
79+
/// let context = String::from("unconfined_u:object_r:user_home_t:s0");
80+
/// let _guard = FsCreateContext::new(Path::new("/path/to/fifo"), None, Some(&context))?;
81+
/// // Anything created from here on is labelled at creation time.
82+
/// # Ok::<(), uucore::selinux::SeLinuxError>(())
83+
/// ```
84+
#[must_use = "the creation context is restored as soon as the guard is dropped"]
85+
pub struct FsCreateContext {
86+
// False when no default context existed, so there is nothing to restore.
87+
installed: bool,
88+
_not_send: PhantomData<*const ()>,
89+
}
90+
91+
/// GNU creates the object unlabelled rather than failing when the policy has no
92+
/// default for the path, or the filesystem cannot hold one (`ignorable_ctx_err`).
93+
fn is_ignorable_context_error(error: &(dyn Error + 'static)) -> bool {
94+
let mut current = Some(error);
95+
while let Some(err) = current {
96+
if let Some(io_error) = err.downcast_ref::<std::io::Error>() {
97+
return matches!(
98+
io_error.raw_os_error(),
99+
Some(libc::ENOENT | libc::ENODATA | libc::ENOTSUP)
100+
);
101+
}
102+
current = err.source();
103+
}
104+
false
105+
}
106+
107+
impl FsCreateContext {
108+
/// Installs the creation context.
109+
///
110+
/// `context` is an explicit `--context=CTX` value. `None` asks the policy
111+
/// for the default context of `path`, which is what a bare `-Z` does;
112+
/// `mode` refines that lookup with the type and permission bits of the
113+
/// object about to be created, and may be `None` when they are not known.
114+
pub fn new(
115+
path: &Path,
116+
mode: Option<libc::mode_t>,
117+
context: Option<&String>,
118+
) -> Result<Self, SeLinuxError> {
119+
if !is_selinux_enabled() {
120+
return Err(SeLinuxError::SELinuxNotEnabled);
121+
}
122+
123+
if let Some(ctx_str) = context {
124+
let c_context = std::ffi::CString::new(ctx_str.as_str()).map_err(|e| {
125+
SeLinuxError::ContextConversionFailure(
126+
ctx_str.to_owned(),
127+
selinux_error_description(&e),
128+
)
129+
})?;
130+
131+
SecurityContext::from_c_str(&c_context, false)
132+
.set_for_new_file_system_objects(false)
133+
.map_err(|e| {
134+
SeLinuxError::ContextSetFailure(
135+
ctx_str.to_owned(),
136+
selinux_error_description(&e),
137+
)
138+
})?;
139+
} else {
140+
// Mirror GNU's `defaultcon`.
141+
let labeler =
142+
selinux::label::Labeler::<selinux::label::back_end::File>::new(&[], false)
143+
.map_err(|e| {
144+
SeLinuxError::ContextRetrievalFailure(selinux_error_description(&e))
145+
})?;
146+
147+
let default_context =
148+
match labeler.look_up_by_path(path, mode.and_then(selinux::FileAccessMode::new)) {
149+
Ok(context) => context,
150+
Err(e) if is_ignorable_context_error(&e) => {
151+
return Ok(Self {
152+
installed: false,
153+
_not_send: PhantomData,
154+
});
155+
}
156+
Err(e) => {
157+
return Err(SeLinuxError::ContextRetrievalFailure(
158+
selinux_error_description(&e),
159+
));
160+
}
161+
};
162+
163+
default_context
164+
.set_for_new_file_system_objects(false)
165+
.map_err(|e| {
166+
SeLinuxError::ContextSetFailure(String::new(), selinux_error_description(&e))
167+
})?;
168+
}
169+
170+
Ok(Self {
171+
installed: true,
172+
_not_send: PhantomData,
173+
})
174+
}
175+
}
176+
177+
impl Drop for FsCreateContext {
178+
fn drop(&mut self) {
179+
if !self.installed {
180+
return;
181+
}
182+
// A failed reset is not actionable here.
183+
let _ = SecurityContext::set_default_context_for_new_file_system_objects();
184+
}
185+
}
186+
65187
/// Returns a string describing the error and its causes.
66188
pub fn selinux_error_description(mut error: &dyn Error) -> String {
67189
let mut description = String::new();

0 commit comments

Comments
 (0)