From 394acc5aa825249ff0073e5c5bac9aa05ea3c856 Mon Sep 17 00:00:00 2001 From: John Kastner <130772734+john-h-kastner-aws@users.noreply.github.com> Date: Thu, 21 Nov 2024 09:16:56 -0500 Subject: [PATCH] Add roundtrip targets for entity data (#481) Signed-off-by: John Kastner Co-authored-by: Craig Disselkoen --- cedar-drt/README.md | 2 + cedar-drt/fuzz/Cargo.toml | 12 ++ .../fuzz_targets/roundtrip-entities-bytes.rs | 50 ++++++++ .../fuzz/fuzz_targets/roundtrip-entities.rs | 118 ++++++++++++++++++ cedar-drt/initialize_corpus.sh | 8 ++ 5 files changed, 190 insertions(+) create mode 100644 cedar-drt/fuzz/fuzz_targets/roundtrip-entities-bytes.rs create mode 100644 cedar-drt/fuzz/fuzz_targets/roundtrip-entities.rs diff --git a/cedar-drt/README.md b/cedar-drt/README.md index 590b466cd..25d2a9903 100644 --- a/cedar-drt/README.md +++ b/cedar-drt/README.md @@ -28,6 +28,8 @@ The table below lists all available fuzz targets, including which component of t | [`convert-schema-human-to-json`](fuzz/fuzz_targets/convert-schema-human-to-json.rs) | Schema parser | PBT | Test we can convert all JSON schemas to an equivalent human format schema. parse-json == parse ∘ pretty-print ∘ parse-json | [`convert-policy-cedar-to-json`](fuzz/fuzz_targets/convert-policy-cedar-to-json.rs) | Parser, Conversion to JSON | PBT | Test we can convert all policies to an equivalent EST. parse-ast ∘ parse-cst == deserialize ∘ serialize ∘ parse-cst | [`convert-policy-json-to-cedar`](fuzz/fuzz_targets/convert-policy-json-to-cedar.rs) | Parser, JSON Parser | PBT | Test we can convert all EST to an equivalent policy in the human-readable cedar syntax. deserialize == parse-ast ∘ pretty-print ∘ deserialize +| [`roundtrip-entities`](fuzz/fuzz_targets/roundtrip-entities.rs) | Entity parser | PBT | Test round trip property for entity data. parse-entity-json ∘ serialize-entity == id for entities | +| [`roundtrip-entities-bytes`](fuzz/fuzz_targets/roundtrip-entities.rs) | Entity parser | PBT | Test the same round trip property for entity data, starting from an arbitrary string instead of generating the entities data structure | | | | | | | [`partial-eval`](fuzz/fuzz_targets/partial-eval.rs) | Partial evaluator | PBT | Test that residual policies with unknowns substituted are equivalent to original policies with unknowns replaced | | [`simple-parser`](fuzz/fuzz_targets/simple-parser.rs) | Parser | PBT | Test that parsing doesn't crash with random input strings | diff --git a/cedar-drt/fuzz/Cargo.toml b/cedar-drt/fuzz/Cargo.toml index 7f8487ec9..d0a5d7afd 100644 --- a/cedar-drt/fuzz/Cargo.toml +++ b/cedar-drt/fuzz/Cargo.toml @@ -199,3 +199,15 @@ name = "request-validation" path = "fuzz_targets/request-validation.rs" test = false doc = false + +[[bin]] +name = "roundtrip-entities" +path = "fuzz_targets/roundtrip-entities.rs" +test = false +doc = false + +[[bin]] +name = "roundtrip-entities-bytes" +path = "fuzz_targets/roundtrip-entities-bytes.rs" +test = false +doc = false diff --git a/cedar-drt/fuzz/fuzz_targets/roundtrip-entities-bytes.rs b/cedar-drt/fuzz/fuzz_targets/roundtrip-entities-bytes.rs new file mode 100644 index 000000000..0249db5a5 --- /dev/null +++ b/cedar-drt/fuzz/fuzz_targets/roundtrip-entities-bytes.rs @@ -0,0 +1,50 @@ +/* + * Copyright Cedar Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![no_main] +use cedar_drt::{ + entities::{EntityJsonParser, NoEntitiesSchema, TCComputation}, + extensions::Extensions, +}; +use cedar_policy::{entities_errors::EntitiesError, entities_json_errors::JsonSerializationError}; +use libfuzzer_sys::fuzz_target; +use similar_asserts::assert_eq; + +fuzz_target!(|input: String| { + let eparser: EntityJsonParser<'_, '_, NoEntitiesSchema> = + EntityJsonParser::new(None, Extensions::all_available(), TCComputation::ComputeNow); + let Ok(entities) = eparser.from_json_str(&input) else { + return; + }; + let json = match entities.to_json_value() { + Ok(json) => json, + Err(EntitiesError::Serialization(JsonSerializationError::ReservedKey(_))) => { + // Serializing to JSON is expected to fail when there's a record + // attribute `__entity`, `__expr`, or `__extn` + return; + } + _ => panic!("Should be able to serialize entities to JSON"), + }; + let eparser: EntityJsonParser<'_, '_, NoEntitiesSchema> = EntityJsonParser::new( + None, + Extensions::all_available(), + TCComputation::EnforceAlreadyComputed, + ); + let rountripped = eparser + .from_json_value(json) + .expect("Should parse serialized entities JSON"); + assert_eq!(entities, rountripped); +}); diff --git a/cedar-drt/fuzz/fuzz_targets/roundtrip-entities.rs b/cedar-drt/fuzz/fuzz_targets/roundtrip-entities.rs new file mode 100644 index 000000000..58b3793c5 --- /dev/null +++ b/cedar-drt/fuzz/fuzz_targets/roundtrip-entities.rs @@ -0,0 +1,118 @@ +/* + * Copyright Cedar Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#![no_main] + +use cedar_drt::{ + entities::{EntityJsonParser, NoEntitiesSchema, TCComputation}, + extensions::Extensions, + Entities, ValidatorSchema, +}; +use cedar_policy::{entities_errors::EntitiesError, entities_json_errors::JsonSerializationError}; +use cedar_policy_generators::{ + hierarchy::HierarchyGenerator, schema::Schema, settings::ABACSettings, +}; +use cedar_policy_validator::CoreSchema; +use libfuzzer_sys::{ + arbitrary::{self, Arbitrary, MaxRecursionReached, Unstructured}, + fuzz_target, +}; +use similar_asserts::assert_eq; + +#[derive(Debug)] +struct FuzzTargetInput { + pub schema: ValidatorSchema, + pub entities: Entities, +} + +const SETTINGS: ABACSettings = ABACSettings { + match_types: true, + enable_extensions: true, + max_depth: 10, + max_width: 10, + enable_additional_attributes: false, + enable_like: true, + enable_action_groups_and_attrs: true, + enable_arbitrary_func_call: true, + enable_unknowns: false, + enable_action_in_constraints: true, + enable_unspecified_apply_spec: true, +}; + +impl<'a> Arbitrary<'a> for FuzzTargetInput { + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let schema = Schema::arbitrary(SETTINGS.clone(), u)?; + let entities: Entities = schema + .arbitrary_hierarchy(u)? + .try_into() + .expect("Should be able to get entities from hierarchy."); + let validator_schema: ValidatorSchema = schema + .try_into() + .expect("Should be able to get validator schema from schema."); + Ok(Self { + entities, + schema: validator_schema, + }) + } + + fn try_size_hint( + depth: usize, + ) -> std::result::Result<(usize, Option), MaxRecursionReached> { + Ok(arbitrary::size_hint::and_all(&[ + Schema::arbitrary_size_hint(depth)?, + HierarchyGenerator::size_hint(depth), + ])) + } +} + +fuzz_target!(|input: FuzzTargetInput| { + let json = match input.entities.to_json_value() { + Ok(json) => json, + Err(EntitiesError::Serialization(JsonSerializationError::ReservedKey(_))) => { + // Serializing to JSON is expected to fail when there's a record + // attribute `__entity`, `__expr`, or `__extn` + return; + } + _ => panic!("Should be able to serialize entities to JSON"), + }; + + let eparser: EntityJsonParser<'_, '_, NoEntitiesSchema> = EntityJsonParser::new( + None, + Extensions::all_available(), + TCComputation::EnforceAlreadyComputed, + ); + let roundtripped_entities = eparser + .from_json_value(json.clone()) + .expect("Should be able to parse serialized entity JSON"); + assert_eq!(input.entities, roundtripped_entities); + + let core_schema = CoreSchema::new(&input.schema); + let eparser: EntityJsonParser<'_, '_, _> = EntityJsonParser::new( + Some(&core_schema), + Extensions::all_available(), + TCComputation::EnforceAlreadyComputed, + ); + let roundtripped_entities = eparser + .from_json_value(json.clone()) + .expect("Should be able to parse serialized entity JSON"); + // Weaker assertion for schema based parsing because it adds actions from the schema into entities. + for e in input.entities { + let roundtripped_e = roundtripped_entities + .entity(e.uid()) + .expect("Schema-based roundtrip dropped entity"); + assert_eq!(&e, roundtripped_e); + } +}); diff --git a/cedar-drt/initialize_corpus.sh b/cedar-drt/initialize_corpus.sh index 4c6b1810f..2b26043d9 100755 --- a/cedar-drt/initialize_corpus.sh +++ b/cedar-drt/initialize_corpus.sh @@ -62,6 +62,14 @@ initialize_corpus() { cp "$f" "$corpus_dir/$(uuidgen)-$(basename "$f")" done ;; + roundtrip-entities-bytes) + for f in ../cedar/**/entity.json; do + cp "$f" "$corpus_dir/$(uuidgen)-$(basename "$f")" + done + for f in ../cedar/**/entities.json; do + cp "$f" "$corpus_dir/$(uuidgen)-$(basename "$f")" + done ;; + *) echo "Nothing to do for fuzz target $1" return ;;