uu_runcon/
runcon.rs

1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5// spell-checker:ignore (vars) RFILE
6
7use clap::builder::ValueParser;
8use uucore::error::{UClapError, UError, UResult};
9
10use clap::{crate_version, Arg, ArgAction, Command};
11use selinux::{OpaqueSecurityContext, SecurityClass, SecurityContext};
12use uucore::{format_usage, help_about, help_section, help_usage};
13
14use std::borrow::Cow;
15use std::ffi::{CStr, CString, OsStr, OsString};
16use std::os::raw::c_char;
17use std::os::unix::ffi::OsStrExt;
18use std::{io, ptr};
19
20mod errors;
21
22use errors::error_exit_status;
23use errors::{Error, Result, RunconError};
24
25const ABOUT: &str = help_about!("runcon.md");
26const USAGE: &str = help_usage!("runcon.md");
27const DESCRIPTION: &str = help_section!("after help", "runcon.md");
28
29pub mod options {
30    pub const COMPUTE: &str = "compute";
31
32    pub const USER: &str = "user";
33    pub const ROLE: &str = "role";
34    pub const TYPE: &str = "type";
35    pub const RANGE: &str = "range";
36}
37
38#[uucore::main]
39pub fn uumain(args: impl uucore::Args) -> UResult<()> {
40    let config = uu_app();
41
42    let options = match parse_command_line(config, args) {
43        Ok(r) => r,
44        Err(r) => {
45            return Err(r);
46        }
47    };
48
49    match &options.mode {
50        CommandLineMode::Print => print_current_context().map_err(|e| RunconError::new(e).into()),
51        CommandLineMode::PlainContext { context, command } => {
52            get_plain_context(context)
53                .and_then(|ctx| set_next_exec_context(&ctx))
54                .map_err(RunconError::new)?;
55            // On successful execution, the following call never returns,
56            // and this process image is replaced.
57            execute_command(command, &options.arguments)
58        }
59        CommandLineMode::CustomContext {
60            compute_transition_context,
61            user,
62            role,
63            the_type,
64            range,
65            command,
66        } => {
67            match command {
68                Some(command) => {
69                    get_custom_context(
70                        *compute_transition_context,
71                        user.as_deref(),
72                        role.as_deref(),
73                        the_type.as_deref(),
74                        range.as_deref(),
75                        command,
76                    )
77                    .and_then(|ctx| set_next_exec_context(&ctx))
78                    .map_err(RunconError::new)?;
79                    // On successful execution, the following call never returns,
80                    // and this process image is replaced.
81                    execute_command(command, &options.arguments)
82                }
83                None => print_current_context().map_err(|e| RunconError::new(e).into()),
84            }
85        }
86    }
87}
88
89pub fn uu_app() -> Command {
90    Command::new(uucore::util_name())
91        .version(crate_version!())
92        .about(ABOUT)
93        .after_help(DESCRIPTION)
94        .override_usage(format_usage(USAGE))
95        .infer_long_args(true)
96        .arg(
97            Arg::new(options::COMPUTE)
98                .short('c')
99                .long(options::COMPUTE)
100                .help("Compute process transition context before modifying.")
101                .action(ArgAction::SetTrue),
102        )
103        .arg(
104            Arg::new(options::USER)
105                .short('u')
106                .long(options::USER)
107                .value_name("USER")
108                .help("Set user USER in the target security context.")
109                .value_parser(ValueParser::os_string()),
110        )
111        .arg(
112            Arg::new(options::ROLE)
113                .short('r')
114                .long(options::ROLE)
115                .value_name("ROLE")
116                .help("Set role ROLE in the target security context.")
117                .value_parser(ValueParser::os_string()),
118        )
119        .arg(
120            Arg::new(options::TYPE)
121                .short('t')
122                .long(options::TYPE)
123                .value_name("TYPE")
124                .help("Set type TYPE in the target security context.")
125                .value_parser(ValueParser::os_string()),
126        )
127        .arg(
128            Arg::new(options::RANGE)
129                .short('l')
130                .long(options::RANGE)
131                .value_name("RANGE")
132                .help("Set range RANGE in the target security context.")
133                .value_parser(ValueParser::os_string()),
134        )
135        .arg(
136            Arg::new("ARG")
137                .action(ArgAction::Append)
138                .value_parser(ValueParser::os_string())
139                .value_hint(clap::ValueHint::CommandName),
140        )
141        // Once "ARG" is parsed, everything after that belongs to it.
142        //
143        // This is not how POSIX does things, but this is how the GNU implementation
144        // parses its command line.
145        .trailing_var_arg(true)
146}
147
148#[derive(Debug)]
149enum CommandLineMode {
150    Print,
151
152    PlainContext {
153        context: OsString,
154        command: OsString,
155    },
156
157    CustomContext {
158        /// Compute process transition context before modifying.
159        compute_transition_context: bool,
160
161        /// Use the current context with the specified user.
162        user: Option<OsString>,
163
164        /// Use the current context with the specified role.
165        role: Option<OsString>,
166
167        /// Use the current context with the specified type.
168        the_type: Option<OsString>,
169
170        /// Use the current context with the specified range.
171        range: Option<OsString>,
172
173        // `command` can be `None`, in which case we're dealing with this syntax:
174        // runcon [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE]
175        //
176        // This syntax is undocumented, but it is accepted by the GNU implementation,
177        // so we do the same for compatibility.
178        command: Option<OsString>,
179    },
180}
181
182#[derive(Debug)]
183struct Options {
184    mode: CommandLineMode,
185    arguments: Vec<OsString>,
186}
187
188fn parse_command_line(config: Command, args: impl uucore::Args) -> UResult<Options> {
189    let matches = config.try_get_matches_from(args).with_exit_code(125)?;
190
191    let compute_transition_context = matches.get_flag(options::COMPUTE);
192
193    let mut args = matches
194        .get_many::<OsString>("ARG")
195        .unwrap_or_default()
196        .map(OsString::from);
197
198    if compute_transition_context
199        || matches.contains_id(options::USER)
200        || matches.contains_id(options::ROLE)
201        || matches.contains_id(options::TYPE)
202        || matches.contains_id(options::RANGE)
203    {
204        // runcon [-c] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] [COMMAND [args]]
205
206        let mode = CommandLineMode::CustomContext {
207            compute_transition_context,
208            user: matches.get_one::<OsString>(options::USER).map(Into::into),
209            role: matches.get_one::<OsString>(options::ROLE).map(Into::into),
210            the_type: matches.get_one::<OsString>(options::TYPE).map(Into::into),
211            range: matches.get_one::<OsString>(options::RANGE).map(Into::into),
212            command: args.next(),
213        };
214
215        Ok(Options {
216            mode,
217            arguments: args.collect(),
218        })
219    } else if let Some(context) = args.next() {
220        // runcon CONTEXT COMMAND [args]
221
222        args.next()
223            .ok_or_else(|| Box::new(Error::MissingCommand) as Box<dyn UError>)
224            .map(move |command| Options {
225                mode: CommandLineMode::PlainContext { context, command },
226                arguments: args.collect(),
227            })
228    } else {
229        // runcon
230
231        Ok(Options {
232            mode: CommandLineMode::Print,
233            arguments: Vec::default(),
234        })
235    }
236}
237
238fn print_current_context() -> Result<()> {
239    let op = "Getting security context of the current process";
240    let context = SecurityContext::current(false).map_err(|r| Error::from_selinux(op, r))?;
241
242    let context = context
243        .to_c_string()
244        .map_err(|r| Error::from_selinux(op, r))?;
245
246    if let Some(context) = context {
247        let context = context.as_ref().to_str()?;
248        println!("{context}");
249    } else {
250        println!();
251    }
252    Ok(())
253}
254
255fn set_next_exec_context(context: &OpaqueSecurityContext) -> Result<()> {
256    let c_context = context
257        .to_c_string()
258        .map_err(|r| Error::from_selinux("Creating new context", r))?;
259
260    let sc = SecurityContext::from_c_str(&c_context, false);
261
262    if sc.check() != Some(true) {
263        let ctx = OsStr::from_bytes(c_context.as_bytes());
264        let err = io::ErrorKind::InvalidInput.into();
265        return Err(Error::from_io1("Checking security context", ctx, err));
266    }
267
268    sc.set_for_next_exec()
269        .map_err(|r| Error::from_selinux("Setting new security context", r))
270}
271
272fn get_plain_context(context: &OsStr) -> Result<OpaqueSecurityContext> {
273    if selinux::kernel_support() == selinux::KernelSupport::Unsupported {
274        return Err(Error::SELinuxNotEnabled);
275    }
276
277    let c_context = os_str_to_c_string(context)?;
278
279    OpaqueSecurityContext::from_c_str(&c_context)
280        .map_err(|r| Error::from_selinux("Creating new context", r))
281}
282
283fn get_transition_context(command: &OsStr) -> Result<SecurityContext> {
284    // Generate context based on process transition.
285    let sec_class = SecurityClass::from_name("process")
286        .map_err(|r| Error::from_selinux("Getting process security class", r))?;
287
288    // Get context of file to be executed.
289    let file_context = match SecurityContext::of_path(command, true, false) {
290        Ok(Some(context)) => context,
291
292        Ok(None) => {
293            let err = io::Error::from_raw_os_error(libc::ENODATA);
294            return Err(Error::from_io1("getfilecon", command, err));
295        }
296
297        Err(r) => {
298            let op = "Getting security context of command file";
299            return Err(Error::from_selinux(op, r));
300        }
301    };
302
303    let process_context = SecurityContext::current(false)
304        .map_err(|r| Error::from_selinux("Getting security context of the current process", r))?;
305
306    // Compute result of process transition.
307    process_context
308        .of_labeling_decision(&file_context, sec_class, "")
309        .map_err(|r| Error::from_selinux("Computing result of process transition", r))
310}
311
312fn get_initial_custom_opaque_context(
313    compute_transition_context: bool,
314    command: &OsStr,
315) -> Result<OpaqueSecurityContext> {
316    let context = if compute_transition_context {
317        get_transition_context(command)?
318    } else {
319        SecurityContext::current(false).map_err(|r| {
320            Error::from_selinux("Getting security context of the current process", r)
321        })?
322    };
323
324    let c_context = context
325        .to_c_string()
326        .map_err(|r| Error::from_selinux("Getting security context", r))?
327        .unwrap_or_else(|| Cow::Owned(CString::default()));
328
329    OpaqueSecurityContext::from_c_str(c_context.as_ref())
330        .map_err(|r| Error::from_selinux("Creating new context", r))
331}
332
333fn get_custom_context(
334    compute_transition_context: bool,
335    user: Option<&OsStr>,
336    role: Option<&OsStr>,
337    the_type: Option<&OsStr>,
338    range: Option<&OsStr>,
339    command: &OsStr,
340) -> Result<OpaqueSecurityContext> {
341    use OpaqueSecurityContext as OSC;
342    type SetNewValueProc = fn(&OSC, &CStr) -> selinux::errors::Result<()>;
343
344    if selinux::kernel_support() == selinux::KernelSupport::Unsupported {
345        return Err(Error::SELinuxNotEnabled);
346    }
347
348    let osc = get_initial_custom_opaque_context(compute_transition_context, command)?;
349
350    let list: &[(Option<&OsStr>, SetNewValueProc, &'static str)] = &[
351        (user, OSC::set_user, "Setting security context user"),
352        (role, OSC::set_role, "Setting security context role"),
353        (the_type, OSC::set_type, "Setting security context type"),
354        (range, OSC::set_range, "Setting security context range"),
355    ];
356
357    for &(new_value, method, op) in list {
358        if let Some(new_value) = new_value {
359            let c_new_value = os_str_to_c_string(new_value)?;
360            method(&osc, &c_new_value).map_err(|r| Error::from_selinux(op, r))?;
361        }
362    }
363    Ok(osc)
364}
365
366/// The actual return type of this function should be `UResult<!>`.
367/// However, until the *never* type is stabilized, one way to indicate to the
368/// compiler the only valid return type is to say "if this returns, it will
369/// always return an error".
370fn execute_command(command: &OsStr, arguments: &[OsString]) -> UResult<()> {
371    let c_command = os_str_to_c_string(command).map_err(RunconError::new)?;
372
373    let argv_storage: Vec<CString> = arguments
374        .iter()
375        .map(AsRef::as_ref)
376        .map(os_str_to_c_string)
377        .collect::<Result<_>>()
378        .map_err(RunconError::new)?;
379
380    let mut argv: Vec<*const c_char> = Vec::with_capacity(arguments.len().saturating_add(2));
381    argv.push(c_command.as_ptr());
382    argv.extend(argv_storage.iter().map(AsRef::as_ref).map(CStr::as_ptr));
383    argv.push(ptr::null());
384
385    unsafe { libc::execvp(c_command.as_ptr(), argv.as_ptr()) };
386
387    let err = io::Error::last_os_error();
388    let exit_status = if err.kind() == io::ErrorKind::NotFound {
389        error_exit_status::NOT_FOUND
390    } else {
391        error_exit_status::COULD_NOT_EXECUTE
392    };
393
394    let err = Error::from_io1("Executing command", command, err);
395    Err(RunconError::with_code(exit_status, err).into())
396}
397
398fn os_str_to_c_string(s: &OsStr) -> Result<CString> {
399    CString::new(s.as_bytes())
400        .map_err(|_r| Error::from_io("CString::new()", io::ErrorKind::InvalidInput.into()))
401}