Skip to content

Commit

Permalink
Added a test case for buggy atomic
Browse files Browse the repository at this point in the history
  • Loading branch information
JustusAdam committed Mar 17, 2024
1 parent 8e52c26 commit 42d22fa
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 47 deletions.
211 changes: 165 additions & 46 deletions crates/paralegal-policy/tests/atomic.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod helpers;

use std::sync::Arc;

use helpers::Test;

use anyhow::Result;
use paralegal_policy::{assert_error, assert_warning, Diagnostics as _, EdgeSelection};
use paralegal_policy::{assert_error, assert_warning, Context, Diagnostics as _, EdgeSelection};
use paralegal_spdg::Identifier;

macro_rules! marker {
Expand Down Expand Up @@ -106,56 +108,173 @@ fn not_influenced_by_commit() -> Result<()> {
}
))?;

test.run(|ctx| {
let commits = ctx.marked_nodes(marker!(commit));
let mut any_sink_reached = false;
let results = commits.filter_map(|commit| {
let check_rights = marker!(check_rights);
// If commit is stored
let stores = ctx.influencees(commit, EdgeSelection::Both)
.filter(|s| ctx.has_marker(marker!(sink), *s))
.collect::<Box<[_]>>();
if stores.is_empty() {
return None;
}
any_sink_reached = true;

let new_resources = ctx.influencees(commit, EdgeSelection::Data)
.filter(|n| ctx.has_marker(marker!(new_resource), *n))
.collect::<Box<[_]>>();

// All checks that flow from the commit but not from a new_resource
let valid_checks = ctx.influencees(commit, EdgeSelection::Data)
.filter(|check|
ctx.has_marker(check_rights, *check)
&& new_resources.iter().all(|r| !ctx.flows_to(*r, *check, EdgeSelection::Data)))
.collect::<Box<[_]>>();

Some(stores.iter().copied().map(|store| {
(store, valid_checks.iter().copied().find(|check| ctx.successors(store).any(|cs| ctx.has_ctrl_influence(*check, cs))))
}).collect::<Box<[_]>>())
});

let likely_result = results.max_by_key(|checks| checks.iter().filter(|(_, v)| v.is_some()).count());

if let Some(checks) = likely_result {
for (store, check) in checks.iter().copied() {
if let Some(check) = check {
let mut msg = ctx.struct_node_note(store, "This store is properly checked");
msg.with_node_note(check, "With this check");
} else {
ctx.node_error(store, "This store is not protected");
}
test.run(atomic_policy)
}

fn atomic_policy(ctx: Arc<Context>) -> Result<()> {
let commits = ctx.marked_nodes(marker!(commit));
let mut any_sink_reached = false;
let results = commits.filter_map(|commit| {
let check_rights = marker!(check_rights);
// If commit is stored
let stores = ctx
.influencees(commit, EdgeSelection::Both)
.filter(|s| ctx.has_marker(marker!(sink), *s))
.collect::<Box<[_]>>();
if stores.is_empty() {
return None;
}
any_sink_reached = true;

let new_resources = ctx
.influencees(commit, EdgeSelection::Data)
.filter(|n| ctx.has_marker(marker!(new_resource), *n))
.collect::<Box<[_]>>();

// All checks that flow from the commit but not from a new_resource
let valid_checks = ctx
.influencees(commit, EdgeSelection::Data)
.filter(|check| {
ctx.has_marker(check_rights, *check)
&& new_resources
.iter()
.all(|r| !ctx.flows_to(*r, *check, EdgeSelection::Data))
})
.collect::<Box<[_]>>();

Some(
stores
.iter()
.copied()
.map(|store| {
(
store,
valid_checks.iter().copied().find(|check| {
ctx.successors(store)
.any(|cs| ctx.has_ctrl_influence(*check, cs))
}),
)
})
.collect::<Box<[_]>>(),
)
});

let likely_result =
results.max_by_key(|checks| checks.iter().filter(|(_, v)| v.is_some()).count());

if let Some(checks) = likely_result {
for (store, check) in checks.iter().copied() {
if let Some(check) = check {
let mut msg = ctx.struct_node_note(store, "This store is properly checked");
msg.with_node_note(check, "With this check");
} else {
ctx.node_error(store, "This store is not protected");
}
} else {
ctx.error("No results at all. No controllers?")
}
assert_error!(
} else {
ctx.error("No results at all. No controllers?")
}
assert_error!(
ctx,
any_sink_reached,
"No sink was reached across controllers, the policy may be vacuous or the markers not correctly assigned/unreachable."
);

Ok(())
})
Ok(())
}

#[test]
fn policy_fail() -> Result<()> {
let mut test = Test::new(stringify!(
type AtomicResult<A> = Result<A, String>;
type Value = String;

#[derive(Clone)]
struct Commit {
subject: String,
set: Option<std::collections::HashMap<String, Value>>,
signer: String,
}

trait Storelike {
#[paralegal::marker(sink, arguments = [1])]
fn add_resource<T>(&self, t: T) -> AtomicResult<()>;

#[paralegal::marker(resource, return)]
fn get_resource(&self, subject: &str) -> AtomicResult<Resource>;
}

struct Resource {
subject: String
}

#[paralegal::marker(check_rights, arguments = [1])]
fn check_write(
store: &impl Storelike,
resource: &Resource,
agent: String,
) -> AtomicResult<bool> {
Ok(true)
}

impl Resource {
#[paralegal::marker(new_resource, return)]
fn set_propval(
&mut self,
property: String,
value: Value,
store: &impl Storelike
) -> AtomicResult<()> {
Ok(())
}

fn new(subject: String) -> Self {
Self { subject }
}
}

impl Commit {
fn into_resource(self, s: &impl Storelike) -> AtomicResult<Resource> {
Ok(Resource { subject: self.subject })
}

#[paralegal::marker(safe, return)]
fn modify_parent<T, Q>(&self, t: T, q: Q) {}

#[paralegal::analyze]
#[paralegal::marker(commit, arguments = [0])]
pub fn apply_opts(
&self,
store: &impl Storelike,
validate_schema: bool,
validate_signature: bool,
validate_timestamp: bool,
validate_rights: bool,
) -> AtomicResult<Resource> {
let commit_resource: Resource = self.clone().into_resource(store)?;
let mut resource = match store.get_resource(&self.subject) {
Ok(rs) => rs,
Err(_) => Resource::new(self.subject.clone()),
};
if let Some(set) = self.set.clone() {
for (prop, val) in set.iter() {
resource.set_propval(prop.into(), val.to_owned(), store)?;
}
}
if validate_rights {
self.modify_parent(&mut resource, store);
if !check_write(store, &resource, self.signer.clone())? {
return Err("".to_string());
}
}
store.add_resource(&commit_resource)?;
store.add_resource(&resource)?;
Ok(commit_resource)
}
}
))?;

test.expect_fail();

test.run(atomic_policy)
}
8 changes: 7 additions & 1 deletion crates/paralegal-policy/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub struct Test {
tool_path: &'static Path,
external_ann_file_name: PathBuf,
cleanup: bool,
expect_fail: bool,
}

fn ensure_run_success(cmd: &mut Command) -> Result<()> {
Expand All @@ -77,9 +78,14 @@ impl Test {
tool_path: &*TOOL_BUILT,
deps: Default::default(),
cleanup: true,
expect_fail: false,
})
}

pub fn expect_fail(&mut self) {
self.expect_fail = true;
}

#[allow(dead_code)]
pub fn with_cleanup(&mut self, cleanup: bool) -> &mut Self {
self.cleanup = cleanup;
Expand Down Expand Up @@ -190,7 +196,7 @@ impl Test {
self.tempdir.display(),
ret.stats
);
ensure!(ret.success);
ensure!(self.expect_fail ^ ret.success);
if self.cleanup {
fs::remove_dir_all(self.tempdir)?;
}
Expand Down

0 comments on commit 42d22fa

Please sign in to comment.