Skip to content

Commit 71af6d2

Browse files
sylvestrecakebaker
authored andcommitted
selinux/uucore: add two functions: contexts_differ & preserve_security_context
1 parent eff2cd9 commit 71af6d2

File tree

1 file changed

+230
-18
lines changed

1 file changed

+230
-18
lines changed

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

Lines changed: 230 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,106 @@ pub fn get_selinux_security_context(path: &Path) -> Result<String, SeLinuxError>
228228
}
229229
}
230230

231+
/// Compares SELinux security contexts of two filesystem paths.
232+
///
233+
/// This function retrieves and compares the SELinux security contexts of two paths.
234+
/// If the contexts differ or an error occurs during retrieval, it returns true.
235+
///
236+
/// # Arguments
237+
///
238+
/// * `from_path` - Source filesystem path.
239+
/// * `to_path` - Destination filesystem path.
240+
///
241+
/// # Returns
242+
///
243+
/// * `true` - If contexts differ, cannot be retrieved, or if SELinux is not enabled.
244+
/// * `false` - If contexts are the same.
245+
///
246+
/// # Examples
247+
///
248+
/// ```
249+
/// use std::path::Path;
250+
/// use uucore::selinux::contexts_differ;
251+
///
252+
/// // Check if contexts differ between two files
253+
/// let differ = contexts_differ(Path::new("/path/to/source"), Path::new("/path/to/destination"));
254+
/// if differ {
255+
/// println!("Files have different SELinux contexts");
256+
/// } else {
257+
/// println!("Files have the same SELinux context");
258+
/// }
259+
/// ```
260+
pub fn contexts_differ(from_path: &Path, to_path: &Path) -> bool {
261+
if !is_selinux_enabled() {
262+
return true;
263+
}
264+
265+
// Check if SELinux contexts differ
266+
match (
267+
selinux::SecurityContext::of_path(from_path, false, false),
268+
selinux::SecurityContext::of_path(to_path, false, false),
269+
) {
270+
(Ok(Some(from_ctx)), Ok(Some(to_ctx))) => {
271+
// Convert contexts to CString and compare
272+
match (from_ctx.to_c_string(), to_ctx.to_c_string()) {
273+
(Ok(Some(from_c_str)), Ok(Some(to_c_str))) => {
274+
from_c_str.to_string_lossy() != to_c_str.to_string_lossy()
275+
}
276+
// If contexts couldn't be converted to CString or were None, consider them different
277+
_ => true,
278+
}
279+
}
280+
// If either context is None or an error occurred, assume contexts differ
281+
_ => true,
282+
}
283+
}
284+
285+
/// Preserves the SELinux security context from one filesystem path to another.
286+
///
287+
/// This function copies the security context from the source path to the destination path.
288+
/// If SELinux is not enabled, or if the source has no context, the function returns success
289+
/// without making any changes.
290+
///
291+
/// # Arguments
292+
///
293+
/// * `from_path` - Source filesystem path from which to copy the SELinux context.
294+
/// * `to_path` - Destination filesystem path to which the context should be applied.
295+
///
296+
/// # Returns
297+
///
298+
/// * `Ok(())` - If the context was successfully preserved or if SELinux is not enabled.
299+
/// * `Err(SeLinuxError)` - If an error occurred during context retrieval or application.
300+
///
301+
/// # Examples
302+
///
303+
/// ```
304+
/// use std::path::Path;
305+
/// use uucore::selinux::preserve_security_context;
306+
///
307+
/// // Preserve the SELinux context from source to destination
308+
/// match preserve_security_context(Path::new("/path/to/source"), Path::new("/path/to/destination")) {
309+
/// Ok(_) => println!("Context preserved successfully (or SELinux is not enabled)"),
310+
/// Err(err) => eprintln!("Failed to preserve context: {}", err),
311+
/// }
312+
/// ```
313+
pub fn preserve_security_context(from_path: &Path, to_path: &Path) -> Result<(), SeLinuxError> {
314+
// If SELinux is not enabled, return success without doing anything
315+
if !is_selinux_enabled() {
316+
return Err(SeLinuxError::SELinuxNotEnabled);
317+
}
318+
319+
// Get context from the source path
320+
let context = get_selinux_security_context(from_path)?;
321+
322+
// If no context was found, just return success (nothing to preserve)
323+
if context.is_empty() {
324+
return Ok(());
325+
}
326+
327+
// Apply the context to the destination path
328+
set_selinux_security_context(to_path, Some(&context))
329+
}
330+
231331
#[cfg(test)]
232332
mod tests {
233333
use super::*;
@@ -295,18 +395,6 @@ mod tests {
295395
let result = set_selinux_security_context(path, Some(&invalid_context));
296396

297397
assert!(result.is_err());
298-
if let Err(err) = result {
299-
match err {
300-
SeLinuxError::ContextConversionFailure(ctx, msg) => {
301-
assert_eq!(ctx, "invalid\0context");
302-
assert!(
303-
msg.contains("nul byte"),
304-
"Error message should mention nul byte"
305-
);
306-
}
307-
_ => panic!("Expected ContextConversionFailure error but got: {}", err),
308-
}
309-
}
310398
}
311399

312400
#[test]
@@ -402,15 +490,139 @@ mod tests {
402490
let result = get_selinux_security_context(path);
403491

404492
assert!(result.is_err());
493+
}
494+
495+
#[test]
496+
fn test_contexts_differ() {
497+
let file1 = NamedTempFile::new().expect("Failed to create first tempfile");
498+
let file2 = NamedTempFile::new().expect("Failed to create second tempfile");
499+
let path1 = file1.path();
500+
let path2 = file2.path();
501+
502+
std::fs::write(path1, b"content for file 1").expect("Failed to write to first tempfile");
503+
std::fs::write(path2, b"content for file 2").expect("Failed to write to second tempfile");
504+
505+
if !is_selinux_enabled() {
506+
assert!(
507+
contexts_differ(path1, path2),
508+
"contexts_differ should return true when SELinux is not enabled"
509+
);
510+
return;
511+
}
512+
513+
let test_context = String::from("system_u:object_r:tmp_t:s0");
514+
let result1 = set_selinux_security_context(path1, Some(&test_context));
515+
let result2 = set_selinux_security_context(path2, Some(&test_context));
516+
517+
if result1.is_ok() && result2.is_ok() {
518+
assert!(
519+
!contexts_differ(path1, path2),
520+
"Contexts should not differ when the same context is set on both files"
521+
);
522+
523+
let different_context = String::from("system_u:object_r:user_tmp_t:s0");
524+
if set_selinux_security_context(path2, Some(&different_context)).is_ok() {
525+
assert!(
526+
contexts_differ(path1, path2),
527+
"Contexts should differ when different contexts are set"
528+
);
529+
}
530+
} else {
531+
println!(
532+
"Note: Couldn't set SELinux contexts to test differences. This is expected if the test doesn't have sufficient permissions."
533+
);
534+
assert!(
535+
contexts_differ(path1, path2),
536+
"Contexts should differ when different contexts are set"
537+
);
538+
}
539+
540+
let nonexistent_path = Path::new("/nonexistent/file/path");
541+
assert!(
542+
contexts_differ(path1, nonexistent_path),
543+
"contexts_differ should return true when one path doesn't exist"
544+
);
545+
}
546+
547+
#[test]
548+
fn test_preserve_security_context() {
549+
let source_file = NamedTempFile::new().expect("Failed to create source tempfile");
550+
let dest_file = NamedTempFile::new().expect("Failed to create destination tempfile");
551+
let source_path = source_file.path();
552+
let dest_path = dest_file.path();
553+
554+
std::fs::write(source_path, b"source content").expect("Failed to write to source tempfile");
555+
std::fs::write(dest_path, b"destination content")
556+
.expect("Failed to write to destination tempfile");
557+
558+
if !is_selinux_enabled() {
559+
let result = preserve_security_context(source_path, dest_path);
560+
assert!(
561+
result.is_err(),
562+
"preserve_security_context should fail when SELinux is not enabled"
563+
);
564+
return;
565+
}
566+
567+
let source_context = String::from("system_u:object_r:tmp_t:s0");
568+
let result = set_selinux_security_context(source_path, Some(&source_context));
569+
570+
if result.is_ok() {
571+
let preserve_result = preserve_security_context(source_path, dest_path);
572+
assert!(
573+
preserve_result.is_ok(),
574+
"Failed to preserve context: {:?}",
575+
preserve_result.err()
576+
);
577+
578+
assert!(
579+
!contexts_differ(source_path, dest_path),
580+
"Contexts should be the same after preserving"
581+
);
582+
} else {
583+
println!(
584+
"Note: Couldn't set SELinux context on source file to test preservation. This is expected if the test doesn't have sufficient permissions."
585+
);
586+
587+
let preserve_result = preserve_security_context(source_path, dest_path);
588+
assert!(preserve_result.is_err());
589+
}
590+
591+
let nonexistent_path = Path::new("/nonexistent/file/path");
592+
let result = preserve_security_context(nonexistent_path, dest_path);
593+
assert!(
594+
result.is_err(),
595+
"preserve_security_context should fail when source file doesn't exist"
596+
);
597+
598+
let result = preserve_security_context(source_path, nonexistent_path);
599+
assert!(
600+
result.is_err(),
601+
"preserve_security_context should fail when destination file doesn't exist"
602+
);
603+
}
604+
605+
#[test]
606+
fn test_preserve_security_context_empty_context() {
607+
let source_file = NamedTempFile::new().expect("Failed to create source tempfile");
608+
let dest_file = NamedTempFile::new().expect("Failed to create destination tempfile");
609+
let source_path = source_file.path();
610+
let dest_path = dest_file.path();
611+
612+
if !is_selinux_enabled() {
613+
return;
614+
}
615+
616+
let result = preserve_security_context(source_path, dest_path);
617+
405618
if let Err(err) = result {
406619
match err {
407-
SeLinuxError::FileOpenFailure(e) => {
408-
assert!(
409-
e.contains("No such file"),
410-
"Error should mention file not found"
411-
);
620+
SeLinuxError::ContextSetFailure(_, _) => {
621+
println!("Note: Could not set context due to permissions: {}", err);
622+
}
623+
unexpected => {
624+
panic!("Unexpected error: {}", unexpected);
412625
}
413-
_ => panic!("Expected FileOpenFailure error but got: {}", err),
414626
}
415627
}
416628
}

0 commit comments

Comments
 (0)