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" ) ]
19use 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" ) ]
616use 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.
8138pub struct ContextMatcher {
139+ #[ cfg( target_os = "linux" ) ]
9140 pattern : Pattern ,
10141}
11142
12143impl 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
20161impl 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}
0 commit comments