Skip to content

fix: [exporter-etw] Enforce depth limit when JSON serializing complex types #238

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 88 additions & 92 deletions opentelemetry-etw-logs/src/logs/converters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,55 @@

impl IntoJson for AnyValue {
fn as_json_value(&self) -> Value {
match &self {
AnyValue::Int(value) => json!(value),
AnyValue::Double(value) => json!(value),
AnyValue::String(value) => json!(value.to_string()),
AnyValue::Boolean(value) => json!(value),
AnyValue::Bytes(_value) => todo!("No support for AnyValue::Bytes yet."),
AnyValue::ListAny(value) => value.as_json_value(),
AnyValue::Map(value) => value.as_json_value(),
&_ => Value::Null,
}
serialize_anyvalue(self, 0)
}
}

impl IntoJson for HashMap<Key, AnyValue> {
fn as_json_value(&self) -> Value {
Value::Object(
self.iter()
.map(|(k, v)| (k.to_string(), v.as_json_value()))
.collect::<Map<String, Value>>(),
)
const ERROR_MSG: &str = "Message truncated as nested lists/maps are not supported.";

fn serialize_anyvalue(value: &AnyValue, depth: usize) -> Value {
match value {
AnyValue::Int(value) => json!(value),
AnyValue::Double(value) => json!(value),
AnyValue::String(value) => json!(value.as_str()),
AnyValue::Boolean(value) => json!(value),
AnyValue::Bytes(_value) => todo!("No support for AnyValue::Bytes yet."),
AnyValue::ListAny(value) => {
if depth > 0 {
// Do not allow nested lists.
json!(ERROR_MSG)
} else {
serialize_anyvalue_slice(value, depth)
}
}
AnyValue::Map(value) => {
if depth > 0 {
// Do not allow nested maps.
json!(ERROR_MSG)
} else {
serialize_hashmap_of_anyvalue(value, depth)
}
}
&_ => Value::Null,

Check warning on line 41 in opentelemetry-etw-logs/src/logs/converters.rs

View check run for this annotation

Codecov / codecov/patch

opentelemetry-etw-logs/src/logs/converters.rs#L41

Added line #L41 was not covered by tests
}
}

impl IntoJson for [AnyValue] {
fn as_json_value(&self) -> Value {
Value::Array(self.iter().map(IntoJson::as_json_value).collect())
}
fn serialize_anyvalue_slice(value: &[AnyValue], depth: usize) -> Value {
Value::Array(
value
.iter()
.map(|v| serialize_anyvalue(v, depth + 1))
.collect(),
)
}

fn serialize_hashmap_of_anyvalue(value: &HashMap<Key, AnyValue>, depth: usize) -> Value {
Value::Object(
value
.iter()
.map(|(k, v)| (k.to_string(), serialize_anyvalue(v, depth + 1)))
.collect::<Map<String, Value>>(),
)
}

#[cfg(test)]
Expand All @@ -45,57 +67,42 @@

#[test]
fn test_convert_vec_of_any_value_to_string() {
let vec = [
let vec = vec![
AnyValue::Int(1),
AnyValue::Int(2),
AnyValue::Int(3),
AnyValue::Int(0),
AnyValue::Int(-2),
];
let result = vec.as_json_value();
let result = AnyValue::ListAny(Box::new(vec)).as_json_value();
assert_eq!(result, json!([1, 2, 3, 0, -2]));

let result = [].as_json_value();
let result = AnyValue::ListAny(Box::default()).as_json_value();
assert_eq!(result, json!([]));

let array = [AnyValue::ListAny(Box::new(vec![
AnyValue::Int(1),
AnyValue::Int(2),
AnyValue::Int(3),
]))];
let result = array.as_json_value();
assert_eq!(result, json!([[1, 2, 3]]));

let array = [
AnyValue::ListAny(Box::new(vec![AnyValue::Int(1), AnyValue::Int(2)])),
AnyValue::ListAny(Box::new(vec![AnyValue::Int(3), AnyValue::Int(4)])),
];
let result = array.as_json_value();
assert_eq!(result, json!([[1, 2], [3, 4]]));

let array = [AnyValue::Boolean(true), AnyValue::Boolean(false)];
let result = array.as_json_value();
let vec_of_bools = vec![AnyValue::Boolean(true), AnyValue::Boolean(false)];
let result = AnyValue::ListAny(Box::new(vec_of_bools)).as_json_value();
assert_eq!(result, json!([true, false]));

let array = [
let vec_of_doubles = vec![
AnyValue::Double(1.0),
AnyValue::Double(-1.0),
AnyValue::Double(0.0),
AnyValue::Double(0.1),
AnyValue::Double(-0.5),
];
let result = array.as_json_value();
let result = AnyValue::ListAny(Box::new(vec_of_doubles)).as_json_value();
assert_eq!(result, json!([1.0, -1.0, 0.0, 0.1, -0.5]));

let array = [
let vec_of_strings = vec![
AnyValue::String("".into()),
AnyValue::String("a".into()),
AnyValue::String(r#"""#.into()),
AnyValue::String(r#""""#.into()),
AnyValue::String(r#"foo bar"#.into()),
AnyValue::String(r#""foo bar""#.into()),
];
let result = array.as_json_value();
let result = AnyValue::ListAny(Box::new(vec_of_strings)).as_json_value();
assert_eq!(
result,
json!(["", "a", "\"", "\"\"", "foo bar", "\"foo bar\""])
Expand All @@ -105,11 +112,11 @@
#[test]
#[should_panic]
fn test_convert_bytes_panics() {
let array = [
let vec = vec![
AnyValue::Bytes(Box::new(vec![97u8, 98u8, 99u8])),
AnyValue::Bytes(Box::default()),
];
let result = array.as_json_value();
let result = AnyValue::ListAny(Box::new(vec)).as_json_value();
assert_eq!(result, json!(["abc", ""]));
}

Expand All @@ -121,28 +128,22 @@
map.insert(Key::new("c"), AnyValue::Int(3));
map.insert(Key::new("d"), AnyValue::Int(0));
map.insert(Key::new("e"), AnyValue::Int(-2));
let result = map.as_json_value();
let result = AnyValue::Map(Box::new(map)).as_json_value();
assert_eq!(result, json!({"a": 1, "b": 2, "c": 3, "d": 0, "e": -2}));

let map = HashMap::new();
let result = map.as_json_value();
let result = AnyValue::Map(Box::new(map)).as_json_value();
assert_eq!(result, json!({}));

let mut inner_map = HashMap::new();
inner_map.insert(Key::new("a"), AnyValue::Int(1));
inner_map.insert(Key::new("b"), AnyValue::Int(2));
inner_map.insert(Key::new("c"), AnyValue::Int(3));
let mut map = HashMap::new();
map.insert(Key::new("d"), AnyValue::Int(4));
map.insert(Key::new("e"), AnyValue::Int(5));
map.insert(Key::new("f"), AnyValue::Map(Box::new(inner_map)));
let result = map.as_json_value();
assert_eq!(result, json!({"d":4,"e":5,"f":{"a":1,"b":2,"c":3}}));

let mut map = HashMap::new();
map.insert(Key::new("True"), AnyValue::Boolean(true));
map.insert(Key::new("False"), AnyValue::Boolean(false));
let result = map.as_json_value();
let result = AnyValue::Map(Box::new(map)).as_json_value();
assert_eq!(result, json!({"True":true,"False":false}));

let mut map = HashMap::new();
Expand All @@ -151,7 +152,7 @@
map.insert(Key::new("c"), AnyValue::Double(0.0));
map.insert(Key::new("d"), AnyValue::Double(0.1));
map.insert(Key::new("e"), AnyValue::Double(-0.5));
let result = map.as_json_value();
let result = AnyValue::Map(Box::new(map)).as_json_value();
assert_eq!(result, json!({"a":1.0,"b":-1.0,"c":0.0,"d":0.1,"e":-0.5}));

let mut map = HashMap::new();
Expand All @@ -164,54 +165,49 @@
map.insert(Key::new(""), AnyValue::String(r#"empty key"#.into()));
map.insert(Key::new(r#"""#), AnyValue::String(r#"quote"#.into()));
map.insert(Key::new(r#""""#), AnyValue::String(r#"quotes"#.into()));
let result = map.as_json_value();
let result = AnyValue::Map(Box::new(map)).as_json_value();
assert_eq!(
result,
json!({"a":"","b":"a","c":"\"","d":"\"\"","e":"foo bar","f":"\"foo bar\"","":"empty key","\"":"quote","\"\"":"quotes"})
);
}

#[test]
fn test_complex_conversions() {
let mut simple_map = HashMap::new();
simple_map.insert(Key::new("a"), AnyValue::Int(1));
simple_map.insert(Key::new("b"), AnyValue::Int(2));

let empty_map: HashMap<Key, AnyValue> = HashMap::new();

let simple_vec = vec![AnyValue::Int(1), AnyValue::Int(2)];
fn enforce_depth_limit() {
let complex_vec = vec![
AnyValue::ListAny(Box::new(vec![AnyValue::Int(3), AnyValue::Int(4)])),
AnyValue::Int(42),
];
let result = AnyValue::ListAny(Box::new(complex_vec)).as_json_value();
assert_eq!(
result.to_string(),
r#"["Message truncated as nested lists/maps are not supported.",42]"#
);

let empty_vec = vec![];
let mut inner_map = HashMap::new();
inner_map.insert(Key::new("a"), AnyValue::Int(100));

let mut complex_map = HashMap::new();
complex_map.insert(Key::new("a"), AnyValue::Map(Box::new(simple_map.clone())));
complex_map.insert(Key::new("b"), AnyValue::Map(Box::new(empty_map.clone())));
complex_map.insert(
Key::new("c"),
AnyValue::ListAny(Box::new(simple_vec.clone())),
);
complex_map.insert(
Key::new("d"),
AnyValue::ListAny(Box::new(empty_vec.clone())),
complex_map.insert(Key::new("a"), AnyValue::Int(1));
complex_map.insert(Key::new("b"), AnyValue::Map(Box::new(inner_map)));
let result = AnyValue::Map(Box::new(complex_map)).as_json_value();
assert_eq!(
result.to_string(),
r#"{"a":1,"b":"Message truncated as nested lists/maps are not supported."}"#
);
let result = complex_map.as_json_value();
assert_eq!(result, json!({"a":{"a":1,"b":2},"b":{},"c":[1,2],"d":[]}));

let complex_vec = [
AnyValue::Map(Box::new(simple_map.clone())),
AnyValue::Map(Box::new(empty_map.clone())),
AnyValue::ListAny(Box::new(simple_vec.clone())),
AnyValue::ListAny(Box::new(empty_vec.clone())),
];
let result = complex_vec.as_json_value();
assert_eq!(result, json!([{"a":1,"b":2},{},[1,2],[]]));

let mut nested_complex_map = HashMap::new();
nested_complex_map.insert(Key::new("a"), AnyValue::Map(Box::new(complex_map.clone())));
let result = nested_complex_map.as_json_value();
// Construct a deeply nested list
// This will create a structure like: [[[[[[[[[[42]]]]]]]]]]
let mut current_value = AnyValue::Int(42);
for _ in 0..10 {
let vec = vec![current_value];
current_value = AnyValue::ListAny(Box::new(vec));
}

let result = current_value.as_json_value();
assert_eq!(
result,
json!({"a":{"a":{"a":1,"b":2},"b":{},"c":[1,2],"d":[]}})
result.to_string(),
r#"["Message truncated as nested lists/maps are not supported."]"#
);
}
}
8 changes: 4 additions & 4 deletions opentelemetry-etw-logs/src/logs/exporter/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@ pub fn add_attribute_to_event(event: &mut tld::EventBuilder, key: &Key, value: &
event.add_binaryc(key.as_str(), b.as_slice(), tld::OutType::Default, 0);
}
#[cfg(feature = "serde_json")]
AnyValue::ListAny(l) => {
AnyValue::ListAny(_) => {
event.add_str8(
key.as_str(),
l.as_json_value().to_string(),
value.as_json_value().to_string(),
tld::OutType::Json,
0,
);
}
#[cfg(feature = "serde_json")]
AnyValue::Map(m) => {
AnyValue::Map(_) => {
event.add_str8(
key.as_str(),
m.as_json_value().to_string(),
value.as_json_value().to_string(),
tld::OutType::Json,
0,
);
Expand Down