github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/audit.go (about) 1 package teams 2 3 import ( 4 "context" 5 "fmt" 6 "sort" 7 "sync" 8 "time" 9 10 lru "github.com/hashicorp/golang-lru" 11 12 "github.com/keybase/client/go/libkb" 13 "github.com/keybase/client/go/protocol/keybase1" 14 "github.com/keybase/client/go/teams/hidden" 15 "github.com/keybase/pipeliner" 16 ) 17 18 // AuditCurrentVersion is the version that works with this code. Older stored 19 // versions will be discarded on load from level DB. 20 const AuditCurrentVersion = keybase1.AuditVersion_V4 21 22 var desktopParams = libkb.TeamAuditParams{ 23 RootFreshness: 5 * time.Minute, 24 MerkleMovementTrigger: keybase1.Seqno(10000), 25 NumPreProbes: 20, 26 NumPostProbes: 20, 27 Parallelism: 4, 28 LRUSize: 1000, 29 } 30 31 var mobileParamsWifi = libkb.TeamAuditParams{ 32 RootFreshness: 10 * time.Minute, 33 MerkleMovementTrigger: keybase1.Seqno(200000), 34 NumPreProbes: 8, 35 NumPostProbes: 8, 36 Parallelism: 3, 37 LRUSize: 500, 38 } 39 40 var mobileParamsNoWifi = libkb.TeamAuditParams{ 41 RootFreshness: 15 * time.Minute, 42 MerkleMovementTrigger: keybase1.Seqno(300000), 43 NumPreProbes: 4, 44 NumPostProbes: 4, 45 Parallelism: 3, 46 LRUSize: 500, 47 } 48 49 var devParams = libkb.TeamAuditParams{ 50 RootFreshness: 10 * time.Minute, 51 MerkleMovementTrigger: keybase1.Seqno(10000), 52 NumPreProbes: 3, 53 NumPostProbes: 3, 54 Parallelism: 3, 55 LRUSize: 500, 56 } 57 58 // getAuditParams will return parameters based on the platform. On mobile, 59 // we're going to be performing a smaller audit, and therefore have a smaller 60 // security margin (1-2^10). But it's worth it given the bandwidth and CPU 61 // constraints. 62 func getAuditParams(m libkb.MetaContext) libkb.TeamAuditParams { 63 if m.G().Env.Test.TeamAuditParams != nil { 64 return *m.G().Env.Test.TeamAuditParams 65 } 66 if m.G().Env.GetRunMode() == libkb.DevelRunMode { 67 return devParams 68 } 69 if libkb.IsMobilePlatform() { 70 if m.G().MobileNetState.State().IsLimited() { 71 return mobileParamsNoWifi 72 } 73 return mobileParamsWifi 74 } 75 return desktopParams 76 } 77 78 type dummyAuditor struct{} 79 80 func (d dummyAuditor) AuditTeam(m libkb.MetaContext, id keybase1.TeamID, isPublic bool, 81 headMerkleSeqno keybase1.Seqno, chain map[keybase1.Seqno]keybase1.LinkID, hiddenChain map[keybase1.Seqno]keybase1.LinkID, 82 maxSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno, lastMerkleRoot *libkb.MerkleRoot, auditMode keybase1.AuditMode) error { 83 return nil 84 } 85 86 type Auditor struct { 87 88 // single-flight lock on TeamID 89 locktab *libkb.LockTable 90 91 // Map of TeamID -> AuditHistory 92 // The LRU is protected by a mutex, because it's swapped out on logout. 93 lruMutex sync.Mutex 94 lru *lru.Cache 95 } 96 97 // NewAuditor makes a new auditor 98 func NewAuditor(g *libkb.GlobalContext) *Auditor { 99 ret := &Auditor{ 100 locktab: libkb.NewLockTable(), 101 } 102 ret.newLRU(libkb.NewMetaContextBackground(g)) 103 return ret 104 } 105 106 // NewAuditorAndInstall makes a new Auditor and dangles it 107 // off of the given GlobalContext. 108 func NewAuditorAndInstall(g *libkb.GlobalContext) { 109 if g.GetEnv().GetDisableTeamAuditor() { 110 g.Log.CDebugf(context.TODO(), "Using dummy auditor, audit disabled") 111 g.SetTeamAuditor(dummyAuditor{}) 112 } else { 113 a := NewAuditor(g) 114 g.SetTeamAuditor(a) 115 g.AddLogoutHook(a, "team auditor") 116 g.AddDbNukeHook(a, "team auditor") 117 } 118 } 119 120 // AuditTeam runs an audit on the links of the given team chain (or team chain suffix). 121 // The security factor of the audit is a function of the hardcoded parameters above, 122 // and the amount of time since the last audit. This method should use some sort of 123 // long-lived cache (via local DB) so that previous audits can be combined with the 124 // current one. headMerkleSeqno is is the Merkle Root claimed in the head of the team. 125 // maxSeqno is the maximum seqno of the chainLinks passed; that is, the highest 126 // Seqno for which chain[s] is defined. 127 func (a *Auditor) AuditTeam(m libkb.MetaContext, id keybase1.TeamID, isPublic bool, headMerkleSeqno keybase1.Seqno, chain map[keybase1.Seqno]keybase1.LinkID, 128 hiddenChain map[keybase1.Seqno]keybase1.LinkID, maxSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno, 129 lastMerkleRoot *libkb.MerkleRoot, auditMode keybase1.AuditMode) (err error) { 130 131 m = m.WithLogTag("AUDIT") 132 defer m.Trace(fmt.Sprintf("Auditor#AuditTeam(%+v)", id), &err)() 133 defer m.PerfTrace(fmt.Sprintf("Auditor#AuditTeam(%+v)", id), &err)() 134 start := time.Now() 135 defer func() { 136 var message string 137 if err == nil { 138 message = fmt.Sprintf("Audited team %s", id) 139 } else { 140 message = fmt.Sprintf("Failed auditing %s", id) 141 } 142 m.G().RuntimeStats.PushPerfEvent(keybase1.PerfEvent{ 143 EventType: keybase1.PerfEventType_TEAMAUDIT, 144 Message: message, 145 Ctime: keybase1.ToTime(start), 146 }) 147 }() 148 149 if id.IsPublic() != isPublic { 150 return NewBadPublicError(id, isPublic) 151 } 152 153 // Single-flight lock by team ID. 154 lock := a.locktab.AcquireOnName(m.Ctx(), m.G(), id.String()) 155 defer lock.Release(m.Ctx()) 156 157 err = a.auditLocked(m, id, headMerkleSeqno, chain, hiddenChain, maxSeqno, maxHiddenSeqno, lastMerkleRoot, auditMode) 158 if hidden.ShouldClearSupportFlagOnError(err) { 159 m.Debug("Clearing support hidden chain flag for team %s because of error %v in Auditor", id, err) 160 m.G().GetHiddenTeamChainManager().ClearSupportFlagIfFalse(m, id) 161 } 162 return err 163 } 164 165 func (a *Auditor) getLRU() *lru.Cache { 166 a.lruMutex.Lock() 167 defer a.lruMutex.Unlock() 168 return a.lru 169 } 170 171 func (a *Auditor) getFromLRU(m libkb.MetaContext, id keybase1.TeamID, lru *lru.Cache) *keybase1.AuditHistory { 172 tmp, found := lru.Get(id) 173 if !found { 174 return nil 175 } 176 ret, ok := tmp.(*keybase1.AuditHistory) 177 if !ok { 178 m.Error("Bad type assertion in Auditor#getFromLRU") 179 return nil 180 } 181 return ret 182 } 183 184 func (a *Auditor) getFromDisk(m libkb.MetaContext, id keybase1.TeamID) (*keybase1.AuditHistory, error) { 185 var ret keybase1.AuditHistory 186 found, err := m.G().LocalDb.GetInto(&ret, libkb.DbKey{Typ: libkb.DBTeamAuditor, Key: string(id)}) 187 if err != nil { 188 return nil, err 189 } 190 if !found { 191 return nil, nil 192 } 193 if ret.Version != AuditCurrentVersion { 194 m.Debug("Discarding audit at version %d (we are supporting %d)", ret.Version, AuditCurrentVersion) 195 return nil, nil 196 } 197 return &ret, nil 198 } 199 200 func (a *Auditor) getFromCache(m libkb.MetaContext, id keybase1.TeamID, lru *lru.Cache) (*keybase1.AuditHistory, error) { 201 202 ret := a.getFromLRU(m, id, lru) 203 if ret != nil { 204 return ret, nil 205 } 206 ret, err := a.getFromDisk(m, id) 207 return ret, err 208 } 209 210 func (a *Auditor) putToCache(m libkb.MetaContext, id keybase1.TeamID, lru *lru.Cache, h *keybase1.AuditHistory) (err error) { 211 lru.Add(id, h) 212 err = m.G().LocalDb.PutObj(libkb.DbKey{Typ: libkb.DBTeamAuditor, Key: string(id)}, nil, *h) 213 return err 214 } 215 216 func (a *Auditor) checkRecent(m libkb.MetaContext, history *keybase1.AuditHistory, root *libkb.MerkleRoot) bool { 217 if root == nil { 218 m.Debug("no recent known merkle root in checkRecent") 219 return false 220 } 221 last := lastAudit(history) 222 if last == nil { 223 m.Debug("no recent audits") 224 return false 225 } 226 diff := *root.Seqno() - last.MaxMerkleSeqno 227 if diff >= getAuditParams(m).MerkleMovementTrigger { 228 m.Debug("previous merkle audit was %v ago", diff) 229 return false 230 } 231 return true 232 } 233 234 func lastAudit(h *keybase1.AuditHistory) *keybase1.Audit { 235 if h == nil { 236 return nil 237 } 238 if len(h.Audits) == 0 { 239 return nil 240 } 241 ret := h.Audits[len(h.Audits)-1] 242 return &ret 243 } 244 245 func maxMerkleProbeInAuditHistory(h *keybase1.AuditHistory) keybase1.Seqno { 246 if h == nil { 247 return keybase1.Seqno(0) 248 } 249 250 // The audits at the back of the list are the most recent, but some 251 // of these audits might have been "nil" audits that didn't actually probe 252 // any new paths (see comment in doPostProbes about how you can short-circuit 253 // doing probes). So keep going backwards until we hit the first non-0 254 // maxMerkleProbe. Remember, maxMerkleProbe is the maximum merkle seqno 255 // probed in the last audit. 256 for i := len(h.Audits) - 1; i >= 0; i-- { 257 if mmp := h.Audits[i].MaxMerkleProbe; mmp >= keybase1.Seqno(0) { 258 return mmp 259 } 260 } 261 return keybase1.Seqno(0) 262 } 263 264 func makeHistory(history *keybase1.AuditHistory, id keybase1.TeamID) *keybase1.AuditHistory { 265 if history == nil { 266 return &keybase1.AuditHistory{ 267 ID: id, 268 Public: id.IsPublic(), 269 Version: AuditCurrentVersion, 270 PreProbes: make(map[keybase1.Seqno]keybase1.Probe), 271 PostProbes: make(map[keybase1.Seqno]keybase1.Probe), 272 Tails: make(map[keybase1.Seqno]keybase1.LinkID), 273 HiddenTails: make(map[keybase1.Seqno]keybase1.LinkID), 274 } 275 } 276 ret := history.DeepCopy() 277 return &ret 278 } 279 280 // doPostProbes probes the sequence timeline _after_ the team was created. 281 func (a *Auditor) doPostProbes(m libkb.MetaContext, history *keybase1.AuditHistory, probeID int, headMerkleSeqno keybase1.Seqno, latestMerkleSeqno keybase1.Seqno, chain map[keybase1.Seqno]keybase1.LinkID, 282 hiddenChain map[keybase1.Seqno]keybase1.LinkID, maxChainSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno, auditMode keybase1.AuditMode) (numProbes int, maxMerkleProbe keybase1.Seqno, probeTuples []probeTuple, err error) { 283 defer m.Trace("Auditor#doPostProbes", &err)() 284 285 var low keybase1.Seqno 286 lastMaxMerkleProbe := maxMerkleProbeInAuditHistory(history) 287 var prev *probeTuple 288 if lastMaxMerkleProbe == keybase1.Seqno(0) { 289 low = headMerkleSeqno 290 } else { 291 low = lastMaxMerkleProbe 292 probe, ok := history.PostProbes[lastMaxMerkleProbe] 293 if !ok { 294 // This might happen if leveldb was corrupted, or if we had a bug of some sort. 295 // But it makes sense not error out of the audit process. 296 m.Warning("previous audit pointed to a bogus probe (seqno=%d); starting from scratch at head Merkle seqno=%d", lastMaxMerkleProbe, headMerkleSeqno) 297 low = headMerkleSeqno 298 } else { 299 prev = &probeTuple{ 300 merkle: lastMaxMerkleProbe, 301 team: probe.TeamSeqno, 302 // leave linkID nil, it's not needed... 303 } 304 } 305 } 306 307 // Probe only after the checkpoint. Merkle roots before the checkpoint aren't examined and are 308 // trusted to be legit, being buried far enough in the past. Therefore probing is not necessary. 309 first := m.G().GetMerkleClient().FirstExaminableHistoricalRoot(m) 310 311 if first == nil { 312 return 0, keybase1.Seqno(0), nil, NewAuditError("cannot find a first modern merkle sequence") 313 } 314 315 if low < *first { 316 m.Debug("bumping low sequence number to last merkle checkpoint: %s", *first) 317 low = *first 318 } 319 320 probeTuples, err = a.computeProbes(m, history.ID, history.PostProbes, probeID, low, latestMerkleSeqno, 0, getAuditParams(m).NumPostProbes, history.PostProbesToRetry) 321 if err != nil { 322 return 0, keybase1.Seqno(0), probeTuples, err 323 } 324 if len(probeTuples) == 0 { 325 m.Debug("No probe tuples, so bailing") 326 return 0, keybase1.Seqno(0), probeTuples, nil 327 } 328 329 ret := 0 330 331 for _, tuple := range probeTuples { 332 m.Debug("postProbe: checking probe at %+v", tuple) 333 334 // Note that we might see tuple.team == 0 in a legit case. There is a race here. If seqno=1 335 // of team foo was made when the last merkle seqno was 2000, let's say, then for merkle seqnos 336 // 2000-2001, the team foo might not be in the tree. So we'd expect the tuple.team to be 0 337 // in that (unlikely) case. So we don't check the validity of the linkID in that case 338 // (since it doesn't exist). However, we still do check that tuple.team's are non-decreasing, 339 // so this 0 value is checked below (see comment). 340 if tuple.team > keybase1.Seqno(0) { 341 expectedLinkID, ok := chain[tuple.team] 342 if !ok { 343 return 0, keybase1.Seqno(0), probeTuples, NewAuditError("team chain rollback: merkle seqno %v referred to team chain seqno %v which is not part of our chain (which at merkle seqno %v ends at %v)", tuple.merkle, tuple.team, latestMerkleSeqno, maxChainSeqno) 344 } 345 if !expectedLinkID.Eq(tuple.linkID) { 346 return 0, keybase1.Seqno(0), probeTuples, NewAuditError("team chain linkID mismatch at %d: wanted %s but got %s via merkle seqno %d", tuple.team, expectedLinkID, tuple.linkID, tuple.merkle) 347 } 348 } 349 350 ret++ 351 352 // This condition is the key ordering condition. It is still checked in the case of the race 353 // condition at tuple.team==0 mentioned just above. 354 if prev != nil && prev.team > tuple.team { 355 return 0, keybase1.Seqno(0), probeTuples, NewAuditError("team chain unexpected jump: %d > %d via merkle seqno %d", prev.team, tuple.team, tuple.merkle) 356 } 357 if tuple.merkle > maxMerkleProbe { 358 maxMerkleProbe = tuple.merkle 359 } 360 prev = &tuple 361 } 362 return ret, maxMerkleProbe, probeTuples, nil 363 } 364 365 // doPreProbes probabilistically checks that no team occupied the slot before the team 366 // in question was created. It selects probes from before the team was created. Each 367 // probed leaf must not be occupied. 368 func (a *Auditor) doPreProbes(m libkb.MetaContext, history *keybase1.AuditHistory, probeID int, headMerkleSeqno keybase1.Seqno, auditMode keybase1.AuditMode) (numProbes int, probeTuples []probeTuple, err error) { 369 defer m.Trace("Auditor#doPreProbes", &err)() 370 371 first := m.G().GetMerkleClient().FirstExaminableHistoricalRoot(m) 372 if first == nil { 373 return 0, nil, NewAuditError("cannot find a first modern merkle sequence") 374 } 375 376 probeTuples, err = a.computeProbes(m, history.ID, history.PreProbes, probeID, *first, headMerkleSeqno, len(history.PreProbes), getAuditParams(m).NumPreProbes, history.PreProbesToRetry) 377 if err != nil { 378 return 0, probeTuples, err 379 } 380 if len(probeTuples) == 0 { 381 m.Debug("No probe pairs, so bailing") 382 return 0, nil, nil 383 } 384 for _, tuple := range probeTuples { 385 m.Debug("preProbe: checking probe at merkle %d", tuple.merkle) 386 if tuple.team != keybase1.Seqno(0) || !tuple.linkID.IsNil() { 387 return 0, probeTuples, NewAuditError("merkle root should not have had a leaf for team %v: got %s/%d at merkle seqno %v", 388 history.ID, tuple.linkID, tuple.team, tuple.merkle) 389 } 390 } 391 return len(probeTuples), probeTuples, nil 392 } 393 394 // randSeqno picks a random number in [lo,hi] inclusively. 395 func randSeqno(m libkb.MetaContext, lo keybase1.Seqno, hi keybase1.Seqno) (keybase1.Seqno, error) { 396 s, err := m.G().GetRandom().RndRange(int64(lo), int64(hi)) 397 return keybase1.Seqno(s), err 398 } 399 400 type probeTuple struct { 401 merkle keybase1.Seqno 402 team keybase1.Seqno 403 linkID keybase1.LinkID 404 } 405 406 func (a *Auditor) computeProbes(m libkb.MetaContext, teamID keybase1.TeamID, probes map[keybase1.Seqno]keybase1.Probe, probeID int, left keybase1.Seqno, right keybase1.Seqno, probesInRange int, n int, probesToRetry []keybase1.Seqno) (ret []probeTuple, err error) { 407 ret, err = a.scheduleProbes(m, probes, probeID, left, right, probesInRange, n, probesToRetry) 408 if err != nil { 409 return nil, err 410 } 411 err = a.lookupProbes(m, teamID, ret) 412 if err != nil { 413 // In case of error, we still return the probes we selected so they can 414 // be retried in the next audit 415 return ret, err 416 } 417 return ret, err 418 } 419 420 func (a *Auditor) scheduleProbes(m libkb.MetaContext, previousProbes map[keybase1.Seqno]keybase1.Probe, probeID int, left keybase1.Seqno, right keybase1.Seqno, probesInRange int, n int, probesToRetry []keybase1.Seqno) (ret []probeTuple, err error) { 421 defer m.Trace(fmt.Sprintf("Auditor#scheduleProbes(left=%d,right=%d)", left, right), &err)() 422 if probesInRange > n { 423 m.Debug("no more probes needed; did %d, wanted %d", probesInRange, n) 424 return nil, nil 425 } 426 rng := right - left + 1 427 if int(rng) <= probesInRange { 428 m.Debug("no more probes needed; range was only %d, and we did %d", rng, probesInRange) 429 return nil, nil 430 } 431 currentProbes := make(map[keybase1.Seqno]bool) 432 for _, s := range probesToRetry { 433 ret = append(ret, probeTuple{merkle: s}) 434 currentProbes[s] = true 435 } 436 currentProbesWanted := n - len(probesToRetry) 437 for i := 0; i < currentProbesWanted; i++ { 438 x, err := randSeqno(m, left, right) 439 if err != nil { 440 return nil, err 441 } 442 if _, found := previousProbes[x]; found { 443 continue 444 } 445 if currentProbes[x] { 446 continue 447 } 448 ret = append(ret, probeTuple{merkle: x}) 449 currentProbes[x] = true 450 } 451 sort.SliceStable(ret, func(i, j int) bool { 452 return ret[i].merkle < ret[j].merkle 453 }) 454 m.Debug("scheduled probes: %+v", ret) 455 return ret, nil 456 } 457 458 func (a *Auditor) lookupProbe(m libkb.MetaContext, teamID keybase1.TeamID, probe *probeTuple) (err error) { 459 defer m.Trace(fmt.Sprintf("Auditor#lookupProbe(%v,%v)", teamID, *probe), &err)() 460 leaf, _, _, err := m.G().GetMerkleClient().LookupLeafAtSeqnoForAudit(m, teamID.AsUserOrTeam(), probe.merkle, hidden.ProcessHiddenResponseFunc) 461 if err != nil { 462 return err 463 } 464 if leaf == nil || leaf.Private == nil { 465 m.Debug("nil leaf at %v/%v", teamID, probe.merkle) 466 return nil 467 } 468 probe.team = leaf.Private.Seqno 469 if leaf.Private.LinkID != nil { 470 probe.linkID = leaf.Private.LinkID.Export() 471 } 472 return nil 473 } 474 475 func (a *Auditor) lookupProbes(m libkb.MetaContext, teamID keybase1.TeamID, tuples []probeTuple) (err error) { 476 pipeliner := pipeliner.NewPipeliner(getAuditParams(m).Parallelism) 477 for i := range tuples { 478 if err = pipeliner.WaitForRoom(m.Ctx()); err != nil { 479 return err 480 } 481 go func(probe *probeTuple) { 482 err := a.lookupProbe(m, teamID, probe) 483 pipeliner.CompleteOne(err) 484 }(&tuples[i]) 485 } 486 err = pipeliner.Flush(m.Ctx()) 487 return err 488 } 489 490 func (a *Auditor) checkTail(m libkb.MetaContext, history *keybase1.AuditHistory, lastAudit keybase1.Audit, chain map[keybase1.Seqno]keybase1.LinkID, hiddenChain map[keybase1.Seqno]keybase1.LinkID, maxChainSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno, auditMode keybase1.AuditMode) (err error) { 491 link, ok := chain[lastAudit.MaxChainSeqno] 492 if !ok || link.IsNil() { 493 return NewAuditError("last audit ended at %d, but wasn't found in new chain", lastAudit.MaxChainSeqno) 494 } 495 tail, ok := history.Tails[lastAudit.MaxChainSeqno] 496 if !ok || tail.IsNil() { 497 return NewAuditError("previous chain tail at %d did not have a linkID", lastAudit.MaxChainSeqno) 498 } 499 if !link.Eq(tail) { 500 return NewAuditError("bad chain tail mismatch (%s != %s) at chain link %d", link, tail, lastAudit.MaxChainSeqno) 501 } 502 link, ok = chain[maxChainSeqno] 503 if !ok || link.IsNil() { 504 return NewAuditError("given chain didn't have a link at %d, but it was expected", maxChainSeqno) 505 } 506 507 if auditMode == keybase1.AuditMode_STANDARD_NO_HIDDEN { 508 m.Debug("Skipping hidden tail check as the auditMode does not require it.") 509 return nil 510 } 511 if lastAudit.MaxHiddenSeqno == 0 { 512 m.Debug("Skipping hidden tail check as there was none in the last audit.") 513 return nil 514 } 515 link, ok = hiddenChain[lastAudit.MaxHiddenSeqno] 516 if !ok || link.IsNil() { 517 return NewAuditError("last audit ended at %d, but wasn't found in new chain", lastAudit.MaxHiddenSeqno) 518 } 519 tail, ok = history.HiddenTails[lastAudit.MaxHiddenSeqno] 520 if !ok || tail.IsNil() { 521 return NewAuditError("previous hidden chain tail at %d did not have a linkID", lastAudit.MaxHiddenSeqno) 522 } 523 if !link.Eq(tail) { 524 return NewAuditError("hidden chain tail mismatch (%s != %s) at chain link %d", link, tail, lastAudit.MaxHiddenSeqno) 525 } 526 return nil 527 } 528 529 func (a *Auditor) skipAuditSinceJustCreated(m libkb.MetaContext, id keybase1.TeamID, headMerkleSeqno keybase1.Seqno) (err error) { 530 now := m.G().Clock().Now() 531 until := now.Add(getAuditParams(m).RootFreshness) 532 m.Debug("team (%s) was just created; skipping the audit until %v", id, until) 533 history := makeHistory(nil, id) 534 history.SkipUntil = keybase1.ToTime(until) 535 history.PriorMerkleSeqno = headMerkleSeqno 536 lru := a.getLRU() 537 return a.putToCache(m, id, lru, history) 538 } 539 540 func (a *Auditor) holdOffSinceJustCreated(m libkb.MetaContext, history *keybase1.AuditHistory) (res bool, err error) { 541 if history == nil || history.SkipUntil == keybase1.Time(0) { 542 return false, nil 543 } 544 now := m.G().Clock().Now() 545 until := keybase1.FromTime(history.SkipUntil) 546 if now.After(until) { 547 return false, nil 548 } 549 m.Debug("holding off on subsequent audits since the team (%s) was just created (until %v)", history.ID, until) 550 return true, nil 551 } 552 553 func (a *Auditor) auditLocked(m libkb.MetaContext, id keybase1.TeamID, headMerkleSeqno keybase1.Seqno, chain map[keybase1.Seqno]keybase1.LinkID, hiddenChain map[keybase1.Seqno]keybase1.LinkID, maxChainSeqno keybase1.Seqno, maxHiddenSeqno keybase1.Seqno, lastMerkleRoot *libkb.MerkleRoot, auditMode keybase1.AuditMode) (err error) { 554 555 defer m.Trace(fmt.Sprintf("Auditor#auditLocked(%v,%s)", id, auditMode), &err)() 556 557 lru := a.getLRU() 558 559 switch auditMode { 560 case keybase1.AuditMode_JUST_CREATED: 561 return a.skipAuditSinceJustCreated(m, id, headMerkleSeqno) 562 case keybase1.AuditMode_SKIP: 563 m.Debug("skipping audit due to AuditModeSkip flag") 564 return nil 565 } 566 567 history, err := a.getFromCache(m, id, lru) 568 if err != nil { 569 return err 570 } 571 572 holdOff, err := a.holdOffSinceJustCreated(m, history) 573 if err != nil { 574 return err 575 } 576 if holdOff { 577 return nil 578 } 579 580 if lastMerkleRoot.Seqno() == nil { 581 return NewAuditError("logic error: nil lastMerkleRoot.Seqno()") 582 } 583 584 last := lastAudit(history) 585 586 // It's possible that we're bouncing back and forth between the Fast and Slow 587 // loader. Therefore, it might have been that we previous audited up to chainlink 588 // 20, and now we're seeing an audit only for link 18 (if one of them was stale). 589 // That's fine, just make sure to short-circuit as long as we've audited past 590 // the given maxChainSeqno. 591 if last != nil && last.MaxChainSeqno >= maxChainSeqno { 592 m.Debug("Short-circuit audit, since there is no new data (@%v <= %v)", maxChainSeqno, last.MaxChainSeqno) 593 return nil 594 } 595 596 // Check that the last time we ran an audit is a subchain of the new links 597 // we got down. It suffices to check that the last link in that chain 598 // appears in the given chain with the right link ID. 599 if last != nil { 600 err = a.checkTail(m, history, *last, chain, hiddenChain, maxChainSeqno, maxHiddenSeqno, auditMode) 601 if err != nil { 602 return err 603 } 604 } 605 606 if history != nil && a.checkRecent(m, history, lastMerkleRoot) { 607 m.Debug("cached audit was recent; short-circuiting") 608 return nil 609 } 610 611 history = makeHistory(history, id) 612 613 newAuditIndex := len(history.Audits) 614 615 var numPreProbes, numPostProbes int 616 617 numPreProbes, preProbeTuples, err := a.doPreProbes(m, history, newAuditIndex, headMerkleSeqno, auditMode) 618 if err != nil { 619 history.PreProbesToRetry = getMerkleSeqnosFromProbes(preProbeTuples) 620 err2 := a.putToCache(m, id, lru, history) 621 if err2 != nil { 622 return NewAuditError("Error during doPreProbes (%v) followed by an error in storing the audit state (%v)", err.Error(), err2.Error()) 623 } 624 return err 625 } 626 627 numPostProbes, maxMerkleProbe, postProbeTuples, err := a.doPostProbes(m, history, newAuditIndex, headMerkleSeqno, *(lastMerkleRoot.Seqno()), chain, hiddenChain, maxChainSeqno, maxHiddenSeqno, auditMode) 628 if err != nil { 629 history.PostProbesToRetry = getMerkleSeqnosFromProbes(postProbeTuples) 630 err2 := a.putToCache(m, id, lru, history) 631 if err2 != nil { 632 return NewAuditError("Error during doPostProbes (%v) followed by an error in storing the audit state (%v)", err.Error(), err2.Error()) 633 } 634 return err 635 } 636 637 for _, tuple := range postProbeTuples { 638 history.PostProbes[tuple.merkle] = keybase1.Probe{Index: newAuditIndex, TeamSeqno: tuple.team} 639 } 640 641 if numPostProbes+numPreProbes == 0 { 642 m.Debug("No new probes, not writing to cache") 643 return nil 644 } 645 646 m.Debug("Probes completed; numPre=%d, numPost=%d", numPreProbes, numPostProbes) 647 648 audit := keybase1.Audit{ 649 Time: keybase1.ToTime(m.G().Clock().Now()), 650 MaxMerkleSeqno: *lastMerkleRoot.Seqno(), 651 MaxChainSeqno: maxChainSeqno, 652 MaxHiddenSeqno: maxHiddenSeqno, 653 // Note that the MaxMerkleProbe can be 0 in the case that there were 654 // pre-probes, but no post-probes. 655 MaxMerkleProbe: maxMerkleProbe, 656 } 657 history.Audits = append(history.Audits, audit) 658 history.PriorMerkleSeqno = headMerkleSeqno 659 history.Tails[maxChainSeqno] = chain[maxChainSeqno] 660 if maxHiddenSeqno != 0 { 661 if hiddenChain[maxHiddenSeqno].IsNil() { 662 m.Debug("hiddenChain on audit for team %s: %+v", id, hiddenChain) 663 return NewAuditError("Logic error while auditing %s: trying to save an audit with maxHiddenSeqno=%v, but the hiddenChain does not have the corresponding link.", id, maxHiddenSeqno) 664 } 665 history.HiddenTails[maxHiddenSeqno] = hiddenChain[maxHiddenSeqno] 666 } 667 668 // if the audit was successful, there will be nothing to retry next time 669 history.PreProbesToRetry = nil 670 history.PostProbesToRetry = nil 671 672 err = a.putToCache(m, id, lru, history) 673 if err != nil { 674 return err 675 } 676 return nil 677 } 678 679 func getMerkleSeqnosFromProbes(probeTuples []probeTuple) (seqnos []keybase1.Seqno) { 680 for _, tuple := range probeTuples { 681 seqnos = append(seqnos, tuple.merkle) 682 } 683 return seqnos 684 } 685 686 func (a *Auditor) newLRU(m libkb.MetaContext) { 687 688 a.lruMutex.Lock() 689 defer a.lruMutex.Unlock() 690 691 if a.lru != nil { 692 a.lru.Purge() 693 } 694 695 lru, err := lru.New(getAuditParams(m).LRUSize) 696 if err != nil { 697 panic(err) 698 } 699 a.lru = lru 700 } 701 702 func (a *Auditor) OnLogout(mctx libkb.MetaContext) error { 703 a.newLRU(mctx) 704 return nil 705 } 706 707 func (a *Auditor) OnDbNuke(mctx libkb.MetaContext) error { 708 a.newLRU(mctx) 709 return nil 710 }