Skip to content
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

Add scripts to generate corpus #192

Merged
merged 10 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion cedar
Submodule cedar updated 459 files
9 changes: 9 additions & 0 deletions cedar-drt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ The table below lists all available fuzz targets, including which component of t
If the fuzz targets are compiled with the `log` features, then they will log their entire corpus to the file pointed at in the `LOGFILE` environment variable.
The sampling rate can be controlled by the `RATE` environment variable, which defaults to 100% if not set.

## Generating corpus tests

When using the `abac` or `abac-type-directed` targets, you can set `DUMP_TEST_DIR` and `DUMP_TEST_NAME` to have the fuzzer write out inputs in the format used by our [integration tests](https://github.com/cedar-policy/cedar/tree/main/cedar-integration-tests).
The `create_corpus.sh` script will run the fuzzer for a set amount of time and then write the (minimized) corpus inputs into a folder using the integration test format.
You can adjust the script's behavior using the following environment variables:
* `FUZZ_TARGET`: `abac` or `abac-type-directed` (default = `abac`)
* `TIMEOUT`: how long to run (default = 15m)
* `JOBS`: number of jobs (default = 4)
* `DUMP_DIR`: where to write the results (default = `./corpus_tests`)

## Debugging build failures

Expand Down
14 changes: 14 additions & 0 deletions cedar-drt/create_corpus.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

FUZZ_TARGET="${FUZZ_TARGET:-abac}"
TIMEOUT="${TIMEOUT:-15m}" # 15m = 900s
JOBS="${JOBS:-4}"
DUMP_DIR="${DUMP_DIR:-corpus_tests}"

# Assumes cedar-drt is already correctly built and configured
rm -rf $DUMP_DIR
source set_env_vars.sh
export FUZZ_TARGET
export DUMP_DIR
timeout $TIMEOUT cargo fuzz run -s none $FUZZ_TARGET -j $JOBS -- -rss_limit_mb=8196
./synthesize_tests.sh
37 changes: 16 additions & 21 deletions cedar-drt/fuzz/fuzz_targets/abac_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
use cedar_drt::*;
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::{ValidationMode, Validator};
use libfuzzer_sys::arbitrary::{self, Arbitrary, Unstructured};
use log::{debug, info};
use serde::Serialize;
Expand Down Expand Up @@ -106,50 +106,45 @@ impl<'a> Arbitrary<'a> for FuzzTargetInput {
}
}

/// 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()
}

// Simple fuzzing of ABAC hierarchy/policy/requests without respect to types.
// `def_impl` is a custom implementation to test against `cedar-policy`.
pub fn fuzz(input: FuzzTargetInput, def_impl: &impl CedarTestImplementation) {
initialize_log();
if let Ok(entities) = Entities::try_from(input.hierarchy) {
let mut policyset = ast::PolicySet::new();
policyset.add_static(input.policy.into()).unwrap();
let policy: ast::StaticPolicy = input.policy.into();
policyset.add_static(policy.clone()).unwrap();
debug!("Policies: {policyset}");
debug!("Entities: {entities}");
let requests = input
.requests
.into_iter()
.map(Into::into)
.collect::<Vec<_>>();
let mut responses = Vec::with_capacity(requests.len());

for request in requests.iter().cloned() {
debug!("Request: {request}");
let (ans, total_dur) =
let (_, total_dur) =
time_function(|| run_auth_test(def_impl, request, &policyset, &entities));
info!("{}{}", TOTAL_MSG, total_dur.as_nanos());
responses.push(ans);
}
if let Ok(test_name) = std::env::var("DUMP_TEST_NAME") {
let passes_validation = {
if let Ok(schema) = ValidatorSchema::try_from(input.schema.clone()) {
let validator = Validator::new(schema);
passes_validation(&validator, &policyset)
} else {
false
}
};
// When the corpus is re-parsed, the policy will be given id "policy0".
// Recreate the policy set and compute responses here to account for this.
let mut policyset = ast::PolicySet::new();
let policy = policy.new_id(ast::PolicyID::from_string("policy0"));
policyset.add_static(policy).unwrap();
let mut responses = Vec::with_capacity(requests.len());
for request in requests.iter() {
let authorizer = Authorizer::new();
let response = authorizer.is_authorized(request.clone(), &policyset, &entities);
responses.push(response);
}
let dump_dir = std::env::var("DUMP_TEST_DIR").unwrap_or_else(|_| ".".to_string());
dump(
dump_dir,
&test_name,
&input.schema.into(),
passes_validation,
&policyset,
&entities,
std::iter::zip(requests.iter(), responses.iter()),
Expand Down
37 changes: 35 additions & 2 deletions cedar-drt/fuzz/fuzz_targets/abac_type_directed_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use cedar_drt::*;
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},
Expand Down Expand Up @@ -111,11 +112,19 @@ impl<'a> Arbitrary<'a> for FuzzTargetInput {
pub fn fuzz(input: FuzzTargetInput, def_impl: &impl CedarTestImplementation) {
initialize_log();
let mut policyset = ast::PolicySet::new();
policyset.add_static(input.policy.into()).unwrap();
let policy: ast::StaticPolicy = input.policy.into();
policyset.add_static(policy.clone()).unwrap();
debug!("Schema: {}\n", input.schema.schemafile_string());
debug!("Policies: {policyset}\n");
debug!("Entities: {}\n", input.entities);
for request in input.requests.into_iter().map(Into::into) {

let requests = input
.requests
.into_iter()
.map(Into::into)
.collect::<Vec<_>>();

for request in requests.iter().cloned() {
debug!("Request : {request}");
let (rust_res, total_dur) =
time_function(|| run_auth_test(def_impl, request, &policyset, &input.entities));
Expand All @@ -135,4 +144,28 @@ pub fn fuzz(input: FuzzTargetInput, def_impl: &impl CedarTestImplementation) {
Vec::<String>::new()
);
}

if let Ok(test_name) = std::env::var("DUMP_TEST_NAME") {
// When the corpus is re-parsed, the policy will be given id "policy0".
// Recreate the policy set and compute responses here to account for this.
let mut policyset = ast::PolicySet::new();
let policy = policy.new_id(ast::PolicyID::from_string("policy0"));
policyset.add_static(policy).unwrap();
let mut responses = Vec::with_capacity(requests.len());
for request in requests.iter() {
let authorizer = Authorizer::new();
let response = authorizer.is_authorized(request.clone(), &policyset, &input.entities);
responses.push(response);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, would it be possible to share some of this code, which is currently duplicated. (doesn't have to be in this PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reformatted in the latest commit. I didn't deal with the code reuse, but you're right that would be cleaner

let dump_dir = std::env::var("DUMP_TEST_DIR").unwrap_or_else(|_| ".".to_string());
dump(
dump_dir,
&test_name,
&input.schema.into(),
&policyset,
&input.entities,
std::iter::zip(requests.iter(), responses.iter()),
)
.expect("failed to dump test case");
}
}
58 changes: 34 additions & 24 deletions cedar-drt/fuzz/fuzz_targets/validation-pbt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
use cedar_drt::initialize_log;
use cedar_drt_inner::*;
use cedar_policy_core::ast;
use cedar_policy_core::authorizer::Authorizer;
use cedar_policy_core::authorizer::{AuthorizationError, Authorizer};
use cedar_policy_core::entities::Entities;
use cedar_policy_core::evaluator::EvaluationErrorKind;
use cedar_policy_generators::{
abac::{ABACPolicy, ABACRequest},
err::{Error, Result},
Expand Down Expand Up @@ -356,30 +357,39 @@ fuzz_target!(|input: FuzzTargetInput| {
debug!("Request: {q}");
let ans = authorizer.is_authorized(q.clone(), &policyset, &entities);

let unexpected_errs = ans.diagnostics.errors.iter().filter_map(|error|
match error {
cedar_policy::AuthorizationError::PolicyEvaluationError { error, .. } => match error.error_kind() {
// Evaluation errors the validator should prevent.
cedar_policy::EvaluationErrorKind::UnspecifiedEntityAccess(_) |
cedar_policy::EvaluationErrorKind::RecordAttrDoesNotExist(_, _) |
cedar_policy::EvaluationErrorKind::EntityAttrDoesNotExist { .. } |
cedar_policy::EvaluationErrorKind::FailedExtensionFunctionLookup(_) |
cedar_policy::EvaluationErrorKind::TypeError { .. } |
cedar_policy::EvaluationErrorKind::WrongNumArguments { .. } => Some(error.to_string()),
// Evaluation errors it shouldn't prevent. Not
// written with a catch all so that we must
// consider if a new error type should cause
// this target to fail.
cedar_policy::EvaluationErrorKind::EntityDoesNotExist(_) |
cedar_policy::EvaluationErrorKind::IntegerOverflow(_) |
cedar_policy::EvaluationErrorKind::InvalidRestrictedExpression(_) |
cedar_policy::EvaluationErrorKind::UnlinkedSlot(_) |
cedar_policy::EvaluationErrorKind::FailedExtensionFunctionApplication { .. } |
cedar_policy::EvaluationErrorKind::NonValue(_) |
cedar_policy::EvaluationErrorKind::RecursionLimit => None,
let unexpected_errs = ans
.diagnostics
.errors
.iter()
.filter_map(|error| match error {
AuthorizationError::PolicyEvaluationError { error, .. } => {
match error.error_kind() {
// Evaluation errors the validator should prevent.
EvaluationErrorKind::UnspecifiedEntityAccess(_)
| EvaluationErrorKind::RecordAttrDoesNotExist(_, _)
| EvaluationErrorKind::EntityAttrDoesNotExist { .. }
| EvaluationErrorKind::FailedExtensionFunctionLookup(_)
| EvaluationErrorKind::TypeError { .. }
| EvaluationErrorKind::WrongNumArguments { .. } => {
Some(error.to_string())
}
// Evaluation errors it shouldn't prevent. Not
// written with a catch all so that we must
// consider if a new error type should cause
// this target to fail.
EvaluationErrorKind::EntityDoesNotExist(_)
| EvaluationErrorKind::IntegerOverflow(_)
| EvaluationErrorKind::InvalidRestrictedExpression(_)
| EvaluationErrorKind::UnlinkedSlot(_)
| EvaluationErrorKind::FailedExtensionFunctionApplication {
..
}
| EvaluationErrorKind::NonValue(_)
| EvaluationErrorKind::RecursionLimit => None,
}
}
}
).collect::<Vec<_>>();
})
.collect::<Vec<_>>();

assert_eq!(
unexpected_errs,
Expand Down
Loading
Loading