|
6 | 6 | //! Set of functions to manage SELinux security contexts |
7 | 7 |
|
8 | 8 | use std::error::Error; |
| 9 | +use std::marker::PhantomData; |
9 | 10 | use std::path::Path; |
10 | 11 |
|
11 | 12 | use crate::translate; |
@@ -62,6 +63,127 @@ pub fn is_selinux_enabled() -> bool { |
62 | 63 | selinux::kernel_support() != selinux::KernelSupport::Unsupported |
63 | 64 | } |
64 | 65 |
|
| 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 | + |
65 | 187 | /// Returns a string describing the error and its causes. |
66 | 188 | pub fn selinux_error_description(mut error: &dyn Error) -> String { |
67 | 189 | let mut description = String::new(); |
|
0 commit comments