scyther/src/type.c

453 lines
10 KiB
C
Raw Normal View History

/*
* Scyther : An automatic verifier for security protocols.
2013-10-05 23:56:12 +01:00
* Copyright (C) 2007-2013 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.
*/
/*
* type.c
*
* Code to check the consistency of types, in the presence of type flaw stuff etc.
*/
#include <stdlib.h>
#include <stdio.h>
#include "term.h"
#include "termlist.h"
#include "system.h"
#include "debug.h"
#include "switches.h"
#include "specialterm.h"
extern Protocol INTRUDER;
extern Termlist TERMLISTERROR;
//! Report a bad substitution, if needed
void
reportBadSubst (const Term tvar, const Term tsubst)
{
#ifdef DEBUG
if (DEBUGL (5))
{
Term tbuf;
tbuf = tvar->subst;
tvar->subst = NULL;
eprintf ("Substitution fails on ");
termPrint (tvar);
eprintf (" -/-> ");
termPrint (tsubst);
eprintf (", maybe because type: \n");
termlistPrint (tvar->stype);
eprintf (" does not contain ");
termlistPrint (tsubst->stype);
eprintf ("\n");
tvar->subst = tbuf;
}
#endif
}
//! Say whether a typelist is 'generic' or should be taken per item.
int
isTypelistGeneric (const Termlist typelist)
{
if (typelist == NULL)
{
return 1;
}
else
{
return inTermlist (typelist, TERM_Ticket);
}
}
//! Say whether this variable can contain tuples and/or encryptions
/**
* Precondition: tvar should be a variable.
*
* This function is specifically used for detecting the problematic Athena case.
*/
int
isOpenVariable (const Term tvar)
{
return isTypelistGeneric (tvar->stype);
}
//! Check whether a single variable term is instantiated correctly.
/**
* Check whether a single variable term is instantiated correctly in this
* system. This takes the matching parameter into account, and is aware of the
* 'ticket' type.
*
* Non-variables etc. imply true.
*/
int
checkTypeTerm (const Term tvar)
{
// Checks are only needed for match < 2 etc.
if (switches.match < 2 && tvar != NULL && realTermVariable (tvar))
{
// Non-instantiated terms are fine.
if (tvar->subst != NULL)
{
if (!isTypelistGeneric (tvar->stype))
{
// So there is a specific (non-ticket) type, and the var is instantiated, match mode 0 or 1
// Is it really a leaf?
Term tsubst;
tsubst = deVar (tvar);
if (!realTermLeaf (tsubst))
{
// Then it's definitively false
reportBadSubst (tvar, tsubst);
return false;
}
else
{
// It is a leaf
if (switches.match == 0)
{
/* Types must match exactly. Thus, one of the variable type should match a type of the constant.
*/
Termlist tl;
tl = tvar->stype;
while (tl != NULL)
{
if (inTermlist (tsubst->stype, tl->term))
{
// One type matches
return true;
}
tl = tl->next;
}
// No type matches.
reportBadSubst (tvar, tsubst);
return false;
}
}
}
}
}
return true;
}
//! Check whether a typelist is a strict agent type list
int
isAgentType (Termlist typelist)
{
return (termlistLength (typelist) == 1 &&
inTermlist (typelist, TERM_Agent));
}
//! Helper function to determine whether a list is compatible with an agent var.
int
agentCompatible (Termlist tl)
{
if (isTypelistGeneric (tl))
{
return 1;
}
else
{
return inTermlist (tl, TERM_Agent);
}
}
//! Check whether two type lists are compatible for variables
/**
* Depends on some input:
*
* agentcheck true if agent type is always restrictive
*
* returns the new type list (needs to be deleted!) or TERMLISTERROR (should
* not be deleted!)
*/
Termlist
typelistConjunct (Termlist typelist1, Termlist typelist2,
const int agentcheck)
{
if (typelist1 != TERMLISTERROR && typelist2 != TERMLISTERROR)
{
/* In the restricted agent case, we check whether agent list occurs in
* either set. If so, the result can only be an agent-restrictive list.
*/
if (agentcheck)
{
if (isAgentType (typelist1) || isAgentType (typelist2))
{
/* Now, the result must surely accept agents. Thus, it must be
* NULL or accept agents.
*/
if (switches.match == 0)
{
// only if we are doing matching, otherwise it is always agent-compatible
if (!
(agentCompatible (typelist1)
&& agentCompatible (typelist2)))
{
// Not good: one of them cannot
return TERMLISTERROR;
}
}
// Good: but because an agent is involved, the type reduces to the simple Agent type only.
return termlistAdd (NULL, TERM_Agent);
}
else
{
/* Not the simple agent variable case. Now other things come in to play.
*/
if (switches.match == 0)
{
/*
* Strict match: (-m0) conjunct of the types must be non-empty.
*/
// Generic exceptions
if (isTypelistGeneric (typelist1))
{
// Copy for later deletion
return termlistShallow (typelist2);
}
else
{
if (isTypelistGeneric (typelist2))
{
// Copy for later deletion
return termlistShallow (typelist1);
}
else
{
/* Apparently neither is generic, and we can take the real conjunct.
* However, this conjunct must not be empty (because that implies that the types cannot match).
*/
Termlist conjunct;
conjunct = termlistConjunct (typelist1, typelist2);
if (conjunct == NULL)
{
// Empty, and thus the variables cannot be instantiated without causing a type flaw.
return TERMLISTERROR;
}
else
{
// Non-empty, which is good in this case.
return conjunct;
}
}
}
}
else
{
/*
* Not so strict: (-m1 or -m2)
*
* Because the variable is not bound, there is certainly no
* binding yet to tuples or crypted terms, and thus any
* typing will do.
*/
return NULL;
}
}
}
}
return TERMLISTERROR;
}
//! Check a single ground variable
/**
* Check whether all variables that map to this one have a possible valid substitution
*/
int
checkGroundVariable (const System sys, const Term groundvar)
{
int allvalid;
allvalid = 1;
if (switches.match < 2)
{
if (realTermVariable (groundvar))
{
Termlist tl;
Termlist typelist;
// Check
typelist = termlistShallow (groundvar->stype);
tl = sys->variables;
while (allvalid == 1 && tl != NULL)
{
Term term;
term = tl->term;
// Not actually the same, of course
if (term != groundvar)
{
// Does this term map to the same variable?
if (isTermEqual (term, groundvar))
{
Termlist tlprev;
// Maps to same ground term
// Take conjunct
tlprev = typelist;
typelist =
typelistConjunct (tlprev, term->stype,
switches.agentTypecheck);
termlistDelete (tlprev);
if (typelist == TERMLISTERROR)
{
// And this is not valid...
allvalid = 0;
}
}
}
tl = tl->next;
}
if (typelist != TERMLISTERROR)
{
termlistDelete (typelist);
}
}
}
return allvalid;
}
//! Global check of the system, for all variables.
/**
* Returns true if all variables are okay.
*
* This version checks all substitutions, and thus takes over all the functions
* that are now in mgu.c. However, we have left them in for speed purposes,
* because pruning early is good.
*/
int
checkAllSubstitutions (const System sys)
{
int allvalid;
Termlist groundvars;
Termlist tl;
/* We scan all unbound variables for so called 'ground variables', which are
* the root of a substitution tree. Such variables are checked for
* satisfyability, i.e. the conjunction of their type lists must not be
* empty.
*
* At the same time, we check also bound variables for their mappings.
*/
groundvars = NULL;
allvalid = 1;
tl = sys->variables;
while ((allvalid == 1) && (tl != NULL))
{
Term tvar;
tvar = tl->term;
if (realTermVariable (tvar))
{
Term tsubst;
tsubst = deVar (tvar);
if (tvar != tsubst)
{
// Substitution going on, check
if (realTermVariable (tsubst))
{
/* Variable -> Variable (unbound)
*
* Check whether we already scanned for this one, because we
* don't want to scan for this ground variable more than once.
*/
if (!inTermlist (groundvars, tsubst))
{
/* Not done before, add now.
*/
groundvars = termlistAdd (groundvars, tsubst);
/* Check whether there exists a valid substitution for
* everything mapping to this ground variable.
*/
allvalid = allvalid
&& checkGroundVariable (sys, tsubst);
}
}
else
{
/* Variable -> term (bound)
*/
if (switches.agentTypecheck && isAgentType (tvar->stype))
{
// *Must* include agent type, regardless of match
allvalid = allvalid && agentCompatible (tsubst->stype);
}
else
{
// Consider match
allvalid = allvalid && checkTypeTerm (tvar);
}
}
}
}
tl = tl->next;
}
termlistDelete (groundvars);
return allvalid;
}
//! Check this variables whether it is a good agent type
/**
* Checks for leaf/etc and correct agent type
*/
int
goodAgentType (Term agent)
{
agent = deVar (agent);
if (!realTermLeaf (agent))
{ // not a leaf
return false;
}
else
{ // real leaf
if (isTermVariable (agent))
{
// Variable: check type consistency (should have a solution)
// Not yet: depends on matching mode also
}
else
{
// Constant: allow only exact type
if (!inTermlist (agent->stype, TERM_Agent))
{
return false;
}
}
}
return true;
}