1#![forbid(unsafe_code)]
14
15use std::{
20 convert::Infallible,
21 env,
22 ffi::{CStr, CString},
23 fs::Permissions,
24 os::{
25 fd::AsFd,
26 unix::{ffi::OsStringExt, fs::PermissionsExt},
27 },
28};
29
30use libc::mode_t;
31use nix::{
32 errno::Errno,
33 fcntl::{fcntl, openat, AtFlags, FcntlArg, OFlag, SealFlag},
34 libc::{
35 c_int, c_uint, F_SEAL_FUTURE_WRITE, F_SEAL_GROW, F_SEAL_SEAL, F_SEAL_SHRINK, F_SEAL_WRITE,
36 MFD_ALLOW_SEALING, MFD_CLOEXEC, MFD_EXEC, MFD_NOEXEC_SEAL,
37 },
38 sys::stat::Mode,
39 unistd::execveat,
40};
41
42use crate::{
43 compat::{fstatx, MFdFlags, STATX_TYPE},
44 config::ENV_SKIP_SCMP,
45 confine::secure_getenv,
46 err::err2no,
47 fd::SafeOwnedFd,
48 io::ReadFd,
49 lookup::FileType,
50 proc::proc_open,
51 retry::retry_on_eintr,
52};
53
54const DEFAULT_MEMFD_NAME: &CStr = c"syd";
56
57const F_SEAL_EXEC: c_int = 0x0020;
60
61const OPTIONS: SealOptions = SealOptions::new().close_on_exec(true).executable(true);
62
63pub fn ensure_sealed() -> Result<(), Errno> {
87 let fd_proc = proc_open(None)?;
89
90 #[expect(clippy::disallowed_methods)]
92 let fd = openat(
93 fd_proc,
94 c"self/exe",
95 OFlag::O_RDONLY | OFlag::O_NOCTTY | OFlag::O_CLOEXEC,
96 Mode::empty(),
97 )
98 .map(SafeOwnedFd::from)?;
99
100 if OPTIONS.is_sealed(&fd) {
101 Ok(())
103 } else {
104 Err(SealedCommand::new(fd)?.exec().unwrap_err())
106 }
107}
108
109pub struct SealedCommand {
111 memfd: SafeOwnedFd,
112}
113
114impl SealedCommand {
115 pub fn new<Fd>(mut program: Fd) -> Result<Self, Errno>
127 where
128 Fd: ReadFd,
129 {
130 let statx = retry_on_eintr(|| fstatx(&program, STATX_TYPE))?;
132 let ftype = FileType::from(mode_t::from(statx.stx_mode));
133 if !ftype.is_file() {
134 return Err(Errno::ENOEXEC);
135 }
136
137 let mut memfd = OPTIONS.create()?;
138 crate::io::copy(&mut program, &mut memfd)?;
139 OPTIONS.seal(&mut memfd)?;
140
141 Ok(Self { memfd })
142 }
143
144 pub fn exec(self) -> Result<Infallible, Errno> {
148 match env::var_os("RUST_BACKTRACE") {
153 Some(val) => env::set_var("SYD_RUST_BACKTRACE", val),
154 None => env::remove_var("SYD_RUST_BACKTRACE"),
155 };
156 if secure_getenv(ENV_SKIP_SCMP).is_none() {
157 env::set_var("RUST_BACKTRACE", "0");
158 }
159
160 let args = env::args_os()
162 .map(|arg| CString::new(arg.into_vec()).or(Err(Errno::EINVAL)))
163 .collect::<Result<Vec<CString>, Errno>>()?;
164
165 let envs = env::vars_os()
167 .map(|(k, v)| {
168 let mut bytes = k.into_vec();
169 bytes.push(b'=');
170 bytes.extend(v.into_vec());
171 CString::new(bytes).or(Err(Errno::EINVAL))
172 })
173 .collect::<Result<Vec<CString>, Errno>>()?;
174
175 execveat(self.memfd, c"", &args, &envs, AtFlags::AT_EMPTY_PATH)
176 }
177}
178
179macro_rules! set_flag {
180 ($flags:expr, $flag:expr, $value:expr) => {
181 if $value {
182 $flags |= $flag;
183 } else {
184 $flags &= !$flag;
185 }
186 };
187}
188
189macro_rules! seal {
190 (
191 $seal_ident:ident
192 $( { $( #[ $attr:meta ] )* } )? ,
193 $must_seal_ident:ident
194 $( { $( #[ $must_attr:meta ] )* } )? ,
195 $( ? $preflight:ident : )? $flag:expr,
196 $try_to:expr,
197 $default:expr
198 ) => {
199 #[doc = concat!("If `true`, try to ", $try_to, ".")]
200 #[doc = ""]
201 #[doc = "If `false`, also set"]
202 #[doc = concat!("[`SealOptions::", stringify!($must_seal_ident), "`]")]
203 #[doc = "to `false`."]
204 #[doc = ""]
205 #[doc = concat!("This flag is `", $default, "` by default.")]
206 $($( #[ $attr ] )*)?
207 pub const fn $seal_ident(mut self, $seal_ident: bool) -> SealOptions {
208 if true $( && self.$preflight() )? {
209 set_flag!(self.seal_flags, $flag, $seal_ident);
210 }
211 if !$seal_ident {
212 self.must_seal_flags &= !$flag;
213 }
214 self
215 }
216
217 #[doc = "If `true`, also set"]
218 #[doc = concat!("[`SealOptions::", stringify!($seal_ident), "`] to `true`")]
219 #[doc = "and ensure it is successful when [`SealOptions::seal`] is called."]
220 #[doc = ""]
221 #[doc = concat!("This flag is `", $default, "` by default.")]
222 $($( #[ $must_attr ] )*)?
223 pub const fn $must_seal_ident(mut self, $must_seal_ident: bool) -> SealOptions {
224 if $must_seal_ident {
225 self.seal_flags |= $flag;
226 }
227 set_flag!(self.must_seal_flags, $flag, $must_seal_ident);
228 self
229 }
230 };
231}
232
233#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
235#[must_use]
236pub struct SealOptions {
237 memfd_flags: c_uint,
238 seal_flags: c_int,
239 must_seal_flags: c_int,
240}
241
242impl Default for SealOptions {
243 fn default() -> Self {
244 Self::new()
245 }
246}
247
248impl SealOptions {
249 pub const fn new() -> Self {
265 Self {
266 memfd_flags: MFD_ALLOW_SEALING | MFD_CLOEXEC,
267 seal_flags: F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE,
268 must_seal_flags: F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE,
269 }
270 }
271
272 pub const fn close_on_exec(mut self, close_on_exec: bool) -> SealOptions {
280 set_flag!(self.memfd_flags, MFD_CLOEXEC, close_on_exec);
281 self
282 }
283
284 pub const fn executable(mut self, executable: bool) -> SealOptions {
313 self.memfd_flags = self.memfd_flags & !MFD_EXEC & !MFD_NOEXEC_SEAL
314 | if executable {
315 MFD_EXEC
316 } else {
317 MFD_NOEXEC_SEAL
318 };
319 self.seal_flags |= F_SEAL_EXEC;
320 self
321 }
322
323 const fn is_executable_set(&self) -> bool {
324 self.memfd_flags & (MFD_EXEC | MFD_NOEXEC_SEAL) != 0
325 }
326
327 seal!(
328 seal_seals,
329 must_seal_seals,
330 F_SEAL_SEAL,
331 "prevent further seals from being set on this file",
332 true
333 );
334 seal!(
335 seal_shrinking,
336 must_seal_shrinking,
337 F_SEAL_SHRINK,
338 "prevent shrinking this file",
339 true
340 );
341 seal!(
342 seal_growing,
343 must_seal_growing,
344 F_SEAL_GROW,
345 "prevent growing this file",
346 true
347 );
348 seal!(
349 seal_writing,
350 must_seal_writing,
351 F_SEAL_WRITE,
352 "prevent writing to this file",
353 true
354 );
355 seal!(
356 seal_future_writing {
357 #[doc = ""]
358 #[doc = "This requires at least Linux 5.1."]
359 },
360 must_seal_future_writing {
361 #[doc = ""]
362 #[doc = "This requires at least Linux 5.1."]
363 },
364 F_SEAL_FUTURE_WRITE,
365 "prevent directly writing to this file or creating new writable mappings, \
366 but allow writes to existing writable mappings",
367 false
368 );
369 seal!(
370 seal_executable {
371 #[doc = ""]
372 #[doc = "If [`SealOptions::executable`] has already been called,"]
373 #[doc = "this function does nothing."]
374 #[doc = ""]
375 #[doc = "This requires at least Linux 6.3."]
376 },
377 must_seal_executable {
378 #[doc = ""]
379 #[doc = "This requires at least Linux 6.3."]
380 },
381 ? seal_executable_preflight : F_SEAL_EXEC,
382 "prevent modifying the executable permission of the file",
383 false
384 );
385
386 const fn seal_executable_preflight(&self) -> bool {
387 !self.is_executable_set()
388 }
389
390 pub fn copy_and_seal<Fd>(&self, reader: &mut Fd) -> Result<SafeOwnedFd, Errno>
397 where
398 Fd: ReadFd,
399 {
400 let mut file = self.create()?;
401 crate::io::copy(reader, &mut file)?;
402 self.seal(&mut file)?;
403 Ok(file)
404 }
405
406 pub fn create(&self) -> Result<SafeOwnedFd, Errno> {
418 let fd = match memfd_create(DEFAULT_MEMFD_NAME, self.memfd_flags) {
419 Ok(fd) => fd,
420 Err(Errno::EINVAL) if self.is_executable_set() => {
421 memfd_create(
428 DEFAULT_MEMFD_NAME,
429 self.memfd_flags & !MFD_EXEC & !MFD_NOEXEC_SEAL,
430 )?
431 }
432 Err(errno) => return Err(errno),
433 };
434
435 if self.is_executable_set() {
436 let permissions = fd.metadata().map_err(|err| err2no(&err))?.permissions();
437 let new_permissions =
438 Permissions::from_mode(if self.memfd_flags & MFD_NOEXEC_SEAL != 0 {
439 permissions.mode() & !0o111
440 } else if self.memfd_flags & MFD_EXEC != 0 {
441 permissions.mode() | 0o111
442 } else {
443 return Ok(fd);
444 });
445 if permissions != new_permissions {
446 fd.set_permissions(new_permissions)
447 .map_err(|err| err2no(&err))?;
448 }
449 }
450
451 Ok(fd)
452 }
453
454 pub fn seal<Fd: AsFd>(&self, fd: Fd) -> Result<(), Errno> {
466 for group in [
469 F_SEAL_EXEC, F_SEAL_FUTURE_WRITE, F_SEAL_SEAL | F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE, ] {
473 match fcntl_add_seals(&fd, self.seal_flags & group) {
474 Ok(()) => {}
475 Err(Errno::EINVAL) => {}
476 Err(errno) => return Err(errno),
477 }
478 }
479
480 if self.is_sealed_inner(fd)? {
481 Ok(())
482 } else {
483 Err(Errno::EBADF)
484 }
485 }
486
487 pub fn is_sealed<Fd: AsFd>(&self, fd: Fd) -> bool {
492 self.is_sealed_inner(fd).unwrap_or(false)
493 }
494
495 fn is_sealed_inner<Fd: AsFd>(&self, fd: Fd) -> Result<bool, Errno> {
496 Ok(fcntl_get_seals(fd)? & self.must_seal_flags == self.must_seal_flags)
497 }
498}
499
500fn memfd_create(name: &CStr, flags: c_uint) -> Result<SafeOwnedFd, Errno> {
501 nix::sys::memfd::memfd_create(name, MFdFlags::from_bits_retain(flags).into())
502 .map(SafeOwnedFd::from)
503}
504
505fn fcntl_get_seals<Fd: AsFd>(fd: Fd) -> Result<c_int, Errno> {
506 fcntl(fd, FcntlArg::F_GET_SEALS)
507}
508
509fn fcntl_add_seals<Fd: AsFd>(fd: Fd, arg: c_int) -> Result<(), Errno> {
510 fcntl(fd, FcntlArg::F_ADD_SEALS(SealFlag::from_bits_retain(arg))).map(drop)
511}
512
513#[cfg(test)]
514mod test {
515 use std::{fs::File, os::unix::fs::PermissionsExt as _};
516
517 use super::*;
518
519 #[test]
520 fn new() {
521 let options = SealOptions {
522 memfd_flags: MFD_ALLOW_SEALING,
523 seal_flags: 0,
524 must_seal_flags: 0,
525 };
526 assert_eq!(
527 options
528 .close_on_exec(true)
529 .must_seal_seals(true)
530 .must_seal_shrinking(true)
531 .must_seal_growing(true)
532 .must_seal_writing(true)
533 .seal_future_writing(false)
534 .seal_executable(false),
535 SealOptions::new()
536 );
537 }
538
539 #[test]
540 fn flags() {
541 const ALL_SEALS: c_int = F_SEAL_SEAL
542 | F_SEAL_SHRINK
543 | F_SEAL_GROW
544 | F_SEAL_WRITE
545 | F_SEAL_FUTURE_WRITE
546 | F_SEAL_EXEC;
547
548 let mut options = SealOptions::new();
549 assert_eq!(options.memfd_flags & MFD_ALLOW_SEALING, MFD_ALLOW_SEALING);
550
551 assert_eq!(options.memfd_flags & MFD_CLOEXEC, MFD_CLOEXEC);
552 options = options.close_on_exec(false);
553 assert_eq!(options.memfd_flags & MFD_CLOEXEC, 0);
554 options = options.close_on_exec(true);
555 assert_eq!(options.memfd_flags & MFD_CLOEXEC, MFD_CLOEXEC);
556
557 assert_eq!(
558 options.seal_flags & ALL_SEALS,
559 ALL_SEALS & !F_SEAL_FUTURE_WRITE & !F_SEAL_EXEC
560 );
561 assert_eq!(
562 options.must_seal_flags & ALL_SEALS,
563 ALL_SEALS & !F_SEAL_FUTURE_WRITE & !F_SEAL_EXEC
564 );
565 options = options
566 .must_seal_future_writing(true)
567 .must_seal_executable(true);
568 assert_eq!(options.seal_flags & ALL_SEALS, ALL_SEALS);
569 assert_eq!(options.must_seal_flags & ALL_SEALS, ALL_SEALS);
570 options = options
572 .seal_seals(false)
573 .seal_shrinking(false)
574 .seal_growing(false)
575 .seal_writing(false)
576 .seal_future_writing(false)
577 .seal_executable(false);
578 assert_eq!(options.seal_flags & ALL_SEALS, 0);
579 assert_eq!(options.must_seal_flags & ALL_SEALS, 0);
580 options = options
582 .seal_seals(true)
583 .seal_shrinking(true)
584 .seal_growing(true)
585 .seal_writing(true)
586 .seal_future_writing(true)
587 .seal_executable(true);
588 assert_eq!(options.seal_flags & ALL_SEALS, ALL_SEALS);
589 assert_eq!(options.must_seal_flags & ALL_SEALS, 0);
590 options = options
592 .seal_seals(false)
593 .seal_shrinking(false)
594 .seal_growing(false)
595 .seal_writing(false)
596 .seal_future_writing(false)
597 .seal_executable(false);
598 assert_eq!(options.seal_flags & ALL_SEALS, 0);
599 assert_eq!(options.must_seal_flags & ALL_SEALS, 0);
600 options = options
601 .must_seal_seals(true)
602 .must_seal_shrinking(true)
603 .must_seal_growing(true)
604 .must_seal_writing(true)
605 .must_seal_future_writing(true)
606 .must_seal_executable(true);
607 assert_eq!(options.seal_flags & ALL_SEALS, ALL_SEALS);
608 assert_eq!(options.must_seal_flags & ALL_SEALS, ALL_SEALS);
609 options = options
611 .must_seal_seals(false)
612 .must_seal_shrinking(false)
613 .must_seal_growing(false)
614 .must_seal_writing(false)
615 .must_seal_future_writing(false)
616 .must_seal_executable(false);
617 assert_eq!(options.seal_flags & ALL_SEALS, ALL_SEALS);
618 assert_eq!(options.must_seal_flags & ALL_SEALS, 0);
619 }
620
621 #[test]
622 fn execute_flags() {
623 let mut options = SealOptions::new();
624 assert_eq!(options.seal_flags & F_SEAL_EXEC, 0);
625 options = options.seal_executable(true);
626 assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
627 options = options.seal_executable(false);
628 assert_eq!(options.seal_flags & F_SEAL_EXEC, 0);
629
630 for _ in 0..2 {
631 options = options.executable(true);
632 assert_eq!(options.memfd_flags & (MFD_EXEC | MFD_NOEXEC_SEAL), MFD_EXEC);
633 assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
634 options = options.seal_executable(false);
636 assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
637
638 options = options.executable(false);
639 assert_eq!(
640 options.memfd_flags & (MFD_EXEC | MFD_NOEXEC_SEAL),
641 MFD_NOEXEC_SEAL
642 );
643 assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
644 options = options.seal_executable(false);
646 assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
647 }
648
649 assert_eq!(options.must_seal_flags & F_SEAL_EXEC, 0);
650 options = options.must_seal_executable(true);
651 assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
652 assert_eq!(options.must_seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
653 options = options.seal_executable(false);
654 assert_eq!(options.seal_flags & F_SEAL_EXEC, F_SEAL_EXEC);
655 assert_eq!(options.must_seal_flags & F_SEAL_EXEC, 0);
656 }
657
658 #[test]
659 fn executable() {
660 let mut null = File::open("/dev/null").unwrap();
661 let file = SealOptions::new()
662 .executable(false)
663 .copy_and_seal(&mut null)
664 .unwrap();
665 assert_eq!(file.metadata().unwrap().permissions().mode() & 0o111, 0);
666
667 let file = SealOptions::new()
668 .executable(true)
669 .copy_and_seal(&mut null)
670 .unwrap();
671 assert_eq!(file.metadata().unwrap().permissions().mode() & 0o111, 0o111);
672 }
673}