diff --git a/src/agent/onefuzz-agent/src/agent.rs b/src/agent/onefuzz-agent/src/agent.rs index ab0358ed65..f0cdb1b244 100644 --- a/src/agent/onefuzz-agent/src/agent.rs +++ b/src/agent/onefuzz-agent/src/agent.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#![allow(clippy::too_many_arguments)] use anyhow::{Error, Result}; use tokio::time; @@ -26,6 +27,7 @@ pub struct Agent { heartbeat: Option, previous_state: NodeState, last_poll_command: Result, PollCommandError>, + managed: bool, } impl Agent { @@ -37,6 +39,7 @@ impl Agent { work_queue: Box, worker_runner: Box, heartbeat: Option, + managed: bool, ) -> Self { let scheduler = Some(scheduler); let previous_state = NodeState::Init; @@ -52,6 +55,7 @@ impl Agent { heartbeat, previous_state, last_poll_command, + managed, } } @@ -286,7 +290,8 @@ impl Agent { Ok(None) => {} Ok(Some(cmd)) => { info!("agent received node command: {:?}", cmd); - self.scheduler()?.execute_command(cmd).await?; + let managed = self.managed; + self.scheduler()?.execute_command(cmd, managed).await?; } Err(PollCommandError::RequestFailed(err)) => { // If we failed to request commands, this could be the service diff --git a/src/agent/onefuzz-agent/src/agent/tests.rs b/src/agent/onefuzz-agent/src/agent/tests.rs index 23d263ad00..a27c7365fc 100644 --- a/src/agent/onefuzz-agent/src/agent/tests.rs +++ b/src/agent/onefuzz-agent/src/agent/tests.rs @@ -35,6 +35,7 @@ impl Fixture { work_queue, worker_runner, None, + true, ) } diff --git a/src/agent/onefuzz-agent/src/commands.rs b/src/agent/onefuzz-agent/src/commands.rs index 049ccc595d..081167fb5d 100644 --- a/src/agent/onefuzz-agent/src/commands.rs +++ b/src/agent/onefuzz-agent/src/commands.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use anyhow::{Context, Result}; -use onefuzz::{auth::Secret, machine_id::get_scaleset_name}; +use onefuzz::auth::Secret; use std::process::Stdio; use tokio::{fs, io::AsyncWriteExt, process::Command}; @@ -32,11 +32,6 @@ pub struct SshKeyInfo { #[cfg(target_family = "windows")] pub async fn add_ssh_key(key_info: &SshKeyInfo) -> Result<()> { - if get_scaleset_name().await?.is_none() { - warn!("adding ssh keys only supported on managed nodes"); - return Ok(()); - } - let mut ssh_path = PathBuf::from(env::var("ProgramData").unwrap_or_else(|_| "c:\\programdata".to_string())); ssh_path.push("ssh"); @@ -160,11 +155,6 @@ pub async fn add_ssh_key(key_info: &SshKeyInfo) -> Result<()> { #[cfg(target_family = "unix")] pub async fn add_ssh_key(key_info: &SshKeyInfo) -> Result<()> { - if get_scaleset_name().await?.is_none() { - warn!("adding ssh keys only supported on managed nodes"); - return Ok(()); - } - let user = get_user_by_name(ONEFUZZ_SERVICE_USER).ok_or_else(|| format_err!("unable to find user"))?; info!("adding ssh key:{:?} to user:{:?}", key_info, user); diff --git a/src/agent/onefuzz-agent/src/config.rs b/src/agent/onefuzz-agent/src/config.rs index e0fafe4841..5d5f65a9ea 100644 --- a/src/agent/onefuzz-agent/src/config.rs +++ b/src/agent/onefuzz-agent/src/config.rs @@ -6,6 +6,7 @@ use onefuzz::{ auth::{ClientCredentials, Credentials, ManagedIdentityCredentials}, http::{is_auth_error_code, ResponseExt}, jitter::delay_with_jitter, + machine_id::MachineIdentity, }; use onefuzz_telemetry::{InstanceTelemetryKey, MicrosoftTelemetryKey}; use reqwest_retry::SendRetry; @@ -37,6 +38,8 @@ pub struct StaticConfig { #[serde(default = "default_as_true")] pub managed: bool, + + pub machine_identity: MachineIdentity, } fn default_as_true() -> bool { @@ -64,10 +67,12 @@ struct RawStaticConfig { #[serde(default = "default_as_true")] pub managed: bool, + + pub machine_identity: Option, } impl StaticConfig { - pub fn new(data: &[u8]) -> Result { + pub async fn new(data: &[u8]) -> Result { let config: RawStaticConfig = serde_json::from_slice(data)?; let credentials = match config.client_credentials { @@ -84,6 +89,11 @@ impl StaticConfig { managed.into() } }; + let machine_identity = match config.machine_identity { + Some(machine_identity) => machine_identity, + None => MachineIdentity::from_metadata().await?, + }; + let config = StaticConfig { credentials, pool_name: config.pool_name, @@ -94,16 +104,17 @@ impl StaticConfig { heartbeat_queue: config.heartbeat_queue, instance_id: config.instance_id, managed: config.managed, + machine_identity, }; Ok(config) } - pub fn from_file(config_path: impl AsRef) -> Result { + pub async fn from_file(config_path: impl AsRef) -> Result { let config_path = config_path.as_ref(); let data = std::fs::read(config_path) .with_context(|| format!("unable to read config file: {}", config_path.display()))?; - Self::new(&data) + Self::new(&data).await } pub fn from_env() -> Result { @@ -115,6 +126,7 @@ impl StaticConfig { let onefuzz_url = Url::parse(&std::env::var("ONEFUZZ_URL")?)?; let pool_name = std::env::var("ONEFUZZ_POOL")?; let is_unmanaged = std::env::var("ONEFUZZ_IS_UNMANAGED").is_ok(); + let machine_identity = MachineIdentity::from_env()?; let heartbeat_queue = if let Ok(key) = std::env::var("ONEFUZZ_HEARTBEAT") { Some(Url::parse(&key)?) @@ -155,6 +167,7 @@ impl StaticConfig { heartbeat_queue, instance_id, managed: !is_unmanaged, + machine_identity, }) } @@ -218,22 +231,21 @@ const REGISTRATION_RETRY_PERIOD: Duration = Duration::from_secs(60); impl Registration { pub async fn create(config: StaticConfig, managed: bool, timeout: Duration) -> Result { let token = config.credentials.access_token().await?; - let machine_name = onefuzz::machine_id::get_machine_name().await?; - let machine_id = onefuzz::machine_id::get_machine_id().await?; + let machine_name = &config.machine_identity.machine_name; + let machine_id = config.machine_identity.machine_id; let mut url = config.register_url(); url.query_pairs_mut() .append_pair("machine_id", &machine_id.to_string()) - .append_pair("machine_name", &machine_name) + .append_pair("machine_name", machine_name) .append_pair("pool_name", &config.pool_name) .append_pair("version", env!("ONEFUZZ_VERSION")) .append_pair("os", std::env::consts::OS); if managed { - let scaleset = onefuzz::machine_id::get_scaleset_name().await?; - match scaleset { + match &config.machine_identity.scaleset_name { Some(scaleset) => { - url.query_pairs_mut().append_pair("scaleset_id", &scaleset); + url.query_pairs_mut().append_pair("scaleset_id", scaleset); } None => { anyhow::bail!("managed instance without scaleset name"); @@ -284,7 +296,7 @@ impl Registration { pub async fn load_existing(config: StaticConfig) -> Result { let dynamic_config = DynamicConfig::load().await?; - let machine_id = onefuzz::machine_id::get_machine_id().await?; + let machine_id = config.machine_identity.machine_id; let mut registration = Self { config, dynamic_config, diff --git a/src/agent/onefuzz-agent/src/heartbeat.rs b/src/agent/onefuzz-agent/src/heartbeat.rs index 166489cde6..3e6415ec06 100644 --- a/src/agent/onefuzz-agent/src/heartbeat.rs +++ b/src/agent/onefuzz-agent/src/heartbeat.rs @@ -2,7 +2,6 @@ // Licensed under the MIT License. use crate::onefuzz::heartbeat::HeartbeatClient; -use crate::onefuzz::machine_id::{get_machine_id, get_machine_name}; use anyhow::Result; use reqwest::Url; use serde::{self, Deserialize, Serialize}; @@ -29,9 +28,11 @@ pub struct AgentContext { pub type AgentHeartbeatClient = HeartbeatClient; -pub async fn init_agent_heartbeat(queue_url: Url) -> Result { - let node_id = get_machine_id().await?; - let machine_name = get_machine_name().await?; +pub async fn init_agent_heartbeat( + queue_url: Url, + node_id: Uuid, + machine_name: String, +) -> Result { let hb = HeartbeatClient::init_heartbeat( AgentContext { node_id, diff --git a/src/agent/onefuzz-agent/src/main.rs b/src/agent/onefuzz-agent/src/main.rs index 642e453c98..ddb549d072 100644 --- a/src/agent/onefuzz-agent/src/main.rs +++ b/src/agent/onefuzz-agent/src/main.rs @@ -21,10 +21,7 @@ use std::process::{Command, Stdio}; use anyhow::{Context, Result}; use clap::Parser; -use onefuzz::{ - machine_id::{get_machine_id, get_scaleset_name}, - process::ExitStatus, -}; +use onefuzz::process::ExitStatus; use onefuzz_telemetry::{self as telemetry, EventData, Role}; use std::io::{self, Write}; use uuid::Uuid; @@ -202,7 +199,7 @@ async fn load_config(opt: RunOpt) -> Result { info!("loading supervisor agent config"); let config = match &opt.config_path { - Some(config_path) => StaticConfig::from_file(config_path)?, + Some(config_path) => StaticConfig::from_file(config_path).await?, None => StaticConfig::from_env()?, }; @@ -266,11 +263,11 @@ async fn check_existing_worksets(coordinator: &mut coordinator::Coordinator) -> async fn run_agent(config: StaticConfig) -> Result<()> { telemetry::set_property(EventData::InstanceId(config.instance_id)); - telemetry::set_property(EventData::MachineId(get_machine_id().await?)); + telemetry::set_property(EventData::MachineId(config.machine_identity.machine_id)); telemetry::set_property(EventData::Version(env!("ONEFUZZ_VERSION").to_string())); telemetry::set_property(EventData::Role(Role::Supervisor)); - let scaleset = get_scaleset_name().await?; - if let Some(scaleset_name) = &scaleset { + + if let Some(scaleset_name) = &config.machine_identity.scaleset_name { telemetry::set_property(EventData::ScalesetId(scaleset_name.to_string())); } @@ -300,7 +297,14 @@ async fn run_agent(config: StaticConfig) -> Result<()> { let work_queue = work::WorkQueue::new(registration.clone())?; let agent_heartbeat = match config.heartbeat_queue { - Some(url) => Some(init_agent_heartbeat(url).await?), + Some(url) => Some( + init_agent_heartbeat( + url, + config.machine_identity.machine_id, + config.machine_identity.machine_name, + ) + .await?, + ), None => None, }; let mut agent = agent::Agent::new( @@ -311,6 +315,7 @@ async fn run_agent(config: StaticConfig) -> Result<()> { Box::new(work_queue), Box::new(worker::WorkerRunner), agent_heartbeat, + config.managed, ); info!("running agent"); diff --git a/src/agent/onefuzz-agent/src/scheduler.rs b/src/agent/onefuzz-agent/src/scheduler.rs index 77bf38936a..e9b5fcc8ae 100644 --- a/src/agent/onefuzz-agent/src/scheduler.rs +++ b/src/agent/onefuzz-agent/src/scheduler.rs @@ -54,10 +54,14 @@ impl Scheduler { Self::default() } - pub async fn execute_command(&mut self, cmd: &NodeCommand) -> Result<()> { + pub async fn execute_command(&mut self, cmd: &NodeCommand, managed: bool) -> Result<()> { match cmd { NodeCommand::AddSshKey(ssh_key_info) => { - add_ssh_key(ssh_key_info).await?; + if managed { + add_ssh_key(ssh_key_info).await?; + } else { + warn!("adding ssh keys only supported on managed nodes"); + } } NodeCommand::StopTask(stop_task) => { if let Scheduler::Busy(state) = self { diff --git a/src/agent/onefuzz-agent/src/worker.rs b/src/agent/onefuzz-agent/src/worker.rs index c0d154e7a6..52b6daecff 100644 --- a/src/agent/onefuzz-agent/src/worker.rs +++ b/src/agent/onefuzz-agent/src/worker.rs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. use std::{ + collections::HashMap, path::{Path, PathBuf}, process::{Child, ChildStderr, ChildStdout, Command, Stdio}, thread::{self, JoinHandle}, @@ -8,12 +9,17 @@ use std::{ use anyhow::{format_err, Context as AnyhowContext, Result}; use downcast_rs::Downcast; -use onefuzz::process::{ExitStatus, Output}; +use onefuzz::{ + machine_id::MachineIdentity, + process::{ExitStatus, Output}, +}; use tokio::fs; use crate::buffer::TailBuffer; use crate::work::*; +use serde_json::Value; + // Max length of captured output streams from worker child processes. const MAX_TAIL_LEN: usize = 40960; @@ -209,9 +215,18 @@ impl IWorkerRunner for WorkerRunner { debug!("created worker working dir: {}", working_dir.display()); + // inject the machine_identity in the config file + let work_config = work.config.expose_ref(); + let mut config: HashMap = serde_json::from_str(work_config.as_str())?; + + config.insert( + "machine_identity".to_string(), + serde_json::to_value(MachineIdentity::default())?, + ); + let config_path = work.config_path()?; - fs::write(&config_path, work.config.expose_ref()) + fs::write(&config_path, serde_json::to_string(&config)?.as_bytes()) .await .with_context(|| format!("unable to save task config: {}", config_path.display()))?; diff --git a/src/agent/onefuzz-task/src/local/libfuzzer_test_input.rs b/src/agent/onefuzz-task/src/local/libfuzzer_test_input.rs index 5d800f0f36..690fa66a52 100644 --- a/src/agent/onefuzz-task/src/local/libfuzzer_test_input.rs +++ b/src/agent/onefuzz-task/src/local/libfuzzer_test_input.rs @@ -35,6 +35,7 @@ pub async fn run(args: &clap::ArgMatches<'_>, event_sender: Option, event_sender: Option) -> Result<()> { let check_oom = out_of_memory(min_available_memory_bytes); let common = config.common().clone(); - let machine_id = get_machine_id().await?; + let machine_id = common.machine_identity.machine_id; let task_logger = if let Some(logs) = common.logs.clone() { let rx = onefuzz_telemetry::subscribe_to_events()?; diff --git a/src/agent/onefuzz-task/src/tasks/analysis/generic.rs b/src/agent/onefuzz-task/src/tasks/analysis/generic.rs index aa5e3e80ef..9717cae3e7 100644 --- a/src/agent/onefuzz-task/src/tasks/analysis/generic.rs +++ b/src/agent/onefuzz-task/src/tasks/analysis/generic.rs @@ -198,7 +198,7 @@ pub async fn run_tool( let target_exe = try_resolve_setup_relative_path(&config.common.setup_dir, &config.target_exe).await?; - let expand = Expand::new() + let expand = Expand::new(&config.common.machine_identity) .machine_id() .await? .input_path(&input) diff --git a/src/agent/onefuzz-task/src/tasks/config.rs b/src/agent/onefuzz-task/src/tasks/config.rs index a128b2fe84..1a31c39e86 100644 --- a/src/agent/onefuzz-task/src/tasks/config.rs +++ b/src/agent/onefuzz-task/src/tasks/config.rs @@ -10,7 +10,7 @@ use crate::tasks::{ merge, regression, report, }; use anyhow::Result; -use onefuzz::machine_id::{get_machine_id, get_scaleset_name}; +use onefuzz::machine_id::MachineIdentity; use onefuzz_telemetry::{ self as telemetry, Event::task_start, EventData, InstanceTelemetryKey, MicrosoftTelemetryKey, Role, @@ -58,6 +58,8 @@ pub struct CommonConfig { /// Can be disabled by setting to 0. #[serde(default = "default_min_available_memory_mb")] pub min_available_memory_mb: u64, + + pub machine_identity: MachineIdentity, } impl CommonConfig { @@ -67,8 +69,15 @@ impl CommonConfig { ) -> Result> { match &self.heartbeat_queue { Some(url) => { - let hb = init_task_heartbeat(url.clone(), self.task_id, self.job_id, initial_delay) - .await?; + let hb = init_task_heartbeat( + url.clone(), + self.task_id, + self.job_id, + initial_delay, + self.machine_identity.machine_id, + self.machine_identity.machine_name.clone(), + ) + .await?; Ok(Some(hb)) } None => Ok(None), @@ -215,13 +224,14 @@ impl Config { pub async fn run(self) -> Result<()> { telemetry::set_property(EventData::JobId(self.common().job_id)); telemetry::set_property(EventData::TaskId(self.common().task_id)); - telemetry::set_property(EventData::MachineId(get_machine_id().await?)); + telemetry::set_property(EventData::MachineId( + self.common().machine_identity.machine_id, + )); telemetry::set_property(EventData::Version(env!("ONEFUZZ_VERSION").to_string())); telemetry::set_property(EventData::InstanceId(self.common().instance_id)); telemetry::set_property(EventData::Role(Role::Agent)); - let scaleset = get_scaleset_name().await?; - if let Some(scaleset_name) = &scaleset { + if let Some(scaleset_name) = &self.common().machine_identity.scaleset_name { telemetry::set_property(EventData::ScalesetId(scaleset_name.to_string())); } diff --git a/src/agent/onefuzz-task/src/tasks/coverage/dotnet.rs b/src/agent/onefuzz-task/src/tasks/coverage/dotnet.rs index 6e0df226aa..d4f3c72c81 100644 --- a/src/agent/onefuzz-task/src/tasks/coverage/dotnet.rs +++ b/src/agent/onefuzz-task/src/tasks/coverage/dotnet.rs @@ -266,7 +266,7 @@ impl<'a> TaskContext<'a> { // Try to expand `target_exe` with support for `{tools_dir}`. // // Allows using `LibFuzzerDotnetLoader.exe` from a shared tools container. - let expand = Expand::new().tools_dir(tools_dir); + let expand = Expand::new(&self.config.common.machine_identity).tools_dir(tools_dir); let expanded = expand.evaluate_value(&self.config.target_exe.to_string_lossy())?; let expanded_path = Path::new(&expanded); @@ -293,7 +293,7 @@ impl<'a> TaskContext<'a> { async fn command_for_input(&self, input: &Path) -> Result { let target_exe = self.target_exe().await?; - let expand = Expand::new() + let expand = Expand::new(&self.config.common.machine_identity) .machine_id() .await? .input_path(input) diff --git a/src/agent/onefuzz-task/src/tasks/coverage/generic.rs b/src/agent/onefuzz-task/src/tasks/coverage/generic.rs index 2acc4458a3..535ee5660e 100644 --- a/src/agent/onefuzz-task/src/tasks/coverage/generic.rs +++ b/src/agent/onefuzz-task/src/tasks/coverage/generic.rs @@ -288,7 +288,7 @@ impl<'a> TaskContext<'a> { try_resolve_setup_relative_path(&self.config.common.setup_dir, &self.config.target_exe) .await?; - let expand = Expand::new() + let expand = Expand::new(&self.config.common.machine_identity) .machine_id() .await? .input_path(input) diff --git a/src/agent/onefuzz-task/src/tasks/fuzz/generator.rs b/src/agent/onefuzz-task/src/tasks/fuzz/generator.rs index 36e55eb82e..a9dad3e48c 100644 --- a/src/agent/onefuzz-task/src/tasks/fuzz/generator.rs +++ b/src/agent/onefuzz-task/src/tasks/fuzz/generator.rs @@ -102,6 +102,7 @@ impl GeneratorTask { &target_exe, &self.config.target_options, &self.config.target_env, + self.config.common.machine_identity.clone(), ) .check_asan_log(self.config.check_asan_log) .check_debugger(self.config.check_debugger) @@ -163,7 +164,7 @@ impl GeneratorTask { ) -> Result<()> { utils::reset_tmp_dir(&output_dir).await?; let (mut generator, generator_path) = { - let expand = Expand::new() + let expand = Expand::new(&self.config.common.machine_identity) .machine_id() .await? .setup_dir(&self.config.common.setup_dir) diff --git a/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/dotnet.rs b/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/dotnet.rs index ba08e22842..9bf36eb5a0 100644 --- a/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/dotnet.rs +++ b/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/dotnet.rs @@ -86,6 +86,7 @@ impl common::LibFuzzerType for LibFuzzerDotnet { options, env, &config.common.setup_dir, + config.common.machine_identity.clone(), )) } diff --git a/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/generic.rs b/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/generic.rs index 51da454f0d..1d9d118d9f 100644 --- a/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/generic.rs +++ b/src/agent/onefuzz-task/src/tasks/fuzz/libfuzzer/generic.rs @@ -28,6 +28,7 @@ impl common::LibFuzzerType for GenericLibFuzzer { config.target_options.clone(), config.target_env.clone(), &config.common.setup_dir, + config.common.machine_identity.clone(), )) } } diff --git a/src/agent/onefuzz-task/src/tasks/fuzz/supervisor.rs b/src/agent/onefuzz-task/src/tasks/fuzz/supervisor.rs index 05955afcd7..df71249c09 100644 --- a/src/agent/onefuzz-task/src/tasks/fuzz/supervisor.rs +++ b/src/agent/onefuzz-task/src/tasks/fuzz/supervisor.rs @@ -144,7 +144,7 @@ pub async fn spawn(config: SupervisorConfig) -> Result<(), Error> { let monitor_path = if let Some(stats_file) = &config.stats_file { Some( - Expand::new() + Expand::new(&config.common.machine_identity) .machine_id() .await? .runtime_dir(runtime_dir.path()) @@ -204,7 +204,7 @@ async fn start_supervisor( None }; - let expand = Expand::new() + let expand = Expand::new(&config.common.machine_identity) .machine_id() .await? .supervisor_exe(&config.supervisor_exe) diff --git a/src/agent/onefuzz-task/src/tasks/heartbeat.rs b/src/agent/onefuzz-task/src/tasks/heartbeat.rs index a82ee5b649..515fa39d0c 100644 --- a/src/agent/onefuzz-task/src/tasks/heartbeat.rs +++ b/src/agent/onefuzz-task/src/tasks/heartbeat.rs @@ -2,7 +2,6 @@ // Licensed under the MIT License. use crate::onefuzz::heartbeat::HeartbeatClient; -use crate::onefuzz::machine_id::{get_machine_id, get_machine_name}; use anyhow::Result; use reqwest::Url; use serde::{self, Deserialize, Serialize}; @@ -40,9 +39,9 @@ pub async fn init_task_heartbeat( task_id: Uuid, job_id: Uuid, initial_delay: Option, + machine_id: Uuid, + machine_name: String, ) -> Result { - let machine_id = get_machine_id().await?; - let machine_name = get_machine_name().await?; let hb = HeartbeatClient::init_heartbeat( TaskContext { task_id, diff --git a/src/agent/onefuzz-task/src/tasks/merge/generic.rs b/src/agent/onefuzz-task/src/tasks/merge/generic.rs index b1b6265093..de804154cf 100644 --- a/src/agent/onefuzz-task/src/tasks/merge/generic.rs +++ b/src/agent/onefuzz-task/src/tasks/merge/generic.rs @@ -130,7 +130,7 @@ async fn merge(config: &Config, output_dir: impl AsRef) -> Result<()> { let target_exe = try_resolve_setup_relative_path(&config.common.setup_dir, &config.target_exe).await?; - let expand = Expand::new() + let expand = Expand::new(&config.common.machine_identity) .machine_id() .await? .input_marker(&config.supervisor_input_marker) diff --git a/src/agent/onefuzz-task/src/tasks/merge/libfuzzer_merge.rs b/src/agent/onefuzz-task/src/tasks/merge/libfuzzer_merge.rs index b119452207..7b8c6f7b43 100644 --- a/src/agent/onefuzz-task/src/tasks/merge/libfuzzer_merge.rs +++ b/src/agent/onefuzz-task/src/tasks/merge/libfuzzer_merge.rs @@ -46,6 +46,7 @@ pub async fn spawn(config: Arc) -> Result<()> { config.target_options.clone(), config.target_env.clone(), &config.common.setup_dir, + config.common.machine_identity.clone(), ); fuzzer.verify(config.check_fuzzer_help, None).await?; @@ -159,6 +160,7 @@ pub async fn merge_inputs( config.target_options.clone(), config.target_env.clone(), &config.common.setup_dir, + config.common.machine_identity.clone(), ); merger .merge(&config.unique_inputs.local_path, &candidates) diff --git a/src/agent/onefuzz-task/src/tasks/regression/generic.rs b/src/agent/onefuzz-task/src/tasks/regression/generic.rs index cbc1e29965..68d0f1643c 100644 --- a/src/agent/onefuzz-task/src/tasks/regression/generic.rs +++ b/src/agent/onefuzz-task/src/tasks/regression/generic.rs @@ -74,6 +74,7 @@ impl RegressionHandler for GenericRegressionTask { check_asan_log: self.config.check_asan_log, check_debugger: self.config.check_debugger, minimized_stack_depth: self.config.minimized_stack_depth, + machine_identity: self.config.common.machine_identity.clone(), }; generic::test_input(args).await } diff --git a/src/agent/onefuzz-task/src/tasks/regression/libfuzzer.rs b/src/agent/onefuzz-task/src/tasks/regression/libfuzzer.rs index ed0322cc04..5a5ce3990b 100644 --- a/src/agent/onefuzz-task/src/tasks/regression/libfuzzer.rs +++ b/src/agent/onefuzz-task/src/tasks/regression/libfuzzer.rs @@ -71,6 +71,7 @@ impl RegressionHandler for LibFuzzerRegressionTask { target_timeout: self.config.target_timeout, check_retry_count: self.config.check_retry_count, minimized_stack_depth: self.config.minimized_stack_depth, + machine_identity: self.config.common.machine_identity.clone(), }; libfuzzer_report::test_input(args).await } 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 a119f303c4..894d1615ee 100644 --- a/src/agent/onefuzz-task/src/tasks/report/dotnet/generic.rs +++ b/src/agent/onefuzz-task/src/tasks/report/dotnet/generic.rs @@ -132,7 +132,7 @@ impl AsanProcessor { // Try to expand `target_exe` with support for `{tools_dir}`. // // Allows using `LibFuzzerDotnetLoader.exe` from a shared tools container. - let expand = Expand::new().tools_dir(tools_dir); + let expand = Expand::new(&self.config.common.machine_identity).tools_dir(tools_dir); let expanded = expand.evaluate_value(&self.config.target_exe.to_string_lossy())?; let expanded_path = Path::new(&expanded); @@ -180,7 +180,7 @@ impl AsanProcessor { let mut args = vec![target_exe]; args.extend(self.config.target_options.clone()); - let expand = Expand::new() + let expand = Expand::new(&self.config.common.machine_identity) .input_path(input) .setup_dir(&self.config.common.setup_dir); let expanded_args = expand.evaluate(&args)?; diff --git a/src/agent/onefuzz-task/src/tasks/report/generic.rs b/src/agent/onefuzz-task/src/tasks/report/generic.rs index 490f4e8a8e..2b3c02c955 100644 --- a/src/agent/onefuzz-task/src/tasks/report/generic.rs +++ b/src/agent/onefuzz-task/src/tasks/report/generic.rs @@ -10,7 +10,9 @@ use crate::tasks::{ }; use anyhow::{Context, Result}; use async_trait::async_trait; -use onefuzz::{blob::BlobUrl, input_tester::Tester, sha256, syncdir::SyncedDir}; +use onefuzz::{ + blob::BlobUrl, input_tester::Tester, machine_id::MachineIdentity, sha256, syncdir::SyncedDir, +}; use reqwest::Url; use serde::Deserialize; use std::{ @@ -118,6 +120,7 @@ pub struct TestInputArgs<'a> { pub check_asan_log: bool, pub check_debugger: bool, pub minimized_stack_depth: Option, + pub machine_identity: MachineIdentity, } pub async fn test_input(args: TestInputArgs<'_>) -> Result { @@ -126,6 +129,7 @@ pub async fn test_input(args: TestInputArgs<'_>) -> Result { args.target_exe, args.target_options, args.target_env, + args.machine_identity.clone(), ) .check_asan_log(args.check_asan_log) .check_debugger(args.check_debugger) @@ -211,6 +215,7 @@ impl<'a> GenericReportProcessor<'a> { check_asan_log: self.config.check_asan_log, check_debugger: self.config.check_debugger, minimized_stack_depth: self.config.minimized_stack_depth, + machine_identity: self.config.common.machine_identity.clone(), }; test_input(args).await.context("test input failed") } diff --git a/src/agent/onefuzz-task/src/tasks/report/libfuzzer_report.rs b/src/agent/onefuzz-task/src/tasks/report/libfuzzer_report.rs index 47e51b0a0d..58d9612179 100644 --- a/src/agent/onefuzz-task/src/tasks/report/libfuzzer_report.rs +++ b/src/agent/onefuzz-task/src/tasks/report/libfuzzer_report.rs @@ -10,7 +10,9 @@ use crate::tasks::{ }; use anyhow::{Context, Result}; use async_trait::async_trait; -use onefuzz::{blob::BlobUrl, libfuzzer::LibFuzzer, sha256, syncdir::SyncedDir}; +use onefuzz::{ + blob::BlobUrl, libfuzzer::LibFuzzer, machine_id::MachineIdentity, sha256, syncdir::SyncedDir, +}; use reqwest::Url; use serde::Deserialize; use std::{ @@ -74,6 +76,7 @@ impl ReportTask { self.config.target_options.clone(), self.config.target_env.clone(), &self.config.common.setup_dir, + self.config.common.machine_identity.clone(), ); fuzzer.verify(self.config.check_fuzzer_help, None).await } @@ -120,6 +123,7 @@ pub struct TestInputArgs<'a> { pub target_timeout: Option, pub check_retry_count: u64, pub minimized_stack_depth: Option, + pub machine_identity: MachineIdentity, } pub async fn test_input(args: TestInputArgs<'_>) -> Result { @@ -128,6 +132,7 @@ pub async fn test_input(args: TestInputArgs<'_>) -> Result { args.target_options.to_vec(), args.target_env.clone(), args.setup_dir, + args.machine_identity, ); let task_id = args.task_id; @@ -215,6 +220,7 @@ impl AsanProcessor { target_timeout: self.config.target_timeout, check_retry_count: self.config.check_retry_count, minimized_stack_depth: self.config.minimized_stack_depth, + machine_identity: self.config.common.machine_identity.clone(), }; let result = test_input(args).await?; diff --git a/src/agent/onefuzz/examples/test-input.rs b/src/agent/onefuzz/examples/test-input.rs index 7066090087..3f83df9107 100644 --- a/src/agent/onefuzz/examples/test-input.rs +++ b/src/agent/onefuzz/examples/test-input.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use anyhow::Result; -use onefuzz::input_tester::Tester; +use onefuzz::{input_tester::Tester, machine_id::MachineIdentity}; use structopt::StructOpt; #[derive(Debug, PartialEq, Eq, StructOpt)] @@ -53,7 +53,13 @@ async fn main() -> Result<()> { } let env = Default::default(); - let tester = Tester::new(&setup_dir, &opt.exe, &target_options, &env); + let tester = Tester::new( + &setup_dir, + &opt.exe, + &target_options, + &env, + MachineIdentity::default(), + ); let check_debugger = !opt.no_check_debugger; let tester = tester diff --git a/src/agent/onefuzz/src/expand.rs b/src/agent/onefuzz/src/expand.rs index d0986d615e..73159f80c0 100644 --- a/src/agent/onefuzz/src/expand.rs +++ b/src/agent/onefuzz/src/expand.rs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -use crate::{machine_id::get_machine_id, sha256::digest_file_blocking}; +use crate::{machine_id::MachineIdentity, sha256::digest_file_blocking}; use anyhow::{format_err, Context, Result}; use onefuzz_telemetry::{InstanceTelemetryKey, MicrosoftTelemetryKey}; use std::path::{Path, PathBuf}; @@ -89,16 +89,11 @@ impl PlaceHolder { pub struct Expand<'a> { values: HashMap>, -} - -impl Default for Expand<'_> { - fn default() -> Self { - Self::new() - } + machine_identity: &'a MachineIdentity, } impl<'a> Expand<'a> { - pub fn new() -> Self { + pub fn new(machine_identity: &'a MachineIdentity) -> Self { let mut values = HashMap::new(); values.insert( PlaceHolder::InputFileNameNoExt.get_string(), @@ -113,12 +108,15 @@ impl<'a> Expand<'a> { ExpandedValue::Mapping(Box::new(Expand::input_file_sha256)), ); - Self { values } + Self { + values, + machine_identity, + } } // Must be manually called to enable the use of async library code. pub async fn machine_id(self) -> Result> { - let id = get_machine_id().await?; + let id = self.machine_identity.machine_id; let value = id.to_string(); Ok(self.set_value(PlaceHolder::MachineId, ExpandedValue::Scalar(value))) } @@ -171,7 +169,10 @@ impl<'a> Expand<'a> { pub fn set_value(self, name: PlaceHolder, value: ExpandedValue<'a>) -> Self { let mut values = self.values; values.insert(name.get_string(), value); - Self { values } + Self { + values, + machine_identity: self.machine_identity, + } } pub fn set_optional_ref<'l, T: 'l>( @@ -412,6 +413,8 @@ impl<'a> Expand<'a> { #[cfg(test)] mod tests { + use crate::machine_id::MachineIdentity; + use super::Expand; use anyhow::{Context, Result}; use std::path::Path; @@ -421,7 +424,7 @@ mod tests { fn test_expand_nested() -> Result<()> { let supervisor_options = vec!["{target_options}".to_string()]; let target_options: Vec<_> = vec!["a", "b", "c"].iter().map(|p| p.to_string()).collect(); - let result = Expand::new() + let result = Expand::new(&MachineIdentity::default()) .target_options(&target_options) .evaluate(&supervisor_options)?; let expected = vec!["a b c"]; @@ -464,7 +467,7 @@ mod tests { let input_corpus_dir = "src"; let generated_inputs_dir = "src"; - let result = Expand::new() + let result = Expand::new(&MachineIdentity::default()) .input_corpus(Path::new(input_corpus_dir)) .generated_inputs(Path::new(generated_inputs_dir)) .target_options(&my_options) @@ -498,14 +501,16 @@ mod tests { ] ); - assert!(Expand::new().evaluate(&my_args).is_err()); + assert!(Expand::new(&MachineIdentity::default()) + .evaluate(&my_args) + .is_err()); Ok(()) } #[test] fn test_expand_in_string() -> Result<()> { - let result = Expand::new() + let result = Expand::new(&MachineIdentity::default()) .input_path("src/lib.rs") .evaluate_value("a {input} b")?; assert!(result.contains("lib.rs")); @@ -514,10 +519,16 @@ mod tests { #[tokio::test] async fn test_expand_machine_id() -> Result<()> { - let expand = Expand::new().machine_id().await?; + let machine_id = Uuid::new_v4(); + let machine_identity = MachineIdentity { + machine_id, + ..Default::default() + }; + let expand = Expand::new(&machine_identity).machine_id().await?; let expanded = expand.evaluate_value("{machine_id}")?; // Check that "{machine_id}" expands to a valid UUID, but don't worry about the actual value. - Uuid::parse_str(&expanded)?; + let expanded_machine_id = Uuid::parse_str(&expanded)?; + assert_eq!(expanded_machine_id, machine_id); Ok(()) } } diff --git a/src/agent/onefuzz/src/input_tester.rs b/src/agent/onefuzz/src/input_tester.rs index 6eb9e0a655..ec4375455d 100644 --- a/src/agent/onefuzz/src/input_tester.rs +++ b/src/agent/onefuzz/src/input_tester.rs @@ -7,6 +7,7 @@ use crate::{ asan::{add_asan_log_env, check_asan_path, check_asan_string}, env::{get_path_with_directory, update_path, LD_LIBRARY_PATH, PATH}, expand::Expand, + machine_id::MachineIdentity, process::run_cmd, }; use anyhow::{Context, Error, Result}; @@ -36,6 +37,7 @@ pub struct Tester<'a> { check_retry_count: u64, add_setup_to_ld_library_path: bool, add_setup_to_path: bool, + machine_identity: MachineIdentity, } #[derive(Debug)] @@ -57,6 +59,7 @@ impl<'a> Tester<'a> { exe_path: &'a Path, arguments: &'a [String], environ: &'a HashMap, + machine_identity: MachineIdentity, ) -> Self { Self { setup_dir, @@ -70,6 +73,7 @@ impl<'a> Tester<'a> { check_retry_count: 0, add_setup_to_ld_library_path: false, add_setup_to_path: false, + machine_identity, } } @@ -289,7 +293,7 @@ impl<'a> Tester<'a> { }; let (argv, env) = { - let expand = Expand::new() + let expand = Expand::new(&self.machine_identity) .machine_id() .await? .input_path(input_file) diff --git a/src/agent/onefuzz/src/libfuzzer.rs b/src/agent/onefuzz/src/libfuzzer.rs index d221732639..ec6aeec262 100644 --- a/src/agent/onefuzz/src/libfuzzer.rs +++ b/src/agent/onefuzz/src/libfuzzer.rs @@ -6,6 +6,7 @@ use crate::{ expand::Expand, fs::{list_files, write_file}, input_tester::{TestResult, Tester}, + machine_id::MachineIdentity, }; use anyhow::{Context, Result}; use rand::seq::SliceRandom; @@ -39,6 +40,7 @@ pub struct LibFuzzer { exe: PathBuf, options: Vec, env: HashMap, + machine_identity: MachineIdentity, } impl LibFuzzer { @@ -47,12 +49,14 @@ impl LibFuzzer { options: Vec, env: HashMap, setup_dir: impl Into, + machine_identity: MachineIdentity, ) -> Self { Self { exe: exe.into(), options, env, setup_dir: setup_dir.into(), + machine_identity, } } @@ -98,7 +102,7 @@ impl LibFuzzer { ); } - let expand = Expand::new() + let expand = Expand::new(&self.machine_identity) .machine_id() .await? .target_exe(&self.exe) @@ -310,11 +314,17 @@ impl LibFuzzer { let mut options = self.options.clone(); options.push("{input}".to_string()); - let mut tester = Tester::new(&self.setup_dir, &self.exe, &options, &self.env) - .check_asan_stderr(true) - .check_retry_count(retry) - .add_setup_to_path(true) - .set_optional(timeout, |tester, timeout| tester.timeout(timeout)); + let mut tester = Tester::new( + &self.setup_dir, + &self.exe, + &options, + &self.env, + self.machine_identity.clone(), + ) + .check_asan_stderr(true) + .check_retry_count(retry) + .add_setup_to_path(true) + .set_optional(timeout, |tester, timeout| tester.timeout(timeout)); if cfg!(target_family = "unix") { tester = tester.add_setup_to_ld_library_path(true); @@ -435,6 +445,7 @@ mod tests { options.clone(), env.clone(), &temp_setup_dir.path(), + MachineIdentity::default(), ); // verify catching bad exits with -help=1 @@ -463,6 +474,7 @@ mod tests { options.clone(), env.clone(), &temp_setup_dir.path(), + MachineIdentity::default(), ); // verify good exits with -help=1 assert!( diff --git a/src/agent/onefuzz/src/machine_id.rs b/src/agent/onefuzz/src/machine_id.rs index 33b222776d..f0949b28ad 100644 --- a/src/agent/onefuzz/src/machine_id.rs +++ b/src/agent/onefuzz/src/machine_id.rs @@ -10,6 +10,13 @@ use std::time::Duration; use tokio::fs; use uuid::Uuid; +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Default, Serialize)] +pub struct MachineIdentity { + pub machine_id: Uuid, + pub machine_name: String, + pub scaleset_name: Option, +} + // https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service#tracking-vm-running-on-azure const IMS_ID_URL: &str = "http://169.254.169.254/metadata/instance/compute/vmId?api-version=2020-06-01&format=text"; @@ -19,127 +26,153 @@ const VM_NAME_URL: &str = "http://169.254.169.254/metadata/instance/compute/name?api-version=2020-06-01&format=text"; const VM_SCALESET_NAME: &str = - "http://169.254.169.254/metadata/instance/compute/vmScaleSetName?api-version=2020-06-01&format=text"; - -pub async fn get_ims_id() -> Result { - let path = onefuzz_etc()?.join("ims_id"); - let body = match fs::read_to_string(&path).await { - Ok(body) => body, - Err(_) => { - let resp = reqwest::Client::new() - .get(IMS_ID_URL) - .timeout(Duration::from_millis(500)) - .header("Metadata", "true") - .send_retry_default() - .await - .context("get_ims_id")?; - let body = resp.text().await?; - write_file(path, &body).await?; - body - } - }; - - let value = Uuid::parse_str(&body)?; - Ok(value) -} +"http://169.254.169.254/metadata/instance/compute/vmScaleSetName?api-version=2020-06-01&format=text"; + +impl MachineIdentity { + pub async fn from_metadata() -> Result { + let machine_id = Self::get_machine_id().await?; + let machine_name = Self::get_machine_name().await?; + let scaleset_name = Self::get_scaleset_name().await?; + + Ok(Self { + machine_id, + machine_name, + scaleset_name, + }) + } -pub async fn get_machine_name() -> Result { - let path = onefuzz_etc()?.join("machine_name"); - let body = match fs::read_to_string(&path).await { - Ok(body) => body, - Err(_) => { - let resp = reqwest::Client::new() - .get(VM_NAME_URL) - .timeout(Duration::from_millis(500)) - .header("Metadata", "true") - .send_retry_default() - .await - .context("get_machine_name")?; - let body = resp.text().await?; - write_file(path, &body).await?; - body - } - }; + pub fn from_env() -> Result { + let machine_id = Uuid::parse_str(&std::env::var("ONEFUZZ_MACHINE_ID")?)?; + let machine_name = std::env::var("ONEFUZZ_MACHINE_NAME")?; + let scaleset_name = std::env::var("ONEFUZZ_SCALESET_NAME").ok(); - Ok(body) -} + Ok(Self { + machine_id, + machine_name, + scaleset_name, + }) + } -pub async fn get_scaleset_name() -> Result> { - let path = onefuzz_etc()?.join("scaleset_name"); - if let Ok(scaleset_name) = fs::read_to_string(&path).await { - return Ok(Some(scaleset_name)); + pub async fn get_ims_id() -> Result { + let path = onefuzz_etc()?.join("ims_id"); + let body = match fs::read_to_string(&path).await { + Ok(body) => body, + Err(_) => { + let resp = reqwest::Client::new() + .get(IMS_ID_URL) + .timeout(Duration::from_millis(500)) + .header("Metadata", "true") + .send_retry_default() + .await + .context("get_ims_id")?; + let body = resp.text().await?; + write_file(path, &body).await?; + body + } + }; + + let value = Uuid::parse_str(&body)?; + Ok(value) } - if let Ok(resp) = reqwest::Client::new() - .get(VM_SCALESET_NAME) - .timeout(Duration::from_millis(500)) - .header("Metadata", "true") - .send_retry_default() - .await - { - let body = resp.text().await?; - write_file(path, &body).await?; - Ok(Some(body)) - } else { - Ok(None) + pub async fn get_machine_name() -> Result { + let path = onefuzz_etc()?.join("machine_name"); + let body = match fs::read_to_string(&path).await { + Ok(body) => body, + Err(_) => { + let resp = reqwest::Client::new() + .get(VM_NAME_URL) + .timeout(Duration::from_millis(500)) + .header("Metadata", "true") + .send_retry_default() + .await + .context("get_machine_name")?; + let body = resp.text().await?; + write_file(path, &body).await?; + body + } + }; + + Ok(body) } -} -#[cfg(target_os = "linux")] -pub async fn get_os_machine_id() -> Result { - let path = Path::new("/etc/machine-id"); - let contents = fs::read_to_string(&path) - .await - .with_context(|| format!("unable to read machine_id: {}", path.display()))?; - let uuid = Uuid::parse_str(contents.trim())?; - Ok(uuid) -} + pub async fn get_scaleset_name() -> Result> { + let path = onefuzz_etc()?.join("scaleset_name"); + if let Ok(scaleset_name) = fs::read_to_string(&path).await { + return Ok(Some(scaleset_name)); + } -#[cfg(target_os = "windows")] -pub async fn get_os_machine_id() -> Result { - use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_64KEY}; - use winreg::RegKey; - - let key: &str = "SOFTWARE\\Microsoft\\Cryptography"; - - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - let crypt = if let Ok(crypt) = hklm.open_subkey_with_flags(key, KEY_READ) { - crypt - } else { - hklm.open_subkey_with_flags(key, KEY_READ | KEY_WOW64_64KEY)? - }; - let guid: String = crypt.get_value("MachineGuid")?; - Ok(Uuid::parse_str(&guid)?) -} + if let Ok(resp) = reqwest::Client::new() + .get(VM_SCALESET_NAME) + .timeout(Duration::from_millis(500)) + .header("Metadata", "true") + .send_retry_default() + .await + { + let body = resp.text().await?; + write_file(path, &body).await?; + Ok(Some(body)) + } else { + Ok(None) + } + } -async fn get_machine_id_impl() -> Result { - let ims_id = get_ims_id().await; - if ims_id.is_ok() { - return ims_id; + #[cfg(target_os = "linux")] + pub async fn get_os_machine_id() -> Result { + let path = Path::new("/etc/machine-id"); + let contents = fs::read_to_string(&path) + .await + .with_context(|| format!("unable to read machine_id: {}", path.display()))?; + let uuid = Uuid::parse_str(contents.trim())?; + Ok(uuid) } - let machine_id = get_os_machine_id().await; - if machine_id.is_ok() { - return machine_id; + #[cfg(target_os = "windows")] + pub async fn get_os_machine_id() -> Result { + use winreg::enums::{HKEY_LOCAL_MACHINE, KEY_READ, KEY_WOW64_64KEY}; + use winreg::RegKey; + + let key: &str = "SOFTWARE\\Microsoft\\Cryptography"; + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + let crypt = if let Ok(crypt) = hklm.open_subkey_with_flags(key, KEY_READ) { + crypt + } else { + hklm.open_subkey_with_flags(key, KEY_READ | KEY_WOW64_64KEY)? + }; + let guid: String = crypt.get_value("MachineGuid")?; + Ok(Uuid::parse_str(&guid)?) } - Ok(Uuid::new_v4()) -} + async fn get_machine_id_impl() -> Result { + let ims_id = Self::get_ims_id().await; + if ims_id.is_ok() { + return ims_id; + } -pub async fn get_machine_id() -> Result { - let path = onefuzz_etc()?.join("machine_id"); - let result = match fs::read_to_string(&path).await { - Ok(body) => Uuid::parse_str(&body)?, - Err(_) => { - let value = get_machine_id_impl().await?; - write_file(path, &value.to_string()).await?; - value + let machine_id = Self::get_os_machine_id().await; + if machine_id.is_ok() { + return machine_id; } - }; - Ok(result) + + Ok(Uuid::new_v4()) + } + + pub async fn get_machine_id() -> Result { + let path = onefuzz_etc()?.join("machine_id"); + let result = match fs::read_to_string(&path).await { + Ok(body) => Uuid::parse_str(&body)?, + Err(_) => { + let value = Self::get_machine_id_impl().await?; + write_file(path, &value.to_string()).await?; + value + } + }; + Ok(result) + } } #[tokio::test] async fn test_get_machine_id() { - get_os_machine_id().await.unwrap(); + MachineIdentity::get_os_machine_id().await.unwrap(); } diff --git a/src/deny.toml b/src/deny.toml index 8fa38dc088..98c5446d0a 100644 --- a/src/deny.toml +++ b/src/deny.toml @@ -17,6 +17,7 @@ yanked = "deny" ignore = [ "RUSTSEC-2022-0048", # xml-rs is unmaintained "RUSTSEC-2021-0139", # ansi_term is unmaintained + "RUSTSEC-2021-0145", # atty bug: we are unaffected (no custom allocator) ] [bans]