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