diff --git a/cedar-drt/README.md b/cedar-drt/README.md index 48e9a3339..4e9cd53c9 100644 --- a/cedar-drt/README.md +++ b/cedar-drt/README.md @@ -33,6 +33,7 @@ The table below lists all available fuzz targets, including which component of t | [`simple-parser`](fuzz/fuzz_targets/simple-parser.rs) | Parser | PBT | Test that parsing doesn't crash with random input strings | | [`validation-pbt`](fuzz/fuzz_targets/validation-pbt.rs) | Validator | PBT | Test that validated policies do not result in type errors | | [`validation-pbt-type-directed`](fuzz/fuzz_targets/validation-pbt-type-directed.rs) | Validator | PBT | Test that validated policies do not result in type errors using (mostly) well-typed inputs | +| [`entity-manifest-drt-type-directed`](fuzz/fuzz_targets/entity-slicing-pbt-type-directed.rs) | Entity Slicing | DRT | Test that entity slicing produces the same authorization response as without it. | | [`wildcard-matching`](fuzz/fuzz_targets/wildcard-matching.rs) | String matching algorithm used for the `like` operator | PBT | Test algorithm against a regex-based implementation | ## Logging diff --git a/cedar-drt/fuzz/Cargo.toml b/cedar-drt/fuzz/Cargo.toml index c0765b33b..7f8487ec9 100644 --- a/cedar-drt/fuzz/Cargo.toml +++ b/cedar-drt/fuzz/Cargo.toml @@ -16,7 +16,7 @@ serde_json = "1.0" cedar-drt = { version = "4.0.0", path = ".." } cedar-policy = { path = "../../cedar/cedar-policy", version = "4.*" } cedar-policy-core = { path = "../../cedar/cedar-policy-core", version = "4.*", features = ["arbitrary"] } -cedar-policy-validator = { path = "../../cedar/cedar-policy-validator", version = "4.*", features = ["arbitrary"] } +cedar-policy-validator = { path = "../../cedar/cedar-policy-validator", version = "4.*", features = ["arbitrary", "entity-manifest"] } cedar-policy-formatter = { path = "../../cedar/cedar-policy-formatter", version = "4.*" } cedar-testing = { path = "../../cedar/cedar-testing", version = "4.*" } cedar-policy-generators = { path = "../../cedar-policy-generators", version = "4.*" } @@ -109,6 +109,13 @@ path = "fuzz_targets/validation-pbt-type-directed.rs" test = false doc = false +[[bin]] +name = "entity-slicing-drt-type-directed" +path = "fuzz_targets/entity-slicing-drt-type-directed.rs" +test = false +doc = false + + [[bin]] name = "validation-drt" path = "fuzz_targets/validation-drt.rs" diff --git a/cedar-drt/fuzz/fuzz_targets/entity-slicing-drt-type-directed.rs b/cedar-drt/fuzz/fuzz_targets/entity-slicing-drt-type-directed.rs new file mode 100644 index 000000000..8ff27f188 --- /dev/null +++ b/cedar-drt/fuzz/fuzz_targets/entity-slicing-drt-type-directed.rs @@ -0,0 +1,154 @@ +/* + * 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::initialize_log; +use cedar_drt_inner::*; +use cedar_policy_core::ast; +use cedar_policy_core::authorizer::Authorizer; +use cedar_policy_core::entities::Entities; +use cedar_policy_generators::{ + abac::{ABACPolicy, ABACRequest}, + hierarchy::{Hierarchy, HierarchyGenerator}, + schema::Schema, + settings::ABACSettings, +}; +use cedar_policy_validator::entity_manifest::compute_entity_manifest; +use cedar_policy_validator::{ValidationMode, Validator, ValidatorSchema}; +use libfuzzer_sys::arbitrary::{self, Arbitrary, Unstructured}; +use log::debug; +use serde::Serialize; +use std::convert::TryFrom; + +/// Input expected by this fuzz target: +/// An ABAC hierarchy, schema, and 8 associated policies +#[derive(Debug, Clone, Serialize)] +struct FuzzTargetInput { + /// generated schema + #[serde(skip)] + pub schema: Schema, + /// generated hierarchy + #[serde(skip)] + pub hierarchy: Hierarchy, + /// the policy which we will see if it validates + pub policy: ABACPolicy, + /// the requests to try, if the policy validates. + /// We try 8 requests per validated policy. + #[serde(skip)] + pub requests: [ABACRequest; 8], +} + +/// settings for this fuzz target +const SETTINGS: ABACSettings = ABACSettings { + match_types: true, + enable_extensions: true, + max_depth: 7, + max_width: 7, + enable_additional_attributes: true, + 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 = Schema::arbitrary(SETTINGS.clone(), u)?; + let hierarchy = schema.arbitrary_hierarchy(u)?; + let policy = schema.arbitrary_policy(&hierarchy, u)?; + let requests = [ + schema.arbitrary_request(&hierarchy, u)?, + schema.arbitrary_request(&hierarchy, u)?, + schema.arbitrary_request(&hierarchy, u)?, + schema.arbitrary_request(&hierarchy, u)?, + schema.arbitrary_request(&hierarchy, u)?, + schema.arbitrary_request(&hierarchy, u)?, + schema.arbitrary_request(&hierarchy, u)?, + schema.arbitrary_request(&hierarchy, u)?, + ]; + Ok(Self { + schema, + hierarchy, + policy, + requests, + }) + } + + fn try_size_hint( + depth: usize, + ) -> arbitrary::Result<(usize, Option), arbitrary::MaxRecursionReached> { + Ok(arbitrary::size_hint::and_all(&[ + Schema::arbitrary_size_hint(depth)?, + HierarchyGenerator::size_hint(depth), + Schema::arbitrary_policy_size_hint(&SETTINGS, depth), + Schema::arbitrary_request_size_hint(depth), + Schema::arbitrary_request_size_hint(depth), + Schema::arbitrary_request_size_hint(depth), + Schema::arbitrary_request_size_hint(depth), + Schema::arbitrary_request_size_hint(depth), + Schema::arbitrary_request_size_hint(depth), + Schema::arbitrary_request_size_hint(depth), + Schema::arbitrary_request_size_hint(depth), + ])) + } +} + +/// helper function that just tells us whether a policyset passes validation +fn passes_validation(validator: &Validator, policyset: &ast::PolicySet) -> bool { + validator + .validate(policyset, ValidationMode::default()) + .validation_passed() +} + +// The main fuzz target. This is for PBT on the validator +fuzz_target!(|input: FuzzTargetInput| { + initialize_log(); + if let Ok(schema) = ValidatorSchema::try_from(input.schema) { + debug!("Schema: {:?}", schema); + if let Ok(entities) = Entities::try_from(input.hierarchy.clone()) { + let validator = Validator::new(schema.clone()); + let mut policyset = ast::PolicySet::new(); + let policy: ast::StaticPolicy = input.policy.into(); + policyset.add_static(policy.clone()).unwrap(); + if passes_validation(&validator, &policyset) { + // policy successfully validated, do entity slicing + let manifest = compute_entity_manifest(&schema, &policyset) + .expect("failed to produce entity manifest"); + + let authorizer = Authorizer::new(); + debug!("Policies: {policyset}"); + debug!("Entities: {entities}"); + for abac_request in input.requests.into_iter() { + let request = ast::Request::from(abac_request); + debug!("Request: {request}"); + let entity_slice = manifest + .slice_entities(&entities, &request) + .expect("failed to slice entities"); + debug!("Entity slice: {entity_slice}"); + let ans_original = + authorizer.is_authorized(request.clone(), &policyset, &entities); + let ans_slice = authorizer.is_authorized(request, &policyset, &entity_slice); + assert_eq!( + ans_original.decision, ans_slice.decision, + "Authorization decision differed with and without entity slicing!" + ); + } + } + } + } +});