Skip to content

Commit 9379a68

Browse files
committed
context: pure rust code to check SELinux
1 parent d320122 commit 9379a68

File tree

2 files changed

+154
-7
lines changed

2 files changed

+154
-7
lines changed

src/find/matchers/context.rs

Lines changed: 154 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,167 @@
1+
//! SELinux context matcher
2+
//!
3+
//! This matcher will match files based on their
4+
//! SELinux context, only available on Linux.
5+
6+
#[cfg(target_os = "linux")]
7+
use nix::{libc::SELINUX_MAGIC, sys::statvfs::FsFlags};
8+
#[cfg(target_os = "linux")]
19
use std::{
210
error::Error,
3-
io::{stderr, Write},
11+
fs::File,
12+
io::{stderr, BufRead, BufReader, Read, Write},
413
};
514

15+
#[cfg(target_os = "linux")]
616
use super::{glob::Pattern, Matcher, MatcherIO, WalkEntry};
717

18+
#[cfg(target_os = "linux")]
19+
const XATTR_NAME_SELINUX: &str = "security.selinux";
20+
#[cfg(target_os = "linux")]
21+
const SELINUX_FS: &str = "selinuxfs";
22+
#[cfg(target_os = "linux")]
23+
const SELINUX_MNT: &str = "/sys/fs/selinux";
24+
#[cfg(target_os = "linux")]
25+
const OLD_SELINUX_MNT: &str = "/selinux";
26+
27+
/// Verify if SELinux mount point exists and is writable.
28+
///
29+
/// This function will return true if the SELinux mount point
30+
/// exists and is writable, false otherwise.
31+
#[cfg(target_os = "linux")]
32+
fn verify_selinux_mnt(mnt: &str) -> bool {
33+
use nix::sys::statfs::{statfs, FsType};
34+
use nix::sys::statvfs::statvfs;
35+
36+
let stat = match statfs(mnt) {
37+
Ok(stat) => stat,
38+
Err(_) => return false,
39+
};
40+
41+
if stat.filesystem_type() == FsType(SELINUX_MAGIC) {
42+
match statvfs(mnt) {
43+
Ok(stat) => {
44+
if stat.flags().contains(FsFlags::ST_RDONLY) {
45+
return false;
46+
}
47+
return true;
48+
}
49+
Err(_) => return false,
50+
}
51+
}
52+
false
53+
}
54+
55+
/// Check if SELinux filesystem exists.
56+
///
57+
/// This function will try to open the `/proc/filesystems` file and
58+
/// check if the SELinux filesystem is listed.
59+
#[cfg(target_os = "linux")]
60+
fn selinuxfs_exists() -> bool {
61+
let fp = match File::open("/proc/filesystems") {
62+
Ok(f) => f,
63+
Err(_) => return true, // Fail as if it exists
64+
};
65+
66+
let reader = BufReader::new(fp);
67+
for line in reader.lines() {
68+
if let Ok(line) = line {
69+
if line.contains(SELINUX_FS) {
70+
return true;
71+
}
72+
}
73+
}
74+
false
75+
}
76+
77+
/// Get SELinux mount point.
78+
#[cfg(target_os = "linux")]
79+
fn get_selinux_mnt() -> Option<String> {
80+
if verify_selinux_mnt(SELINUX_MNT) {
81+
return Some(SELINUX_MNT.to_string());
82+
}
83+
84+
if verify_selinux_mnt(OLD_SELINUX_MNT) {
85+
return Some(OLD_SELINUX_MNT.to_string());
86+
}
87+
88+
if !selinuxfs_exists() {
89+
return None;
90+
}
91+
92+
let fp = match File::open("/proc/mounts") {
93+
Ok(f) => f,
94+
Err(_) => return None,
95+
};
96+
97+
let reader = BufReader::new(fp);
98+
for line in reader.lines() {
99+
if let Ok(line) = line {
100+
let mut parts = line.splitn(3, ' ');
101+
if let (Some(_), Some(mnt), Some(fs_type)) = (parts.next(), parts.next(), parts.next())
102+
{
103+
if fs_type.starts_with(SELINUX_FS) {
104+
if verify_selinux_mnt(mnt) {
105+
return Some(mnt.to_string());
106+
}
107+
}
108+
}
109+
}
110+
}
111+
None
112+
}
113+
114+
/// Check if SELinux is enforced.
115+
#[cfg(target_os = "linux")]
116+
fn get_selinux_enforced() -> Result<bool, Box<dyn Error>> {
117+
let mnt = match get_selinux_mnt() {
118+
Some(mnt) => mnt,
119+
None => return Ok(false),
120+
};
121+
122+
let path = format!("{mnt}/enforce");
123+
let mut fd = match File::open(path) {
124+
Ok(f) => f,
125+
Err(_) => return Ok(false),
126+
};
127+
128+
let mut buf = String::with_capacity(20);
129+
if fd.read_to_string(&mut buf).is_err() {
130+
return Ok(false);
131+
}
132+
let enforce = i8::from_str_radix(&buf, 10)?;
133+
134+
Ok(enforce != 0)
135+
}
136+
137+
/// Matcher for SELinux context.
8138
pub struct ContextMatcher {
139+
#[cfg(target_os = "linux")]
9140
pattern: Pattern,
10141
}
11142

12143
impl ContextMatcher {
144+
#[cfg(target_os = "linux")]
13145
pub fn new(pattern: &str) -> Result<Self, Box<dyn Error>> {
146+
if !get_selinux_enforced()? {
147+
return Err(From::from("SELinux is not enabled"));
148+
}
149+
14150
let pattern = Pattern::new(pattern, false);
15151

16152
Ok(Self { pattern })
17153
}
154+
155+
#[cfg(not(target_os = "linux"))]
156+
pub fn new(_pattern: &str) -> Result<Self, Box<dyn Error>> {
157+
Self {}
158+
}
18159
}
19160

20161
impl Matcher for ContextMatcher {
162+
#[cfg(target_os = "linux")]
21163
fn matches(&self, path: &WalkEntry, _: &mut MatcherIO) -> bool {
22-
let attr = match xattr::get(path.path(), "security.selinux") {
164+
let attr = match xattr::get(path.path(), XATTR_NAME_SELINUX) {
23165
Ok(attr) => match attr {
24166
Some(attr) => attr,
25167
None => {
@@ -34,10 +176,19 @@ impl Matcher for ContextMatcher {
34176
let selinux_ctx = match String::from_utf8(attr) {
35177
Ok(selinux_ctx) => selinux_ctx,
36178
Err(e) => {
37-
writeln!(&mut stderr(), "Failed to convert SELinux context to UTF-8: {e}").unwrap();
179+
writeln!(
180+
&mut stderr(),
181+
"Failed to convert SELinux context to UTF-8: {e}"
182+
)
183+
.unwrap();
38184
return false;
39185
}
40186
};
41187
return self.pattern.matches(&selinux_ctx);
42188
}
189+
190+
#[cfg(not(target_os = "linux"))]
191+
fn matches(&self, _: &WalkEntry, _: &mut MatcherIO) -> bool {
192+
false
193+
}
43194
}

src/find/matchers/mod.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ use chrono::{DateTime, Datelike, NaiveDateTime, Utc};
6464
use context::ContextMatcher;
6565
use fs::FileSystemMatcher;
6666
use ls::Ls;
67-
use std::fs::exists;
6867
use std::{
6968
error::Error,
7069
fs::{File, Metadata},
@@ -778,9 +777,6 @@ fn build_matcher_tree(
778777
Some(PermMatcher::new(args[i])?.into_box())
779778
}
780779
"-context" => {
781-
if !exists("/sys/fs/selinux/enforce").unwrap_or(false) {
782-
return Err(From::from(format!("SELinux is not enabled")));
783-
}
784780
if i >= args.len() - 1 {
785781
return Err(From::from(format!("missing argument to {}", args[i])));
786782
}

0 commit comments

Comments
 (0)