1
+ use std:: borrow:: Cow ;
1
2
use std:: io:: Write ;
2
3
use std:: os:: fd:: AsRawFd ;
3
4
use std:: os:: unix:: process:: CommandExt ;
@@ -230,8 +231,11 @@ pub(crate) fn has_security_selinux(root: &Dir, path: &Utf8Path) -> Result<SELinu
230
231
}
231
232
}
232
233
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.
233
238
pub ( crate ) fn set_security_selinux_path ( root : & Dir , path : & Utf8Path , label : & [ u8 ] ) -> Result < ( ) > {
234
- // TODO: avoid hardcoding a max size here
235
239
let fdpath = format ! ( "/proc/self/fd/{}/" , root. as_raw_fd( ) ) ;
236
240
let fdpath = & Path :: new ( & fdpath) . join ( path) ;
237
241
rustix:: fs:: lsetxattr (
@@ -243,6 +247,9 @@ pub(crate) fn set_security_selinux_path(root: &Dir, path: &Utf8Path, label: &[u8
243
247
Ok ( ( ) )
244
248
}
245
249
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.
246
253
pub ( crate ) fn ensure_labeled (
247
254
root : & Dir ,
248
255
path : & Utf8Path ,
@@ -251,44 +258,97 @@ pub(crate) fn ensure_labeled(
251
258
) -> Result < SELinuxLabelState > {
252
259
let r = has_security_selinux ( root, path) ?;
253
260
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) ?;
258
262
}
259
263
Ok ( r)
260
264
}
261
265
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 (
263
270
root : & Dir ,
264
- destname : impl AsRef < Utf8Path > ,
271
+ metadata : & Metadata ,
272
+ path : & Utf8Path ,
265
273
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 ,
268
290
) -> 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
+ ) ?;
271
300
272
- for ent in root. read_dir ( destname. as_ref ( ) ) ? {
301
+ // Relabel all children
302
+ for ent in root. read_dir ( & path) ? {
273
303
let ent = ent?;
274
304
let metadata = ent. metadata ( ) ?;
275
305
let name = ent. file_name ( ) ;
276
306
let name = name
277
307
. to_str ( )
278
308
. 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
+ }
280
314
281
315
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) ?;
283
318
} 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) ;
285
328
}
286
- dest_path. pop ( ) ;
287
329
}
288
330
289
331
Ok ( ( ) )
290
332
}
291
333
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
+
292
352
/// A wrapper for creating a directory, also optionally setting a SELinux label.
293
353
/// The provided `skip` parameter is a device/inode that we will ignore (and not traverse).
294
354
pub ( crate ) fn ensure_dir_labeled_recurse (
0 commit comments