From f1d317147b3c8f132164f560a20ec157e7eb5e68 Mon Sep 17 00:00:00 2001 From: Jan Scheer Date: Sat, 19 Jun 2021 20:26:28 +0200 Subject: [PATCH] id: add support for showing SELinux context (--context/-Z) --- .github/workflows/CICD.yml | 5 +- .github/workflows/GNU.yml | 9 +- Cargo.lock | 164 ++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/uu/id/Cargo.toml | 1 + src/uu/id/src/id.rs | 57 +++++++++++-- tests/by-util/test_id.rs | 101 +++++++++++++++++++++-- 7 files changed, 315 insertions(+), 23 deletions(-) diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index fcaddd310..6626c2e1e 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -11,7 +11,7 @@ env: PROJECT_NAME: coreutils PROJECT_DESC: "Core universal (cross-platform) utilities" PROJECT_AUTH: "uutils" - RUST_MIN_SRV: "1.43.1" ## v1.43.0 + RUST_MIN_SRV: "1.51.0" ## v1.43.0 RUST_COV_SRV: "2020-08-01" ## (~v1.47.0) supported rust version for code coverage; (date required/used by 'coverage') ## !maint: refactor when code coverage support is included in the stable channel on: [push, pull_request] @@ -222,7 +222,7 @@ jobs: - name: "Run make build" shell: bash run: | - sudo apt-get -y update ; sudo apt-get -y install python3-sphinx; + sudo apt-get -y update ; sudo apt-get -y install python3-sphinx libselinux1 libselinux1-dev ; make build build: @@ -260,6 +260,7 @@ jobs: esac case '${{ matrix.job.os }}' in macos-latest) brew install coreutils ;; # needed for testing + ubuntu-latest) sudo apt-get -y update ; sudo apt-get -y install libselinux1 libselinux1-dev ;; # TODO: probably redundant here esac - name: Initialize workflow variables id: vars diff --git a/.github/workflows/GNU.yml b/.github/workflows/GNU.yml index 1f9250900..570217865 100644 --- a/.github/workflows/GNU.yml +++ b/.github/workflows/GNU.yml @@ -45,7 +45,14 @@ jobs: - name: Run GNU tests shell: bash run: | - bash uutils/util/run-gnu-test.sh + # bash uutils/util/run-gnu-test.sh # TODO: revert after testing + bash uutils/util/run-gnu-test.sh tests/id/context.sh + bash uutils/util/run-gnu-test.sh tests/id/no-context.sh + bash uutils/util/run-gnu-test.sh tests/id/smack.sh + bash uutils/util/run-gnu-test.sh tests/id/uid.sh + bash uutils/util/run-gnu-test.sh tests/id/setgid.sh + bash uutils/util/run-gnu-test.sh tests/id/zero.sh + bash uutils/util/run-gnu-test.sh tests/id/gnu-zero-uids.sh - name: Extract tests info shell: bash run: | diff --git a/Cargo.lock b/Cargo.lock index a059c1cd5..d70b94de6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,6 +71,29 @@ dependencies = [ "compare", ] +[[package]] +name = "bindgen" +version = "0.58.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8523b410d7187a43085e7e064416ea32ded16bd0a4e6fc025e21616d01258f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "clap", + "env_logger 0.8.4", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote 1.0.9", + "regex", + "rustc-hash", + "shlex", + "which", +] + [[package]] name = "bit-set" version = "0.5.2" @@ -142,6 +165,15 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -167,6 +199,17 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "clang-sys" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +dependencies = [ + "glob 0.3.0", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.33.3" @@ -228,6 +271,7 @@ dependencies = [ "rand 0.7.3", "regex", "rlimit", + "selinux", "sha1", "tempfile", "textwrap", @@ -578,6 +622,19 @@ dependencies = [ "regex", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -727,6 +784,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "if_rust_version" version = "1.0.0" @@ -782,12 +845,28 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" +[[package]] +name = "libloading" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +dependencies = [ + "cfg-if 1.0.0", + "winapi 0.3.9", +] + [[package]] name = "locale" version = "0.2.2" @@ -919,6 +998,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr 2.4.0", + "version_check", +] + [[package]] name = "ntapi" version = "0.3.6" @@ -982,9 +1071,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "once_cell" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "onig" @@ -1084,6 +1173,12 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "pkg-config" version = "0.3.19" @@ -1175,7 +1270,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" dependencies = [ - "env_logger", + "env_logger 0.7.1", "log", "rand 0.7.3", "rand_core 0.5.1", @@ -1364,6 +1459,12 @@ dependencies = [ "redox_syscall 0.2.8", ] +[[package]] +name = "reference-counted-singleton" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e03aec30be874d0786379a6ee483c653c58dd6863ad4ed873e697ed968f9358" + [[package]] name = "regex" version = "1.5.4" @@ -1418,6 +1519,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "same-file" version = "1.0.6" @@ -1433,6 +1540,32 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "selinux" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e36fd4d2d189b51696bcff8d45a8defe6d9c3a353a6b1449ccc46d22a81d8e" +dependencies = [ + "bitflags", + "libc", + "once_cell", + "reference-counted-singleton", + "selinux-sys", + "thiserror", +] + +[[package]] +name = "selinux-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9dc2e3e97d611bf255de85a72a385565d1f6340bc6fc6fd6402555b54382a9f" +dependencies = [ + "bindgen", + "cc", + "dunce", + "walkdir", +] + [[package]] name = "semver" version = "0.9.0" @@ -1479,6 +1612,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "shlex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" + [[package]] name = "signal-hook" version = "0.1.17" @@ -1599,6 +1738,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "termion" version = "1.5.6" @@ -2073,6 +2221,7 @@ name = "uu_id" version = "0.0.6" dependencies = [ "clap", + "selinux", "uucore", "uucore_procs", ] @@ -2830,6 +2979,15 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "which" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724" +dependencies = [ + "libc", +] + [[package]] name = "wild" version = "2.0.4" diff --git a/Cargo.toml b/Cargo.toml index 804c5f978..06e6ed505 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -352,6 +352,7 @@ unindent = "0.1" uucore = { version=">=0.0.8", package="uucore", path="src/uucore", features=["entries", "process"] } walkdir = "2.2" atty = "0.2" +selinux = "0.1.1" [target.'cfg(unix)'.dev-dependencies] rlimit = "0.4.0" diff --git a/src/uu/id/Cargo.toml b/src/uu/id/Cargo.toml index 308d6089d..a14422924 100644 --- a/src/uu/id/Cargo.toml +++ b/src/uu/id/Cargo.toml @@ -18,6 +18,7 @@ path = "src/id.rs" clap = "2.33" uucore = { version=">=0.0.8", package="uucore", path="../../uucore", features=["entries", "process"] } uucore_procs = { version=">=0.0.5", package="uucore_procs", path="../../uucore_procs" } +selinux = "0.1.1" [[bin]] name = "id" diff --git a/src/uu/id/src/id.rs b/src/uu/id/src/id.rs index 9037745eb..0e29536e3 100644 --- a/src/uu/id/src/id.rs +++ b/src/uu/id/src/id.rs @@ -26,7 +26,7 @@ // * Help text based on BSD's `id` manpage and GNU's `id` manpage. // -// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag testsuite +// spell-checker:ignore (ToDO) asid auditid auditinfo auid cstr egid emod euid getaudit getlogin gflag nflag pline rflag termid uflag gsflag zflag cflag testsuite #![allow(non_camel_case_types)] #![allow(dead_code)] @@ -35,6 +35,8 @@ extern crate uucore; use clap::{crate_version, App, Arg}; +#[cfg(target_os = "linux")] +use selinux::{self, KernelSupport, SecurityContext}; use std::ffi::CStr; use uucore::entries::{self, Group, Locate, Passwd}; pub use uucore::libc; @@ -93,6 +95,8 @@ struct State { gsflag: bool, // --groups rflag: bool, // --real zflag: bool, // --zero + cflag: bool, // --context + selinux_supported: bool, ids: Option, // The behavior for calling GNU's `id` and calling GNU's `id $USER` is similar but different. // * The SELinux context is only displayed without a specified user. @@ -147,6 +151,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::OPT_GROUP) .short("g") .long(options::OPT_GROUP) + .conflicts_with(options::OPT_EFFECTIVE_USER) .help("Display only the effective group ID as a number"), ) .arg( @@ -156,6 +161,7 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .conflicts_with_all(&[ options::OPT_GROUP, options::OPT_EFFECTIVE_USER, + options::OPT_CONTEXT, options::OPT_HUMAN_READABLE, options::OPT_PASSWORD, options::OPT_AUDIT, @@ -207,7 +213,8 @@ pub fn uumain(args: impl uucore::Args) -> i32 { Arg::with_name(options::OPT_CONTEXT) .short("Z") .long(options::OPT_CONTEXT) - .help("NotImplemented: print only the security context of the process"), + .conflicts_with_all(&[options::OPT_GROUP, options::OPT_EFFECTIVE_USER]) + .help("print only the security context of the process"), ) .arg( Arg::with_name(options::ARG_USERS) @@ -229,6 +236,12 @@ pub fn uumain(args: impl uucore::Args) -> i32 { gsflag: matches.is_present(options::OPT_GROUPS), rflag: matches.is_present(options::OPT_REAL_ID), zflag: matches.is_present(options::OPT_ZERO), + cflag: matches.is_present(options::OPT_CONTEXT), + + #[cfg(not(target_os = "linux"))] + selinux_supported: false, + #[cfg(target_os = "linux")] + selinux_supported: selinux::kernel_support() != KernelSupport::Unsupported, user_specified: !users.is_empty(), ids: None, }; @@ -238,13 +251,16 @@ pub fn uumain(args: impl uucore::Args) -> i32 { !(state.uflag || state.gflag || state.gsflag) }; - if (state.nflag || state.rflag) && default_format { + if (state.nflag || state.rflag) && default_format && !state.cflag { crash!(1, "cannot print only names or real IDs in default format"); } - if (state.zflag) && default_format { + if state.zflag && default_format && !state.cflag { // NOTE: GNU testsuite "id/zero.sh" needs this stderr output: crash!(1, "option --zero not permitted in default format"); } + if state.user_specified && state.cflag { + crash!(1, "cannot print security context when user specified"); + } let delimiter = { if state.zflag { @@ -262,6 +278,23 @@ pub fn uumain(args: impl uucore::Args) -> i32 { }; let mut exit_code = 0; + if state.cflag { + if state.selinux_supported { + // print SElinux context and exit + #[cfg(target_os = "linux")] + if let Ok(context) = SecurityContext::current(false) { + let bytes = context.as_bytes(); + print!("{}{}", String::from_utf8_lossy(bytes), line_ending); + } else { + // print error because `cflag` was explicitly requested + crash!(1, "can't get process context"); + } + return exit_code; + } else { + crash!(1, "--context (-Z) works only on an SELinux-enabled kernel"); + } + } + for i in 0..=users.len() { let possible_pw = if !state.user_specified { None @@ -518,11 +551,17 @@ fn id_print(state: &State, groups: Vec) { .join(",") ); - // NOTE: (SELinux NotImplemented) placeholder: - // if !state.user_specified { - // // print SElinux context (does not depend on "-Z") - // print!(" context={}", get_selinux_contexts().join(":")); - // } + #[cfg(target_os = "linux")] + if state.selinux_supported + && !state.user_specified + && std::env::var_os("POSIXLY_CORRECT").is_none() + { + // print SElinux context (does not depend on "-Z") + if let Ok(context) = SecurityContext::current(false) { + let bytes = context.as_bytes(); + print!(" context={}", String::from_utf8_lossy(bytes)); + } + } } #[cfg(not(target_os = "linux"))] diff --git a/tests/by-util/test_id.rs b/tests/by-util/test_id.rs index b4b929a2c..bbb9533af 100644 --- a/tests/by-util/test_id.rs +++ b/tests/by-util/test_id.rs @@ -52,14 +52,6 @@ fn test_id_no_specified_user() { let exp_result = unwrap_or_return!(expected_result(&[])); let mut _exp_stdout = exp_result.stdout_str().to_string(); - #[cfg(target_os = "linux")] - { - // NOTE: (SELinux NotImplemented) strip 'context' part from exp_stdout: - if let Some(context_offset) = exp_result.stdout_str().find(" context=") { - _exp_stdout.replace_range(context_offset.._exp_stdout.len() - 1, ""); - } - } - result .stdout_is(_exp_stdout) .stderr_is(exp_result.stderr_str()) @@ -425,6 +417,99 @@ fn test_id_zero() { } } +#[test] +#[cfg(target_os = "linux")] +fn test_id_context() { + use selinux::{self, KernelSupport}; + if selinux::kernel_support() == KernelSupport::Unsupported { + println!( + "{}: test skipped: Kernel has no support for SElinux context", + UUTILS_INFO + ); + return; + } + let scene = TestScenario::new(util_name!()); + for c_flag in &["-Z", "--context"] { + scene + .ucmd() + .args(&[c_flag]) + .succeeds() + .stdout_only(unwrap_or_return!(expected_result(&[c_flag])).stdout_str()); + for &z_flag in &["-z", "--zero"] { + let args = [c_flag, z_flag]; + scene + .ucmd() + .args(&args) + .succeeds() + .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); + for &opt1 in &["--name", "--real"] { + // id: cannot print only names or real IDs in default format + let args = [opt1, c_flag]; + scene + .ucmd() + .args(&args) + .succeeds() + .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); + let args = [opt1, c_flag, z_flag]; + scene + .ucmd() + .args(&args) + .succeeds() + .stdout_only(unwrap_or_return!(expected_result(&args)).stdout_str()); + for &opt2 in &["--user", "--group", "--groups"] { + // u/g/G n/r z Z + // for now, we print clap's standard response for "conflicts_with" instead of: + // id: cannot print "only" of more than one choice + let args = [opt2, c_flag, opt1]; + let _result = scene.ucmd().args(&args).fails(); + // let exp_result = unwrap_or_return!(expected_result(&args)); + // result + // .stdout_is(exp_result.stdout_str()) + // .stderr_is(exp_result.stderr_str()) + // .code_is(exp_result.code()); + } + } + for &opt2 in &["--user", "--group", "--groups"] { + // u/g/G z Z + // for now, we print clap's standard response for "conflicts_with" instead of: + // id: cannot print "only" of more than one choice + let args = [opt2, c_flag]; + let _result = scene.ucmd().args(&args).fails(); + // let exp_result = unwrap_or_return!(expected_result(&args)); + // result + // .stdout_is(exp_result.stdout_str()) + // .stderr_is(exp_result.stderr_str()) + // .code_is(exp_result.code()); + } + } + } +} + +#[test] +#[cfg(unix)] +fn test_id_no_specified_user_posixly() { + // gnu/tests/id/no-context.sh + + let scene = TestScenario::new(util_name!()); + let result = scene.ucmd().env("POSIXLY_CORRECT", "1").succeeds(); + assert!(!result.stdout_str().contains("context=")); + + #[cfg(target_os = "linux")] + { + use selinux::{self, KernelSupport}; + if selinux::kernel_support() == KernelSupport::Unsupported { + println!( + "{}: test skipped: Kernel has no support for SElinux context", + UUTILS_INFO + ); + return; + } else { + let result = scene.ucmd().succeeds(); + assert!(result.stdout_str().contains("context=")); + } + } +} + fn check_coreutil_version(util_name: &str, version_expected: &str) -> String { // example: // $ id --version | head -n 1