scyther/src/arachne.c

1093 lines
20 KiB
C
Raw Normal View History

2004-08-11 10:51:17 +01:00
/**
*@file arachne.c
*
* Introduces a method for proofs akin to the Athena modelchecker
* http://www.ece.cmu.edu/~dawnsong/athena/
*
*/
2004-08-11 13:08:10 +01:00
#include "term.h"
#include "termlist.h"
2004-08-11 13:08:10 +01:00
#include "role.h"
2004-08-11 10:51:17 +01:00
#include "system.h"
#include "knowledge.h"
#include "compiler.h"
#include "states.h"
#include "mgu.h"
2004-08-11 10:51:17 +01:00
#include "arachne.h"
2004-08-14 16:59:14 +01:00
#include "error.h"
#include "claim.h"
#include "debug.h"
#include "binding.h"
2004-08-11 10:51:17 +01:00
extern Term CLAIM_Secret;
extern Term CLAIM_Nisynch;
extern Term CLAIM_Niagree;
extern Term TERM_Agent;
static System sys;
Protocol INTRUDER; // Pointers, to be set by the Init
Role I_GOAL; // Same here.
Role I_TEE;
Role I_SPLIT;
Role I_TUPLE;
Role I_ENCRYPT;
Role I_DECRYPT;
Role I_M0;
2004-08-11 13:08:10 +01:00
static int indentDepth;
#ifdef DEBUG
static char *explanation; // Pointer to a string that describes what we just tried to do
static int e_run;
static Term e_term1;
static Term e_term2;
static Term e_term3;
#endif
2004-08-11 13:08:10 +01:00
struct goalstruct
{
int run;
int index;
Roledef rd;
2004-08-11 13:08:10 +01:00
};
typedef struct goalstruct Goal;
/**
* Forward declarations
*/
int iterate ();
/**
* Program code
*/
//! Init Arachne engine
void
arachneInit (const System mysys)
{
Roledef rd = NULL;
Termlist tl, know0;
void add_event (int event, Term message)
{
rd = roledefAdd (rd, event, NULL, NULL, NULL, message, NULL);
}
Role add_role (const char *rolenamestring)
{
Role r;
Term rolename;
rolename = makeGlobalConstant (rolenamestring);
r = roleCreate (rolename);
r->roledef = rd;
rd = NULL;
r->next = INTRUDER->roles;
INTRUDER->roles = r;
// compute_role_variables (sys, INTRUDER, r);
return r;
}
sys = mysys; // make sys available for this module as a global
/**
* Very important: turn role terms that are local to a run, into variables.
*/
term_rolelocals_are_variables ();
/*
* Add intruder protocol roles
*/
INTRUDER = protocolCreate (makeGlobalConstant (" INTRUDER "));
add_event (READ, NULL);
I_GOAL = add_role (" I_GOAL ");
know0 = knowledgeSet (sys->know);
tl = know0;
while (tl != NULL)
{
add_event (SEND, tl->term);
I_M0 = add_role (" I_M0 ");
tl = tl->next;
}
termlistDelete (know0);
return;
}
//! Close Arachne engine
void
arachneDone ()
{
return;
}
2004-08-11 13:08:10 +01:00
//------------------------------------------------------------------------
// Detail
//------------------------------------------------------------------------
/*
* runs[rid].step is now the number of 'valid' events within the run, but we
* call it 'length' here.
*/
#define INVALID -1
#define isGoal(rd) (rd->type == READ && !rd->internal)
#define isBound(rd) (rd->bound)
2004-08-11 13:08:10 +01:00
#define length step
//! Indent print
void
indentPrint ()
{
#ifdef DEBUG
int i;
for (i = 0; i < indentDepth; i++)
eprintf ("%i ", i);
#else
eprintf (">> ");
#endif
}
//! Iterate but discard the info of the termlist
int
mgu_iterate (const Termlist tl)
{
return iterate ();
}
//! Determine the run that follows from a substitution.
/**
* After an Arachne unification, stuff might go wrong w.r.t. nonce instantiation.
* This function determines the run that is implied by a substitution list.
* @returns >= 0: a run, -1 for invalid, -2 for any run.
*/
int
determine_unification_run (Termlist tl)
{
int run;
run = -2;
while (tl != NULL)
{
//! Again, hardcoded reference to compiler.c. Level -3 means a local constant for a role.
if (tl->term->type != VARIABLE && tl->term->right.runid == -3)
{
Term t;
t = tl->term->subst;
// It is required that it is actually a leaf, because we construct it.
if (!realTermLeaf (t))
{
return -1;
}
else
{
if (run == -2)
{
// Any run
run = t->right.runid;
}
else
{
// Specific run: compare
if (run != t->right.runid)
{
return -1;
}
}
}
}
tl = tl->next;
}
return run;
}
2004-08-11 13:08:10 +01:00
//------------------------------------------------------------------------
// Sub
//------------------------------------------------------------------------
//! Iterate over all send types in the roles (including the intruder ones)
/**
* Function is called with (protocol pointer, role pointer, roledef pointer, index)
* and returns an integer. If it is false, iteration aborts.
*/
int
iterate_role_sends (int (*func) ())
{
Protocol p;
p = sys->protocols;
while (p != NULL)
{
Role r;
r = p->roles;
while (r != NULL)
{
Roledef rd;
int index;
rd = r->roledef;
index = 0;
while (rd != NULL)
{
if (rd->type == SEND)
{
if (!func (p, r, rd, index))
return 0;
}
index++;
rd = rd->next;
}
r = r->next;
}
p = p->next;
}
return 1;
}
2004-08-13 11:25:23 +01:00
//! Generate a new intruder goal
int
2004-08-13 11:25:23 +01:00
create_intruder_goal (Term t)
2004-08-11 13:08:10 +01:00
{
int run;
Roledef rd;
2004-08-11 13:08:10 +01:00
roleInstance (sys, INTRUDER, I_GOAL, NULL, NULL);
run = sys->maxruns - 1;
rd = sys->runs[run].start;
sys->runs[run].length = 1;
2004-08-13 11:25:23 +01:00
rd->message = termDuplicate (t);
#ifdef DEBUG
if (DEBUGL (3))
{
explanation = "Adding intruder goal for message ";
e_term1 = t;
}
#endif
2004-08-13 11:25:23 +01:00
return run;
}
//! Generates a new intruder goal, iterates
/**
* Sloppy, does not unify term but hardcodes it into the stuff.
*/
int
add_intruder_goal_iterate (Goal goal)
{
int flag;
int run;
run = create_intruder_goal (goal.rd->message);
if (binding_add (run, 0, goal.run, goal.index))
{
flag = iterate ();
}
else
{
indentPrint ();
eprintf ("Aborted adding intruder goal because of cycle.\n");
flag = 1;
}
binding_remove_last ();
roleInstanceDestroy (sys); // destroy the created run
return flag;
2004-08-11 13:08:10 +01:00
}
//! Try to bind a specific existing run to a goal.
/**
* The key goals are bound to the goal.
*@param subterm determines whether it is a subterm unification or not.
*/
int
bind_existing_to_goal (const Goal goal, const int index, const int run,
const int subterm)
{
Roledef rd;
int flag;
int old_length;
int subterm_iterate (Termlist substlist, Termlist keylist)
{
int keycount;
int flag;
#ifdef DEBUG
if (DEBUGL (5))
{
indentPrint ();
eprintf ("Adding key list : ");
termlistPrint (keylist);
eprintf ("\n");
}
#endif
flag = 1;
keycount = 0;
while (flag && keylist != NULL)
{
int keyrun;
keyrun = create_intruder_goal (keylist->term);
if (!binding_add (keyrun, 0, goal.run, goal.index))
flag = 0;
keylist = keylist->next;
keycount++;
}
flag = flag && iterate ();
while (keycount > 0)
{
binding_remove_last ();
roleInstanceDestroy (sys);
keycount--;
}
termlistDestroy (keylist);
return flag;
}
int interm_iterate (Termlist substlist)
{
iterate ();
}
//----------------------------
// Roledef entry
rd = roledef_shift (sys->runs[run].start, index);
// Fix length
old_length = sys->runs[run].length;
if ((index + 1) > old_length)
sys->runs[run].length = index + 1;
#ifdef DEBUG
if (DEBUGL (3))
{
explanation = "Bind existing run (generic) ";
e_run = run;
e_term1 = goal.rd->message;
e_term2 = rd->message;
}
#endif
if (binding_add (run, index, goal.run, goal.index))
{
if (subterm)
{
flag = termMguSubTerm (goal.rd->message, rd->message,
subterm_iterate, sys->know->inverses, NULL);
}
else
{
flag = termMguInTerm (goal.rd->message, rd->message,
interm_iterate);
}
}
else
{
#ifdef DEBUG
if (DEBUGL (5))
{
indentPrint ();
eprintf ("Aborted binding existing run because of cycle.\n");
}
#endif
}
binding_remove_last ();
// Reset length
sys->runs[run].length = old_length;
return flag;
}
//! Bind a goal to an existing regular run, if possible
int
bind_existing_run (const Goal goal, const Protocol p, const Role r,
const int index, const int subterm)
{
int run, flag;
#ifdef DEBUG
if (DEBUGL (4))
{
indentPrint ();
eprintf ("Trying to bind ");
termPrint (goal.rd->message);
eprintf (" to an existing instance of ");
termPrint (p->nameterm);
eprintf (", ");
termPrint (r->nameterm);
eprintf (" (%i)\n", subterm);
}
#endif
flag = 1;
for (run = 0; run < sys->maxruns; run++)
{
if (sys->runs[run].protocol == p && sys->runs[run].role == r)
{
flag = flag && bind_existing_to_goal (goal, index, run, subterm);
}
}
return flag;
}
//! Bind a goal to a new run
int
bind_new_run (const Goal goal, const Protocol p, const Role r,
const int index, const int subterm)
{
2004-08-11 16:05:13 +01:00
int run;
int flag;
#ifdef DEBUG
if (DEBUGL (4))
{
indentPrint ();
eprintf ("Trying to bind ");
termPrint (goal.rd->message);
eprintf (" to a new instance of ");
termPrint (p->nameterm);
eprintf (", ");
termPrint (r->nameterm);
eprintf (" (%i)\n", subterm);
}
#endif
roleInstance (sys, p, r, NULL, NULL);
run = sys->maxruns - 1;
flag = bind_existing_to_goal (goal, index, run, subterm);
2004-08-11 16:05:13 +01:00
roleInstanceDestroy (sys);
return flag;
}
//! Print the current semistate
void
printSemiState ()
{
int run;
2004-08-14 15:38:30 +01:00
int open;
List bl;
int binding_indent_print (void *data)
{
indentPrint ();
eprintf ("!! ");
binding_print (data);
return 1;
}
2004-08-14 15:38:30 +01:00
indentPrint ();
eprintf ("!! --=[ Semistate ]=--\n");
open = 0;
for (run = 0; run < sys->maxruns; run++)
{
int index;
2004-08-15 18:16:13 +01:00
Role r;
Roledef rd;
2004-08-15 18:16:13 +01:00
Term oldagent;
2004-08-15 18:16:13 +01:00
indentPrint ();
eprintf ("!!\n");
indentPrint ();
2004-08-14 15:38:30 +01:00
eprintf ("!! [ Run %i, ", run);
2004-08-15 18:16:13 +01:00
termPrint (sys->runs[run].protocol->nameterm);
eprintf (", ");
r = sys->runs[run].role;
oldagent = r->nameterm->subst;
r->nameterm->subst = NULL;
termPrint (r->nameterm);
r->nameterm->subst = oldagent;
if (oldagent != NULL)
{
eprintf (": ");
termPrint (oldagent);
}
eprintf (" ]\n");
index = 0;
rd = sys->runs[run].start;
while (index < sys->runs[run].length)
{
indentPrint ();
2004-08-14 15:38:30 +01:00
eprintf ("!! %i ", index);
roledefPrint (rd);
eprintf ("\n");
2004-08-14 15:38:30 +01:00
if (isGoal (rd) && !isBound (rd))
open++;
index++;
rd = rd->next;
}
}
if (sys->bindings != NULL)
{
indentPrint ();
eprintf ("!!\n");
list_iterate (sys->bindings, binding_indent_print);
}
2004-08-14 15:38:30 +01:00
indentPrint ();
2004-08-15 18:16:13 +01:00
eprintf ("!!\n");
indentPrint ();
2004-08-14 15:38:30 +01:00
eprintf ("!! - open: %i -\n", open);
}
//------------------------------------------------------------------------
// Larger logical componentents
//------------------------------------------------------------------------
//! Goal selection
/**
* Should be ordered to prefer most constrained; for now, it is simply the first one encountered.
*/
Goal
select_goal ()
{
Goal goal;
int run;
goal.run = INVALID;
goal.rd = NULL;
for (run = 0; run < sys->maxruns; run++)
{
Roledef rd;
int index;
index = 0;
rd = sys->runs[run].start;
while (rd != NULL && index < sys->runs[run].length)
{
if (isGoal (rd) && !isBound (rd))
{
// Return this goal
goal.run = run;
goal.index = index;
goal.rd = rd;
return goal;
}
index++;
rd = rd->next;
}
}
return goal;
}
2004-08-11 13:08:10 +01:00
//! Bind a regular goal
int
bind_goal_regular (const Goal goal)
2004-08-11 13:08:10 +01:00
{
int flag;
/*
* This is a local function so we have access to goal
*/
int bind_this_role_send (Protocol p, Role r, Roledef rd, int index)
{
int test_unification (Termlist substlist)
{
// A unification exists; return the signal
return 0;
}
if (p == INTRUDER)
{
/* only bind to regular runs */
return 1;
}
else
{
// Test for interm unification
#ifdef DEBUG
if (DEBUGL (5))
{
indentPrint ();
eprintf ("Checking send candidate with message ");
termPrint (rd->message);
eprintf (" from ");
termPrint (p->nameterm);
eprintf (", ");
termPrint (r->nameterm);
eprintf (", index %i\n", index);
}
#endif
if (!termMguInTerm (goal.rd->message, rd->message, test_unification))
{
// A good candidate
#ifdef DEBUG
if (DEBUGL (5))
{
indentPrint ();
eprintf ("Term ");
termPrint (goal.rd->message);
eprintf (" can possibly be bound by role ");
termPrint (r->nameterm);
eprintf (", index %i\n", index);
}
#endif
return (bind_new_run (goal, p, r, index, 0) &&
bind_existing_run (goal, p, r, index, 0));
}
else
{
// Cannot unify: no attacks
return 1;
}
}
}
2004-08-13 11:25:23 +01:00
// Bind to all possible sends or intruder node;
#ifdef DEBUG
if (DEBUGL (5))
{
indentPrint ();
eprintf ("Try regular role send.\n");
}
#endif
flag = iterate_role_sends (bind_this_role_send);
#ifdef DEBUG
if (DEBUGL (5))
{
indentPrint ();
eprintf ("Try intruder send.\n");
}
#endif
return (flag && add_intruder_goal_iterate (goal));
2004-08-13 11:25:23 +01:00
}
//! Bind an intruder goal to a regular run
int
bind_intruder_to_regular (Goal goal)
2004-08-13 11:25:23 +01:00
{
int bind_this_roleevent (Protocol p, Role r, Roledef rd, int index)
2004-08-13 11:25:23 +01:00
{
int cannotUnify;
int test_unification (Termlist substlist, Termlist keylist)
{
// Signal that unification is possible.
return 0;
}
2004-08-13 11:25:23 +01:00
/**
* Note that we only bind to regular runs here
*/
if (p == INTRUDER)
{
return 1; // don't abort scans
}
else
{ // Test for subterm unification
if (termMguSubTerm
(goal.rd->message, rd->message, test_unification,
sys->know->inverses, NULL))
{
// cannot unify
return 1;
}
else
{
/**
* Either from an existing, or from a new run.
*/
return (bind_new_run (goal, p, r, index, 1)
&& bind_existing_run (goal, p, r, index, 1));
}
}
2004-08-13 11:25:23 +01:00
}
// Bind to all possible sends?
return iterate_role_sends (bind_this_roleevent);
2004-08-11 13:08:10 +01:00
}
2004-08-13 11:25:23 +01:00
//! Bind an intruder goal by intruder construction
/**
* Handles the case where the intruder constructs a composed term himself.
*/
2004-08-13 11:25:23 +01:00
int
bind_intruder_to_construct (const Goal goal)
{
Term term;
term = goal.rd->message;
if (!realTermLeaf (term))
{
Term t1, t2;
int flag;
if (realTermTuple (term))
{
// tuple construction
t1 = term->left.op1;
t2 = term->right.op2;
}
else
{
// must be encryption
t1 = term->left.op;
t2 = term->right.key;
}
create_intruder_goal (t1);
create_intruder_goal (t2);
flag = iterate ();
roleInstanceDestroy (sys);
roleInstanceDestroy (sys);
return flag;
}
else
{
return 1;
}
2004-08-13 11:25:23 +01:00
}
2004-08-11 13:08:10 +01:00
//! Bind an intruder goal
2004-08-13 11:25:23 +01:00
/**
* Computes F2 as in Athena explanations.
*/
2004-08-11 13:08:10 +01:00
int
bind_goal_intruder (const Goal goal)
2004-08-11 13:08:10 +01:00
{
2004-08-15 18:07:38 +01:00
/**
* Special case: when the intruder can bind it to the initial knowledge.
*/
Termlist tl;
int flag;
flag = 1;
tl = knowledgeSet (sys->know);
while (flag && tl != NULL)
{
int hasvars;
Termlist substlist;
substlist = termMguTerm (tl->term, goal.rd->message);
if (substlist != MGUFAIL)
{
// This seems to work
flag = flag && iterate ();
2004-08-15 18:16:13 +01:00
termlistSubstReset (substlist);
2004-08-15 18:07:38 +01:00
}
tl = tl->next;
}
termlistDelete (tl);
return (flag && bind_intruder_to_regular (goal) &&
2004-08-13 11:25:23 +01:00
bind_intruder_to_construct (goal));
2004-08-11 13:08:10 +01:00
}
//! Bind a goal in all possible ways
int
bind_goal (const Goal goal)
2004-08-11 13:08:10 +01:00
{
if (!goal.rd->bound)
2004-08-11 13:08:10 +01:00
{
int flag;
goal.rd->bound = 1;
if (sys->runs[goal.run].protocol == INTRUDER)
{
flag = bind_goal_intruder (goal);
}
else
{
flag = bind_goal_regular (goal);
}
goal.rd->bound = 0;
return flag;
2004-08-11 13:08:10 +01:00
}
else
{
return 1;
2004-08-11 13:08:10 +01:00
}
}
//! Prune determination
/**
*@returns true iff this state is invalid for some reason
*/
int
prune ()
{
Termlist tl;
if (indentDepth > 20)
{
// Hardcoded limit on iterations
#ifdef DEBUG
if (DEBUGL (3))
{
indentPrint ();
eprintf ("Pruned because too many iteration levels.\n");
}
#endif
return 1;
}
if (sys->maxruns > 4)
{
// Hardcoded limit on runs
#ifdef DEBUG
if (DEBUGL (3))
{
indentPrint ();
eprintf ("Pruned because too many runs.\n");
}
#endif
return 1;
}
// Check if all agents are valid
tl = sys->runs[0].agents;
while (tl != NULL)
{
Term agent;
agent = deVar (tl->term);
if (!realTermLeaf (agent))
{
#ifdef DEBUG
if (DEBUGL (3))
{
indentPrint ();
eprintf ("Pruned because agent cannot be compound term.\n");
}
#endif
return 1;
}
if (!inTermlist (agent->stype, TERM_Agent))
{
#ifdef DEBUG
if (DEBUGL (3))
{
indentPrint ();
eprintf ("Pruned because agent must contain agent type.\n");
}
#endif
return 1;
}
if (inTermlist (sys->untrusted, agent))
{
#ifdef DEBUG
if (DEBUGL (3))
{
indentPrint ();
eprintf ("Pruned because agents must be untrusted.\n");
}
#endif
return 1;
}
tl = tl->next;
}
return 0;
}
//! Setup system for specific claim test
add_claim_specifics (Claimlist cl, Roledef rd)
{
if (cl->type == CLAIM_Secret)
{
/**
* Secrecy claim
*/
create_intruder_goal (rd->message);
}
}
2004-08-11 13:08:10 +01:00
//------------------------------------------------------------------------
// Main logic core
//------------------------------------------------------------------------
2004-08-11 10:51:17 +01:00
//! Main recursive procedure for Arachne
int
iterate ()
{
int flag;
2004-08-11 13:08:10 +01:00
Goal goal;
flag = 1;
indentDepth++;
if (!prune ())
{
/**
* Not pruned: count
*/
sys->states = statesIncrease (sys->states);
#ifdef DEBUG
if (DEBUGL (3) && explanation != NULL)
{
indentPrint ();
2004-08-14 19:38:43 +01:00
eprintf ("ITERATE: %s", explanation);
if (e_run != INVALID)
eprintf ("#%i ", e_run);
if (e_term1 != NULL)
{
termPrint (e_term1);
eprintf (" ");
}
if (e_term2 != NULL)
{
termPrint (e_term2);
eprintf (" ");
}
if (e_term3 != NULL)
{
termPrint (e_term3);
eprintf (" ");
}
2004-08-14 19:38:43 +01:00
eprintf (" ]}>=--\n");
}
#endif
2004-08-11 13:08:10 +01:00
/**
* Check whether its a final state (i.e. all goals bound)
2004-08-11 13:08:10 +01:00
*/
goal = select_goal ();
if (goal.run == INVALID)
{
/*
* all goals bound, check for property
*/
sys->claims = statesIncrease (sys->claims);
#ifdef DEBUG
if (DEBUGL (3))
{
printSemiState ();
}
#endif
//!@todo Property check in Arachne.
}
else
{
#ifdef DEBUG
if (DEBUGL (3))
{
indentPrint ();
eprintf ("Trying to bind goal ");
termPrint (goal.rd->message);
eprintf (" from run %i, index %i.\n", goal.run, goal.index);
}
#endif
/*
* bind this goal in all possible ways and iterate
*/
flag = bind_goal (goal);
}
2004-08-11 13:08:10 +01:00
}
#ifdef DEBUG
explanation = NULL;
e_run = INVALID;
e_term1 = NULL;
e_term2 = NULL;
e_term3 = NULL;
#endif
indentDepth--;
return flag;
}
//! Main code for Arachne
/**
* For this test, we manually set up some stuff.
*
* But later, this will just iterate over all claims.
*/
int
arachne ()
2004-08-11 10:51:17 +01:00
{
2004-08-14 16:59:14 +01:00
Claimlist cl;
/*
* set up claim role(s)
*/
if (sys->maxruns > 0)
{
2004-08-14 16:59:14 +01:00
error ("Something is wrong, number of runs >0.");
}
int print_send (Protocol p, Role r, Roledef rd, int index)
{
eprintf ("IRS: ");
termPrint (p->nameterm);
eprintf (", ");
termPrint (r->nameterm);
eprintf (", %i, ", index);
roledefPrint (rd);
eprintf ("\n");
}
#ifdef DEBUG
if (DEBUGL (1))
{
iterate_role_sends (print_send);
}
#endif
indentDepth = 0;
2004-08-14 16:59:14 +01:00
cl = sys->claimlist;
while (cl != NULL)
{
/**
* Check each claim
*/
2004-08-14 16:59:14 +01:00
Protocol p;
Role r;
if (sys->switchClaimToCheck == NULL
|| sys->switchClaimToCheck == cl->type)
{
#ifdef DEBUG
explanation = NULL;
e_run = INVALID;
e_term1 = NULL;
e_term2 = NULL;
e_term3 = NULL;
#endif
p = (Protocol) cl->protocol;
r = (Role) cl->role;
#ifdef DEBUG
if (DEBUGL (2))
{
indentPrint ();
eprintf ("Testing Claim ");
termPrint (cl->type);
eprintf (" in protocol ");
termPrint (p->nameterm);
eprintf (", role ");
termPrint (r->nameterm);
eprintf (" at index %i.\n", cl->ev);
}
#endif
2004-08-14 16:59:14 +01:00
roleInstance (sys, p, r, NULL, NULL);
sys->runs[0].length = cl->ev + 1;
/**
* Add specific goal info
*/
add_claim_specifics (cl,
roledef_shift (sys->runs[0].start, cl->ev));
2004-08-14 16:59:14 +01:00
#ifdef DEBUG
if (DEBUGL (5))
{
printSemiState ();
}
2004-08-14 16:59:14 +01:00
#endif
/*
* iterate
*/
iterate ();
2004-08-14 16:59:14 +01:00
//! Destroy
while (sys->maxruns > 0)
{
roleInstanceDestroy (sys);
}
}
2004-08-14 16:59:14 +01:00
// next
cl = cl->next;
}
2004-08-11 10:51:17 +01:00
}