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 +}