diff --git a/Cargo.toml b/Cargo.toml index 80abc048..e498460d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ tempfile = "3.24.0" [workspace.lints.clippy] all = { level = "warn", priority = -1 } nursery = { level = "warn", priority = -1 } +style = { level = "warn", priority = -1 } option_if_let_else = "allow" missing_errors_doc = "allow" missing_const_for_fn = "allow" diff --git a/crates/tracexec-backend-ptrace/src/ptrace/tracer.rs b/crates/tracexec-backend-ptrace/src/ptrace/tracer.rs index 41754e35..da4b065e 100644 --- a/crates/tracexec-backend-ptrace/src/ptrace/tracer.rs +++ b/crates/tracexec-backend-ptrace/src/ptrace/tracer.rs @@ -11,6 +11,10 @@ use std::{ time::Duration, }; +use color_eyre::eyre::{ + bail, + eyre, +}; use enumflags2::BitFlags; use nix::{ errno::Errno, @@ -118,6 +122,14 @@ pub trait BuildPtraceTracer: Sealed { impl BuildPtraceTracer for TracerBuilder { fn build_ptrace(self) -> color_eyre::Result<(Tracer, SpawnToken)> { + let mode = self.mode.ok_or_else(|| eyre!("tracer mode is required"))?; + let msg_tx = self + .tx + .ok_or_else(|| eyre!("tracer event sender is required"))?; + let printer = self.printer.ok_or_else(|| eyre!("printer is required"))?; + let baseline = self + .baseline + .ok_or_else(|| eyre!("baseline process information is required"))?; let seccomp_bpf = if self.seccomp_bpf == SeccompBpf::Auto { // TODO: check if the kernel supports seccomp-bpf // Let's just enable it for now and see if anyone complains @@ -131,7 +143,7 @@ impl BuildPtraceTracer for TracerBuilder { } else { self.seccomp_bpf }; - let with_tty = match self.mode.as_ref().unwrap() { + let with_tty = match &mode { TracerMode::Tui(tty) => tty.is_some(), TracerMode::Log { .. } => true, }; @@ -140,16 +152,16 @@ impl BuildPtraceTracer for TracerBuilder { Tracer { with_tty, seccomp_bpf, - msg_tx: self.tx.expect("tracer_tx is required for ptrace tracer"), + msg_tx, user: self.user, - printer: self.printer.unwrap(), + printer, modifier_args: self.modifier, filter: { let mut filter = self .filter .unwrap_or_else(BitFlags::::all); trace!("Event filter: {:?}", filter); - if let TracerMode::Log { .. } = self.mode.as_ref().unwrap() { + if let TracerMode::Log { .. } = &mode { // FIXME: In logging mode, we rely on root child exit event to exit the process // with the same exit code as the root child. It is not printed in logging mode. // Ideally we should use another channel to send the exit code to the main thread. @@ -157,7 +169,7 @@ impl BuildPtraceTracer for TracerBuilder { } filter }, - baseline: self.baseline.unwrap(), + baseline, req_tx: req_tx.clone(), polling_interval: { if self.ptrace_blocking == Some(true) { @@ -177,7 +189,7 @@ impl BuildPtraceTracer for TracerBuilder { } }, tracee_env: self.tracee_env, - mode: self.mode.unwrap(), + mode, }, SpawnToken { req_rx, req_tx }, )) @@ -208,7 +220,7 @@ impl Tracer { tokio::task::JoinHandle>, )> { if !self.req_tx.same_channel(&token.req_tx) { - panic!("The spawn token used does not match the tracer") + bail!("the spawn token does not belong to this tracer"); } drop(token.req_tx); let breakpoints = Arc::new(RwLock::new(BTreeMap::new())); @@ -243,7 +255,8 @@ impl Tracer { let result = tokio::runtime::Handle::current() .block_on(async move { inner.run(args, token.req_rx).await }); if let Err(e) = &result { - tx.send(TracerMessage::FatalError(e.to_string())).unwrap(); + // The receiver may have been dropped while the tracer was shutting down. + let _ = tx.send(TracerMessage::FatalError(e.to_string())); } result } diff --git a/crates/tracexec-backend-ptrace/src/ptrace/tracer/inner.rs b/crates/tracexec-backend-ptrace/src/ptrace/tracer/inner.rs index c752da99..51346291 100644 --- a/crates/tracexec-backend-ptrace/src/ptrace/tracer/inner.rs +++ b/crates/tracexec-backend-ptrace/src/ptrace/tracer/inner.rs @@ -1462,8 +1462,10 @@ mod tests { #[test] fn test_get_filename_for_display_resolves_proc_self_exe() { - let mut modifier = ModifierArgs::default(); - modifier.resolve_proc_self_exe = true; + let modifier = ModifierArgs { + resolve_proc_self_exe: true, + ..Default::default() + }; let (inner, _rx) = build_inner(modifier, BitFlags::empty(), SeccompBpf::Off); let pid = getpid(); let filename = cached_string("/proc/self/exe".to_string()); @@ -1548,8 +1550,10 @@ mod tests { #[test] fn test_timestamp_now_and_seccomp_bpf() { - let mut modifier = ModifierArgs::default(); - modifier.timestamp = true; + let modifier = ModifierArgs { + timestamp: true, + ..Default::default() + }; let (inner, _rx) = build_inner(modifier, BitFlags::empty(), SeccompBpf::On); assert!(inner.timestamp_now().is_some()); assert!(inner.seccomp_bpf()); diff --git a/crates/tracexec-backend-ptrace/src/ptrace/tracer/test.rs b/crates/tracexec-backend-ptrace/src/ptrace/tracer/test.rs index c91d9be8..8bfcc350 100644 --- a/crates/tracexec-backend-ptrace/src/ptrace/tracer/test.rs +++ b/crates/tracexec-backend-ptrace/src/ptrace/tracer/test.rs @@ -121,6 +121,33 @@ async fn run_exe_and_collect_msgs( type TracerFixture = (Tracer, UnboundedReceiver, SpawnToken); +#[test] +fn build_ptrace_reports_missing_required_configuration() { + let err = TracerBuilder::new() + .build_ptrace() + .err() + .expect("an incomplete builder must fail"); + assert!(err.to_string().contains("tracer mode is required")); + + let err = TracerBuilder::new() + .mode(TracerMode::Log { foreground: false }) + .build_ptrace() + .err() + .expect("an incomplete builder must fail"); + assert!(err.to_string().contains("tracer event sender is required")); +} + +#[test] +fn spawn_rejects_token_from_another_tracer() { + let (subject, _rx, _token) = tracer(ModifierArgs::default(), SeccompBpf::Off); + let (_other_tracer, _other_rx, other_token) = tracer(ModifierArgs::default(), SeccompBpf::Off); + + let err = subject + .spawn(Vec::new(), None, other_token) + .expect_err("a token from another tracer must fail"); + assert!(err.to_string().contains("does not belong to this tracer")); +} + #[traced_test] #[rstest] #[case(true)] diff --git a/crates/tracexec-core/src/cli.rs b/crates/tracexec-core/src/cli.rs index 6eec06b1..35ceb0aa 100644 --- a/crates/tracexec-core/src/cli.rs +++ b/crates/tracexec-core/src/cli.rs @@ -514,7 +514,7 @@ mod tests { writeln!(out, "Hello world").unwrap(); drop(out); - let content = fs::read_to_string(path.clone()).unwrap(); + let content = fs::read_to_string(path).unwrap(); assert!(content.contains("Hello world")); } diff --git a/crates/tracexec-core/src/cli/config.rs b/crates/tracexec-core/src/cli/config.rs index 3123db39..aebd3cb6 100644 --- a/crates/tracexec-core/src/cli/config.rs +++ b/crates/tracexec-core/src/cli/config.rs @@ -326,9 +326,9 @@ inline_format = "%H:%M:%S" "#; let cfg: ModifierConfig = toml::from_str(toml_str).unwrap(); - assert_eq!(cfg.successful_only.unwrap(), true); - assert_eq!(cfg.stdio_in_cmdline.unwrap(), true); - assert_eq!(cfg.timestamp.as_ref().unwrap().enable, true); + assert!(cfg.successful_only.unwrap()); + assert!(cfg.stdio_in_cmdline.unwrap()); + assert!(cfg.timestamp.as_ref().unwrap().enable); assert_eq!( cfg .timestamp @@ -357,9 +357,9 @@ color_level = "More" foreground = false "#; let cfg: LogModeConfig = toml::from_str(toml_str).unwrap(); - assert_eq!(cfg.show_interpreter.unwrap(), true); + assert!(cfg.show_interpreter.unwrap()); assert_eq!(cfg.color_level.unwrap(), ColorLevel::More); - assert_eq!(cfg.foreground.unwrap(), false); + assert!(!cfg.foreground.unwrap()); } #[test] @@ -372,7 +372,7 @@ theme-file = "nord.toml" theme = { app-title = { fg = "cyan", modifiers = ["bold"] } } "#; let cfg: TuiModeConfig = toml::from_str(toml_str).unwrap(); - assert_eq!(cfg.follow.unwrap(), true); + assert!(cfg.follow.unwrap()); assert_eq!(cfg.frame_rate.unwrap(), 12.5); assert_eq!(cfg.max_events.unwrap(), 100); assert_eq!(cfg.theme_file, Some(PathBuf::from("nord.toml"))); diff --git a/crates/tracexec-core/src/event.rs b/crates/tracexec-core/src/event.rs index 4a4bc1c9..8d99e937 100644 --- a/crates/tracexec-core/src/event.rs +++ b/crates/tracexec-core/src/event.rs @@ -451,7 +451,7 @@ mod tests { path: "/".to_string(), }, }; - let exec_detail = TracerEventDetails::Exec(Box::new(exec_event.clone())); + let exec_detail = TracerEventDetails::Exec(Box::new(exec_event)); assert_eq!(exec_detail.timestamp(), Some(ts)); } diff --git a/crates/tracexec-core/src/event/message.rs b/crates/tracexec-core/src/event/message.rs index 273aedde..2e4f54e0 100644 --- a/crates/tracexec-core/src/event/message.rs +++ b/crates/tracexec-core/src/event/message.rs @@ -335,7 +335,7 @@ mod tests { #[test] fn test_from_arcstr() { let s: ArcStr = ArcStr::from("hello"); - let msg: OutputMsg = s.clone().into(); + let msg: OutputMsg = s.into(); assert_eq!(msg.as_ref(), "hello"); } diff --git a/crates/tracexec-core/src/printer.rs b/crates/tracexec-core/src/printer.rs index b36868e9..25003715 100644 --- a/crates/tracexec-core/src/printer.rs +++ b/crates/tracexec-core/src/printer.rs @@ -744,7 +744,7 @@ impl Printer { write!( out, " -C {}", - &exec_data.cwd.cli_bash_escaped_with_style(THEME.cwd) + exec_data.cwd.cli_bash_escaped_with_style(THEME.cwd) )?; } else { write!(out, " -C {}", exec_data.cwd.bash_escaped())?; diff --git a/crates/tracexec-core/src/pty.rs b/crates/tracexec-core/src/pty.rs index b54501f6..78d111bd 100644 --- a/crates/tracexec-core/src/pty.rs +++ b/crates/tracexec-core/src/pty.rs @@ -872,11 +872,11 @@ mod tests { // give the child a moment to write std::thread::sleep(Duration::from_millis(100)); - reader.read(&mut buf); + let bytes_read = reader.read(&mut buf).unwrap(); eprintln!("buf: {}", String::from_utf8_lossy(&buf)); - assert!(buf.windows(5).any(|w| w == b"hello".as_slice())); + assert!(buf[..bytes_read].windows(5).any(|w| w == b"hello")); // reap child waitpid(pid, None).unwrap(); diff --git a/crates/tracexec-exporter-json/src/lib.rs b/crates/tracexec-exporter-json/src/lib.rs index 6c40e366..7e1e7dd6 100644 --- a/crates/tracexec-exporter-json/src/lib.rs +++ b/crates/tracexec-exporter-json/src/lib.rs @@ -386,8 +386,7 @@ mod tests { fn test_json_result_from_result() { let ok: JsonResult = JsonResult::from_result(Ok::(7u32)); assert!(matches!(ok, JsonResult::Success(7))); - let err: JsonResult = - JsonResult::from_result(Err(io::Error::new(io::ErrorKind::Other, "boom"))); + let err: JsonResult = JsonResult::from_result(Err(io::Error::other("boom"))); assert!(matches!(err, JsonResult::Error(msg) if msg.contains("boom"))); } diff --git a/crates/tracexec-exporter-perfetto/src/packet.rs b/crates/tracexec-exporter-perfetto/src/packet.rs index 9c6f82d0..c8db4ff6 100644 --- a/crates/tracexec-exporter-perfetto/src/packet.rs +++ b/crates/tracexec-exporter-perfetto/src/packet.rs @@ -539,7 +539,7 @@ mod tests { ); assert_eq!( format_cgroup_annotation(&CgroupInfo::Error(CgroupError::CgroupIdNotFound)), - format!("Error: {}", CgroupError::CgroupIdNotFound.to_string()) + format!("Error: {}", CgroupError::CgroupIdNotFound) ); } diff --git a/crates/tracexec-tui/src/app/ui.rs b/crates/tracexec-tui/src/app/ui.rs index 255d1512..44583053 100644 --- a/crates/tracexec-tui/src/app/ui.rs +++ b/crates/tracexec-tui/src/app/ui.rs @@ -193,14 +193,17 @@ impl App { } let mut items = Vec::from_iter( - Some(help_item!( - self.key_bindings.switch_pane.display(), - "Switch\u{00a0}Pane", - self.theme - )) - .filter(|_| self.term.is_some()) - .into_iter() - .flatten(), + self + .term + .is_some() + .then_some(help_item!( + self.key_bindings.switch_pane.display(), + "Switch\u{00a0}Pane", + self.theme + )) + .filter(|_| self.term.is_some()) + .into_iter() + .flatten(), ); if let Some(popup) = &self.popup.last() { diff --git a/deny.toml b/deny.toml index 59405989..e7be127f 100644 --- a/deny.toml +++ b/deny.toml @@ -72,6 +72,8 @@ feature-depth = 1 ignore = [ #"RUSTSEC-0000-0000", { id = "RUSTSEC-2024-0436", reason = "Not serious enough. paste is very stable." }, + { id = "RUSTSEC-2026-0194", reason = "Vulnerability in 3rd-party crate that is not actually used." }, + { id = "RUSTSEC-2026-0195", reason = "Vulnerability in 3rd-party crate that is not actually used." } #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, ]