Skip to content

Commit f74950b

Browse files
committed
mknod: implement selinux support
+ improve the option management a bit
1 parent 77c4ba5 commit f74950b

File tree

4 files changed

+159
-20
lines changed

4 files changed

+159
-20
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ feat_selinux = [
5151
"id/selinux",
5252
"ls/selinux",
5353
"mkdir/selinux",
54+
"mknod/selinux",
5455
"stat/selinux",
5556
"selinux",
5657
"feat_require_selinux",

src/uu/mknod/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ clap = { workspace = true }
2323
libc = { workspace = true }
2424
uucore = { workspace = true, features = ["mode"] }
2525

26+
[features]
27+
selinux = ["uucore/selinux"]
28+
2629
[[bin]]
2730
name = "mknod"
2831
path = "src/main.rs"

src/uu/mknod/src/mknod.rs

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
// spell-checker:ignore (ToDO) parsemode makedev sysmacros perror IFBLK IFCHR IFIFO
77

8-
use clap::{Arg, ArgMatches, Command, value_parser};
8+
use clap::{Arg, ArgAction, Command, value_parser};
99
use libc::{S_IFBLK, S_IFCHR, S_IFIFO, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR};
1010
use libc::{dev_t, mode_t};
1111
use std::ffi::CString;
@@ -20,6 +20,15 @@ const AFTER_HELP: &str = help_section!("after help", "mknod.md");
2020

2121
const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
2222

23+
mod options {
24+
pub const MODE: &str = "mode";
25+
pub const TYPE: &str = "type";
26+
pub const MAJOR: &str = "major";
27+
pub const MINOR: &str = "minor";
28+
pub const SELINUX: &str = "z";
29+
pub const CONTEXT: &str = "context";
30+
}
31+
2332
#[inline(always)]
2433
fn makedev(maj: u64, min: u64) -> dev_t {
2534
// pick up from <sys/sysmacros.h>
@@ -38,18 +47,33 @@ enum FileType {
3847
Fifo,
3948
}
4049

50+
/// Configuration for directory creation.
51+
pub struct Config<'a> {
52+
/// Mode
53+
pub mode: mode_t,
54+
55+
/// dev
56+
pub dev: dev_t,
57+
58+
/// Set SELinux security context.
59+
pub set_selinux_context: bool,
60+
61+
/// Specific SELinux context.
62+
pub context: Option<&'a String>,
63+
}
64+
4165
#[cfg(unix)]
42-
fn _mknod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 {
66+
fn _mknod(file_name: &str, config: Config) -> i32 {
4367
let c_str = CString::new(file_name).expect("Failed to convert to CString");
4468

4569
// the user supplied a mode
46-
let set_umask = mode & MODE_RW_UGO != MODE_RW_UGO;
70+
let set_umask = config.mode & MODE_RW_UGO != MODE_RW_UGO;
4771

4872
unsafe {
4973
// store prev umask
5074
let last_umask = if set_umask { libc::umask(0) } else { 0 };
5175

52-
let errno = libc::mknod(c_str.as_ptr(), mode, dev);
76+
let errno = libc::mknod(c_str.as_ptr(), config.mode, config.dev);
5377

5478
// set umask back to original value
5579
if set_umask {
@@ -62,16 +86,27 @@ fn _mknod(file_name: &str, mode: mode_t, dev: dev_t) -> i32 {
6286
// shows the error from the mknod syscall
6387
libc::perror(c_str.as_ptr());
6488
}
89+
90+
// Apply SELinux context if requested
91+
#[cfg(feature = "selinux")]
92+
if config.set_selinux_context {
93+
if let Err(e) = uucore::selinux::set_selinux_security_context(
94+
std::path::Path::new(file_name),
95+
config.context,
96+
) {
97+
// if it fails, delete the file
98+
let _ = std::fs::remove_dir(file_name);
99+
eprintln!("failed to set SELinux security context: {}", e);
100+
return 1;
101+
}
102+
}
103+
65104
errno
66105
}
67106
}
68107

69108
#[uucore::main]
70109
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
71-
// Linux-specific options, not implemented
72-
// opts.optflag("Z", "", "set the SELinux security context to default type");
73-
// opts.optopt("", "context", "like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX");
74-
75110
let matches = uu_app().try_get_matches_from(args)?;
76111

77112
let mode = get_mode(&matches).map_err(|e| USimpleError::new(1, e))?;
@@ -81,32 +116,50 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
81116
.expect("Missing argument 'NAME'");
82117

83118
let file_type = matches.get_one::<FileType>("type").unwrap();
119+
// Extract the SELinux related flags and options
120+
let set_selinux_context = matches.get_flag(options::SELINUX);
121+
let context = matches.get_one::<String>(options::CONTEXT);
122+
123+
let mut config = Config {
124+
mode,
125+
dev: 0,
126+
set_selinux_context: set_selinux_context || context.is_some(),
127+
context,
128+
};
84129

85130
if *file_type == FileType::Fifo {
86-
if matches.contains_id("major") || matches.contains_id("minor") {
131+
if matches.contains_id(options::MAJOR) || matches.contains_id(options::MINOR) {
87132
Err(UUsageError::new(
88133
1,
89134
"Fifos do not have major and minor device numbers.",
90135
))
91136
} else {
92-
let exit_code = _mknod(file_name, S_IFIFO | mode, 0);
137+
config.mode = S_IFIFO | mode;
138+
let exit_code = _mknod(file_name, config);
93139
set_exit_code(exit_code);
94140
Ok(())
95141
}
96142
} else {
97143
match (
98-
matches.get_one::<u64>("major"),
99-
matches.get_one::<u64>("minor"),
144+
matches.get_one::<u64>(options::MAJOR),
145+
matches.get_one::<u64>(options::MINOR),
100146
) {
101147
(_, None) | (None, _) => Err(UUsageError::new(
102148
1,
103149
"Special files require major and minor device numbers.",
104150
)),
105151
(Some(&major), Some(&minor)) => {
106152
let dev = makedev(major, minor);
153+
config.dev = dev;
107154
let exit_code = match file_type {
108-
FileType::Block => _mknod(file_name, S_IFBLK | mode, dev),
109-
FileType::Character => _mknod(file_name, S_IFCHR | mode, dev),
155+
FileType::Block => {
156+
config.mode |= S_IFBLK;
157+
_mknod(file_name, config)
158+
}
159+
FileType::Character => {
160+
config.mode |= S_IFCHR;
161+
_mknod(file_name, config)
162+
}
110163
FileType::Fifo => {
111164
unreachable!("file_type was validated to be only block or character")
112165
}
@@ -126,7 +179,7 @@ pub fn uu_app() -> Command {
126179
.about(ABOUT)
127180
.infer_long_args(true)
128181
.arg(
129-
Arg::new("mode")
182+
Arg::new(options::MODE)
130183
.short('m')
131184
.long("mode")
132185
.value_name("MODE")
@@ -140,24 +193,33 @@ pub fn uu_app() -> Command {
140193
.value_hint(clap::ValueHint::AnyPath),
141194
)
142195
.arg(
143-
Arg::new("type")
196+
Arg::new(options::TYPE)
144197
.value_name("TYPE")
145198
.help("type of the new file (b, c, u or p)")
146199
.required(true)
147200
.value_parser(parse_type),
148201
)
149202
.arg(
150-
Arg::new("major")
151-
.value_name("MAJOR")
203+
Arg::new(options::MAJOR)
204+
.value_name(options::MAJOR)
152205
.help("major file type")
153206
.value_parser(value_parser!(u64)),
154207
)
155208
.arg(
156-
Arg::new("minor")
157-
.value_name("MINOR")
209+
Arg::new(options::MINOR)
210+
.value_name(options::MINOR)
158211
.help("minor file type")
159212
.value_parser(value_parser!(u64)),
160213
)
214+
.arg(
215+
Arg::new(options::SELINUX)
216+
.short('Z')
217+
.help("set SELinux security context of each created directory to the default type")
218+
.action(ArgAction::SetTrue),
219+
)
220+
.arg(Arg::new(options::CONTEXT).long(options::CONTEXT).value_name("CTX").help(
221+
"like -Z, or if CTX is specified then set the SELinux or SMACK security context to CTX",
222+
))
161223
}
162224

163225
fn get_mode(matches: &ArgMatches) -> Result<mode_t, String> {

tests/by-util/test_mknod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
//
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
5+
6+
// spell-checker:ignore getfattr nconfined
7+
58
use uutests::new_ucmd;
69
use uutests::util::TestScenario;
710
use uutests::util_name;
@@ -131,3 +134,73 @@ fn test_mknod_invalid_mode() {
131134
.code_is(1)
132135
.stderr_contains("invalid mode");
133136
}
137+
138+
#[test]
139+
#[cfg(feature = "feat_selinux")]
140+
fn test_mknod_selinux() {
141+
use std::process::Command;
142+
let ts = TestScenario::new(util_name!());
143+
let at = &ts.fixtures;
144+
let dest = "test_file";
145+
let args = ["-Z", "--context=unconfined_u:object_r:user_tmp_t:s0"];
146+
for arg in args {
147+
ts.ucmd()
148+
.arg(arg)
149+
.arg("-m")
150+
.arg("a=r")
151+
.arg(dest)
152+
.arg("p")
153+
.succeeds();
154+
assert!(ts.fixtures.is_fifo("test_file"));
155+
assert!(ts.fixtures.metadata("test_file").permissions().readonly());
156+
157+
let getfattr_output = Command::new("getfattr")
158+
.arg(at.plus_as_string(dest))
159+
.arg("-n")
160+
.arg("security.selinux")
161+
.output()
162+
.expect("Failed to run `getfattr` on the destination file");
163+
println!("{:?}", getfattr_output);
164+
assert!(
165+
getfattr_output.status.success(),
166+
"getfattr did not run successfully: {}",
167+
String::from_utf8_lossy(&getfattr_output.stderr)
168+
);
169+
170+
let stdout = String::from_utf8_lossy(&getfattr_output.stdout);
171+
assert!(
172+
stdout.contains("unconfined_u"),
173+
"Expected '{}' not found in getfattr output:\n{}",
174+
"foo",
175+
stdout
176+
);
177+
at.remove(&at.plus_as_string(dest));
178+
}
179+
}
180+
181+
#[test]
182+
#[cfg(feature = "feat_selinux")]
183+
fn test_mknod_selinux_invalid() {
184+
let scene = TestScenario::new(util_name!());
185+
let at = &scene.fixtures;
186+
let dest = "orig";
187+
188+
let args = [
189+
"--context=a",
190+
"--context=unconfined_u:object_r:user_tmp_t:s0:a",
191+
"--context=nconfined_u:object_r:user_tmp_t:s0",
192+
];
193+
for arg in args {
194+
new_ucmd!()
195+
.arg(arg)
196+
.arg("-m")
197+
.arg("a=r")
198+
.arg(dest)
199+
.arg("p")
200+
.fails()
201+
.stderr_contains("Failed to");
202+
if at.file_exists(dest) {
203+
at.remove(dest);
204+
}
205+
}
206+
}

0 commit comments

Comments
 (0)