1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
#[cfg(test)]
mod tests;
use core::ffi::CStr;
use core::marker::PhantomData;
use core::{iter, ptr};
use std::io;
use std::os::raw::{c_int, c_uint};
use std::path::Path;
use crate::errors::{Error, Result};
use crate::label::Labeler;
use crate::utils::{
OptionalNativeFunctions, c_str_ptr_to_path, c_str_ptr_to_str, os_str_to_c_string,
ret_val_to_result,
};
bitflags! {
/// Flags controlling relabeling operations.
pub struct RestoreFlags: c_uint {
/// Force the checking of labels even if the stored SHA1 digest matches
/// the specfile entries SHA1 digest.
///
/// The specfile entries digest will be written to the `security.sehash`
/// extended attribute once relabeling has been completed successfully
/// provided the [`NO_CHANGE`] flag has not been set.
///
/// [`NO_CHANGE`]: Self::NO_CHANGE
#[allow(clippy::as_conversions)]
const IGNORE_DIGEST = selinux_sys::SELINUX_RESTORECON_IGNORE_DIGEST as c_uint;
/// Don't change any file labels (passive check) or update the digest in
/// the `security.sehash` extended attribute.
#[allow(clippy::as_conversions)]
const NO_CHANGE = selinux_sys::SELINUX_RESTORECON_NOCHANGE as c_uint;
/// If set, reset the files label to match the default spec file context.
/// If not set only reset the files "type" component of the context
/// to match the default spec file context.
#[allow(clippy::as_conversions)]
const SET_SPEC_FILE_CTX =
selinux_sys::SELINUX_RESTORECON_SET_SPECFILE_CTX as c_uint;
/// Change file and directory labels recursively (descend directories)
/// and if successful write an SHA1 digest of the spec file entries
/// to an extended attribute.
#[allow(clippy::as_conversions)]
const RECURSE = selinux_sys::SELINUX_RESTORECON_RECURSE as c_uint;
/// Log file label changes.
///
/// Note that if [`VERBOSE`] and [`PROGRESS`] flags are set,
/// then [`PROGRESS`] will take precedence.
///
/// [`VERBOSE`]: Self::VERBOSE
/// [`PROGRESS`]: Self::PROGRESS
#[allow(clippy::as_conversions)]
const VERBOSE = selinux_sys::SELINUX_RESTORECON_VERBOSE as c_uint;
/// Show progress by outputting the number of files in 1k blocks
/// processed to stdout.
///
/// If the [`MASS_RELABEL`] flag is also set then the approximate
/// percentage complete will be shown.
///
/// [`MASS_RELABEL`]: Self::MASS_RELABEL
#[allow(clippy::as_conversions)]
const PROGRESS = selinux_sys::SELINUX_RESTORECON_PROGRESS as c_uint;
/// Convert passed-in path name to the canonical path name using
/// `realpath()`.
#[allow(clippy::as_conversions)]
const REAL_PATH = selinux_sys::SELINUX_RESTORECON_REALPATH as c_uint;
/// Prevent descending into directories that have a different device
/// number than the path name entry from which the descent began.
#[allow(clippy::as_conversions)]
const XDEV = selinux_sys::SELINUX_RESTORECON_XDEV as c_uint;
/// Attempt to add an association between an inode and a specification.
/// If there is already an association for the inode and it conflicts
/// with the specification, then use the last matching specification.
#[allow(clippy::as_conversions)]
const ADD_ASSOC = selinux_sys::SELINUX_RESTORECON_ADD_ASSOC as c_uint;
/// Abort on errors during the file tree walk.
#[allow(clippy::as_conversions)]
const ABORT_ON_ERROR = selinux_sys::SELINUX_RESTORECON_ABORT_ON_ERROR as c_uint;
/// Log any label changes to `syslog()`.
#[allow(clippy::as_conversions)]
const SYS_LOG_CHANGES = selinux_sys::SELINUX_RESTORECON_SYSLOG_CHANGES as c_uint;
/// Log what spec file context matched each file.
#[allow(clippy::as_conversions)]
const LOG_MATCHES = selinux_sys::SELINUX_RESTORECON_LOG_MATCHES as c_uint;
/// Ignore files that do not exist.
#[allow(clippy::as_conversions)]
const IGNORE_NO_ENTRY = selinux_sys::SELINUX_RESTORECON_IGNORE_NOENTRY as c_uint;
/// Do not read `/proc/mounts` to obtain a list of non-seclabel mounts
/// to be excluded from relabeling checks.
///
/// Setting [`IGNORE_MOUNTS`] is useful where there is a non-seclabel fs
/// mounted with a seclabel fs mounted on a directory below this.
///
/// [`IGNORE_MOUNTS`]: Self::IGNORE_MOUNTS
#[allow(clippy::as_conversions)]
const IGNORE_MOUNTS = selinux_sys::SELINUX_RESTORECON_IGNORE_MOUNTS as c_uint;
/// Generally set when relabeling the entire OS, that will then show
/// the approximate percentage complete.
///
/// The [`PROGRESS`] flag must also be set.
///
/// [`PROGRESS`]: Self::PROGRESS
#[allow(clippy::as_conversions)]
const MASS_RELABEL = selinux_sys::SELINUX_RESTORECON_MASS_RELABEL as c_uint;
// The rest of the constants were defined after version 2.8, so selinux_sys might not
// export them. We therefore define them manually.
/// Do not check or update any extended attribute security.sehash entries.
///
/// This flag is supported only by `libselinux` version `3.0` or later.
const SKIP_DIGEST = 0x08000;
/// Treat conflicting specifications, such as where two hardlinks for
/// the same inode have different contexts, as errors.
///
/// This flag is supported only by `libselinux` version `3.1` or later.
const CONFLICT_ERROR = 0x10000;
/// Count, but otherwise ignore, errors during the file tree walk.
///
/// This flag requires `libselinux` version `3.4` or later.
const COUNT_ERRORS = 0x20000;
}
}
bitflags! {
/// Flags of [`ContextRestore::manage_security_sehash_xattr_entries`].
pub struct XAttrFlags: c_uint {
/// Recursively descend directories.
#[allow(clippy::as_conversions)]
const RECURSE = selinux_sys::SELINUX_RESTORECON_XATTR_RECURSE as c_uint;
/// Delete non-matching digests from each directory in path name.
#[allow(clippy::as_conversions)]
const DELETE_NON_MATCH_DIGESTS = selinux_sys::SELINUX_RESTORECON_XATTR_DELETE_NONMATCH_DIGESTS as c_uint;
/// Delete all digests from each directory in path name.
#[allow(clippy::as_conversions)]
const DELETE_ALL_DIGESTS = selinux_sys::SELINUX_RESTORECON_XATTR_DELETE_ALL_DIGESTS as c_uint;
/// Don't read `/proc/mounts` to obtain a list of non-seclabel mounts
/// to be excluded from the search.
///
/// Setting [`IGNORE_MOUNTS`] is useful where there is a non-seclabel fs
/// mounted with a seclabel fs mounted on a directory below this.
///
/// [`IGNORE_MOUNTS`]: Self::IGNORE_MOUNTS
#[allow(clippy::as_conversions)]
const IGNORE_MOUNTS = selinux_sys::SELINUX_RESTORECON_XATTR_IGNORE_MOUNTS as c_uint;
}
}
/// Restore file(s) default SELinux security contexts.
#[derive(Debug, Default)]
pub struct ContextRestore<'labeler, T: crate::label::back_end::BackEnd> {
labeler: Option<&'labeler mut Labeler<T>>,
}
impl<'labeler, T> ContextRestore<'labeler, T>
where
T: crate::label::back_end::BackEnd,
{
/// Set a labeling handle for relabeling.
///
/// See: `selinux_restorecon_set_sehandle()`.
#[doc(alias = "selinux_restorecon_set_sehandle")]
pub fn with_labeler(labeler: &'labeler mut Labeler<T>) -> Self {
Self {
labeler: Some(labeler),
}
}
/// Get the labeling handle to be used for relabeling.
#[must_use]
pub fn labeler(&self) -> Option<&&'labeler mut Labeler<T>> {
self.labeler.as_ref()
}
/// Set an alternate root path for relabeling.
///
/// See: `selinux_restorecon_set_alt_rootpath()`.
#[doc(alias = "selinux_restorecon_set_alt_rootpath")]
pub fn set_alternative_root_path(&mut self, path: impl AsRef<Path>) -> Result<()> {
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
let r = unsafe { selinux_sys::selinux_restorecon_set_alt_rootpath(c_path.as_ptr()) };
ret_val_to_result("selinux_restorecon_set_alt_rootpath()", r)
}
/// Add to the list of directories to be excluded from relabeling.
///
/// See: `selinux_restorecon_set_exclude_list()`.
#[doc(alias = "selinux_restorecon_set_exclude_list")]
pub fn add_exclude_list<P>(
&mut self,
exclusion_patterns: impl IntoIterator<Item = P>,
) -> Result<()>
where
P: AsRef<Path>,
{
let c_list_storage = exclusion_patterns
.into_iter()
.map(|path| os_str_to_c_string(path.as_ref().as_os_str()))
.collect::<Result<Vec<_>>>()?;
if !c_list_storage.is_empty() {
let mut c_ptr_list: Vec<_> = c_list_storage
.iter()
.map(AsRef::as_ref)
.map(CStr::as_ptr)
.chain(iter::once(ptr::null()))
.collect();
unsafe { selinux_sys::selinux_restorecon_set_exclude_list(c_ptr_list.as_mut_ptr()) };
}
Ok(())
}
/// Restore file(s) default SELinux security contexts.
///
/// If `threads_count` is zero, then:
/// - If `selinux_restorecon_parallel()` is supported by `libselinux` (version 3.4 or later),
/// then this operation will use as many threads as the number of online processor
/// cores present.
/// - Otherwise, this operation will run in one thread.
///
/// When this method succeeds:
/// - If `flags` includes [`RestoreFlags::COUNT_ERRORS`], then this returns `Ok(Some(N))`
/// where `N` is the number of errors that were ignored while walking the file system tree
/// specified by `path`.
/// - Otherwise, `Ok(None)` is returned.
///
/// See: `selinux_restorecon()`, `selinux_restorecon_parallel()`.
#[doc(alias = "selinux_restorecon")]
#[doc(alias = "selinux_restorecon_parallel")]
pub fn restore_context_of_file_system_entry(
self,
path: impl AsRef<Path>,
threads_count: usize,
flags: RestoreFlags,
) -> Result<Option<u64>> {
if let Some(labeler) = self.labeler.map(Labeler::as_mut_ptr) {
unsafe { selinux_sys::selinux_restorecon_set_sehandle(labeler) };
}
let c_path = os_str_to_c_string(path.as_ref().as_os_str())?;
match threads_count {
0 => {
// Call `selinux_restorecon_parallel()` if possible.
Error::clear_errno();
let r = unsafe {
(OptionalNativeFunctions::get().selinux_restorecon_parallel)(
c_path.as_ptr(),
flags.bits(),
threads_count,
)
};
if r == -1_i32 {
if io::Error::last_os_error().raw_os_error() != Some(libc::ENOSYS) {
return Err(Error::last_io_error("selinux_restorecon_parallel()"));
}
// `selinux_restorecon_parallel()` is unsupported.
// Call `selinux_restorecon()` instead.
let r =
unsafe { selinux_sys::selinux_restorecon(c_path.as_ptr(), flags.bits()) };
ret_val_to_result("selinux_restorecon()", r)?;
}
}
1 => {
let r = unsafe { selinux_sys::selinux_restorecon(c_path.as_ptr(), flags.bits()) };
ret_val_to_result("selinux_restorecon()", r)?;
}
_ => {
let r = unsafe {
(OptionalNativeFunctions::get().selinux_restorecon_parallel)(
c_path.as_ptr(),
flags.bits(),
threads_count,
)
};
ret_val_to_result("selinux_restorecon_parallel()", r)?;
}
}
Ok(flags.contains(RestoreFlags::COUNT_ERRORS).then(|| {
#[allow(clippy::useless_conversion)]
u64::from(unsafe {
(OptionalNativeFunctions::get().selinux_restorecon_get_skipped_errors)()
})
}))
}
/// Manage default `security.sehash` extended attribute entries added by
/// `selinux_restorecon()`, `setfiles()` or `restorecon()`.
///
/// See: `selinux_restorecon_xattr()`.
#[doc(alias = "selinux_restorecon_xattr")]
pub fn manage_security_sehash_xattr_entries(
dir_path: impl AsRef<Path>,
flags: XAttrFlags,
) -> Result<DirectoryXAttributesIter> {
let mut xattr_list_ptr: *mut *mut selinux_sys::dir_xattr = ptr::null_mut();
let c_dir_path = os_str_to_c_string(dir_path.as_ref().as_os_str())?;
let r: c_int = unsafe {
selinux_sys::selinux_restorecon_xattr(
c_dir_path.as_ptr(),
flags.bits(),
&mut xattr_list_ptr,
)
};
if r == -1_i32 {
Err(Error::last_io_error("selinux_restorecon_xattr()"))
} else {
let xattr_list = ptr::NonNull::new(xattr_list_ptr).map_or(
ptr::null_mut(),
|mut xattr_list_ptr| unsafe {
let xattr_list = *xattr_list_ptr.as_ref();
// Detach the linked list from libselinux, so that we own it from now on.
*xattr_list_ptr.as_mut() = ptr::null_mut();
xattr_list
},
);
Ok(DirectoryXAttributesIter(xattr_list))
}
}
}
/// Status of a [`DirectoryXAttributes`].
#[derive(Debug)]
#[non_exhaustive]
pub enum DirectoryDigestResult {
/// Match.
Match {
/// Matching digest deleted from the directory.
deleted: bool,
},
/// No match.
NoMatch {
/// Non-matching digest deleted from the directory.
deleted: bool,
},
/// Error.
Error,
/// Unknown status.
Unknown(c_uint),
}
/// Result of [`ContextRestore::manage_security_sehash_xattr_entries`].
#[derive(Debug)]
pub struct DirectoryXAttributes {
pointer: ptr::NonNull<selinux_sys::dir_xattr>,
_phantom_data: PhantomData<selinux_sys::dir_xattr>,
}
impl DirectoryXAttributes {
/// Return the managed raw pointer to [`selinux_sys::dir_xattr`].
#[must_use]
pub fn as_ptr(&self) -> *const selinux_sys::dir_xattr {
self.pointer.as_ptr()
}
/// Return the managed raw pointer to [`selinux_sys::dir_xattr`].
#[must_use]
pub fn as_mut_ptr(&mut self) -> *mut selinux_sys::dir_xattr {
self.pointer.as_ptr()
}
/// Directory path.
#[must_use]
pub fn directory_path(&self) -> &Path {
c_str_ptr_to_path(unsafe { self.pointer.as_ref().directory })
}
/// A hex encoded string that can be printed.
pub fn digest(&self) -> Result<&str> {
c_str_ptr_to_str(unsafe { self.pointer.as_ref().digest })
}
/// Status of this entry.
#[must_use]
pub fn digest_result(&self) -> DirectoryDigestResult {
match unsafe { self.pointer.as_ref().result } {
selinux_sys::digest_result::MATCH => DirectoryDigestResult::Match { deleted: false },
selinux_sys::digest_result::NOMATCH => {
DirectoryDigestResult::NoMatch { deleted: false }
}
selinux_sys::digest_result::DELETED_MATCH => {
DirectoryDigestResult::Match { deleted: true }
}
selinux_sys::digest_result::DELETED_NOMATCH => {
DirectoryDigestResult::NoMatch { deleted: true }
}
selinux_sys::digest_result::ERROR => DirectoryDigestResult::Error,
value => DirectoryDigestResult::Unknown(value),
}
}
}
impl Drop for DirectoryXAttributes {
fn drop(&mut self) {
unsafe {
libc::free(self.pointer.as_ref().directory.cast());
libc::free(self.pointer.as_ref().digest.cast());
libc::free(self.pointer.as_ptr().cast());
}
}
}
/// Iterator producing [`DirectoryXAttributes`] elements.
#[derive(Debug)]
pub struct DirectoryXAttributesIter(*mut selinux_sys::dir_xattr);
impl Iterator for DirectoryXAttributesIter {
type Item = DirectoryXAttributes;
fn next(&mut self) -> Option<DirectoryXAttributes> {
ptr::NonNull::new(self.0).map(|pointer| {
self.0 = unsafe { pointer.as_ref().next };
DirectoryXAttributes {
pointer,
_phantom_data: PhantomData,
}
})
}
}