From 6b0b23562aca02df70d1daa0bd4a3313cb4d491b Mon Sep 17 00:00:00 2001 From: Chris Klochek Date: Thu, 30 Jul 2026 13:16:17 -0400 Subject: [PATCH 1/2] fix(relay): Limit maximum number of otel logs deserialized from JSON --- Cargo.lock | 1 + relay-config/src/config.rs | 8 ++ relay-server/Cargo.toml | 1 + .../src/processing/logs/integrations/mod.rs | 3 +- .../src/processing/logs/integrations/otel.rs | 134 ++++++++++++++++-- relay-server/src/processing/logs/mod.rs | 2 +- relay-server/src/processing/logs/process.rs | 7 +- 7 files changed, 142 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7efd03b78f..a5a87634ca6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5064,6 +5064,7 @@ dependencies = [ "relay-redis", "relay-replays", "relay-sampling", + "relay-serialization", "relay-spans", "relay-statsd", "relay-system", diff --git a/relay-config/src/config.rs b/relay-config/src/config.rs index d79f9445162..005ad2c08d6 100644 --- a/relay-config/src/config.rs +++ b/relay-config/src/config.rs @@ -645,6 +645,8 @@ pub struct Limits { pub max_trace_metric_size: ByteSize, /// The maximum payload size for a log. pub max_log_size: ByteSize, + /// The maximum number of operations that can occur in a log expansion. + pub max_expanded_log_operations: usize, /// The maximum payload size for a span. pub max_span_size: ByteSize, /// The maximum amount of standalone transaction spans per envelope. @@ -739,6 +741,7 @@ impl Default for Limits { max_profile_size: ByteSize::mebibytes(50), max_trace_metric_size: ByteSize::mebibytes(1), max_log_size: ByteSize::mebibytes(2), + max_expanded_log_operations: 20000, max_span_size: ByteSize::mebibytes(10), max_standalone_span_count: 25, max_container_size: ByteSize::mebibytes(12), @@ -2434,6 +2437,11 @@ impl Config { self.values.limits.max_log_size.as_bytes() } + /// Returns the maximum number of operations to allow for a log expansion. + pub fn max_expanded_log_operations(&self) -> usize { + self.values.limits.max_expanded_log_operations + } + /// Returns the maximum payload size of a span in bytes. pub fn max_span_size(&self) -> usize { self.values.limits.max_span_size.as_bytes() diff --git a/relay-server/Cargo.toml b/relay-server/Cargo.toml index 9fb48615a93..dfdfa46325e 100644 --- a/relay-server/Cargo.toml +++ b/relay-server/Cargo.toml @@ -95,6 +95,7 @@ relay-redis = { workspace = true } relay-replays = { workspace = true } relay-conventions = { workspace = true } relay-sampling = { workspace = true } +relay-serialization = { workspace = true } relay-spans = { workspace = true } relay-statsd = { workspace = true } relay-system = { workspace = true } diff --git a/relay-server/src/processing/logs/integrations/mod.rs b/relay-server/src/processing/logs/integrations/mod.rs index 29c2b070c03..9ad153b9418 100644 --- a/relay-server/src/processing/logs/integrations/mod.rs +++ b/relay-server/src/processing/logs/integrations/mod.rs @@ -17,6 +17,7 @@ pub fn expand( item: Item, records: &mut RecordKeeper<'_>, headers: &EnvelopeHeaders, + max_ops: usize, ) -> Option<(Settings, ContainerItems)> { let integration = match item.integration() { Some(Integration::Logs(integration)) => integration, @@ -46,7 +47,7 @@ pub fn expand( let settings = match integration { LogsIntegration::Nel => nel::expand(&payload, headers, produce), - LogsIntegration::OtelV1 { format } => otel::expand(format, &payload, produce), + LogsIntegration::OtelV1 { format } => otel::expand(format, &payload, max_ops, produce), LogsIntegration::VercelDrainLog { format } => vercel::expand(format, &payload, produce), }; let settings = match settings { diff --git a/relay-server/src/processing/logs/integrations/otel.rs b/relay-server/src/processing/logs/integrations/otel.rs index 3d51c5b0cb0..aec29884636 100644 --- a/relay-server/src/processing/logs/integrations/otel.rs +++ b/relay-server/src/processing/logs/integrations/otel.rs @@ -7,11 +7,16 @@ use crate::processing::logs::{Error, Result, Settings}; use crate::services::outcome::DiscardReason; /// Expands OTeL logs into the [`OurLog`] format. -pub fn expand(format: OtelFormat, payload: &[u8], mut produce: F) -> Result +pub fn expand( + format: OtelFormat, + payload: &[u8], + max_ops: usize, + mut produce: F, +) -> Result where F: FnMut(OurLog), { - let logs = parse_logs_data(format, payload)?; + let logs = parse_logs_data(format, payload, max_ops)?; for resource_logs in logs.resource_logs { let resource = resource_logs.resource.as_ref(); @@ -27,15 +32,18 @@ where Ok(Settings::default()) } -fn parse_logs_data(format: OtelFormat, payload: &[u8]) -> Result { +fn parse_logs_data(format: OtelFormat, payload: &[u8], max_ops: usize) -> Result { match format { - OtelFormat::Json => serde_json::from_slice(payload).map_err(|e| { - relay_log::debug!( - error = &e as &dyn std::error::Error, - "Failed to parse logs data as JSON" - ); - Error::Invalid(DiscardReason::InvalidJson) - }), + OtelFormat::Json => { + let mut de = serde_json::Deserializer::from_reader(payload); + relay_serialization::serde::deserialize(&mut de, max_ops).map_err(|e| { + relay_log::debug!( + error = &e as &dyn std::error::Error, + "Failed to parse logs data as JSON" + ); + Error::Invalid(DiscardReason::InvalidJson) + }) + } OtelFormat::Protobuf => LogsData::decode(payload).map_err(|e| { relay_log::debug!( error = &e as &dyn std::error::Error, @@ -45,3 +53,109 @@ fn parse_logs_data(format: OtelFormat, payload: &[u8]) -> Result) -> Result, Rejected> { +pub fn expand( + logs: Managed, + max_ops: usize, +) -> Result, Rejected> { let trust = logs.headers.meta().request_trust(); logs.try_map(|logs, records| { @@ -44,7 +47,7 @@ pub fn expand(logs: Managed) -> Result, Re let (settings, logs) = match items { LogItems::Container(item) => expand_log_container(&item, trust)?, LogItems::Integration(item) => { - logs::integrations::expand(item, records, &headers).unwrap_or_default() + logs::integrations::expand(item, records, &headers, max_ops).unwrap_or_default() } }; From 3b3451344d7a0f7abc44f482007b15d7154de477 Mon Sep 17 00:00:00 2001 From: Chris Klochek Date: Fri, 28 Aug 2026 10:38:10 +0200 Subject: [PATCH 2/2] up log ops limit --- relay-config/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relay-config/src/config.rs b/relay-config/src/config.rs index 005ad2c08d6..8b8a9eea537 100644 --- a/relay-config/src/config.rs +++ b/relay-config/src/config.rs @@ -741,7 +741,7 @@ impl Default for Limits { max_profile_size: ByteSize::mebibytes(50), max_trace_metric_size: ByteSize::mebibytes(1), max_log_size: ByteSize::mebibytes(2), - max_expanded_log_operations: 20000, + max_expanded_log_operations: 2_000_000, max_span_size: ByteSize::mebibytes(10), max_standalone_span_count: 25, max_container_size: ByteSize::mebibytes(12),