diff --git a/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs b/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs index fdcb88d84b..ccd5cfe946 100644 --- a/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs +++ b/src/ApiService/ApiService/onefuzzlib/notifications/NotificationsBase.cs @@ -138,7 +138,7 @@ public async Async.Task Render(string templateString, Uri instanceUrl, b true => new TemplateContext { EnableRelaxedFunctionAccess = false, EnableRelaxedIndexerAccess = false, - EnableRelaxedMemberAccess = false, + EnableRelaxedMemberAccess = true, EnableRelaxedTargetAccess = false }, _ => new TemplateContext() diff --git a/src/ApiService/IntegrationTests/JinjaToScribanMigrationTests.cs b/src/ApiService/IntegrationTests/JinjaToScribanMigrationTests.cs index f4a9acfa40..90607f35db 100644 --- a/src/ApiService/IntegrationTests/JinjaToScribanMigrationTests.cs +++ b/src/ApiService/IntegrationTests/JinjaToScribanMigrationTests.cs @@ -362,6 +362,12 @@ public async Async.Task Access_WithoutAuthorization_IsRejected() { result.StatusCode.Should().Be(System.Net.HttpStatusCode.BadRequest); } + [Fact] + public async Async.Task Do_Not_Enforce_Key_Exists_In_Strict_Validation() { + (await JinjaTemplateAdapter.IsValidScribanNotificationTemplate(Context, Logger, ValidScribanAdoTemplate())) + .Should().BeTrue(); + } + private async Async.Task ConfigureAuth() { await Context.InsertAll( new InstanceConfig(Context.ServiceConfiguration.OneFuzzInstanceName!) { Admins = new[] { _userObjectId } } // needed for admin check @@ -412,4 +418,19 @@ private static GithubIssuesTemplate MigratableGithubTemplate() { private static TeamsTemplate GetTeamsTemplate() { return new TeamsTemplate(new SecretData(new SecretValue("https://example.com"))); } + + private static AdoTemplate ValidScribanAdoTemplate() { + return new AdoTemplate( + new Uri("http://example.com"), + new SecretData(new SecretValue("some secret")), + "{{ if task.tags.project }} blah {{ end }}", + string.Empty, + Array.Empty().ToList(), + new Dictionary(), + new ADODuplicateTemplate( + Array.Empty().ToList(), + new Dictionary(), + new Dictionary() + )); + } } diff --git a/src/agent/onefuzz-task/src/tasks/report/dotnet/generic.rs b/src/agent/onefuzz-task/src/tasks/report/dotnet/generic.rs index 8e6ae6650f..c630169d3b 100644 --- a/src/agent/onefuzz-task/src/tasks/report/dotnet/generic.rs +++ b/src/agent/onefuzz-task/src/tasks/report/dotnet/generic.rs @@ -208,7 +208,7 @@ impl AsanProcessor { input_blob, executable, crash_type: exception.exception, - crash_site: exception.call_stack[0].clone(), + crash_site: exception.call_stack.first().cloned().unwrap_or_default(), call_stack: exception.call_stack, call_stack_sha256, minimized_stack: None, diff --git a/src/agent/onefuzz/src/input_tester.rs b/src/agent/onefuzz/src/input_tester.rs index 25b292a4bc..30f455163a 100644 --- a/src/agent/onefuzz/src/input_tester.rs +++ b/src/agent/onefuzz/src/input_tester.rs @@ -16,14 +16,13 @@ use nix::sys::signal::{kill, Signal}; use stacktrace_parser::CrashLog; #[cfg(any(target_os = "linux", target_family = "windows"))] use stacktrace_parser::StackEntry; +use std::ffi::OsStr; #[cfg(target_os = "linux")] use std::process::Stdio; use std::{collections::HashMap, path::Path, time::Duration}; use tempfile::tempdir; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5); -#[cfg(any(target_os = "linux", target_family = "windows"))] -const CRASH_SITE_UNAVAILABLE: &str = ""; pub struct Tester<'a> { setup_dir: &'a Path, @@ -140,14 +139,14 @@ impl<'a> Tester<'a> { #[cfg(target_family = "windows")] async fn test_input_debugger( &self, - argv: Vec, - env: HashMap, + argv: &[impl AsRef], + env: &HashMap, ) -> Result> { const IGNORE_FIRST_CHANCE_EXCEPTIONS: bool = true; let report = input_tester::crash_detector::test_process( self.exe_path, - &argv, - &env, + argv, + env, self.timeout, IGNORE_FIRST_CHANCE_EXCEPTIONS, )?; @@ -182,18 +181,11 @@ impl<'a> Tester<'a> { }) .collect(); - let crash_site = if let Some(frame) = call_stack.get(0) { - frame.line.to_owned() - } else { - CRASH_SITE_UNAVAILABLE.to_owned() - }; - let fault_type = exception.description.to_string(); let sanitizer = fault_type.to_string(); - let summary = crash_site; Some(CrashLog::new( - None, summary, sanitizer, fault_type, None, None, call_stack, + None, None, sanitizer, fault_type, None, None, call_stack, )?) } else { None @@ -205,12 +197,12 @@ impl<'a> Tester<'a> { #[cfg(target_os = "linux")] async fn test_input_debugger( &self, - args: Vec, - env: HashMap, + args: &[impl AsRef], + env: &HashMap, ) -> Result> { let mut cmd = std::process::Command::new(self.exe_path); cmd.args(args).stdin(Stdio::null()); - cmd.envs(&env); + cmd.envs(env); let (sender, receiver) = std::sync::mpsc::channel(); @@ -265,19 +257,11 @@ impl<'a> Tester<'a> { .collect(); let crash_type = crash.signal.to_string(); - - let crash_site = if let Some(frame) = crash_thread.callstack.get(0) { - frame.to_string() - } else { - CRASH_SITE_UNAVAILABLE.to_owned() - }; - - let summary = crash_site; let sanitizer = crash_type.clone(); let fault_type = crash_type; Some(CrashLog::new( - None, summary, sanitizer, fault_type, None, None, call_stack, + None, None, sanitizer, fault_type, None, None, call_stack, )?) } else { None @@ -301,9 +285,7 @@ impl<'a> Tester<'a> { .target_exe(self.exe_path) .target_options(self.arguments) .setup_dir(self.setup_dir) - .set_optional(self.extra_dir.as_ref(), |expand, extra_dir| { - expand.extra_dir(extra_dir) - }); + .set_optional(self.extra_dir, Expand::extra_dir); let argv = expand.evaluate(self.arguments)?; let mut env: HashMap = HashMap::new(); @@ -317,6 +299,7 @@ impl<'a> Tester<'a> { Some(v) => update_path(v.clone().into(), setup_dir)?, None => get_path_with_directory(PATH, setup_dir)?, }; + env.insert(PATH.to_string(), new_path.to_string_lossy().to_string()); } if self.add_setup_to_ld_library_path { @@ -343,7 +326,7 @@ impl<'a> Tester<'a> { let attempts = 1 + self.check_retry_count; for _ in 0..attempts { let result = if self.check_debugger { - match self.test_input_debugger(argv.clone(), env.clone()).await { + match self.test_input_debugger(&argv, &env).await { Ok(crash) => (crash, None, None), Err(error) => (None, Some(error), None), } diff --git a/src/agent/stacktrace-parser/src/lib.rs b/src/agent/stacktrace-parser/src/lib.rs index 177df4ba8b..b2b7989042 100644 --- a/src/agent/stacktrace-parser/src/lib.rs +++ b/src/agent/stacktrace-parser/src/lib.rs @@ -155,7 +155,7 @@ fn filter_funcs(entry: &StackEntry, stack_filter: &RegexSet) -> Option, - summary: String, + summary: Option, sanitizer: String, fault_type: String, scariness_score: Option, @@ -197,6 +197,15 @@ impl CrashLog { let minimized_stack_function_names = stack_names(&minimized_stack_details); let minimized_stack_function_lines = stack_function_lines(&minimized_stack_details); + // if summary was not supplied, + // use first line of minimized stack + // or else first line of stack, + // or else nothing + let summary = summary + .or_else(|| minimized_stack.first().cloned()) + .or_else(|| call_stack.first().cloned()) + .unwrap_or_else(|| "".to_string()); + Ok(Self { text, sanitizer, @@ -220,7 +229,7 @@ impl CrashLog { let (scariness_score, scariness_description) = parse_scariness(&text); Self::new( Some(text), - summary, + Some(summary), sanitizer, fault_type, scariness_score, @@ -325,6 +334,8 @@ mod tests { if skip_files.contains(&file_name) { eprintln!("skipping file: {file_name}"); continue; + } else { + eprintln!("parsing file: {file_name}"); } let data_raw =