github.com/jlmucb/cloudproxy@v0.0.0-20170830161738-b5aa0b619bc4/diffscussions/reviews/datalog_inf_loop.diffscuss (about)

     1  #* 
     2  #* author: Kevin Walsh
     3  #* email: kwalsh@holycross.edu
     4  #* date: 2015-05-19T19:13:15-0400
     5  #* 
     6  #- Fixed infinite loop in datalog guard
     7  #- 
     8  #- 
     9  diff --git a/go/tao/datalog_guard.go b/go/tao/datalog_guard.go
    10  index 9537f5b..f0d3d3f 100644
    11  --- a/go/tao/datalog_guard.go
    12  +++ b/go/tao/datalog_guard.go
    13  @@ -83,147 +83,173 @@ type DatalogGuard struct {
    14   // detection. The predicate Subprin(S, P, E) in auth is special-cased in
    15   // DatalogGuard to write in datalog to subprin/3 with arguments S, P, E.
    16   type subprinPrim struct {
    17   	datalog.DistinctPred
    18   }
    19   
    20   // String returns a string representation of the subprin custom datalog
    21   // predicate.
    22   func (sp *subprinPrim) String() string {
    23   	return "subprin"
    24   }
    25   
    26   func (sp *subprinPrim) Assert(c *datalog.Clause) error {
    27   	return newError("datalog: can't assert for custom predicates")
    28   }
    29   
    30   func (sp *subprinPrim) Retract(c *datalog.Clause) error {
    31   	return newError("datalog: can't retract for custom predicates")
    32   }
    33   
    34  -// parseRootExtPrins parses a pair of terms as a key/tpm principal and an
    35  -// extension principal tail. Both Terms must implement fmt.Stringer.
    36  -func parseRootExtPrins(o datalog.Term, e datalog.Term) (oprin auth.Prin, eprin auth.PrinTail, err error) {
    37  -	// Report subprin(O.E, O, E) as discovered.
    38  -	ostringer, ok1 := o.(fmt.Stringer)
    39  -	estringer, ok2 := e.(fmt.Stringer)
    40  -	if !ok1 || !ok2 {
    41  -		err = fmt.Errorf("arguments 2 and 3 must implement fmt.Stringer in subprin/3")
    42  -		return
    43  -	}
    44  -
    45  -	// The first must be a regular rooted principal, and the second must be
    46  -	// an ext principal tail.
    47  -	var ostr string
    48  -	if _, err = fmt.Sscanf(ostringer.String(), "%q", &ostr); err != nil {
    49  -		return
    50  -	}
    51  -
    52  -	var estr string
    53  -	if _, err = fmt.Sscanf(estringer.String(), "%q", &estr); err != nil {
    54  -		return
    55  -	}
    56  -
    57  -	if _, err = fmt.Sscanf(ostr, "%v", &oprin); err != nil {
    58  -		return
    59  +// parsePrinTail parses Term (which msut be a datalog.Quoted) as principal tail.
    60  +func parsePrinTail(e datalog.Term) (tail auth.PrinTail, ok bool) {
    61  +	// Parse p as Parent.Ext and report subprin(Parent.Ext, Parent, Ext).
    62  +	// Note: The way the translation works between DatalogGuard and the Datalog
    63  +	// engine, e will be a dlengine.Quoted holding a string. 
    64  +	q, ok := e.(*dlengine.Quoted)
    65  +	if !ok {
    66  +        return
    67   	}
    68  -	if _, err = fmt.Sscanf(estr, "%v", &eprin); err != nil {
    69  -		return
    70  +	if _, err := fmt.Sscanf(q.Value, "%v", &tail); err != nil {
    71  +        ok = false
    72   	}
    73  -	return
    74  +    return
    75   }
    76   
    77  -// parseCompositePrin parses a Term (which must implement fmt.Stringer) as a
    78  -// principal with at least one extension.
    79  -func parseCompositePrin(p datalog.Term) (prin auth.Prin, err error) {
    80  +// parsePrin parses a Term (which must be a datalog.Quoted) as a principal.
    81  +func parsePrin(p datalog.Term) (prin auth.Prin, ok bool) {
    82   	// Parse p as Parent.Ext and report subprin(Parent.Ext, Parent, Ext).
    83  -	pstringer, ok := p.(fmt.Stringer)
    84  +	// Note: The way the translation works between DatalogGuard and the Datalog
    85  +	// engine, p will be a dlengine.Quoted holding a string. 
    86  +	q, ok := p.(*dlengine.Quoted)
    87   	if !ok {
    88  -		err = fmt.Errorf("A composite principal must be a Stringer")
    89  -		return
    90  +        return
    91   	}
    92  -
    93  -	// Due to the way the translation works between DatalogGuard and the Datalog
    94  -	// engine, this is a quoted string. So, trim the quotes at the beginning and
    95  -	// the end of the string before parsing it.
    96  -	var pstr string
    97  -	if _, err = fmt.Sscanf(pstringer.String(), "%q", &pstr); err != nil {
    98  -		return
    99  +	if _, err := fmt.Sscanf(q.Value, "%v", &prin); err != nil {
   100  +        ok = false
   101   	}
   102  -	if _, err = fmt.Sscanf(pstr, "%v", &prin); err != nil {
   103  -		return
   104  -	}
   105  -	if len(prin.Ext) < 1 {
   106  -		err = fmt.Errorf("A composite principal must have extensions")
   107  -		return
   108  -	}
   109  -
   110  -	return
   111  +    return
   112   }
   113   
   114   // Search implements the subprinPrim custom datalog primitive by parsing
   115   // constant arguments of subprin/3 as principals and reporting any clauses it
   116  -// discovers.
   117  +// discovers. Note: Datalog termination requires that Search discover only a
   118  +// finite number of new facts, and more specifically, that Search introduces
   119  +// new, distinct terms only if these will not kick off new Search calls in an
   120  +// infinite regression. Search meets this requirement by only generating new
   121  +// principal (or principal tail) terms that are strictly "smaller" than those
   122  +// already present, where "smaller" is essentially the subprincipal relation.
   123   func (sp *subprinPrim) Search(target *datalog.Literal, discovered func(c *datalog.Clause)) {
   124   	p := target.Arg[0]
   125   	o := target.Arg[1]
   126   	e := target.Arg[2]
   127   	if p.Constant() && o.Variable() && e.Variable() {
   128  -		prin, err := parseCompositePrin(p)
   129  -		if err != nil {
   130  +        // fmt.Printf("splitting prin %v\n", p)
   131  +		prin, ok := parsePrin(p)
   132  +		if !ok || len(prin.Ext) < 1 {
   133   			return
   134   		}
   135  -		extIndex := len(prin.Ext) - 1
   136  -		trimmedPrin := auth.Prin{
   137  -			Type: prin.Type,
   138  -			Key:  prin.Key,
   139  -			Ext:  prin.Ext[:extIndex],
   140  -		}
   141  -		extPrin := auth.PrinTail{
   142  -			Ext: []auth.PrinExt{prin.Ext[extIndex]},
   143  +        // enumerate all possible tails
   144  +        for i := 1; i < len(prin.Ext); i++ {
   145  +            trimmedPrin := auth.Prin{
   146  +                Type: prin.Type,
   147  +                Key:  prin.Key,
   148  +                Ext:  prin.Ext[:i],
   149  +            }
   150  +            extPrin := auth.PrinTail{
   151  +                Ext: prin.Ext[i:],
   152  +            }
   153  +            o = dlengine.NewQuoted(trimmedPrin.String())
   154  +            e = dlengine.NewQuoted(extPrin.String())
   155  +            discovered(datalog.NewClause(datalog.NewLiteral(sp, p, o, e)))
   156  +        }
   157  +	} else if p.Constant() && o.Variable() && e.Constant() {
   158  +        // fmt.Printf("checking prin %v against tail %v\n", p, e)
   159  +		prin, ok1 := parsePrin(p)
   160  +		etail, ok2 := parsePrinTail(e)
   161  +		if !ok1 || !ok2 || len(etail.Ext) < 1 {
   162  +			return
   163   		}
   164  -
   165  -		parentIdent := dlengine.NewIdent(fmt.Sprintf("%q", trimmedPrin.String()))
   166  -		extIdent := dlengine.NewIdent(fmt.Sprintf("%q", extPrin.String()))
   167  -		discovered(datalog.NewClause(datalog.NewLiteral(sp, p, parentIdent, extIdent)))
   168  -	} else if p.Variable() && o.Constant() && e.Constant() {
   169  -		oprin, eprin, err := parseRootExtPrins(o, e)
   170  -		if err != nil {
   171  +        n := len(prin.Ext) - len(etail.Ext)
   172  +        if n < 0 {
   173  +            return
   174  +        }
   175  +        extPrin := auth.PrinTail{
   176  +            Ext: prin.Ext[n:],
   177  +        }
   178  +        if !extPrin.Identical(etail) {
   179  +            return
   180  +        }
   181  +        trimmedPrin := auth.Prin{
   182  +            Type: prin.Type,
   183  +            Key:  prin.Key,
   184  +            Ext:  prin.Ext[0:n],
   185  +        }
   186  +        o = dlengine.NewQuoted(trimmedPrin.String())
   187  +        discovered(datalog.NewClause(datalog.NewLiteral(sp, p, o, e)))
   188  +	} else if p.Constant() && o.Constant() && e.Variable() {
   189  +        // fmt.Printf("extracting from prin %v tail for %v\n", p, o)
   190  +		prin, ok1 := parsePrin(p)
   191  +		oprin, ok2 := parsePrin(o)
   192  +		if !ok1 || !ok2 {
   193   			return
   194   		}
   195  -		oprin.Ext = append(oprin.Ext, eprin.Ext...)
   196  -		oeIdent := dlengine.NewIdent(fmt.Sprintf("%q", oprin.String()))
   197  -		discovered(datalog.NewClause(datalog.NewLiteral(sp, oeIdent, o, e)))
   198  +        n := len(oprin.Ext)
   199  +        if len(prin.Ext) <= n {
   200  +            return
   201  +        }
   202  +        trimmedPrin := auth.Prin{
   203  +            Type: prin.Type,
   204  +            Key:  prin.Key,
   205  +            Ext:  prin.Ext[0:n],
   206  +        }
   207  +		if !trimmedPrin.Identical(oprin) {
   208  +            return
   209  +        }
   210  +        extPrin := auth.PrinTail{
   211  +            Ext: prin.Ext[n:],
   212  +        }
   213  +        e = dlengine.NewQuoted(extPrin.String())
   214  +        discovered(datalog.NewClause(datalog.NewLiteral(sp, p, o, e)))
   215  +	} else if p.Variable() && o.Constant() && e.Constant() {
   216  +        // fmt.Printf("refusing to join %v with extension %v\n", o, e)
   217  +        // Note: although it seems that p must equal "o.e", it is unsafe to
   218  +        // report this as discovered. That is, "o.e" may be a new,
   219  +        // never-before-seen term, and it may cause an infinite regression of
   220  +        // new calls to Search.
   221  +        // Note also that refusing to discover "o.e" here does not limit the
   222  +        // power of datalog too much... in practice, the engine will eventually
   223  +        // call Search using "o.e" for argument p, if "o.e" is within the
   224  +        // universe of discussion, and that Search call will succeed.
   225  +        return
   226   	} else if p.Constant() && o.Constant() && e.Constant() {
   227  +        // fmt.Printf("checking %v against prin %v extension %v\n", p, o, e)
   228   		// Check that the constraint holds and report it as discovered.
   229  -		prin, err := parseCompositePrin(p)
   230  -		if err != nil {
   231  +		prin, ok1 := parsePrin(p)
   232  +        oprin, ok2 := parsePrin(o)
   233  +        etail, ok3 := parsePrinTail(e)
   234  +		if !ok1 || !ok2 || !ok3 {
   235   			return
   236   		}
   237  -		oprin, eprin, err := parseRootExtPrins(o, e)
   238  -		if err != nil {
   239  -			return
   240  -		}
   241  -
   242   		// Extend the root principal with the extension from the ext principal
   243   		// and check identity.
   244  -		oprin.Ext = append(oprin.Ext, eprin.Ext...)
   245  +		oprin.Ext = append(oprin.Ext, etail.Ext...)
   246   		if prin.Identical(oprin) {
   247   			discovered(datalog.NewClause(datalog.NewLiteral(sp, p, o, e)))
   248   		}
   249   	}
   250   }
   251   
   252   // NewTemporaryDatalogGuard returns a new datalog guard with a fresh, unsigned,
   253   // non-persistent rule set. It adds a custom predicate subprin(P, O, E) to check
   254   // if a principal P is a subprincipal O.E.
   255   func NewTemporaryDatalogGuard() Guard {
   256   	sp := new(subprinPrim)
   257   	sp.SetArity(3)
   258   	eng := dlengine.NewEngine()
   259   	eng.AddPred(sp)
   260   	return &DatalogGuard{dl: eng}
   261   }
   262   
   263   // NewDatalogGuard returns a new datalog guard that uses a signed, persistent
   264   // signed rule set. ReloadIfModified() should be called to load the rule set.
   265   func NewDatalogGuard(key *Verifier, config DatalogGuardDetails) (*DatalogGuard, error) {
   266  diff --git a/go/tao/datalog_guard_test.go b/go/tao/datalog_guard_test.go
   267  index a50838b..57240e7 100644
   268  --- a/go/tao/datalog_guard_test.go
   269  +++ b/go/tao/datalog_guard_test.go
   270  @@ -205,20 +205,62 @@ func TestDatalogSubprin(t *testing.T) {
   271   	for _, s := range datalogSubprinProg {
   272   		if err := g.AddRule(s); err != nil {
   273   			t.Fatal("Couldn't add rule '", s, "':", err)
   274   		}
   275   	}
   276   
   277   	pprin := auth.Prin{
   278   		Type: "key",
   279   		Key:  auth.Bytes([]byte{0x70}),
   280   		Ext: []auth.PrinExt{
   281   			auth.PrinExt{
   282   				Name: "Hash",
   283   				Arg:  []auth.Term{auth.Bytes([]byte{0x71})},
   284   			},
   285   		},
   286   	}
   287   	if !g.IsAuthorized(pprin, "Execute", nil) {
   288   		t.Fatal("Subprin authorization check failed")
   289   	}
   290   }
   291  +
   292  +var datalogLoops = []string{
   293  +	"(forall Host: forall Hash: forall P: TrustedHost(Host) and TrustedHash(Hash) and Subprin(P, Host, Hash) implies TrustedHost(P))",
   294  +	"(TrustedHost(key([70])))",
   295  +	"(TrustedHash(ext.Hash([71])))",
   296  +}
   297  +
   298  +func TestDatalogLoop(t *testing.T) {
   299  +	g, key, tmpdir := makeDatalogGuard(t)
   300  +	defer os.RemoveAll(tmpdir)
   301  +	err := g.Save(key)
   302  +
   303  +	for _, s := range datalogLoops {
   304  +		if err := g.AddRule(s); err != nil {
   305  +			t.Fatalf("Couldn't add rule '%s': %s", s, err)
   306  +		}
   307  +	}
   308  +
   309  +	ok, err := g.Query("TrustedHost(key([70]).Hash([71]))")
   310  +	if err != nil {
   311  +		t.Fatalf("Couldn't query the datalog guard: %s", err)
   312  +	}
   313  +	if !ok {
   314  +		t.Fatal("Datalog guard incorrectly returned false for a true statement")
   315  +	}
   316  +
   317  +	ok, err = g.Query("TrustedHost(key([70]))")
   318  +	if err != nil {
   319  +		t.Fatalf("Couldn't query the datalog guard: %s", err)
   320  +	}
   321  +	if !ok {
   322  +		t.Fatal("Datalog guard incorrectly returned false for a true statement")
   323  +	}
   324  +
   325  +	ok, err = g.Query("TrustedHost(key([70]).Hash([72]))")
   326  +	if err != nil {
   327  +		t.Fatalf("Couldn't query the datalog guard: %s", err)
   328  +	}
   329  +	if ok {
   330  +		t.Fatal("Datalog guard incorrectly returned true for a false statement")
   331  +	}
   332  +}