scyther/src/modelchecker.c

1451 lines
33 KiB
C
Raw Normal View History

2004-07-19 14:31:44 +01:00
/*!\file modelchecker.c
* \brief The main procedures guiding the (optimized) traversal of the state space.
*
* This file implements various traversal methods through the state space.
*/
2004-04-23 11:58:43 +01:00
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include "substitution.h"
2004-04-23 11:58:43 +01:00
#include "knowledge.h"
#include "system.h"
2004-04-23 11:58:43 +01:00
#include "debug.h"
#include "modelchecker.h"
#include "report.h"
#include "memory.h"
#include "match_basic.h"
#include "match_clp.h"
#include "output.h"
#include "tracebuf.h"
#include "attackminimize.h"
#include "claim.h"
#include "switches.h"
#include "specialterm.h"
2004-04-23 11:58:43 +01:00
/*
A model checker. Really.
*/
/*
Some forward declarations.
*/
__inline__ int traverseSimple (const System oldsys);
__inline__ int traverseNonReads (const System oldsys);
__inline__ int traversePOR4 (const System oldsys);
__inline__ int traversePOR5 (const System oldsys);
__inline__ int traversePOR6 (const System oldsys);
__inline__ int traversePOR7 (const System oldsys);
__inline__ int traversePOR8 (const System oldsys);
2004-04-23 11:58:43 +01:00
int propertyCheck (const System sys);
int executeTry (const System sys, int run);
int claimSecrecy (const System sys, const Term t);
int violateClaim (const System sys, int length, int claimev, Termlist reqt);
Termlist secrecyUnfolding (Term t, const Knowledge know);
/*
Main code.
*/
void
statePrint (const System sys)
{
int i, s;
Roledef rd;
indent ();
printf ("state %i: ", sys->step);
for (i = 0; i < sys->maxruns; i++)
{
s = 0;
rd = runPointerGet (sys, i);
while (rd != NULL)
{
rd = rd->next;
s++;
}
printf ("%i ", s);
}
printf (" - phase %i, done %i", sys->PORphase, sys->PORdone);
printf ("\n");
}
//! Scenario selection makes sure implicit and explicit chooses are selected first.
/**
* This help function traverses any chooses first.
*/
__inline__ int
traverse_chooses_first (const System sys)
{
int run_scan;
for (run_scan = 0; run_scan < sys->maxruns; run_scan++)
{
Roledef rd_scan;
rd_scan = runPointerGet (sys, run_scan);
if (rd_scan != NULL && // Not empty run
rd_scan == sys->runs[run_scan].start && // First event
rd_scan->type == READ) // Read
{
if (executeTry (sys, run_scan))
return 1;
}
}
return 0;
}
//! Main traversal call.
/**
* Branches into submethods.
*/
2004-04-23 11:58:43 +01:00
int
traverse (const System sys)
{
/* maybe chooses have precedence over _all_ methods */
if (switches.chooseFirst)
{
if (traverse_chooses_first (sys))
return 1;
}
2004-04-23 11:58:43 +01:00
/* branch for traversal methods */
switch (switches.traverse)
2004-04-23 11:58:43 +01:00
{
case 1:
return traverseSimple (sys);
case 2:
return traverseNonReads (sys);
case 3:
case 4:
case 5:
case 6:
case 7:
error ("%i is an obsolete traversal method.", switches.traverse);
2004-04-23 11:58:43 +01:00
case 8:
return traversePOR4 (sys);
case 9:
return traversePOR5 (sys);
case 10:
return traversePOR6 (sys);
case 11:
return traversePOR7 (sys);
case 12:
return traversePOR8 (sys);
2004-04-23 11:58:43 +01:00
default:
error ("%i is NOT an existing traversal method.", switches.traverse);
2004-04-23 11:58:43 +01:00
}
}
//! Progress counters to next step.
/**
2004-04-23 11:58:43 +01:00
* Does not really execute anything, it's just bookkeeping, progressing
* counters and such.
*
*@returns If it returns TRUE, explore. If false, don't traverse.
2004-04-23 11:58:43 +01:00
*/
int
executeStep (const System sys, const int run)
{
Roledef runPoint;
runPoint = runPointerGet (sys, run);
#ifdef DEBUG
if (DEBUGL (3))
{
indent ();
printf ("exec: ");
roledefPrint (runPoint);
printf ("#%i\n", run);
}
#endif
2004-07-14 07:55:05 +01:00
sys->runs[run].step++;
2004-04-23 11:58:43 +01:00
runPointerSet (sys, run, runPoint->next);
/* store knowledge for this step */
(sys->step)++;
sys->traceKnow[sys->step] = sys->know;
/* check for properties */
propertyCheck (sys);
/* set indent for printing */
indentSet (sys->step);
/* hmmm, but what should it return if not exploring? */
if (!sys->explore)
return 0;
/* we want to explore it, but are we allowed by pruning? */
if (sys->step >= sys->maxtracelength)
{
/* cut off traces that are too long */
#ifdef DEBUG
if (DEBUGL (4))
{
indent ();
printf ("trace cut off.\n");
if (DEBUGL (5))
{
(sys->step)--;
tracePrint (sys);
(sys->step)++;
}
}
#endif
return 0;
}
/* we will explore this state, so count it. */
sys->states = statesIncrease (sys->states);
2004-04-23 11:58:43 +01:00
/* what about scenario exploration? */
if (switches.scenario && sys->step + 1 > switches.scenarioSize)
{
/* count states within scenario */
sys->statesScenario = statesIncrease (sys->statesScenario);
}
2004-04-23 11:58:43 +01:00
/* show progression */
if (switches.reportStates > 0)
2004-04-23 11:58:43 +01:00
{
sys->interval = statesIncrease (sys->interval);
if (!statesSmallerThan
(sys->interval, (unsigned long int) switches.reportStates))
2004-04-23 11:58:43 +01:00
{
globalError++;
sys->interval = STATES0;
eprintf ("States ");
statesFormat (sys->states);
eprintf (" \r");
globalError--;
2004-04-23 11:58:43 +01:00
}
}
/* store new node numbder */
sys->traceNode[sys->step] = sys->states;
/* the construction below always assumes MAX_GRAPH_STATES to be smaller than the unsigned long it, which seems realistic. */
if (switches.output == STATESPACE
&& statesSmallerThan (sys->states, MAX_GRAPH_STATES))
{
/* display graph */
graphNode (sys);
}
2004-04-23 11:58:43 +01:00
return 1;
}
//! Determine end of run after removing end reads, and optionally claims.
Roledef
removeDangling (Roledef rd, const int killclaims)
{
Roledef rdkill;
rdkill = rd;
while (rd != NULL)
{
if (rd->type == SEND || (!killclaims && rd->type == CLAIM))
rdkill = rd;
rd = rd->next;
}
/* remove after rdkill */
return rdkill;
}
/**
* Determine for a roledef that is instantiated, the uninteresting ends bits.
*
*@todo "What is interesting" relies on the fact that there are only secrecy, sychnr and agreement properties.
*/
Roledef
removeIrrelevant (const System sys, const int run, Roledef rd)
{
2005-07-01 14:25:54 +01:00
if (!isRunTrusted (sys,run))
{
// untrusted, so also remove claims
return removeDangling (rd, 1);
}
else
{
// trusted, so only remove reads
return removeDangling (rd, 0);
}
}
//! Unblock any waiting sends of my type.
/**
* Note that the caller must ensure that rd->forbidden is restored afterwards.
*\sa tryChoiceSend()
*/
void
unblock_synchronising_labels (const System sys, const int run,
const Roledef rd)
{
if (rd->type != READ || rd->internal)
return;
if (!inTermlist (sys->synchronising_labels, rd->label))
return;
// This read possibly unblocks other sends
int run_scan;
for (run_scan = 0; run_scan < sys->maxruns; run_scan++)
{
if (run_scan != run)
{
Roledef rd_scan;
rd_scan = sys->runs[run_scan].index;
if (rd_scan != NULL &&
rd_scan->type == SEND &&
rd_scan->forbidden != NULL &&
isTermEqual (rd_scan->label, rd->label) &&
isTermEqual (rd_scan->message, rd->message) &&
isTermEqual (rd_scan->from, rd->from) &&
isTermEqual (rd_scan->to, rd->to))
{
rd_scan->forbidden = NULL;
}
}
}
return;
}
//! Explores the system state given by the next step of a run.
/** Grandiose naming scheme (c) sjors dubya.
*
* After an event was instantiated, this function is called to explore the
* remainder of the system.
*
* Note that some additional pruning is done, so this function
* also determines the enabledness of the event that was just instantiated.
*
*@returns 1 (true) if the event was enabled.
*\sa match_basic(),executeTry()
2004-04-23 11:58:43 +01:00
*/
int
explorify (const System sys, const int run)
2004-04-23 11:58:43 +01:00
{
Roledef rd;
2004-04-23 11:58:43 +01:00
int flag;
2004-07-14 07:55:05 +01:00
int myStep;
Roledef roleCap, roleCapPart;
Knowledge forbiddenBuffer;
2004-04-23 11:58:43 +01:00
rd = runPointerGet (sys, run);
myStep = sys->runs[run].step;
roleCap = NULL;
2004-07-14 07:55:05 +01:00
if (rd == NULL)
2004-04-23 11:58:43 +01:00
{
error ("Trying to progress completed run!\n");
2004-04-23 11:58:43 +01:00
}
flag = 0;
/**
* --------------------------------------------
* Is the event really enabled?
* Typically, a read event is only instantiated
* at this point, so we can only now do some
* instantiation related determinings.
* --------------------------------------------
*/
/*
* Special checks after (implicit) choose events; always first in run reads.
*/
if (myStep == 0 && rd->type == READ)
{
int rid;
if (inTermlist (sys->untrusted, agentOfRun (sys, run)))
{
/* this run is executed by an untrusted agent, do not explore */
return 0;
}
/* executed by trusted agent */
/* Special check 1: if agents have been instantiated in such a way that no more claims in any run
* need to be evaluated, then we can skip
* further traversal.
*/
//!@todo This implementation relies on the fact that there are only secrecy, synchr and agreement properties.
if (switches.pruneNomoreClaims && sys->secrets == NULL)
{ /* there are no remaining secrecy claims to be checked */
Roledef rdscan;
int validclaim;
rid = 0;
validclaim = 0;
/* check for each run */
while (rid < sys->maxruns)
{
/* are claims in this run evaluated anyway? */
2005-07-01 14:25:54 +01:00
if (isRunTrusted(sys, rid))
{ /* possibly claims to be checked in this run */
rdscan = runPointerGet (sys, rid);
while (rdscan != NULL)
{
if (rdscan->type == CLAIM)
{
/* force abort of loop */
validclaim = 1;
rdscan = NULL;
rid = sys->maxruns;
}
else
{
rdscan = rdscan->next;
}
}
}
rid++;
}
if (validclaim == 0)
{ /* no valid claims, abort */
return 0;
}
}
/* Special check 2: Symmetry reduction on chooses.
* If the run we depend upon has already been activated (otherwise warn!) check for instance ordering
*/
if (switches.agentSymmetries && sys->runs[run].prevSymmRun != -1)
{
/* there is such a run on which we depend */
int ridSymm;
ridSymm = sys->runs[run].prevSymmRun;
if (sys->runs[ridSymm].step == 0)
{
2004-07-14 09:33:28 +01:00
/*
* dependency run was not chosen yet, so we can't do anything now
2004-07-14 09:33:28 +01:00
*/
// warning ("Symmetrical run dependency #%i (for run #%i) has not chosen yet!", ridSymm, run);
}
else
{
/* dependent run has chosen, so we can compare */
int order;
order =
termlistOrder (sys->runs[run].agents,
sys->runs[ridSymm].agents);
if (order < 0)
{
/* we only explore the other half */
return 0;
}
if (order == 0 && switches.reduceClaims)
{
/* identical run; only the first would be checked for a claim */
/* so we cut off this run, including claims, turning it into a dummy run */
roleCap = removeDangling (rd, 1);
}
}
}
2004-08-11 09:20:22 +01:00
/* Special check 3: if after choosing, this run is untrusted and ends on (read|skippedclaim)*, we can remove that part already.
*/
if (switches.reduceEndgame && roleCap == NULL)
roleCap = removeIrrelevant (sys, run, rd);
/* Special check x: if all agents in each run send only encrypted stuff, and all agents are trusted,
* there is no way for the intruder to learn anything else than encrypted terms, so secrecy claims will not
* be violated anymore if they contain no terms that are encrypted with such keys */
//!@todo For now, there is no check that the runs only send publicly encrypted stuff! Just an assumption to be made true using static analysis.
/*
rid = 0;
while (rid < sys->maxruns)
{
2005-07-01 14:25:54 +01:00
if (!isRunTrusted (sys, rid))
{
}
rid++;
}
*/
2004-04-23 11:58:43 +01:00
}
/*
* Special check b1: symmetry reduction part II on similar read events for equal roles.
*/
if (switches.readSymmetries)
{
if (sys->runs[run].firstNonAgentRead == myStep)
{
/* Apparently, we have a possible ordering with our symmetrical friend.
* Check if it has progressed enough, and has the same agents.
*/
int ridSymm;
if (rd->type != READ)
{
error ("firstNonAgentRead is not a read?!");
}
ridSymm = sys->runs[run].prevSymmRun;
if (isTermlistEqual
(sys->runs[run].agents, sys->runs[ridSymm].agents))
{
/* same agents, so relevant */
if (myStep > 0 && sys->runs[ridSymm].step < myStep)
{
// warning ("Symmetrical firstread dependency #%i (for run #%i) has not chosen yet!", ridSymm, run);
}
else
{
if (sys->runs[ridSymm].step <= myStep)
{
// warning ("Symmetrical firstread dependency #%i (for run #%i) has not read it's firstNonAgentRead %i yet, as it is only at %i!", ridSymm, run, myStep, sys->runs[ridSymm].step);
}
else
{
/* read was done, so we can compare them */
int i;
Roledef rdSymm;
rdSymm = sys->runs[ridSymm].start;
i = myStep;
while (i > 0)
{
rdSymm = rdSymm->next;
i--;
}
/* rdSymm now points to the instance of the symmetrical read */
i = termOrder (rdSymm->message, rd->message);
if (i < 0)
{
/* only explore symmetrical variant */
return 0;
}
}
}
}
}
}
/* Special check b2: symmetry order reduction.
*
* Concept: when there are two identical runs w.r.t. agents, we can make sure one goes before the other.
* Depends on prevSymm, skipping chooses even.
*/
if (switches.orderSymmetries && myStep == sys->runs[run].firstReal)
{
if (sys->runs[run].prevSymmRun != -1)
{
/* there is such a run on which we depend */
int ridSymm;
ridSymm = sys->runs[run].prevSymmRun;
/* equal runs? */
if (isTermlistEqual
(sys->runs[run].agents, sys->runs[ridSymm].agents))
{
/* so, we have an identical partner */
/* is our partner there already? */
if (sys->runs[ridSymm].step <= myStep)
{
/* not yet there, this is not a valid exploration */
/* verify !! */
return 0;
}
}
}
}
/**
* Final special check; we must be sure that chooses have been done. Only
* necessary for scenario != 0.
*
* Note: any choose selection after this would result in empty scenarios, so this
* should be the last special check.
*/
if (switches.scenario != 0)
{
/* two variants. If scenario size is 0, we operate on the old method involving chooses */
if (switches.scenarioSize == 0)
{
/* only after chooses */
if (myStep == 0 && rd->type == READ)
{
if (run == sys->lastChooseRun)
{
/* We are just after the last choose instance */
/* count this instance */
if (sys->countScenario < INT_MAX)
{
sys->countScenario++;
}
/* If we are displaying scenarios, print it */
if (switches.output == SCENARIOS)
{
printf ("%i\t", sys->countScenario);
scenarioPrint (sys);
printf ("\n");
}
/* If it is not the selected one, abort */
if (switches.scenario != sys->countScenario)
{
/* this branch is not interesting */
/* unfortunately, it is also not drawn in the state graph because of this */
if (switches.output == STATESPACE)
{
graphScenario (sys, run, rd);
}
/* act as if executed, but don't really explore it. */
return 1;
}
}
}
}
else
{
/* scenario size is not zero */
//!@todo Optimization: if the good scenario is already traversed, other trace prefixes need not be explored any further.
if (sys->step + 1 == switches.scenarioSize)
{
/* Now, the prefix has been set. Count it */
if (sys->countScenario < INT_MAX)
{
sys->countScenario++;
}
if (switches.output == SCENARIOS)
{
/* apparently we want the output */
int index;
eprintf ("%i\t", sys->countScenario);
index = 0;
while (index < switches.scenarioSize)
{
roledefPrint (sys->traceEvent[index]);
eprintf ("#%i; ", sys->traceRun[index]);
index++;
}
eprintf ("\n");
}
/* Is this the selected one? */
if (switches.scenario != sys->countScenario)
{
/* unfortunately, it is also not drawn in the state graph because of this */
if (switches.output == STATESPACE)
{
graphScenario (sys, run, rd);
}
/* act as if executed, but don't really explore it. */
return 1;
}
}
}
}
/**
* --------------------------------------------
* Now we assume the event is indeed enabled !
* --------------------------------------------
*/
/* (Possibly) do some local damage */
// Unblock any sends
forbiddenBuffer = rd->forbidden;
if (sys->synchronising_labels != NULL)
{
unblock_synchronising_labels (sys, run, rd);
}
// Cap roles
if (roleCap != NULL)
{
roleCapPart = roleCap->next;
roleCap->next = NULL;
}
/* And explore the resulting system */
if (executeStep (sys, run))
{
/* traverse the system after the step */
flag = traverse (sys);
}
/* restore executeStep "damage" */
runPointerSet (sys, run, rd); // reset rd pointer
sys->runs[run].step = myStep; // reset local index
sys->step--;
indentSet (sys->step);
/* restore local damage */
rd->forbidden = forbiddenBuffer;
if (roleCap != NULL)
{
roleCap->next = roleCapPart;
}
return 1; // The event was indeed enabled (irrespective of traverse!)
2004-04-23 11:58:43 +01:00
}
//! Simple nondeterministic traversal.
__inline__ int
2004-04-23 11:58:43 +01:00
traverseSimple (const System sys)
{
int run;
int flag = 0;
for (run = 0; run < (sys->maxruns); run++)
{
if (runPointerGet (sys, run) != NULL)
{
flag = 1;
executeTry (sys, run);
}
}
if (!flag)
{
/* trace was not succesful */
}
return flag;
}
/**
* -----------------------------------------------------
*
* Traversal Methods
*
* -----------------------------------------------------
*/
/*
* Some assistance macros
*/
#define predRead(sys,rd) ( rd->type == READ && !rd->internal )
#define isRead(sys,rd) ( rd != NULL && predRead(sys,rd) )
#define nonRead(sys,rd) ( rd != NULL && !predRead(sys,rd) )
2004-04-23 11:58:43 +01:00
/*
* nonReads
*
* Do a certain type of action first, i.e. that which satisfies nonRead(System,
* Roledef). Use the inverse of this predicate to detect the other type of
* event.
*/
__inline__ int
2004-04-23 11:58:43 +01:00
nonReads (const System sys)
{
/* all sends first, then simple nondeterministic traversal */
int run;
Roledef rd;
/* check for existence of executable sends */
for (run = 0; run < (sys->maxruns); run++)
{
rd = runPointerGet (sys, run);
if (nonRead (sys, rd))
{
return executeTry (sys, run);
2004-04-23 11:58:43 +01:00
}
}
return 0;
}
/*
* First traverse any non-reads, then non-deterministically the reads.
*/
__inline__ int
2004-04-23 11:58:43 +01:00
traverseNonReads (const System sys)
{
if (nonReads (sys))
return 1;
else
return traverseSimple (sys);
}
//! Execute a send
/**
*\sa unblock_synchronising_labels()
*/
__inline__ int
tryChoiceSend (const System sys, const int run, const Roledef rd)
{
int flag;
flag = 0;
if (rd->forbidden == NULL)
{
/* this is allowed. So we either try it, or we try it later (if blocking) */
/* Note that a send in executetry will always succeed. */
/* 1. Simply try */
flag = executeTry (sys, run);
/* 2. Postpone if synchonisable */
if (flag && inTermlist (sys->synchronising_labels, rd->label))
{
/* This is supposed to be blocked, so we do so */
/* It will possibly be unblocked by a corresponding read event,
* the actual code would be in explorify, post instantiation of the read event.
*/
if (switches.clp)
{
block_clp (sys, run);
}
else
{
block_basic (sys, run);
}
}
}
return flag;
}
//! Execute a read in the knowledge postponing way, on which the partial order reduction for secrecy depends.
__inline__ int
tryChoiceRead (const System sys, const int run, const Roledef rd)
{
int flag;
flag = 0;
/* the sendsdone check only prevent
* some unneccessary inKnowledge tests,
* and branch tests, still improves
* about 15% */
if (sys->knowPhase > rd->knowPhase)
{
/* apparently there has been a new knowledge item since the
* previous check */
/* implicit check for enabledness */
flag = executeTry (sys, run);
/* if it was enabled (flag) we postpone it if it makes sense
* to do so (hasVariable, non internal) */
if (flag && hasTermVariable (rd->message) && !rd->internal)
{
int stackKnowPhase = rd->knowPhase;
rd->knowPhase = sys->knowPhase;
if (switches.clp)
{
block_clp (sys, run);
}
else
{
block_basic (sys, run);
}
rd->knowPhase = stackKnowPhase;
}
}
return flag;
}
//! Try to execute the event at the roledef. Returns true iff it was enabled, and thus explored.
/**
* Note that rd should not be NULL
*/
__inline__ int
tryChoiceRoledef (const System sys, const int run, const Roledef rd)
{
int flag;
#ifdef DEBUG
if (rd == NULL)
error ("tryChoiceRoledef should not be called with a NULL rd pointer");
#endif
flag = 0;
switch (rd->type)
{
case CLAIM:
flag = executeTry (sys, run);
break;
case SEND:
flag = tryChoiceSend (sys, run, rd);
break;
case READ:
flag = tryChoiceRead (sys, run, rd);
break;
default:
fprintf (stderr, "Encountered unknown event type %i.\n", rd->type);
exit (1);
}
return flag;
}
//! Try to execute the event in a given run
__inline__ int
tryChoiceRun (const System sys, const int run)
{
Roledef rd;
rd = runPointerGet (sys, run);
if (rd != NULL)
return tryChoiceRoledef (sys, run, rd);
else
return 0;
}
//! Yield the last active run in the partial trace, or 0 if there is none.
__inline__ int
lastActiveRun (const System sys)
{
if (sys->step == 0)
{
/* first step, start at 0 */
return 0;
}
else
{
/* there was a previous action, start scan from there */
2004-07-26 09:32:01 +01:00
#ifdef DEBUG
if (switches.switchP < 100)
return sys->traceRun[sys->step - 1] + switches.switchP;
2004-07-26 09:32:01 +01:00
#endif
return sys->traceRun[sys->step - 1];
}
}
//! Determine whether a roleevent is a choose event
__inline__ int
isChooseRoledef (const System sys, const int run, const Roledef rd)
{
return (rd == sys->runs[run].start && rd->type == READ && rd->internal);
}
//! Explore possible chooses first
__inline__ int
tryChoosesFirst (const System sys)
2004-04-23 11:58:43 +01:00
{
int flag;
2004-04-23 11:58:43 +01:00
int run;
Roledef rd;
2004-04-23 11:58:43 +01:00
flag = 0;
for (run = 0; run < sys->maxruns && !flag; run++)
{
rd = runPointerGet (sys, run);
if (isChooseRoledef (sys, run, rd))
flag = tryChoiceRoledef (sys, run, rd);
}
return flag;
}
2004-04-23 11:58:43 +01:00
//! Explore left-to-right (from an offset)
__inline__ int
tryEventsOffset (const System sys, const int offset)
{
int flag;
int run;
int i;
2004-04-23 11:58:43 +01:00
flag = 0;
for (i = 0; i < sys->maxruns && !flag; i++)
{
run = (i + offset) % sys->maxruns;
flag = tryChoiceRun (sys, run);
}
return flag;
}
/*
* POR4
*
* This is the simplified version of the algorithm, to be compared with
* the -t7 version.
*
* Based on some new considerations.
*/
__inline__ int
traversePOR4 (const System sys)
{
return tryEventsOffset (sys, lastActiveRun (sys));
}
/*
* POR5
*
* POR4 but does chooses first.
*/
__inline__ int
traversePOR5 (const System sys)
{
if (tryChoosesFirst (sys))
return 1;
return tryEventsOffset (sys, lastActiveRun (sys));
}
/*
* POR6
*
* POR5 but has a left-oriented scan instead of working from the current run.
*/
__inline__ int
traversePOR6 (const System sys)
{
return tryEventsOffset (sys, 0);
}
/*
* POR7
*
* Left-oriented scan, to ensure reductions. However, first does all initial actions.
*/
__inline__ int
traversePOR7 (const System sys)
{
if (tryChoosesFirst (sys))
return 1;
tryEventsOffset (sys, 0);
}
2004-04-23 11:58:43 +01:00
/*
* POR8
*
* POR6, but tries to continue on the current run first. This turned out to be highly more efficient.
*/
2004-04-23 11:58:43 +01:00
__inline__ int
traversePOR8 (const System sys)
{
int flag;
int run;
int last;
2004-04-23 11:58:43 +01:00
last = lastActiveRun (sys);
flag = tryChoiceRun (sys, last);
for (run = 0; run < sys->maxruns && !flag; run++)
{
if (run != last)
flag = tryChoiceRun (sys, run);
2004-04-23 11:58:43 +01:00
}
return flag;
}
//! Check for the properties that have lasting effects throughout the trace.
/**
* Currently, only functions for secrecy.
*@returns 1 (true) iff everything is okay, and no attack is found. 0 (false) if an attack is found.
*/
2004-04-23 11:58:43 +01:00
int
propertyCheck (const System sys)
{
int flag = 1; // default: properties are true, no attack
2004-04-23 11:58:43 +01:00
/* for now, we only check secrecy */
if (sys->secrets != NULL)
{
Termlist scan;
scan = sys->secrets;
while (scan != NULL)
{
if (!claimSecrecy (sys, scan->term))
{
/* apparently, secrecy of this term was violated */
/* find the violated claim event */
Termlist tl = NULL;
int claimev = -1;
int i = 0;
while (claimev == -1 && i <= sys->step)
{
if (sys->traceEvent[i]->type == CLAIM &&
sys->traceEvent[i]->to == CLAIM_Secret)
{
Termlist tl = secrecyUnfolding (scan->term, sys->know);
2004-04-23 11:58:43 +01:00
if (tl != NULL)
{
/* This was indeed a violated claim */
2004-04-23 11:58:43 +01:00
claimev = i;
}
2004-04-23 11:58:43 +01:00
}
i++;
}
/* do we have it? */
if (claimev == -1)
{
/* weird, should not occur */
fprintf (stderr, "Violation, but cannot locate claim.\n");
printf
("A secrecy claim was supposed to be violated on term ");
termPrint (scan->term);
printf (" but we couldn't find the corresponding claim.\n");
exit (1);
2004-04-23 11:58:43 +01:00
}
else
{
/* fine. so it's violated */
violateClaim (sys, sys->step, claimev, tl);
termlistDelete (tl);
2004-04-23 11:58:43 +01:00
flag = 0;
}
}
scan = scan->next;
}
}
return flag;
}
/* true iff the term is secret */
int
isTermSecret (const System sys, const Term t)
{
switch (switches.clp)
2004-04-23 11:58:43 +01:00
{
case 0:
/* test for simple inclusion */
if (inKnowledge (sys->know, t))
return 0;
if (isTermVariable (t))
{
/* it's a variable! */
/* TODO that does not necessarily mean we can choose it, does
* it? NO: the rule should be: there is at least one message
* in knowledge. We don't check it currently.*/
return 0;
}
return 1;
case 1:
/* CLP stuff */
return secret_clp (sys, t);
default:
return 0;
}
}
/* true iff the claim is valid */
int
claimSecrecy (const System sys, const Term t)
{
int csScan (Term t)
{
t = deVar (t);
if (isTermTuple (t))
2004-11-16 12:07:55 +00:00
return csScan (TermOp1 (t)) && csScan (TermOp2 (t));
2004-04-23 11:58:43 +01:00
else
return isTermSecret (sys, t);
}
if (csScan (t))
return 1;
else
{
/* Not reported anymore here, but only at the end */
// reportSecrecy (sys, t);
return 0;
}
}
/*
* Unfold the secrecy tuple and construct a list of terms that violate it.
*/
Termlist
secrecyUnfolding (Term t, const Knowledge know)
{
t = deVar (t);
if (isTermTuple (t))
2004-11-16 12:07:55 +00:00
return termlistConcat (secrecyUnfolding (TermOp1 (t), know),
secrecyUnfolding (TermOp2 (t), know));
2004-04-23 11:58:43 +01:00
else
{
if (inKnowledge (know, t))
return termlistAdd (NULL, t);
2004-04-23 11:58:43 +01:00
else
return NULL;
2004-04-23 11:58:43 +01:00
}
}
/*
* for reporting we need a more detailed output of the claims.
* Output is a termlist pointer, or -1.
*
* in: claim roledef, knowledge for which it is violated
*
* -1 : claim was ignored
* NULL : claim is fulfilled (true)
* Termlist: claim was violated, termlist terms are know to the intruder.
*/
Termlist
claimViolationDetails (const System sys, const int run, const Roledef rd,
const Knowledge know)
2004-04-23 11:58:43 +01:00
{
if (rd->type != CLAIM)
{
fprintf (stderr,
"Trying to determine details of something other than a claim!\n");
exit (-1);
2004-04-23 11:58:43 +01:00
}
/* cases */
if (rd->to == CLAIM_Secret)
{
/* secrecy claim */
2005-07-01 14:25:54 +01:00
if (!isRunTrusted (sys,run))
2004-04-23 11:58:43 +01:00
{
/* claim was skipped */
return (Termlist) - 1;
2004-04-23 11:58:43 +01:00
}
else
{
/* construct violating subterms list */
return secrecyUnfolding (rd->message, know);
2004-04-23 11:58:43 +01:00
}
}
return NULL;
}
//! A claim was violated.
/**
* This happens when we violate a claim.
2004-04-23 11:58:43 +01:00
* Lots of administration.
*@returns True iff explorify is in order.
2004-04-23 11:58:43 +01:00
*/
int
violateClaim (const System sys, int length, int claimev, Termlist reqt)
{
int flag;
Claimlist clinfo;
2004-04-23 11:58:43 +01:00
/* default = no adaption of pruning, continue search */
flag = 1;
/* Count the violations */
sys->attackid++;
sys->failed = statesIncrease (sys->failed);
clinfo = sys->traceEvent[claimev]->claiminfo;
clinfo->failed = statesIncrease (clinfo->failed); // note: for modelchecking secrecy, this can lead to more fails (at further events in branches of the tree) than claim encounters
2004-04-23 11:58:43 +01:00
/* mark the path in the state graph? */
if (switches.output == STATESPACE)
{
graphPath (sys, length);
}
2004-04-23 11:58:43 +01:00
/* Copy the current trace to the buffer, if the new one is shorter than the previous one. */
if (sys->attack == NULL || length < sys->attack->reallength)
{
tracebufDone (sys->attack);
sys->attack = tracebufSet (sys, length, claimev);
2004-04-23 11:58:43 +01:00
attackMinimize (sys, sys->attack);
sys->shortestattack = sys->attack->reallength;
/* maybe there is some new pruning going on */
flag = 0;
switch (switches.prune)
2004-04-23 11:58:43 +01:00
{
case 0:
flag = 1;
break;
case 1:
break;
case 2:
sys->maxtracelength = sys->shortestattack - 1;
break;
}
2004-04-23 11:58:43 +01:00
}
return flag;
}
//! Try to execute the next event in a run.
/**
* One of the core functions of the system.
*@returns 0 (false) if the event was not enabled. 1 (true) if there was an enabled instantiation of this event.
*/
2004-04-23 11:58:43 +01:00
int
executeTry (const System sys, int run)
{
Roledef runPoint;
int flag = 0;
runPoint = runPointerGet (sys, run);
sys->traceEvent[sys->step] = runPoint; // store for later usage, problem: variables are substituted later...
sys->traceRun[sys->step] = run; // same
2004-04-23 11:58:43 +01:00
if (runPoint == NULL)
{
#ifdef DEBUG
/* warning, ought not to occur */
debug (2, "Trying to activate completed run");
#endif
}
else
{
#ifdef DEBUG
if (DEBUGL (4))
{
indent ();
printf ("try: ");
roledefPrint (runPoint);
printf ("#%i\n", run);
}
#endif
if (runPoint->type == READ)
{
if (switches.clp)
2004-04-23 11:58:43 +01:00
return matchRead_clp (sys, run, explorify);
else
return matchRead_basic (sys, run, explorify);
}
if (runPoint->type == SEND)
{
if (switches.clp)
2004-04-23 11:58:43 +01:00
flag = send_clp (sys, run);
else
flag = send_basic (sys, run);
return flag;
}
/*
* Execute claim event
*/
2004-04-23 11:58:43 +01:00
if (runPoint->type == CLAIM)
{
/* first we might dynamically determine whether the claim is valid */
2005-07-01 14:25:54 +01:00
if (!isRunTrusted (sys,run))
2004-04-23 11:58:43 +01:00
{
/* for untrusted agents we check no claim violations at all
* so: we know it's okay. */
/* TODO for CLP this doesn't work and call for branching, if the
* agent is a variable */
#ifdef DEBUG
if (DEBUGL (3))
{
indent ();
printf ("Skipped claim in untrusted run with agents ");
termlistPrint (sys->runs[run].agents);
printf ("\n");
}
#endif
explorify (sys, run);
return 1;
}
/* determine type of claim, and parameters */
#ifdef DEBUG
if (DEBUGL (2))
{
indent ();
printf ("claim: ");
roledefPrint (runPoint);
printf ("#%i\n", run);
}
#endif
/*
* update claim counters
*/
sys->claims = statesIncrease (sys->claims);
2004-04-23 11:58:43 +01:00
/*
* distinguish claim types
*/
2004-04-23 11:58:43 +01:00
if (runPoint->to == CLAIM_Secret)
{
/*
* SECRECY
*/
2004-04-23 11:58:43 +01:00
/* TODO claims now have their own type, test for that */
/* TODO for now it is secrecy of the message */
Termlist oldsecrets = sys->secrets;
/* TODO this can be more efficient, by filtering out double occurrences */
sys->secrets =
termlistAdd (termlistShallow (oldsecrets), runPoint->message);
flag = claimSecrecy (sys, runPoint->message);
runPoint->claiminfo->count++;
2004-04-23 11:58:43 +01:00
/* now check whether the claim failed for further actions */
if (!flag)
{
/* violation */
Termlist tl;
tl = claimViolationDetails (sys, run, runPoint, sys->know);
if (violateClaim (sys, sys->step + 1, sys->step, tl))
flag = explorify (sys, run);
termlistDelete (tl);
2004-04-23 11:58:43 +01:00
}
else
{
/* no violation */
flag = explorify (sys, run);
}
/* reset secrets list */
termlistDelete (sys->secrets);
sys->secrets = oldsecrets;
}
else if (runPoint->to == CLAIM_Nisynch)
2004-04-23 11:58:43 +01:00
{
/*
* NISYNCH
*/
flag = check_claim_nisynch (sys, sys->step);
if (!flag)
{
/* violation */
if (violateClaim (sys, sys->step + 1, sys->step, NULL))
flag = explorify (sys, run);
}
else
{
/* no violation */
flag = explorify (sys, run);
}
2004-04-23 11:58:43 +01:00
}
else if (runPoint->to == CLAIM_Niagree)
{
/*
* NIAGREE
*/
flag = check_claim_niagree (sys, sys->step);
if (!flag)
{
/* violation */
if (violateClaim (sys, sys->step + 1, sys->step, NULL))
flag = explorify (sys, run);
}
else
{
/* no violation */
flag = explorify (sys, run);
}
}
else // if (runPoint->to == CLAIM_Empty)
{
// Skip other claim types
flag = explorify (sys, run);
}
2004-04-23 11:58:43 +01:00
}
/* a claim always succeeds */
flag = 1;
}
return flag;
}