Skip to content

Commit 8c4d69b

Browse files
authored
Merge pull request #7845 from sylvestre/selinux-error
set_selinux_security_context should return an Error, not String
2 parents c8dbd18 + c177362 commit 8c4d69b

File tree

6 files changed

+169
-75
lines changed

6 files changed

+169
-75
lines changed

src/uu/mkdir/src/mkdir.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,10 +298,7 @@ fn create_dir(path: &Path, is_parent: bool, config: &Config) -> UResult<()> {
298298
if let Err(e) = uucore::selinux::set_selinux_security_context(path, config.context)
299299
{
300300
let _ = std::fs::remove_dir(path);
301-
return Err(USimpleError::new(
302-
1,
303-
format!("failed to set SELinux security context: {}", e),
304-
));
301+
return Err(USimpleError::new(1, e.to_string()));
305302
}
306303
}
307304

src/uucore/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ proc-info = ["tty", "walkdir"]
114114
quoting-style = []
115115
ranges = []
116116
ringbuffer = []
117-
selinux = ["dep:selinux"]
117+
selinux = ["dep:selinux", "thiserror"]
118118
signals = []
119119
sum = [
120120
"digest",

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

Lines changed: 164 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,34 @@
66
use std::path::Path;
77

88
use selinux::SecurityContext;
9+
use thiserror::Error;
910

10-
#[derive(Debug)]
11-
pub enum Error {
11+
#[derive(Debug, Error)]
12+
pub enum SeLinuxError {
13+
#[error("SELinux is not enabled on this system")]
1214
SELinuxNotEnabled,
13-
FileOpenFailure,
14-
ContextRetrievalFailure,
15-
ContextConversionFailure,
15+
16+
#[error("Failed to open the file: {0}")]
17+
FileOpenFailure(String),
18+
19+
#[error("Failed to retrieve the security context: {0}")]
20+
ContextRetrievalFailure(String),
21+
22+
#[error("Failed to set default file creation context to '{0}': {1}")]
23+
ContextSetFailure(String, String),
24+
25+
#[error("Failed to set default file creation context to '{0}': {1}")]
26+
ContextConversionFailure(String, String),
1627
}
1728

18-
impl From<Error> for i32 {
19-
fn from(error: Error) -> i32 {
29+
impl From<SeLinuxError> for i32 {
30+
fn from(error: SeLinuxError) -> i32 {
2031
match error {
21-
Error::SELinuxNotEnabled => 1,
22-
Error::FileOpenFailure => 2,
23-
Error::ContextRetrievalFailure => 3,
24-
Error::ContextConversionFailure => 4,
32+
SeLinuxError::SELinuxNotEnabled => 1,
33+
SeLinuxError::FileOpenFailure(_) => 2,
34+
SeLinuxError::ContextRetrievalFailure(_) => 3,
35+
SeLinuxError::ContextSetFailure(_, _) => 4,
36+
SeLinuxError::ContextConversionFailure(_, _) => 5,
2537
}
2638
}
2739
}
@@ -76,31 +88,39 @@ pub fn is_selinux_enabled() -> bool {
7688
/// eprintln!("Failed to set context: {}", err);
7789
/// }
7890
/// ```
79-
pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Result<(), String> {
91+
pub fn set_selinux_security_context(
92+
path: &Path,
93+
context: Option<&String>,
94+
) -> Result<(), SeLinuxError> {
8095
if !is_selinux_enabled() {
81-
return Err("SELinux is not enabled on this system".to_string());
96+
return Err(SeLinuxError::SELinuxNotEnabled);
8297
}
8398

8499
if let Some(ctx_str) = context {
85100
// Create a CString from the provided context string
86-
let c_context = std::ffi::CString::new(ctx_str.as_str())
87-
.map_err(|_| "Invalid context string (contains null bytes)".to_string())?;
101+
let c_context = std::ffi::CString::new(ctx_str.as_str()).map_err(|e| {
102+
SeLinuxError::ContextConversionFailure(ctx_str.to_string(), e.to_string())
103+
})?;
88104

89105
// Convert the CString into an SELinux security context
90-
let security_context = selinux::OpaqueSecurityContext::from_c_str(&c_context)
91-
.map_err(|e| format!("Failed to create security context: {}", e))?;
106+
let security_context =
107+
selinux::OpaqueSecurityContext::from_c_str(&c_context).map_err(|e| {
108+
SeLinuxError::ContextConversionFailure(ctx_str.to_string(), e.to_string())
109+
})?;
92110

93111
// Set the provided security context on the specified path
94112
SecurityContext::from_c_str(
95-
&security_context.to_c_string().map_err(|e| e.to_string())?,
113+
&security_context.to_c_string().map_err(|e| {
114+
SeLinuxError::ContextConversionFailure(ctx_str.to_string(), e.to_string())
115+
})?,
96116
false,
97117
)
98118
.set_for_path(path, false, false)
99-
.map_err(|e| format!("Failed to set context: {}", e))
119+
.map_err(|e| SeLinuxError::ContextSetFailure(ctx_str.to_string(), e.to_string()))
100120
} else {
101121
// If no context provided, set the default SELinux context for the path
102122
SecurityContext::set_default_for_path(path)
103-
.map_err(|e| format!("Failed to set default context: {}", e))
123+
.map_err(|e| SeLinuxError::ContextSetFailure(String::new(), e.to_string()))
104124
}
105125
}
106126

@@ -117,17 +137,18 @@ pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Re
117137
///
118138
/// * `Ok(String)` - The SELinux context string if successfully retrieved. Returns an empty
119139
/// string if no context was found.
120-
/// * `Err(Error)` - An error variant indicating the type of failure:
121-
/// - `Error::SELinuxNotEnabled` - SELinux is not enabled on the system.
122-
/// - `Error::FileOpenFailure` - Failed to open the specified file.
123-
/// - `Error::ContextRetrievalFailure` - Failed to retrieve the security context.
124-
/// - `Error::ContextConversionFailure` - Failed to convert the security context to a string.
140+
/// * `Err(SeLinuxError)` - An error variant indicating the type of failure:
141+
/// - `SeLinuxError::SELinuxNotEnabled` - SELinux is not enabled on the system.
142+
/// - `SeLinuxError::FileOpenFailure` - Failed to open the specified file.
143+
/// - `SeLinuxError::ContextRetrievalFailure` - Failed to retrieve the security context.
144+
/// - `SeLinuxError::ContextConversionFailure` - Failed to convert the security context to a string.
145+
/// - `SeLinuxError::ContextSetFailure` - Failed to set the security context.
125146
///
126147
/// # Examples
127148
///
128149
/// ```
129150
/// use std::path::Path;
130-
/// use uucore::selinux::{get_selinux_security_context, Error};
151+
/// use uucore::selinux::{get_selinux_security_context, SeLinuxError};
131152
///
132153
/// // Get the SELinux context for a file
133154
/// match get_selinux_security_context(Path::new("/path/to/file")) {
@@ -138,29 +159,30 @@ pub fn set_selinux_security_context(path: &Path, context: Option<&String>) -> Re
138159
/// println!("SELinux context: {}", context);
139160
/// }
140161
/// },
141-
/// Err(Error::SELinuxNotEnabled) => println!("SELinux is not enabled on this system"),
142-
/// Err(Error::FileOpenFailure) => println!("Failed to open the file"),
143-
/// Err(Error::ContextRetrievalFailure) => println!("Failed to retrieve the security context"),
144-
/// Err(Error::ContextConversionFailure) => println!("Failed to convert the security context to a string"),
162+
/// Err(SeLinuxError::SELinuxNotEnabled) => println!("SELinux is not enabled on this system"),
163+
/// Err(SeLinuxError::FileOpenFailure(e)) => println!("Failed to open the file: {}", e),
164+
/// Err(SeLinuxError::ContextRetrievalFailure(e)) => println!("Failed to retrieve the security context: {}", e),
165+
/// Err(SeLinuxError::ContextConversionFailure(ctx, e)) => println!("Failed to convert context '{}': {}", ctx, e),
166+
/// Err(SeLinuxError::ContextSetFailure(ctx, e)) => println!("Failed to set context '{}': {}", ctx, e),
145167
/// }
146168
/// ```
147-
pub fn get_selinux_security_context(path: &Path) -> Result<String, Error> {
169+
pub fn get_selinux_security_context(path: &Path) -> Result<String, SeLinuxError> {
148170
if !is_selinux_enabled() {
149-
return Err(Error::SELinuxNotEnabled);
171+
return Err(SeLinuxError::SELinuxNotEnabled);
150172
}
151173

152-
let f = std::fs::File::open(path).map_err(|_| Error::FileOpenFailure)?;
174+
let f = std::fs::File::open(path).map_err(|e| SeLinuxError::FileOpenFailure(e.to_string()))?;
153175

154176
// Get the security context of the file
155177
let context = match SecurityContext::of_file(&f, false) {
156178
Ok(Some(ctx)) => ctx,
157179
Ok(None) => return Ok(String::new()), // No context found, return empty string
158-
Err(_) => return Err(Error::ContextRetrievalFailure),
180+
Err(e) => return Err(SeLinuxError::ContextRetrievalFailure(e.to_string())),
159181
};
160182

161183
let context_c_string = context
162184
.to_c_string()
163-
.map_err(|_| Error::ContextConversionFailure)?;
185+
.map_err(|e| SeLinuxError::ContextConversionFailure(String::new(), e.to_string()))?;
164186

165187
if let Some(c_str) = context_c_string {
166188
// Convert the C string to a Rust String
@@ -180,29 +202,50 @@ mod tests {
180202
let tmpfile = NamedTempFile::new().expect("Failed to create tempfile");
181203
let path = tmpfile.path();
182204

183-
let result = set_selinux_security_context(path, None);
205+
if !is_selinux_enabled() {
206+
let result = set_selinux_security_context(path, None);
207+
assert!(result.is_err(), "Expected error when SELinux is disabled");
208+
match result.unwrap_err() {
209+
SeLinuxError::SELinuxNotEnabled => {
210+
// This is the expected error when SELinux is not enabled
211+
}
212+
err => panic!("Expected SELinuxNotEnabled error but got: {}", err),
213+
}
214+
return;
215+
}
184216

185-
if result.is_ok() {
186-
// SELinux enabled and successfully set default context
187-
assert!(true, "Successfully set SELinux context");
188-
} else {
189-
let err = result.unwrap_err();
190-
let valid_errors = [
191-
"SELinux is not enabled on this system",
192-
&format!(
193-
"Failed to set default context: selinux_lsetfilecon_default() failed on path '{}'",
194-
path.display()
195-
),
196-
];
217+
let default_result = set_selinux_security_context(path, None);
218+
assert!(
219+
default_result.is_ok(),
220+
"Failed to set default context: {:?}",
221+
default_result.err()
222+
);
223+
224+
let context = get_selinux_security_context(path).expect("Failed to get context");
225+
assert!(
226+
!context.is_empty(),
227+
"Expected non-empty context after setting default context"
228+
);
229+
230+
let test_context = String::from("system_u:object_r:tmp_t:s0");
231+
let explicit_result = set_selinux_security_context(path, Some(&test_context));
232+
233+
if explicit_result.is_ok() {
234+
let new_context = get_selinux_security_context(path)
235+
.expect("Failed to get context after setting explicit context");
197236

198237
assert!(
199-
valid_errors.contains(&err.as_str()),
200-
"Unexpected error message: {}",
201-
err
238+
new_context.contains("tmp_t"),
239+
"Expected context to contain 'tmp_t', but got: {}",
240+
new_context
241+
);
242+
} else {
243+
println!(
244+
"Note: Could not set explicit context {:?}",
245+
explicit_result.err()
202246
);
203247
}
204248
}
205-
206249
#[test]
207250
fn test_invalid_context_string_error() {
208251
let tmpfile = NamedTempFile::new().expect("Failed to create tempfile");
@@ -213,10 +256,18 @@ mod tests {
213256
let result = set_selinux_security_context(path, Some(&invalid_context));
214257

215258
assert!(result.is_err());
216-
assert_eq!(
217-
result.unwrap_err(),
218-
"Invalid context string (contains null bytes)"
219-
);
259+
if let Err(err) = result {
260+
match err {
261+
SeLinuxError::ContextConversionFailure(ctx, msg) => {
262+
assert_eq!(ctx, "invalid\0context");
263+
assert!(
264+
msg.contains("nul byte"),
265+
"Error message should mention nul byte"
266+
);
267+
}
268+
_ => panic!("Expected ContextConversionFailure error but got: {}", err),
269+
}
270+
}
220271
}
221272

222273
#[test]
@@ -243,17 +294,56 @@ mod tests {
243294
let result = get_selinux_security_context(path);
244295

245296
if result.is_ok() {
246-
println!("Retrieved SELinux context: {}", result.unwrap());
297+
let context = result.unwrap();
298+
println!("Retrieved SELinux context: {}", context);
299+
300+
assert!(
301+
is_selinux_enabled(),
302+
"Got a successful context result but SELinux is not enabled"
303+
);
304+
305+
if !context.is_empty() {
306+
assert!(
307+
context.contains(':'),
308+
"SELinux context '{}' doesn't match expected format",
309+
context
310+
);
311+
}
247312
} else {
248313
let err = result.unwrap_err();
249314

250-
// Valid error types
251315
match err {
252-
Error::SELinuxNotEnabled => assert!(true, "SELinux not supported"),
253-
Error::ContextRetrievalFailure => assert!(true, "Context retrieval failure"),
254-
Error::ContextConversionFailure => assert!(true, "Context conversion failure"),
255-
Error::FileOpenFailure => {
256-
panic!("File open failure occurred despite file being created")
316+
SeLinuxError::SELinuxNotEnabled => {
317+
assert!(
318+
!is_selinux_enabled(),
319+
"Got SELinuxNotEnabled error, but is_selinux_enabled() returned true"
320+
);
321+
}
322+
SeLinuxError::ContextRetrievalFailure(e) => {
323+
assert!(
324+
is_selinux_enabled(),
325+
"Got ContextRetrievalFailure when SELinux is not enabled"
326+
);
327+
assert!(!e.is_empty(), "Error message should not be empty");
328+
println!("Context retrieval failure: {}", e);
329+
}
330+
SeLinuxError::ContextConversionFailure(ctx, e) => {
331+
assert!(
332+
is_selinux_enabled(),
333+
"Got ContextConversionFailure when SELinux is not enabled"
334+
);
335+
assert!(!e.is_empty(), "Error message should not be empty");
336+
println!("Context conversion failure for '{}': {}", ctx, e);
337+
}
338+
SeLinuxError::ContextSetFailure(ctx, e) => {
339+
assert!(false);
340+
}
341+
SeLinuxError::FileOpenFailure(e) => {
342+
assert!(
343+
Path::new(path).exists(),
344+
"File open failure occurred despite file being created: {}",
345+
e
346+
);
257347
}
258348
}
259349
}
@@ -266,9 +356,16 @@ mod tests {
266356
let result = get_selinux_security_context(path);
267357

268358
assert!(result.is_err());
269-
assert!(
270-
matches!(result.unwrap_err(), Error::FileOpenFailure),
271-
"Expected file open error for nonexistent file"
272-
);
359+
if let Err(err) = result {
360+
match err {
361+
SeLinuxError::FileOpenFailure(e) => {
362+
assert!(
363+
e.contains("No such file"),
364+
"Error should mention file not found"
365+
);
366+
}
367+
_ => panic!("Expected FileOpenFailure error but got: {}", err),
368+
}
369+
}
273370
}
274371
}

tests/by-util/test_mkdir.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ fn test_selinux_invalid() {
411411
.arg(at.plus_as_string(dest))
412412
.fails()
413413
.no_stdout()
414-
.stderr_contains("failed to set SELinux security context:");
414+
.stderr_contains("Failed to set default file creation context to 'testtest':");
415415
// invalid context, so, no directory
416416
assert!(!at.dir_exists(dest));
417417
}

tests/by-util/test_mkfifo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ fn test_mkfifo_selinux_invalid() {
160160
.arg(arg)
161161
.arg(dest)
162162
.fails()
163-
.stderr_contains("Failed to");
163+
.stderr_contains("failed to");
164164
if at.file_exists(dest) {
165165
at.remove(dest);
166166
}

tests/by-util/test_mknod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ fn test_mknod_selinux_invalid() {
187187
.arg(dest)
188188
.arg("p")
189189
.fails()
190-
.stderr_contains("Failed to");
190+
.stderr_contains("failed to");
191191
if at.file_exists(dest) {
192192
at.remove(dest);
193193
}

0 commit comments

Comments
 (0)