1+ use std:: borrow:: Cow ;
12use std:: io:: Write ;
23use std:: os:: fd:: AsRawFd ;
34use std:: os:: unix:: process:: CommandExt ;
@@ -230,8 +231,11 @@ pub(crate) fn has_security_selinux(root: &Dir, path: &Utf8Path) -> Result<SELinu
230231 }
231232}
232233
234+ /// Directly set the `security.selinux` extended atttribute on the target
235+ /// path. Symbolic links are not followed for the target.
236+ ///
237+ /// Note that this API will work even if SELinux is disabled.
233238pub ( crate ) fn set_security_selinux_path ( root : & Dir , path : & Utf8Path , label : & [ u8 ] ) -> Result < ( ) > {
234- // TODO: avoid hardcoding a max size here
235239 let fdpath = format ! ( "/proc/self/fd/{}/" , root. as_raw_fd( ) ) ;
236240 let fdpath = & Path :: new ( & fdpath) . join ( path) ;
237241 rustix:: fs:: lsetxattr (
@@ -243,6 +247,9 @@ pub(crate) fn set_security_selinux_path(root: &Dir, path: &Utf8Path, label: &[u8
243247 Ok ( ( ) )
244248}
245249
250+ /// Given a policy, ensure the target file path has a security.selinux label.
251+ /// If the path already is labeled, this function is a no-op, even if
252+ /// the policy would default to a different label.
246253pub ( crate ) fn ensure_labeled (
247254 root : & Dir ,
248255 path : & Utf8Path ,
@@ -251,44 +258,97 @@ pub(crate) fn ensure_labeled(
251258) -> Result < SELinuxLabelState > {
252259 let r = has_security_selinux ( root, path) ?;
253260 if matches ! ( r, SELinuxLabelState :: Unlabeled ) {
254- let abspath = Utf8Path :: new ( "/" ) . join ( & path) ;
255- let label = require_label ( policy, & abspath, metadata. mode ( ) ) ?;
256- tracing:: trace!( "Setting label for {path} to {label}" ) ;
257- set_security_selinux_path ( root, & path, label. as_bytes ( ) ) ?;
261+ relabel ( root, metadata, path, None , policy) ?;
258262 }
259263 Ok ( r)
260264}
261265
262- pub ( crate ) fn ensure_dir_labeled_recurse_policy (
266+ /// Given the policy, relabel the target file or directory.
267+ /// Optionally, an override for the path can be provided
268+ /// to set the label as if the target has that filename.
269+ pub ( crate ) fn relabel (
263270 root : & Dir ,
264- destname : impl AsRef < Utf8Path > ,
271+ metadata : & Metadata ,
272+ path : & Utf8Path ,
265273 as_path : Option < & Utf8Path > ,
266- mode : rustix:: fs:: Mode ,
267- policy : Option < & ostree:: SePolicy > ,
274+ policy : & ostree:: SePolicy ,
275+ ) -> Result < ( ) > {
276+ assert ! ( !path. starts_with( "/" ) ) ;
277+ let as_path = as_path
278+ . map ( Cow :: Borrowed )
279+ . unwrap_or_else ( || Utf8Path :: new ( "/" ) . join ( path) . into ( ) ) ;
280+ let label = require_label ( policy, & as_path, metadata. mode ( ) ) ?;
281+ tracing:: trace!( "Setting label for {path} to {label}" ) ;
282+ set_security_selinux_path ( root, & path, label. as_bytes ( ) )
283+ }
284+
285+ pub ( crate ) fn relabel_recurse_inner (
286+ root : & Dir ,
287+ path : & mut Utf8PathBuf ,
288+ mut as_path : Option < & mut Utf8PathBuf > ,
289+ policy : & ostree:: SePolicy ,
268290) -> Result < ( ) > {
269- ensure_dir_labeled ( root, & destname, as_path, mode, policy) ?;
270- let mut dest_path = destname. as_ref ( ) . to_path_buf ( ) ;
291+ // Relabel this directory
292+ let self_meta = root. dir_metadata ( ) ?;
293+ relabel (
294+ root,
295+ & self_meta,
296+ path,
297+ as_path. as_ref ( ) . map ( |p| p. as_path ( ) ) ,
298+ policy,
299+ ) ?;
271300
272- for ent in root. read_dir ( destname. as_ref ( ) ) ? {
301+ // Relabel all children
302+ for ent in root. read_dir ( & path) ? {
273303 let ent = ent?;
274304 let metadata = ent. metadata ( ) ?;
275305 let name = ent. file_name ( ) ;
276306 let name = name
277307 . to_str ( )
278308 . ok_or_else ( || anyhow:: anyhow!( "Invalid non-UTF-8 filename: {name:?}" ) ) ?;
279- dest_path. push ( name) ;
309+ // Extend both copies of the path
310+ path. push ( name) ;
311+ if let Some ( p) = as_path. as_mut ( ) {
312+ p. push ( name) ;
313+ }
280314
281315 if metadata. is_dir ( ) {
282- ensure_dir_labeled_recurse_policy ( root, & dest_path, as_path, mode, policy) ?;
316+ let as_path = as_path. as_deref_mut ( ) ;
317+ relabel_recurse_inner ( root, path, as_path, policy) ?;
283318 } else {
284- ensure_dir_labeled ( root, & dest_path, as_path, mode, policy) ?
319+ let as_path = as_path. as_ref ( ) . map ( |p| p. as_path ( ) ) ;
320+ relabel ( root, & metadata, & path, as_path, policy) ?
321+ }
322+ // Trim what we added to the path
323+ let r = path. pop ( ) ;
324+ assert ! ( r) ;
325+ if let Some ( p) = as_path. as_mut ( ) {
326+ let r = p. pop ( ) ;
327+ assert ! ( r) ;
285328 }
286- dest_path. pop ( ) ;
287329 }
288330
289331 Ok ( ( ) )
290332}
291333
334+ /// Recursively relabel the target directory.
335+ pub ( crate ) fn relabel_recurse (
336+ root : & Dir ,
337+ path : impl AsRef < Utf8Path > ,
338+ as_path : Option < & Utf8Path > ,
339+ policy : & ostree:: SePolicy ,
340+ ) -> Result < ( ) > {
341+ let mut path = path. as_ref ( ) . to_owned ( ) ;
342+ // This path must be relative, as we access via cap-std
343+ assert ! ( !path. starts_with( "/" ) ) ;
344+ let mut as_path = as_path. map ( |v| v. to_owned ( ) ) ;
345+ // But the as_path must be absolute, if provided
346+ if let Some ( as_path) = as_path. as_deref ( ) {
347+ assert ! ( as_path. starts_with( "/" ) ) ;
348+ }
349+ relabel_recurse_inner ( root, & mut path, as_path. as_mut ( ) , policy)
350+ }
351+
292352/// A wrapper for creating a directory, also optionally setting a SELinux label.
293353/// The provided `skip` parameter is a device/inode that we will ignore (and not traverse).
294354pub ( crate ) fn ensure_dir_labeled_recurse (
0 commit comments