539 lines
12 KiB
C
539 lines
12 KiB
C
/*
|
|
* Scyther : An automatic verifier for security protocols.
|
|
* Copyright (C) 2007 Cas Cremers
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include "term.h"
|
|
#include "termlist.h"
|
|
#include "mgu.h"
|
|
#include "type.h"
|
|
#include "debug.h"
|
|
#include "specialterm.h"
|
|
#include "switches.h"
|
|
#include "arachne.h"
|
|
|
|
/*
|
|
Most General Unifier
|
|
|
|
Unification etc.
|
|
|
|
New version yields a termlist with substituted variables, which can later be reset to NULL.
|
|
*/
|
|
|
|
/**
|
|
* switches.match
|
|
* 0 typed
|
|
* 1 basic typeflaws
|
|
* 2 all typeflaws
|
|
*/
|
|
|
|
void
|
|
showSubst (Term t)
|
|
{
|
|
#ifdef DEBUG
|
|
if (!DEBUGL (5))
|
|
return;
|
|
|
|
indent ();
|
|
eprintf ("Substituting ");
|
|
termPrint (t);
|
|
eprintf (", typed ");
|
|
termlistPrint (t->stype);
|
|
if (realTermLeaf (t->subst))
|
|
{
|
|
eprintf ("->");
|
|
termlistPrint (t->subst->stype);
|
|
}
|
|
else
|
|
{
|
|
eprintf (", composite term");
|
|
}
|
|
if (t->type != VARIABLE)
|
|
{
|
|
eprintf (" (bound roleconstant)");
|
|
}
|
|
eprintf ("\n");
|
|
#endif
|
|
}
|
|
|
|
//! See if this is preferred substitution
|
|
/**
|
|
* By default, ta->tb will map. Returning 0 (false) will swap them.
|
|
*/
|
|
int
|
|
preferSubstitutionOrder (Term ta, Term tb)
|
|
{
|
|
if (termlistLength (ta->stype) == 1 && inTermlist (ta->stype, TERM_Agent))
|
|
{
|
|
/**
|
|
* If the first one is an agent type, we prefer swapping.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
// Per default, leave it as it is.
|
|
return 1;
|
|
}
|
|
|
|
//! See if a substitution is valid
|
|
__inline__ int
|
|
goodsubst (Term tvar, Term tsubst)
|
|
{
|
|
Term tbuf;
|
|
int res;
|
|
|
|
tbuf = tvar->subst;
|
|
tvar->subst = tsubst;
|
|
|
|
res = checkTypeTerm (tvar);
|
|
|
|
tvar->subst = tbuf;
|
|
return res;
|
|
}
|
|
|
|
//! Undo all substitutions in a list of variables.
|
|
/**
|
|
* The termlist should contain only variables.
|
|
*/
|
|
void
|
|
termlistSubstReset (Termlist tl)
|
|
{
|
|
while (tl != NULL)
|
|
{
|
|
tl->term->subst = NULL;
|
|
tl = tl->next;
|
|
}
|
|
}
|
|
|
|
//! Most general unifier iteration
|
|
/**
|
|
* Try to determine the most general unifier of two terms, if so calls function.
|
|
*
|
|
* The callback receives a list of variables, that were previously open, but are now closed
|
|
* in such a way that the two terms unify.
|
|
*
|
|
* The callback must return true for the iteration to proceed: if it returns false, a single call would abort the scan.
|
|
* The return value shows this: it is false if the scan was aborted, and true if not.
|
|
*/
|
|
int
|
|
unify (Term t1, Term t2, Termlist tl, int (*callback) (Termlist))
|
|
{
|
|
int callsubst (Termlist tl, Term t, Term tsubst)
|
|
{
|
|
int proceed;
|
|
|
|
t->subst = tsubst;
|
|
#ifdef DEBUG
|
|
showSubst (t);
|
|
#endif
|
|
tl = termlistAdd (tl, t);
|
|
proceed = callback (tl);
|
|
tl = termlistDelTerm (tl);
|
|
t->subst = NULL;
|
|
return proceed;
|
|
}
|
|
|
|
/* added for speed */
|
|
t1 = deVar (t1);
|
|
t2 = deVar (t2);
|
|
if (t1 == t2)
|
|
{
|
|
return callback (tl);
|
|
}
|
|
|
|
if (!(hasTermVariable (t1) || hasTermVariable (t2)))
|
|
{
|
|
// None has a variable
|
|
if (isTermEqual (t1, t2))
|
|
{
|
|
// Equal!
|
|
return callback (tl);
|
|
}
|
|
else
|
|
{
|
|
// Can never be fixed, no variables
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Distinguish a special case where both are unbound variables that will be
|
|
* connected, and I want to give one priority over the other for readability.
|
|
*
|
|
* Because t1 and t2 have been deVar'd means that if they are variables, they
|
|
* are also unbound.
|
|
*/
|
|
|
|
if (realTermVariable (t1) && realTermVariable (t2) && goodsubst (t1, t2))
|
|
{
|
|
/* Both are unbound variables. Decide.
|
|
*
|
|
* The plan: t1->subst will point to t2. But maybe we prefer the other
|
|
* way around?
|
|
*/
|
|
if (preferSubstitutionOrder (t2, t1))
|
|
{
|
|
Term t3;
|
|
|
|
// Swappy.
|
|
t3 = t1;
|
|
t1 = t2;
|
|
t2 = t3;
|
|
}
|
|
return callsubst (tl, t1, t2);
|
|
}
|
|
|
|
/* symmetrical tests for single variable.
|
|
*/
|
|
|
|
if (realTermVariable (t2))
|
|
{
|
|
if (termSubTerm (t1, t2) || !goodsubst (t2, t1))
|
|
return true;
|
|
else
|
|
{
|
|
return callsubst (tl, t2, t1);
|
|
}
|
|
}
|
|
if (realTermVariable (t1))
|
|
{
|
|
if (termSubTerm (t2, t1) || !goodsubst (t1, t2))
|
|
return true;
|
|
else
|
|
{
|
|
return callsubst (tl, t1, t2);
|
|
}
|
|
}
|
|
|
|
/* left & right are compounds with variables */
|
|
if (t1->type != t2->type)
|
|
return true;
|
|
|
|
/* identical compound types */
|
|
|
|
/* encryption first */
|
|
if (realTermEncrypt (t1))
|
|
{
|
|
int unify_combined_enc (Termlist tl)
|
|
{
|
|
// now the keys are unified (subst in this tl)
|
|
// and we try the inner terms
|
|
return unify (TermOp (t1), TermOp (t2), tl, callback);
|
|
}
|
|
|
|
return unify (TermKey (t1), TermKey (t2), tl, unify_combined_enc);
|
|
}
|
|
|
|
/* tupling second
|
|
non-associative version ! TODO other version */
|
|
if (isTermTuple (t1))
|
|
{
|
|
int unify_combined_tup (Termlist tl)
|
|
{
|
|
// now the keys are unified (subst in this tl)
|
|
// and we try the inner terms
|
|
return unify (TermOp2 (t1), TermOp2 (t2), tl, callback);
|
|
}
|
|
return unify (TermOp1 (t1), TermOp1 (t2), tl, unify_combined_tup);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//! Subterm unification
|
|
/**
|
|
* Try to unify (a subterm of) tbig with tsmall.
|
|
*
|
|
* Callback is called with a list of substitutions, and a list of terms that
|
|
* need to be decrypted in order for this to work.
|
|
*
|
|
* E.g. subtermUnify ( {{m}k1}k2, m ) yields a list : {{m}k1}k2, {m}k1 (where
|
|
* the {m}k1 is the last added node to the list)
|
|
*
|
|
* The callback should return true for the iteration to proceed, or false to abort.
|
|
* The final result is this flag.
|
|
*
|
|
* This is the actual procedure used by the Arachne algorithm in archne.c
|
|
*/
|
|
int
|
|
subtermUnify (Term tbig, Term tsmall, Termlist tl, Termlist keylist,
|
|
int (*callback) (Termlist, Termlist))
|
|
{
|
|
int proceed;
|
|
|
|
int keycallback (Termlist tl)
|
|
{
|
|
return callback (tl, keylist);
|
|
}
|
|
|
|
proceed = true;
|
|
|
|
// Devar
|
|
tbig = deVar (tbig);
|
|
tsmall = deVar (tsmall);
|
|
|
|
// Three options:
|
|
// 1. simple unification
|
|
proceed = proceed && unify (tbig, tsmall, tl, keycallback);
|
|
|
|
// [2/3]: complex
|
|
if (switches.intruder)
|
|
{
|
|
// 2. interm unification
|
|
// Only if there is an intruder
|
|
if (realTermTuple (tbig))
|
|
{
|
|
proceed = proceed
|
|
&& subtermUnify (TermOp1 (tbig), tsmall, tl, keylist, callback);
|
|
proceed = proceed
|
|
&& subtermUnify (TermOp2 (tbig), tsmall, tl, keylist, callback);
|
|
}
|
|
|
|
// 3. unification with encryption needed
|
|
if (realTermEncrypt (tbig))
|
|
{
|
|
// extend the keylist
|
|
keylist = termlistAdd (keylist, tbig);
|
|
proceed = proceed
|
|
&& subtermUnify (TermOp (tbig), tsmall, tl, keylist, callback);
|
|
// remove last item again
|
|
keylist = termlistDelTerm (keylist);
|
|
}
|
|
}
|
|
|
|
// Athena problem case: open variable about to be unified.
|
|
/**
|
|
* In this case we really need to consider the problematic Athena case for untyped variables.
|
|
*/
|
|
if (isTermVariable (tbig))
|
|
{
|
|
// Check the type: can it contain tuples, encryptions?
|
|
if (isOpenVariable (tbig))
|
|
{
|
|
// This one needs to be pursued by further constraint adding
|
|
/**
|
|
* Currently, this is not implemented yet. TODO.
|
|
* This is actually the main Athena problem that we haven't solved yet.
|
|
*/
|
|
// Mark that we don't have a full proof, and possibly remark in proof output.
|
|
markNoFullProof (tbig, tsmall);
|
|
}
|
|
}
|
|
|
|
return proceed;
|
|
}
|
|
|
|
|
|
//! Most general unifier.
|
|
/**
|
|
* Try to determine the most general unifier of two terms.
|
|
* Resulting termlist must be termlistDelete'd.
|
|
*
|
|
*@return Returns a list of variables, that were previously open, but are now closed
|
|
* in such a way that the two terms unify. Returns \ref MGUFAIL if it is impossible.
|
|
* The termlist should be deleted.
|
|
*
|
|
* @TODO this code should be removed, as it duplicates 'unify' code, and is
|
|
* ill-suited for adaption later on with multiple unifiers.
|
|
*/
|
|
Termlist
|
|
termMguTerm (Term t1, Term t2)
|
|
{
|
|
/* added for speed */
|
|
t1 = deVar (t1);
|
|
t2 = deVar (t2);
|
|
if (t1 == t2)
|
|
return NULL;
|
|
|
|
if (!(hasTermVariable (t1) || hasTermVariable (t2)))
|
|
{
|
|
if (isTermEqual (t1, t2))
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return MGUFAIL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Distinguish a special case where both are unbound variables that will be
|
|
* connected, and I want to give one priority over the other for readability.
|
|
*
|
|
* Because t1 and t2 have been deVar'd means that if they are variables, they
|
|
* are also unbound.
|
|
*/
|
|
|
|
if (realTermVariable (t1) && realTermVariable (t2) && goodsubst (t1, t2))
|
|
{
|
|
/* Both are unbound variables. Decide.
|
|
*
|
|
* The plan: t1->subst will point to t2. But maybe we prefer the other
|
|
* way around?
|
|
*/
|
|
if (preferSubstitutionOrder (t2, t1))
|
|
{
|
|
Term t3;
|
|
|
|
// Swappy.
|
|
t3 = t1;
|
|
t1 = t2;
|
|
t2 = t3;
|
|
}
|
|
t1->subst = t2;
|
|
#ifdef DEBUG
|
|
showSubst (t1);
|
|
#endif
|
|
return termlistAdd (NULL, t1);
|
|
}
|
|
|
|
/* symmetrical tests for single variable.
|
|
*/
|
|
|
|
if (realTermVariable (t2))
|
|
{
|
|
if (termSubTerm (t1, t2) || !goodsubst (t2, t1))
|
|
return MGUFAIL;
|
|
else
|
|
{
|
|
t2->subst = t1;
|
|
#ifdef DEBUG
|
|
showSubst (t2);
|
|
#endif
|
|
return termlistAdd (NULL, t2);
|
|
}
|
|
}
|
|
if (realTermVariable (t1))
|
|
{
|
|
if (termSubTerm (t2, t1) || !goodsubst (t1, t2))
|
|
return MGUFAIL;
|
|
else
|
|
{
|
|
t1->subst = t2;
|
|
#ifdef DEBUG
|
|
showSubst (t1);
|
|
#endif
|
|
return termlistAdd (NULL, t1);
|
|
}
|
|
}
|
|
|
|
/* left & right are compounds with variables */
|
|
if (t1->type != t2->type)
|
|
return MGUFAIL;
|
|
|
|
/* identical compounds */
|
|
/* encryption first */
|
|
if (realTermEncrypt (t1))
|
|
{
|
|
Termlist tl1, tl2;
|
|
|
|
tl1 = termMguTerm (TermKey (t1), TermKey (t2));
|
|
if (tl1 == MGUFAIL)
|
|
{
|
|
return MGUFAIL;
|
|
}
|
|
else
|
|
{
|
|
tl2 = termMguTerm (TermOp (t1), TermOp (t2));
|
|
if (tl2 == MGUFAIL)
|
|
{
|
|
termlistSubstReset (tl1);
|
|
termlistDelete (tl1);
|
|
return MGUFAIL;
|
|
}
|
|
else
|
|
{
|
|
return termlistConcat (tl1, tl2);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* tupling second
|
|
non-associative version ! TODO other version */
|
|
if (isTermTuple (t1))
|
|
{
|
|
Termlist tl1, tl2;
|
|
|
|
tl1 = termMguTerm (TermOp1 (t1), TermOp1 (t2));
|
|
if (tl1 == MGUFAIL)
|
|
{
|
|
return MGUFAIL;
|
|
}
|
|
else
|
|
{
|
|
tl2 = termMguTerm (TermOp2 (t1), TermOp2 (t2));
|
|
if (tl2 == MGUFAIL)
|
|
{
|
|
termlistSubstReset (tl1);
|
|
termlistDelete (tl1);
|
|
return MGUFAIL;
|
|
}
|
|
else
|
|
{
|
|
return termlistConcat (tl1, tl2);
|
|
}
|
|
}
|
|
}
|
|
return MGUFAIL;
|
|
}
|
|
|
|
//! Check if role terms might match in some way
|
|
/**
|
|
* Interesting case: role names are variables here, so they always match. We catch that case by inspecting the variable list.
|
|
*/
|
|
int
|
|
checkRoletermMatch (const Term t1, const Term t2, const Termlist notmapped)
|
|
{
|
|
Termlist tl;
|
|
|
|
// simple clause or combined
|
|
tl = termMguTerm (t1, t2);
|
|
if (tl == MGUFAIL)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
int result;
|
|
Termlist vl;
|
|
|
|
result = true;
|
|
// Reset variables
|
|
termlistSubstReset (tl);
|
|
// Check variable list etc: should not contain mapped role names
|
|
vl = tl;
|
|
while (vl != NULL)
|
|
{
|
|
// This term should not be in the notmapped list
|
|
if (inTermlist (notmapped, vl->term))
|
|
{
|
|
result = false;
|
|
break;
|
|
}
|
|
vl = vl->next;
|
|
|
|
}
|
|
// Remove list
|
|
termlistDelete (tl);
|
|
return result;
|
|
}
|
|
}
|